小程序支付流程(小程序如何支付功能怎么弄)
ccwgpt 2024-10-12 02:43 22 浏览 0 评论
微信支付之小程序支付
微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式
说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序必须具备企业性质,必须拥有微信支付商户平台的账号
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 = `<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>`;
} else {
ctx.body = `<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[ERROR]]></return_msg>
</xml>`;
}
}
}
?
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
相关推荐
- 用Steam启动Epic游戏会更快吗?(epic怎么用steam启动)
-
Epic商店很香,但也有不少抱怨,其中一条是启动游戏太慢。那么,如果让Steam启动Epic游戏,会不会速度更快?众所周知,Steam可以启动非Steam游戏,方法是在客户端左下方点击“添加游戏”,然...
- Docker看这一篇入门就够了(dockerl)
-
安装DockerLinux:$curl-fsSLhttps://get.docker.com-oget-docker.sh$sudoshget-docker.sh注意:如果安装了旧版...
- AYUI 炫丽PC开发UI框架2016年6月15日对外免费开发使用 [1]
-
2016年6月15日,我AY对外发布AYUI(WPF4.0开发)的UI框架,开发时候,你可以无任何影响的去开发PC电脑上的软件exe程序。AYUI兼容XP操作系统,在Win7/8/8.1/10上都顺利...
- 别再说C#/C++套壳方案多了!Tauri这“借壳生蛋”你可能没看懂!
-
浏览器套壳方案,C#和C++有更多,你说的没错,从数量和历史积淀来看,C#和C++确实有不少方式来套壳浏览器,让Web内容在桌面应用里跑起来。但咱们得把这套壳二字掰扯清楚,因为这里面学问可大了!不同的...
- OneCode 核心概念解析——Page(页面)
-
在接触到OneCode最先接触到的就是,Page页面,在低代码引擎中,页面(Page)设计的灵活性是平衡“快速开发”与“复杂需求适配”的关键。以下从架构设计、组件系统、配置能力等维度,解析确...
- React是最后的前端框架吗,为什么这么说的?
-
油管上有一位叫Theo的博主说,React是终极前端框架,为什么这么说呢?让我们来看看其逻辑:这个标题看起来像假的,对吧?React之后明明有无数新框架诞生,凭什么说它是最后一个?我说的“最后一个”不...
- 面试辅导(二):2025前端面试密码:用3个底层逻辑征服技术官
-
面试官放下简历,手指在桌上敲了三下:"你上次解决的技术难题,现在回头看有什么不足?"眼前的候选人瞬间僵住——这是上周真实发生在蚂蚁金服终面的场景。2025年的前端战场早已不是框架熟练...
- 前端新星崛起!Astro框架能否终结React的霸主地位?
-
引言:当"背着背包的全能选手"遇上"轻装上阵的短跑冠军"如果你是一名前端开发者,2024年的框架之争绝对让你眼花缭乱——一边是React这位"背着全家桶的全能选...
- 基于函数计算的 BFF 架构(基于函数计算的 bff 架构是什么)
-
什么是BFFBFF全称是BackendsForFrontends(服务于前端的后端),起源于2015年SamNewman一篇博客文章《Pattern:BackendsFor...
- 谷歌 Prompt Engineering 白皮书:2025年 AI 提示词工程的 10 个技巧
-
在AI技术飞速发展的当下,如何更高效地与大语言模型(LLM)沟通,以获取更准确、更有价值的输出,成为了一个备受关注的问题。谷歌最新发布的《PromptEngineering》白皮书,为这一问题提供了...
- 光的艺术:灯具创意设计(灯光艺术作品展示)
-
本文转自|艺术与设计微信号|artdesign_org_cn“光”是文明的起源,是思维的开端,同样也是人类睁眼的开始。每个人在出生一刻,便接受了光的照耀和洗礼。远古时候,人们将光奉为神明,用火来...
- MoE模型已成新风口,AI基础设施竞速升级
-
机器之心报道编辑:Panda因为基准测试成绩与实际表现相差较大,近期开源的Llama4系列模型正陷入争议的漩涡之中,但有一点却毫无疑问:MoE(混合专家)定然是未来AI大模型的主流范式之一。...
- Meta Spatial SDK重大改进:重塑Horizon OS应用开发格局
-
由文心大模型生成的文章摘要Meta持续深耕SpatialSDK技术生态,提供开自去年9月正式推出以来,Meta持续深耕其SpatialSDK技术生态,通过一系列重大迭代与功能增强,不断革新H...
- "上云"到底是个啥?用"租房"给你讲明白IaaS/PaaS/SaaS的区别
-
半夜三点被机房报警电话惊醒,顶着黑眼圈排查服务器故障——这是十年前互联网公司运维的日常。而现在,程序员小王正敷着面膜刷剧,因为公司的系统全"搬"到了云上。"部署到云上"...
- php宝塔搭建部署thinkphp机械设备响应式企业网站php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的机械设备响应式企业网站php源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试了一下...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 用Steam启动Epic游戏会更快吗?(epic怎么用steam启动)
- Docker看这一篇入门就够了(dockerl)
- AYUI 炫丽PC开发UI框架2016年6月15日对外免费开发使用 [1]
- 别再说C#/C++套壳方案多了!Tauri这“借壳生蛋”你可能没看懂!
- OneCode 核心概念解析——Page(页面)
- React是最后的前端框架吗,为什么这么说的?
- 面试辅导(二):2025前端面试密码:用3个底层逻辑征服技术官
- 前端新星崛起!Astro框架能否终结React的霸主地位?
- 基于函数计算的 BFF 架构(基于函数计算的 bff 架构是什么)
- 谷歌 Prompt Engineering 白皮书:2025年 AI 提示词工程的 10 个技巧
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)