美文网首页
微信小程序支付/微信小程序+node服务 支付爬坑 v2

微信小程序支付/微信小程序+node服务 支付爬坑 v2

作者: 莫伊剑客 | 来源:发表于2021-03-16 17:26 被阅读0次

    一、前端

    小程序登录及支付请求和唤起支付界面

    // app.js
    const {request} = require('./assets/js/utils')
    // app.js
    App({
      onLaunch() {
        // 展示本地存储能力
        const logs = wx.getStorageSync('logs') || []
        logs.unshift(Date.now())
        wx.setStorageSync('logs', logs)
    
        // 登录
        wx.login({
          success: res => {
            console.log(this)
            // 发送 res.code 到后台换取 openId, sessionKey, unionId
            if (res.code) {
              request('/wx/login', {code: res.code}, 'POST').then(result => {
                console.log(result)
                this.globalData = {...this.globalData, ...result.data.data}
              })
            }
          }
        })
      },
      globalData: {
        userInfo: null
      }
    })
    
    

    wxml

    <!--pages/payment/index.wxml-->
    <view class="container">
      <block wx:if="{{!hasUserInfo}}">
        <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
        <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
        <view wx:else> 请使用1.4.4及以上版本基础库 </view>
      </block>
      <button bindtap="payment">点击进行支付</button>
    </view>
    
    

    payment/index.js

    // pages/payment/index.js
    // 获取应用实例
    const app = getApp()
    const {request} = require('../../assets/js/utils')
    Page({
    
      /**
       * 页面的初始数据
       */
      data: {
        hasUserInfo: false,
        canIUse: wx.canIUse('button.open-type.getUserInfo'),
        canIUseGetUserProfile: false,
        userInfo: ''
      },
      // 第一种获取用户信息方法
      getUserProfile(e) {
        // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
        wx.getUserProfile({
          desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
          success: (res) => {
            console.log(res)
            // 存储用户信息
            wx.setStorageSync('userInfo', JSON.stringify(res.userInfo));
            this.setData({
              userInfo: res.userInfo,
              hasUserInfo: true
            })
          }
        })
      },
      // 第二种获取用户信息方法
      getUserInfo(e) {
        // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
        console.log(e)
        // 存储用户信息
        wx.setStorageSync('userInfo', JSON.stringify(e.detail.userInfo));
        this.setData({
          userInfo: e.detail.userInfo,
          hasUserInfo: true
        })
      },
      // 支付事件
      payment() {
        const params = {
          openid: app.globalData.openid,
          total: 0.01,
          orderCode: new Date().toLocaleString().replace(/[^\d]/g, '')
        }
        request('/wx/pay/unifiedorder', params, "POST").then(res => {
          const {nonceStr, timeStamp, signType, paySign} = res.data.data
          const pack = res.data.data.package
          wx.requestPayment({
            nonceStr,
            timeStamp,
            package: pack,
            signType,
            paySign,
            success(res) {
              console.log(res)
            },
            fail(res) {
              console.log(res)
            },
            complete(res){
              console.log(res)
            }
          })
        })
      },
      pageInit() {
        if (wx.getUserProfile) {
          this.setData({
            canIUseGetUserProfile: true
          })
        }
        // 如果缓存不存在用户信息时暂时获取用户信息按钮
        if (this.data.userInfo || wx.getStorageSync('userInfo')) {
          const userInfo = this.data.userInfo || JSON.parse(wx.getStorageSync('userInfo'))
          this.setData({
            hasUserInfo: true,
            userInfo
          })
        }
      }
    

    util.js request封装

    exports.request = function (requestMapping, data, requestWay, contentType) {
      wx.showLoading({
        title: '请稍后',
      })
      return new Promise(function (resolve, reject) {
        console.log('请求中。。。。。')
        wx.request({
          url: baseUrl + requestMapping,
          data: data,
          header: {
            'content-type': contentType || "application/x-www-form-urlencoded" // 默认值
          },
          timeout: 3000,
          method: requestWay,
          success(res) {
            //console.log(res)
            if (res.data.success == false || res.data.statusCode == 404) {
              reject(res)
            } else {
              resolve(res)
            }
          },
          fail: (e) => {
            wx.showToast({
              title: '连接失败',
              icon: 'none'
            })
          },
          complete: () => {
            wx.hideLoading()
          }
        })
      })
    }
    

    二、node服务端

    我这里使用的是eggjs,使用koa,express同理
    router.js

    'use strict';
    /**
     * @param {Egg.Application} app - egg application
     */
    module.exports = app => {
      const { router, controller } = app;
      router.post('/wx/login', controller.wx.login);
      router.post('/wx/pay/unifiedorder', controller.wx.unifiedorder);
    };
    

    Controller

    'use strict';
    const { Controller } = require('egg');
    
    class WxController extends Controller {
      // 登录
      async login() {
        const { ctx } = this;
        ctx.body = await ctx.service.wx.login()
      }
      // 统一下单
      async unifiedorder(){
        const { ctx } = this;
        ctx.body = await ctx.service.wx.unifiedorder()
      }
    }
    
    module.exports = WxController;
    
    

    Service

    'use strict';
    const { Service } = require('egg');
    const appid = 'wx5591*****e47'; // 小程序id
    const secret = '670c7*****1a7de971c3'; // 小程序secret
    const mch_id = '1607****01'; // 商户id
    const mch_key = '52052*****2522ghi52352'; // 商户key
    
    class Wx extends Service {
    // 登录获取openid,session_key并返回给前端
      async login() {
        const { ctx } = this;
        const { code } = ctx.request.body;
        const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
        const res = await ctx.curl(url, {
          method: 'POST',
          dataType: 'json',
        });
        console.log(res, res.data);
        return ctx.helper.success({ ctx, res: res.data });
      }
    
      async unifiedorder() {
        const { ctx } = this;
        // 自己封装的微信支付工具
        const wxpayUitls = ctx.helper.wxpayUitls;
        const { openid, total, orderCode } = ctx.request.body;
        const url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        // params 参数参考地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1
        // 所有能使用到到的参数都罗列了,根据实际需求自己选择  
      const params = {
          appid,
          mch_id,
          // 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
          // device_info: '',
          // 随机字符串,长度要求在32位以内。推荐随机数生成算法
          nonce_str: wxpayUitls.createNonceStr(),
          // 通过签名算法计算得出的签名值,详见签名生成算法
          sign: '',
          // 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
          // sign_type: 'MD5',
          // 商品简单描述,该字段请按照规范传递,具体请见参数规定
          body: '支付测试',
          // 商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传,详见“单品优惠参数说明”
          // detail: '',
          // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
          // attach: '',
          // 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号
          out_trade_no: orderCode,
          // 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
          fee_type: 'CNY',
          // 订单总金额,单位为分,详见支付金额
          total_fee: wxpayUitls.getmoney(total),
          // 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
          spbill_create_ip: '192.168.43.187' || ctx.request.ip,
          // 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
          // time_start: '',
          // 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。
          // 订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,
          // 所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则
          // 建议:最短失效时间间隔大于1分钟
          // time_expire: '',
          // 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
          // goods_tag: '',
          // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
          notify_url: 'http://cxbly.top',
          // 小程序取值如下:JSAPI,详细说明见参数规定
          trade_type: 'JSAPI',
          // trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
          // product_id: '',
          // 上传此参数no_credit--可限制用户不能使用信用卡支付
          // limit_pay: 'no_credit',
          // trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。
          openid,
          // Y,传入Y时,支付成功消息和支付详情页将出现开票入口。
          // 需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效
          // receipt: '',
          // Y-是,需要分账
          // N-否,不分账
          // 字母要求大写,不传默认不分账
          // profit_sharing: '',
          // 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,
          // 商户也可以按需求自己上报相关信息。该字段为JSON对象数据,
          // 对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }} ,
          // 字段详细说明请点击行前的+展开
          // scene_info: ''
        };
        //动态生成统一下单需要的签名MD5字符串
        params.sign = wxpayUitls.createSign(params, mch_key);
        const res = await ctx.curl(url, {
          method: 'POST',
          dataType: 'text/xml', // 注意接口数据类型
          data: wxpayUitls.createXML(params) //动态生成的xml
        });
        // console.log('统一下单结果:', res, 'data:', res.data.toString());
        // 解析统一下单后返回的xml
        const {xml} = await wxpayUitls.parserXML(res.data.toString())
        const r={
          appId:appid,
          timeStamp:Date.now().toString(), //注意类型String
          nonceStr:xml.nonce_str,
          package:'prepay_id='+xml.prepay_id,
          signType:'MD5'
        }
        //  paySign:wxpayUitls.createSign(r,mch_key) 动态生成wx.requestPayment使用的签名paySign MD5字符串
        return ctx.helper.success({ctx,res:{...r,paySign:wxpayUitls.createSign(r,mch_key)}})
      }
    }
    
    module.exports = Wx;
    

    注意事项

    • 统一下单 签名规则
      将统一下单所需的所有的参数进行签名计算(空值,sgin除外)
    • wx.requestPayment 签名规则
      参与签名计算的参数有:appId,timeStamp,nonceStr,package,signType,注意 timeStamp转字符串。

    wxpayUitls工具

    const crypto = require('crypto');
    const Xml2 = require('xml2js');
    const MD5 = require('md5');
    
    exports.wxpayUitls = {
      //把金额转为分
      getmoney: function(money) {
        return parseFloat(money) * 100;
      },
      // 随机字符串产生函数
      createNonceStr: function() {
        return Math.random()
          .toString(36)
          .substr(2, 15)
          .toUpperCase();
      },
      // 时间戳产生函数
      createTimeStamp: function() {
        return parseInt(new Date().getTime() / 1000) + '';
      },
      // 动态生成签名方法
      createSign: function(params, mchkey) {
        let str = raw(params);
        str += `&key=${mchkey}`;
        console.log('str=====', str);
        // 第一种方法
        // return MD5(str).toUpperCase()
        // 第二种方法
        return crypto.createHash('md5')
          .update(str, 'utf8')
          .digest('hex')
          .toUpperCase();
      },
      // 根据对象生成xml
      createXML: function(params) {
        // let xml = `<xml>`;
        // Object.keys(params)
        //   .map(key => {
        //     xml += `<${key}>${params[ key ]}</${key}>`;
        //   });
        // xml += `</xml>`;
        // return xml;
        const builder = new Xml2.Builder();
        return builder.buildObject(params);
      },
      // 解析xml
      parserXML: function(xml) {
        const Parser = new Xml2.Parser({ explicitArray: false, ignoreAttrs: false });
        return new Promise((resolve, reject) => {
          Parser.parseString(xml, function(err, result) {
            if (err) reject(err);
            resolve(result);
          });
        });
      }
    };
    

    订单查询,关闭,申退,查询退款等后续流程 等爬完之后再更新!

    相关文章

      网友评论

          本文标题:微信小程序支付/微信小程序+node服务 支付爬坑 v2

          本文链接:https://www.haomeiwen.com/subject/zgmdcltx.html