Java接入支付宝 JSAPI 支付保姆教程(二):流程讲解与前后端代码讲解

一、 前言

本篇是《Java接入支付宝 JSAPI 支付保姆教程》系列教程的第二篇。

上篇我们已经完成了支付宝商户号、支付宝开放平台、支付宝小程序、密钥、服务器配置等准备工作。 本篇开始,直接进入项目实战。
为了让教程更加贴近实际开发环境,本文不会重新从0创建一个完整的Java 项目和支付宝小程序项目,而是直接基于一个已经存在并且投入企业使用的真实项目进行部分演示。
本文会使用一个已经开发完成的 Java 后端项目,以及一个已经创建好的支付宝小程序作为示例,在不涉及具体业务代码的情况下,带大家完成支付宝 JSAPI 支付接入项目准备工作。
本文使用的项目是实际项目中的简化示例,涉及公司名称、项目名称、业务数据、数据库表接口等信息均会进行隐藏或脱敏,大家不需要关注具体业务,只需要关注支付宝支付接入相关的代码和配置即可。
如果你手上已经有自己的 Java 项目和支付宝小程序项目,也可以直接照本文的步骤进行改造。

二、支付宝JSAPI 支付流程

请自行仔细查看下面这张流程图,这是整个支付宝 JSAPI 支付小程序支付的总的流程图。

简单讲解:

1、创建订单:

整个逻辑流程就是:用户首先是在我们上架的支付宝小程序中进行操作,比如说想要购买某一件商品,用户的操作就是直接点击购买或加入购物车后点击购买,当用户点击购买时候,小程序端这里会根据当前商品的数据信息,比如说商品ID,像后端发送一个请求,请求创建一个新的订单,根据小程序端传递过来的商品数据,来计算价格,将价格确定到订单数据中,商户后端也会去调用支付宝提供的SDK向支付宝服务端请求创建订单,将商户的订单号和价格传递给支付宝服务端,支付宝服务端响应返回所需要的数据,然后在这些过程完成后,商户服务端响应必须得数据信息返回给小程序端。

2、小程序拉取支付宝收银台

小程序在得到必须要的数据后,通过这些数据,作为参数在小程序中调用my.tradePay()方法,唤起支付宝的收银台,需要参入参数有这些:

主要核心的就是tradeNo支付宝交易号。

3、支付完成,支付回调

小程序端在成功获取商户服务端传递的参数后,并且成功调用my.tradePay()方法后,支付宝会请求到支付宝服务器,然后当场返回支付结果,并且异步去调用商户提供的商户服务端的支付回调地址,来通知商户服务端,该笔订单支付成功或者失败。

my.tradePay() 存在几个回调函数,其中重要的常使用的就是success回调函数和fail回调函数,这里重点讲一下 success回调函数中的响应值,可以参考下面:

属性 类型 描述
resultCode String 支付结果码
resultCode 枚举值说明
枚举值 描述 解决方案
4 无权限调用(N22104) 个人小程序应用没有开放 JSAPI 支付能力,请更换为企业小程序。
4000 订单处理失败 JSAPI 支付: tradeNO 必填,检查 tradeNO 是否正确,注意 NO 必须大写。tradeNO 必须使用 alipay.trade.create(统一收单交易创建接口)返回的 trade_no,而不是 out_trade_no。 预授权支付: orderStr 必须传入。检查 alipay.fund.auth.order.app.freeze(线上资金授权冻结接口)的参数是否正确。建议先只传递必填参数进行测试,避免其他参数干扰。
6001 支付未完成 用户中途取消签约或支付,请用户重新签约或支付。同时检查 tradeNO 是否正常,该值必须是 alipay.trade.create 返回的 trade_no。 在小程序场景中,buyer_id(2088 开头)为必填参数,并且必须与前端唤起支付的支付宝 UID 保持一致。
6002 网络连接出错 检查当前网络连接,确认网络正常后重试。
6004 处理结果未知(有可能已经成功) 调用 alipay.trade.query(统一收单交易查询接口),通过商户网站唯一订单号 out_trade_no 或支付宝交易号 trade_no 查询订单实际支付状态,以查询接口返回结果为准。
8000 正在处理中(支付结果未知,有可能已经支付成功) 调用 alipay.trade.query(统一收单交易查询接口),通过 out_trade_no 或 trade_no 查询订单实际支付状态,以查询接口返回结果为准。
9000 订单处理成功 注意:不建议直接根据 my.tradePay 接口同步返回的 9000 判断支付成功。 应在调用 alipay.trade.create 时配置 notify_url,通过支付宝服务端异步通知获取最终支付结果。当异步通知中的 trade_status 为 TRADE_SUCCESS 时,才表示支付成功。
99 用户点击「忘记密码」导致快捷界面退出(仅 iOS) 用户点击「忘记密码」导致快捷支付界面退出,该情况仅发生在 iOS。

