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.
This commit is contained in:
DevSoftTech Server 2026-08-18 02:13:47 +05:30
parent c8f44e4493
commit 841b15d27b
6 changed files with 92 additions and 10 deletions

View File

@ -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'] ?? '')

View File

@ -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.
*

View File

@ -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(

View File

@ -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(

View File

@ -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'] ?? '');

View File

@ -79,7 +79,7 @@
<span class="input-group-text"><i class="bi bi-phone"></i></span>
<input type="text" class="form-control" id="abhaCreateMobile" maxlength="10" inputmode="numeric" autocomplete="tel" placeholder="10-digit mobile">
</div>
<div class="form-text">Required by ABDM enrolment. If this differs from the Aadhaar-linked mobile, it is verified separately.</div>
<div class="form-text">Required by ABDM enrolment. A different number is verified through the Aadhaar-bound enrolment transaction.</div>
</div>
</div>
@ -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('<?= base_url('abha/create/communication') ?>', { mobile: communicationMobile, txn_id: createTxnId, '<?= csrf_token() ?>': 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 <strong>' + escapeHtml(maskMobile(communicationMobile)) + '</strong> 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.'));
});
}