Skip to content

Fix/refund atomicity and batch transaction - #280

Merged
martinzhames merged 5 commits into
dupdab:mainfrom
janetpius-cmd:fix/refund-atomicity-and-batch-transaction
Sep 1, 2026
Merged

Fix/refund atomicity and batch transaction#280
martinzhames merged 5 commits into
dupdab:mainfrom
janetpius-cmd:fix/refund-atomicity-and-batch-transaction

Conversation

@janetpius-cmd

Copy link
Copy Markdown
Contributor

Closes #164
Closes #163
Closes #162
Closes #161

…t double-payout

Wraps the refund read-check-transfer-write sequence in a DB transaction
using manager.findOne(..., { lock: { mode: 'pessimistic_write' } })
instead of a plain findOne() + separate save(). Previously, two
concurrent POST /payments/:id/refund calls for the same settled
payment (double-click, retried request) could both pass the
status !== SETTLED check before either had saved, sending two real
Stellar payments for one logical refund.
…und amount

Adds PaymentStatus.PARTIALLY_REFUNDED and changes refund() to sum
refundAmountUsd across multiple calls instead of overwriting it, and
to guard against refunding more than the remaining un-refunded
balance rather than the full original amount. Previously, a single
partial refund incorrectly flipped status to REFUNDED (implying the
full amount was returned) and blocked any further legitimate partial
refund via the status !== SETTLED check.
Splits refund() into two phases: the Stellar transfer + DB save keep
their own try/catch (the genuine refund-failure path), while webhook
dispatch and notification emails run afterward in separate best-effort
try/catch blocks that log on failure instead of throwing. Previously a
webhook or email error after a successful Stellar payout was caught by
the same catch block and reported to the caller as "Stellar refund
failed", which could mislead a merchant into manually re-attempting a
refund that had already gone through.
createBatch()'s docstring claims the entire batch reverts if any
single entry is invalid and "no partial writes ever reach the
database," but the actual persistence step was a plain
paymentsRepo.save(records) on an array, which TypeORM does not
guarantee is atomic against DB-level failures (e.g. a unique
constraint collision on a later item, or connection loss mid-batch).
Wrapping the save in dataSource.transaction() backs that atomicity
claim with an actual database transaction.
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@janetpius-cmd 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 106dc45 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