feat: (payment) add eBirr as synchronous API_PURCHASE wallet debit

This commit is contained in:
Abubeker Yasin
2026-08-10 11:36:05 +03:00
parent 78d69eb02b
commit 6471c3511a
19 changed files with 1591 additions and 380 deletions

View File

@@ -0,0 +1,53 @@
/**
* Ethiopian MSISDN normalisation.
*
* Passenger phone numbers are stored inconsistently — `+2519…`, `2519…` and local `09…` all
* appear (see the variant-building comment in the passenger API's bookings.service). Ethiopian
* mobile wallets want the bare international form with no `+` and no leading zero, e.g.
* `251923582676`.
*
* This is deliberately separate from `normalizeCacMobile` (cac-bank.json.ts), which does the
* opposite for Djibouti — it *strips* the 253 country code to a national number.
*/
/** Ethiopian mobile subscriber numbers are 9 digits and always start with 9 (or 7 for Safaricom). */
const ET_NATIONAL_LENGTH = 9;
const ET_COUNTRY_CODE = '251';
/**
* Convert any accepted Ethiopian phone format to the bare `251XXXXXXXXX` form.
*
* Accepts `+251923582676`, `251923582676`, `0923582676` and `923582676`, plus spaces, dashes and
* parentheses anywhere. Throws on anything that isn't a plausible Ethiopian mobile number rather
* than silently sending a wrong account — a mistyped number would push a PIN prompt to a
* stranger's handset.
*/
export function normalizeEthiopianMsisdn(input: string): string {
const digits = (input ?? '').replace(/[\s()+-]/g, '');
if (!/^\d+$/.test(digits)) {
throw new Error(`Invalid Ethiopian mobile number: ${input}`);
}
let national: string;
if (digits.startsWith('00' + ET_COUNTRY_CODE)) {
national = digits.slice(2 + ET_COUNTRY_CODE.length);
} else if (digits.startsWith(ET_COUNTRY_CODE)) {
national = digits.slice(ET_COUNTRY_CODE.length);
} else if (digits.startsWith('0')) {
national = digits.slice(1);
} else {
national = digits;
}
if (national.length !== ET_NATIONAL_LENGTH || !/^[79]/.test(national)) {
throw new Error(`Invalid Ethiopian mobile number: ${input}`);
}
return `${ET_COUNTRY_CODE}${national}`;
}
/** Mask an MSISDN for display/logging: `251923582676` → `2519****2676`. */
export function maskMsisdn(msisdn: string): string {
if (msisdn.length <= 8) return '****';
return `${msisdn.slice(0, 4)}****${msisdn.slice(-4)}`;
}