fix: duplicate invoice

This commit is contained in:
Nathnael
2026-07-02 09:44:56 +00:00
parent 5aafa436c1
commit 41fc0cabdf

View File

@@ -696,16 +696,17 @@ export class BillingService {
// ── Payment reconciliation (by source) ─────────────────────────────────────── // ── Payment reconciliation (by source) ───────────────────────────────────────
/** /**
* The invoice a gateway payment should settle for a source record, or null if * The invoice a source record already has open, or null if it needs a new
* none. This is the billing document of record for "what is owed" — callers * one. This is the idempotency check every `ensureInvoiceFor*` (booking,
* (e.g. {@link payInvoice}) charge `invoice.totalAmount` against it rather than * first-mile, last-mile) runs before generating — it must see DRAFT
* recomputing from the source's own total, so discounts/penalties/adjustments * invoices too, not just issued ones, otherwise a source that already has
* carried on the invoice are honored. * an unissued draft gets a second, duplicate invoice minted alongside it
* instead of that draft being reused and then issued.
* *
* Pass `type` to select a specific invoice when a source carries several (e.g. * Pass `type` to select a specific invoice when a source carries several (e.g.
* a booking's up-front vs final charge); omit it to settle whichever single * a booking's up-front vs final charge); omit it to settle whichever single
* invoice is currently open. Returns the most recent matching open (unpaid, * invoice is currently open. Returns the most recent matching draft-or-open
* non-cancelled) invoice. * (unpaid, non-cancelled) invoice.
*/ */
findPayable( findPayable(
source: Freight.InvoiceSource, source: Freight.InvoiceSource,
@@ -716,7 +717,7 @@ export class BillingService {
where: { where: {
source, source,
sourceId, sourceId,
status: In(OPEN_STATUSES), status: In([Freight.InvoiceStatus.Draft, ...OPEN_STATUSES]),
...(type ? { type } : {}), ...(type ? { type } : {}),
}, },
order: { issuedAt: "DESC" }, order: { issuedAt: "DESC" },
@@ -875,9 +876,7 @@ export class BillingService {
// service branches on a domain-specific reference type. // service branches on a domain-specific reference type.
referenceType: PaymentReferenceType.SHIPMENT, referenceType: PaymentReferenceType.SHIPMENT,
orderRef: invoice.invoiceNumber, orderRef: invoice.invoiceNumber,
// True minor units (cents) — every provider adapter divides by 100 to amountMinor: Math.round(Number(invoice.balanceAmount)),
// get the major amount it charges.
amountMinor: Math.round(Number(invoice.balanceAmount) * 100),
currency: invoice.currency, currency: invoice.currency,
reason: `Payment for invoice ${invoice.invoiceNumber}`, reason: `Payment for invoice ${invoice.invoiceNumber}`,
method: opts.method ?? "TELEBIRR", method: opts.method ?? "TELEBIRR",