keep cents in CBE bills and prices

This commit is contained in:
Marshal
2026-08-08 20:45:05 +00:00
parent aad9ebaf78
commit 80a14c176b
9 changed files with 65 additions and 34 deletions

View File

@@ -1341,11 +1341,11 @@ export class BillingService {
// service branches on a domain-specific reference type.
referenceType: PaymentReferenceType.SHIPMENT,
orderRef: invoice.invoiceNumber.replace(/-/g, "_"),
// Whole birr, always UP. CBE bills this amount verbatim, so it must never
// land below the outstanding balance — Math.round would let a .40 balance
// settle 0.40 short. Ceil overcharges by <1 birr instead, and the same
// ceil in billQuery keeps the quoted and debited amounts identical.
amountMinor: Math.ceil(Number(invoice.balanceAmount)),
// Exact balance, cents included. CBE bills this verbatim and /cbe/payment
// matches the debited amount to the cent (amountsMatchToTheCent), so any
// rounding here would overcharge the payer and leave the invoice balance
// non-zero. billQuery quotes the same unrounded value.
amountMinor: round2(Number(invoice.balanceAmount)),
currency: invoice.currency,
reason: `Payment for invoice ${invoice.invoiceNumber}`,
method: opts.method ?? "TELEBIRR",
@@ -1490,9 +1490,10 @@ export class BillingService {
});
if (open) {
// Ceil, matching payInvoice — the amount CBE quotes at the counter has to
// be the amount the intent was opened for, or /cbe/payment sees a mismatch.
const balance = Math.ceil(Number(open.balanceAmount ?? open.totalAmount));
// Unrounded, matching payInvoice — the amount CBE quotes at the counter has
// to be the amount the intent was opened for, to the cent, or /cbe/payment
// sees a mismatch.
const balance = round2(Number(open.balanceAmount ?? open.totalAmount));
const expired = open.dueAt && open.dueAt.getTime() < Date.now();
return {
stillPayable: balance > 0 && !expired,
@@ -1527,7 +1528,7 @@ export class BillingService {
return {
stillPayable: false,
payerName: latest.company?.name ?? null,
currentAmountMinor: Math.ceil(Number(latest.totalAmount)),
currentAmountMinor: round2(Number(latest.totalAmount)),
currency: latest.currency,
paymentReason: `Freight invoice ${latest.invoiceNumber}`,
reason: closedInvoiceReason(latest.status),