Skip to content

Fix #507: enforce ledger close-time for invoice settlement classification - #527

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

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

Conversation

@s6pa1rta3n-lab

Copy link
Copy Markdown
Contributor

Summary

Resolves #507.

Enforces ledger close_time for invoice settlement classification across memory and Postgres storage backends, API endpoints, payment monitor polling, and frontend timeline/receipt surfaces.

Changes

  1. Shared Settlement Contract:

    • Centralized settlement classification logic in shared/settlement.ts, defining settlementFieldsForInvoice() and SettlementTimeUnavailableError.
    • Compares ledger close_time against invoice expires_at and cancelled_at to classify settlement as ON_TIME, AFTER_EXPIRY, or AFTER_CANCEL.
    • Produces standard warning codes: PAYMENT_RECEIVED_AFTER_EXPIRY and PAYMENT_RECEIVED_AFTER_CANCEL.
  2. Backend Services & Endpoints:

    • Updated Postgres InvoiceService.markAsPaid to accept settled_at, conditionally settling EXPIRED or PENDING invoices and setting settlement_context, prior_status, and late_payment_warning_code.
    • Updated MemoryStorage.markAsPaid to mirror Postgres behavior, rejecting missing settlement times on expired/cancelled invoices.
    • Updated verifyPayment in invoice.handlers.ts to pass Horizon created_at (close_time) as settledAt and fail closed with HTTP 503 TRANSACTION_CLOSE_TIME_UNAVAILABLE when close_time is unavailable.
    • Updated PaymentMonitorService to pass transaction created_at to verification and mark invoices paid with proper expiry settlement context.
  3. Frontend Timeline & Receipts:

    • Updated InvoiceTimeline and buildInvoiceTimelineEvents to display expired milestone before paid when settled AFTER_EXPIRY.
    • Updated PaymentReceipt and PDF exports to render warning banners when latePaymentWarningCode is present.
  4. Testing:

    • Added backend/tests/invoice-expiry-settlement.test.ts verifying on-time vs after-expiry classification, clock skew resilience, and fail-closed 503 behavior on missing close_time.
    • Added unit test in frontend/tests/invoice-workspace.test.js validating timeline sequence for after-expiry payments.

Acceptance Criteria Checklist

  • Payment with ledger close_time before expiresAt settles as ON_TIME even if verified after expiry.
  • Payment with ledger close_time after expiresAt settles as AFTER_EXPIRY with PAYMENT_RECEIVED_AFTER_EXPIRY warning.
  • Verification fails closed with HTTP 503 TRANSACTION_CLOSE_TIME_UNAVAILABLE when Horizon close_time is unavailable.
  • In-memory storage and Postgres database double exhibit identical classification behavior.
  • Payment monitor honors ledger close_time during automated sync cycles.
  • Frontend timeline displays created -> expired -> paid events chronologically for late payments.
  • Payment receipt and PDF export display late payment warning notices.
  • All 488 backend tests, 500 frontend tests, and 19 shared tests pass with zero errors.

Payout Routing

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

@CemAyyildiz

Copy link
Copy Markdown
Collaborator

This branch has merge conflicts with main. Please rebase and mark ready for review when CI is green. #576 targets the same issue (#507); only one 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.

Classify late settlement from Horizon close_time vs expiry and cancel

2 participants