当前位置: 首页 > news >正文

微信小程序云开发订单微信支付与小票和标签打印的完整高效流程

一个字“全”!!!

  • 前言
  • 一、流程设定
    • 1、如何开通云支付流程
    • 2、以订单下单为例的支付流程
      • 2.1 业务场景介绍
      • 2.2 业务场景流程图
  • 二、代码与代码文件组成
    • 1、页面JS
    • 2、云函数payPre
    • 3、支付回调函数pay_cb
      • 3.1 准备条件
      • 3.2 必要认知
      • 3.3 pay_cb 完整函数如下
    • 4、标签打印函数
    • 5、小票打印函数
  • 三、回调函数常见问题
    • 1、提高云开发数据库订单更新与查询效率
    • 2、避免阻塞与防止回调函数一直回调
  • 五、参考文档

前言

本片文章全文12000字,细致讲述从申请账户到小程序注册到打印机的选型最后到实现微信小程序云开发订单支付更新与小票,标签打印完整的高效流程。

一、流程设定

1、如何开通云支付流程

无论是云支付还是其他平台语言的原生支付接入都有共同一点:非个人,是组织,企
业,个体商家[1],说白了就是有合理合法经营的营业执照,具体可参考[1]。

1.1 开通商户,获取商户号,商户号获取指引文档[2]

1.2 注册微信小程序,微信小程序注册要是“组织,企业,个体商家”,并且主体要与注册
商户号的主体保持一致。注册完成后等待认证完成即可进入微信公众平台[3]

微信公众平台

1.3 进入微信小程序开发者平台后填写相关的资料并进行类目确定。再次有一次非常需要注意的事项。

注意:根据交易类小程序运营规范,对于小程序内提供珠宝玉石、3C 数码、盲盒、服饰内衣、海淘、美妆、酒类、家用电器、玩具、箱包皮具、鞋靴、运动户外等商品在线销售及配送服务时,不可使用云调用支付能力。

1.4 对接商户号,进行JASP支付相关确认,接入准备文档[4]

注意:微信小程序云开发支付是一种免鉴权,免密钥配置的安全的支付方式,所以不
需要配置相关的密钥,直接进行到下一步1.5

云函数支付的官网流程图

1.5 云开发控制台确认

此处请参考微信开发者文档[5],文档内容详细。

2、以订单下单为例的支付流程

2.1 业务场景介绍

客户A在我们的点餐平台上点了一杯咖啡,所以客户要在选好后付款,在付款完成后商家的小票打印机和标签打印机打印出本订单的相关信息。如下:图2.1为小票打印机,图2.2为标签打印机。

小票打印机
图2.1 小票打印机
在这里插入图片描述
图2.2 标签打印机

2.2 业务场景流程图

在这里插入图片描述

二、代码与代码文件组成

1、页面JS

作用:发起支付请求与回调后续相关逻辑操作,例如:返回上一级,或跳转到相关页面。

