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

一、前端

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

复制代码
// 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);
      });
    });
  }
};

订单查询,关闭,申退,查询退款等后续流程 等爬完之后再更新!
最后编辑于:2024-12-10 21:52:36
© 著作权归作者所有,转载或内容合作请联系作者

喜欢的朋友记得点赞、收藏、关注哦!!!

相关推荐
喝醉的小喵2 分钟前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
linkingvision8 分钟前
H5S 视频监控AWS S3 对象存储
linux·运维·aws·视频监控s3对象存储
doupoa20 分钟前
Fabric 服务端插件开发简述与聊天事件监听转发
运维·python·fabric
BillKu20 分钟前
服务器多JAR程序运行与管理指南
运维·服务器·jar
付出不多42 分钟前
Linux——mysql主从复制与读写分离
数据库·mysql
QQ2740287561 小时前
BlockMesh Ai项目 监控节点部署教程
运维·服务器·web3
源码云商1 小时前
【带文档】网上点餐系统 springboot + vue 全栈项目实战(源码+数据库+万字说明文档)
数据库·vue.js·spring boot
ZHOU_WUYI1 小时前
使用 Docker 部署 React + Nginx 应用教程
nginx·react.js·docker
C4程序员1 小时前
Java百度身份证识别接口实现【配置即用】
java·开发语言
源远流长jerry1 小时前
MySQL的缓存策略
数据库·mysql·缓存