diff --git a/.env.example b/.env.example index 38241be..a076200 100644 --- a/.env.example +++ b/.env.example @@ -63,3 +63,7 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +TWILIO_SID= +TWILIO_AUTH_TOKEN= +TWILIO_NUMBER= diff --git a/app/Actions/PasswordResetAction.php b/app/Actions/PasswordResetAction.php index b8a152b..320e294 100644 --- a/app/Actions/PasswordResetAction.php +++ b/app/Actions/PasswordResetAction.php @@ -5,10 +5,16 @@ use App\Exceptions\UserNotFoundException; use App\Models\User; use App\Services\OTPService; +use App\Services\TwilioService; +use Twilio\Exceptions\TwilioException; final readonly class PasswordResetAction { - public function __construct(private SendPasswordResetMailAction $mailAction, private OTPService $otpService) {} + public function __construct( + private SendPasswordResetMailAction $mailAction, + private OTPService $otpService, + private TwilioService $twilioService + ) {} /** * @throws \Throwable @@ -20,5 +26,13 @@ public function execute(array $data): void $otp = $this->otpService->generate($user); $this->mailAction->execute($user->email, $otp); + + if ($user?->type->phone !== null) { + try { + $this->twilioService->sendSms($user->type->phone, "Your OTP is $otp"); + } catch (TwilioException $e) { + \Log::error('SMS send failed', [$e->getMessage()]); + } + } } } diff --git a/app/Services/TwilioService.php b/app/Services/TwilioService.php new file mode 100644 index 0000000..65f28b0 --- /dev/null +++ b/app/Services/TwilioService.php @@ -0,0 +1,24 @@ +client = new Client(config('services.twilio.sid'), config('services.twilio.token')); + } + + /** + * @throws TwilioException + */ + public function sendSms(string $to, string $message): void + { + $this->client->messages->create($to, ['from' => config('services.twilio.from'), 'body' => $message]); + } +} diff --git a/composer.json b/composer.json index 76fbade..6c7a7a2 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "php": "^8.2", "blade-ui-kit/blade-heroicons": "^2.6", "laravel/framework": "^12.0", - "laravel/tinker": "^2.10.1" + "laravel/tinker": "^2.10.1", + "twilio/sdk": "^8.10" }, "require-dev": { "barryvdh/laravel-ide-helper": "^3.6", diff --git a/composer.lock b/composer.lock index cd7dd6e..c99cf20 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e289c914430caaa92d59d569997723b8", + "content-hash": "95679c3122694dcdfec2c16e31b13c3d", "packages": [ { "name": "blade-ui-kit/blade-heroicons", @@ -5987,6 +5987,56 @@ }, "time": "2025-12-02T11:56:42+00:00" }, + { + "name": "twilio/sdk", + "version": "8.10.1", + "source": { + "type": "git", + "url": "https://github.com/twilio/twilio-php", + "reference": "84fc8e4b2b5ff32d20e73b17718b13e5ed56bc8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twilio/twilio-php/zipball/84fc8e4b2b5ff32d20e73b17718b13e5ed56bc8c", + "reference": "84fc8e4b2b5ff32d20e73b17718b13e5ed56bc8c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "guzzlehttp/guzzle": "^6.3 || ^7.0", + "phpunit/phpunit": ">=7.0 < 10" + }, + "suggest": { + "guzzlehttp/guzzle": "An HTTP client to execute the API requests" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twilio\\": "src/Twilio/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Twilio API Team", + "email": "api@twilio.com" + } + ], + "description": "A PHP wrapper for Twilio's API", + "homepage": "https://github.com/twilio/twilio-php", + "keywords": [ + "api", + "sms", + "twilio" + ], + "time": "2026-01-07T09:28:54+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.6.3", diff --git a/config/services.php b/config/services.php index 6a90eb8..b8e68eb 100644 --- a/config/services.php +++ b/config/services.php @@ -35,4 +35,10 @@ ], ], + 'twilio' => [ + 'sid' => env('TWILIO_SID'), + 'token' => env('TWILIO_AUTH_TOKEN'), + 'from' => env('TWILIO_NUMBER'), + ], + ];