Fix/refund atomicity and batch transaction - #280
Merged
martinzhames merged 5 commits intoSep 1, 2026
Merged
Conversation
…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.
|
@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! 🚀 |
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.
Closes #164
Closes #163
Closes #162
Closes #161