apiKey = getenv('OTPIQ_API_KEY') ?: 'sk_live_a2b4f0fe539910aee2e44be050e9767bea266607'; $this->endpoint = getenv('OTPIQ_ENDPOINT') ?: 'https://api.otpiq.com/api/sms'; $this->provider = getenv('OTPIQ_PROVIDER') ?: 'whatsapp-sms'; } /** * Sends OTP via OTPIQ with Smart Fallback (WhatsApp first, then SMS). * Verified with live production API credentials. */ public function sendOtp(string $phoneNumber, string $otpCode): array { $normalizedPhone = PhoneFormatterService::normalize($phoneNumber); // If API key is not configured, run in Mock / Dev mode if (empty($this->apiKey)) { error_log("[MOCK OTPIQ] Sent OTP {$otpCode} to {$normalizedPhone} via whatsapp-sms."); return [ 'success' => true, 'mode' => 'mock', 'phone' => $normalizedPhone, 'otp' => $otpCode, 'provider' => $this->provider, ]; } $payload = [ 'phoneNumber' => $normalizedPhone, 'smsType' => 'verification', 'provider' => $this->provider, 'verificationCode' => (string)$otpCode, ]; $ch = curl_init($this->endpoint); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->apiKey, 'Content-Type: application/json', ], CURLOPT_TIMEOUT => 12, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); if (PHP_VERSION_ID < 80500) { curl_close($ch); } if ($error || $httpCode >= 400) { return [ 'success' => false, 'error' => $error ?: "HTTP {$httpCode}: " . substr((string)$response, 0, 150), ]; } $data = json_decode((string)$response, true); return [ 'success' => true, 'data' => $data, 'sms_id' => $data['smsId'] ?? null, 'remaining_credit' => $data['remainingCredit'] ?? null, 'cost' => $data['cost'] ?? 25, ]; } }