From 841b15d27bd88c477a46d42983278c2689c27979 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Tue, 18 Aug 2026 02:13:47 +0530 Subject: [PATCH] fix(abdm): bind alternate mobile to enrolment Use the dedicated Bridge enrolment-mobile OTP endpoints for child/dependent ABHA creation and reject any alternate-mobile response whose ABHA differs from the Aadhaar-verified identity. --- app/Controllers/Abha.php | 34 +++++++++++++++++-- app/Libraries/Abdm/AbdmConnectorInterface.php | 4 +++ app/Libraries/Abdm/DirectAbdmConnector.php | 10 ++++++ app/Libraries/Abdm/DreamsoftConnector.php | 10 ++++++ app/Libraries/Abdm/EAtriaBridgeConnector.php | 31 +++++++++++++++++ app/Views/partials/abha_create_modal.php | 13 ++++--- 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index dda2439..4f7a24c 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -110,6 +110,10 @@ class Abha extends BaseController $profile = $this->pickGatewayAbhaProfile($payload); $abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? ''); + $aadhaarVerifiedAbha = preg_replace('/\D/', '', $abhaNum); + if ($newTxnId && strlen($aadhaarVerifiedAbha) === 14) { + session()->set('abha_enrol_identity_' . $newTxnId, $aadhaarVerifiedAbha); + } $abhaAddress = (string) ( ($profile['preferredAddress'] ?? '') ?: ($profile['preferredAbhaAddress'] ?? '') @@ -224,15 +228,28 @@ class Abha extends BaseController return $this->response->setJSON(['ok' => 0, 'error_text' => 'Valid 10-digit mobile number is required']); } + if ($txnId === '') { + return $this->response->setJSON(['ok' => 0, 'error_text' => 'Aadhaar enrolment txn_id is required']); + } + try { - $result = AbdmConnectorFactory::make()->abhaMobileGenerateOtp(['mobile' => $mobile, 'txnId' => $txnId]); + $result = AbdmConnectorFactory::make()->abhaEnrolMobileRequestOtp(['mobile' => $mobile, 'txnId' => $txnId]); } catch (\Throwable $e) { return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]); } if (! empty($result['ok']) && $result['ok'] == 1) { $newTxnId = $result['txn_id'] ?? $result['data']['txnId'] ?? $result['data']['txn_id'] ?? $txnId; - return $this->response->setJSON(['ok' => 1, 'txn_id' => $newTxnId]); + $expectedAbha = (string) (session()->get('abha_enrol_identity_' . $txnId) ?? ''); + if ($expectedAbha !== '') { + session()->set('abha_enrol_identity_' . $newTxnId, $expectedAbha); + } + return $this->response->setJSON([ + 'ok' => 1, + 'txn_id' => $newTxnId, + 'message' => (string) ($result['message'] ?? $result['data']['message'] ?? 'OTP sent to alternate mobile.'), + 'request_id' => (string) ($result['request_id'] ?? ''), + ]); } return $this->response->setJSON([ @@ -264,7 +281,7 @@ class Abha extends BaseController } try { - $result = AbdmConnectorFactory::make()->abhaMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]); + $result = AbdmConnectorFactory::make()->abhaEnrolMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]); } catch (\Throwable $e) { return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]); } @@ -290,6 +307,17 @@ class Abha extends BaseController $profile = $this->pickGatewayAbhaProfile($payload); $abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? ''); + $expectedAbha = preg_replace('/\D/', '', (string) (session()->get('abha_enrol_identity_' . $txnId) ?? '')); + $returnedAbha = preg_replace('/\D/', '', $abhaNum); + if (strlen($expectedAbha) !== 14 || $returnedAbha !== $expectedAbha) { + log_message('error', '[ABHA] Enrolment identity mismatch blocked for txn ' . $txnId); + return $this->response->setStatusCode(409)->setJSON([ + 'ok' => 0, + 'error_text' => 'ABHA identity mismatch. The alternate-mobile response did not match the Aadhaar-verified person and was rejected.', + 'request_id' => (string) ($result['request_id'] ?? ''), + ]); + } + session()->remove('abha_enrol_identity_' . $txnId); $abhaAddress = (string) ( ($profile['preferredAddress'] ?? '') ?: ($profile['preferredAbhaAddress'] ?? '') diff --git a/app/Libraries/Abdm/AbdmConnectorInterface.php b/app/Libraries/Abdm/AbdmConnectorInterface.php index 1f639c0..3451f05 100644 --- a/app/Libraries/Abdm/AbdmConnectorInterface.php +++ b/app/Libraries/Abdm/AbdmConnectorInterface.php @@ -115,6 +115,10 @@ interface AbdmConnectorInterface */ public function abhaMobileVerifyOtp(array $payload): array; + public function abhaEnrolMobileRequestOtp(array $payload): array; + + public function abhaEnrolMobileVerifyOtp(array $payload): array; + /** * ABDM M1: List ABHA address suggestions for an in-progress enrolment. * diff --git a/app/Libraries/Abdm/DirectAbdmConnector.php b/app/Libraries/Abdm/DirectAbdmConnector.php index a3755de..9f65ca2 100644 --- a/app/Libraries/Abdm/DirectAbdmConnector.php +++ b/app/Libraries/Abdm/DirectAbdmConnector.php @@ -157,6 +157,16 @@ class DirectAbdmConnector implements AbdmConnectorInterface ); } + public function abhaEnrolMobileRequestOtp(array $payload): array + { + throw new \RuntimeException('DirectAbdmConnector::abhaEnrolMobileRequestOtp() not implemented.'); + } + + public function abhaEnrolMobileVerifyOtp(array $payload): array + { + throw new \RuntimeException('DirectAbdmConnector::abhaEnrolMobileVerifyOtp() not implemented.'); + } + public function abhaAddressSuggestions(array $payload): array { throw new \RuntimeException( diff --git a/app/Libraries/Abdm/DreamsoftConnector.php b/app/Libraries/Abdm/DreamsoftConnector.php index fb8b3ef..b847561 100644 --- a/app/Libraries/Abdm/DreamsoftConnector.php +++ b/app/Libraries/Abdm/DreamsoftConnector.php @@ -172,6 +172,16 @@ class DreamsoftConnector implements AbdmConnectorInterface ); } + public function abhaEnrolMobileRequestOtp(array $payload): array + { + throw new \RuntimeException('ABHA enrolment mobile OTP requires the synchronous e-Atria Bridge connector.'); + } + + public function abhaEnrolMobileVerifyOtp(array $payload): array + { + throw new \RuntimeException('ABHA enrolment mobile OTP verification requires the synchronous e-Atria Bridge connector.'); + } + public function abhaAddressSuggestions(array $payload): array { throw new \RuntimeException( diff --git a/app/Libraries/Abdm/EAtriaBridgeConnector.php b/app/Libraries/Abdm/EAtriaBridgeConnector.php index 6b11309..6dcf5b4 100644 --- a/app/Libraries/Abdm/EAtriaBridgeConnector.php +++ b/app/Libraries/Abdm/EAtriaBridgeConnector.php @@ -968,6 +968,37 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface return $this->attachOfficialAbhaCard($result); } + public function abhaEnrolMobileRequestOtp(array $payload): array + { + $body = [ + 'txnId' => (string) ($payload['txnId'] ?? $payload['txn_id'] ?? ''), + 'mobile' => (string) ($payload['mobile'] ?? ''), + ]; + if ($this->hfrId !== '') { + $body['hfr_id'] = $this->hfrId; + } + + return $this->post('/v3/abha/enrol/mobile/request-otp', $body); + } + + public function abhaEnrolMobileVerifyOtp(array $payload): array + { + $body = [ + 'txnId' => (string) ($payload['txnId'] ?? $payload['txn_id'] ?? ''), + 'otp' => (string) ($payload['otp'] ?? ''), + ]; + if ($this->hfrId !== '') { + $body['hfr_id'] = $this->hfrId; + } + + $result = $this->post('/v3/abha/enrol/mobile/verify-otp', $body); + if (empty($result['ok']) || (int) $result['ok'] !== 1) { + return $result; + } + + return $this->attachOfficialAbhaCard($result); + } + public function abhaAddressSuggestions(array $payload): array { $txnId = (string) ($payload['txn_id'] ?? $payload['txnId'] ?? ''); diff --git a/app/Views/partials/abha_create_modal.php b/app/Views/partials/abha_create_modal.php index 6c9c838..c6f69e3 100644 --- a/app/Views/partials/abha_create_modal.php +++ b/app/Views/partials/abha_create_modal.php @@ -79,7 +79,7 @@ -
Required by ABDM enrolment. If this differs from the Aadhaar-linked mobile, it is verified separately.
+
Required by ABDM enrolment. A different number is verified through the Aadhaar-bound enrolment transaction.
@@ -374,8 +374,7 @@ window.AbhaCreateModal = (function () { stopTimers(); createdProfile = response; createTxnId = response.txn_id || createTxnId; - var abhaMobile = digits(response.mobile).slice(-10); - if (communicationMobile && communicationMobile !== abhaMobile) { + if (communicationMobile && digits(response.mobile).slice(-10) !== communicationMobile) { requestMobileOtp(); return; } @@ -387,22 +386,22 @@ window.AbhaCreateModal = (function () { } function requestMobileOtp() { - alertBox('info', 'ABHA verified. Requesting an OTP for the alternate communication mobile...'); + alertBox('info', 'Aadhaar verified. Requesting OTP for the alternate communication mobile...'); $.post('', { mobile: communicationMobile, txn_id: createTxnId, '': csrf() }, function (response) { if (!response || response.ok != 1) { renderProfile(createdProfile || {}); - alertBox('warning', 'ABHA verification succeeded, but the communication mobile was not updated: ' + apiMessage(response, 'Unable to send the mobile OTP.')); + alertBox('warning', 'Aadhaar verification succeeded, but the alternate mobile OTP could not be sent: ' + apiMessage(response, 'Unable to send mobile OTP.')); return; } mobileTxnId = response.txn_id || createTxnId; - $('#abhaCreateMobileHint').html('Enter the OTP sent to ' + escapeHtml(maskMobile(communicationMobile)) + ' to update the mobile registered on this ABHA account.'); + $('#abhaCreateMobileHint').text(response.message || ('OTP sent to ' + maskMobile(communicationMobile) + '.')); $('#abhaCreateMobileOtp').val(''); showStep(3); startMobileResendTimer($('#abhaCreateMobileResendBtn')); $('#abhaCreateMobileOtp').trigger('focus'); }, 'json').fail(function (xhr) { renderProfile(createdProfile || {}); - alertBox('warning', 'ABHA verification succeeded, but the communication mobile was not updated: ' + apiMessage(xhr.responseJSON, 'Unable to send the mobile OTP.')); + alertBox('warning', 'Aadhaar verification succeeded, but the alternate mobile OTP could not be sent: ' + apiMessage(xhr.responseJSON, 'Unable to send mobile OTP.')); }); }