从零开始,小程序后端接入微信支付 APIv3
前言
最近参与的项目需要接入微信支付系统(不然哪来收益),但我对微信的支付系统一无所知;在学习过程中,我发现我搜索到的文章,十篇有九篇语焉不详地解释了如何从1%开始开发一个99%完成度的能接入微信支付的前后端系统的相关步骤——然而,我最不知道的恰恰从0到1需要做些什么。所以,为了帮助和我同样对小程序接入微信支付一无所知但有相关需求的朋友们,我写下这篇博文,记录自己对于小程序接入微信支付系统的理解。
本文基于[v3版本的微信支付API](开发指引-小程序支付 | 微信支付商户平台文档中心 (qq.com))撰写。
从官方文档开始
按照开发指引-小程序支付 | 微信支付商户平台文档中心 (qq.com)的说法,小程序及其后端服务进行一次完整的微信支付的流程,可以用这张图来描述:
简而言之,以后端服务的视角来看,它需要做这样几件事:
- 监听小程序发来的下单请求,创建后端订单,并且向微信支付系统请求下单;得到请求结果后,把支付参数发给小程序
- 监听微信支付系统发来的支付结果,更改后端订单状态,并且向微信支付系统返回结果
- 监听小程序查询订单状态的请求。这个请求应当是长轮询的。
到这里为止,官方文档表达的逻辑非常清晰易懂。这三件事,需要后端实现两个和微信支付系统强相关的功能:
- 向微信支付请求下单
- 接收微信支付结果
以及除此以外的常规的业务代码。
实战&踩坑
看到这里,您恐怕会以为我也是一个从 CSDN 跑出来的只会抄文档的 CV 工程师,但实际上不是的——否则我才懒得把我的经历总结成博客。在这一部分,我将对我遇到的一些问题进行总结,并给出我的解决方案。
如果您不熟悉微信支付业务流程,可以先看下文的省流文档,以免难以理解本部分。
发送请求鉴权
当时我对照“向微信支付请求下单”和“接收微信支付结果”这两个接口的官方文档(下文有我二次总结的版本),编写了代码,希望对微信支付系统的接口进行了测试——然后,在测试前者的过程中,我按文档要求填写了所有的字段,但返回结果喜提 401 Unauthorized。
这究竟是怎么回事呢?查看请求的返回报文的 body,我发现,诶,还有一个叫签名的东西!
1 | { |
当时我一头雾水,啊?什么签名?整篇文档里只有错误码部分提到了签名啊?
然后我搜索了这个《微信支付商户 REST API 签名规则》微信把 RESTful 写成 REST 一定是想让我多休息,才发现,原来发送请求给微信支付系统的 v3 API 时,需要在请求头中的 Authorization 字段内携带鉴权信息。
那么这个鉴权信息是什么呢?熟悉 Jwt 的同学肯定知道,采用 Jwt 作为鉴权方案时,Authorization 字段的值为 Bearer jwt_content
;同样地,微信支付要求 Authorization 字段的值为认证类型 签名信息
——认证类型现在固定为 WECHATPAY2-SHA256-RSA2048
,而如何生成签名信息则是本段要着重讲解的。
签名是由签名算法作用于被签名的文本生成的,微信支付 v3 API 采用 SHA256-RSA 作为签名算法。它是一种非对称加密算法,所以需要我们在微信支付的后台页面设置 APIv3 密钥,并生成公钥和私钥。注意,密钥,公钥和私钥,三者是三个不同的东西!
设置完上文提及的公钥、私钥、密钥之后,就可以参考签名生成-接口规则来编写代码,生成请求头中的 Authorization 字段了。文档比较长,其中的内容是比较清晰的——我不想全面复述一遍,只想稍作总结:
首先,要获取本次请求的 HTTP 方法,请求的 URL 的域名之后的部分,时间戳 timestamp ,随机字符串 nonce_str四项信息;然后,将四项信息按照刚才的顺序以
a=114514\b=1919810\n
这样的形式(最后一行之后也要加换行符)拼成一个字符串,并用 SHA256 with RSA ,导入私钥进行签名;最后,将其用 base64 编码。
这样就得到了签名的内容。注意,签名是签名信息的一部分,还有其他部分!
有了签名之后,就可以构造签名信息字符串了:
1 | WECHATPAY2-SHA256-RSA2048 mchid="114514",nonce_str="hhaaaaa",signature="bAse64sIgnATure==",timestamp="213379200",serial_no="1919810" |
携带这样的鉴权消息,即可给微信支付系统正常发送请求啦!
由于我使用klover2/wechatpay-node-v3-ts来完成这一步,所以我无法给出我实际编写使用的代码,以免提及我不知道的东西,传播错误的知识。
解密支付通知回调
刚才的流程图中提到,微信支付通知后端服务支付完成的实现方式是“发送一个请求给后端服务的某接口”,而这个接口是在发送请求时设置的,并且它不会也不能添加任何参数,所有信息都放在 POST 请求的 body 中。
解密密文时,请使用上文提及的 密钥,也就是微信支付后台显示的 APIv3 密钥。
省流版官方文档
向微信支付请求下单
POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
参数
必填字段
整个接口没有需要 query 传参的,全部放在请求报文的 body 里。
- appid:小程序的 appid,开发小程序过程中必然接触到的概念。注意,这个参数要以字符串格式传输!
- mchid:商户号。小程序需要接入商户号才可以进行支付。如果您使用服务商模式而非商户模式,那么本文对您的参考价值可能并不大。注意,这个参数同样要以字符串格式传输!
- description:商品描述,长度为1-127的字符串。
- out_trade_no:商户订单号,由6-32位大小写字母,数字,以及_-*三个符号组成,并且在同一个商户号下只能是唯一的。
- notify_url:通知地址,必须以 HTTPS 协议访问,不能携带参数,是上文中提及后端需要实现的两个功能之一,下文中会详细阐述。
- amount:订单金额,是一个 object,表示订单金额信息,别直接把金额数字填进去。
- total:int,总金额,单位为分!
- currency: string[1,16],币种,境内商户号仅支持人民币 CNY,填别的应该都会报错。
- payer:object,支付者信息
- openid:string[1,128],微信小程序用户的 openid,和上文的 appid 一样,都是微信小程序开发中遇到的概念。
非必填字段
time_expire:交易结束时间,string[1,64],格式为 yyyy-MM-DDTHH:mm:ss+TIMEZONE,其中 DD 后面的 T 就是字母 T,TIMEZONE 在中国一般为+08:00,表示东八区。如果要为西某区开发,那请参考RFC3339规范
attach:附加数据,在查询和支付通知中原样返回,而且只有支付完成状态才会返回该字段。
goods_tag:订单优惠标记, string[1,32],看起来只是个标记。
support_fapiao:电子发票入口开放与否的标识,在微信支付商户平台或微信公众平台开通电子发票功能后才可以为 true。传入 true 时,支付成功消息和支付详情页将出现开票入口。
还有三个传入 object 的非必填字段 detail,scene_info 和 settle_info,我自己暂时用不到,懒得写了。
示例
请求示例
这是官方给的。
1 | { |
返回示例
这也是官方给的。如果没出错,返回的就应该是形如它的 json。
1 |
|
接收微信支付结果
后端服务需要监听在”向微信支付请求下单“时发送的请求里的 notify_url。注意,它不能携带参数,这也意味着,微信支付系统发过来的请求,会包含”区分这个请求是哪一个订单的支付结果“的必要信息。
监听到微信支付系统发来的消息后,需要给它回应。一旦它收到您后端服务的应答不符合规范或超时,就会认为这次通知失败了,然后通过一定的策略定期重新发起通知。通知间隔为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
这部分的官方文档异常清晰(这真的是我能在腾讯的官网上看到的文档吗,微信小程序学着点),所以我不打算多费笔墨去重新解释——我只打算把我实际遇到的一些小问题写下来,充当注意事项,其余的内容请参考微信支付官方文档。