前言
支付宝和微信现在都有提供沙箱支付系统,供开发人员测试支付功能使用;但微信的沙箱需要商家资质,对于个人开发者很不友好。鉴于在微信小程序中只是单纯为了实现支付流程,决定采用支付宝的沙箱系统。缺点是用户必须复制支付链接后在浏览器打开进行支付,再返回小程序确认支付状态。
订单支付的工作流程
用户从购物车选好物品后,点击 去结算 按钮,小程序请求服务端创建订单,此时会服务端生成一个订单号(本例中使用毫秒级时间戳代替)作为该订单的标识,并将订单信息返回给小程序。
然后用户在结算页面,点击确认支付后,小程序携带订单号和总金额请求服务端 /api/pre/set/order/state
接口,服务端调用AlipaySDK 的 alipay.trade.page.pay
接口,生成一个订单支付链接,如:
https://openapi-sandbox.dl.alipaydev.com/gateway.do?method=alipay.trade.page.pay&app_id=XXXXXXX&charset=utf-8&version=1.0&sign_type=RSA2×tamp=2024-03-09%2022%3A03%3A12&return_url=XXX&sign=XXXXXXXXXXXXXXXXXymqag%3D%3D&alipay_sdk=alipay-sdk-nodejs-3.6.1&biz_content=%7B%22out_trade_no%22%3A%XXXX%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%2C%22total_amount%22%3A1088%2C%22subject%22%22%7D
此时服务端将此链接返回给小程序,提示用户复制链接到浏览器打开并支付即可。
当用户点击复制按钮后,程序自动将该链接复制到用户手机的剪贴板上,此时界面停留在 “确认我已支付” 界面,待用户在浏览器支付完成并返回小程序,点击 “我已支付” 按钮时,小程序携带订单号请求服务端 /api/pre/get/pay/state
接口,服务端调用 AlipaySDK 的 alipay.trade.query
接口,同样会返回一个查询订单状态的链接,我们在服务端直接使用 GET 请求该链接,返回内容即为目标订单状态。
状态码 | 状态 | 含义 |
---|---|---|
10001 | WAIT_BUYER_PAY | 支付宝有交易记录,没付款 |
10002 | TRADE_FINISHED | 交易完成(交易结束,不可退款) |
10002 | TRADE_SUCCESS | 交易完成 |
10003 | TRADE_CLOSED | 交易关闭 |
环境准备
首先需要登录 https://open.alipay.com/develop/sandbox/app 注册一个沙箱应用,并拿到沙箱的AppID、支付宝公钥、应用私钥:
然后在后端项目安装 alipay-sdk
和 axios
npm install alipay-sdk -S
npm install axios -S
后端接入
封装alipay.js
const AlipaySdk = require("alipay-sdk").default;
const alipaySdk = new AlipaySdk({
// AppId
appId: "XXXXXXX",
// 签名算法
signType: "RSA2",
// 支付宝网关
gateway: "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
// 支付宝公钥
alipayPublicKey: "XXXXXXXXXXXXXXXXXXXX",
// 应用私钥
privateKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
});
module.exports = alipaySdk;
生成支付链接
const goodsName = req.body.goodsName // 订单标题
const totalPrice = req.body.price // 订单总价
const formData = new AlipayFormData();
formData.addField("bizContent", {
outTradeNo: orderNumber, // 订单号
productCode: "FAST_INSTANT_TRADE_PAY",
totalAmount: totalPrice, // 订单总价
subject: goodsName, // 订单名称
body: goodsName, // 订单描述
});
formData.addField("returnUrl", "http://localhost:3303/api/back/payment");
formData.setMethod("get");
// 返回promise
const result = await alipaySdk.exec('alipay.trade.page.pay', // alipay.trade.wap.pay 为手机端接口
{}, // api 请求的参数(包含“公共请求参数”和“业务参数”)
{ formData: formData },
);
res.send({
code: 200,
success: true,
msg: "支付中",
paymentUrl: result, // result为用户需要打开的支付链接
});
查询支付结果
const { orderNumber } = req.body
const formData = new AlipayFormData();
formData.setMethod("get");
formData.addField("bizContent", { outTradeNo: orderNumber });
const result = alipaySdk.exec("alipay.trade.query",{},{ formData: formData }).catch(error=>console.error('caught error!', error));
result.then(resData => {
axios({
method: "GET",
url: resData
}).then(e => {
let code = e.data.alipay_trade_query_response;
if (code.code == 10000) {
switch (code.trade_status) {
case 'WAIT_BUYER_PAY':
res.send({
code: 10001,
message: "支付宝有交易记录,没付款"
})
break;
case 'TRADE_FINISHED':
// 完成交易的逻辑
res.send({
code: 10002,
message: "交易完成(交易结束,不可退款)"
})
break;
case 'TRADE_SUCCESS':
// 完成交易的逻辑
res.send({
code: 10002,
message: "交易完成"
})
break;
case 'TRADE_CLOSED':
// 交易关闭的逻辑
res.send({
code: 10003,
message: "交易关闭"
})
break;
}
// 订单已支付 更新订单状态
if (code.trade_status === "TRADE_FINISHED" || code.trade_status === "TRADE_SUCCESS") {
// 更新订单状态(执行SQL)
}
} else if (code.code == 40004) {
return res.send({
code: 40004,
message: "交易不存在"
})
}
}).catch(err => {
console.log(err);
return res.send({
code: 50000,
msg: "交易失败",
data: err
})
})
})