fix(payment-providers): stop halving every non-DJF CAC Bank amount

Both callers already hand the provider a major-currency amount before this
call — freight sends invoice.balanceAmount (e.g. 700 for $700), passenger
sends CurrencyService's converted charge amount — with the target currency's
own decimal precision already applied (0dp DJF, 2dp USD/ETB). CAC's
toMajorAmount then divided anything that wasn't DJF by 100 on top of that,
so a USD 700 charge reached the bank as 7.00. This never surfaced because
CAC has only ever been wired for DJF (passenger) until now.

Also scoped the bank's documented 10-100,000 bound to DJF — it's the only
currency the spec states bounds for, so a USD amount outside that DJF-shaped
range is no longer rejected locally; an amount genuinely out of range still
comes back as a bank rejection.

Passenger is unaffected: its CAC currency has always been DJF, and the DJF
branch's behavior (amount unchanged, bounds enforced) is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathnael
2026-07-31 10:33:46 +00:00
parent 991881108d
commit a975aa7547
2 changed files with 123 additions and 11 deletions

View File

@@ -27,7 +27,9 @@ import type {
CacPaymentInitiateResponse,
} from "./cac-bank.types";
/** Bank-enforced amount bounds (PaymentInitiateRequest spec: between 10 and 100,000 DJF). */
/** Bank-enforced amount bounds (PaymentInitiateRequest spec: between 10 and 100,000 DJF). Not
* documented for other settlement currencies (e.g. USD) — skip the local pre-check there and
* let the bank's own validation reject an out-of-range amount. */
const CAC_MIN_AMOUNT = 10;
const CAC_MAX_AMOUNT = 100_000;
@@ -73,10 +75,14 @@ export class CacBankProvider implements PaymentProvider, OnModuleInit {
}
const customerMobile = normalizeCacMobile(input.payerAccount);
const amount = this.toMajorAmount(input.amountMinor, input.currency);
if (amount < CAC_MIN_AMOUNT || amount > CAC_MAX_AMOUNT) {
const amount = this.toMajorAmount(input.amountMinor);
const currency = (input.currency || this.defaultCurrency).toUpperCase();
if (
currency === "DJF" &&
(amount < CAC_MIN_AMOUNT || amount > CAC_MAX_AMOUNT)
) {
throw new Error(
`CAC Bank amount ${amount} ${input.currency} is outside the accepted range ` +
`CAC Bank amount ${amount} DJF is outside the accepted range ` +
`(${CAC_MIN_AMOUNT}${CAC_MAX_AMOUNT} DJF)`,
);
}
@@ -85,7 +91,7 @@ export class CacBankProvider implements PaymentProvider, OnModuleInit {
app_key: this.appKey,
api_key: this.apiKey,
customer_mobile: customerMobile,
currency: input.currency || this.defaultCurrency,
currency,
desc: `${input.orderRef}`.slice(0, 500),
vender_ref: input.merchantOrderId,
amount,
@@ -303,12 +309,14 @@ export class CacBankProvider implements PaymentProvider, OnModuleInit {
return this.auth;
}
/** DJF has no fractional units — amountMinor is the major amount. */
private toMajorAmount(amountMinor: number, currency: string): number {
if (currency.toUpperCase() === "DJF") {
return amountMinor;
}
return amountMinor / 100;
/**
* Both callers (freight's invoice.balanceAmount, passenger's CurrencyService) already
* hand off a major-currency amount — e.g. 700 for $700, not 70000 cents — with the target
* currency's own decimal precision already applied (0dp for DJF, 2dp for USD/ETB). CAC Bank
* bills in that same major unit, so it is forwarded unchanged.
*/
private toMajorAmount(amountMinor: number): number {
return amountMinor;
}
private sanitizeKeys(