WeChat H5
Use wechat_h5 for a checkout in a mobile browser outside WeChat. A native app should use WeChat App. This entry is not a WeChat in-app browser or Mini Program integration.
1. Prepare your mobile checkout
Have the entry enabled for your application. Provide NextAppMarket with the checkout domain and return destination required for activation. Keep the assigned return_url_id on your server. The backend must determine the customer's actual IP using a trusted proxy configuration.
The checkout page needs an authenticated session and a saved business order that remains available after the customer leaves the browser. WeChat's H5 flow depends on the approved domain and browser context; see WeChat's H5 launch guide.
Setup checklist
- Use a mobile HTTPS checkout page outside WeChat. Your customer needs WeChat installed; there is no JavaScript or native payment SDK to install on this page.
- Give NextAppMarket the exact checkout host and return host, and obtain the approved
return_url_id. A different subdomain is a different configuration. Use a return path on the approved host. - Implement
POST /api/checkoutandGET /api/orders/:businessOrderIdon your own backend using the client/backend contract. The backend selectswechat_h5and suppliesscenefrom trusted server context. - If you run behind a proxy/CDN, configure trusted proxy addresses in your framework. The edge must replace untrusted forwarding headers. Read the customer address from that trusted configuration, not the first arbitrary
X-Forwarded-Forvalue. Do not use your server address or127.0.0.1asscene.client_ip. - Copy checkout-browser.mjs into your frontend source. It implements authenticated app requests, invocation validation, and bounded status polling. Follow the browser setup for its CSRF token and HTML elements.
- Configure the checkout response's referrer policy to preserve the approved origin for the cross-origin HTTPS navigation. For example,
Referrer-Policy: strict-origin-when-cross-originsends the origin;no-referrerwould suppress it. Do not fabricate a different site's referrer to bypass domain activation.
Checkpoint: your browser page is served from the activated host, your backend identifies the real client IP, and your return route can recover the signed-in customer's pending purchase.
2. Create the order on your backend
Authenticate the customer, calculate the price, and save the business order and payment attempt as described in Build your payment backend. Save the following body as payment.json, replacing the application and external order IDs:
{
"app_id": "app_demo",
"payment_entry": "wechat_h5",
"amount_minor": 1000,
"currency": "CNY",
"external_order_id": "shop_order_1042",
"description": "100 app credits",
"scene": {
"client_ip": "203.0.113.10",
"return_url_id": "return_checkout"
}
}
curl --fail-with-body --request POST \
'https://pay.appconnect.cn/payment/v1/payment-sessions' \
--header "Authorization: Bearer $PAYMENT_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: create-shop-order-1042-v1' \
--data-binary @payment.json
Use the key and exact body persisted for this attempt. Save data.order.id before returning the business order ID and data.invocation to the client. The Payment API key stays on your server.
If the invocation is null, keep the same order and retrieve its payment session. Do not create another order while the first result is unresolved.
Replace the example IP with the customer's actual payment IP. Do not trust a client-supplied client_ip or an unverified forwarding header. return_checkout represents an approved configuration ID, not a URL that the customer can choose.
3. Open the returned payment URL
Expect redirect_url. In your mobile checkout's browser code:
function openWechatH5(invocation) {
if (invocation.kind !== 'redirect_url') throw new Error('Unexpected payment instructions');
const expires = Date.parse(invocation.expires_at);
if (!Number.isFinite(expires) || expires <= Date.now()) throw new Error('Payment instructions expired');
const url = new URL(invocation.redirect_url);
if (url.protocol !== 'https:') throw new Error('Invalid payment URL');
window.location.assign(invocation.redirect_url);
}
Call this with the invocation received from your authenticated backend. Your backend should validate the expected provider destination before forwarding it. Use a top-level navigation from the approved checkout page and preserve the provider-required referrer. Test the referrer policy and redirect chain in your deployed browser flow.
The platform has already added the configured return destination. Do not append another redirect_url, change the signature, or use an arbitrary redirect proxy.
When your return page loads, recover the business order from your authenticated checkout state and query your backend. A return can happen before the final financial result arrives. Provide a Check payment action and a route back to order history when automatic return is unavailable.
Wire the Pay button to this handler
Use these elements on the checkout page. The backend renders the CSRF token using your normal session protection. The cart ID below is illustrative; use the cart owned by the signed-in customer.
<meta name="csrf-token" content="SERVER_RENDERED_TOKEN">
<button id="pay" type="button">Pay</button>
<button id="check-payment" type="button">Check payment</button>
<p id="payment-status" role="status"></p>
Copy this into your frontend module alongside the handler above:
import { checkoutRequest } from './checkout-browser.mjs';
const payButton = document.getElementById('pay');
const status = document.getElementById('payment-status');
payButton.addEventListener('click', async () => {
payButton.disabled = true;
try {
const checkout = await checkoutRequest('/api/checkout', {
method: 'POST',
csrfToken: document.querySelector('meta[name="csrf-token"]').content,
body: { cartId: 'cart_1042', paymentEntry: 'wechat_h5' },
});
sessionStorage.setItem('pendingBusinessOrderId', checkout.businessOrderId);
if (checkout.paymentStatus === 'paid') {
window.location.assign('/orders/' + encodeURIComponent(checkout.businessOrderId));
return;
}
if (!['pending', 'processing'].includes(checkout.paymentStatus) || !checkout.invocation) {
status.textContent = 'Payment is not ready. Check the existing order.';
return;
}
openWechatH5(checkout.invocation);
} catch {
status.textContent = 'Unable to open payment. Your order can be checked before retrying.';
} finally { payButton.disabled = false; }
});
The /orders/… path is your application's order page. POST /api/checkout must reuse an unresolved attempt, so another tap cannot silently create a duplicate charge. Your backend supplies any entry-specific scene fields from trusted configuration.
Connect the Check payment button in the same module:
document.getElementById('check-payment').addEventListener('click', () => {
const id = sessionStorage.getItem('pendingBusinessOrderId');
if (id) window.location.assign('/orders/' + encodeURIComponent(id));
else status.textContent = 'Sign in and open this purchase from your order history.';
});
Build the return page
Use the complete return/status page example with the saved business order. The Check payment action and order history must work even if the provider does not navigate back. The return URL transports the customer; your backend transports the financial truth.
4. Confirm payment and deliver the purchase
When the customer returns, ask your backend for the business order's status. Your backend checks ownership and retrieves the saved platform order:
curl --fail-with-body \
'https://pay.appconnect.cn/payment/v1/orders/ord_demo_001' \
--header "Authorization: Bearer $PAYMENT_API_KEY"
Validate the returned identity, currency, and amount. Fulfill only after the authoritative status is paid, using a unique fulfillment record. A callback or a return to your page is a reason to check this order, not a reason to grant the purchase.
Implement webhooks and order reconciliation so confirmation works even when the customer closes the app or browser. Show a pending state for unresolved results and offer Check payment instead of immediately starting another charge.
5. Refund or close the order
For a paid order, follow Refund a payment to submit a full or partial refund and track it to completion. Use the saved platform order ID; your client does not refund through the provider SDK.
For an abandoned unpaid order, use the close-order flow. Removing the payment UI does not close the order. Run the integration checklist before release.
6. Test the mobile journey
Run an approved payment in a supported mobile browser with WeChat installed. Confirm the actual checkout host, client IP, returned invocation, provider screen, browser return, and authoritative order result. Repeat cancellation, manual app switching, a missing return, and a payment that completes while the browser is closed.
7. Common problems and how to fix them
| Symptom | Check and fix |
|---|---|
| Invalid merchant parameters | Inspect the request to the returned URL in browser network tools. It must come from your approved page with the expected referrer. Avoid copying the provider URL into the address bar or applying no-referrer. |
| Merchant configuration missing | Compare the exact checkout and return hosts with activation, including subdomains. Ask the platform to update the configuration; changing only your local URL does not activate a host. |
| H5 transaction parameters invalid | Inspect the server-populated scene.client_ip. It must be the actual customer address from your trusted proxy setup, not the server, CDN, loopback, or a user-supplied header. |
| Payment request expired | Stop launching the stale URL. Query the original order, recover its session if appropriate, and only create a new attempt after the original is definitively resolved. |
| Message says to open outside WeChat | Open the checkout in a supported external browser. wechat_h5 is not JSAPI payment; do not keep retrying inside WeChat's embedded browser. |
| WeChat never opens | Confirm WeChat is installed, the browser allows the user-initiated app switch, and the URL was returned for this order. Check the browser flow on a physical device. |
| Signature failure or system busy after modifying the link | Use the exact returned URL. Remove your added redirect/tracking/query parameters and do not decode and re-encode the signature. Do not share one payment invocation across different customers. |
| Network safety check fails | Confirm the client IP and that the customer's network did not change between creation and launch. Keep the existing order and reconcile before offering a later attempt. |
| Return page opens before payment finishes | Display pending and query the backend. Browser navigation can precede the final result. Keep a manual Check payment button. |
| Return opens another tab/browser and loses the order | Recover through authenticated server-side checkout state or order history. Session storage in the original tab cannot be your only record. |
| Customer paid but the page keeps spinning | Check the original platform order, then your webhook inbox and fulfillment worker. Stop the foreground spinner after the bounded polling window and keep status recovery available. |
These checks adapt WeChat's H5 FAQ to the platform integration. Provider setup changes go through NextAppMarket; your application continues to query and refund the same platform order.