4、商户服务端处理支付回调通知

在用户成功支付之后,支付宝会异步通知给商户服务端,商户服务端接受到该通知,需要从请求里通过官方提供的 SDK 来解析获取信息,进行确定,最后进行发货处理,发货处理比如说基本的将订单状态从未支付 更新到 已支付,并将商品发放给用户登操作。

三、前后端讲解

后端技术栈简单介绍;Java21、SpringBoot 3、 MyBatisPlus、Redis、MySQL8、Rabbit

前端技术栈简单介绍:支付宝小程序原生、Vue3、ElementPlus、Axios

3.1 后端引入支付宝官方的SDK依赖

本项目中maven的pom配置中,加入支付宝官方的SDK依赖,我这里选用的SDK版本为 <alipay.version>4.40.0.ALL</alipay.version>,你可以参考我这里。

复制代码
            <dependency>
                <groupId>com.alipay.sdk</groupId>
                <artifactId>alipay-sdk-java</artifactId>
                <version>${alipay.version}</version>
            </dependency>

你可以自行搜索alipay-sdk-java 前往支付宝官方文档查看详情,这里我提供一张来自支付宝官方文档的截图,并且我标注好,需要看哪些材料?

在给Java后端项目添加好支付宝SDK后,我们可以去maven加载下载依赖软件包。

完成后,只要不出现报错基本上没有什么问题。

3.2 后端支付宝支付配置

这里为了更好的理解,我简单的介绍一下本项目的后端支付部分,支付部分我们单独封装了一个模块名xxx-module-payment。

然后该模块的pom.xml配置如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project >
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.xxx</groupId>
        <artifactId>xxx-module</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>xxx-module-payment</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.xxx</groupId>
            <artifactId>xxx-module-system</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
        </dependency>
    </dependencies>

</project>

这里你只需要关注到我这里引用了alipay-sdk-java即可。

然后我们需要在我们整个后端配置中,添加一段关于支付宝支付的配置,在application.yml中添加。这是该项目的application.yml关于支付宝JSAPI支付配置截取:

yml 复制代码
  # 支付宝 支持
  alipay:
    # 支付宝支付配置
    pay:
      # 支付宝网关
      gateway: https://openapi.alipay.com/gateway.do
      # 应用AppId
      app-id: 2021xxxxxxx
      # 应用私钥
      private-key: xxxxx
      # 支付宝公钥
      public-key: xxxxx
      # 支付成功回调地址
      notify-url: https://api.xxxx.xxx.com/public/alipay/notify

这里的 app-id支付宝小程序APPID、private-key应用私钥、public-key支付宝公钥,在本系列第一篇中就提到过,你可以从你保存的txt中,复制过来替换上去,然后这里的支付回调地址,你可以参考,具体要根据你的实际项目部署情况来填写。

注意:这里的支付成功回调地址一定是公开可以随意调用的,不需要Token验证等,可以让支付宝服务器访问到!!!

然后我们创建 AlipayProperties 类:

java 复制代码
/**
 * 支付宝配置
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "xxx.alipay.pay")
public class AlipayProperties {

    /**
     * 支付宝网关
     */
    private String gateway;

    /**
     * 应用AppId
     */
    private String appId;

    /**
     * 应用私钥
     */
    private String privateKey;

    /**
     * 支付宝公钥
     */
    private String publicKey;

    /**
     * 回调地址
     */
    private String notifyUrl;
}

