How to Prevent Duplicate Payments in E‑Commerce: Strategies & Best Practices
This article explains the complete e‑commerce payment flow, identifies why orders can be paid twice—including lack of deduplication, lost orders, and multi‑channel issues—and presents practical solutions such as distributed locking, result caching, transaction cancellation, refunds, active polling, and sync‑async notifications to reliably avoid duplicate payments.
Order Payment Process Overview
Below is a brief overview of the e‑commerce order payment flow.
Steps from order creation to payment:
Order/Checkout : The order is created with a status of "unpaid"; payment‑related amounts are determined here.
Apply for Payment : The user initiates payment, the client calls the payment service, and a payment record with status "unpaid" is generated.
Initiate Payment : The payment service contacts a third‑party provider, receiving payment links that the client processes.
Wallet Payment : The user completes payment via a wallet. The handling differs across platforms:
APP: Typically launches the wallet directly.
WAP: Mobile web pages launch the wallet; if it fails, they fall back to a redirect.
PC: Shows a QR code for the user to scan with a wallet.
Payment Callback : After the user pays, the third‑party platform notifies the merchant of the result.
Synchronize Order Status : Once payment is confirmed, the payment service updates the order status from "unpaid" to "ready to ship"; the client reflects this via polling, long‑polling, or push notifications.
Payment record status transitions:
From
unpaidto a final state, there is an intermediate
payingstatus.
During the period when the user opens the wallet, completes payment, and the callback is pending, the record remains in
paying.
Why duplicate payments occur?
Unprotected Duplicate Payments
On PC, scanning different QR codes can generate multiple payment records; if the user clicks pay repeatedly, two distinct QR codes may be scanned, causing duplicate payments.
Lost Orders (Drop Orders)
When the payment result is not synchronized promptly, the order may still appear unpaid, prompting the user to place another order.
External drop: third‑party payment status fails to sync to the shop.
Internal drop: payment service does not update the order or the client does not fetch the latest status.
Multi‑Channel Payments
Using different payment methods (e.g., Boleto then PayPal) can lead to multiple successful transactions for the same order.
How to Prevent Duplicate Payments
Locking
Both the "apply for payment" and "payment callback" steps should acquire a lock at the order level to prevent concurrent duplicate operations, typically using a Redis distributed lock.
Caching Results
Cache the outcome of successful payment applications and callbacks; subsequent attempts should first check the cached status.
Cancel In‑Progress Transactions
If a duplicate payment request arrives while a previous one is still in the
payingstate, cancel the ongoing transaction before proceeding.
Refund Completed Transactions
When a new payment is initiated while another is still pending and the third‑party cannot cancel the original, allow the new payment and refund the earlier successful transaction after confirming the final status.
Active Polling & Retry to Prevent Drop Orders
Active Polling for External Drops
If callbacks are missed, start polling the payment status a few seconds after the user initiates payment, gradually increasing the interval (e.g., 3 s, 10 s, 30 s, 3 min).
Scheduled Task Polling : Scan payment records periodically; drawbacks include database load and fixed intervals.
Delayed Message Polling : Use delayed messages to query status, reducing DB pressure but adding implementation complexity.
Sync + Async for Internal Drops
After receiving an async callback or completing a poll, the payment service notifies the order service synchronously (with retries) and also sends an async message to ensure eventual consistency.
Sync call may fail due to network issues; retries are possible.
Async notification leverages message‑queue retry mechanisms.
Clients should receive updates via pull (periodic polling when returning to the order page) or push (WebSocket for web, third‑party push services for apps).
Minimize External Jumps on Client Side
Ideally, the client should complete payment within the app without redirecting to external pages; modern wallets like Alipay support in‑app payments, improving user experience and success rates.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.