Skip to content

fix(domain): enforce terminal status invariants between paid and cancelled (#558) - #580

Draft
s6pa1rta3n-lab wants to merge 1 commit into
Quittance-Labs:mainfrom
s6pa1rta3n-lab:fix-issue-558
Draft

s6pa1rta3n-lab wants to merge 1 commit into
Quittance-Labs:mainfrom
s6pa1rta3n-lab:fix-issue-558

Conversation

@s6pa1rta3n-lab

Copy link
Copy Markdown
Contributor

Summary

Fixes #558 by enforcing explicit status transition invariants across the domain model, storage engines, payment monitor, and HTTP route handlers.

Changes

  • Domain (invoice-settlement.ts):
    • Defined explicit legal state transitions:
      • PENDING -> PAID | EXPIRED | CANCELLED
      • EXPIRED -> PAID
      • PAID -> terminal
      • CANCELLED -> terminal
    • Added IllegalStatusTransitionError and assertLegalStatusTransition().
    • Prohibited transitioning CANCELLED -> PAID in settlementFieldsForInvoice().
  • Storage (memory-storage.ts, invoice.service.ts):
    • Removed late settlement override for CANCELLED invoices.
    • Enforced IllegalStatusTransitionError on attempts to settle a cancelled invoice.
    • Restricted Postgres CTE mutation strictly to status IN ('PENDING', 'EXPIRED').
  • Payment Monitor (payment-monitor.service.ts):
    • Rejects incoming Stellar transactions destined for cancelled invoices.
    • Emits PAYMENT_REJECTED audit event with code INVOICE_NOT_PENDING.
  • HTTP Handlers (invoice.handlers.ts):
    • POST /invoices/:id/verify rejects payment verification for cancelled invoices with 400 INVOICE_NOT_PENDING.
  • Frontend State Machine (payment-page-state.js, payment-page-state.d.ts):
    • Added CANCELLED as a terminal payment state with descriptive screen-reader text.
    • Refreshes invoice state in invoice detail view upon cancel conflict.
  • Documentation (docs/EXPIRY-AND-LATE-PAYMENT.md):
    • Updated transition table to mark CANCELLED as terminal.

Verification

  • Backend test suite: 772 passing tests across storage doubles, race conditions, handlers, and abuse controls.
  • Frontend test suite: 600 passing tests across payment page states, a11y, and components.

Payout Routing

  • EVM (Base/Arbitrum/Polygon/ETH): 0xF46C9F6d70C50BF81ef3588AB523a90a594a2F89
  • Stellar: GCL6OXAMLD75BMTINA6EMRUDWK5THQUSHMYNLSNBCJAPZJHNYJTUNIBC

@CemAyyildiz

Copy link
Copy Markdown
Collaborator

Thanks for this. CI was green, but #577 (Postgres storage wiring for #555) just landed on main and this branch now conflicts in three test files:

  • backend/tests/cross-seller-isolation.test.ts
  • backend/tests/invoice-dual-backend.test.ts
  • backend/tests/invoice-handlers.test.ts

The conflicts are small (a comment, a reformatted markAsPaid call, and a payment fixture that main now builds with the paymentTransaction() helper). Please rebase on main, take main's side in those hunks while keeping your other changes in the same files, and mark the PR ready for review once CI is green again.

Note: #563 targets the same issue (#558); only one of the two will be merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cancel, manual verify, and the monitor must commit one terminal invoice outcome

2 participants