这里来映射 application.yml的支付宝配置参数。

依据支付宝官方SDK的使用说明,我们需要将 AlipayClient去注册成 Bean 然后封装或者通过AlipayClient去调用支付宝官方服务端提供的能力。

这里我创建了一个 AlipayConfig类:

java 复制代码
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.junoyi.payment.properties.AlipayProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RequiredArgsConstructor
public class AlipayConfig {

    private final AlipayProperties properties;

    /**
     * 支付宝客户端Bean
     */
    @Bean
    public AlipayClient alipayClient(){
        return new DefaultAlipayClient(
                properties.getGateway(),
                properties.getAppId(),
                properties.getPrivateKey(),
                "json",
                "UTF-8",
                properties.getPublicKey(),
                "RSA2"
        );

    }
}

你如果想要后续更方便,代码可以复用,你可以在此基础上,去再次通过AlipayClient去封装各种关于支付宝服务端详细能力的方法或类。本项目因为不需要这些,在业务类中直接去注入 AlipayClient 然后直接去操作。

3.3 后端接口及业务逻辑

在开始后端支付接口开发前,一定要确定已经设计好了订单表,如果没有订单表,没法进一步开发,你可以自行设计,这里不作提供。

支付宝JSAPI支付接口

用户在点击购买点击支付时候,以及在小程序的my.tradePay()方法调用之前,必须要提供一个接口调用,返回支付宝流水号作为参数入参,这里我提供一个 支付宝JSAPI支付接口,提供调用,详细请参考下面逻辑:

AlipayPaymentController

java 复制代码
import com.junoyi.framework.core.domain.module.R;
import com.junoyi.framework.core.utils.StringUtils;
import com.junoyi.framework.web.domain.BaseController;
import com.junoyi.payment.domain.dto.AlipayJsapiPayDTO;
import com.junoyi.payment.service.IAlipayPaymentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * 支付宝支付控制器
 *
 * <p>
 *     本控制器包含支付宝的JSAPI支付过程:
 *     通过订单号获取订单进行支持,回调通知由AlipayPaymentPublicController控制器进行处理
 * </p>
 *
 * @author Fan
 */
@RestController
@RequestMapping("/pay/alipay")
@RequiredArgsConstructor
public class AlipayPaymentController extends BaseController {

    private final IAlipayPaymentService alipayPaymentService;


    /**
     * 支付宝JSAPI支付(创建支付订单)
     */
    @PostMapping("/jsapi")
    public R<String> jsapiPay(@RequestBody AlipayJsapiPayDTO dto) throws Exception{
        if (dto == null)
            return R.fail("非法参数");

        if (StringUtils.isBlank(dto.getCollectionNo()) || dto.getCollectionNo() == null)
            return R.fail("非法参数");

        if (StringUtils.isBlank(dto.getAuthCode()))
            return R.fail("非法参数");

        return R.ok("支付宝支付流水号",alipayPaymentService.createJsapiPayOrder(dto));
    }
}

这里 jsapi接口 要接收前端小程序端传递过来的参数,然后做一个简单基础Controller层判断,这里的 AlipayJsapiPayDTO结构是这样的:

java 复制代码
/**
 * 支付宝 JSAPI 支付 DTO
 *
 * @author Fan
 */
@Data
public class AlipayJsapiPayDTO {

    /**
     * 付款单号
     */
    private String collectionNo;

    /**
     * 付款备注
     */
    private String payRemark;

    /**
     * 支付宝授权码
     */
    private String authCode;
}

IAlipayPaymentService支付宝支付业务接口

java 复制代码
/**
 * 支付宝支付业务接口
 *
 * @author Fan
 */
public interface IAlipayPaymentService {

    /**
     * 创建支付宝JSAPI支付订单(如果已经创建了支付订单,直接使用)
     * @param dto 数据
     * @return 支付宝支付流水号
     */
    String createJsapiPayOrder(AlipayJsapiPayDTO dto) throws Exception;

