gateway = $this->resolveGateway(); } /** * Send an SMS and return a normalized response array. * * @return array {success, status, senttime, msgId, smsCost, error, raw} */ public function sendSms(string $message, string $receiver): array { return $this->gateway->send($message, $receiver); } /** * Send an SMS, save the record to sms_msg, and return the saved Sms model. * * @param string $message The SMS text * @param string $receiver Destination phone number * @param array $extra Extra fillable attributes (lead_id, contact_id, campaign_id, account_id, company_id, notes, sms_sender_id) * @return Sms */ public function sendAndSave(string $message, string $receiver, array $extra = []): Sms { $msgLength = mb_strlen($message, 'UTF-8'); $smstype = preg_match('/\p{Arabic}/u', $message) ? 'unicode' : 'English'; $result = $this->sendSms($message, $receiver); $record = new Sms(); $record->msg = $message; $record->receiver = $receiver; $record->smsLength = $msgLength; $record->smstype = $smstype; $record->msgId = $result['msgId'] ?? null; $record->senttime = $result['senttime'] ?? Carbon::now(); $record->smsCost = $result['smsCost'] ?? null; $record->status = $result['status'] ?? ($result['success'] ? 'Sent' : 'Failed'); foreach (['lead_id', 'contact_id', 'campaign_id', 'account_id', 'company_id', 'notes', 'sms_sender_id'] as $key) { if (array_key_exists($key, $extra)) { $record->{$key} = $extra[$key]; } } $record->save(); return $record; } /** * Send the configured missed-call SMS to the caller associated with a linkedid. */ public function sendMissedCallSms(string $linkedid): ?Sms { $report = InboundReport::where('linkedid', $linkedid) ->where('call_status', 'missed') ->first(); if (!$report || !$report->call_time || empty($report->caller_number)) { return null; } $missedAt = Carbon::parse($report->call_time); if (!$missedAt->between(now()->startOfDay(), now(), true)) { return null; } $message = trim((string) CompanySettings::get(SettingsHelper::S_MISSED_CALLS_SMS_MESSAGE)); if ($message === '') { return null; } $receiver = PhoneNormalizer::toE164( trim($report->caller_number), config('sms.country_code', '20') ); $sentDate = now()->toDateString(); try { DB::table('missed_call_sms_logs')->insert([ 'receiver' => $receiver, 'sent_date' => $sentDate, 'linkedid' => $linkedid, 'status' => 'pending', 'created_at' => now(), 'updated_at' => now(), ]); } catch (QueryException $e) { $alreadyClaimed = DB::table('missed_call_sms_logs') ->where('receiver', $receiver) ->where('sent_date', $sentDate) ->exists(); if ($alreadyClaimed) { Log::info("[MissedCallsService] Skipping duplicate daily SMS for {$linkedid}"); return null; } throw $e; } try { $sms = $this->sendAndSave($message, $receiver); DB::table('missed_call_sms_logs') ->where('receiver', $receiver) ->where('sent_date', $sentDate) ->update([ 'sms_id' => $sms->id, 'status' => $sms->status, 'updated_at' => now(), ]); if ($sms->status === 'Failed') { Log::warning("[MissedCallsService] Missed-call SMS failed for {$linkedid}"); } return $sms; } catch (\Throwable $e) { DB::table('missed_call_sms_logs') ->where('receiver', $receiver) ->where('sent_date', $sentDate) ->update(['status' => 'Failed', 'updated_at' => now()]); throw $e; } } /** * Resolve the active SMS gateway based on the SMS_GATEWAY_TYPE env variable. * Defaults to the Egyptian provider for backward compatibility. */ private function resolveGateway(): SmsGatewayInterface { $type = strtolower(config('sms.gateway_type', 'egyptian')); return match ($type) { 'telnyx' => new TelnyxSmsGateway(), default => new EgyptianSmsGateway(), }; } }