一、前端
小程序登录及支付请求和唤起支付界面
// 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, unionIdif (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.dataconst pack = res.data.data.packagewx.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'; // 商户keyclass 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的机器IPspbill_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());// 解析统一下单后返回的xmlconst {xml} = await wxpayUitls.parserXML(res.data.toString())const r={appId:appid,timeStamp:Date.now().toString(), //注意类型StringnonceStr: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();},// 根据对象生成xmlcreateXML: 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);},// 解析xmlparserXML: 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);});});}
};
订单查询,关闭,申退,查询退款等后续流程 等爬完之后再更新!
© 著作权归作者所有,转载或内容合作请联系作者
喜欢的朋友记得点赞、收藏、关注哦!!!