有关支付类开发,现在比以前要简单很多了,微信和支付宝两大支付巨头早已经给出了非常详细的接入文档,并且迭代了好多版本,但在实际开发中其实文档的可读性还是有些磕磕绊绊的,而且也有一些坑需要注意。以微信支付来说,其实大多数商户的接入方式,文档里还不是很直白,引导也有些混乱,我也是搞了一段时间才摸清楚里面的大致逻辑。
1. 准备工作
首先需要公司以商户身份在微信支付中开通账号,涉及到上传公司的一些营业执照,法人身份等信息,审核通过后即可开通。后台界面如下:
2. 功能介绍
交易中心是查看你的微信支付的订单信息的,你的系统接入微信支付后,你系统里的订单和微信支付的订单有个1对1的对应关系,你的应用用户以后凡是支付过的订单都可以在这里面查看,核对。
账户中心,在这里公司管理员可以给公司各种角色,比如开发人员,财务,客服等员工开通账号并配置权限,而且还有支付安全信息的配置,比如密钥,证书这些。
营销中心,可以给你的应用配置红包,满减,消费券等活动参数,小型应用可以不看。
产品中心,这个界面是员工的主要操作台,管理员给公司各个角色配置的功能,都在这里操作,比如我作为开发人员,财务,运营,在这里都能看到我可以操作的功能模块。
数据中心,主要用来排查错误异常的。
本文以开发者角度来科普微信支付怎么接入,技术栈为angularjs+nodejs+mongo
3. 开发接入
我开发的系统是pc网站和h5手机端接入微信支付,需求很简单:pc端用户选购完产品点击支付,弹出二维码并设置倒计时时间,用户扫码完成支付,类似12306pc网站那种。h5手机站需要在用户点击支付时候,唤起微信app进行支付操作。(网站只接入微信支付,不接入微信登录)
用户支付的钱先到我们公司的总账户,然后公司对公分润给供应商。
3.1 管理员开通权限
管理员需要在账户中心给开发人员开通:native支付,h5支付这三个功能。pc端网站生成二维码可以用native支付,手机端h5站就选择h5支付。
3.2 关键参数配置
要接入微信支付并调用api接口,以下几个关键参数要在开发前进行配置。
mchid和appid
mchid就是商户号,只要注册通过成为商户,登陆后右上角会自动显示你的商户号。
appid是你的应用id,怎么理解呢,就是你要接入微信支付这个功能前,必须注册一个微信生态里的应用,才能使用微信支付。这些应用可以是服务号,订阅号,小程序等等,具体解释可以看这里:https://kf.qq.com/faq/1801116VJfua1801113QVNVz.html
感觉appid这个逻辑还是有些强制性,微信强制开发者遵循他自身的生态闭环,比如我们要开发的这个平台,用的是我们公司之前的一个服务号,但是这个服务号跟系统其实没啥联系。
API证书和私钥, Apiv3密钥,serial_no
这三个概念容易搞混,我大白话解释一下,你想把你的系统接入微信支付,微信官方得给你一个授权的身份,你只有这个身份合法了,调用人家接口人家才认得你,不然都按非法请求。API证书和私钥就类似于你主动调用微信接口使用到的公私钥对,获取方式和概念解释如下:
https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
利用证书工具最终会生成apiclient_cert.pem,apiclient_key.pem这两个文件,可以简单理解这两个文件就是证书和私钥。调用时候需要在http请求头设置一个Authorization参数,微信端就是来通过解析Authorization的值判断你这个请求是否合法。
Authorization参数怎么生成,具体方法可以看签名生成-接口规则 | 微信支付商户平台文档中心,
上面解释了api证书和私钥,那另外的Apiv3密钥是什么,当微信端回调你的接口时候,发过来加密的请求,你要用的就是Apiv3密钥进行解密。再直白点说两者区别,当你调用微信接口时候用到的是api证书和私钥加签,当微信端回调你的接口时候,你用Apiv3密钥解签。微信生成订单接口支持回调的,就是说支付成功后微信可以回调我的一个接口修改这个订单状态,但是文档里说可能有延迟以及支付成功但始终无回调的情况发生,因此在我的开发中对于订单状态的修改,我是主动去查询微信端接口的,而不是依赖于微信回调,所以在我的系统中Apiv3密钥就没有用到。
serial_no是商户api证书序列号,发送请求时候放在header里的Wechatpay-Serial就行了。
4. 代码相关
微信支付api相关的接口调用文档在微信支付-开发者文档,注意微信目前文档里涉及到v2和v3两个版本,目前接入一般直接选择最新的v3版本,所以看文档要看v3版本的文档。我在实际开发中查看微信官方文档时,里面有些跳转混乱,会在v2和v3来回跳,记住看到的文档里url包含/v3就是最新文档,比如下面这个:
我后台用的是node,但微信api接口只有java,.net, python示例(为啥不支持node),我后来在社区里找到了两个支持node版本的npm包,wechatpay-node-v3和wxpay-v3,前者比较好用。项目git地址在GitHub - klover2/wechatpay-node-v3-ts: 微信支付v3
可以通过组装http调用,如下:
var WxPay = require('wechatpay-node-v3');
const pay = new WxPay({
appid: config.appid,
mchid: config.mchid,
publicKey: fs.readFileSync(__dirname + "/wxkey/" + 'apiclient_cert.pem'), // 公钥
privateKey: fs.readFileSync(__dirname + "/wxkey/" + 'apiclient_key.pem'), // 秘钥
serial_no: config.serial_no
});
var orderNo = "native121775ee0120140703355773bb";
function testPay() {
try {
const params =
{
"mchid": config.mchid, // 商户号
"out_trade_no": orderNo, // 系统订单号
"appid": config.appid, // appId
"time_expire": "2022-05-23T14:57:00+08:00", // 过期时间
"description": "Image形象店-深圳腾大-QQ公仔", //
"notify_url": "https://weixin.qq.com/", // 回调地址,如果不参与回调,可以随便填
"amount": {
"total": 1, // 金额,单位分
"currency": "CNY"
},
}
const nonce_str = Math.random().toString(36).substr(2, 15), //随机字符串
timestamp = parseInt(+new Date() / 1000 + '').toString(), //时间戳 秒
url = '/v3/pay/transactions/native';
// 获取签名
const signature = pay.getSignature('POST', nonce_str, timestamp, url, params); //如果是get 请求 则不需要params 参数拼接在url上 例如 /v3/pay/transactions/id/12177525012014?mchid=1230000109
// 获取头部authorization 参数
const authorization = pay.getAuthorization(nonce_str, timestamp, signature);
const result = await request
.post('https:api.mch.weixin.qq.com/v3/pay/transactions/native')
.send(params)
.set({
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
Authorization: authorization,
"Wechatpay-Serial" : config.serial_no
});
const result = await pay.transactions_native(params);
} catch (error) {
console.log(error);
}
}
这个npm包也支持隐式直接调用,也可以一个方法搞定,源码大家可以自行研究
function testPay() {
return new Promise ((resolve) => {
pay.transactions_native(params).then((result) => {
console.log('result==========>', result);
if (result.status == 200) {
resolve(result.code_url);
} else {
resolve(result.message);
}
})
})
}
主流程主要涉及的三个接口:
1. 生成订单
对应wechatpay-node-v3里面的方法就是pay.transactions_native(pc端),pay.transactions_h5(手机端)
https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_native.md
https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md
微信api对应:
2.关闭订单(取消)
对应wechatpay-node-v3里面的方法就是pay.closewechatpay-node-v3-ts/close.md at master · klover2/wechatpay-node-v3-ts · GitHub
微信api对应:关闭订单
3. 查询订单状态
对应wechatpay-node-v3里面的方法就是pay.queryhttps://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/query.md
微信api对应:查询订单状态
5. 业务逻辑与实现
pc端用户点击支付后弹出二维码并显示倒计时90s,用户支付成功后,在这里有两种更新订单状态的方式,第一种常见的方式是,在前台或后台有个定时器在90s期间轮询到微信端查询接口查看订单状态,一旦发现成功就自动刷新整个页面给出支付成功提示。这样的方式用户体验度很好,不过当出现类似秒杀这种瞬间大批量用户同时支付时候,会给后台很大请求压力。
另一种方式是二维码弹出框下有支付成功和支付取消两个按钮,用户支付后自己点击支付成功,我后台去更新订单状态。这样即使在秒杀活动中也能分担掉大批量请求同时涌入的情况,不过缺点就是牺牲了一些用户体验度,另外我们更新订单状态不能只依赖用户自己点击这一种情况,如果用户支付后直接关掉页面或者刷新页面订单状态就迟迟未更新。所以这时我们还需要在后台有一个1-2m的定时任务,去扫描订单表中超时的订单做状态更新。
我用的是第二种方式,这里面除了需要后台有额外一个定时任务,还需要在状态更新方法里考虑多种情况:用户未支付但点击了支付成功,用户支付成功后但点击了支付取消,支付时间过期后点击了支付成功等等情况。