Ten Pitfalls of ECPay Integration: Lessons from a Team That Takes Orders Every Day
ECPay is probably the most widely used payment gateway in Taiwanese e-commerce, and its documentation is reasonably complete. But once you're live and taking real orders, you discover some pitfalls the docs don't spell out — and some they do, which you won't notice until your first real order gets stuck. Our own store uses the full ECPay family — credit cards, convenience-store payment codes, cash on delivery, convenience-store pickup logistics (7-Eleven/FamilyMart), Apple Pay — taking real orders every day. Each of the ten pitfalls below has an order number behind it.
Pitfalls during integration
- CheckMacValue validation failures are 90% encoding problems. Parameters must be sorted alphabetically, URL-encoded, and then passed through ECPay's specified character substitutions (which emulate .NET's encoding behavior). Hand-rolling this, it's easy to die on the two possible encodings of the space character. Use the official SDK, or write the substitution table into your test cases.
- A MerchantTradeNo, once used, can never be reused. Including on a failed payment. If a customer's card is declined and they want to retry, you must generate a new transaction number — you cannot resubmit the old one. Our approach is to separate the order number from the payment transaction number: every submission to the gateway gets a fresh transaction number with a serial suffix.
- ReturnURL and OrderResultURL are two different things. ReturnURL is the server-to-server payment notification; OrderResultURL is the foreground redirect that sends the shopper back to your site. Order status may only trust the former. Never update payment state in the latter — the shopper closes the browser, and your order sits at "processing" forever.
Pitfalls after you start taking orders
- Payment notifications get resent — your handler must be idempotent. If ECPay doesn't receive your success response string "1|OK", it retries. Without idempotency in your notification handler, you get duplicate emails, duplicate invoices, even duplicate loyalty points. Every action triggered by payment must be safe to execute a second time. Related warning: if any downstream logic inside the handler throws an exception, you may fail to return the success string, triggering unnecessary resends — splitting "acknowledge the notification" from "downstream processing" is the sturdier architecture.
- Don't rely on callbacks alone — actively query the order. Anything can get lost on the network. We've seen notifications delayed by more than ten minutes while the customer was already asking on LINE, "I paid — why isn't it confirmed?" The fix: in addition to the callback, run a scheduled job that actively queries order status. Double insurance.
- Convenience-store payment codes expire — expired orders need an exit. Code-based payment has inherent drop-off (plenty of people take the code and never pay), so design automatic cancellation on expiry, stock release, and payment-reminder recovery messages. Skip this and your inventory gets locked up by orders that will never be paid — fatal during campaigns.
- All-green in the test environment doesn't mean production will work. Test-environment card numbers always succeed, and payment codes never require an actual trip to the store — many paths simply never get exercised. Before launch, run the entire chain with real payments in small amounts. This matters enough that we wrote a separate piece: Your first order shouldn't be a debugging session.
Pitfalls in logistics and advanced features
- Changing a cash-on-delivery amount: there is no "edit" option. If the customer changes the order and the amount shifts, the logistics order's amount can't be modified — it can only be voided and recreated, and the new one needs a brand-new transaction number (see pitfall 2). We built void-and-reissue as a one-click back-office action; details in our convenience-store logistics notes.
- The logistics query API has versions. The older query returns incomplete status fields; after getting burned, we moved everything to the V5 query. Before integrating, confirm which version of the docs you're reading.
- Apple Pay isn't just a switch you flip. It requires a separate activation application and domain verification, and testing needs a real Apple device. Budget that application back-and-forth into your schedule.
The hard part of payment integration isn't the first successful order — it's the first order that goes off-script.
All ten of these pitfalls were paid for with real orders. If you're about to integrate payments — or already have, but something never feels quite stable — see our e-commerce build services. The full payments–invoicing–logistics chain is something we operate every day, not something we skim the docs for when a project comes in.
We solve these problems on our own products every day
Free 30-min discovery call · No hard sell · Reply within one business day
Keep Reading