refactor(invoice): drop dead paid/paidStamp; classify VOIDED; warn on unknown payment status
Audit cleanup of payment-status code paths uncovered while shipping the partial-refund fix: #1 Drop `viewModel.paid` (boolean). It was set from `displayFinancialStatus === "PAID"` and never read anywhere. With refunds in the picture it had become a footgun: a fully refunded order that started PAID would still satisfy `paid === true`, but `paymentStatus === "refunded"`. Callers should use `paymentStatus` / `requiresPayment` exclusively. #2 Remove the unused `paidStamp` translation ("BEZAHLT" / "PAID"). Defined in both locales but never rendered. #3 Classify VOIDED orders as a distinct `"voided"` payment status (rendered "Annulliert" / "Voided") instead of "unpaid". A voided order had its authorisation cancelled before capture — no money was received and none is owed. The previous "Offen" / "Outstanding" label combined with a GiroCode would have invited the customer to pay an order that's already been called off. `requiresPayment` now also excludes `"voided"`, so GiroCode + payment-terms paragraph are suppressed (mirrors the `"refunded"` treatment). "Annulliert" is used in German rather than "Storniert" to avoid confusion with our storno cancellation document concept. #6 `derivePaymentStatus` now logs a `console.warn` when it encounters a non-empty `displayFinancialStatus` value that isn't one of the documented Shopify enum members (PAID, PARTIALLY_PAID, REFUNDED, PARTIALLY_REFUNDED, VOIDED, PENDING, AUTHORIZED, EXPIRED). Future Shopify enum additions will surface in logs instead of silently mapping to "unpaid". EXPIRED stays mapped to "unpaid" — abandoned-checkout-style edge case left intentionally for a separate decision (#4 in the audit). Verification: render-sample now also exercises a VOIDED fixture (status row "Annulliert", no GiroCode, no payment terms). tsc / smoke / tests / build all green.
This commit is contained in:
@@ -635,6 +635,29 @@ async function main() {
|
||||
assert("requiresPayment still false", vmFull.requiresPayment === false);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// VOIDED: authorisation cancelled before capture. No money received,
|
||||
// none owed. Must classify as "voided" (not "unpaid") and suppress
|
||||
// GiroCode + payment terms.
|
||||
// ----------------------------------------------------------------
|
||||
console.log("• Voided order (VOIDED) classifies as voided, no GiroCode");
|
||||
{
|
||||
const voidedOrder = { ...buildAtB2BOrder(), displayFinancialStatus: "VOIDED" };
|
||||
const voidedVm = composeInvoice({
|
||||
order: voidedOrder, settings: settings as never, invoiceNumber: "RE-1018",
|
||||
});
|
||||
assertEq("paymentStatus=voided for VOIDED", voidedVm.paymentStatus, "voided");
|
||||
assert("requiresPayment=false for voided", voidedVm.requiresPayment === false);
|
||||
voidedVm.issuer.logoDataUrl = vm.issuer.logoDataUrl;
|
||||
const voidedText = await pdfToText(await renderInvoicePdf(voidedVm));
|
||||
assert("Voided PDF shows the 'Annulliert' status row",
|
||||
voidedText.includes("Annulliert"));
|
||||
assert("Voided PDF does NOT show GiroCode caption",
|
||||
!voidedText.includes("GiroCode"));
|
||||
assert("Voided PDF does NOT show DE payment terms",
|
||||
!voidedText.includes("Bitte überweise"));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Footer note translation
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user