Skip to content

feat: idempotent refunds - #1900

Merged
kryputh merged 2 commits into
Stellar-Mail:mainfrom
emarc99:feat/1546-idempotent-refunds
Jul 29, 2026
Merged

feat: idempotent refunds#1900
kryputh merged 2 commits into
Stellar-Mail:mainfrom
emarc99:feat/1546-idempotent-refunds

Conversation

@emarc99

@emarc99 emarc99 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Resolves issue by introducing durable idempotency support and atomic conflict handling for the postage refund endpoint (POST /api/v1/postage/:messageId/refund).

Problem

Refund and settlement requests can race or be retried after client timeouts or network disruptions. Without idempotency and atomic state transitions, retries could result in duplicate side-effects or ambiguous escrow states.

Changes Implemented

  1. Refund Route Idempotency (src/routes/api/v1/postage/$messageId/refund.ts):

    • Integrated optional X-Idempotency-Key header handling.
    • Uses acquireIdempotency to lease actor-scoped idempotency keys (scoped per recipient).
    • Replays stored successful responses (200 OK) and terminal-state error responses (409 Conflict) with header x-idempotency-replayed: true.
    • Handles in_progress concurrency locks with a 409 Conflict error.
    • Records successful refunds and terminal state conflict outcomes via recordIdempotency.
    • Added comprehensive JSDoc documentation and usage examples.
  2. Unit Tests (tests/unit/api/postage-refund-idempotency.test.ts):

    • Added 14 unit tests covering:
      • Deterministic terminal state errors (409 Conflict) when refunding already-refunded or already-settled postage.
      • Concurrency races ensuring only one refund side-effect occurs when multiple calls race without idempotency keys.
      • Concurrency races between settlement and refund operations (ensuring only one wins).
      • Refund losing cleanly after settlement has completed.
      • Success and terminal error replay via checkIdempotency and recordIdempotency.
      • Actor isolation ensuring different recipients cannot replay each other's idempotency records.
      • Network retry scenarios and data integrity preservation.
  3. Documentation:

    • Created docs/api/REFUND_IDEMPOTENCY.md with detailed API specifications, flow diagrams, error payload contracts, security/actor isolation rationale, and client implementation guidelines.
    • Updated docs/api/README.md and src/routes/api/v1/postage/README.md to reference REFUND_IDEMPOTENCY.md and the refund idempotency contract.

Acceptance Criteria Verification

  • Only one refund side effect occurs under concurrency.
  • Refund cannot win after settlement.
  • Retry after refund returns a deterministic replayed response (200 OK or 409 Conflict).
  • Concurrency tests cover settlement-versus-refund.

Fixes #1546

@kryputh
kryputh merged commit de16632 into Stellar-Mail:main Jul 29, 2026
5 checks passed
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.

Add idempotent postage refunds with atomic conflict handling

2 participants