Two bugs in payoutRetryService.retryPayout:
- The "already on-chain" path guarded the user-stats update with
NOT EXISTS (... WHERE id = this AND status = 'completed'), but it had just
marked that same row completed in the same transaction, so the guard was
always true-negative and stats were never counted. Replaced with a
conditional UPDATE ... WHERE status <> 'completed' RETURNING, so the row's
transition drives an exactly-once stat count.
- A successful retry wrote tx_hash in a standalone query and then marked the
payout completed in a separate transaction; a crash between the two left a
tx_hash on a non-completed row. Now tx_hash + completion + stats happen in
one transaction.
Adds payoutRetry.test.js (count-once, no-double-count, atomic completion).
Full suite: 16 suites / 134 tests.