From 1e599b917bc59086ad691d893ae34639fbba5ead Mon Sep 17 00:00:00 2001 From: mehrab esmailnia Date: Mon, 21 Sep 2026 02:47:12 +0330 Subject: [PATCH] feat: implement SMS notification service and integrate with existing services --- app/Jobs/SendPatternSms.php | 29 ++++++++++ app/Services/Auth/OtpService.php | 54 +++++-------------- app/Services/Document/CaseService.php | 8 ++- app/Services/Document/FastRequestService.php | 8 ++- app/Services/Invoice/InvoiceService.php | 10 ++++ app/Services/Sms/IranPayamakService.php | 39 ++++++++++++++ app/Services/Sms/SmsNotificationService.php | 42 +++++++++++++++ config/services.php | 10 ++-- .../js/Admin/Pages/WithdrawRequests/index.vue | 1 + 9 files changed, 153 insertions(+), 48 deletions(-) create mode 100644 app/Jobs/SendPatternSms.php create mode 100644 app/Services/Sms/IranPayamakService.php create mode 100644 app/Services/Sms/SmsNotificationService.php diff --git a/app/Jobs/SendPatternSms.php b/app/Jobs/SendPatternSms.php new file mode 100644 index 0000000..9829e3d --- /dev/null +++ b/app/Jobs/SendPatternSms.php @@ -0,0 +1,29 @@ +afterCommit(); + } + + public function handle(IranPayamakService $iranPayamakService): void + { + $iranPayamakService->sendPattern($this->phone, $this->patternKey, $this->attributes); + } +} diff --git a/app/Services/Auth/OtpService.php b/app/Services/Auth/OtpService.php index 20b8db9..0239705 100644 --- a/app/Services/Auth/OtpService.php +++ b/app/Services/Auth/OtpService.php @@ -3,10 +3,17 @@ namespace App\Services\Auth; use App\Models\OtpCode; -use Illuminate\Support\Facades\Http; +use App\Services\Sms\IranPayamakService; class OtpService { + protected IranPayamakService $iranPayamakService; + + public function __construct(IranPayamakService $iranPayamakService) + { + $this->iranPayamakService = $iranPayamakService; + } + public function generateCode(): string { return (string) random_int(1000, 9999); @@ -45,51 +52,14 @@ class OtpService ->exists(); } - private function sendSms(string $phone, string $code): array + private function sendSms(string $phone, string $code): void { try { - $formattedRecipient = $this->formatPhoneForSms($phone); - - $response = Http::withHeaders([ - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - 'Api-Key' => config('services.iranpayamak.api_key'), - ]) - ->post(config('services.iranpayamak.api_url'), [ - 'code' => config('services.iranpayamak.patterns.verify_code'), - 'attributes' => [ - 'otp' => $code, - 'status' => 'بازنشانی رمز عبور' - ], - 'recipient' => $formattedRecipient, - 'line_number' => config('services.iranpayamak.line_number'), - 'number_format' => 'english', + $this->iranPayamakService->sendPattern($phone, 'verify_code', [ + 'otp' => $code, + 'status' => 'بازنشانی رمز عبور', ]); - - if ($response->successful()) { - return $response->json(); - } else { - return [ - 'status' => 'error', - 'message' => 'SMS sending failed: ' . $response->status(), - 'data' => $response->body() - ]; - } } catch (\Exception $e) { - return [ - 'status' => 'error', - 'message' => 'SMS sending exception: ' . $e->getMessage(), - 'data' => null - ]; } } - - - private function formatPhoneForSms(string $phone): string - { - if (str_starts_with($phone, '+98')) { - return '0' . substr($phone, 3); - } - return $phone; - } } diff --git a/app/Services/Document/CaseService.php b/app/Services/Document/CaseService.php index 961c61f..c9d95b3 100644 --- a/app/Services/Document/CaseService.php +++ b/app/Services/Document/CaseService.php @@ -14,6 +14,7 @@ use App\Models\Report; use App\Models\User; use App\Services\FileStorageService; use App\Services\Shift\ShiftScheduleService; +use App\Services\Sms\SmsNotificationService; use App\Services\Wallet\WalletService; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; @@ -27,11 +28,14 @@ class CaseService protected ShiftScheduleService $shiftScheduleService; - public function __construct(FileStorageService $fileStorageService, WalletService $walletService, ShiftScheduleService $shiftScheduleService) + protected SmsNotificationService $smsNotificationService; + + public function __construct(FileStorageService $fileStorageService, WalletService $walletService, ShiftScheduleService $shiftScheduleService, SmsNotificationService $smsNotificationService) { $this->fileStorageService = $fileStorageService; $this->walletService = $walletService; $this->shiftScheduleService = $shiftScheduleService; + $this->smsNotificationService = $smsNotificationService; } public function pendingForUser(User $user): Builder @@ -95,6 +99,8 @@ class CaseService 'finalized_by' => $doctor->id, ]); + $this->smsNotificationService->reportReady($document); + return $report; }); } diff --git a/app/Services/Document/FastRequestService.php b/app/Services/Document/FastRequestService.php index 0c324b4..ea6eca6 100644 --- a/app/Services/Document/FastRequestService.php +++ b/app/Services/Document/FastRequestService.php @@ -12,6 +12,7 @@ use App\Models\User; use App\Services\FileStorageService; use App\Services\Invoice\InvoiceService; use App\Services\Shift\ShiftScheduleService; +use App\Services\Sms\SmsNotificationService; use Illuminate\Support\Facades\DB; class FastRequestService @@ -22,11 +23,14 @@ class FastRequestService protected InvoiceService $invoiceService; - public function __construct(FileStorageService $fileStorageService, ShiftScheduleService $shiftScheduleService, InvoiceService $invoiceService) + protected SmsNotificationService $smsNotificationService; + + public function __construct(FileStorageService $fileStorageService, ShiftScheduleService $shiftScheduleService, InvoiceService $invoiceService, SmsNotificationService $smsNotificationService) { $this->fileStorageService = $fileStorageService; $this->shiftScheduleService = $shiftScheduleService; $this->invoiceService = $invoiceService; + $this->smsNotificationService = $smsNotificationService; } public function create(User $requester, array $data, array $historyFiles = [], array $scanFiles = []): Document @@ -58,6 +62,8 @@ class FastRequestService ]); } + $this->smsNotificationService->requestRegistered($document); + return $document; }); } diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index cbad981..09f72a7 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -8,9 +8,17 @@ use App\Enums\InvoicePaymentMethodEnum; use App\Enums\InvoiceStatusEnum; use App\Models\Document; use App\Models\Invoice; +use App\Services\Sms\SmsNotificationService; class InvoiceService { + protected SmsNotificationService $smsNotificationService; + + public function __construct(SmsNotificationService $smsNotificationService) + { + $this->smsNotificationService = $smsNotificationService; + } + public function createForDocument(Document $document): Invoice { $invoice = Invoice::create([ @@ -43,6 +51,8 @@ class InvoiceService ]); } + $this->smsNotificationService->paymentConfirmed($invoice); + return $invoice; } } diff --git a/app/Services/Sms/IranPayamakService.php b/app/Services/Sms/IranPayamakService.php new file mode 100644 index 0000000..1218da5 --- /dev/null +++ b/app/Services/Sms/IranPayamakService.php @@ -0,0 +1,39 @@ + 'application/json', + 'Content-Type' => 'application/json', + 'Api-Key' => config('services.iranpayamak.api_key'), + ])->post(config('services.iranpayamak.api_url'), [ + 'code' => config("services.iranpayamak.patterns.{$patternKey}"), + 'attributes' => $attributes, + 'recipient' => $this->formatRecipient($phone), + 'line_number' => config('services.iranpayamak.line_number'), + 'number_format' => 'english', + ]); + + if (!$response->successful()) { + throw new RuntimeException('SMS sending failed: ' . $response->status() . ' ' . $response->body()); + } + + return $response->json() ?? []; + } + + protected function formatRecipient(string $phone): string + { + if (str_starts_with($phone, '+98')) { + return '0' . substr($phone, 3); + } + + return $phone; + } +} diff --git a/app/Services/Sms/SmsNotificationService.php b/app/Services/Sms/SmsNotificationService.php new file mode 100644 index 0000000..e3c2dbd --- /dev/null +++ b/app/Services/Sms/SmsNotificationService.php @@ -0,0 +1,42 @@ +dispatch($document->requester, 'request_registered', [ + 'code' => $document->code, + ]); + } + + public function paymentConfirmed(Invoice $invoice): void + { + $this->dispatch($invoice->user, 'payment_confirmed', [ + 'invoice' => $invoice->number, + 'amount' => number_format($invoice->amount), + ]); + } + + public function reportReady(Document $document): void + { + $this->dispatch($document->requester, 'report_ready', [ + 'code' => $document->code, + ]); + } + + protected function dispatch(?User $user, string $patternKey, array $attributes): void + { + if (empty($user?->phone) || empty(config("services.iranpayamak.patterns.{$patternKey}"))) { + return; + } + + SendPatternSms::dispatch($user->phone, $patternKey, $attributes); + } +} diff --git a/config/services.php b/config/services.php index a5ba2f1..f90cd9d 100644 --- a/config/services.php +++ b/config/services.php @@ -43,12 +43,14 @@ return [ 'iranpayamak' => [ 'api_url' => env('IRANPAYAMAK_SMS_API_URL', 'https://api.iranpayamak.com/ws/v1/sms/pattern'), - 'line_number' => env('IRANPAYAMAK_SMS_API_URL', '50002178584000'), - 'api_key' => env('IRANPAYAMAK_SMS_API_URL', 'jnCyVq4ein7uGsgIGjHsVRrWz0112IA2v2qbZM5q0kHtEXubsh'), - + 'line_number' => env('IRANPAYAMAK_SMS_LINE_NUMBER', '50002178584000'), + 'api_key' => env('IRANPAYAMAK_SMS_API_KEY', 'jnCyVq4ein7uGsgIGjHsVRrWz0112IA2v2qbZM5q0kHtEXubsh'), 'patterns' => [ - 'verify_code' => 'EvYDvfcUSk', + 'verify_code' => env('IRANPAYAMAK_SMS_PATTERN_VERIFY_CODE', 'EvYDvfcUSk'), + 'request_registered' => env('IRANPAYAMAK_SMS_PATTERN_REQUEST_REGISTERED' , 'GO2xzefAO8'), + 'payment_confirmed' => env('IRANPAYAMAK_SMS_PATTERN_PAYMENT_CONFIRMED' , 'bl3k2UZDPe'), + 'report_ready' => env('IRANPAYAMAK_SMS_PATTERN_REPORT_READY' , 'ImUrYHVWTx'), ], ], diff --git a/resources/js/Admin/Pages/WithdrawRequests/index.vue b/resources/js/Admin/Pages/WithdrawRequests/index.vue index 226b8b9..d6c9141 100644 --- a/resources/js/Admin/Pages/WithdrawRequests/index.vue +++ b/resources/js/Admin/Pages/WithdrawRequests/index.vue @@ -222,6 +222,7 @@ const submitReject = () => { @click="openRejectModal(data)" /> + -