Hasib
Back to Blog
Database

MFS Production Conversation: "You Know One Duplicate Request Could Create Financial Mismatch, Right?"

Why duplicate cash-out and send-money requests from timeouts and retries can double-debit wallets and break ledger reconciliation in an MFS system — and how idempotency keys, request tracking, and ledger-based posting prevent it.

July 2, 202612 min read64 views

Today, I want to talk about one of the most important production questions in a Mobile Financial Services system.

The question looks simple:

"If a customer sends the same cash-out, send-money, or payment request twice because of timeout or network retry, what should the system do?"

At first, someone may answer:

Process the request again.

But in an MFS system, this answer can be dangerous.

A careless duplicate transaction can create double debit, double credit, wrong wallet balance, ledger mismatch, customer complaint, reconciliation failure, and serious financial risk.

This is where the concept of idempotency becomes extremely important.

Let me explain it through a conversation.


The Conversation: "You Know That Retry Could Debit the Customer Twice, Right?"

I was having a technical discussion with a senior MFS system engineer. We were talking about wallet transactions, ledger posting, API timeout, and financial reconciliation.

Then he asked me a very practical question.

Senior Engineer: "Suppose a customer sends 1,000 BDT from Wallet A to Wallet B. The request reaches the server, the transaction is processed, but the customer app does not receive the response because of a network timeout. The app retries the same request. What should happen?"

I replied quickly:

Me: "The system should process the second request also if it is valid."

He smiled and replied:

Senior Engineer: "You know that could debit the customer twice, right?"

That sentence changed the whole discussion.

I realized this was not just an API question. It was a production financial-system question.

In MFS, the real challenge is not only processing transactions.

The real challenge is processing each financial event exactly once from the business point of view, even if the network sends the same request multiple times.


The Hidden Danger Behind a Simple Retry

In normal software systems, retry is common.

If the request fails, retry again.

But in financial systems, retry is dangerous if it is not controlled.

Imagine this scenario:

Customer A sends 1,000 BDT to Customer B.

Step 1: App sends request to MFS API.
Step 2: MFS system debits Customer A.
Step 3: MFS system credits Customer B.
Step 4: Ledger entry is created.
Step 5: Response is sent to customer app.
Step 6: Network timeout happens before response reaches the app.
Step 7: App thinks the transaction failed.
Step 8: App retries the same request.

Now the dangerous question is:

Should the system process it again?

If yes, then this can happen:

First request:
Customer A debited 1,000
Customer B credited 1,000

Second retry:
Customer A debited another 1,000
Customer B credited another 1,000

Now the customer intended to send 1,000 BDT, but the system processed 2,000 BDT.

That is a financial incident.


Why Duplicate Requests Happen in MFS

Duplicate requests are very common in real production systems.

They can happen because of:

  • Mobile network timeout
  • Customer pressing the submit button multiple times
  • App retry mechanism
  • API gateway timeout
  • Load balancer retry
  • Message queue redelivery
  • Core service timeout
  • Database slow response
  • Partner bank or third-party API delay
  • SMS/USSD session instability
  • Client not receiving final transaction response

So, the system must assume:

The same transaction request may come more than once.

A mature MFS system should not depend on customer behavior or network stability.

It must protect the transaction internally.


The Core Concept: Idempotency

Idempotency means:

If the same request is submitted multiple times, the final business result should happen only once.

For example:

Request ID: TXN-20260702-000001
Amount: 1,000 BDT
From Wallet: 017XXXXXXXX
To Wallet: 018XXXXXXXX
Transaction Type: Send Money

If the exact same request comes again with the same request ID, the system should not create a new transaction.

Instead, it should return the previous result.

Example:

First request:
Processed successfully.
Transaction ID: MFS123456789

Second same request:
Do not process again.
Return previous result:
Transaction ID: MFS123456789
Status: Successful

That is idempotency.

The duplicate request is accepted, but the business effect is not repeated.


What Happens Without Idempotency?

Without idempotency, an MFS system can face serious problems.

1. Double Debit

The customer may be debited twice for one intended transaction.

Example:

Customer wanted to send: 1,000 BDT
Actual debit happened: 2,000 BDT

This creates direct customer impact.


2. Double Credit

The receiver may get money twice.

Example:

Receiver expected: 1,000 BDT
Receiver received: 2,000 BDT

