Implement IMPS fund transfer via initiate and verify-otp

Initiate runs the IMPS name inquiry and sends an OTP; verify-otp sends
ReqPay. The result screen now distinguishes success, failure and
pending reconciliation. The payer is temporarily fixed to the test
account 002002000100019, and the to-mobile form no longer blocks on
mobile/MMID format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Abin Toppo
2026-09-25 16:02:03 +05:30
co-authored by Claude Opus 5.5
parent 473f9d7415
commit abb14d5768
9 changed files with 673 additions and 162 deletions
+3 -3
View File
@@ -9,9 +9,9 @@ import {
} from './cbsErrors';
describe('error code catalogue', () => {
it('covers all 46 documented codes, with no duplicates', () => {
expect(CBS_ERROR_CODES).toHaveLength(46);
expect(new Set(CBS_ERROR_CODES).size).toBe(46);
it('covers all 48 documented codes, with no duplicates', () => {
expect(CBS_ERROR_CODES).toHaveLength(48);
expect(new Set(CBS_ERROR_CODES).size).toBe(48);
});
it('does not invite a retry when the bank withholds a balance', () => {
+12 -1
View File
@@ -51,6 +51,8 @@ export const CBS_ERROR_CODES = [
'SMS_DELIVERY_FAILED',
'OTP_PROVIDER_UNAVAILABLE',
'CONNECTOR_UNAVAILABLE',
'IMPS_REQUEST_REJECTED',
'TRANSFER_NOT_AWAITING_OTP',
] as const;
export type CbsErrorCode = (typeof CBS_ERROR_CODES)[number];
@@ -256,7 +258,7 @@ const ERROR_COPY: Record<AnyErrorCode, ErrorCopy> = {
},
OTP_ATTEMPTS_EXCEEDED: {
title: 'Too many wrong codes',
description: 'Start registration again to get a new code.',
description: 'Start again to get a new code.',
},
INTERNAL_ERROR: {
title: 'Something went wrong at the bank',
@@ -282,6 +284,15 @@ const ERROR_COPY: Record<AnyErrorCode, ErrorCopy> = {
title: 'Payee checks are unavailable',
description: 'This is a server configuration issue, not a problem with the details you entered.',
},
IMPS_REQUEST_REJECTED: {
title: 'The payment network refused the request',
description: 'Nothing was sent. Please try again in a moment.',
retryable: true,
},
TRANSFER_NOT_AWAITING_OTP: {
title: 'This transfer is already finished',
description: 'It is no longer waiting for a code, so nothing was sent again. Check your transactions for its outcome.',
},
MMID_ALLOCATION_FAILED: {
title: 'We could not finish setting up your accounts',
description: 'Please try again shortly.',
+9
View File
@@ -69,6 +69,15 @@ export const endpoints = {
save: '/beneficiaries',
validate: '/beneficiaries/validate',
},
/**
* IMPS transfers, in two steps. `initiate` runs the name inquiry and sends an
* OTP but moves no money; `verifyOtp` consumes the OTP and sends ReqPay. The
* server never sends ReqPay before a verified OTP, and never retries it.
*/
fundTransfers: {
initiate: '/fund-transfers/initiate',
verifyOtp: (transferId: string) => `/fund-transfers/${transferId}/verify-otp`,
},
dashboard: {
summary: '/dashboard/summary',
},