    /**
     * 处理支付宝支付通知
     * @param params 支付宝回调参数
     * @return 是否处理成功
     */
    boolean handleNotify(Map<String, String> params) throws Exception;
}

AlipayPaymentServiceImpl支付宝支付业务接口实现

java 复制代码
/**
 * 支付宝支付业务接口实现
 *
 * @author Fan
 */
@Service
@RequiredArgsConstructor
public class AlipayPaymentServiceImpl implements IAlipayPaymentService {

    private final JunoYiLog log = JunoYiLogFactory.getLogger(AlipayPaymentServiceImpl.class);

    private final PaymentCollectionOrderMapper paymentCollectionOrderMapper;
    private final PaymentOrderMapper paymentOrderMapper;

    private final AlipayClient alipayClient;
    private final AlipayProperties alipayProperties;
    private final IAlipayUserService alipayUserService;

    /**
     * 创建支付宝JSAPI支付订单(如果已经创建了支付订单,直接使用)
     * @param dto 数据
     * @return 支付宝支付流水号
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createJsapiPayOrder(AlipayJsapiPayDTO dto)  throws Exception{
        // 通过收款单号,去查收款单数据
        LambdaQueryWrapper<PaymentCollectionOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PaymentCollectionOrder::getDelFlag,false)
                .eq(PaymentCollectionOrder::getCollectionNo,dto.getCollectionNo());

        // 查询出来的收款单数据
        PaymentCollectionOrder paymentCollectionOrder = paymentCollectionOrderMapper.selectOne(queryWrapper);
        if (paymentCollectionOrder == null)
            throw new PaymentException("不存在该收款单");

        // 查是否已经存在支付宝支付订单
        LambdaQueryWrapper<PaymentOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(PaymentOrder::getCollectionId,paymentCollectionOrder.getId())
                .eq(PaymentOrder::getCollectionNo,dto.getCollectionNo())
                .eq(PaymentOrder::getChannelType,1)
                        .last("limit 1");
        PaymentOrder existOrder = paymentOrderMapper.selectOne(orderWrapper);

        if (existOrder != null){
            if (existOrder.getStatus() == 1)
                throw new PaymentException("该订单已支付完成");

            // 待支付
            if (existOrder.getStatus() == 0){
                if (StringUtils.isNotBlank(existOrder.getThirdTradeNo())){
                    return existOrder.getThirdTradeNo();
                }
            }
        }

        // 创建支付订单
        PaymentOrder paymentOrder = new PaymentOrder();
        paymentOrder.setOrderNo(NoUtils.generatePaymentOrder());
        paymentOrder.setCollectionId(paymentCollectionOrder.getId());
        paymentOrder.setCollectionNo(paymentCollectionOrder.getCollectionNo());
        // 1为支付宝
        paymentOrder.setChannelType(1);
        BigDecimal amount = paymentCollectionOrder.getAmount();
        paymentOrder.setAmount(amount);
        // 支付宝费率为 0.6%
        BigDecimal feeRate = new BigDecimal("0.006");
        BigDecimal feeAmount = amount.multiply(feeRate).setScale(2, RoundingMode.HALF_UP);
        paymentOrder.setFeeAmount(feeAmount);

        BigDecimal actualAmount = amount.subtract(feeAmount).setScale(2, RoundingMode.HALF_UP);
        paymentOrder.setActualAmount(actualAmount);

        // 待支付
        paymentOrder.setStatus(0);
        // 付款备注
        if (StringUtils.isNotBlank(dto.getPayRemark()))
            paymentOrder.setSummary(dto.getPayRemark());

        // 默认参数
        paymentOrder.setCreateBy("system");
        paymentOrder.setCreateTime(DateUtils.getNowDate());

        // 然后确定要支付的价格,创建支付订单
        // 向支付宝传递自己的支付订单号和要支付的价格,向支付宝换取支付宝流水号
        // 返回支付宝流水号
        String buyerId = alipayUserService.getUserId(dto.getAuthCode());
        String tradeNo = createPay(paymentCollectionOrder,paymentOrder, buyerId);
        paymentOrderMapper.insert(paymentOrder);

        // 更新收款单状态
        // 1为收款中
        paymentCollectionOrder.setStatus(1);
        paymentCollectionOrderMapper.updateById(paymentCollectionOrder);

        return tradeNo;
    }

    /**
     * 调用支付宝SDK,向支付宝发送请求,创建支付
     * @param collectionOrder 收款单
     * @param order 商户的订单数据
     * @param buyerId 购买者支付宝ID
     * @return 返回支付宝支付流水号
     */
    private String createPay(PaymentCollectionOrder collectionOrder,PaymentOrder order, String buyerId) throws Exception{
        AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
        request.setNotifyUrl("https://api.ythpt.junoyi.com/public/alipay/notify");
        request.setBizContent("""
        {
            "out_trade_no":"%s",
            "total_amount":"%s",
            "subject":"%s",
            "product_code":"JSAPI_PAY",
            "buyer_open_id":"%s"
        }
        """.formatted(order.getOrderNo(), order.getAmount(), collectionOrder.getSubject(),buyerId));

        AlipayTradeCreateResponse response = alipayClient.execute(request);
        if (!response.isSuccess())
            throw new PaymentException(response.getSubMsg());

        // 第三方平台流水号
        order.setThirdTradeNo(response.getTradeNo());

        return response.getTradeNo();
    }

