因为需要做一个付款在winform应用程序上扫码付款,所以采用native扫码的模式一来实现.
微信支付扫码的签名验证借用了github上的开源框架 node-tenpay
node后台笔者偏爱于插件式的开源轻量级框架thinkjs
再补充一个微信扫码技术文档 微信开发文档
第一步: 商户支付回调URL设置:
进入商户平台-->产品中心-->开发配置 扫码支付回调链接
我设置的是 https://域名/pay/notify
第二步: 生成二维码:
首先 npm 引入tenpay 框架
npm i tenpay --save
全局创建一个tenpay api对象
// wxpay.js
const Tenpay = require('tenpay')
const config = require('../config/config.js')
/**
* tenpayConfig: {
appid: '公众号ID',
mchid: '微信商户号',
partnerKey: '微信支付安全密钥',
notify_url: 'https://域名/pay/complete', // 支付后回调的接口
spbill_create_ip: '服务器ip地址'
}
*/
let api = null
module.exports = {
getApi () {
if(!api) api = new Tenpay(config.tenpayConfig)
return api
}
}
根据商品ID生成url链接
api.getNativeUrl({
product_id: product.id
})
将商品链接转成二维码
node二维码生成开源框架:node-qrcode
npm install --save qrcode // npm 安装.
const QRCode = require('qrcode')
...
const qr = await QRCode.toDataURL(order.url) // 转成base64
this.header('Content-Type', 'image/png; charset=UTF-8') // 设置头部
this.body = base64ToSteam(qr) // 输出图片流
// 将base64图片转流方法.
function base64ToSteam (data) {
const base64 = data.replace(/^data:image\/\w+;base64,/, '')
const dataBuffer = Buffer.from(base64, 'base64')
const bufferStream = new stream.PassThrough()
bufferStream.end(dataBuffer)
return bufferStream
}
生成商品二维码后,只要有用户用微信扫码,微信会自动调你之前在商户后台设置的回调链接
第三步 扫码回调链接开发
tenpay框架已经将几个回调的参数校验解析已中间件的形式开放出来,这里我们只需要将他集成到thinkjs的中间件管理里,因为有多个地方用到了中间件,集中封装一下:
const wxpay = require('./wxpay') // 代码参考最上面,就是拿到tenpay的实例对象.
// 默认options
const defaultOptions = {
type: 'nativePay' // 扫码的回调中间件传入nativePay
}
module.exports = (options = {}) => {
options = Object.assign({}, defaultOptions, options);
let api = wxpay.getApi() // 拿到实例对象
let pay = api.middleware(options.type) // 创建回调函数
return pay
}
然后在thinkjs的middleware.js 中间件管理中的后面插入:
// middleware.js
const pay = require('../utils/paycallback')
....
{
handle: 'payload', // thinkjs 自己的参数转换中间件,类似于bodyParser的功能.
options: {
...
}
},
// 扫码回调
{
handle: pay,
options: {
type: 'nativePay'
},
match: '/pay/notify' // 这里根据自己的接口匹配设置,代表中间件只在这个接口起作用.
}
然后开发 /pay/notify 接口:
中间件会帮你在ctx对象下绑定两个方法:
replyNative : 扫描成功,传入微信的预订单编号
reply : 失败,传入失败原因.
try {
let res = this.ctx.request
let info = res.weixin // 中间件会将微信的参数解析到request下的weixin对象中.
let product_id = info.product_id // 商品id,就是你生成url链接时候,传入的商品id
let openid = info.openid // 用户的openid 在你商户下对应微信唯一ID
// 下面是业务代码
// 获取商品信息.
let product = await this.model('product_list').where({ id: product_id }).find()
if (think.isEmpty(product)) {
return this.ctx.reply('未找到商品')
}
// 自己数据库生成订单号.
....
// 调用tenpay的unifiedOrder方法统一下单,生成预订单编号.
let result = await api.unifiedOrder({
out_trade_no: orderId, // 订单编号
body: product.name, // 商品名称
total_fee: product.amount * 100, // 我们数据库是元为单位,微信要求分为单位提交
openid: openid,
trade_type: 'NATIVE'
})
....
return this.ctx.replyNative(result.prepay_id) // 成功后将统一下单生成的prepay_id 通过replyNative方法返回给微信服务器
} catch (error) {
this.ctx.reply(error.message || '服务器错误')
}
然后将代码发布到服务器上后,你测试用微信扫码后,会发现服务器会返回报错,原因是tenpay要求开发人员将微信的xml内容解析后直接已字符串的形式放在body下,而thinkjs的payload会把xml内容解析后,将xml变成对象的形式,放在 body.xml 下.所以我们还要设置一步让payload不要解析xml,直接把流转成文字放在body下就可以了(这是我看了tenpay的源码后才发现的...)
{
handle: 'payload',
options: {
....
extendTypes: {
text: ['text/xml', 'application/xml'] // xml以text形式解析.
}
}
},
第四步 用户支付后微信回调
tenpay框架的config里如果设置了notify_url地址,微信在用户支付后会回调这个接口
首先一样,让tenpay框架的中间件去处理微信解析和校验的功能
// middleware.js
....
一样要放在payload后面
// 用户支付完成通知.
{
handle: pay,
options: {
type: 'pay'
},
match: '/pay/complete' // 你的回调链接 同上.
},
....
然后开发支付回调/pay/complete接口:
try {
let info = this.ctx.request.weixin // 拿到中间件解析内容.
const orderModel = this.model('order')
if (info.return_code === 'SUCCESS') {
let orderId = info.out_trade_no // 订单号
let total_fee = parseInt(info.total_fee) / 100 // 金额
if (info.result_code === 'SUCCESS') { // 支付成功.
// 交易成功.
let order = await orderModel.where({ id: orderId }).find()
if (think.isEmpty(order)) throw new Error('未找到订单')
// 自己数据库订单处理
....
} else { // 支付失败
....
}
}
this.ctx.reply() // 回复消息(参数为空回复成功, 传值则为错误消息)
} catch (error) {
this.ctx.reply(error.message || '服务器错误')
}
第五步 主动向微信服务器查询订单信息
到第4步已经走完了,但是总有些订单有可能因为网络/数据库原因,complete接口没有接收到或者保存失败之类的.这时候要有一个主动向服务器请求订单状态的接口,这里简单写一下提供参考:
let code = this.query('code')
let result = await api.orderQuery({
out_trade_no: code
})
// return this.success(result)
if (result.return_code === 'SUCCESS') {
if (result.result_code === 'SUCCESS') {
if (result.trade_state === 'SUCCESS') {
// 付款成功
let amount = parseInt(result.total_fee) / 100
let complete_time = result.time_end.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1-$2-$3 $4:$5:$6')
....
// 成功回调.
} else {
// 付款失败
return this.success({
status: 1,
message: result.trade_state_desc,
info: result
})
}
} else {
return this.fail(result.err_code_des)
}
} else {
return this.fail(result.return_msg)
}