feat: validate refund path on invoice cancellation - #313
Open
blizzj wants to merge 1 commit into
Open
Conversation
- cancel_invoice (contracts/invoice): Paid invoices now transition to RefundRequested instead of being rejected, preventing funds from getting stuck. Added AlreadyRefundRequested error variant (= 11) for the case where a refund is already in progress. Terminal states (Expired, Released, Cancelled) return NotPending. - cancel_invoiced (COMEBACKHERE-contracts/invoice): Same match-based status-transition guard. Paid → RefundRequested (emits invoice_refund_req event). AlreadyRefundRequested returned when refund already open. Terminal states return InvoiceCancelled. Also adds missing caller.require_auth() enforcement. Tests added (contracts/invoice): - test_cancel_pending_invoice_no_fund_movement - test_cancel_paid_invoice_transitions_to_refund_requested - test_cancel_refund_requested_invoice_returns_already_refund_requested - test_cancel_expired_invoice_returns_not_pending Tests added (COMEBACKHERE-contracts/invoice): - test_cancel_pending_invoice_no_fund_movement - test_cancel_pending_invoice_by_customer_succeeds - test_cancel_paid_invoice_transitions_to_refund_requested - test_cancel_refund_requested_invoice_returns_already_refund_requested - test_cancel_already_cancelled_invoice_returns_invoice_cancelled - test_cancel_by_stranger_returns_unauthorized - test_cancel_nonexistent_invoice_returns_not_found - test_cancel_when_paused_returns_contract_paused
|
@blizzj Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cancel_invoice and cancel_invoiced previously rejected any cancellation attempt on a non-Pending invoice
with a generic error, leaving funds permanently stuck in escrow with no valid state transition when an
invoice had already been paid.
This PR replaces the single status guard with an exhaustive match over all invoice statuses, ensuring
every cancellation attempt either succeeds with a clear state transition or fails with a meaningful error.
Status transition table
┌────────────────────────┬────────────────────────┬────────────────────────────────────────────────────┐
│ Status at cancellation │ Before │ After │
├────────────────────────────────┼────────────────────────┼────────────────────────────────────────────┤
│ Pending │ ✅ → Cancelled │ ✅ → Cancelled (unchanged) │
├────────────────────────────────┼────────────────────────┼────────────────────────────────────────────┤
│ Paid │ ❌ Error (funds stuck) │ ✅ → RefundRequested (opens refund path) │
├────────────────────────────────┼────────────────────────┼────────────────────────────────────────────┤
│ RefundRequested │ ❌ Generic error │ ❌ AlreadyRefundRequested (refund already │
│ │ │ in progress) │
├────────────────────────────────┼────────────────────────┼────────────────────────────────────────────┤
│ Expired / Released / Cancelled │ ❌ Generic error │ ❌ NotPending / InvoiceCancelled (terminal │
│ │ │ state, clear error) │
└────────────────────────────────┴────────────────────────┴────────────────────────────────────────────┘
When a Paid invoice is cancelled, the status transitions to RefundRequested, feeding directly into the
existing release_escrow flow so funds can be returned without any additional plumbing.
Changes
contracts/invoice/src/lib.rs
AmountPrecision = 12)
COMEBACKHERE-contracts/contracts/invoice/src/lib.rs
entirely)
Tests added
contracts/invoice (4 new tests):
COMEBACKHERE-contracts/invoice (7 new tests):
Testing
cargo test --package invoice
│⚠️ cargo is not available in the authoring environment. CI (ci-contracts.yml) will provide the full test
output.
closes #189