Skip to main content

Build your payment backend

Build these endpoints in your existing application backend. Your app never needs a Payment API key or a provider signing key. The platform handles provider order creation and signing; your backend connects those financial records to your customers and products.

1. Keep the identifiers separate​

IdentifierOwnerStore it for
Customer IDYour applicationAuthentication and ownership checks.
Business order ID / external_order_idYour applicationA stable reference to the purchase and recovery after a lost response.
Platform app_idNextAppMarketSelecting your activated application.
Platform order ID / data.order.idPayment APIQueries, refunds, and webhook lookup.
Provider AppIDWeChat or Alipay configurationNative SDK registration; distinct from platform app_id.
Idempotency keyYour backendIdentifying one command across retries.

A useful persistence model has a business order, payment attempts, fulfillment records, and refund requests. A payment attempt stores the selected entry, platform application, external reference, expected amount and currency, exact request body, idempotency key, platform order ID when known, latest status/version, and creation time.

The examples use the business order ID as external_order_id for the first attempt. If you deliberately create a later attempt after the original is definitively closed or failed, allocate a new external reference and keep both attempts linked to the same business purchase.

Enforce a unique platform order ID and at most one unresolved attempt for a business purchase. Use a unique fulfillment key for the business purchase too: if two attempts unexpectedly become paid, record both payments for reconciliation but do not deliver the purchase twice.

2. Implement your checkout endpoint​

Example route: POST /api/checkout. This route belongs to your application.

  1. Authenticate the customer and load a cart they own. Validate that its products can still be sold.
  2. Calculate the amount from your server's product catalog. Validate the requested entry against the entries enabled for your app.
  3. In a database transaction, lock the business order and return its existing unresolved attempt if one exists. Otherwise save a new attempt with an external reference, serialized request, and idempotency key. Commit before the network call.
  4. Submit the saved body to the Payment API. Save the returned platform order mapping and version before responding to the client.
  5. Return the business order ID, payment status, and invocation. Keep credentials, refund controls, and internal records on your server.

Do not hold a database transaction open while waiting for the provider. Concurrent requests must converge on the same saved attempt through database constraints or a lock, even if more than one worker reaches the network call.

Use the sample HTTP client​

Download payment-client.mjs into your server project. It uses Node.js 20 or later with built-in fetch and has no package dependencies. It is a small HTTP helper, not an application server: you supply database storage, authentication, authorization, scheduling, and fulfillment.

import { createPaymentClient } from './payment-client.mjs';

const payments = createPaymentClient({ apiKey: process.env.PAYMENT_API_KEY });

// Load this record from your database after committing it in step 3.
// attempt.requestBody is the original JSON string, not a reconstructed object.
const { data: session } = await payments.createSession({
body: attempt.requestBody,
idempotencyKey: attempt.idempotencyKey,
});

// Save the mapping durably before sending the invocation to the app.
await savePlatformOrder(attempt.id, session.order);
return {
businessOrderId: attempt.businessOrderId,
paymentOrderId: session.order.id,
paymentStatus: session.order.status,
invocation: session.invocation,
};

This handler fragment uses your own attempt record and savePlatformOrder database function. The latter must check the response identity and amount and avoid overwriting a newer version. Use the method-specific request body when creating the attempt.

The helper returns the full {data, meta} envelope. It does not retry writes automatically, follow redirects with your credential, or log payment payloads.

3. Recover an interrupted create request​

A timeout means your backend did not learn the result. It does not mean the provider rejected payment.

const { data: order } = await payments.findOrder({
appId: attempt.appId,
externalOrderId: attempt.externalOrderId,
});
const { data: session } = await payments.getSession(order.id);

Save the recovered mapping. If lookup returns not found, keep the attempt unresolved while reconciling: the original request may still be running. Retry the original create request with the same key and identical saved bytes when appropriate. Follow REQUEST_IN_PROGRESS using its operation ID. Do not create another order to make an uncertain result disappear.

For HTTP errors, the helper throws PaymentApiError with status, code, requestId, retryable, operationId, and currentVersion. Network failures and unreadable responses use PaymentOutcomeUnknown. Keep the attempt and reconcile it. retryable is a scheduling hint, not permission to change the command identity. See idempotency and errors.

4. Implement your order-status endpoint​

Example route: GET /api/orders/:businessOrderId.

Check customer ownership before reading the saved platform ID. Query payments.getOrder(platformOrderId), validate the result, and run the same fulfillment procedure used by your webhook worker. Return the small app response shown in the quickstart.

Coalesce repeated client polls for the same order and apply backend rate limits. Do not allow a caller to pass an arbitrary platform order ID and read another customer's payment. Your order history can use your own database; query the Payment API to resolve unfinished or uncertain records.

5. Receive events and process refunds​

Expose a server webhook endpoint and verify its raw request body using the webhook signature guide. Persist accepted events before acknowledging them. A background worker maps each event's platform order ID to your business order and updates the same records used by your status endpoint.

Create an authenticated refund endpoint only for users or operators allowed by your refund policy. Have that handler calculate the permitted amount, persist its command, and follow the refund walkthrough. A client request for a refund is not authorization to refund an arbitrary order or amount.

6. Keep reconciliation running​

Run a scheduled worker for unresolved creates, processing or unknown orders, pending fulfillment, and incomplete refunds. Use bounded exponential backoff with jitter, record the last check, and surface long-running cases to your support workflow. This worker is what completes purchases after an app is closed or a network connection is lost.