Skip to content

2ac46f80 - Add resume endpoint for stopped buy-crypto transactions - #4625

Merged
TaprootFreak merged 10 commits into
developfrom
feat/transaction-admin-resume
Aug 4, 2026
Merged

2ac46f80 - Add resume endpoint for stopped buy-crypto transactions#4625
TaprootFreak merged 10 commits into
developfrom
feat/transaction-admin-resume

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What

Adds POST /transaction/admin/:id/resume (COMPLIANCE role) as the counterpart to the existing POST /transaction/admin/:id/stop. It sets a stopped buy-crypto back to Created, so batchAndOptimizeTransactions picks it up again automatically.

Why

A stopped buy-crypto is excluded from every automated process (batching, fee refresh) and there is no code path back — resuming currently requires a manual DB update. Compliance needs to be able to un-stop a transaction once the reason for the stop is resolved (e.g. liquidity has been restored).

Fail-closed by design

Only a transaction that never left the pre-batching stage can be resumed. The preconditions are checked twice: as explicit guards for a meaningful error message, and again inside the WHERE clause of a single conditional update, so a concurrent change between read and write cannot slip through (same pattern as stop() and BuyCryptoService.refundClaimWhere).

Condition Response
Transaction does not exist 404
Not a BuyCrypto 400
Status is not Stopped 400
amlCheck is not Pass 400
Already batched or paid out (batch, txId) 400
Chargeback requested or executed 400
State changed concurrently (affected !== 1) 409

Automatic stops for blocked users set amlCheck=Fail and therefore stay blocked; a transaction whose refund is already in motion can never be pushed back into payout.

Changes

  • BuyCrypto.resume() entity method (mirrors stop(), returns the UpdateResult tuple)
  • TransactionService.resume(id): guard chain + atomic conditional update, ConflictException on a lost race
  • TransactionAdminController: POST :id/resume, same guard stack as :id/stop
  • 8 unit tests covering the happy path (asserting the full update criteria), every guard, and the conflict case

No entity/column changes, so no migration. Frontend counterpart: DFXswiss/services#1254.

@TaprootFreak
TaprootFreak force-pushed the feat/transaction-admin-resume branch from ea48add to e0b2ed6 Compare August 3, 2026 22:18
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Six review passes were needed to reach zero findings. What changed along the way:

  1. Switched to the repository's UpdateResult pattern (targeted update instead of saving a full entity) and added a test for the 404 guard.
  2. Blocked resuming a transaction that is already batched or paid out — without that guard a resumed transaction would have been stranded outside the batcher.
  3. Made the write atomic: every precondition is repeated in the WHERE clause of a single conditional update, with ConflictException when the row no longer matches. Chargeback markers were added to the guard set.
  4. Asserted the complete update criteria in the happy-path test — it previously checked 3 of 13 conditions, so dropping one would have gone unnoticed.
  5. Blocked resuming when a refund or forward already started on a related row: CheckoutTx (card refunds) and CryptoInput (pay-in return/forward) carry that state, and neither is visible on the buy_crypto row. Same invariant set as resetAmlCheckForReview, with the same pessimistic locks.
  6. Fixed the locking itself: the locked rows were re-read but their result discarded, so the guards still evaluated the pre-lock snapshot. The locked rows are now the ones evaluated, and two tests model exactly that race.

Verified locally on every push: format:check, lint, type-check and the suite (21 tests). The Coverage check needed one re-run after a node-gyp install failure on the runner (unrelated to this change); all 12 checks are green.

@TaprootFreak
TaprootFreak marked this pull request as ready for review August 3, 2026 23:42
@TaprootFreak
TaprootFreak merged commit a9d29a4 into develop Aug 4, 2026
17 of 18 checks passed
@TaprootFreak
TaprootFreak deleted the feat/transaction-admin-resume branch August 4, 2026 00:43
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.

1 participant