node egg 微信支付服務

這裏和你們分享一下用egg 作微信支付的服務端web

建立egg應用

1、初始化egg應用

npm init egg --type=simple
npm i
複製代碼

2、配置微信支付用到的插件

plugin.js 必備插件(egg-wechat-pay能夠選擇別的插件代替)npm

module.exports = {
 cors: {
   enable: true,
   package: 'egg-cors',
 },
 wechatPay: {
   enable: true,
   package: 'egg-wechat-pay'
 },
};
複製代碼

3、配置中間件

由於微信支付返回的數據都是xml格式數據 因此咱們的解析xml數據 否則接收不到微信支付回調的請求數據小程序

xmlparse.js (可使用koa的其它中間件也行)api

module.exports = require('co-wechat-body');
複製代碼

4、配置config.default.js

//部分主要配置
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',
        }
    }
複製代碼

5、工具函數util.js

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
  }
}


複製代碼

6、編寫Service

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

相關文章
相關標籤/搜索