fix(billing): ceil CBE invoice amounts to whole birr

This commit is contained in:
Nathnael
2026-08-06 09:16:01 +00:00
parent eda3a82a6c
commit 23e756e0c7
4 changed files with 77 additions and 5 deletions

View File

@@ -668,3 +668,66 @@ describe("BillingService — CAC Bank (OTP debit)", () => {
expect(confirmOtp).toHaveBeenCalledWith("intent-1", "123456");
});
});
describe("BillingService — CBE bill amounts round UP to whole birr", () => {
// CBE bills whole birr. Ceil, never Math.round: a .40 balance rounded down
// settles 0.40 short while markInvoiceAsPaid still writes paidAmount =
// totalAmount — money missing from the bank with the books saying paid.
// payInvoice and billQuery must agree, or /cbe/payment sees a mismatch.
const invoice = {
id: "inv-1",
status: Freight.InvoiceStatus.Pending,
source: Freight.InvoiceSource.Booking,
sourceId: "booking-1",
type: "PREPAID",
invoiceNumber: "INV-20260101-00001",
currency: "ETB",
// .40 — the case Math.round gets wrong (rounds down, underpays).
balanceAmount: 12345.4,
totalAmount: 12345.4,
company: { name: "Acme PLC" },
paymentId: null,
dueAt: null,
};
const build = (payment: Record<string, unknown> = {}) => {
const repo = {
findOne: jest.fn().mockResolvedValue(invoice),
update: jest.fn().mockResolvedValue(undefined),
};
const service = new BillingService(
{ getRepository: () => repo } as never,
{} as never,
{} as never,
makeEvents() as never,
payment as never,
{} as never,
{} as never,
);
return { service, repo };
};
it("opens the intent for the ceiled balance, never below it", async () => {
const initiate = jest.fn().mockResolvedValue({
intentId: "intent-1",
immediateSuccess: false,
response: { intentId: "intent-1", status: "REQUIRES_ACTION" },
});
const { service } = build({ initiate });
await service.payInvoice("inv-1", { method: "CBE_BILL" });
expect(initiate).toHaveBeenCalledWith(
expect.objectContaining({ amountMinor: 12346 }),
);
});
it("quotes the same ceiled amount on bill-query as payInvoice opened", async () => {
const { service } = build();
await expect(service.billQuery("booking-1")).resolves.toMatchObject({
stillPayable: true,
currentAmountMinor: 12346,
});
});
});