← Back to Blog
Abstract digital transaction visualization with glowing network nodes
Engineering

Avoiding Double Refunds: How We Solved a Race Condition in Our Payout System

How we used optimistic locking to prevent double refunds and maintain wallet accuracy under high concurrency.

Protize Engineering β€’
#fintech #payouts #concurrency #optimistic-locking #wallet-system

When two processes β€” the webhook response and the status-check job β€” handled failed payouts simultaneously, our users sometimes got refunded twice. This post explains how our payout system encountered a concurrency issue and how we fixed it using optimistic locking.


πŸ’₯ The Problem

At first glance, our payout flow looked simple:

  1. A user requests a payout.
  2. We send the request to our acquirer.
  3. The acquirer sends a webhook with the final status (success or failed).
  4. Separately, we run a scheduled status-check job to catch missed webhooks.
  5. If a payout fails β†’ we refund the amount to the user’s wallet.

But occasionally, both the webhook and the job would detect a failure at almost the same moment β€” and both triggered refunds. The result? The user’s wallet balance increased twice πŸ’ΈπŸ’Έ.


Abstract fintech automation concept with blue and purple tones

This concurrency race condition wasn’t frequent β€” but in financial systems, even one duplicate refund is unacceptable.


βš™οΈ The Solution: Optimistic Locking

Instead of introducing complex distributed locks, we implemented a lightweight optimistic locking mechanism at the wallet level.

How It Works

Optimistic locking assumes that conflicts are rare but possible. It works like this:

Example schema:

ALTER TABLE wallets ADD COLUMN version INT DEFAULT 0 NOT NULL;

Example update query:

UPDATE wallets
SET balance = balance + 100, version = version + 1
WHERE id = 123 AND version = 5;

If no row is affected (because the version has changed), the application knows someone else already updated it β€” avoiding double refunds.


Database transaction locks visualized as nodes with concurrency arrows


πŸ”‘ Benefits

Implementing optimistic locking brought immediate benefits:


πŸ§ͺ Testing the System

Before deploying, we stress-tested the new logic to ensure it handled real-world concurrency.

Test Scenarios:

Expected Outcome:


🧭 Lessons Learned

  1. Race conditions don’t always appear in development β€” but they always exist in distributed systems.
  2. Optimistic locking provides a clean, scalable safeguard without slowing down transactions.
  3. Monitoring and observability are just as important β€” logs must clearly show conflicts and retries.
  4. Small design improvements like this can save massive financial losses and improve user trust.

Server and code reflection in glass panels symbolizing concurrency safety

Final Thoughts

In payment systems, concurrency control is just as critical as correctness. By adding a version-based optimistic lock to our wallet updates, we eliminated duplicate refunds β€” without adding latency or operational complexity.

Simple fix. Huge impact.

β€” Protize Engineering Team

← Back to Blog