feat: implement SMS notification service and integrate with existing services
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Services\Sms\IranPayamakService;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
class SendPatternSms implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public int $tries = 3;
|
||||||
|
|
||||||
|
public array $backoff = [60, 300, 900];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected string $phone,
|
||||||
|
protected string $patternKey,
|
||||||
|
protected array $attributes
|
||||||
|
) {
|
||||||
|
$this->afterCommit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(IranPayamakService $iranPayamakService): void
|
||||||
|
{
|
||||||
|
$iranPayamakService->sendPattern($this->phone, $this->patternKey, $this->attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,17 @@
|
|||||||
namespace App\Services\Auth;
|
namespace App\Services\Auth;
|
||||||
|
|
||||||
use App\Models\OtpCode;
|
use App\Models\OtpCode;
|
||||||
use Illuminate\Support\Facades\Http;
|
use App\Services\Sms\IranPayamakService;
|
||||||
|
|
||||||
class OtpService
|
class OtpService
|
||||||
{
|
{
|
||||||
|
protected IranPayamakService $iranPayamakService;
|
||||||
|
|
||||||
|
public function __construct(IranPayamakService $iranPayamakService)
|
||||||
|
{
|
||||||
|
$this->iranPayamakService = $iranPayamakService;
|
||||||
|
}
|
||||||
|
|
||||||
public function generateCode(): string
|
public function generateCode(): string
|
||||||
{
|
{
|
||||||
return (string) random_int(1000, 9999);
|
return (string) random_int(1000, 9999);
|
||||||
@@ -45,51 +52,14 @@ class OtpService
|
|||||||
->exists();
|
->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendSms(string $phone, string $code): array
|
private function sendSms(string $phone, string $code): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$formattedRecipient = $this->formatPhoneForSms($phone);
|
$this->iranPayamakService->sendPattern($phone, 'verify_code', [
|
||||||
|
|
||||||
$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,
|
'otp' => $code,
|
||||||
'status' => 'بازنشانی رمز عبور'
|
'status' => 'بازنشانی رمز عبور',
|
||||||
],
|
|
||||||
'recipient' => $formattedRecipient,
|
|
||||||
'line_number' => config('services.iranpayamak.line_number'),
|
|
||||||
'number_format' => 'english',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
return $response->json();
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => 'SMS sending failed: ' . $response->status(),
|
|
||||||
'data' => $response->body()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
} 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use App\Models\Report;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\FileStorageService;
|
use App\Services\FileStorageService;
|
||||||
use App\Services\Shift\ShiftScheduleService;
|
use App\Services\Shift\ShiftScheduleService;
|
||||||
|
use App\Services\Sms\SmsNotificationService;
|
||||||
use App\Services\Wallet\WalletService;
|
use App\Services\Wallet\WalletService;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -27,11 +28,14 @@ class CaseService
|
|||||||
|
|
||||||
protected ShiftScheduleService $shiftScheduleService;
|
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->fileStorageService = $fileStorageService;
|
||||||
$this->walletService = $walletService;
|
$this->walletService = $walletService;
|
||||||
$this->shiftScheduleService = $shiftScheduleService;
|
$this->shiftScheduleService = $shiftScheduleService;
|
||||||
|
$this->smsNotificationService = $smsNotificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pendingForUser(User $user): Builder
|
public function pendingForUser(User $user): Builder
|
||||||
@@ -95,6 +99,8 @@ class CaseService
|
|||||||
'finalized_by' => $doctor->id,
|
'finalized_by' => $doctor->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->smsNotificationService->reportReady($document);
|
||||||
|
|
||||||
return $report;
|
return $report;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use App\Models\User;
|
|||||||
use App\Services\FileStorageService;
|
use App\Services\FileStorageService;
|
||||||
use App\Services\Invoice\InvoiceService;
|
use App\Services\Invoice\InvoiceService;
|
||||||
use App\Services\Shift\ShiftScheduleService;
|
use App\Services\Shift\ShiftScheduleService;
|
||||||
|
use App\Services\Sms\SmsNotificationService;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class FastRequestService
|
class FastRequestService
|
||||||
@@ -22,11 +23,14 @@ class FastRequestService
|
|||||||
|
|
||||||
protected InvoiceService $invoiceService;
|
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->fileStorageService = $fileStorageService;
|
||||||
$this->shiftScheduleService = $shiftScheduleService;
|
$this->shiftScheduleService = $shiftScheduleService;
|
||||||
$this->invoiceService = $invoiceService;
|
$this->invoiceService = $invoiceService;
|
||||||
|
$this->smsNotificationService = $smsNotificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(User $requester, array $data, array $historyFiles = [], array $scanFiles = []): Document
|
public function create(User $requester, array $data, array $historyFiles = [], array $scanFiles = []): Document
|
||||||
@@ -58,6 +62,8 @@ class FastRequestService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->smsNotificationService->requestRegistered($document);
|
||||||
|
|
||||||
return $document;
|
return $document;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ use App\Enums\InvoicePaymentMethodEnum;
|
|||||||
use App\Enums\InvoiceStatusEnum;
|
use App\Enums\InvoiceStatusEnum;
|
||||||
use App\Models\Document;
|
use App\Models\Document;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use App\Services\Sms\SmsNotificationService;
|
||||||
|
|
||||||
class InvoiceService
|
class InvoiceService
|
||||||
{
|
{
|
||||||
|
protected SmsNotificationService $smsNotificationService;
|
||||||
|
|
||||||
|
public function __construct(SmsNotificationService $smsNotificationService)
|
||||||
|
{
|
||||||
|
$this->smsNotificationService = $smsNotificationService;
|
||||||
|
}
|
||||||
|
|
||||||
public function createForDocument(Document $document): Invoice
|
public function createForDocument(Document $document): Invoice
|
||||||
{
|
{
|
||||||
$invoice = Invoice::create([
|
$invoice = Invoice::create([
|
||||||
@@ -43,6 +51,8 @@ class InvoiceService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->smsNotificationService->paymentConfirmed($invoice);
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Sms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class IranPayamakService
|
||||||
|
{
|
||||||
|
public function sendPattern(string $phone, string $patternKey, array $attributes): array
|
||||||
|
{
|
||||||
|
$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.{$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Sms;
|
||||||
|
|
||||||
|
use App\Jobs\SendPatternSms;
|
||||||
|
use App\Models\Document;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class SmsNotificationService
|
||||||
|
{
|
||||||
|
public function requestRegistered(Document $document): void
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-4
@@ -43,12 +43,14 @@ return [
|
|||||||
|
|
||||||
'iranpayamak' => [
|
'iranpayamak' => [
|
||||||
'api_url' => env('IRANPAYAMAK_SMS_API_URL', 'https://api.iranpayamak.com/ws/v1/sms/pattern'),
|
'api_url' => env('IRANPAYAMAK_SMS_API_URL', 'https://api.iranpayamak.com/ws/v1/sms/pattern'),
|
||||||
'line_number' => env('IRANPAYAMAK_SMS_API_URL', '50002178584000'),
|
'line_number' => env('IRANPAYAMAK_SMS_LINE_NUMBER', '50002178584000'),
|
||||||
'api_key' => env('IRANPAYAMAK_SMS_API_URL', 'jnCyVq4ein7uGsgIGjHsVRrWz0112IA2v2qbZM5q0kHtEXubsh'),
|
'api_key' => env('IRANPAYAMAK_SMS_API_KEY', 'jnCyVq4ein7uGsgIGjHsVRrWz0112IA2v2qbZM5q0kHtEXubsh'),
|
||||||
|
|
||||||
|
|
||||||
'patterns' => [
|
'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'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ const submitReject = () => {
|
|||||||
@click="openRejectModal(data)"
|
@click="openRejectModal(data)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<span v-else class="text-xs text-gray-400">-</span>
|
||||||
</template>
|
</template>
|
||||||
</TableGeneric>
|
</TableGeneric>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user