    /**
     * 处理支付宝支付通知
     * @param params 支付宝回调参数
     * @return 是否处理成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean handleNotify(Map<String, String> params) throws Exception{
        // 验证支付宝公钥签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayProperties.getPublicKey(), "UTF-8", "RSA2");

        if (!signVerified){
            log.error("支付宝支付回调","支付宝回调验证签名失败");
            return false;
        }

        // 获取参数
        // 支付宝支付流水状态
        String tradeStatus = params.get("trade_status");
        // 商家支付流水号(商家订单号)
        String outTradeNo = params.get("out_trade_no");
        // 支付宝流水号
        String tradeNo = params.get("trade_no");
        if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus))
            return true;

        // 查询订单
        LambdaQueryWrapper<PaymentOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PaymentOrder::getOrderNo, outTradeNo);
        PaymentOrder paymentOrder = paymentOrderMapper.selectOne(queryWrapper);

        if (paymentOrder == null){
            log.error("支付宝支付回调","支付宝回调订单不存在:{}",outTradeNo);
            return false;
        }

        if (paymentOrder.getStatus() == 1)
            return true;

        // 更新支付订单
        Date now = DateUtils.getNowDate();
        // 1为支付成功
        paymentOrder.setStatus(1);
        paymentOrder.setPayTime(now);
        paymentOrder.setUpdateBy("system");
        paymentOrder.setUpdateTime(now);
        paymentOrderMapper.updateById(paymentOrder);


        // 更新收款单
        LambdaUpdateWrapper<PaymentCollectionOrder> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(PaymentCollectionOrder::getCollectionNo, paymentOrder.getCollectionNo())
                .set(PaymentCollectionOrder::getPaidAmount, paymentOrder.getAmount())
                .set(PaymentCollectionOrder::getStatus, 2);
        paymentCollectionOrderMapper.update(updateWrapper);


        return true;

    }
}

这整个业务实现,业务逻辑都是围绕用户要付款了,我们先看看这笔收款单是什么、之前有没有创建支付宝订单,如果没有,就创建一个自己的支付订单,然后调用支付宝接口换取支付宝交易好,最后把这些信息保存下来,然后返回响应需要的数据信息,也就是支付宝小程序中my.tradePay()必须要的tradeNo支付宝交易号响应过去。

其他收款单逻辑是该项目的业务逻辑,不必关注

该业务的主要逻辑就是:

查询收款单 → 检查有没有已有支付宝订单 → 有就直接复用 → 没有就创建支付订单 → 调用支付宝创建交易 → 获取支付宝 trade_no → 保存支付订单 → 更新收款单状态 → 把 trade_no 返回给小程序。

支付宝支付回调接口

这个接口,不需要跟我们的前端对接或者跟小程序对接,主要就是提供给支付宝服务端调用,配置中的支付通知回调地址就是该接口地址。

AlipayPaymentPublicController支付宝支付公开控制器

java 复制代码
/**
 * 支付宝支付控制器(公开)
 *
 * @author Fan
 */
@RestController
@RequestMapping("/public/alipay")
@RequiredArgsConstructor
public class AlipayPaymentPublicController extends BaseController {

