【愚公系列】2022年10月 微信小程序-电商项目-小微商户支付后端功能实现(node版)

2022-10-31 10:39:26 浏览数 (1)

文章目录

  • 前言
  • 一、小微商户支付后端功能实现(node版)
    • 1.相关文档
    • 2.项目配置

前言

微信支付是腾讯集团旗下的第三方支付平台,致力于为用户和企业提供安全、便捷、专业的在线支付服务。以“微信支付,不止支付”为核心理念,为个人用户创造了多种便民服务和应用场景。微信支付为各类企业以及小微商户提供专业的收款能力,运营能力,资金结算解决方案,以及安全保障。用户可以使用微信支付来购物、吃饭、旅游、就医、交水电费等。企业、商品、门店、用户已经通过微信连在了一起,让智慧生活,变成了现实。

小程序实现微信支付主要有两种方式:

  • 小程序内部API,要求商户开通了小程序支付功能
  • 第三方网站

一、小微商户支付后端功能实现(node版)

1.相关文档

接口文档:https://pay.xunhuweb.com/document

2.项目配置

node安装相关依赖

router .js

代码语言:javascript复制
npm install koa3-wechat
npm install short-uuid
npm i raw-body
npm i redis
npm i babel-runtime@6.26.0

在路由模块中引入支付模块

代码语言:javascript复制
const Pay = require("./pay")
const Router = require("@koa/router")
const router = new Router({
  prefix: '/user'
});

Pay.init(router)

module.exports = router

pay.js

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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
}

0 人点赞