Backend Development 12 min read

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.

macrozheng
macrozheng
macrozheng
How to Prevent Duplicate Payments in E‑Commerce: Strategies & Best Practices

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

unpaid

to a final state, there is an intermediate

paying

status.

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

paying

state, 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.

e-commercecachingdistributed lockpaymentPollingduplicate paymentrefund
macrozheng
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.