Now reversing the extra amount becomes difficult, especially if the receiver already used the money.


3. Ledger Mismatch

MFS systems depend heavily on ledger accuracy.

Every debit and credit must match.

A duplicate transaction can create mismatch between:

Wallet balance
Ledger balance
Bank settlement account
Partner account
Commission account
Reconciliation report

Once ledger mismatch starts, it becomes very difficult to fix without a proper audit trail.


4. Customer Complaint

From the customer side, the issue looks simple:

"I sent money once, but my wallet was debited twice."

But from the system side, the investigation may involve:

  • API logs
  • Transaction table
  • Ledger table
  • Wallet balance table
  • SMS notification
  • App request logs
  • Gateway logs
  • Queue logs
  • Database logs
  • Reconciliation report

That is why MFS production systems must prevent the issue before it happens.


5. Reconciliation Failure

At the end of the day, MFS systems need reconciliation.

The system may need to reconcile with:

  • Core wallet ledger
  • Bank account
  • Partner bank
  • Merchant settlement
  • Agent commission
  • Distributor account
  • External payment gateway
  • SMS notification system
  • Reporting database

If duplicate transactions are not controlled, reconciliation becomes painful.


The Professional MFS Design: How to Handle Duplicate Transactions Safely

A good MFS system does not simply process requests blindly.

It follows a controlled transaction flow.


Method 1: Use a Unique Request ID

Every financial request should have a unique request ID.

Example:

{
  "request_id": "APP-20260702-000001",
  "from_wallet": "017XXXXXXXX",
  "to_wallet": "018XXXXXXXX",
  "amount": 1000,
  "transaction_type": "SEND_MONEY"
}

The system stores this request_id before or during transaction processing.

The database should enforce uniqueness:

CREATE UNIQUE INDEX uq_transaction_request_id
ON wallet_transaction(request_id);

This is very important.

Application-level checking is not enough.

The database must also protect against duplicate insertion.


Method 2: Check Request ID Before Processing

Before processing the transaction, the system should check:

Has this request_id already been received?

If not found:

Create new transaction.
Process debit.
Process credit.
Create ledger entries.
Return final response.

If found:

Do not process again.
Return the previous transaction status.

Example logic:

Receive request
   |
   v
Check request_id
   |
   |-- Already SUCCESS?
   |       Return previous success response
   |
   |-- Already FAILED?
   |       Return previous failure response
   |
   |-- Already PROCESSING?
   |       Return pending/in-progress response
   |
   |-- Not found?
           Create and process new transaction

This protects the system from duplicate financial impact.


Method 3: Store Transaction Status Properly

A transaction should have clear statuses.

Example:

INITIATED
PROCESSING
SUCCESS
FAILED
REVERSED
TIMEOUT
PENDING_RECONCILIATION

This is important because not every transaction finishes instantly.

Sometimes the wallet service is successful, but notification fails.

Sometimes the debit is successful, but credit confirmation is delayed.

Sometimes the internal transaction is successful, but the response does not reach the app.

The system must know the actual state.


Method 4: Use Ledger-Based Posting

A serious MFS system should not only update wallet balance directly.

It should maintain a proper ledger.

Example send-money transaction:

Debit Customer A wallet:       -1,000
Credit Customer B wallet:      +1,000
Debit Sender fee account:      -10
Credit Revenue account:        +10

Every financial event should produce ledger entries.

The ledger is the permanent financial record.

The wallet balance can be derived, checked, or reconciled from ledger movement.

A simplified ledger table may look like this:

CREATE TABLE wallet_ledger (
    ledger_id BIGSERIAL PRIMARY KEY,
    transaction_id BIGINT NOT NULL,
    wallet_id BIGINT NOT NULL,
    entry_type VARCHAR(10) NOT NULL,
    amount NUMERIC(18,2) NOT NULL,
    balance_after NUMERIC(18,2) NOT NULL,
    created_at TIMESTAMP DEFAULT now()
);

A duplicate request should not create a second ledger posting.

That is the key point.


Method 5: Lock the Wallet During Balance Update

For wallet balance update, concurrency control is very important.

Suppose two transactions are trying to debit the same wallet at the same time.

The system must prevent race conditions.

Example:

SELECT balance
FROM wallet_account
WHERE wallet_id = 1001
FOR UPDATE;

This locks the specific wallet row during the transaction.

Then the system checks balance and updates safely:

