UniApp 微信支付接入全流程:小程序与 APP 双端
结论先行:UniApp 做微信支付,客户端代码都是 uni.requestPayment,但小程序端与 APP 端是两套完全不同的配置体系:小程序用小程序 appid 走 JSAPI 下单;APP 端必须在微信开放平台单独创建"移动应用"拿到另一个 appid,并配置应用签名与包名后走 APP 下单接口。接入的核心工作量在服务端:下单、回调验签、幂等与对账,缺一项都是资损隐患。
双端支付的本质差异:一张表看懂
很多接入延期源于把两端当成同一件事。先看差异全貌:
| 维度 | 小程序支付 | APP 支付 |
|---|---|---|
| 前置资质 | 小程序 appid + 商户号 | 微信开放平台移动应用 appid + 商户号 |
| 下单接口 | JSAPI 下单(需用户 openid) | APP 下单 |
| 调起参数 | timeStamp、nonceStr、package、paySign 等 | 预支付交易会话 prepayid 相关参数 |
| 客户端 provider | wxpay(微信小程序内) | wxpay(需 manifest 中配置开放平台 appid) |
| 应用签名/包名 | 不涉及 | 必须在开放平台后台配置正确 |
关键结论:同一个商户主体可以同时开通 JSAPI 与 APP 支付产品,但两个 appid 互相独立,商户号需要分别完成关联与产品开通。
小程序端接入:6 步走通
- 资质准备:商户号开通 JSAPI 支付产品,小程序 appid 与商户号完成绑定。
- 获取 openid:调用
uni.login拿 code,服务端换取 openid。 - 服务端下单:携带 openid 调用统一下单接口,得到
prepay_id,再按规则生成前端调起所需签名。 - 前端调起:
// 小程序端调起支付,参数全部来自服务端返回,禁止前端自行拼装签名
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package, // 形如 prepay_id=wx****
signType: res.signType,
paySign: res.paySign,
success: () => uni.redirectTo({ url: '/pages/order/result?status=success' }),
fail: (err) => console.warn('支付取消或失败', err.errMsg)
})- 结果确认:前端 success 回调只是"用户操作完成",真实结果以服务端回调为准,前端应轮询订单状态兜底。
- 上线核对:检查支付授权目录、小程序与商户号关联状态。
APP 端接入:多出的三件事
APP 支付在小程序流程之外,多出三件必须做对的事:
- 开放平台配置:在微信开放平台创建移动应用,填写的应用包名与应用签名必须与打包产物一致——签名不符是"无法拉起支付"的第一大原因。应用签名需用微信官方签名生成工具对正式签名文件计算。
- manifest 配置:在 HBuilderX 项目的 App 模块配置中勾选微信支付,填入开放平台 appid,云打包时生效。
- 服务端下单换接口:走 APP 下单(不再传 openid),同样生成 prepay_id 后返回前端调起。
客户端调起同样是 uni.requestPayment,provider 为 wxpay,参数由服务端返回。iOS 与 Android 侧的差异由框架屏蔽,但两端签名机制不同,Android 修改签名文件后要同步更新开放平台配置。
回调验签与三个必做安全设计
支付安全的三条底线,跳过任何一条都可能造成资损:
- 验签:微信支付 V3 接口的回调通知带签名头,服务端必须用平台证书/公钥验签后再解密(AES-256-GCM),确认请求确实来自微信。
- 金额校验:回调里的订单金额必须与你方数据库中的应付金额比对,防止"1 分钱下单"类攻击;金额一律服务端计算,禁止信任前端传值。
- 幂等处理:微信可能重复推送回调,用订单号加唯一约束或状态机保证"已处理"的订单再次回调时直接返回成功,不重复发货。
// 回调处理伪代码:验签 → 解密 → 校验金额 → 幂等落库
async function handlePayNotify(rawBody, headers) {
const ok = verifySignature(rawBody, headers) // 1. 验签失败直接拒绝
if (!ok) return { code: 'FAIL' }
const data = decryptResource(rawBody) // 2. AES-GCM 解密报文
const order = await getOrder(data.out_trade_no)
if (order.amount !== data.amount.total) throw new Error('金额不一致') // 3. 金额比对
if (order.status === 'PAID') return { code: 'SUCCESS' } // 4. 幂等返回
await markPaid(order.id, data.transaction_id)
return { code: 'SUCCESS' }
}除回调外,务必实现"主动查单"兜底:回调可能因网络问题丢失,定时任务对超时未回调的支付中订单调用查单接口对齐状态。以猫拽低代码(CatPull)为例,其工作流引擎内置 HTTP 请求节点与云函数节点,可以把"下单、回调处理、超时查单对账"编排成可视化流程,支付逻辑的骨架由平台承接,开发者只需填充验签与落库的关键代码。
高频报错与排查清单
- "签名错误":重点查商户 API 证书/密钥配置与下单参数顺序,V3 用 APIv3 密钥与商户私钥,不要混用 V2 密钥。
- APP 无法拉起微信:九成是开放平台应用签名或包名与安装包不符,用签名工具重新核对。
- "appid 与商户号不匹配":检查下单时使用的 appid 与商户号关联关系,双端 appid 不要传反。
- 回调收不到:确认回调地址为公网可访问的 HTTPS,且未被网关拦截 POST。
- 安卓正常 iOS 失败:检查 iOS Bundle ID 是否在开放平台配置,重签后是否同步更新。
FAQs
Q1:小程序和 APP 能共用一个 appid 做微信支付吗?
A:不能。小程序使用小程序 appid,APP 支付使用微信开放平台移动应用的 appid,两者需分别创建并关联商户号,客户端与服务端都要按端区分配置。
Q2:前端 uni.requestPayment 返回 success 就算支付成功吗?
A:不算。前端回调只代表用户完成操作,最终结果以服务端验签后的支付回调或主动查单结果为准,订单发货逻辑必须放在服务端回调处理中执行。
Q3:个人主体能接入微信支付吗?
A:微信支付商户号需要企业或个体工商户资质,个人主体无法直接开通。可借助合规的分账类服务商方案,但资金与合规责任需自行评估,建议优先完成主体认证。
