119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
/**
|
|
* Cheque status lookup.
|
|
*
|
|
* There is no CBS endpoint for this yet — `endpoints.ts` has cheque book
|
|
* requests and stop payments but nothing that answers "what happened to leaf
|
|
* 123456". Until there is, the answer is resolved locally, in the same way the
|
|
* dashboard, cards and loans resolve from fixtures today.
|
|
*
|
|
* The result is derived from the cheque number rather than drawn at random, so
|
|
* the same number always gives the same answer. A demo that changes its mind
|
|
* between two taps of the same button reads as a bug, and it makes the screen
|
|
* impossible to write a test against.
|
|
*
|
|
* To go live, replace the body of `lookupChequeFate` with the API call. The
|
|
* `ChequeFate` shape is what the screen renders, so it is the contract to map
|
|
* the response onto.
|
|
*/
|
|
|
|
export type ChequeFateStatus = 'paid' | 'in_clearing' | 'returned' | 'stopped' | 'not_found';
|
|
|
|
export type ChequeFate = {
|
|
chequeNumber: string;
|
|
status: ChequeFateStatus;
|
|
/** Short human label for the status, shown as the result headline. */
|
|
label: string;
|
|
amount?: number;
|
|
presentedOn?: string;
|
|
clearedOn?: string;
|
|
payee?: string;
|
|
/** Why a cheque was returned or stopped. Absent for a clean outcome. */
|
|
reason?: string;
|
|
/** The caveat under the result — what the customer should do next. */
|
|
note: string;
|
|
};
|
|
|
|
const formatDay = (daysAgo: number) => {
|
|
const date = new Date();
|
|
|
|
date.setDate(date.getDate() - daysAgo);
|
|
|
|
return new Intl.DateTimeFormat('en-IN', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
}).format(date);
|
|
};
|
|
|
|
export type ChequeFateQuery = {
|
|
accountNumber: string;
|
|
chequeNumber: string;
|
|
};
|
|
|
|
export const lookupChequeFate = async ({
|
|
accountNumber,
|
|
chequeNumber,
|
|
}: ChequeFateQuery): Promise<ChequeFate> => {
|
|
// Stands in for the round trip, so the button's loading state is visible and
|
|
// the screen is exercised the way it will behave against the real API.
|
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
|
|
const digits = Number.parseInt(chequeNumber, 10);
|
|
// Mixing the account in means the same leaf number on two accounts gives two
|
|
// answers, which is the behaviour the real ledger has.
|
|
const seed = (Number.isNaN(digits) ? 0 : digits) + Number(accountNumber.slice(-2) || 0);
|
|
const amount = 1500 + (seed % 220) * 125;
|
|
|
|
switch (seed % 5) {
|
|
case 0:
|
|
return {
|
|
chequeNumber,
|
|
status: 'paid',
|
|
label: 'Paid',
|
|
amount,
|
|
presentedOn: formatDay(6),
|
|
clearedOn: formatDay(4),
|
|
payee: 'Meghana Rao',
|
|
note: 'Funds have left your account. The clearing house record is final.',
|
|
};
|
|
case 1:
|
|
return {
|
|
chequeNumber,
|
|
status: 'in_clearing',
|
|
label: 'In clearing',
|
|
amount,
|
|
presentedOn: formatDay(1),
|
|
payee: 'Sunrise Traders',
|
|
note: 'Presented and awaiting settlement. Cheques normally clear within two working days.',
|
|
};
|
|
case 2:
|
|
return {
|
|
chequeNumber,
|
|
status: 'returned',
|
|
label: 'Returned unpaid',
|
|
amount,
|
|
presentedOn: formatDay(3),
|
|
payee: 'Vasan Electricals',
|
|
reason: 'Insufficient funds',
|
|
note: 'A return charge may apply. Contact your branch before re-presenting the cheque.',
|
|
};
|
|
case 3:
|
|
return {
|
|
chequeNumber,
|
|
status: 'stopped',
|
|
label: 'Stop payment in force',
|
|
amount,
|
|
presentedOn: formatDay(2),
|
|
reason: 'Stop payment requested by you',
|
|
note: 'This cheque will not be paid while the stop instruction stands.',
|
|
};
|
|
default:
|
|
return {
|
|
chequeNumber,
|
|
status: 'not_found',
|
|
label: 'No record found',
|
|
note: 'This cheque has not been presented on the selected account. Check the number and the account, then try again.',
|
|
};
|
|
}
|
|
};
|