The Hard Part of Payments Is the Order State Loop

One of the first judgments that changed for me while building payment systems was this: payment is not a button, and it is not a channel SDK.

The frontend sees a cashier page, QR code, H5 redirect, or JSAPI invocation. The business sees whether an order was paid, whether it can be refunded, and whether the status is accurate. The system has to connect merchant clients, orders, payment channels, callbacks, refunds, and backend management into a state loop.

If that loop is unclear, the payment page can still feel smooth while the backend becomes hard to explain. The user says they paid, the system says they did not. A channel callback arrived, but the order did not update. A refund was triggered, but the backend does not know whether it succeeded.

The Cashier Page Is Only the Entry

The cashier page is the most visible part of a payment system.

It shows the amount, description, and available payment methods, then sends the user to WeChat H5, WeChat QR payment, WeChat JSAPI, Alipay H5, or Alipay PC payment. But the cashier page should not own too much business judgment. It is a stable entry point that guides the user into the correct channel.

The business state belongs to the order. The order needs to know which merchant it belongs to, how much it costs, what status it is in, whether a refund happened, and what the channel returned. Cashier pages can change. Payment channels can be added. Order state cannot be vague.

Channel Differences Need a Boundary

WeChat Pay and Alipay both solve payment, but their APIs, signatures, callback styles, H5/QR/JSAPI parameters, and refund behavior differ.

If every controller writes channel details directly, the system becomes difficult to change. I prefer letting each channel service own its complexity. WeChat handles merchant certificates, platform certificates, API v3 requests, H5, Native, JSAPI, and refunds. Alipay handles EasySDK, page payment, mobile web payment, queries, and refunds.

The business layer asks the same questions: create a payment entry for this order, query this order, refund this amount. Channel details stay inside services, so order logic is not tied to SDK details.

Callbacks Decide Whether State Is Trustworthy

A payment system cannot trust frontend redirects as proof.

When a user returns from payment, it only means they came back. It does not prove money arrived. The trustworthy state comes from channel callbacks and active queries. When a callback enters the system, the system needs to confirm it came from the channel, find the internal order, update the status, and record the relevant result.

There are many boundaries here: repeated callbacks, notifications after an order is already paid, amount mismatch, merchant mismatch, callback failure retries, and when backend staff are allowed to intervene manually.

The quality of a payment loop often shows up in these inconvenient cases.

Refunds Are Not Just Reducing an Amount

Refunds are not a simple field edit.

When the backend triggers a refund, the system needs to know the original order amount, already refunded amount, current refund amount, channel refund result, and current order status. A refund may succeed, stay processing, or fail. Writing a single refund number on the order makes later troubleshooting difficult.

Even if the early system does not have a full refund ledger, I still treat refunds as state changes rather than normal edits. Once a refund happens, the meaning of the order changes for the business, finance, and user.

Every State Should Be Explainable

The main lesson from payment systems is that transaction features cannot stop at "payment can be invoked".

A reliable payment system should answer why an order is in its current state, what the channel returned, which entry the user used, whether the callback arrived, whether a refund was submitted, and whether the backend result matches the channel result.

When I look at payment requirements now, I draw the order state before I design the page. The page helps the user complete an action. State lets the system explain the transaction over time. Once the state loop is clear, even a lightweight first version has room to grow.

Have a 0-to-1 system or technical lead role to discuss? Email me

©2026 Eddie Xu. All rights reserved