Vue3+Node/Express支付宝沙箱支付与确认支付
支付宝沙箱配置
支付宝开放平台,进行登录,进入控制台
进入沙箱
选择自定义密钥
密钥工具下载
生成密钥
格式转换
将应用私钥复制至格式转换
自定义密钥设置
将生成的应用公钥复制到自定义密钥设置中
保存后如下图所示
最后点击确认即可,此时公钥模式已启用
Express
安装依赖
"alipay-sdk": "^3.6.1",
"axios": "^1.8.1",
本篇文章使用的sdk
为^3.6.1
,axios
是为寻求支付结果需要使用到的
项目目录
├─db
├─jwt_config
├─limit
├─public
│ └─upload
├─router
├─router_handler
└─utils
└─alipay.js
创建alipay.js
utils
目录下新建alipay.js
文件,代码如下:
const AlipaySdk = require("alipay-sdk").default;
const alipaySdk = new AlipaySdk({
appId: "",
signType: "RSA2",
gateway: "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
alipayPublicKey:
"",
privateKey:
""
});
module.exports = alipaySdk;
这里的appId
可在支付宝开放平台的沙盒里看到,如下图所示:
signType
和gateway
分别是签名算法
和网关
,这个默认和上述代码即可
alipayPublicKey
就是自定义密钥设置一节中的支付宝公钥
privateKey
则为格式转换后输出的密钥
请求(打开支付)代码
router_handler
下新建pay.js
路由处理程序文件,代码如下:
const alipaySdk = require("../utils/alipay");
const AlipayFormData = require("alipay-sdk/lib/form").default;
exports.pay = (req, res) => {
console.log(req.body);
(async () => {
// 调用 setMethod 并传入 get,会返回可以跳转到支付页面的 url
const formData = new AlipayFormData();
formData.setMethod("get");
// 通过 addField 增加参数
// 在用户支付完成之后,支付宝服务器会根据传入的 notify_url,以 POST 请求的形式将支付结果作为参数通知到商户系统。
formData.addField("returnUrl", "http://localhost:8080/#/home"); // 支付成功回调地址,必须为可以直接访问的地址,不能带参数
formData.addField("bizContent", {
outTradeNo: req.body.outTradeNo, // 商户订单号,64个字符以内、可包含字母、数字、下划线,且不能重复
productCode: "FAST_INSTANT_TRADE_PAY", // 销售产品码,与支付宝签约的产品码名称,仅支持FAST_INSTANT_TRADE_PAY
totalAmount: req.body.all_money, // 订单总金额,单位为元,精确到小数点后两位
subject: "商品", // 订单标题
body: "商品详情", // 订单描述
}); // 如果需要支付后跳转到商户界面,可以增加属性"returnUrl"
const result = await alipaySdk.exec(
"alipay.trade.page.pay", // 统一收单下单并支付页面接口
{}, // api 请求的参数(包含“公共请求参数”和“业务参数”)
{
formData: formData,
}
); // result 为可以跳转到支付链接的 url
res.json({
url: result,
});
})();
};
这里需要注意的是returnUrl
对应的地址,这是当支付成功后跳转的页面,一般来说都是跳转回商城的首页
bizContent
里面的内容则是与支付有关,outTradeNo
是订单号(可以使用随机函数唯一生成,用于查询结果,非常重要),productCode
默认为FAST_INSTANT_TRADE_PAY
无需改变,totalAmount
是商品的总价,也就是需要支付的价格,需要注意的是要精确到小数点后两位,可以使用fixed(2)
实现,subject
和body
则是与商品的基本信息有关,可以通过前端传值进行赋值
router/pay.js
// router/pay.js
const express = require("express");
const router = express.Router();
const PayHandler = require("../router_handler/pay");
router.post("/pay", PayHandler.pay);
module.exports = router;
app.js
// app.js
const PayRouter = require("./router/pay");
app.use("/pay", PayRouter);
至此基础的就完成了,可以请求当前接口进行支付
前端代码
前端封装接口
// 订单号和总价
export const pay = (outTradeNo, all_money) => {
return instance({
url: "/pay/pay",
method: "POST",
data: {
outTradeNo,
all_money,
},
});
};
前端调用
const res = await pay(outTradeNo, totalPrice1.toFixed(2));
if (res.url) {
window.open(res.url, "_blank"); // 在新窗口中打开支付链接
} else {
alert("未获取到支付链接!");
return;
}
实现支付
当请求后发起后会在浏览器打开支付宝的支付窗口,如下所示:
账户名和支付密码在沙箱账号中,如下图所示:
支付成功,等待2~3秒会跳转至returnUrl
指定的地址
查询结果代码
开箱即用,传入订单号outTradeNo
即可查询结果
// router/pay.js
const express = require("express");
const router = express.Router();
const axios = require("axios");
const PayHandler = require("../router_handler/pay");
const alipaySdk = require("../utils/alipay");
router.post("/pay", PayHandler.pay);
const AlipayFormData = require("alipay-sdk/lib/form").default;
router.post("/query", function (req, res) {
(async function () {
const { outTradeNo } = req.body;
console.log(outTradeNo);
const formData = new AlipayFormData();
formData.setMethod("get");
formData.addField("bizContent", {
outTradeNo,
}); // 通过该接口主动查询订单状态const result = await alipaySdk.exec( 'alipay.trade.query',
const result = await alipaySdk.exec(
"alipay.trade.query", // 统一收单下单并支付页面接口
{}, // api 请求的参数(包含“公共请求参数”和“业务参数”)
{
formData: formData,
}
);
axios({
method: "GET",
url: result,
})
.then((data) => {
let r = data.data.alipay_trade_query_response;
if (r.code === "10000") {
// 接口调用成功
switch (r.trade_status) {
case "WAIT_BUYER_PAY":
res.send("交易创建,等待买家付款");
break;
case "TRADE_CLOSED":
res.send("未付款交易超时关闭,或支付完成后全额退款");
break;
case "TRADE_SUCCESS":
res.send({
status: 0,
msg: "支付成功",
});
break;
case "TRADE_FINISHED":
res.send("交易结束,不可退款");
break;
}
} else if (r.code === "40004") {
res.send("交易不存在");
}
})
.catch((err) => {
res.json({
msg: "查询失败",
err,
});
});
})();
});
module.exports = router;
优化和实现思路(重点)
支付需要结果
支付了需要查询结果,大部分文章仅仅讲到了支付,并没有谈到如何获取结果,这里参考了使用Node.js打通支付宝支付(沙箱环境),作者是字节跳动程序员
如何更新页面?
查询到结果如何更新页面?使用了ElMessageBox
和ElLoading
第一步是弹窗,如下图所示:
由于此时无法确定用户是否已支付或未支付(打开了弹窗但未支付),所以不管是取消和确定都需要调用接口进行查询
请求时间增加健壮性
由于请求结果是由支付宝服务器传回,所以需要最大程度的考虑请求的响应时间,经过测试,60s
是个不错的选择,代码如下:
// 二次封装axios
import axios from "axios";
const instance = axios.create({
// 后端url地址
baseURL: "http://127.0.0.1:3007",
timeout: 60000, //设置超时
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
当然,请求失败后再次进行请求也是一个不错的选择
请求过程优化
添加loading
状态
直至结果返回后取消
一些相关的思路
①支付成功后需要连带更新库存和销量
②支付成功后需要更新购物车或当前页面(假设当前页面存在①的数据)
③支付成功后需要更新用户的订单列表
④由于订单号唯一性,初次支付过程中未支付(生成了订单号),下次支付时需要更新改商品已存在的订单号
⑤多个商品同时结算,利用多条记录的订单号相同的思路,便可通过订单号查询结果时一并更新用户订单,也就是订单号已经为支付状态了,把相同订单号的记录的信息更新至用户的订单表中