外观
看购支付接入文档
简介
本文档介绍如何在看购 App 内的 H5 页面中唤起微信支付,适用于在看购平台上架 H5APP 商城的开发者。
看购采用微信支付服务商模式:看购是服务商,你是看购名下的子商户。你的业务服务器通过看购支付(KGPay)提供的微信兼容 API 下单,协议与微信支付 APIv3 服务商接口一致,可以直接使用微信支付官方 SDK。
更详细的看购支付(KGPay)帮助文档,请登录看购支付平台 pay.citv.cc 查看。
读完本文,你应该能让自己的 H5 商城在看购 App 里完成一笔真实付款。
一、业务流程
① 你的业务服务器 持有 KGPay 发放的微信兼容 API 证书
│ POST {网关}/v3/pay/partner/transactions/app 预下单
│ 网关应答里直接带着签好的 APP 调起参数(wxApp)
▼
② 你的 H5 商城(运行在看购 App 的 WebView 中)
│ kg.chooseWXPay(wxApp)
▼
③ 看购 App ──▶ 微信(直接唤起,不经过看购服务端)
④ 支付结果:KGPay 网关 ──支付通知──▶ 你的业务服务器(notify_url)
你的业务服务器 ──查单──▶ KGPay 网关(兜底)看购服务端不参与这条链路:不转发、不签名、不记流水、不参与定价。看购 App 只负责拿着你服务器签好的参数唤起微信。款项进入你的子商户号。
也正因为看购服务端不经手订单,支付结果只能由你自己的服务器确认——H5 里的 success 回调不带任何订单状态。
三条红线
上线前请确认你没有踩到这三条中的任何一条:
- 商户私钥、APIv3 密钥绝不能出现在 H5 页面里。 前端能读到,就等于任何人都能用你的子商户号任意金额下单、伪造支付通知。预下单和签名必须在你的服务器上完成。
- 金额由服务器决定,H5 只传订单号。 不要让 H5 把金额传给服务器再去下单,否则改一下请求就能 1 分钱买走商品。
- 不要拿
success回调当作支付成功。 它只代表微信回了"支付完成",用户可能付完没回到 App、订单也可能已关闭。发货一律以你服务器收到的支付通知或查单结果为准。
二、接入前准备
| 步骤 | 在哪里做 | 拿到什么 |
|---|---|---|
| 1. 开通子商户 | 看购支付平台 / 联系看购运营 | 子商户号 subMchid,款项进入这个商户号 |
| 2. 领取参数 | 看购支付平台 | 一份 meta.txt + 商户私钥 apiclient_key.pem + 微信支付公钥 pub_key.pem |
| 3. 部署 | 你自己的业务服务器 | 以上参数写进服务端配置(环境变量或配置中心,不要提交进代码库) |
自建 H5 商城不需要在看购开发者中心登记支付渠道——看购服务端不参与你的支付。
meta.txt 参数说明
| 参数 | 示例 | 用途 |
|---|---|---|
gatewayUrl | https://pay.example.com | 网关地址,替代微信的 https://api.mch.weixin.qq.com |
compatMchid | 5957139992 | 兼容商户号。请求签名的 mchid,下单时的 sp_mchid。不是APP 调起参数的 partnerId |
merchantSerialNo | 6A92D712… | 商户 API 证书序列号,请求签名用 |
pubKeyId | PUB_KEY_ID_… | 微信支付公钥 ID,验证网关应答和支付通知的签名 |
apiV3Key | 32 位字符串 | APIv3 密钥,解密支付通知 |
subMchid | 1750389389 | 你的子商户号,下单时的 sub_mchid |
spAppid | wxa381da5bf6852984 | 看购 App 的微信 AppID,下单时的 sp_appid,APP 调起参数的 appId |
subAppid | 空 | 不用填 |
sp_appid 只能是看购 App 的 AppID
你的 H5 运行在看购 App 里,微信只认看购 App 这一个 AppID。sp_appid 填了别的值,预下单可能成功,但 App 会直接报错、唤不起微信。
三、第一步:业务服务器预下单
3.1 请求参数
POST {gatewayUrl}/v3/pay/partner/transactions/app| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sp_appid | string | 是 | meta.txt 的 spAppid |
sp_mchid | string | 是 | meta.txt 的 compatMchid |
sub_mchid | string | 是 | meta.txt 的 subMchid |
out_trade_no | string | 是 | 你自己的订单号,见 3.2 |
description | string | 是 | 商品描述,会显示在微信支付界面和账单里 |
notify_url | string | 是 | 你自己业务服务器的支付通知地址,公网可达,不能带 query 参数 |
amount.total | int | 是 | 金额,单位分,必须大于 0 |
amount.currency | string | 否 | 固定 CNY |
其余可选字段以 KGPay 文档为准。
3.2 关于 out_trade_no
- 由你自己生成,6–32 位,只能是数字、大小写字母和
_-|* - 在你的子商户号下必须唯一
- 同一张业务单允许多次发起支付(用户取消后重试)时,建议每次生成新的
out_trade_no,而不是直接复用业务订单号,并在自己的库里记录两者的对应关系
3.3 请求签名
签名规则与微信支付 APIv3 完全一致(WECHATPAY2-SHA256-RSA2048),推荐直接用微信支付官方 SDK,只需把请求地址换成 gatewayUrl,并用「微信支付公钥」模式初始化:
go
// 官方 SDK:github.com/wechatpay-apiv3/wechatpay-go
priv, _ := utils.LoadPrivateKeyWithPath("apiclient_key.pem")
pub, _ := utils.LoadPublicKeyWithPath("pub_key.pem")
client, err := core.NewClient(ctx,
option.WithWechatPayPublicKeyAuthCipher(compatMchid, merchantSerialNo, priv, pubKeyId, pub))
result, err := client.Post(ctx, gatewayUrl+"/v3/pay/partner/transactions/app", map[string]any{
"sp_appid": spAppid,
"sp_mchid": compatMchid,
"sub_mchid": subMchid,
"out_trade_no": outTradeNo,
"description": "商品A",
"notify_url": "https://your-server.com/pay/notify",
"amount": map[string]any{"total": 1, "currency": "CNY"},
})
var rs struct {
PrepayId string `json:"prepay_id"`
AppId string `json:"appid"`
PartnerId string `json:"partnerid"`
Package string `json:"package"`
NonceStr string `json:"noncestr"`
Timestamp string `json:"timestamp"`
Sign string `json:"sign"`
}
err = core.UnMarshalResponse(result.Response, &rs)不要用写死微信域名的 SDK
部分第三方 SDK 把 api.mch.weixin.qq.com 写死在代码里,无法指向网关。其他语言请使用对应的微信支付官方 SDK(Java / PHP / Node.js 等),同样选择「微信支付公钥」模式。
3.4 取 APP 调起参数
网关的预下单应答除 prepay_id 外,直接带着一套签好的 APP 调起参数:
json
{
"prepay_id": "wx80216678…",
"appid": "wxa381da5bf6852984",
"partnerid": "1117385739",
"prepayid": "wx80216678…",
"package": "Sign=WXPay",
"noncestr": "e0b05134…",
"timestamp": "1789465671",
"sign": "flxa2tfR…"
}partnerid 是看购在微信侧的真实服务商号,sign 由网关用真实服务商证书签出,微信只认这一套。原样转成下面的结构返回,不要改任何字段,也不要自己重签:
go
wxApp := map[string]string{
"appId": rs.AppId,
"partnerId": rs.PartnerId,
"prepayId": rs.PrepayId,
"package": rs.Package,
"nonceStr": rs.NonceStr,
"timeStamp": rs.Timestamp,
"sign": rs.Sign,
}不要用 compatMchid 和商户私钥自己签调起参数
compatMchid 不是微信商户号。自己拼 appid\ntimestamp\nnoncestr\nprepayid\n 用商户私钥签出来的参数,预下单看着成功,App 里却唤不起微信,而且没有明确报错。
把 { outTradeNo, wxApp } 返回给你的 H5 页面。
四、第二步:H5 页面唤起支付
js
// 页面加载时初始化一次(必须先于支付调用)
kg.config({ appId: "YOUR_APP_KEY" });
kg.ready(async () => {
if (kg.getInitError()) return;
// 1. 向你自己的服务器下单:只传订单号,金额由服务器决定
const data = await fetch("/api/my-shop/prepay", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ orderId: "xxx" }),
}).then((r) => r.json());
// 2. 把 wxApp 原样交给看购 App
kg.chooseWXPay({
...data.wxApp,
success() {
// 只代表微信回了支付完成,去自己的服务器确认
pollMyOrder(data.outTradeNo);
},
cancel() {
/* 用户取消 */
},
fail(res) {
showToast(res.errMsg);
},
});
});
| 参数 | 说明 |
|---|---|
appId | 看购 App 的微信 AppID,须与预下单的 sp_appid 一致 |
partnerId | 网关应答的 partnerid(真实微信服务商号) |
prepayId | 预下单返回的 prepay_id |
package | 固定 Sign=WXPay,可不传 |
nonceStr | 随机串 |
timeStamp | 秒级时间戳 |
sign | 网关应答的 sign |
字段名也接受微信 APP 支付文档里的全小写写法(appid / partnerid / prepayid / noncestr / timestamp),服务端 SDK 的返回值可以直接透传。
回调含义:
| 回调 | 含义 |
|---|---|
success | 微信回了支付完成。不带订单状态,必须到你自己的服务器确认 |
cancel | 用户在微信里取消了支付 |
fail | 参数缺失、appId 不对、App 版本过旧等,res.errMsg 里有原因,见第七节 |
要的是 APP 支付参数,不是公众号 JSAPI 参数
公众号 JSAPI 的 { timestamp, nonceStr, package: 'prepay_id=…', signType, paySign } 签名串与 APP 支付不同,在看购 App 里唤不起微信,SDK 会直接走 fail。请用 /v3/pay/partner/transactions/app 下单。
建议 success 后每 2 秒向自己的服务器查一次订单状态,持续约 1 分钟;服务器那边订单仍是待支付时,顺手调一次第六节的查单接口。
五、第三步:接收支付通知
这是你判断订单是否付款成功的依据。 前端回调只用于改善体验,不能拿来发货。
支付成功后,网关向预下单时填写的 notify_url 发送 POST 请求,格式与微信支付 APIv3 支付通知一致。官方 SDK 提供了现成的通知处理器,以下是它替你做的事情。
5.1 验签
用微信支付公钥验证请求头里的签名,Wechatpay-Serial 应等于 pubKeyId:
验签串 = {Wechatpay-Timestamp}\n{Wechatpay-Nonce}\n{请求体原文}\nDANGER
验签不通过一律丢弃。 否则任何人构造一个请求就能让你白白发货。验签要用原始请求体,不要先反序列化再序列化。
5.2 解密
请求体的 resource 是密文,算法 AEAD_AES_256_GCM,密钥是 apiV3Key,nonce 与 associated_data 在 resource 里。
解密后的主要字段:
| 字段 | 说明 |
|---|---|
sp_mchid | 兼容商户号 |
sub_mchid | 子商户号,应等于你的 subMchid |
out_trade_no | 你的订单号,用它定位自己的订单 |
transaction_id | 微信支付交易号 |
trade_state | 交易状态,见下表 |
success_time | 支付完成时间 |
amount.total | 订单金额(分),应等于你下单时的金额 |
trade_state | 含义 | 建议处理 |
|---|---|---|
SUCCESS | 支付成功 | 发货 |
NOTPAY | 未支付 | 保持待支付 |
USERPAYING | 用户支付中 | 保持待支付 |
CLOSED | 已关闭 | 置关闭 |
REVOKED | 已撤销 | 置失败 |
PAYERROR | 支付失败 | 置失败 |
REFUND | 转入退款 | 走退款流程 |
5.3 校验
发货前再核对两项,任何一项不符都不要发货,并记录日志:
sub_mchid等于你自己的子商户号amount.total等于你库里这笔订单的金额
5.4 应答
处理成功返回 HTTP 200(或 204)。处理失败返回非 2xx 状态码,并带上 {"code":"FAIL","message":"原因"},网关会稍后重新推送。
5.5 幂等
通知会重复投递,这是正常现象而不是故障。 你的处理必须幂等:
- 先查自己的订单状态,已经是"已支付"就直接返回成功,不要重复发货、不要覆盖支付时间
- 更新语句建议带状态守卫,例如
UPDATE ... WHERE out_trade_no = ? AND pay_status <> 1
六、第四步:主动查单兜底
通知可能因网络问题丢失。以下两种情况请主动查单:H5 收到 success 后来问你订单状态、而你还没收到通知时;订单超过 5 分钟仍是待支付时。
GET {gatewayUrl}/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={compatMchid}返回的 trade_state / transaction_id / amount.total 含义同 5.2,处理规则也一样:SUCCESS 且金额一致才置为已支付。
用户放弃支付的订单可以关单,关单后同一个 out_trade_no 不能再支付:
POST {gatewayUrl}/v3/pay/transactions/out-trade-no/{out_trade_no}/close
{"mchid": "{compatMchid}"}七、常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
chooseWXPay:fail 未知 action | 用户的看购 App 版本过旧,不支持服务商模式 | 提示用户升级看购 App |
chooseWXPay:fail appId 与看购 App 不一致… | sp_appid / appId 不是看购 App 的 AppID | 改用 meta.txt 的 spAppid 重新下单 |
chooseWXPay:fail 缺少参数 … | wxApp 字段不全 | 检查服务器返回的结构,见 3.4 |
chooseWXPay:fail 提示公众号 JSAPI 参数 | 用了 JSAPI 下单接口或 JSAPI 签名方式 | 改用 /v3/pay/partner/transactions/app,见 3.4 |
chooseWXPay:fail 当前 App 未集成微信支付 | 在非正式构建的看购 App(如 Expo Go)中打开 | 在正式看购 App 内测试。普通浏览器里 SDK 会模拟成功,不能用来验证支付 |
| 网关返回签名错误 / 401 | 商户号、证书序列号、私钥三者不匹配;或自己拼签名串拼错了 | 核对 meta.txt;改用官方 SDK |
| SDK 报应答验签失败 | 用了「平台证书」模式,或 pubKeyId / 公钥文件不对 | 改用「微信支付公钥」模式初始化,见 3.3 |
| 唤起微信后立刻失败,无提示 | 没用网关返回的调起参数,自己用 compatMchid 重签了;或改动了其中字段 | 按 3.4 原样透传网关应答 |
| 用户付了钱但订单还是待支付 | 通知丢失,或验签失败被丢弃 | 检查通知日志;补上第六节的主动查单 |
收到 success 后查单仍是 NOTPAY | 支付结果同步有延迟 | 间隔几秒再查,不要立即判定失败 |
八、上线自检清单
上线前逐条确认:
- [ ] 商户私钥、
apiV3Key只存在于服务器,H5 打包产物里搜不到 - [ ] H5 只把订单号传给服务器,金额由服务器决定
- [ ]
sp_appid是meta.txt的spAppid(看购 App 的 AppID) - [ ] APP 调起参数原样取自网关应答,
partnerId不是compatMchid - [ ]
amount.total单位是分 - [ ]
notify_url是自己的地址,公网可达,不带 query 参数 - [ ] 支付通知做了验签,验签失败直接丢弃
- [ ] 通知处理核对了
sub_mchid和金额 - [ ] 通知处理是幂等的,重复投递不会重复发货
- [ ] 有主动查单兜底,不把
success回调当作支付成功 - [ ] 在正式看购 App 里用 0.01 元真实付过一笔
九、支付的其他问题
退款、账单下载等能力请查看 KGPay 支付平台(pay.citv.cc)文档,你的业务服务器用同一套证书调用。
附:KGPay 网关表单模式
早期接入的 kg.chooseWXPay({ form }) 方式(业务服务器用 appSecret 对 KGPay 下单表单做 MD5 签名,经看购服务端转发)仍然可以使用,新接入请使用本文的服务商模式。两种方式的调用区别见 JSSDK 文档 · 支付。