UPDATE wallet_account
SET balance = balance - 1000
WHERE wallet_id = 1001;

This prevents two transactions from reading the same old balance and both approving incorrectly.


Method 6: Return the Same Response for Same Request

This is an important idempotency rule.

For the same request ID, the system should return the same transaction result.

Example:

First request:
SUCCESS
Transaction ID: TXN100001

Retry request:
SUCCESS
Transaction ID: TXN100001

The retry should not create:

TXN100002

That would be a new transaction, which is wrong.


A Realistic MFS Transaction Flow

A safe send-money flow may look like this:

1. Customer submits send-money request.
2. API receives request with request_id.
3. System checks whether request_id already exists.
4. If request_id exists, return previous result.
5. If not exists, create transaction with INITIATED status.
6. Lock sender wallet.
7. Check sender balance.
8. Debit sender wallet.
9. Credit receiver wallet.
10. Create ledger entries.
11. Mark transaction SUCCESS.
12. Send notification.
13. Return response to customer.

If the customer retries after timeout:

1. Same request_id comes again.
2. System finds existing SUCCESS transaction.
3. System returns previous transaction result.
4. No new debit.
5. No new credit.
6. No new ledger posting.

This is how financial systems protect themselves.


The Dangerous Wrong Design

A weak design may look like this:

Receive request
Debit wallet
Credit wallet
Insert transaction
Return response

This design is dangerous because the transaction record may not be created early enough to detect duplicate retries.

A safer design is:

Receive request
Create transaction/request record
Then process financial posting
Then update final status

The request tracking must start before the risky financial operation.


What If the First Request Is Still Processing?

This is a very important case.

Suppose the first request is still processing and the same request comes again.

The system should not process the second request.

It should return something like:

{
  "status": "PROCESSING",
  "message": "Transaction is already being processed",
  "request_id": "APP-20260702-000001"
}

The app can then check transaction status using a status inquiry API.

Example:

GET /transaction/status/APP-20260702-000001

This is better than blindly retrying financial posting.


What If the First Request Failed?

This depends on business logic.

If the first request failed permanently, such as insufficient balance, then the retry with the same request ID should return the same failure.

Example:

First request:
FAILED - Insufficient balance

Retry request:
FAILED - Insufficient balance

But if the failure was technical and the transaction state is unclear, the system should not guess.

It should mark the transaction as:

PENDING_RECONCILIATION

or:

UNKNOWN

Then a reconciliation or reversal process should decide the final status.

In financial systems, guessing is dangerous.


MFS Interview Answer: How Should You Explain It?

If someone asks:

"How do you prevent duplicate transactions in an MFS system?"

A professional answer would be:

In an MFS system, duplicate requests are expected because of timeout, retry, network instability, app retry, queue redelivery, or third-party API delay. So I would design the transaction API to be idempotent.

Each financial request must carry a unique request_id or idempotency key. The system should store this key with a unique constraint in the database. Before processing a transaction, the system checks whether the same request already exists.

If the request already succeeded, the system returns the previous success response and does not debit or credit again. If it failed, it returns the previous failure. If it is still processing, it returns a pending status and asks the client to check the transaction status.

For wallet balance safety, I would use database transaction control, row-level locking, proper ledger posting, and atomic debit-credit logic. Every financial event must create balanced ledger entries. I would also monitor transaction status, reconciliation mismatch, duplicate request count, timeout rate, and failed transaction rate.

The goal is not only to process the request. The goal is to ensure that the same business transaction affects the wallet and ledger only once.

This answer shows that you understand real MFS production risk.

A normal developer may think:

Retry means process again.

But an MFS engineer thinks:

Retry means check previous state first.

That is the difference.


Final Thought

In MFS, one duplicate request is not just a technical bug.

It can become:

  • Customer balance mismatch
  • Ledger mismatch
  • Settlement mismatch
  • Reconciliation failure
  • Customer complaint
  • Financial loss
  • Regulatory and audit concern

So before designing any MFS transaction API, remember this:

A payment system should not trust the network. A wallet system should not trust retries. A ledger system should not allow the same financial event to be posted twice.

That is why idempotency is not an optional feature in MFS.

It is a production survival rule.

Sheikh Wasiu Al Hasib

Senior DevOps Engineer & DBA

Comments

Comments are reviewed before they're published.

No comments yet. Be the first to share your thoughts.