    private final IAlipayPaymentService alipayPaymentService;

    /**
     * 支付宝支付回调
     */
    @PostMapping("/notify")
    public String payNotify(HttpServletRequest request){
        try {
            // 获取支付宝回调参数
            Map<String, String> params = new HashMap<>();
            Map<String, String[]> requestParams = request.getParameterMap();
            for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
                params.put(
                        entry.getKey(),
                        String.join(",", entry.getValue())
                );
            }

            // 验签 + 业务处理
            boolean success = alipayPaymentService.handleNotify(params);
            if (success)
                return "success";
        } catch (Exception e) {
            log.error("支付宝支付回调异常", e);
        }
        return "fail";
    }
}

本项目在jwt认证机制中已经规定好 /public 都是为公开接口允许公开访问的接口。

在这里我们手动通过SpringBoot 中的HttpServletRequest去获取请求中的信息数据,解析获取支付宝服务端请求的数据。

然后传递参数进行验签业务逻辑。

java 复制代码
  /**
     * 处理支付宝支付通知
     * @param params 支付宝回调参数
     * @return 是否处理成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean handleNotify(Map<String, String> params) throws Exception{
        // 验证支付宝公钥签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayProperties.getPublicKey(), "UTF-8", "RSA2");

        if (!signVerified){
            log.error("支付宝支付回调","支付宝回调验证签名失败");
            return false;
        }

        // 获取参数
        // 支付宝支付流水状态
        String tradeStatus = params.get("trade_status");
        // 商家支付流水号(商家订单号)
        String outTradeNo = params.get("out_trade_no");
        // 支付宝流水号
        String tradeNo = params.get("trade_no");
        if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus))
            return true;

        // 查询订单
        LambdaQueryWrapper<PaymentOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PaymentOrder::getOrderNo, outTradeNo);
        PaymentOrder paymentOrder = paymentOrderMapper.selectOne(queryWrapper);

        if (paymentOrder == null){
            log.error("支付宝支付回调","支付宝回调订单不存在:{}",outTradeNo);
            return false;
        }

        if (paymentOrder.getStatus() == 1)
            return true;

        // 更新支付订单
        Date now = DateUtils.getNowDate();
        // 1为支付成功
        paymentOrder.setStatus(1);
        paymentOrder.setPayTime(now);
        paymentOrder.setUpdateBy("system");
        paymentOrder.setUpdateTime(now);
        paymentOrderMapper.updateById(paymentOrder);


        // 更新收款单
        LambdaUpdateWrapper<PaymentCollectionOrder> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(PaymentCollectionOrder::getCollectionNo, paymentOrder.getCollectionNo())
                .set(PaymentCollectionOrder::getPaidAmount, paymentOrder.getAmount())
                .set(PaymentCollectionOrder::getStatus, 2);
        paymentCollectionOrderMapper.update(updateWrapper);


        return true;

    }

这里通过验签后,获取支付宝流水号和商户订单号然后去查询这里订单状态,然后更新修改订单状态,从未支付到已支付更新,这里业务里并没有像商城系统的商品发放等发货机制,只需要修改订单状态即可。

3.4 小程序端接口对接及支付调用

js 复制代码
/**
   * 加载收款订单数据,准备开始支付
   */
  loadCollectionOrderData() {
    // https://api.ythpt.junoyi.com
    // http://localhost:7589
    my.request({
      url: `https://api.xxx.xxx.com/public/payment/collection/order/payment-info?collectionNo=${this.data.collectionNo}`,
      method: 'GET',
      success: (res) => {
        this.setData({
          paymentInfo: {
            payeeName: res.data.data.payeeName,
            productName: res.data.data.productName,
            amount: Number(res.data.data.amount).toFixed(2)
          }
        })
      }
      })
  },

