1、小程序(小程序ID和密钥)
申请微信支付,如果之前有申请过微信支付的公众号,可以关联小程序
2、小程序关联商户号(拿到商户ID和密钥)
3、支持https
测试阶段不需要https
文档:
业务流程
小程序支付文档
1)调用小程序登录接口,获取用户openid
登录API
服务端登录接口
2)商户server统一下单、生成预支付订单,返回小程序支付接口需要的数据
预支付交易单接口
拿着预支付订单信息进行再次签名
3)统一下单结束后,小程序前端调用统一下单接口
调用wx.requestPayment(OBJECT)发起微信支付
// node koa2
// index.js
const router = require('koa-router')()
const md5 = require('md5');
const request = require('superagent');
const parser = require("fast-xml-parser"); // join to xml
let j2xParser =parser.j2xParser
router.prefix('/wx')
const config={
WX_MCH_ID: '1535063641', // 商户id
WX_SHOP_KEY: '23782ASDAsagdjgHDK1123SASasfdsfd', // 商户密钥
WX_APP_ID: 'wx7884e3fd54e3bf98', // 小程序id
WX_APP_SECRET: '5862d91d01fe030d10858ed491d4156e' // 小程序密钥
}
// 随机数
function getRnd32(){
let str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
for(let i=0; i<32;i++){
let rnd = Math.floor(Math.random()*str.length)
result +=str[rnd]
}
return result
}
function getTradeNo(){
let date = new Date()
let arr = [date.getFullYear(), (date.getMonth()+1),date.getDate(),date.getHours(),date.getMinutes(),date.getSeconds(),date.getMilliseconds(), Math.round(Math.random() * 23 + 100)]
return arr.join('')
}
// ip
function getClientIp(req) {
var ip = req.headers['x-forwarded-for'] ||
req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress || '';
if(ip.split(',').length>0){
ip = ip.split(',')[0]
}
return ip;
};
// 签名
function getWXSing(params, shopKey){
let str = []
for(const key in params){
str.push(key+'='+params[key])
}
str.sort(function(a,b){
return a.localeCompare(b)
})
str.push('key='+ shopKey)
str = str.join('&')
str = md5(str)
return str
}
// 1、登录获取用户opendi
router.get('/code2session', async (ctx, next) => {
let js_code = ctx.query.code
let result = await request
.get('https://api.weixin.qq.com/sns/jscode2session')
.query({
appid: config.WX_APP_ID,
secret: config.WX_APP_SECRET,
js_code: js_code,
grant_type: 'authorization_code'
})
if(result.statusCode == 200){
let {session_key, openid} = JSON.parse(result.text)
}else{
console.log(result.error)
}
ctx.body = { 'success': 'true', 'data': JSON.parse(result.text) }
})
// 2、在微信支付服务后台生成预支付交易单
router.get('/wxpay', async (ctx, next) => {
let openid = ctx.query.openid
let params ={
appid: config.WX_APP_ID,
mch_id: config.WX_MCH_ID,
nonce_str: getRnd32(),
body: 'ccc',
out_trade_no: getTradeNo(),
total_fee: 1,
spbill_create_ip: getClientIp(ctx),
notify_url: 'http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php',
trade_type: 'JSAPI',
openid: openid,
}
let str = getWXSing(params, config.WX_SHOP_KEY)
params.sign = str
var j2xparser = new j2xParser();
let obj = {
xml: params
}
var xml = j2xparser.parse(obj)
// console.log(xml)
let result = await request
.post('https://api.mch.weixin.qq.com/pay/unifiedorder')
.set('Content-Type', 'application/xml')
.send(xml)
let data
if(result.statusCode == 200){
var jsonObj = parser.parse(result.text)
data = {
timeStamp: parseInt(new Date().getTime() / 1000) + '',
nonceStr: params.nonce_str,
package: 'prepay_id=' + jsonObj.xml.prepay_id,
signType: 'MD5',
appId: config.WX_APP_ID
}
let sing = getWXSing(data, config.WX_SHOP_KEY)
data = Object.assign(data , { paySign:sing })
}else{
console.log(result.error)
}
ctx.body = { 'success': 'true', 'data': data }
})
module.exports = router
// 微信代码
//app.js
App({
onLaunch: function () {
var that = this
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
if (res.code) {
wx.request({
url: 'http://localhost:8083/wx/code2session',
data: {
code: res.code
},
success(res) {
console.log()
that.globalData.openid = res.data.data.openid
}
})
}
}
})
},
globalData: {
openid: null
}
})
// 微信代码
//index.js
const app = getApp()
Page({
data: {
},
pay:function(e){
wx.request({
url: 'http://localhost:8083/wx/wxpay', //仅为示例,并非真实的接口地址
data: {
openid: app.globalData.openid
},
header: {
'content-type': 'application/json' // 默认值
},
success(res) {
console.log(res.data.data)
let data = res.data.data
wx.requestPayment({
timeStamp: data.timeStamp,
nonceStr: data.nonceStr,
package: data.package,
signType: data.signType,
paySign: data.paySign,
success:function(res){
console.log('success')
console.log(res)
},
fail:function(res){
console.log('fail')
console.log(res)
},
complete:function(res){
console.log('complete')
console.log(res)
}
})
}
})
}
})
1、页面一直报,签名验证失败
二次加密的时候记得要带上 appId
2、用了它校验后一直没成功过,耗了一些时间,最终还是按文档来整
校验签名是否成功