Native(PC 扫码)支付集成指南
在 CloudBase 控制台「集成中心」创建微信支付集成后,平台会自动为你生成一个 HTTP 云函数,封装好下单、查单、退款 、回调等全部能力。本指南介绍如何基于该云函数完成 Native 支付——即 PC 网页/桌面应用展示二维码,用户用手机微信扫码完成支付的接入闭环。
关于函数名的约定:集成中心生成的云函数名形如
<集成名>-<随机串>(例如miniapp-wxpay-rwmx67sc)。为方便讲解,本文统一称其为pay-common——你在自己的代码中替换为实际函数名即可。
适用场景:PC 收银台、桌面客户端、自助终端、广告屏等所有"非微信内浏览器"的扫码支付需求。
不适用:
- 微信内 H5 → 用 JSAPI 网页支付
- 小程序 → 用小程序支付
- 微信外手机浏览器 → 用 H5 支付
整体调用链路:
- 正向请求:网页 → CloudBase 云 API 网关(accessToken 鉴权)→
pay-commonHTTP 云函数 → 微信支付 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) |
第一步 · 准备商户凭证
| # | 字段 | 说明 |
|---|---|---|
| 1 | appId | 公众号 / 小程序 / 移动应用 AppID(已与商户号关联) |
| 2 | merchantId | 10 位数字商户号 |
| 3 | apiV3Key | 32 字符 APIv3 密钥 |
| 4 | merchantSerialNumber | 40 位十六进制证书序列号 |
| 5 | privateKey | apiclient_key.pem 完整内容 |
| 6 | wxPayPublicKey | 微信支付公钥 PEM |
| 7 | wxPayPublicKeyId | 公钥 ID(PUB_KEY_ID_...) |
Native 支付下,
appId可以是公众号或小程序 AppID(甚至开放平台移动应用 AppID),只要在商户平台「Native 支付」产品下与商户号完成关联即可。
第二步 · 在 CloudBase 创建集成
与小程序支付指南完全相同。集成创建后,系统自动生成:
- HTTP 云函数
pay-common - 回调基础域名
https://<集成标识>.integration-callback.tcloudbase.com - 支付回调路径
/wechatpay/order - 退款回调路径
/wechatpay/refund
完整支付回调 URL 需填入微信支付商户平台 → 产品中心 → 开发配置 → 支付通知 URL。
第三步 · 了解 pay-common 云函数
完全复用微信小程序支付集成指南所述源码结构与环境变量注入逻辑。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:云开发环境 IDFN_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/webv3/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/小程序简单:
- 用任意桌面浏览器(Chrome/Safari)打开
pc-cashier.html,看到二维码即下单成功。 - 手机微信「扫一扫」二维码,弹出支付页,输入密码完成支付。
- PC 端应在 2–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 小时后过期;如果用户长时间没扫,需要重新下单生成新链接。前端应同时提供"刷新二维码"