wx.cloud.database().collection('orderUser').add({data:{subTime:this.getDateandTime().time,/* 下单时间 */subDate:this.getDateandTime().date,/* 下单日期 */orderCode:hexiaoCode,//核销码orderRows:[this.data.rows],orderGroups:tempInfo,//点单详情status:'制作中', // 出餐情况style:"点餐", //用餐方式pre_title:'**** 微信支付点单',pay_status:'wait', //交易状态orderNumber:orderTemp, //交意单号order_price:this.data.priceAll, //价格userName:wx.getStorageSync('userInfo').userName,phone:wx.getStorageSync('userInfo').phone,orderRemarks:this.data.orderRemarks},success:(event)=>{//支付接口/* 价格处理 */var price = parseFloat(this.data.priceAll)// 订单处理wx.cloud.callFunction({name:'payPre',data:{pro_name:'***店自取订单',pro_codeNum:orderTemp,pro_price:price*100,},success:(res_3)=>{wx.hideLoading()console.log("支付接口",res_3);// 调起支付wx.requestPayment({timeStamp: res_3.result.payment.timeStamp,nonceStr: res_3.result.payment.nonceStr,package: res_3.result.payment.package,signType: 'MD5',//加密方式paySign: res_3.result.payment.paySign,success (res_4) {//这里写付款完成后的逻辑函数},fail (res_4) {console.log('发起支付窗口失败');}})},fail(res_3){console.log('调用payPre失败:',res_3);}})},fail:(res)=>{console.log("创建订单出错误",res);}})

2、云函数payPre

作用调用云函数支付接口与返回支付的信息

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({ env: '' }) // 使用当前云环境
// 云函数入口函数
exports.main = async (event, context) => {const res = await cloud.cloudPay.unifiedOrder({"body" :event.pro_name, // 订单名称"outTradeNo" :event.pro_codeNum, // 订单号"spbillCreateIp" : "127.0.0.1","subMchId" : "*****", //商户号ID"totalFee" : event.pro_price, // 价格"envId": "",//云环境ID"functionName": "pay_cb" //回调函数必须有})return res
}

3、支付回调函数pay_cb

作用:支付完成后的回调函数,这个函数很重要,所有的支付完成的云操作都在这里边进行。

3.1 准备条件

由于用到了第三方库:flatted,所有要下载好Node.js并且使用NPM命令进行远程下载

return {errcode: 0, //必须要写,否则微信服务器没有响应则会一直调用errmsg: "订单未找到",
};

3.2 必要认知

pay_cb作为回调函数,则必须要有一个固定返回形式,参考文档如[6]

在这里插入图片描述

npm install flatted

3.3 pay_cb 完整函数如下

const cloud = require('wx-server-sdk');
const { stringify, parse } = require('flatted');// 此处的作用是:设置此函数的超时,主要是防止大量数据的处理时间造成的报错cloud.init({ env: '', // 云环境环境IDtimeout: 10000 // 设置超时时间为10秒
});const db = cloud.database();// 更新订单状态并查询订单信息函数
const updateOrderStatusAndFetch = async (outTradeNo) => {try {// 更新订单状态await db.collection("orderUser").where({orderNumber: outTradeNo}).update({data: {pay_status: "订单完成"}});// 查询订单信息const res = await db.collection("orderUser").where({orderNumber: outTradeNo}).get();return res.data[0];} catch (error) {console.error("订单更新或查询出错", error);throw error;}
};// 标签打印函数
const printLabel = async (allOrder) => {try {const result = await cloud.callFunction({name: 'printBiaoQian',data: {allOrder}});return { name: "标签打印", value: result.result };} catch (error) {console.error("标签打印出错", error);throw error;}
};// 小票打印函数
const printTicket = async (printLabelObj) => {try {const result = await cloud.callFunction({name: 'printer',data: parse(stringify({payStyle: printLabelObj.payStyle,time: printLabelObj.time,ordercode: printLabelObj.ordercode,takecode: printLabelObj.takecode,info: printLabelObj.info,price: printLabelObj.price,phone: printLabelObj.phone,name: printLabelObj.name,remarks: printLabelObj.remarks}))});return { name: "小票打印", value: result.result };} catch (error) {console.error("小票打印出错", error);throw error;}
};// 云函数入口函数
exports.main = async (event, context) => {const { outTradeNo, resultCode } = event;let resultAll = [];if (!outTradeNo || outTradeNo.length === 0) {return {errcode: 0, //必须要写,否则微信服务器没有响应则会一直调用errmsg: "订单未找到",};}try {console.log("开始处理订单支付");// 立即响应微信服务端const response = {errcode: 0,errmsg: event.returnCode,};// 异步处理订单更新和打印逻辑updateOrderStatusAndFetch(outTradeNo).then(async (order) => {if (!order) {console.error("订单未找到");return;}console.log("订单信息查询完成");// 准备标签打印数据console.log("准备标签打印数据中...");const allOrderGroups = order.orderGroups;const tempNowTemp = allOrderGroups.map(element => {const orderShuXingStr = element.orderShuXing.map(el => `#${el.value}`).join('');return {sumNum: element.orderNum,title: element.goodGroups.shopTitle,tuple: `(${orderShuXingStr})`,order: order.orderCode,time: order.subTime,price: element.priceAll,style: "小程序点单"};});// 准备小票打印数据console.log("准备小票打印数据中...");const tupleInfoArray = allOrderGroups.map(element => {const orderShuXingStr = element.orderShuXing.map(el => `#${el.value}`).join('');return {tuple: orderShuXingStr,title: element.goodGroups.shopTitle,num: element.orderNum};});const printLabelObj = {payStyle: "小程序微信支付",time: order.subTime,ordercode: order.orderNumber,takecode: order.orderCode,info: tupleInfoArray,price: order.order_price,phone: order.phone,name: order.userName,remarks: order.orderRemarks};// 标签打印console.log("标签打印中...");const printBiaoQianResult = await printLabel(tempNowTemp);resultAll.push(printBiaoQianResult);console.log("标签打印完成");// 小票打印console.log("小票打印中...");const printerResult = await printTicket(printLabelObj);resultAll.push(printerResult);console.log("小票打印完成");// 打印完成后的处理console.log("支付处理完成");}).catch((error) => {console.error("异步处理订单出错", error);});// 立即返回响应return response;} catch (error) {console.error("支付处理出错", error);return {errcode: 0,errmsg: "支付处理出错",};}
};

4、标签打印函数

在本文章当中,我所使用的标签打印机是“芯烨云”的打印机,SDK比较明确,主流打印机,使用后端语言比较多,具体请参考官网文档[7]。

// 引入云函数 SDK 和 axios
const cloud = require('wx-server-sdk');
const axios = require('axios'); //提前用npm下载此库// 初始化云环境
cloud.init({env: '' //云开发云函数环境ID
});// 生成签名的函数
function generateSignature(user, userKey, timestamp) {const crypto = require('crypto');const str = user + userKey + timestamp;return crypto.createHash('sha1').update(str).digest('hex');
}// 打印标签的云函数
exports.main = async (event, context) => {// 获取传入的订单数组const allOrder = event.allOrder;// 设置相关参数const user = ''; //开放用户账户名const userKey = ''; // 用户密钥const timestamp = Math.floor(Date.now() / 1000);const sign = generateSignature(user, userKey, timestamp);// 添加打印机const addPrinterResponse = await axios.post('https://open.xpyun.net/api/openapi/xprinter/addPrinters', {user: user,timestamp: timestamp,sign: sign,items: [{sn: '打印机sn号', name: '店铺名称'}]}, {headers: {'Content-Type': 'application/json;charset=UTF-8'}});if (addPrinterResponse.data.code !== 0) {// 添加打印机失败return addPrinterResponse.data;}// 遍历订单数组,生成打印内容并发送打印请求for (const order of allOrder) {for (let i = 1; i <= order.sumNum; i++) {// 初始化单个订单的打印内容let printContent = '';printContent += `<TEXT x="10" y="10" font="9" w="1" h="1" r="0">${order.style} #${i}/${order.sumNum}</TEXT>\n`;printContent += `<TEXT x="10" y="40" font="9" w="1" h="1" r="0">取餐号:${order.order}</TEXT>\n`;printContent += `<TEXT x="10" y="70" font="9" w="1" h="1" r="0">${order.title}</TEXT>\n`;printContent += `<TEXT x="10" y="100" font="9" w="1" h="1" r="0">${order.tuple}</TEXT>\n`;printContent += `<TEXT x="10" y="130" font="9" w="1" h="1" r="0">${order.price}元</TEXT>\n`;printContent += `<TEXT x="10" y="160" font="9" w="1" h="1" r="0">${order.time}</TEXT>\n`;printContent += `<TEXT x="10" y="190" font="9" w="1" h="1" r="0">NFIVE COFFEE</TEXT>\n`;// 构建打印请求体const printRequestBody = {sn: '',content: printContent,user: user,timestamp: timestamp,sign: generateSignature(user, userKey, timestamp), // 重新生成签名debug: '0'};// 发送打印请求const printResponse = await axios.post('https://open.xpyun.net/api/openapi/xprinter/printLabel', printRequestBody, {headers: {'Content-Type': 'application/json;charset=UTF-8'}});// 检查打印结果if (printResponse.data.code !== 0) {// 打印失败return printResponse.data;}}}// 所有订单打印成功return { code: 0, msg: 'All orders printed successfully' };
};

5、小票打印函数

在本文章当中,我所使用的小票打印机是“优声云”的打印机,SDK比较明确,使用后端语言比较多,具体请参考官网SDK文档链接[8]。

// 云函数入口文件
const cloud = require('wx-server-sdk')
const crypto = require('crypto');  //提前用npm下载此库
//axios版本
const axios = require('axios')  //提前用npm下载此库
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
// 云函数入口函数
exports.main = async (event, context) => {//event为客户端上传的参数console.log('event : ', event)//通用参数var appid = ''; //应用ID,申请的ID,值为number类型var appsecret = ''; //申请的appsecretvar sign = ''; //签名,不需要填写,会自动生成。var timestamp = parseInt((new Date().getTime()) / 1000); //当前时间戳,不需要填写var deviceid = ''; //设备号var devicesecret = ''; //设备密钥// 构建打印内容var printdata = '<B1><C>小程序订单</C></B1>\n';printdata += '<B1><C></C></B1>\n';printdata += '--------------------------------\n';printdata += '<B1><C>' + event.payStyle + '</C></B1>\n';printdata += '下单时间:' + event.time + '\n';printdata += '订单编号:' + event.ordercode + '\n';printdata += '取餐码:' + event.takecode + '\n';printdata += '--------------------------------\n';event.info.forEach(item => {printdata += item.title + ' x ' + item.num + '\n';printdata += item.tuple + '\n';});printdata += '--------------------------------\n';printdata += '实付金额:' + event.price + '\n';printdata += '下单手机号:' + event.phone + '\n';printdata += '客户姓名:' + event.name + '\n';printdata += '备注:' + event.remarks + '\n';//打印var parameter = {appid: appid,timestamp: timestamp,deviceid: deviceid,devicesecret: devicesecret,printdata: printdata,//请在此添加您需要的参数,格式为上面定义的变量名为key值和value值}//键值排序function objKeySort(obj) { //排序的函数var newkey = Object.keys(obj).sort();//先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组var newObj = {}; //创建一个新的对象,用于存放排好序的键值对for (var i = 0; i < newkey.length; i++) { //遍历newkey数组newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对}return newObj; //返回排好序的新对象}var newObj = objKeySort(parameter); //函数执行var valueJian = '';for (var x in newObj) {if (!newObj[x]) {break;}valueJian += x + newObj[x]}valueJian += appsecret;//console.log(valueJian);sign = crypto.createHash('md5').update(valueJian).digest("hex")//需要的参数对象列表parameter.sign = sign;try {const response = await axios.post('https://open-api.ushengyun.com/printer/print', parameter);// 处理循环引用const data = JSON.parse(JSON.stringify(response, (key, value) => {if (typeof value === 'object' && value !== null) {const seen = new WeakSet();if (seen.has(value)) {return;}seen.add(value);}return value;}));console.log(data);return data;} catch (error) {console.error('Error:', error);return { errorCode: 1, errorMessage: error.message };}
}

三、回调函数常见问题

在接下来的回调函数我们统一使用pay_cb表示

1、提高云开发数据库订单更新与查询效率

面临大量的数据并发,会造函数执行超时,对于这个云数据库操作我们有以下方法

1.1 在云开发数据库集合当中增加索引,对于经常使用的查询键,可以增加到索引当中去。如图
在这里插入图片描述 1.2 减少数据库调用

将查询和更新操作合并为一个操作,减少数据库调用的次数。具体请仔细查看pay_cb

回调函数中的订单更新与查询操作语句写的方法。

1.3 提高云函数超时时间

在云函数的配置中增加超时时间,可以在调用 cloud.init 时设置 timeout 参数。

cloud.init({ env: '',timeout: 10000 // 设置超时时间为10秒
});

2、避免阻塞与防止回调函数一直回调

由于在付款完成后,回调函数执行当中很多时间浪费在云数据库操作查询更新上,更新的时候使用了await,阻塞了应答微信服务端的应答,应答晚了,微信服务端认为你没有应答,所以就造成微信服务器一直在回调
所以我们可以按照pay_cb完整代码当中那样的结构去写,通过在接收支付回调时立即返回响应,然后异步更新数据库,这样做可以避免阻塞微信服务端的响应时间。

五、参考文档

1. 云调用对接微信支付
2. 商户号获取指引文档
3. 微信公众平台
4. 接入准备文档
5. 微信开发者文档云支付确认
6. 芯烨云标签打印机参考官网文档
7. 优声云小票打印机参考官网文档

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【C# 】Pipe管道通信使用
  • Java实现数据库图片上传(包含从数据库拿图片传递前端渲染)-图文详解
  • SSRF-labs-master靶场
  • FFmpeg研究
  • ai智能写作软件哪个好?高效写作少不了这5个
  • 大数据——Hive原理
  • mysql 数据库空间统计sql
  • 一条命令安装mysql,php
  • C++——从前序与中序遍历序列构造二叉树leetcode105
  • 网络安全管理制度
  • java算法day27
  • Android13 控制设置界面 双栏显示或单栏显示
  • go语言day18 reflect反射
  • 数仓建模:DWS层该如何建设?如何设计通用数据模型?
  • 分布式相关理论详解
  • 深入了解以太坊
  • 分享的文章《人生如棋》
  • 【面试系列】之二:关于js原型
  • Android优雅地处理按钮重复点击
  • Apache的80端口被占用以及访问时报错403
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • PAT A1092
  • Promise面试题2实现异步串行执行
  • Python 反序列化安全问题(二)
  • React16时代,该用什么姿势写 React ?
  • Swoft 源码剖析 - 代码自动更新机制
  • vue的全局变量和全局拦截请求器
  • 来,膜拜下android roadmap,强大的执行力
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 你真的知道 == 和 equals 的区别吗?
  • 批量截取pdf文件
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 如何合理的规划jvm性能调优
  • 小而合理的前端理论:rscss和rsjs
  • 一个完整Java Web项目背后的密码
  • 如何正确理解,内页权重高于首页?
  • ​flutter 代码混淆
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # Redis 入门到精通(九)-- 主从复制(1)
  • #C++ 智能指针 std::unique_ptr 、std::shared_ptr 和 std::weak_ptr
  • #DBA杂记1
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (三)elasticsearch 源码之启动流程分析
  • (十一)c52学习之旅-动态数码管
  • (四)Controller接口控制器详解(三)
  • (四)进入MySQL 【事务】
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ******之网络***——物理***
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .apk文件,IIS不支持下载解决