Files

59 lines
2.0 KiB
PHP

<?php
namespace App\Services;
/**
* Chooses which channel delivers an OTP.
*
* OTP_PROVIDER=nabeh -> Nabeh gateway
* OTP_PROVIDER=whatsapp -> legacy self-hosted WhatsApp bots
*
* Callers pass the code, not a rendered message: Nabeh composes its own message
* around the code, while the WhatsApp bots need finished text. Keeping that
* difference here means call sites do not care which channel is active.
*
* With OTP_FALLBACK_ENABLED on, a failure is retried on the other channel
* before the login is refused - losing OTP delivery locks out every user.
*/
class OtpSender
{
public static function sendOtp(string $phone, string $otp): array
{
$provider = strtolower(trim((string)env('OTP_PROVIDER', 'whatsapp')));
$fallback = strtolower((string)env('OTP_FALLBACK_ENABLED', 'true')) === 'true';
$result = self::dispatch($provider, $phone, $otp);
$result['provider'] = $provider;
if ($result['success'] || !$fallback) {
return $result;
}
$alternate = $provider === 'nabeh' ? 'whatsapp' : 'nabeh';
error_log(sprintf(
'[OTP] primary channel "%s" failed, falling back to "%s" | error=%s',
$provider,
$alternate,
$result['error'] ?? substr((string)($result['raw_response'] ?? ''), 0, 200)
));
$fallbackResult = self::dispatch($alternate, $phone, $otp);
$fallbackResult['provider'] = $alternate;
$fallbackResult['primary_error'] = $result['error'] ?? null;
return $fallbackResult;
}
private static function dispatch(string $channel, string $phone, string $otp): array
{
if ($channel === 'nabeh') {
return (new NabehOtpService())->sendOtp($phone, $otp);
}
$appName = (string)env('NABEH_APP_NAME', 'مُصادَق');
$message = "رمز التحقق لتطبيق {$appName}:\n*{$otp}*\n\nصالح لمدة 5 دقائق.";
return (new WhatsAppProxyService())->sendMessage($phone, $message);
}
}