Skip to main content

Official Account JSAPI Web Pay Integration Guide

After creating a "WeChat Pay" integration in the CloudBase console, the system automatically generates an HTTP cloud function (function name like <integration-name>-<random-string>, hereinafter referred to as pay-common). This guide describes how to use that cloud function on the WeChat Official Account web (H5 within WeChat) side, using JSAPI Pay to complete the full ordering-to-callback payment loop.

Applicable scenario: User opens an Official Account H5 page within the WeChat browser to initiate payment (most common Service Account pay). Not applicable: Browsers outside WeChat (Chrome/Safari/system browsers) → use the H5 ordering API; PC web → use Native (QR-code) Pay.

End-to-end pipeline:

  • Outbound requests: Web → business backend obtains WeChat web authorization → CloudBase cloud API gateway (accessToken auth) → pay-common HTTP cloud function → WeChat Pay API
  • Asynchronous callbacks: WeChat Pay → Integration Center (verify + decrypt) → pay-common → business side

Architecture Overview

┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Off. Account │ │ Web auth │ │ CloudBase Cloud │
│ Web Page │───►│ Backend │ │ API Gateway │
│ (WeChat) │ │ code → openid│ │ accessToken auth │
│ │ └──────────────┘ │ │
│ fetch │ │ │ HTTP invoke │
│ ──────────►│ Bearer │ │ ────────────►│
│ │ Token │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────┴───┐
│ │ │ pay-common (HTTP cloud function) │
│ │ │ SDK self-sign → /v3/pay/transactions/jsapi
│ │ └────────────┬─────────────────────────────┘
│ │ │ Unified order
│ │ ▼
│ │ ┌──────────────────┐
│ │ │ WeChat Pay API │
│ │ │ (JSAPI/MiniProg)│
│ │ └────────┬─────────┘
│ WeixinJSBridge.invoke │ Async pay callback
│ ('getBrandWCPayRequest') ▼
│ │ ┌──────────────────┐
│ │ │ Integration │
└──────────────┘ │ Center │
│ (verify+decrypt) │
└────────┬─────────┘
│ Plaintext forward

┌──────────────────┐
│ pay-common │
│ /wechatpay/order │
└──────────────────┘

Key differences between JSAPI and Mini Program payment:

AspectMini Program PayOfficial Account JSAPI Web Pay
Order API/v3/pay/transactions/jsapi/v3/pay/transactions/jsapi (same one)
appidMini Program AppIDOfficial Account AppID
openid sourcesignInWithOpenId() directlyWeChat web authorization (OAuth 2.0) code → openid
Pay invocation APIwx.requestPaymentWeixinJSBridge.invoke('getBrandWCPayRequest', ...)
Runtime environmentWeChat Mini Program containerWeChat built-in browser (required)

Therefore the pay-common function itself is identical; the differences are only in "openid acquisition" and "frontend invocation API".

Responsibility breakdown:

RoleResponsibility
Official Account webObtains code via WeChat web authorization → business backend exchanges openid; calls pay-common with Bearer Token; invokes payment via WeixinJSBridge.invoke
Business backend (self-provided)Holds the Official Account AppSecret; calls sns/oauth2/access_token to exchange code for openid (AppSecret must not appear in the frontend)
Cloud API gatewayValidates Bearer {accessToken}; forwards requests to the HTTP cloud function
pay-common cloud functionHandles ordering, querying, and refunding; receives and handles callbacks
WeChat Pay APIThe party that actually executes payment business
Integration CenterHosts credentials uniformly; performs signature verification and decryption on callbacks, then forwards plaintext to the cloud function

Prerequisites

ItemRequirement
Official AccountService Account (Subscription Accounts do not support JSAPI Pay) WeChat-verified
Official Account JSAPI Pay capabilityEnabled and approved in "WeChat Pay" on the Official Accounts platform
WeChat Pay merchant IDApplied for, and bound to the above Official Account on the merchant platform
Official Account pay authorization directoryMerchant platform → Product Center → JSAPI Pay → Add authorization directory (must match the URL prefix of the ordering page, including trailing /)
Official Account web authorization domainOfficial Accounts platform → Settings & Development → Official Account Settings → Functional Settings → Web Authorization Domain
Merchant super-admin permissionUsed to download the API certificate and set the APIv3 key
Business backend (any)A lightweight service for exchanging openid (existing Node/Java/Go service, or a standalone cloud function)

Step 1 · Prepare Merchant Credentials

You need 7 credentials:

#FieldDescription
1appIdOfficial Account AppID (not Mini Program AppID)
2merchantId10-digit merchant ID
3apiV3Key32-char APIv3 key
4merchantSerialNumber40-char hex certificate serial number
5privateKeyFull content of apiclient_key.pem
6wxPayPublicKeyWeChat Pay public key PEM
7wxPayPublicKeyIdPublic Key ID (PUB_KEY_ID_...)

⚠️ appId must be the Official Account AppID, and that AppID must be bound to the merchant ID under "JSAPI Pay" on the merchant platform. See the AppID distinctions in the appendix of the Mini Program guide.


Step 2 · Create the Integration in CloudBase

Same as the Mini Program Pay guide. After integration creation, the system automatically generates:

  • HTTP cloud function pay-common
  • Callback base domain https://<integration-id>.integration-callback.tcloudbase.com
  • Pay callback path /wechatpay/order
  • Refund callback path /wechatpay/refund

The full pay callback URL must be filled in the merchant platform's "Product Center → Development Settings → Pay Notification URL".


Step 3 · Understand the Cloud Function and Environment Variables

Fully reuses the source structure and env-var injection logic described in the Mini Program guide. The /v3/pay/transactions/jsapi order implementation in pay-common is the same code for Mini Programs and Official Account web (route _action: wxpay_order).

The only difference: the payer.openid passed by the caller must be the openid of a user under that Official Account AppID.


Step 4 · Integrate with the Official Account Web Page

After pay-common is deployed, integrating Official Account web has four steps: web authorization to get openid → call pay-common to place order → invoke payment via JSBridge → server handles callback.

4.1 Configure Official Account Pay Authorization Directory and Web Authorization Domain

Pay authorization directory (required):

Path: Merchant platform → Product Center → JSAPI Pay → Add authorization directory.

Fill in the URL prefix of the page that invokes payment, must end with /. For example, if the page is https://shop.example.com/pay/cashier.html, fill the authorization directory as https://shop.example.com/pay/.

Web authorization domain (required):

Path: Official Accounts platform → Settings & Development → Official Account Settings → Functional Settings → Web Authorization Domain.

4.2 Get openid via WeChat Web Authorization

Official Account JSAPI Pay requires payer.openid, which must be obtained via WeChat web authorization (OAuth 2.0). The exchange step cannot be done in the frontend — it would leak the Official Account AppSecret. Below are two implementation approaches:

The "WeChat Official Account" integration provided by the CloudBase platform (the offiaccount-common cloud function) has packaged the web authorization pipeline. The business side does not need to build a backend or store the AppSecret.

For detailed onboarding steps, see Official Account Integration Guide · Web Authorization.

Advantages: AppSecret stays in the offiaccount-common cloud function env vars and never leaves Integration Center; capabilities like web authorization, user profile retrieval, and token refresh are out-of-the-box.

Alternative: Build Your Own Backend to Exchange openid

If the business side already has its own backend service and does not want to introduce the Official Account integration, you can call the WeChat web authorization API directly. Important: the openid exchange must happen on the backend; the frontend only handles redirects and callbacks.

1. Web redirect: https://open.weixin.qq.com/connect/oauth2/authorize
?appid=<Official Account AppID>
&redirect_uri=<URL-encoded business callback URL>
&response_type=code
&scope=snsapi_base ← Silent authorization, only gets openid; no user confirmation
&state=<custom>
#wechat_redirect

2. WeChat redirects back to redirect_uri with ?code=xxx&state=xxx

3. **Business backend** uses code to call WeChat API to get openid (do not call from frontend; would leak AppSecret):
GET https://api.weixin.qq.com/sns/oauth2/access_token
?appid=<Official Account AppID>
&secret=<Official Account AppSecret>
&code=<code from previous step>
&grant_type=authorization_code

Returns: { access_token, openid, refresh_token, expires_in, scope, ... }

4. Backend writes openid into session/cookie or returns it to the frontend

scope=snsapi_base does not require a user confirmation popup; as long as the user opens it within an Official Account session, openid is obtained the moment they redirect — best UX. If the business also needs nickname/avatar etc., use scope=snsapi_userinfo, which adds an authorization popup.

JSAPI Web Pay and Mini Program Pay use the same set of routes; the only difference is the client-side invocation method. Common routes:

_actionCategoryDescription
wxpay_orderOrderJSAPI / Mini Program ordering (used in this guide)
wxpay_query_order_by_out_trade_noQueryQuery order by merchant order number
wxpay_query_order_by_transaction_idQueryQuery order by WeChat order number
wxpay_close_orderCloseClose order (recommend proactive close after 10 minutes unpaid)
wxpay_refundRefundApply for refund
wxpay_refund_queryRefundQuery refund