这里是小程序支付页面加载逻辑,这里项目是要通过收款单去查,展示商品信息,价格等。

js 复制代码
  /**
   * 支付
   */
  handlePay() {

    // 防止重复点击
    if (this.data.paying){
      console.log('支出处理中,请勿重复点击')
      return 
    }

    this.setData({
      paying: true
    })

    console.log("【调试】:准备支付")

    const no = this.data.collectionNo
    const remark = this.data.remark

    // 获取支付宝授权码
    my.getAuthCode({
      scopes: ['auth_user'],
      success:  (authRes) => {
        // 通过销售单,请求创建支付订单
        my.request({
          url: 'https://api.ythpt.junoyi.com/pay/alipay/jsapi',
          method: 'POST',
          data: {
            collectionNo: no,
            payRemark: remark,
            authCode: authRes.authCode
          },
          success: (res) => {

            if (!res.data.data){
              throw Error('没有获取到支付宝的流水号')
            }

            my.tradePay({
              tradeNO: res.data.data,
              success: (res) => {
                // 支付成功
                this.setData({
                  paying: false
                })
              },


              fail: (err) => {
                console.error("支付失败!",err)
                this.setData({
                  paying: false
                })
              }
            })
          },
          fail: (err) => {
            console.error('创建支付订单失败:',err)
            this.setData({
              paying: false
            })
          }
        })
      }
    })

    console.info("【调试】:支付完成")

  },

这里就是通过收款单号,去查询,然后去调用后端jsapi接口,创建订单,你可以理解成通过商品去查,然后创建订单。

js 复制代码
            my.tradePay({
              tradeNO: res.data.data,
              success: (res) => {
                // 支付成功
                this.setData({
                  paying: false
                })
              },

这里通过后端jsapi接口响应的支付宝流水号,来唤起支付宝收银台,然后完成支付,并且这里不管支付成功与否,直接关闭支付中动画加载效果。

并且后端的jsapi接口 如果用户已经创建了订单,将不会重新创建新的订单,而是可以沿用,如果当前平台支付宝已经创建了订单可以沿用,后续可以修改更新为如果换平台,比如说微信,可以查,可以将其他平台已经创建并且还没支付的订单,更新修改,然后继续完成支付。

四、总结

到这里,本篇关于支付宝 JSAPI 支付的项目准备和前后端代码对接就讲完了。

本文没有从 0 开始创建项目,而是直接基于一个已经存在的 Java 后端项目和支付宝小程序项目进行讲解,重点带大家了解支付宝 JSAPI 支付在实际项目中的完整代码流程。

整个支付流程可以简单总结为:

复制代码
支付宝小程序 → 请求后端创建支付订单 → 后端调用支付宝 SDK 创建交易 → 获取支付宝 trade_no → 小程序调用 my.tradePay() → 唤起支付宝收银台 → 用户完成支付 → 支付宝异步通知后端 → 后端验签 → 更新订单状态。

其中最重要的几个概念,一定要理解清楚:

  • out_trade_no:我们自己系统生成的商户订单号。
  • trade_no:支付宝生成的支付宝交易号,也是小程序调用 my.tradePay() 时需要使用的核心参数。
  • notify_url:支付宝支付完成后,异步通知我们后端的地址。
  • my.tradePay():支付宝小程序中用于唤起支付宝收银台的方法。
  • AlipaySignature.rsaCheckV1():后端处理支付宝回调时,用于验证支付宝通知签名。

本篇主要是把项目和支付代码跑通,并且把整个支付链路串起来。

相关推荐
wangruofeng6 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
脚本自动化开发6 小时前
玩影之刃零,两张四千元级显卡怎么挑
后端
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈9 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技9 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户479492835691510 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
李纲明10 小时前
微信小程序团课临开课才取消或空位没人补:先核取消时限与自动释放,再查候补确认与预扣回滚
微信小程序·devdiy·团课预约·取消时限·空位释放·候补确认·预扣回滚
努力的小雨11 小时前
别再复制日志给AI了!我让Codex自己去观测云查报错
后端
风骏时光牛马12 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng12 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构