import { apiClient } from '../../../services/api/apiClient'; import { baseApi } from '../../../services/api/baseApi'; import { endpoints } from '../../../services/api/endpoints'; import { resolveLive, resolveWithMock } from '../../../services/api/serviceHelpers'; import type { Beneficiary } from '../../../types/app'; import type { BeneficiarySaveRequest, BeneficiaryValidateRequest, BeneficiaryValidateResult, CbsBeneficiary, } from '../../../types/cbs'; import { favoriteStore, toBeneficiary } from '../utils/beneficiaryMapper'; /** * TEMPORARY — fixed payer account for payee validation. * * Every validate call goes out with this as the payer, overriding whichever * account the customer selected on screen. The development CBS only recognises * this account as a payer; the profile's own accounts come back * `ACCOUNT_NOT_FOUND`, which reads on screen as the *payee* being unverifiable. * * Delete this and pass `request` through untouched once the backend accepts * the profile's accounts. It must not ship: in production it would validate * every payee against an account that is not the customer's. */ const HARDCODED_PAYER_ACCOUNT = '002002000100019'; export interface BankTransferRequest { beneficiaryId: string; amount: number; transferType: 'IMPS' | 'NEFT' | 'RTGS' | 'Internal'; remarks?: string; sourceAccountId: string; } export interface BankTransferResponse { referenceId: string; status: 'Process_Success' | 'Process_Pending'; } export const paymentsApi = baseApi.injectEndpoints({ endpoints: (builder) => ({ /** The customer's saved payees, newest first. */ getBeneficiaries: builder.query({ queryFn: () => resolveLive(async () => { const [response, favorites] = await Promise.all([ apiClient.get(endpoints.beneficiaries.list), favoriteStore.read(), ]); const favoriteSet = new Set(favorites); return (response.data ?? []).map((payee) => toBeneficiary(payee, favoriteSet)); }), providesTags: ['Beneficiaries'], }), /** * Asks IMPS whether this payee exists, and who they are. Saves nothing. * * This is what the confirmation step shows, because a mistyped account * number is the most common way money reaches the wrong person. Read * `result` rather than the status code: the switch answers 200 whether or * not it recognised the payee. */ validateBeneficiary: builder.mutation({ queryFn: (request) => resolveLive(async () => { const response = await apiClient.post( endpoints.beneficiaries.validate, // See HARDCODED_PAYER_ACCOUNT — the caller's payer is discarded. { ...request, payer: { accountNumber: HARDCODED_PAYER_ACCOUNT } }, ); return response.data; }), }), /** * Saves the payee, resolved through the bank's CBS lookup. * * A different body and a different lookup from validate, which goes to * IMPS — so the two can disagree, and this one's answer is what gets * stored. Validate first regardless: the server does not enforce the * order, which means nothing but the app stops a customer saving a payee * nobody has confirmed. */ saveBeneficiary: builder.mutation({ queryFn: (request) => resolveLive(async () => { const response = await apiClient.post( endpoints.beneficiaries.save, request, ); return toBeneficiary(response.data); }), invalidatesTags: ['Beneficiaries'], }), /** No transfer endpoint exists yet, so this stays on fixtures. */ submitBankTransfer: builder.mutation({ queryFn: (req) => resolveWithMock({ mockData: () => ({ referenceId: `${req.transferType}${Date.now()}`, status: req.transferType === 'NEFT' ? 'Process_Pending' : 'Process_Success', }), request: async () => { throw new Error('Transfer submission is not implemented by the CBS API yet'); }, }), invalidatesTags: ['Payments', 'Transactions'], }), }), }); export const { useGetBeneficiariesQuery, useValidateBeneficiaryMutation, useSaveBeneficiaryMutation, useSubmitBankTransferMutation, } = paymentsApi;