All routes are dispatched by the _action field in the body. Except for callback routes, all must carry Authorization: Bearer {access_token}.

4.4 Callback Handling

Fully reuses the hook methods (handlerUnified / handlerUnifiedTrigger / handlerRefund / handlerRefundTrigger) in services/orderService.js described in the Mini Program guide; same principles apply: idempotency, amount cross-check, atomic state update.

JSAPI Pay and Mini Program Pay have 100% identical callback structures — both event_type: TRANSACTION.SUCCESS, with out_trade_no / transaction_id / amount etc. in plaintext. The business side does not need to differentiate callback code by payment method.

4.5 Complete Invocation Example

Suitable for native H5 self-built projects. Below is the end-to-end example from getting openid to invoking payment.

Key constraints:

  • ENV_ID: CloudBase env ID
  • FN_NAME: the auto-generated HTTP cloud function name after integration creation (like <integration-id>-<random-string>)
  • The page must be opened in the WeChat built-in browser; otherwise WeixinJSBridge does not exist
  • The payer.openid must contain an openid under the Official Account AppID
  • accessToken must come from "authenticated login" — anonymous login (signInAnonymously) tokens have no permission to call cloud functions; the gateway will return 401. For login methods (email, phone, custom login, etc.), see CloudBase Web SDK · Authenticated Login.
<!-- pay.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Official Account Pay</title>
</head>
<body>
<button id="payBtn">WeChat Pay 0.2 yuan</button>
<script src="https://cdn.jsdelivr.net/npm/@cloudbase/js-sdk/dist/cloudbase.full.js"></script>
<script>
const ENV_ID = 'your-env-id'
const FN_NAME = 'pay-common'

// openid of the current user (Official Account AppID dimension), obtained via business backend
const OPENID = window.__USER_OPENID__ // injected by backend template or cookie

// 1. Initialize CloudBase Web SDK, get accessToken
const app = cloudbase.init({ env: ENV_ID })
const auth = app.auth({ persistence: 'local' })

async function getAccessToken() {
// ⚠️ Anonymous login (signInAnonymously) tokens cannot call cloud functions (401)
// Authenticated login is required. For login methods (email, phone, custom login, etc.), see:
// https://docs.cloudbase.net/api-reference/webv2/authentication#%E8%AE%A4%E8%AF%81%E7%99%BB%E5%BD%95
throw new Error('Implement authenticated login per CloudBase Web SDK docs and return accessToken')
}

document.getElementById('payBtn').onclick = async () => {
const accessToken = await getAccessToken()
const out_trade_no = 'ORDER_' + Date.now()

// 2. Call pay-common to place order
const res = await fetch(
`https://${ENV_ID}.api.tcloudbasegateway.com/v1/functions/${FN_NAME}?webfn=true`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
_action: 'wxpay_order',
description: 'Test product',
out_trade_no,
amount: { total: 20, currency: 'CNY' }, // 0.2 yuan
payer: { openid: OPENID },
}),
}
)
const body = await res.json()
if (body.code !== 0) {
alert(body.msg || 'Order failed')
return
}
const p = body.data && body.data.data ? body.data.data : body.data
// p = { timeStamp, nonceStr, package, signType, paySign }

// 3. Invoke JSBridge payment
callJsBridgePay(p, out_trade_no)
}

function callJsBridgePay(p, outTradeNo) {
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: '<Official Account AppID>', // must match the appId used at order time
timeStamp: p.timeStamp,
nonceStr: p.nonceStr,
package: p.package, // 'prepay_id=wx20...'
signType: p.signType || 'RSA',
paySign: p.paySign,
},
function (resp) {
if (resp.err_msg === 'get_brand_wcpay_request:ok') {
// Pay success (frontend only updates UI; final state determined by server callback)
// Recommended: 1-2 seconds later, call wxpay_query_order_by_out_trade_no for fallback
location.href = `/order/result?out_trade_no=${outTradeNo}`
} else if (resp.err_msg === 'get_brand_wcpay_request:cancel') {
alert('User canceled payment')
} else {
alert('Payment failed: ' + resp.err_msg)
}
}
)
}
if (typeof WeixinJSBridge === 'undefined') {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
} else {
onBridgeReady()
}
}
</script>
</body>
</html>

Implementation notes:

  • WeixinJSBridge only exists in the WeChat built-in browser; external browsers must switch to the H5 Pay scheme (out of scope for this guide).
  • The appId field in the getBrandWCPayRequest input must exactly match the Official Account AppID used at order time, otherwise it will report "appid and openid mismatch".
  • Use the package value from the API response as-is; do not concatenate prepay_id=... yourself.
  • The final payment state is determined by the server callback; the frontend ok is only for redirecting to the result page; the server executes business side effects like shipping after receiving the callback.

