mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-01 17:03:27 +00:00
feat(types): own the currency list in one place
PAYMENT_CURRENCIES was copy-pasted into six files — four freight DTOs and two portal zod schemas — with no owner, and @edr/api-common's exchange service kept a seventh copy of its own as a bare "USD" | "ETB" union. They had already drifted: the DTOs and the exchange service did not agree on what the platform could price in, so a form could offer a currency the rate service had never heard of. Adds PAYMENT_CURRENCIES (now including DJF), the PaymentCurrency type and an isPaymentCurrency guard for the free-string currency columns the ORMs hand back. Every existing list re-exports this one instead of becoming a seventh copy. Also carries the two rules that have to travel with the list: CURRENCY_DECIMALS, because DJF is a zero-decimal currency — an amount with centimes is not a more precise payment, it is a malformed one, and CAC Bank rejects it. roundMoney() applies it. PROVIDER_CURRENCIES, which gateway settles which currency. Only CBE_BILL carries a restriction the code can evidence (CBE settles ETB, plan D8); eBirr, Waafi and D-Money each pass input.currency to the provider verbatim with an explicit comment saying so. The other rows are therefore permissive on purpose rather than guessed — settlement currency is really a property of the merchant account, not the provider brand, and tightening a row on a hunch would break a live flow.
This commit is contained in:
@@ -245,3 +245,76 @@ export interface PaymentFailedEvent extends PaymentEventBase {
|
||||
}
|
||||
|
||||
export type PaymentEvent = PaymentSucceededEvent | PaymentFailedEvent;
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* Currency
|
||||
*
|
||||
* The single owner of "which currencies this platform bills in". Before this existed the list
|
||||
* was copy-pasted into six files (four freight DTOs and two portal zod schemas) with no owner,
|
||||
* and `@edr/api-common`'s exchange service kept a seventh copy of its own.
|
||||
* ---------------------------------------------------------------------------------------------- */
|
||||
|
||||
/** Currencies the platform can price, invoice and settle in. */
|
||||
export const PAYMENT_CURRENCIES = ["ETB", "USD", "DJF"] as const;
|
||||
|
||||
export type PaymentCurrency = (typeof PAYMENT_CURRENCIES)[number];
|
||||
|
||||
/** Narrowing guard for the free-string `currency` columns the ORMs hand back. */
|
||||
export function isPaymentCurrency(value: unknown): value is PaymentCurrency {
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
(PAYMENT_CURRENCIES as readonly string[]).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimal places each currency is quoted in. **DJF is a zero-decimal currency** — an amount
|
||||
* with cents is not a smaller payment, it is a malformed one, and CAC Bank rejects it.
|
||||
*/
|
||||
export const CURRENCY_DECIMALS: Record<PaymentCurrency, number> = {
|
||||
ETB: 2,
|
||||
USD: 2,
|
||||
DJF: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Which currencies each gateway will settle.
|
||||
*
|
||||
* Only CBE_BILL carries a restriction we can actually evidence: CBE settles ETB and the
|
||||
* adapter says so (docs/cbe/CBE_IMPLEMENTATION_PLAN.md D8). Every other adapter passes
|
||||
* `input.currency` to the provider verbatim — eBirr, Waafi and D-Money all carry an explicit
|
||||
* "charge exactly the currency the caller already converted to" comment — so no other row can
|
||||
* be tightened without inventing a rule the code does not have.
|
||||
*
|
||||
* ponytail: permissive on purpose. Settlement currency is really a property of the merchant
|
||||
* account, not the provider brand; tighten a row here once ops confirms what its merchant
|
||||
* account actually accepts, rather than guessing now and breaking a live flow.
|
||||
*/
|
||||
export const PROVIDER_CURRENCIES: Record<ProviderMethod, readonly PaymentCurrency[]> = {
|
||||
[ProviderMethod.CBE_BILL]: ["ETB"],
|
||||
[ProviderMethod.CBE_BIRR]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.TELEBIRR]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.CARD]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.EBIRR]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.WAAFI]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.DMONEY]: PAYMENT_CURRENCIES,
|
||||
[ProviderMethod.CAC_BANK]: PAYMENT_CURRENCIES,
|
||||
};
|
||||
|
||||
/** Whether `provider` can settle `currency`. Unknown currencies are never settleable. */
|
||||
export function providerSupportsCurrency(
|
||||
provider: ProviderMethod,
|
||||
currency: string | null | undefined,
|
||||
): boolean {
|
||||
const code = currency?.toUpperCase();
|
||||
if (!isPaymentCurrency(code)) return false;
|
||||
return PROVIDER_CURRENCIES[provider].includes(code);
|
||||
}
|
||||
|
||||
/** Rounds a money amount to its currency's precision — 0dp for DJF, 2dp for ETB/USD. */
|
||||
export function roundMoney(amount: number, currency: string | null | undefined): number {
|
||||
const code = currency?.toUpperCase();
|
||||
const decimals = isPaymentCurrency(code) ? CURRENCY_DECIMALS[code] : 2;
|
||||
const factor = 10 ** decimals;
|
||||
return Math.round((amount + Number.EPSILON) * factor) / factor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user