這裏和你們分享一下用egg 作微信支付的服務端web
npm init egg --type=simple
npm i
複製代碼
plugin.js 必備插件(egg-wechat-pay能夠選擇別的插件代替)npm
module.exports = {
cors: {
enable: true,
package: 'egg-cors',
},
wechatPay: {
enable: true,
package: 'egg-wechat-pay'
},
};
複製代碼
由於微信支付返回的數據都是xml格式數據 因此咱們的解析xml數據 否則接收不到微信支付回調的請求數據小程序
xmlparse.js (可使用koa的其它中間件也行)api
module.exports = require('co-wechat-body');
複製代碼
//部分主要配置
config.security = {
csrf: {
enable: false,
},
domainWhiteList: ['*'],
}//跨域請求
const fs = require('fs')
const path = require('path')
config.mpWeixin = {
appid: '',微信公衆號或小程序號
AppSecret: '',密鑰
Host: 'https://api.weixin.qq.com/',
MsgToken: '',
EncodingAESKey: ''
}
config.wechatPay = {
client: {
bodyPrefix: '',//
appId: '',//微信公衆號或小程序號
merchantId: '',//商戶號
secret: '',//商戶密鑰
notifyUrl: '',//支付成功回調地址
REFUNDNotifyUrl: '',//退款成功回調地址
pfx: fs.readFileSync(path.join(__dirname, '../app/public/wxpaly/apiclient_cert.p12'))//退款證書地址
},
URLS: {
UNIFIED_ORDER: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
ORDER_QUERY: 'https://api.mch.weixin.qq.com/pay/orderquery',
REFUND: 'https://api.mch.weixin.qq.com/secapi/pay/refund',
REFUND_QUERY: 'https://api.mch.weixin.qq.com/pay/refundquery',
DOWNLOAD_BILL: 'https://api.mch.weixin.qq.com/pay/downloadbill',
SHORT_URL: 'https://api.mch.weixin.qq.com/tools/shorturl',
CLOSE_ORDER: 'https://api.mch.weixin.qq.com/pay/closeorder',
REDPACK_SEND: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack',
REDPACK_QUERY: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo',
TRANSFERS: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers',
TRANSFERS_QUERY: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo',
}
}
複製代碼
const crypto = require('crypto');
const fs = require('fs')
const path = require('path')
function randomString(len) {
// isFinite 判斷是否爲有限數值
if (!Number.isFinite(len)) {
throw new TypeError('Expected a finite number');
}
return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len);
}
function getSign(params, key, type = 'MD5') {//支付簽名
const paramsArr = Object.keys(params);
paramsArr.sort();
const stringArr = []
paramsArr.map(key => {
if (key != 'sign' && params[key])
stringArr.push(key + '=' + params[key]);
})
if (type == "MD5") {
// 最後加上 商戶Key
stringArr.push("key=" + key)
const string = stringArr.join('&');
const MD5 = crypto.createHash('md5');
return MD5.update(string).digest('hex').toUpperCase();
} else {
return crypto.createHmac('sha256', key)
.update(stringArr.join('&'))
.digest('hex').toUpperCase();
}
}
function checkSign(params, key) { //驗證簽名是否正確
const {
sign
} = params;
const Newsign = getSign(params, key)
if (sign === Newsign) {
return true
} else {
return false
}
}
複製代碼
Payment.js跨域
'use strict';
const Service = require('egg').Service;
const { randomString, ransign, getSign, checkSign, } = require('util.js')
const moment = require('moment');
const Decimal = require('decimal.js');
class PaymentService extends Service {
/**
* H5支付或微信掃一掃支付
*
* @param {*} body 費用說明
* @param {*} money 金額
* @returns
* @memberof PaymentService
*/
async pay(PayType, body, money) {
const { ctx, service, app } = this;
let No = Date.now();
let OrderNo = moment().format('YYYYMMDDHHmm') + randomString(3) + No;
let createTime = moment().format('YYYY-MM-DD HH:mm:ss');
let order = { //統一訂單參數
device_info: 'WEB',
body,
out_trade_no: OrderNo,
total_fee: new Decimal(money).mul(new Decimal(100)).toFixed(),
attach: `${UserId}`
};
if (PayType == 'H5') {
order.scene_info = JSON.stringify({
h5_info: 'h5_info',
type: 'Wap',
wap_url: this.config.h5host,
wap_name: '峯會報名付款'
})
}
const unifiedRes = await this.unifiedOrder(order, PayType == 'H5' ? 'MWEB' : 'NATIVE');
return unifiedRes
}
//支付回調
async payaction(params) {
const {
ctx,
service,
app
} = this;
const {
appid, //公衆帳號ID
bank_type, //付款銀行
cash_fee, //現金支付金額
device_info, //設備號
fee_type, //貨幣種類
is_subscribe, //是否關注公衆帳號
mch_id, //商戶號
nonce_str, //隨機字符串
openid, //用戶標識
out_trade_no, //商戶訂單號
result_code, //業務結果
return_code, //返回狀態碼
sign, //簽名
time_end, //支付完成時間
total_fee, //訂單金額
trade_type, //交易類型
transaction_id, //微信支付訂單號
attach
} = params;
if (return_code == "SUCCESS") {
if (checkSign(params, this.config.wechatPay.client.secret)) {//解析回調數據是否正確
//本身處理
} else {
return {
msg: '簽名錯誤',
status: 'error',
}
}
} else {
return {
msg: '支付失敗',
status: 'error',
}
}
}
//退款
async refund(out_trade_no,refund_fee,refund_desc,PayPrice) {
const {
ctx,
service,
app
} = this;
let nonce_str = randomString(32);
let out_refund_no = moment().format('YYYYMMDDHHmmss') + randomString(25);
let data = {
appid: this.config.wechatPay.client.appId,
mch_id: this.config.wechatPay.client.merchantId,
nonce_str,//隨機字符串
out_trade_no,//訂單號
out_refund_no,//退款訂單號
total_fee: new Decimal(PayPrice).mul(new Decimal(100)).toFixed(),//總金額
refund_fee: new Decimal(refund_fee).mul(new Decimal(100)).toFixed(),//退款金額
refund_desc,//退款緣由描述
notify_url: this.config.wechatPay.client.REFUNDNotifyUrl//退款成功回調地址
}
let sign = getSign(data, this.config.wechatPay.client.secret);//簽名
let xml = {
...data,
sign
};
try {
const curlres = await ctx.curl(`${this.config.wechatPay.URLS.REFUND}`, {
method: 'POST',
pfx: this.config.wechatPay.client.pfx,//退款證書
passphrase: this.config.wechatPay.client.merchantId,
// 直接發送原始 xml 數據,不須要 HttpClient 作特殊處理
content: this.app.wechatPay.stringify(xml),
});
let rdata = await this.app.wechatPay.parse(curlres.data);//解析xml數據
return {
res: rdata
}
} catch (error) {
console.log(error, "error");
if (error == "invalid refund_fee") {
return {
msg: '退款金額不正確',
status: 'error',
}
} else {
return {
msg: '退款失敗',
status: 'error',
}
}
}
}
//微信內部瀏覽器或小程序 統一建立支付
async CreatePaymentInfo(money,openid) {
const {
ctx,
service,
app
} = this;
let No = Date.now();
let OrderNo = moment().format('YYYYMMDDHHmmss') + randomString(3) + No;
const order = { //統一訂單參數
device_info: 'WEB',
body: "",費用說明
out_trade_no: OrderNo,//訂單編號
total_fee: new Decimal(money).mul(new Decimal(100)).toFixed(),//支付金額 分爲單位
openid,//用戶openid
};
const res = await this.unifiedOrder(order); //請求生成訂單信息
return {
res
}
}
async unifiedOrder(order, trade_type = 'JSAPI') {
const {
ctx,
service
} = this;
let nonce_str = randomString(32);//隨機字符串
let Data = {
appid: this.config.wechatPay.client.appId,
mch_id: this.config.wechatPay.client.merchantId,//商戶號
nonce_str,
sign_type: 'MD5',//簽名方式
spbill_create_ip: ctx.ip,//用戶ip
trade_type,//支付類型
notify_url: this.config.wechatPay.client.notifyUrl,//支付成功回調地址
time_expire: moment().add(1, 'h').format('YYYYMMDDHHmmss'),
...order
}
let sign = getSign(Data, this.config.wechatPay.client.secret);//簽名
let xml = {
...Data,
sign
};
const cur = await ctx.curl(`${this.config.wechatPay.URLS.UNIFIED_ORDER}`, {
method: 'POST',
// 直接發送原始 xml 數據,不須要 HttpClient 作特殊處理
content: this.app.wechatPay.stringify(xml),
});
const parseData = await this.app.wechatPay.parse(cur.data);
let res = {
appId: Data.appid,
nonceStr: nonce_str,
package: `prepay_id=${parseData.prepay_id}`,
timeStamp: moment().add(1, 'h').unix().toString(),
signType: 'MD5',
}
if (trade_type == 'MWEB') {//手機瀏覽器H5支付
res.mweb_url = parseData.mweb_url
}
if (trade_type == 'NATIVE') {//可用於微信掃一掃支付
res.code_url = parseData.code_url
}
return {
...res,
paySign: getSign(res, this.config.wechatPay.client.secret)
}
}
}
module.exports = PaymentService;
複製代碼
主要代碼基本能夠實現微信支付的服務端瀏覽器
寫的太糙了hhh 不會寫只能貼代碼 有什麼問題 能夠留言bash