4.6 Real-device Test

The debugging pipeline for JSAPI Pay is more cumbersome than for Mini Programs:

  • Must visit the page in the WeChat built-in browser; desktop browsers cannot invoke WeixinJSBridge.
  • WeChat Developer Tools has an "Official Account Web Debug" mode (menu → Official Account Web Project → WeChat Web Debug) that simulates the WeChat UA, but still cannot initiate real payment — must use a real device.
  • Before accessing the page on a real device, confirm:
    1. Domain is in the Official Account "Web Authorization Domain" allowlist
    2. Page URL prefix is in the merchant platform "JSAPI Pay Authorization Directory" (with trailing /)
    3. User Official Account follow status doesn't matter (snsapi_base does not require following)

Test amount recommendation: same as Mini Program — use 0.1–1 yuan to avoid 0.01 yuan triggering risk control.


FAQ

Q1: Invoking payment reports "appid and openid mismatch"

The most common error. The appId in the getBrandWCPayRequest input, the appId filled at order time, and the AppID used to exchange openid must be identical at all three places and all be the Official Account AppID.

Troubleshoot:

  1. Integration Center is filled with the Official Account AppID, not the Mini Program AppID
  2. The business backend's OAuth2 also uses the same Official Account AppID + AppSecret
  3. The frontend's getBrandWCPayRequest input does not hard-code the wrong appId

Q2: Reports APPID_MCHID_NOT_MATCH or "merchant does not exist"

The Official Account AppID is not bound to the merchant ID on the merchant platform. Merchant platform → Product Center → JSAPI Pay → Associate AppID; submit the application; takes effect after the Official Account admin approves.

Q3: Reports "Current URL is not in pay authorization directory"

The page URL invoking payment is not in the "JSAPI Pay Authorization Directory" allowlist. Merchant platform → Product Center → JSAPI Pay → Add authorization directory; fill in the URL prefix of the page (with trailing /). Takes effect in about 5 minutes.

Q4: No code received after web authorization redirect

The redirect_uri domain is not in the Official Account "Web Authorization Domain" allowlist, or the domain verification file is not online. Complete the web authorization domain verification and retry.

Q5: WeixinJSBridge is not defined

The page is opened in a non-WeChat built-in browser. JSAPI Pay can only run in the WeChat browser; external browsers must use H5 Pay (/v3/pay/transactions/h5).

Q6: Can desktop Chrome with modified UA spoofing WeChat invoke payment?

No. WeixinJSBridge is a global object natively injected by WeChat; UA spoofing cannot inject it. During development, you can use WeChat Developer Tools' "Official Account Web Project" mode to debug page rendering and requests; but pay invocation must use a real device.

Q7: Do you need wx.config or JSSDK auth?

JSAPI Pay (getBrandWCPayRequest) does not require wx.config. Only when using WeChat JSSDK capabilities like camera, location, sharing etc. is wx.config needed.

Q8: Can the same integration as Mini Program Pay be reused?

Yes, but it depends on your appId configuration. The pay-common integration only has one appId field; whichever is filled determines which app it serves. To support both Mini Program and Official Account JSAPI, you can either:

  1. Create two independent integrations (one filled with Mini Program AppID, the other with Official Account AppID) to serve the two ends respectively
  2. Modify the pay-common code to dynamically switch appId by caller, and add env vars like jsapiAppId

Appendix · Differences from Mini Program Pay Quick Reference

AspectMini ProgramOfficial Account JSAPI Web
Integration Center appIdMini Program AppIDOfficial Account AppID
openid sourcesignInWithOpenId()OAuth 2.0 web authorization + business backend exchange
Pay invocation APIwx.requestPaymentWeixinJSBridge.invoke('getBrandWCPayRequest')
Runtime environmentMini Program containerWeChat built-in browser
Domain allowlistMini Program request legal domainOfficial Account web authorization domain + pay authorization directory
pay-common route_action: wxpay_order_action: wxpay_order (same one)
Callback structureTRANSACTION.SUCCESSTRANSACTION.SUCCESS (identical)

Further Reading

TopicLink
JSAPI Ordering (Mini Program/Official Account Pay)https://pay.weixin.qq.com/doc/v3/merchant/4012791897
Official Account Web Authorizationhttps://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
getBrandWCPayRequest APIhttps://pay.weixin.qq.com/doc/v3/merchant/4013080345
Pay Callback Protocolhttps://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/payment-notice.html
CloudBase Auth · Web SDKhttps://docs.cloudbase.net/api-reference/webv2/authentication