Confirm payment and fulfill an order
After the customer finishes in the SDK, browser, or payment app, show Checking payment and retrieve the original order through your backend. Use this same confirmation path when the app resumes and when a webhook arrives.
1. Retrieve the authoritative order
Your app calls your own authenticated order endpoint. Your backend verifies ownership, loads the saved platform order ID, and sends:
curl --fail-with-body \
'https://pay.appconnect.cn/payment/v1/orders/ord_demo_001' \
--header "Authorization: Bearer $PAYMENT_API_KEY"
The order is in data. Compare id, app_id, external_order_id, amount_minor, and currency with your stored attempt. Before granting a fully paid purchase, also check paid_amount_minor against the expected amount. Quarantine mismatches for investigation instead of delivering based only on status.
For an order that already has refunds, apply your recorded refund and entitlement policy before granting or restoring access. Do not use a historical paid status to restore a refunded purchase.
2. Show the right state in your app
| Platform status | Suggested customer experience | Backend action |
|---|---|---|
pending | Awaiting payment. | Present a valid invocation for the existing order if appropriate. |
processing | Checking payment. | Keep the same order and query again. |
unknown | We are confirming your payment. Check your order shortly. | Reconcile in the background; do not start a replacement charge. |
paid | Payment received; preparing your purchase, then purchase complete. | Validate and fulfill once. |
failed | Payment was unsuccessful. | Explain the result and allow an explicit new attempt after reconciliation. |
closed | This payment is closed. | Remove the old payment instructions. |
SDK cancellation describes the customer's client interaction. It does not close the platform order. A browser return URL and a callback with success also do not establish payment. All three lead to this status check.
An invocation can be ready while an order is still awaiting its financial result. When launching a newly created payment, follow the method guide and valid invocation; after launch, use the table above for confirmation.
3. Refresh without trapping the customer in a spinner
For example, poll your backend, with one request in flight at a time, after 2, 3, 5, 8, 13, and 20 seconds. Add jitter, honor rate limits, and stop when the view closes or a definitive result arrives. These intervals are an application example, not a platform polling requirement.
After the short foreground window, show a pending order screen with a Check payment action. Continue background reconciliation on your backend. Refresh on app resume and in order history. If an SDK never calls back, these paths still recover the purchase.
Persist the business order ID before opening the provider. Do not depend on an in-memory callback variable surviving an app restart.
4. Deliver the purchase once
Both your webhook worker and your status endpoint should call one server-side reconciliation function. A typical transaction is:
begin transaction
lock the business order
validate the platform order against the saved payment attempt
ignore an older version of the same platform resource
record the latest payment state
if the confirmed payment qualifies for fulfillment:
insert fulfillment using a unique business-purchase key
if the insert is new:
grant the entitlement in this transaction
or enqueue delivery in a transactional outbox
commit
For an external delivery system, make the outbox consumer idempotent using the same fulfillment key. Mark delivery complete only after the downstream action succeeds. A database record saying payment is paid is not by itself evidence that goods or credits were delivered.
Keep separate paymentStatus and fulfillmentStatus fields in your own API. This lets the app explain a delivery delay without asking the customer to pay again.
5. Handle webhooks and missing events
Use verified webhooks to update orders even when the app is closed. Deduplicate event IDs, and use resource versions to avoid overwriting newer state. Payment webhooks omit your external_order_id; resolve the event through your saved platform-order mapping.
If an event arrives before the create response is saved, retain it for retry. Recover the order by exact external reference and associate the event once the mapping exists. Queries remain the recovery path for missing or delayed events.
Recover a lost platform order ID
curl --fail-with-body --get 'https://pay.appconnect.cn/payment/v1/orders' \
--header "Authorization: Bearer $PAYMENT_API_KEY" \
--data-urlencode 'app_id=app_demo' \
--data-urlencode 'external_order_id=shop_order_1042'
This exact lookup returns the matched order in data; it is not an order-list endpoint. Retrieve its payment session if you still need the original invocation. Follow safe recovery if lookup and the original create are both unresolved.
Read amounts after a refund
| Field | Meaning |
|---|---|
amount_minor | Original requested payment amount. |
paid_amount_minor | Confirmed paid amount. |
refunded_amount_minor | Principal successfully returned. |
pending_refund_amount_minor | Principal reserved for refunds still in progress. |
refund_request_deadline_at | Exclusive deadline for receiving an ordinary refund request. |
An order can remain paid after a full refund. Use these amounts and the refund resource, rather than expecting a refunded order status.
Versions and reporting
Supply the current order version when closing or refunding an order. After VERSION_CONFLICT, retrieve the order and reassess the action. Compare versions only within the same resource.
The Business API also exposes orders for reporting. Its meta.data_source, meta.as_of, and meta.sync_lag_seconds describe freshness; a null lag is not zero. Use the CN Payment API above to resolve a checkout result. See the Business API reference for reporting and pagination.