mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
reject cents mismatch on CBE bills
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
import { amountsMatchToTheCent } from "./cbe-bill.service";
|
||||||
|
|
||||||
|
describe("amountsMatchToTheCent (CBE payment amount gate)", () => {
|
||||||
|
it("accepts the exact amount", () => {
|
||||||
|
expect(amountsMatchToTheCent(1234.34, 1234.34)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects a cents-only difference (the 1234.89 vs 1234.34 bug)", () => {
|
||||||
|
expect(amountsMatchToTheCent(1234.89, 1234.34)).toBe(false);
|
||||||
|
expect(amountsMatchToTheCent(1234.35, 1234.34)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects whole-unit differences", () => {
|
||||||
|
expect(amountsMatchToTheCent(1235.34, 1234.34)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("absorbs double-precision storage noise", () => {
|
||||||
|
expect(amountsMatchToTheCent(1234.34, 1234.3399999999999)).toBe(true);
|
||||||
|
// classic float artifact: 0.1 + 0.2 !== 0.3
|
||||||
|
expect(amountsMatchToTheCent(0.1 + 0.2, 0.3)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -38,8 +38,15 @@ import {
|
|||||||
/** Postgres unique_violation — the DB-level idempotency backstop firing on a concurrent duplicate. */
|
/** Postgres unique_violation — the DB-level idempotency backstop firing on a concurrent duplicate. */
|
||||||
const PG_UNIQUE_VIOLATION = "23505";
|
const PG_UNIQUE_VIOLATION = "23505";
|
||||||
|
|
||||||
/** Mirrors the short-pay tolerance already applied in handlePaymentEvent. */
|
/**
|
||||||
const AMOUNT_TOLERANCE = 0.01;
|
* CBE must pay the bill to the exact cent — compare in integer cents so
|
||||||
|
* double-precision storage noise (1234.34 stored as 1234.33999…) can neither
|
||||||
|
* mask nor fabricate a difference. A relative tolerance is wrong here: 1% of a
|
||||||
|
* 1234.34 bill would wave through anything up to ±12.34.
|
||||||
|
*/
|
||||||
|
export function amountsMatchToTheCent(a: number, b: number): boolean {
|
||||||
|
return Math.round(a * 100) === Math.round(b * 100);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate an intent's own terminal state into the same reason vocabulary the domain apps
|
* Translate an intent's own terminal state into the same reason vocabulary the domain apps
|
||||||
@@ -268,8 +275,7 @@ export class CbeBillService {
|
|||||||
const amount = Number(dto.Amount);
|
const amount = Number(dto.Amount);
|
||||||
if (
|
if (
|
||||||
!Number.isFinite(amount) ||
|
!Number.isFinite(amount) ||
|
||||||
Math.abs(amount - intent.amountMinor) >
|
!amountsMatchToTheCent(amount, intent.amountMinor)
|
||||||
intent.amountMinor * AMOUNT_TOLERANCE
|
|
||||||
) {
|
) {
|
||||||
throw new CbeBillError("Amount mismatch", "BUSINESS");
|
throw new CbeBillError("Amount mismatch", "BUSINESS");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user