generateCode(); $expiresAt = now()->addMinutes($minutes); $otpCode = OtpCode::updateOrCreate( ['phone' => $phone], ['code' => $code, 'expires_at' => $expiresAt] ); $this->sendSms($phone, $code); return $otpCode; } public function verifyCode(string $phone, string $code): bool { $deleted = OtpCode::where('phone', $phone) ->where('code', $code) ->where('expires_at', '>', now()) ->delete(); return (bool) $deleted; } public function existsValid(string $phone, string $code): bool { return OtpCode::where('phone', $phone) ->where('code', $code) ->where('expires_at', '>', now()) ->exists(); } private function sendSms(string $phone, string $code): array { 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', ]); 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; } }