Skip to content

fix(stellar): per-payment expiry failure isolation, event-cursor correctness, callback idempotency - #274

Merged
martinzhames merged 2 commits into
dupdab:mainfrom
tegaReed-crypto:feature/issue-192-195
Sep 1, 2026
Merged

fix(stellar): per-payment expiry failure isolation, event-cursor correctness, callback idempotency#274
martinzhames merged 2 commits into
dupdab:mainfrom
tegaReed-crypto:feature/issue-192-195

Conversation

@tegaReed-crypto

@tegaReed-crypto tegaReed-crypto commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

expireOldPayments aborts the whole batch on one failure

  • StellarMonitorService.expireOldPayments looped over expired payments calling
    stellar.invokeContract('expire', ...) with no per-iteration try/catch, so a
    single transient Soroban RPC failure aborted the entire batch and silently
    delayed expiry of every subsequent payment until the next ~30s cycle.
  • Each iteration is now wrapped in its own try/catch. On failure we log the error
    and raise a deduped STELLAR_MONITOR admin alert, then continue processing the
    remaining payments in the batch (mirroring the existing confirmPayment error path).

SorobanEventIndexer advances cursor past failed events

  • SorobanEventIndexer.pollEvents set latestSeenLedger from the RPC response's
    latestLedger before processing events and persisted that cursor regardless of
    whether some events failed to parse/dispatch and were sent to the DLQ.
  • The persisted cursor now advances only to the ledger of the last successfully
    dispatched event (or the RPC latestLedger for an empty page, where there is
    nothing to reprocess), so failed/DLQ'd events are re-fetched on subsequent polls
    instead of being silently skipped.

handlePartnerCallback has no idempotency check

  • SettlementsService.handlePartnerCallback looked up the settlement by
    payload.reference and unconditionally processed it, so retried partner webhooks
    re-invoked stellar.invokeContract('settle', ...), re-dispatched
    payment.settled webhooks, and re-sent settlement-completed emails.
  • The handler now short-circuits (logs and returns) when the settlement is already
    in the terminal state matching the callback intent (success +
    completed, or failed + failed).

Testing

  • tsc --noEmit introduces no new errors in the changed files (remaining
    diagnostics are pre-existing in untouched spec/admin files).
  • No new tests added, per instructions.

closes #192, closes #193, closes #194, closes #195.

…potency

- stellar-monitor: wrap each expireOldPayments iteration in try/catch so a
  single invokeContract failure no longer aborts the whole batch; log and
  raise an admin alert per failure then continue remaining payments (dupdab#192)
- soroban-event-indexer: advance the persisted cursor only to the ledger of
  the last successfully-dispatched event instead of the RPC-reported latest
  ledger, so failed/DLQ'd events are not skipped on subsequent polls (dupdab#193)
- settlements: short-circuit handlePartnerCallback when the settlement is
  already in the terminal state matching the callback intent, preventing
  duplicate on-chain settlement attempts and re-dispatch of merchant
  webhooks/emails on retried callbacks (dupdab#195)
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@tegaReed-crypto 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! 🚀

Learn more about application limits

@martinzhames
martinzhames merged commit 4217453 into dupdab:main Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants