Vue3+Node/Express支付宝沙箱支付与确认支付

支付宝沙箱配置

支付宝开放平台,进行登录,进入控制台
进入控制台

进入沙箱

进入沙箱

选择自定义密钥

选择自定义密钥

密钥工具下载

密钥工具下载
密钥工具下载

生成密钥

生成密钥
密钥生成

格式转换

应用密钥
将应用私钥复制至格式转换
格式转换

自定义密钥设置

将生成的应用公钥复制到自定义密钥设置中
复制应用公钥
保存后如下图所示
保存后
最后点击确认即可,此时公钥模式已启用
公钥模式已启用

Express

安装依赖

    "alipay-sdk": "^3.6.1",
    "axios": "^1.8.1",

本篇文章使用的sdk^3.6.1axios是为寻求支付结果需要使用到的

项目目录

├─db
├─jwt_config
├─limit
├─public
│  └─upload
├─router
├─router_handler
└─utils
   └─alipay.js

创建alipay.js

代码1
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可在支付宝开放平台的沙盒里看到,如下图所示:
APPID
signTypegateway分别是签名算法网关,这个默认和上述代码即可

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)实现,subjectbody则是与商品的基本信息有关,可以通过前端传值进行赋值

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状态
loading
直至结果返回后取消

一些相关的思路

①支付成功后需要连带更新库存销量
②支付成功后需要更新购物车或当前页面(假设当前页面存在①的数据)
③支付成功后需要更新用户的订单列表
④由于订单号唯一性,初次支付过程中未支付(生成了订单号),下次支付时需要更新改商品已存在的订单号
⑤多个商品同时结算,利用多条记录的订单号相同的思路,便可通过订单号查询结果时一并更新用户订单,也就是订单号已经为支付状态了,把相同订单号的记录的信息更新至用户的订单表中

参考链接

Node对接支付宝沙箱完成支付完整流程
使用Node.js打通支付宝支付(沙箱环境)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端小王hs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值