小程序下单账号与支付账号不一致不让支付_微信小程序支付流程
微信支付之小程序支付
微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式
说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序必须具备企业性质,必须拥有微信支付商户平台的账号
PS:申请微信支付商户平台需要一个微信小程序或公众号等,建议按照以下流程进行操作
准备工作
1、申请微信小程序账号
申请成功可拿到 AppID(小程序 id)和 AppSecret(小程序密钥)
申请类型为企业性质,否则无法接入微信支付
2、微信小程序认证
通过认证的小程序才能接入微信支付和绑定商户平台
3、申请商户平台账号
需要第一步申请的 AppID
申请成功可拿到 MchID(商户 id)和 MchKey(商户密钥)
4、信小程序关联商户号
微信和商户都认证成功后,在微信后台微信支付菜单中进行关联
5、接入微信支付
在微信后台微信支付菜单中进行接入
小程序支付流程
简要支付流程如下:
- 用户发起支付请求
- 后端调用统一下单接口得到 prepay_id
- 把支付所需参数返回前端
- 前端调用支付接口进行支付操作
- 支付结果通知
- 前端根据不同的支付结果给用户不同的提示
PS:难点在第 2、3、5 步,一定要仔细查看相关接口文档,否则容易出错,接下来我们按照以上 6 个步骤详细讲解在微信小程序中的支付流程
支付前的操作
因为严格意义上来说这不属于支付流程中的步骤,但支付过程中需要用到用户唯一标识openid,所以建议在用户进入小程序时就进行这一步的操作
- 调用wx.login()接口获取 code,并把code传到服务器
- 后端服务器拿到 code 后调用code2Session 接口获取 openid 和 session_key
建议把openid存入数据库,方便随时获取,下面的步骤也会用到 - 后端服务器保好 appid, secret, mch_id, mch_key(这些数据分别在小程序后台和商户平台中获得,我是把它们做成 commonjs 模块并保存在config/wx.js文件中以方便调用)
PS:开发者需要自行维护用户登录状态(用户登录状态的维护本文不做展开,请自行查阅相关资料)
1、小程序端:用户向商户服务器发起支付请求
这步没什么好说的,当用户点击支付按钮时,给我们自己的后端接口发起一个请求,携带必要的参数(如:body,total_fee 等),接口地址需要自行编写,如我的接口地址为/payment/order
// http对象为wx.request()的二次封装import http from "../utils";// 向后端发请请求const res = await http.post("/payment/order", { body: "腾讯QQ-购买会员", // 商品描述 total_fee: 998, // 总金额,单位为分});if (res.status === 200) { try { // 得到接口返回的数据,向微信发起支付 const result = await wx.requestPayment({ ...res.data, }); wx.showToast({ title: "支付成功", }); console.log("支付结果:", result); } catch (err) { wx.showToast({ title: "支付失败", }); }}
PS:可能会有小伙伴产生疑惑,为什么不直接通过 wx.requestPayment() 在小程序端发起请求而要先请求商户自己的服务器呢?原因很简单,安全性问题,wx.requestPayment()需要 2 个重要参数paySign和package,需要 appid,secret,openid,mch_key 等私密数据,这些私密的数据不应该在前端暴露出来,而是放在自己的服务器中更安全,所以需要向自己的服务器发起这个请求拿到这些参数,下一步才能真正发起支付。接下来我们来看看后端是如果操作的
2、商户后端服务器:签名+生成预支付标识
后端代码使用 egg 框架(基于 NodeJS+Koa)实现,文中涉及到 egg 用法和 koa 的用法不再额外说明,请自行查阅相关资料
调用统一下单接口获取 预支付会话标识 prepay_id
注意:该接口需要发送 xml 格式参数,同时返回 xml 格式数据,需自行转换(我使用的是xml-js第三方模块)
- 该接口必填参数:appid,mch_id,nonce_str,sign_type,body,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,sign,其中 sign 为前面所有参数加密后的字符
async order(ctx) { // egg框架写法 const { service, request } = ctx; // 获取前端传入参数 const { userid, total_fee, body } = request.body; // 引入微信配置参数(上面准备工作中保存的config/wx.js文件,包含小程序id,密钥,商户id,商户密钥) const { config } = require("../../config/wx"); // 生成订单号(保证唯一性:我采用时间戳拼6位随机数的方式) const tradeNo = Date.now() + '' + randomCode(100000, 999999); // 统一下单签名参数 const orderParams = { appid: config.appid, // 小程序id mch_id: config.mch_id, // 商户id nonce_str: service.wx.randomStr(), // 自定义生成随机字符方法 sign_type: "MD5", // 加密类型 body, // 商品简单描述,有格式要求 out_trade_no: tradeNo, // 订单号 total_fee, // 单位:分 spbill_create_ip: "121.34.253.98", // 服务器ip notify_url: "https://你的服务器域名/payment/wxnotify", // 支付成功通知地址 trade_type: "JSAPI", // 支付方式(小程序支付选JSAPI) openid: user.openid, // 用户openid,步骤0保存的数据 }; // 签名:对上面所有参数加密(签名算法请查看接口文档,下同) const orderSign = service.wx.sign(orderParams); // json->xml const xmlData = convert.js2xml( { xml: { ...orderParams, sign: orderSign } }, { compact: true } ); // 调用统一下单接口(接口没说明,但必须为post请求) const { data } = await ctx.curl( "https://api.mch.weixin.qq.com/pay/unifiedorder", { method: "post", data: xmlData, } ); // xml->js const result = convert.xml2js(data, { compact: true }); if (result.prepay_id) { // 此处可以把订单信息保存到数据库 // 返回prepay_id后,接着就是把参数返回前端 // =>为了更清晰,我把这里的代码写在下一步 // ... } }
3、给前端返回支付参数+签名
// 支付签名参数 const payParams = { appId: config.appid, // 商户 id timeStamp: Date.now(), // 时间戳 nonceStr: this.randomStr(), // 随机字符 package: "prepay_id=" + result.prepay_id, //预支付会话标识(格式为:prepay_id=统一下单接口返回数据) signType: "MD5", //签名类型(必须与上面的统一下单接口一致) }; // 签名 const paySign = service.wx.sign(payParams); // 把参数+签名返回给前端 ctx.body = formatData({ data: { timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: payParams.signType, paySign, }, });
附上封装好的签名方法sign()和生成随机字符串的方法randomStr(),我写在service/wx.js
"use strict"; const { Service } = require("egg"); const crypto = require("crypto"); // 微信基本配置 const { weapp } = require("../../config/wx"); class wxService extends Service { randomStr(len = 24) { const str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let result = ""; for (let i = 0; i < len; i++) { result += str[Math.floor(Math.random() * str.length)]; } return result; } sign(data, signType = "MD5") { const keys = []; for (const key in data) { if (data[key] !== undefined) { keys.push(key); } } // 字典排序=>key=value const stringA = keys .sort() .map((key) => `${key}=${decodeURIComponent(data[key])}`) .join("&"); // 拼接商户key const stringSignTemp = stringA + "&key=" + weapp.mch_key; console.log("stringSignTemp", stringSignTemp); // 加密 let hash; if (signType === "MD5") { hash = crypto.createHash("md5"); } else { hash = crypto.createHmac("sha256", "laoxie"); } hash.update(stringSignTemp); const paySign = hash.digest("hex").toUpperCase(); return paySign; } } module.exports = wxService;
4、小程序端:向微信服务器发起请求
第 1 步的数据返回后,向微信服务器接口wx.requestPayment()发请求,唤起支付界面,请查看第一步 try...catch 中的代码
5、微信服务器:支付结果通知
在第 2 步向统一下单接口发起请求时附带了一个notify_url,此地址一定要是可外网访问的接口地址(商户自行编写),由微信服务器调用该接口,不管支付成功与否,此接口都会调用,并返回相应数据(查看接口数据),所以商户可以在此接口中编写相关业务逻辑、如支付成功后写入数据库等操作
注意:商户需要在此接口中做接收处理,并向微信服务器返回应答(按接口规范返回特定数据)。如果微信收到商户的应答不是成功或超时,微信会认为通知失败,微信会通过一定的策略定期重新发起通知,通知频率为:15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h,但微信不保证通知最终一定能成功。
"use strict"; const Controller = require("egg").Controller; const getRawBody = require("raw-body"); const contentType = require("content-type"); const { formatData, randomCode, params, formatParams } = require("../utils"); class PaymentController extends Controller { // 微信支付回调地址 async notify(ctx) { const { req } = ctx; // 微信调用该接口时传入的数据为xml,所以先转换 const data = await getRawBody(req, { length: req.headers["content-length"], limit: "1mb", encoding: contentType.parse(req).parameters.charset, }); const result = params.xml2js(data); // 验签:微信传入的除sign外的所有数据进行签名,拒后与sign进行对比是否一致 // 一致说明支付成功,否则支付失败 // 并根据不同的结果通知微信服务器(响应不同的xml数据,如下) const resultSign = result.sign; delete result.sign; const mySign = ctx.service.wx.sign(result); console.log("sign:", resultSign, mySign); ctx.set("content-type", "text/plain"); if (resultSign === mySign) { // 修改商户订单状态 const { device_info, openid, trade_type, bank_type, total_fee, settlement_total_fee, fee_type, transaction_id, time_end, attach, } = result; // 格式化自定义参数 let myattach = {}; if (attach) { myattach = params.parse(attach); } // 格式化支付时间:20200423161017=>2020/04/23 16:10:17 let pay_time = time_end.replace( /(d{4})(d{2})(d{2})(d{2})(d{2})(d{2})/, "$1/$2/$3 $4:$5:$6" ); pay_time = new Date(pay_time); // 根据订单号更新数据库中的订单状态 const newData = { device_info, openid, trade_type, bank_type, total_fee, settlement_total_fee, fee_type, transaction_id, pay_time, status: 1, ...myattach, }; db.update( "purchase", { out_trade_no: result.out_trade_no, }, { $set: newData, } ); ctx.body = ``; } else { ctx.body = ``; } } } module.exports = PaymentController;
附上以上代码中会用的封装好的方法parse()、xml2js()、js2xml(),我写在utils/index.js中
const params = { parse(queryString) { // 'a=1&b=2' => {a:1,b:2} return queryString.split("&").reduce((res, item) => { const arr = item.split("="); res[arr[0]] = arr[1]; return res; }, {}); }, js2xml(data) { return convert.js2xml({ xml: data }, { compact: true }); }, xml2js(xml) { const result = convert.xml2js(xml, { compact: true, textKey: "value", cdataKey: "value", }).xml; const data = {}; for (const key in result) { data[key] = result[key].value; } return data; }, }; module.exports = { params }
到此微信支付之小程序支付就完成了,过程比较繁杂,一定要一步步去实现,也许会踩坑,但相信我,这是每个程序员的必经这路,面对它,勇敢地走过去,你对能到达胜利的彼岸。
注意事项
- appid、appsecret、mchid、mchkey、openid 为小程序或商户私密信息,应保存在服务端
- 注意参数大小写:每个接口大小写可能不同
- 签名算法:请查看接口文档
- 一定要注意看文档,根据我多冷踩坑的经历,90%以上的问题都是没有仔细看文档所致
参考网址与接口
- 微信支付商户平台:https://pay.weixin.qq.com
- 微信公众平台:https://mp.weixin.qq.com
- 微信支付接口:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
- 统一下单接口:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
- 支付结果通知接口:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8
- 签名算法:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
[mp] https://mp.weixin.qq.com
[pay] https://pay.weixin.qq.com
[payment] https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
[notify] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8
[login] https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
[unifiedorder] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
[code2session] https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
[sign] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
小程序下单账号与支付账号不一致不让支付_微信小程序支付流程相关推荐
- 小程序 多个 veb-view返回 返回了两个页面_微信小程序学习心得 - 忒扎心
我们写小程序时都要跳转页面的,也会有底部导航来进行切换 这个时候就要介绍下窗口是怎样配置的 要在文件里写一个tabBer对象 里面在定义一个list数组里面放我们定义的几个需要切换的页面 如下 最多l ...
- 微信小程序怎么把获取的值传到引用组件内_微信小程序如何将接口获取的数据传递给自定义组件...
2019-07-11 回答 不知道你是什么意思.帮你改了下 class program { static int n = 4; int i, m; dsd[] a = new dsd[n]; publ ...
- 视频教程-微信小程序系统教程python版[3/3阶段]_微信小程序支付-手游开发
微信小程序系统教程python版[3/3阶段]_微信小程序支付 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试 ...
- 小程序 微信统计表格_微信小程序登录机制
" 不是0,也不是1,有0也有1 " 总有一个瞬间,你想记录当下的一些事情,所以有了这篇文章,不会口吐芬芳,我直接开门了,但愿能让你见山. 1. 背景 21 届的校园招聘已经打响了 ...
- 微信小程序python解析获取用户手机号_微信小程序获取用户手机号
获取微信用户绑定的手机号,需先调用wx.login接口. 小程序获取code. 后台得到session_key,openid. 组件触发getPhoneNumber 因为需要用户主动触发才能发起获取手 ...
- 黑马优购_微信小程序
黑马优购_微信小程序项目 介绍 2021年5月6日-2021年5月12日在校参加微信小程序培训,由黑马讲师授课,能够利用微信提供的组件和API实现轮播图.授权用户信息.上拉加载更多等功能,由于之前对u ...
- 600多个微信小程序源码_微信小程序在线音乐播放器及源码下载
引言 自己刚开始学微信小程序的时候,自己做着玩玩的. 现在分享出来给大家学习用用,如果觉得有借鉴意义,我的目的就算达到了. 成果 效果图 废话不多说,直接上效果图: 这里 本来是GIF的图,但是太大了 ...
- 微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-翟东平-专题视频课程...
微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-2445人已学习 课程介绍 微信小程序系统教程[初级阶段],微信小程序0基础学起,讲解微信小程序开发的基础知识. 微信小 ...
- 视频教程-微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-微信开发
微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试 ...
- 微信小程序页面栈_微信小程序之页面传值(路由、页面栈、globalData、缓存)
1. 通过url带参数传递 1.1 固定参数传递 例如,从 list 页面到 detail 页面, 传递一个或多个固定值 list页面传值: 点此进入 detail detail页面取值: onLoa ...
最新文章
- 如果再写for循环,我就锤自己!
- 五年程序员败在阿里三面,还是Java底层原理的问题啊!
- 阿里九峰:云计算开启的基础设施新时代
- 计算机英语学情分析怎么写,2016年信息技术教学计划及学情分析(600字)
- P5782-[POI2001]和平委员会【2-SAT】
- 【算法设计与分析】05 有关函数的渐进的界的定理
- vue 获取请求url_vue 获取url里参数的两种方法小结
- 在互联网大环境下,IT编程以及网络营销,到底学哪个比较好就业?
- go语言slice使用的时候遇到的奇怪现象以及分析
- 中间环节越多,大家就越赚钱?
- matlab中构建Cuk变换器,CUK变换器的SIMULINK仿真与应用.pdf
- UEditor的使用方法
- CCNA学习指南第十章
- 数学建模常用模型10 :数据包络(DEA)分析法(投入产出法)
- 项目管理工具之SWOT分析法
- 电商api全境,Python网络爬虫与数据采集
- WebGL unsupported in this browser 谷歌浏览器,edge不支持WebGL
- 零基础自学Python好难?学起来很吃力,想放弃?看看别人是怎样学习的
- Bhuman应用篇——带球及踢球
- 这么多编程语言,初学者选择哪个比较好?