跳到主要内容

Native(PC 扫码)支付集成指南

在 CloudBase 控制台创建「微信支付」集成后,系统会自动生成一个 HTTP 云函数(函数名形如 <集成名>-<随机串>,本文统称 pay-common)。本指南介绍如何基于该云函数完成 Native 支付——即 PC 网页/桌面应用展示二维码,用户用手机微信扫码完成支付的接入闭环。

适用场景:PC 收银台、桌面客户端、自助终端、广告屏等所有"非微信内浏览器"的扫码支付需求。 不适用:微信内 H5 → 用 JSAPI 网页支付;小程序 → 用小程序支付;微信外手机浏览器 → 用 H5 支付。

整体调用链路:

  • 正向请求:网页 → CloudBase 云 API 网关(accessToken 鉴权)→ pay-common HTTP 云函数 → 微信支付 API(/v3/pay/transactions/native
  • 异步回调:微信支付 → 集成中心(验签 + 解密)→ pay-common → 业务侧
  • 状态同步:网页轮询查单接口或前端 WebSocket/SSE 推送,发现支付完成后跳转结果页

架构总览

┌──────────────┐ ┌──────────────────┐ ┌────────────────────┐
│ PC 收银台 │ │ CloudBase 云 API │ │ pay-common │
│ 网页/客户端 │ │ 网关 │ HTTP │ (HTTP 云函数) │
│ │ Bearer │ accessToken │ invoke │ SDK 自签 │
│ fetch ─────►│ Token │ 鉴权 │─────────►│ /v3/pay/transactions/native
│ │ └──────────────────┘ └─────────┬──────────┘
│ │ │ 统一下单
│ │ ▼
│ │ ┌────────────────────────┐
│ │ ◄──── 返回 code_url ──────────────── │ 微信支付 API │
│ │ │ api.mch.weixin.qq.com │
│ 渲染二维码 │ └──────────┬─────────────┘
│ (qrcode.js) │ │ 异步支付回调
│ │ ┌── 用户用手机微信扫码 ──┐ ▼
│ │ │ │ ┌────────────────────────┐
│ 轮询查单 │ ▼ │ │ 集成中心 │
│ 或推送状态 │ 手机微信完成支付 │ │ (验签 + 解密) │
│ │ └─────────────────────────┘ └──────────┬─────────────┘
│ │ │ 明文转发
│ │ ▼
│ │ ┌────────────────────────┐
│ │ │ pay-common │
│ │ │ /wechatpay/order │
│ │ │ → orderService 处理 │
└──────────────┘ └────────────────────────┘

Native 支付的关键特点:

维度与 JSAPI/小程序的区别
下单接口/v3/pay/transactions/native(不同接口)
下单返回{ code_url: "weixin://wxpay/bizpayurl?pr=..." }需要前端生成二维码
是否需要 openid不需要,无 payer.openid 字段
appid公众号 AppID 或小程序 AppID(已与商户号关联即可)
支付完成感知没有前端调起回调,需要轮询查单或服务端推送
运行环境任意浏览器/客户端,无微信容器依赖

Native 支付的服务端逻辑与小程序/JSAPI 走的是同一个 pay-common 云函数模板,只是 _action 不同,路由内部走不同的下单 strategy。


前置条件

要求
公众号 / 小程序 / 移动应用至少其一已通过认证;其 AppID 将作为下单 appid
微信支付 Native 支付权限商户平台「产品中心」中开通 Native 支付(一般认证后默认开通)
微信支付商户号已申请并完成与上述 AppID 的关联
商户超管权限用于下载 API 证书、设置 APIv3 密钥
前端二维码渲染任意 QR Code 库(qrcode / qrcode.js / 后端生成 PNG)

第一步 · 准备商户凭证

#字段说明
1appId公众号 / 小程序 / 移动应用 AppID(已与商户号关联)
2merchantId10 位数字商户号
3apiV3Key32 字符 APIv3 密钥
4merchantSerialNumber40 位十六进制证书序列号
5privateKeyapiclient_key.pem 完整内容
6wxPayPublicKey微信支付公钥 PEM
7wxPayPublicKeyId公钥 ID(PUB_KEY_ID_...

Native 支付下,appId 可以是公众号或小程序 AppID(甚至开放平台移动应用 AppID),只要在商户平台「Native 支付」产品下与商户号完成关联即可。


第二步 · 在 CloudBase 创建集成

与小程序支付指南完全相同。集成创建后,系统自动生成:

  • HTTP 云函数 pay-common
  • 回调基础域名 https://<集成标识>.integration-callback.tcloudbase.com
  • 支付回调路径 /wechatpay/order
  • 退款回调路径 /wechatpay/refund

完整支付回调 URL 需填入微信支付商户平台 → 产品中心 → 开发配置 → 支付通知 URL


第三步 · 了解云函数和环境变量

完全复用小程序指南所述源码结构与环境变量注入逻辑。Native 下单已在 pay-common 中实现,对应 _action: wxpay_order_native


第四步 · 接入 PC 收银台

pay-common 部署完成后,PC 端的接入分四步:调用 pay-common 拿 code_url → 前端渲染二维码 → 轮询查单 → 跳转结果页。

4.1 支付相关路由

Native 与其他支付方式共享同一套 pay-common 路由,差异只在下单 _action

_action类别说明
wxpay_order_native下单Native 扫码下单(本指南使用)
wxpay_query_order_by_out_trade_no查询按商户订单号查单(Native 必备,前端轮询使用)
wxpay_query_order_by_transaction_id查询按微信订单号查单
wxpay_close_order关单关闭订单(10 分钟未支付建议主动关闭)
wxpay_refund退款申请退款
wxpay_refund_query退款查询退款

与 JSAPI/小程序最大差别:Native 下单wxpay_order_native返回 { code_url } 而非 { timeStamp, paySign, ... }

所有路由通过 body 中的 _action 字段区分,除回调路由外均需携带 Authorization: Bearer {access_token}

4.2 回调处理

完全复用小程序指南所述 services/orderService.js 中的方法(handlerUnified / handlerUnifiedTrigger / handlerRefund / handlerRefundTrigger),原则相同:尽早返回、幂等、金额校验、状态原子更新。

Native 支付与 JSAPI/小程序回调结构 100% 一致——都是 event_type: TRANSACTION.SUCCESS,明文中拿到 out_trade_no / transaction_id / amount 等字段。业务侧无需为支付方式区分回调代码。

4.3 完整调用示例

适合 PC 网页场景。下面是从下单到展示二维码、轮询查单的端到端示例。

关键约束(与 pay-common 源码行为一致,详见 GitHub pay-common):

  • ENV_ID:云开发环境 ID
  • FN_NAME:集成创建后自动生成的 HTTP 云函数名(形如 <集成标识>-<随机串>
  • 下单请求体必填 3 个字段description(商品描述,≤ 127 字符)、amount.total(正整数,单位分)、out_trade_no(6–32 位字母/数字/下划线/连字符,全局唯一)
  • 无需 payer.openid(仅 JSAPI/小程序需要)、无需 appid(pay-common 自动注入集成配置的 AppID)、无需 notify_url(pay-common 自动使用集成中心的回调地址)
  • accessToken 必须来自「认证登录」——匿名登录(signInAnonymously)拿到的 token 无权调用云函数,网关会直接返回 401。具体登录方式(邮箱、手机号、微信公众号/开放平台、CloudBase 自定义登录等)请参考 CloudBase Web SDK · 认证登录
  • 返回结构有三层嵌套{ code, msg, data: { status, data: { code_url } } }——云 API 网关外层 { code, msg, data }、wechatpay-node-v3 中间层 { status, data }、最内层是微信返回的 { code_url }
  • 二维码内容直接用 code_url 原文(形如 weixin://wxpay/bizpayurl?pr=...),不要做 URL 编码
  • 支付状态以服务端回调为准,前端轮询查单只是 UI 提示
<!-- pc-cashier.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>扫码支付</title>
<style>
.cashier { text-align: center; padding: 40px; }
#qrcode { margin: 24px auto; }
.status { color: #888; font-size: 14px; margin-top: 12px; }
</style>
</head>
<body>
<div class="cashier">
<h2>请使用微信扫码支付</h2>
<h3>金额:<span id="amount">¥ 0.20</span></h3>
<div id="qrcode"></div>
<div class="status" id="status">等待扫码……</div>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script src="https://static.cloudbase.net/cloudbase-js-sdk/latest/cloudbase.full.js"></script>
<script>
const ENV_ID = 'your-env-id'
const FN_NAME = 'pay-common' // 集成创建后生成的实际函数名
const POLL_INTERVAL = 2000 // 查单间隔 2s
const POLL_TIMEOUT = 600000 // 10 分钟后停止轮询并关单

const app = cloudbase.init({ env: ENV_ID })
const auth = app.auth({ persistence: 'local' })

// Web 端拿 accessToken
// ⚠️ 匿名登录(signInAnonymously)拿到的 accessToken 无法通过云 API 网关调用云函数,
// 云函数侧会返回 401。必须使用「认证登录」获取 accessToken。
// 可选登录方式(邮箱、手机号、微信公众号/开放平台、CloudBase 自定义登录等)与完整代码,
// 参见 CloudBase Web SDK 文档:
// https://docs.cloudbase.net/api-reference/webv2/authentication#%E8%AE%A4%E8%AF%81%E7%99%BB%E5%BD%95
async function getAccessToken() {
// 示例:使用邮箱 + 验证码登录,获取可访问云函数的 accessToken
// const loginState = await auth.signIn({
// username: 'user@example.com',
// verification_code: '...',
// verification_token: '...',
// })
// return loginState.credential.accessToken
throw new Error('请参考 CloudBase Web SDK 文档实现认证登录后返回 accessToken')
}

// 统一封装:调用 pay-common 的任意路由
async function callPayCommon(token, payload) {
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 ${token}`,
},
body: JSON.stringify(payload),
}
)
return res.json()
}

// 从 pay-common 返回结构中解包业务数据
// pay-common 返回: { code: 0, msg: 'success', data: { status: 200, data: <业务> } }
function unwrap(body) {
if (!body || body.code !== 0) return null
const inner = body.data
return inner && inner.data ? inner.data : inner
}

;(async () => {
const token = await getAccessToken()
const out_trade_no = 'ORDER_' + Date.now()

// 1. Native 下单(无需 openid / appid / notify_url)
const orderRes = await callPayCommon(token, {
_action: 'wxpay_order_native',
description: '测试商品',
out_trade_no,
amount: { total: 20, currency: 'CNY' }, // 0.2 元
})
const order = unwrap(orderRes)
if (!order || !order.code_url) {
document.getElementById('status').textContent = '下单失败:' + (orderRes.msg || '')
return
}
// order.code_url 形如 'weixin://wxpay/bizpayurl?pr=...'

// 2. 渲染二维码(code_url 原样作为二维码内容,不要 URL 编码)
QRCode.toCanvas(document.getElementById('qrcode'), order.code_url, { width: 240 })

// 3. 轮询查单
const startedAt = Date.now()
const timer = setInterval(async () => {
// 超时关单
if (Date.now() - startedAt > POLL_TIMEOUT) {
clearInterval(timer)
await callPayCommon(token, {
_action: 'wxpay_close_order',
out_trade_no,
})
document.getElementById('status').textContent = '订单已超时关闭'
return
}

const queryRes = await callPayCommon(token, {
_action: 'wxpay_query_order_by_out_trade_no',
out_trade_no,
})
const detail = unwrap(queryRes)
const state = detail && detail.trade_state

if (state === 'SUCCESS') {
clearInterval(timer)
document.getElementById('status').textContent = '支付成功,正在跳转……'
location.href = `/order/result?out_trade_no=${out_trade_no}`
} else if (state === 'CLOSED' || state === 'PAYERROR' || state === 'REVOKED') {
clearInterval(timer)
document.getElementById('status').textContent = '订单已 ' + state
} else if (state === 'USERPAYING') {
document.getElementById('status').textContent = '用户支付中……'
}
// NOTPAY:继续等待
}, POLL_INTERVAL)
})()
</script>
</body>
</html>

实现要点:

  • code_url 直接用作二维码内容,不要 URL 编码、不要嵌入 HTTPS 链接,扫码后微信会自行跳转支付页。
  • 三层解包:示例中的 unwrap() 函数封装了固定的三层结构——云 API 网关的 { code, msg, data } → wechatpay-node-v3 的 { status, data } → 微信原始业务数据。所有 pay-common 路由的返回都遵循这个结构。
  • 轮询查单是 Native 支付的常规感知方式,建议 2–3 秒一次,10 分钟未支付则前端主动调 wxpay_close_order 关闭订单。
  • 更优体验:服务端在 handlerUnifiedTrigger 收到回调后,通过 WebSocket / SSE 主动推送结果到该用户对应的 PC 会话,可省去轮询;本指南为简化未演示。
  • 最终发货等业务副作用以服务端回调为准,前端轮询到 SUCCESS 仅用于跳转 UI。

多 AppID 场景

pay-common 支持通过 useServiceAccount 参数动态切换 appid(需在云函数环境变量中额外配置 service_app_id)。典型场景:集成里默认是小程序 AppID,但某些 Native 场景希望用服务号 AppID 下单:

// 下单时带上 useServiceAccount: true
await callPayCommon(token, {
_action: 'wxpay_order_native',
useServiceAccount: true, // 用 service_app_id 下单
description: '测试商品',
out_trade_no,
amount: { total: 20, currency: 'CNY' },
})

若未配置 service_app_id 环境变量,pay-common 会直接报错返回 useServiceAccount=true 但未配置 service_app_id 环境变量

4.4 真机测试

Native 支付测试比 JSAPI/小程序简单:

  1. 用任意桌面浏览器(Chrome/Safari)打开 pc-cashier.html,看到二维码即下单成功。
  2. 手机微信「扫一扫」二维码,弹出支付页,输入密码完成支付。
  3. PC 端应在 2–4 秒内显示"支付成功"并跳转结果页(取决于轮询间隔)。
  4. 同步检查云函数日志:tcb fn log <FN_NAME> 中应能看到 handlerUnifiedTrigger - 支付结果: ... SUCCESS 表示回调链路也成功。

测试金额建议同小程序:使用 0.1–1 元,避免 0.01 元触发风控;同一账号单日扫码支付次数也有限制。


常见问题 FAQ

Q1:返回的 code_url 形如 weixin:// 开头,能直接放到 img src 吗

不能。weixin:// 是私有 schema,不是 HTTPS URL。必须用前端 QR Code 库(qrcode.js / canvas)把字符串绘制成二维码图片

Q2:扫码后微信提示「该链接无法访问」

通常是把 code_url 做了 URL 编码或包了一层跳转链接。code_url 必须原样作为二维码内容,连去除前后空白都不要做。

Q3:扫码后微信提示「支付链接已失效」

code_url 默认 2 小时后过期;如果用户长时间没扫,需要重新下单生成新链接。前端应同时提供"刷新二维码"按钮。

Q4:手机扫码后没反应,没弹支付页

可能原因:

  1. 商户号未开通 Native 支付权限(商户平台「产品中心」→ Native 支付 → 申请开通)
  2. appId 与商户号未完成关联
  3. 二维码生成时使用了错误编码(比如 base64)导致内容损坏

Q5:PC 网页轮询查单报 401 / token 过期

CloudBase Web SDK 拿到的 accessToken 默认 2 小时过期。简单做法:每次调用前重新拿 token;更优做法:在 auth.onLoginStateChanged 中监听并刷新。

Q6:能否不轮询,只靠服务端回调通知前端

可以,常见方案:

  1. WebSocket / SSE:业务后端在 handlerUnifiedTrigger 收到回调后,通过 ws/sse 推送给该 out_trade_no 对应的 PC 会话。
  2. CloudBase 实时数据库:服务端把订单状态写到云数据库,PC 前端用实时 SDK db.collection('orders').doc(id).watch() 监听变更。

两种都能省去前端轮询,体验最好。轮询作为兜底依然建议保留。

Q7:Native 支付能否用同一个集成同时支持小程序支付

能。pay-common 模板中 Native 与 JSAPI/小程序下单走同一个 SDK,只是路由(_action)不同。注意:

  • 集成中心填的 appId 决定下单时的 appid。如果该 AppID 在商户平台已开通 Native 与 JSAPI 两种支付,pay-common 就能同时支持。
  • 如果 Native 想用一个 AppID(如开放平台移动应用 AppID)、小程序支付想用另一个 AppID,需要两个独立集成或改 pay-common 代码动态切换。

Q8:Native 支付的回调与小程序支付有什么差别

没有差别。微信支付 V3 的回调对所有支付方式(JSAPI / Native / H5 / APP)都是同一份结构 TRANSACTION.SUCCESS,验签机制、解密方式完全一致。集成中心已代做验签和解密,pay-common 的 handlerUnifiedTrigger 拿到的明文格式与下单方式无关。


附录 · 与其他支付方式的差异速查

维度小程序JSAPI 网页Native(本指南)
_actionwxpay_orderwxpay_orderwxpay_order_native
下单接口/v3/pay/transactions/jsapi/v3/pay/transactions/jsapi/v3/pay/transactions/native
是否需 openid
下单返回prepay_id + 5 字段prepay_id + 5 字段code_url
调起方式wx.requestPaymentWeixinJSBridge.invoke二维码 + 用户扫码
支付完成感知前端 success 回调JSBridge ok 回调轮询查单 / 服务端推送
运行环境微信小程序微信内置浏览器任意浏览器/客户端
回调结构TRANSACTION.SUCCESSTRANSACTION.SUCCESSTRANSACTION.SUCCESS(一致)

延伸阅读

主题链接
Native 下单https://pay.weixin.qq.com/doc/v3/merchant/4012791900
查询订单https://pay.weixin.qq.com/doc/v3/merchant/4012791898
关闭订单https://pay.weixin.qq.com/doc/v3/merchant/4012791899
支付回调协议https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/payment-notice.html
CloudBase Auth · Web SDKhttps://docs.cloudbase.net/api-reference/webv2/authentication
QRCode.jshttps://github.com/soldair/node-qrcode