前段时间在开发一个功能,要求是通过微信二维码进行扫码支付。这个情景我们屡见不鲜了,各种电子商城、线下的自动贩卖机等等都会有这个功能。平时只是使用者,如今变为开发者,也是有不小的坑。所以特此写一篇博客记录一下。
注: 要开发微信二维码支付,你必须要有相应的商户号的权限,否则你是无法开发的。若无相应权限,本文不推荐阅读。
两种模式
打开微信支付的文档,我们可以看到两种支付模式:模式一和模式二。这二者的流程图微信的文档里都给出了(不过说实话画得真的有点丑)。
文档里指出了二者的区别:
模式一开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid和openid。
模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。
模式一是我们平时在网购的时候比较常见的,会弹出一个专门的页面用于扫码支付,然后支付成功后这个页面会再次跳转回回调页面,通知你支付成功。第二种的话想对少一些,不过第二种开发起来相对简单点。 本文主要介绍模式二的开发 。
搭建Koa2的简单开发环境
快速搭建Koa2的开发环境我推荐可以使用koa-generator 。脚手架能帮我们省去Koa项目一开始的一些基本中间件的书写步骤。(如果你想学习Koa最好自己搭建一个。如果你已经会Koa了就可以使用一些快速脚手架了。)
首先全局安装 koa-generator :
npm install -g koa-generator #or yarn global add koa-generator
然后找一个目录用来存放Koa项目,我们打算给这个项目取个名字叫做 koa-wechatpay ,然后就可以输入 koa2 koa-wechatpay 。然后脚手架会自动创建相应文件夹 koa-wechatpay ,并生成基本骨架。进入这个文件夹,安装相应的插件。输入:
npm install #or yarn
接着你可以输入 npm start 或者 yarn start 来运行项目(默认监听在3000端口)。
如果不出意外,你的项目跑起来了,然后我们用postman测试一下:
这条路由是在 routes/index.js 里。

如果你看到了
{
"title": "koa2 json"
}就说明没问题。(如果有问题,检查一下是不是端口被占用了等等。)
接下来在 routes 文件夹里我们新建一个 wechatpay.js 的文件用来书写我们的流程。
签名
跟微信的服务器交流很关键的一环是签名必须正确,如果签名不正确,那么一切都白搭。
首先我们需要去公众号的后台获取我们所需要的如下相应的id或者key的信息。其中 notify_url 和 server_ip 是用于当我们支付成功后,微信会主动往这个url post 支付成功的信息。
签名算法如下:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
为了签名正确,我们需要安装一下 md5 。
npm install md5 --save #or yarn add md5
const md5 = require('md5')
const appid = 'xxx'
const mch_id = 'yyy'
const mch_api_key = 'zzz'
const notify_url = 'http://xxx/api/notify' // 服务端可访问的域名和接口
const server_ip = 'xx.xx.xx.xx' // 服务端的ip地址
const trade_type = 'NATIVE' // NATIVE对应的是二维码扫码支付
let body = 'XXX的充值支付' // 用于显示在支付界面的提示词然后开始写签名函数:
const signString = (fee, ip, nonce) => {
let tempString = `appid=${appid}&body=${body}&mch_id=${mch_id}&nonce_str=${nonce}¬ify_url=${notify_url}&out_trade_no=${nonce}&spbill_create_ip=${ip}&total_fee=${fee}&trade_type=${trade_type}&key=${mch_api_key}`
return md5(tempString).toUpperCase()
}其中 fee 是要充值的费用,以分为单位。比如要充值1块钱, fee 就是100。ip是个比较随意的选项,只要符合规则的ip经过测试都是可以的,下文里我用的是 server_ip 。 nonce 就是微信要求的不重复的32位以内的字符串,通常可以使用订单号等唯一标识的字符串。
由于跟微信的服务器交流都是用xml来交流,所以现在我们要手动组装一下post请求的 xml :
const xmlBody = (fee, nonce_str) => {
const xml = `
<xml>
<appid>${appid}</appid>
<body>${body}</body>
<mch_id>${mch_id}</mch_id>
<nonce_str>${nonce_str}</nonce_str>
<notify_url>${notify_url}</notify_url>
<out_trade_no>${nonce_str}</out_trade_no>
<total_fee>${fee}</total_fee>
<spbill_create_ip>${server_ip}</spbill_create_ip>
<trade_type>NATIVE</trade_type>
<sign>${signString(fee, server_ip, nonce_str)}</sign>
</xml>
`
return {
xml,
out_trade_no: nonce_str
}
}如果你怕自己的签名的 xml 串有问题,可以提前在微信提供的签名校验工具里先校验一遍,看看是否能通过。
发送请求
因为需要跟微信服务端发请求,所以我选择了 axios 这个在浏览器端和node端都能发起ajax请求的库。
安装过程不再赘述。继续在 wechatpay.js 写发请求的逻辑。
由于微信给我们返回的也将是一个xml格式的字符串。所以我们需要预先写好解析函数,将xml解析成js对象。为此你可以安装一个 xml2js 。安装过程跟上面的类似,不再赘述。
微信会给我们返回一个诸如下面格式的 xml 字符串:
<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wx742xxxxxxxxxxxxx]]></appid> <mch_id><![CDATA[14899xxxxx]]></mch_id> <nonce_str><![CDATA[R69QXXXXXXXX6O]]></nonce_str> <sign><![CDATA[79F0891XXXXXX189507A184XXXXXXXXX]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <prepay_id><![CDATA[wx152316xxxxxxxxxxxxxxxxxxxxxxxxxxx]]></prepay_id> <trade_type><![CDATA[NATIVE]]></trade_type> <code_url><![CDATA[weixin://wxpay/xxxurl?pr=dQNakHH]]></code_url> </xml>
我们的目标是转为如下的js对象,好让我们用js来操作数据:
{
return_code: 'SUCCESS', // SUCCESS 或者 FAIL
return_msg: 'OK',
appid: 'wx742xxxxxxxxxxxxx',
mch_id: '14899xxxxx',
nonce_str: 'R69QXXXXXXXX6O',
sign: '79F0891XXXXXX189507A184XXXXXXXXX',
result_code: 'SUCCESS',
prepay_id: 'wx152316xxxxxxxxxxxxxxxxxxxxxxxxxxx',
trade_type: 'NATIVE',
code_url: 'weixin://wxpay/xxxurl?pr=dQNakHH' // 用于生成支付二

