gateway = 'twofactor'; // Using 2Factor as primary gateway // SMS Gateway configurations $this->config = [ 'msg91' => [ 'auth_key' => 'YOUR_MSG91_AUTH_KEY', // Replace with actual key 'template_id' => 'YOUR_TEMPLATE_ID', // Replace with actual template ID 'route' => '4', // Transactional route 'country' => '91' ], 'textlocal' => [ 'api_key' => 'YOUR_TEXTLOCAL_API_KEY', 'username' => 'YOUR_TEXTLOCAL_USERNAME', 'hash' => 'YOUR_TEXTLOCAL_HASH', 'sender' => 'TXTLCL' // 6 characters or less ], 'twofactor' => [ 'api_key' => '79d4feb6-d168-11ea-9fa5-0200cd936042', // Your actual 2Factor API key 'sender_id' => 'RELREF', // Matches 2Factor dashboard: Sender Id = RELREF 'template_name' => 'Profile OTP' // Matches 2Factor dashboard: Template Name = Profile OTP ], 'fast2sms' => [ 'api_key' => 'YOUR_FAST2SMS_API_KEY', 'route' => 'dlt', // DLT route for OTP 'sender_id' => 'FSTSMS' ] ]; } /** * Send OTP SMS */ public function sendOTP($mobileNumber, $otpCode, $templateMessage = null) { // Clean mobile number $mobileNumber = preg_replace('/[^0-9]/', '', $mobileNumber); // Remove leading zero if present if (substr($mobileNumber, 0, 1) === '0') { $mobileNumber = substr($mobileNumber, 1); } // Default OTP message if (!$templateMessage) { $templateMessage = "Your Relevant Reflex verification code is: {$otpCode}. Valid for 10 minutes. Do not share this with anyone."; } switch ($this->gateway) { case 'msg91': return $this->sendViaMSG91($mobileNumber, $otpCode, $templateMessage); case 'textlocal': return $this->sendViaTextLocal($mobileNumber, $templateMessage); case 'twofactor': return $this->sendViaTwoFactor($mobileNumber, $otpCode); case 'fast2sms': return $this->sendViaFast2SMS($mobileNumber, $templateMessage); default: return $this->mockSend($mobileNumber, $otpCode); // For testing } } /** * MSG91 SMS Gateway */ private function sendViaMSG91($mobileNumber, $otpCode, $message) { $config = $this->config['msg91']; $url = "https://api.msg91.com/api/v5/otp"; $data = [ 'template_id' => $config['template_id'], 'mobile' => $config['country'] . $mobileNumber, 'authkey' => $config['auth_key'], 'otp' => $otpCode, 'extra_param' => json_encode(['otp' => $otpCode]) ]; return $this->makeAPICall($url, $data, 'POST'); } /** * TextLocal SMS Gateway */ private function sendViaTextLocal($mobileNumber, $message) { $config = $this->config['textlocal']; $url = "https://api.textlocal.in/send/"; $data = [ 'apikey' => $config['api_key'], 'numbers' => '91' . $mobileNumber, 'message' => $message, 'sender' => $config['sender'] ]; return $this->makeAPICall($url, $data, 'POST'); } /** * 2Factor SMS Gateway * * Uses OTP SMS API with explicit template name to force SMS delivery. * Template: "XXXX is the OTP to verify your mobile number at Relevant Reflex" * Sender ID: RELREF | Template Name: Profile OTP * * By specifying the template name in the URL, 2Factor uses the registered * DLT SMS template instead of defaulting to voice calls. */ private function sendViaTwoFactor($mobileNumber, $otpCode) { $config = $this->config['twofactor']; // OTP API with template name — forces SMS delivery via registered DLT template // Format: /SMS/{phone}/{otp}/{template_name} $templateName = urlencode($config['template_name']); $url = "https://2factor.in/API/V1/{$config['api_key']}/SMS/{$mobileNumber}/{$otpCode}/{$templateName}"; $result = $this->makeAPICall($url, [], 'GET'); // Log the attempt logError('2Factor OTP SMS with template', [ 'mobile' => $mobileNumber, 'template' => $config['template_name'], 'status' => $result['response']['Status'] ?? 'unknown', 'details' => $result['response']['Details'] ?? 'none' ]); if ($result['success'] && isset($result['response']['Status']) && $result['response']['Status'] == 'Success') { return [ 'success' => true, 'message' => 'OTP sent via SMS successfully', 'session_id' => $result['response']['Details'] ?? null, 'response' => $result['response'] ]; } return [ 'success' => false, 'message' => 'Failed to send SMS OTP: ' . ($result['response']['Details'] ?? 'Unknown error'), 'response' => $result['response'] ?? [] ]; } /** * Fast2SMS Gateway */ private function sendViaFast2SMS($mobileNumber, $message) { $config = $this->config['fast2sms']; $url = "https://www.fast2sms.com/dev/bulkV2"; $data = [ 'authorization' => $config['api_key'], 'sender_id' => $config['sender_id'], 'message' => $message, 'route' => $config['route'], 'numbers' => $mobileNumber ]; $headers = [ 'authorization: ' . $config['api_key'], 'Content-Type: application/json' ]; return $this->makeAPICall($url, $data, 'POST', $headers); } /** * Mock SMS sending for testing */ private function mockSend($mobileNumber, $otpCode) { logError('Mock SMS sent', [ 'mobile' => $mobileNumber, 'otp' => $otpCode, 'message' => 'OTP for testing: ' . $otpCode ]); return [ 'success' => true, 'message' => 'SMS sent successfully (Mock)', 'reference_id' => 'MOCK_' . time() ]; } /** * Make API call to SMS gateway */ private function makeAPICall($url, $data, $method = 'POST', $headers = []) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); if (!empty($data)) { if (empty($headers) || !in_array('Content-Type: application/json', $headers)) { curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); } else { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } } } if (!empty($headers)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { logError('SMS API Error', ['error' => $error, 'url' => $url]); return [ 'success' => false, 'message' => 'Network error: ' . $error ]; } $decodedResponse = json_decode($response, true); logError('SMS API Response', [ 'url' => $url, 'http_code' => $httpCode, 'response' => $decodedResponse ?: $response ]); // Parse response based on gateway return $this->parseResponse($decodedResponse ?: $response, $httpCode); } /** * Parse SMS gateway response */ private function parseResponse($response, $httpCode) { if ($httpCode >= 200 && $httpCode < 300) { return [ 'success' => true, 'message' => 'SMS sent successfully', 'response' => $response ]; } else { return [ 'success' => false, 'message' => 'SMS sending failed', 'response' => $response ]; } } /** * Get available SMS balance (if supported by gateway) */ public function getBalance() { switch ($this->gateway) { case 'msg91': $config = $this->config['msg91']; $url = "https://api.msg91.com/api/balance.php?authkey={$config['auth_key']}&type=4"; return $this->makeAPICall($url, [], 'GET'); case 'twofactor': $config = $this->config['twofactor']; $url = "https://2factor.in/API/V1/{$config['api_key']}/ADDON_SERVICES/BAL/SMS"; return $this->makeAPICall($url, [], 'GET'); default: return ['success' => false, 'message' => 'Balance check not supported for this gateway']; } } } // Global SMS helper function function sendOTPSMS($mobileNumber, $otpCode) { $smsManager = new SMSManager(); return $smsManager->sendOTP($mobileNumber, $otpCode); } /* * SETUP INSTRUCTIONS FOR 2FACTOR SMS GATEWAY * =========================================== * * Your 2Factor API Key: 79d4feb6-d168-11ea-9fa5-0200cd936042 * * FIXING "VOICE CALL INSTEAD OF SMS" ISSUE: * ------------------------------------------ * The 2Factor OTP API has auto-retry that sends voice calls when SMS fails. * To get SMS working properly: * * STEP 1: Login to https://2factor.in with your account * * STEP 2: Disable Voice Auto-Retry * - Go to Dashboard → Settings (or OTP Settings) * - Find "Auto Retry" / "Voice OTP Retry" / "Retry Configuration" * - DISABLE it (turn off voice fallback) * * STEP 3: Check DLT Registration (REQUIRED for SMS in India) * - In India, TRAI requires DLT registration for all SMS * - Go to 2Factor Dashboard → DLT Settings / SMS Templates * - You need: * a) A registered Sender ID (e.g., "RREFX") * b) A DLT-approved Content Template for OTP * Example template: "Your Relevant Reflex OTP is {#var#}. Valid for 10 minutes." * - Update the sender_id and template_name in config above after registering * - If you don't have DLT registration, register at: * Jio: https://trueconnect.jio.com * Airtel: https://www.airtel.in/business/commercial-communication * Vodafone: https://smartping.live/entity/reg-as * * STEP 4: Check SMS Balance * - Go to Dashboard → Balance * - Verify you have SMS credits (not just voice credits) * - Current balance: ~7,038 SMS credits * * STEP 5: Test * - After disabling auto-retry and setting up DLT template * - Try sending OTP from mobile-verification.php * - Check errors.log for detailed API responses * * HOW THIS CODE WORKS: * - First tries Transactional SMS API (no voice fallback at all) * - If that fails, falls back to OTP API (may trigger voice if auto-retry is on) * - Both attempts are logged to errors.log for debugging * */ ?>