微信支付是腾讯集团旗下的第三方支付平台,致力于为用户和企业提供安全、便捷、专业的在线支付服务。以“微信支付,不止支付”为核心理念,为个人用户创造了多种便民服务和应用场景。微信支付为各类企业以及小微商户提供专业的收款能力,运营能力,资金结算解决方案,以及安全保障。用户可以使用微信支付来购物、吃饭、旅游、就医、交水电费等。企业、商品、门店、用户已经通过微信连在了一起,让智慧生活,变成了现实。
小程序实现微信支付主要有两种方式:
接口文档:https://pay.xunhuweb.com/document
node安装相关依赖
router .js
npm install koa3-wechat
npm install short-uuid
npm i raw-body
npm i redis
npm i babel-runtime@6.26.0
在路由模块中引入支付模块
const Pay = require("./pay")
const Router = require("@koa/router")
const router = new Router({
prefix: '/user'
});
Pay.init(router)
module.exports = router
pay.js
const { wepay: WechatPay } = require('koa3-wechat');
const short = require('short-uuid');
// const fs = require('fs');
const wepay = require("../lib/wepay")
const Order = require("../models/order-model")
const wepay2 = require('../lib/wepay2')
const wepay3 = require('../lib/wepay3')
function init(router) {
// post /user/my/order
// 下新定单,准备支付
router.post('/my/order', async ctx => {
let { uid: userId, openId } = ctx.user
let { totalFee, addressId, addressDesc, goodsCartsIds, goodsNameDesc } = ctx.request.body
// 为测试方便,所有金额支付数均为1分
totalFee = 1
let payState = 0
// 依照Order模型接收参数
let outTradeNo = `${new Date().getFullYear()}${short().new()}`
// 获取订单的预支付信息
var trade = {
body: goodsNameDesc.substr(0, 127), //最长127字节
attach: '支付测试', //最长127字节
out_trade_no: outTradeNo, //
total_fee: totalFee, //以分为单位,货币的最小金额
trade_type: 'JSAPI',//NATIVE
spbill_create_ip: ctx.request.ip, //ctx.request.ip
openid: openId
};
var params = await wepay.getBrandWCPayRequestParams(trade);
let err = '', res
if (params && params.package && params.paySign) {
// 创建记录
res = await Order.create({
userId,
outTradeNo,
payState,
totalFee,
addressId,
addressDesc,
goodsCartsIds,
goodsNameDesc
})
if (!res) err = 'db create error'
} else {
err = 'error! return null!'
console.log(err);
}
ctx.status = 200
ctx.body = {
code: 200,
msg: !err ? 'ok' : '',
data: {
res,
params
}
}
})
// post /user/my/order2
// 使用weixin-pay实现的接口,测试通过
router.post('/my/order2', async ctx => {
let { uid: userId, openId } = ctx.user
let { totalFee, addressId, addressDesc, goodsCartsIds, goodsNameDesc } = ctx.request.body
// 为测试方便,所有金额支付数均为1分
totalFee = 1
let payState = 0
// 依照Order模型接收参数
let outTradeNo = `${new Date().getFullYear()}${short().new()}`
console.log('outTradeNo', outTradeNo);
// 获取订单的预支付信息
var trade = {
body: goodsNameDesc.substr(0, 127), //最长127字节
out_trade_no: outTradeNo, //
total_fee: totalFee, //以分为单位,货币的最小金额
spbill_create_ip: ctx.request.ip, //ctx.request.ip
notify_url: 'https://rxyk.cn/apis/pay_notify', // 支付成功通知地址
trade_type: 'JSAPI',
openid: openId
};
let params = await (() => {
return new Promise((resolve, reject) => {
wepay2.getBrandWCPayRequestParams(trade, (err, result) => {
console.log(err, result);
if (err) reject(err)
else resolve(result)
});
})
})()
let err = '', res
if (params && params.package && params.paySign) {
// 创建记录
res = await Order.create({
userId,
outTradeNo,
payState,
totalFee,
addressId,
addressDesc,
goodsCartsIds,
goodsNameDesc
})
if (!res) err = 'db create error'
} else {
err = 'error! getBrandWCPayRequestParams() return null!'
console.log(err);
}
ctx.status = 200
ctx.body = {
code: 200,
msg: !err ? 'ok' : '',
data: {
res,
params
}
}
})
// 使用小微商户支付
// post /user/my/order3
// 使用weixin-pay实现的接口,测试通过
router.post('/my/order3', async ctx => {
let { uid: userId, openId } = ctx.user
let { totalFee, addressId, addressDesc, goodsCartsIds, goodsNameDesc } = ctx.request.body
// 为测试方便,所有金额支付数均为1分
totalFee = 1
let payState = 0
// 依照Order模型接收参数
let outTradeNo = `${new Date().getFullYear()}${short().new()}`
// console.log('outTradeNo', outTradeNo);
// 获取订单的预支付信息
var trade = {
body: goodsNameDesc.substr(0, 127), //最长127字节
out_trade_no: outTradeNo, //
total_fee: totalFee, //以分为单位,货币的最小金额
spbill_create_ip: ctx.request.ip, //ctx.request.ip
notify_url: 'https://rxyk.cn/apis/pay_notify2', // 支付成功通知地址
trade_type: 'JSAPI',
openid: openId
};
let params = wepay3.getOrderParams(trade)
console.log('params', params);
let err = '', res
// 在这里还没有产生package,因为prepay_id还没有产生
if (params && params.sign) {
// 创建记录
res = await Order.create({
userId,
outTradeNo,
payState,
totalFee,
addressId,
addressDesc,
goodsCartsIds,
goodsNameDesc
})
if (!res) err = 'db create error'
} else {
err = 'error! return null!'
console.log(err);
}
ctx.status = 200
ctx.body = {
code: 200,
msg: !err ? 'ok' : '',
data: {
res,
params
}
}
})
}
module.exports = {
init
}
order.js
const DataTypes = require('sequelize')
const db = require("./mysql-db")
module.exports = db.define("order",{
id:{
type:DataTypes.INTEGER(11),
allowNull:false,
primaryKey:true,
autoIncrement:true
},
userId:{
type:DataTypes.INTEGER(20),
allowNull:false
},
outTradeNo:{// 微信商号单号
type:DataTypes.STRING(50),
allowNull:false
},
transactionId:{// 微信交易单号
type:DataTypes.STRING(50),
allowNull:true //允许为空
},
payState:{// 支付订单的状态,0=未支付,1=已支付,2=取消或其它
type:DataTypes.INTEGER,
defaultValue:0,
allowNull: false
},
totalFee:{//总价,单位分
type:DataTypes.INTEGER(11),
allowNull:false
},
addressId:{//收货地址id
type:DataTypes.INTEGER(20),
allowNull:false
},
addressDesc:{//收货地址总描述
type:DataTypes.TEXT('tiny'),//最大长度255个字节
allowNull:false
},
goodsCartsIds:{//购买车商品ids
type:DataTypes.JSON,
allowNull:false
},
goodsNameDesc:{//商品名称描述
type:DataTypes.TEXT('tiny'),//最大长度255个字节
allowNull:false
}
},{
indexes: [{
unique: true,// 唯一索引
fields: ['out_trade_no']
}],
underscored: true,
freezeTableName:true,
timestamps:true
});
wepay.js
const {wepay:WechatPay} = require('koa3-wechat');
// const short = require('short-uuid');
const fs = require('fs');
// 普通商户的参数
let config = {
appId: '自己的APPID', // 小程序APPID
mchId: '自己的商户id',//商户id
notifyUrl: '自己的通知地址', // 支付成功通知地址
partnerKey: '自己的api key', // 微信商户平台的api key,在pay.weixin.qq.com设置
pfx: fs.readFileSync(__dirname + '/apiclient_cert.p12'),
// passphrase: '预留信息',// 添加了无法退款
}
// 初始化
let wepay = new WechatPay(config);
module.exports = wepay
wepay3.js
const md5Util = require('./md5.js')
const request = require('request')
const xml2js = require('xml2js')
// 在下面设置商户号
const mchid = ''
// 在下面设置密钥
const secret = ''
const buildXML = function(json){
var builder = new xml2js.Builder();
return builder.buildObject(json);
};
const getRandomNumber = (minNum = 1000000000, maxNum = 99999999999999) => parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
const getSign = obj => {
let keys = Object.keys(obj)
keys.sort()
let params = []
keys.forEach(e => {
if (obj[e] != '') {
params.push(e + '=' + obj[e])
}
})
params.push('key=' + secret)
let paramStr = params.join('&')
let signResult = md5Util.md5(paramStr).toUpperCase()
return signResult
}
const getOrderParams = (trade) => {
// 支付参数
let nonce_str = getRandomNumber() // 随机数
let goods_detail = ''
let attach = ''
let paramsObject = {
mchid,
total_fee: trade.total_fee,
out_trade_no: trade.out_trade_no,
body:trade.body,
goods_detail,
attach,
notify_url:trade.notify_url,
nonce_str
}
let sign = getSign(paramsObject)
paramsObject.sign = sign
return paramsObject
}
//
const refund = async (order_id)=>{
let order = {
mchid,
order_id,
nonce_str:getRandomNumber(),
refund_desc:'no',
notify_url:'https://rxyk.cn/apis/pay_notify3',
}
order.sign = getSign(order);
// 以json方式提交
return new Promise((resolve, reject) => {
request({
url: "https://admin.xunhuweb.com/pay/refund",
method: "POST",
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(order),
}, function(err, res, body){
console.log(err, res, body)
if (err) reject(err)
else resolve(body)
});
})
}
module.exports = {
getOrderParams,
refund
}