Spring boot 项目接入支付宝沙箱支付

一、准备工作 

1、注册开发者账号

先去支付宝开发平台注册账号,登录后点击控制台

进入控制台后,翻到最下面,就能看见沙箱支付点击进去就能看见我们所需要的基本信息

2、密钥相关

可以直接在右上角搜索密钥工具,然后点击进去,下载自己系统对应的软件

打开后生成密钥

然后就可以看见自己的密钥了

应用私钥要记住

如果使用自定义密钥

没使用前点击设置并查看

输入自己密钥就可以生成了

3、引入依赖

      <!--支付宝支付-->
      <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-sdk-java</artifactId>
        <version>4.40.298.ALL</version>
      </dependency>

然后在自己的.yml中写入基本配置

alipay:
  app-id: 9021000150615149
  private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyprnmNu6MCnBP757Hwo2NFBSG3/Mg/d+x1eXe7IxkxZxHoexhiVtev9NiI0vX+jadvLBrR8Zdfz0IP4li9pOVMEqPTepVapnDOlDyi3s5Hy7oSKH8jcG90/hfHIayLmZSKGEipTdm8I/sC9ST4NPiFC/EcdfCObpj/vzm2pGU/wjTttXHjVHZWeRlMqLLs3uV8DuiIOzzrJAiox7dt9tvJriTwPSLvcFxjLAKaZ0iL9llPWfQbUsb8M/jBRpVv1wOIVmXF3Ef34cTEn4L7ppoixq0Z8Hqt74THlBtwm+Z/c85T4LDSAA0+bjjqsz1c6nrKgMru5/+CrXcrC+h4ENDAgMBAAECggEABWI+4fAv2iqKXe5Ts6NqTqEXSUVAd3MPJbXTepdYV8UYw5PeosXX6hAP17pGk9ingrNoAaOlF7Y46aG4izHxGqLc7FqlwDM5NQfx3o1ThlIpQLjHoOvItebIal1atc3uKXWsD7Y46DJBtBRbMKWq3PpMwjfID8NbmZ3akz5s+rdz5ZLmWpOAVUmPgL9UG3fFool4mtBvm950ScxG9S3muHgRkEOlU8yRd/eh7mxUi8u3r4I+DaTL8Nb9xTFHBqUPHnqEo2d17VSrBmO2Wcz7yWAniNxj3h+tusxATwuFAm+ix42rcsjzcKMnAkPLyqN+zE/wf+mlIzOVnmyQMpCCEQKBgQDpKPA3ITD4/TzNHMNBI5I4cAPbaPzqdU/za5RWASThAsrXlDiM/1lomuENoFWfS8+AWI/FzTddTmDRbehvKLnEm7Vk0FNPp7KwlvW+RijvchliV+GKcKrUI5Kot+SMSxzYP9n7653gVAeDGnIc3/mQCIXOoO4g1XosdDI09mS+OwKBgQDEJtlLXzfOUrrdsQ97N+T5vjahQW5bX6Iypyu4usLzYDOxQTkhPI83dOx1K3AGop+rdWu2gd59Nck1APdPSQJAiB2FUCaZZSxnIjCdW0+B9MRMxk3d+OC7eEwgm5N6lKkSCmGQonfurSafX1jTiyKNA1b51+hzgzSHl6J4QVyWmQKBgEM+aFEuBGa6+k8ZjYJ61M/sFmCwHcksvjsw+p33RICUgpW8DLc1ab7OdDuu0yLXBWg5onVkoyQI18luzktIzpLSaXVC09Q0REfDseoHaATuvj1rpQN9TPdTrxdzublwbEg92gtav7ST53q7JNAJFHuiyZx3M1ZK2yp49IXrzdBDAoGBAIfTMuJ7Q8RuCxGaGtNDCB5bta6CBW3mUgVrNt5LDNXERuc1995o9e772wTjmEYv9AqhW33cffkL9pDqY1YWgyW2W2LF9oR/x3h+91TvNqYRnKBhLOhI/y0xht2JfV0DdfOVFqw1pIC0i6sONg7+KGWm62JEPKVINGaz/H/zGq75AoGBAOFlishy0x80RD+AIwwtc+Rw2bd+Puh3oWVp4qXxuXD67Pzr7llqRIZDMv1k9VRUIqv3LZzUY1UWSxNBRn3prr+EZJjmveSP0geEiCKdsiAJwdFyflolLWJxh1rm7M3pU0ma83NPiHv+mfLQ9lm2GuJ+1eo6SuoqpdLzAKlGezy7
  public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmbRz7Qy7E2l0VMvbdxIuZifHA+FPgiX+lsvWJkJdRzD0fgnnKucQ1ieFbXbuFuhkOjhQWzpDdk9FqiGI3uO9VTlR4NH2yVkMGrEOk7x11LgUxWbv92UV/7X7lGnX14Y4D7tvYINeYs5gmTuNEwbBFIodVVjb85moYtrgMm3xt2try77U40lHBLTPeTmKW3nDCxRBGMiijtacXn39Ks7a7vFrOXqUn2YkXjZuPNbIhfqh5qG5dAcKhm9ThYJEvpPpcCWFI0Ye0vgQxaeR6zWUSRSuco7S+EUndzTcrGXitxQ1We2Vj2gDdiK825ni5xNRj0BoEjruA+3ZJ87Td9xm4wIDAQAB
  notify-url: http://q6d8e65c.natappfree.cc/app/alipay/notify
  return-url: http://localhost:5050/pay/success
  gateway-url: https://openapi-sandbox.alipay.com/gateway.do
app-id:就是基本信息中的APPID
private-key:自己的私钥
public-key:支付宝公钥
notify-url: http://se7658ad.natappfree.cc/app/alipay/notify 支付宝服务器主动通知商户支付结果的异步回调地址
return-url: http://localhost:5050/pay/success  支付完成后用户浏览器同步跳转的页面地址
gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do  沙箱接口地址

4、内网穿透工具

  因为支付宝是外网,所以如果想要访问我们的项目就必须需要内网穿透技术

直接访问natapp官网就可以了 NATAPP-内网穿透 基于ngrok的国内高速内网映射工具,进入后先注册登录一个账户,然后购买一个免费隧道就可以了

根据自己的端口号输入端口号购买

然后记住这个authtoken

最后根据自己的系统下载客户端

下载好后不需要点击exe文件使用,只需要新建一个.bat文件

输入natapp.exe -authtoken=自己的authtoken号,然后保存双击运行就可以实现

弹出这种画面

就代表运行成功了

然后就可以进行后端代码的编写了

二、后端代码实现

1、支付宝支付的具体实现类AlipayAdapter

package com.old.senior.payment;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.old.senior.entity.Order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @TODO
 * @Author yang
 * 2025/7/6
 */
@Component("alipayAdapter")
public class AlipayAdapter implements PaymentAdapter {

    @Value("${alipay.app-id}")
    private String appId;
    @Value("${alipay.private-key}")
    private String privateKey;
    @Value("${alipay.public-key}")
    private String alipayPublicKey;
    @Value("${alipay.notify-url}")
    private String notifyUrl;
    @Value("${alipay.return-url}")
    private String returnUrl;
    @Value("${alipay.gateway-url}")
    private String gatewayUrl;

    @Override
    public PaymentResult pay(Order order) {
        PaymentResult result = new PaymentResult();
        try {
            AlipayClient alipayClient = new DefaultAlipayClient(
                    gatewayUrl, appId, privateKey, "json", "UTF-8", alipayPublicKey, "RSA2");

            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            request.setNotifyUrl(notifyUrl);
            request.setReturnUrl(returnUrl);

            String uniqueOutTradeNo = order.getNumber() + "-" + System.currentTimeMillis();
            
            String bizContent = "{" +
                    "\"out_trade_no\":\"" + uniqueOutTradeNo + "\"," +
                    "\"total_amount\":\"" + order.getPrice() + "\"," +
                    "\"subject\":\"Elderly Care Service Order\"," +
                    "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"" +
                    "}";

            // 在发送前,将确切的 bizContent 打印到控制台
            System.out.println("======================================================================");
            System.out.println("ALIPAY DEBUG: The exact bizContent string being sent to Alipay SDK is:");
            System.out.println(bizContent);
            System.out.println("======================================================================");

            request.setBizContent(bizContent);
            // ======================= 诊断结束 =======================

            // 生成支付表单
            String form = alipayClient.pageExecute(request).getBody();

            result.setSuccess(true);
            result.setMessage("OK");
            result.setThirdPartyOrderId(order.getNumber());
            result.setForm(form);
        } catch (AlipayApiException e) {
            result.setSuccess(false);
            result.setMessage("支付宝支付异常: " + e.getErrMsg());
        }
        return result;
    }

    @Override
    public PaymentResult refund(Order order) {
        // 退款实现同理,调用alipay的退款接口
        PaymentResult result = new PaymentResult();
        result.setSuccess(false);
        result.setMessage("未实现");
        return result;
    }
}

2、支付宝通知控制器(AlipayNotifyController)、

AlipayNotifyController是处理支付宝异步支付通知的关键组件,它负责接收支付宝服务器在用户完成支付后发送的异步通知,并根据通知内容更新订单状态。

package com.old.senior.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.old.senior.entity.Order;
import com.old.senior.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
public class AlipayNotifyController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private StringRedisTemplate redisTemplate;



    @Value("${alipay.public-key}")
    private String alipayPublicKey;

    @PostMapping("/app/alipay/notify")
    public String alipayNotify(HttpServletRequest request) throws IOException {
        System.out.println("支付宝回调收到!");
        // 1. 获取支付宝POST过来的参数
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            String[] values = requestParams.get(name);
            StringBuilder valueStr = new StringBuilder();
            for (int i = 0; i < values.length; i++) {
                valueStr.append(i == values.length - 1 ? values[i] : values[i] + ",");
            }
            params.put(name, valueStr.toString());
        }

        // 2. 验签
        boolean signVerified = false;
        try {
            signVerified = AlipaySignature.rsaCheckV1(
                    params,
                    alipayPublicKey,
                    "UTF-8",
                    "RSA2"
            );
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        if (!signVerified) {
            // 验签失败
            return "fail";
        }

        // 3. 判断支付状态,更新订单
        String outTradeNoWithTimestamp = params.get("out_trade_no"); // 从支付宝回调中获取带时间戳的商户订单号
        String tradeStatus = params.get("trade_status");

        // 从支付宝的订单号中解析出我们系统的原始订单号
        String originalOrderNumber = outTradeNoWithTimestamp;
        if (outTradeNoWithTimestamp != null && outTradeNoWithTimestamp.contains("-")) {
            originalOrderNumber = outTradeNoWithTimestamp.substring(0, outTradeNoWithTimestamp.lastIndexOf('-'));
        }

        // 查询数据库用的原始订单号
        Order order = orderService.getOrderByNumber(originalOrderNumber);
        if (order == null) {
            System.out.println("支付宝回调:根据订单号 " + originalOrderNumber + " 未找到订单。");
            return "fail"; // 订单不存在,直接返回失败
        }

        // Redis锁的key必须使用数据库中的主键ID,才能和支付接口加的锁对应上
        String redisKey = "order:pay:" + order.getId();

        try {
            if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
                // 支付成功
                if (order.getStatus() != 1) { // 检查订单状态,防止重复处理
                    order.setStatus(1); // 1=已支付
                    orderService.updateById(order);
                    System.out.println("支付宝回调:订单 " + originalOrderNumber + " 状态更新为已支付。");
                }

                // 不论订单是否已处理,都将Redis锁更新为"paid",确保幂等性
                redisTemplate.opsForValue().set(redisKey, "paid", 60, TimeUnit.MINUTES); // 延长锁时间

            } else if ("TRADE_CLOSED".equals(tradeStatus)) {
                // 交易关闭(未付款超时关闭,或支付完成后全额退款)
                // 支付失败或关闭,删除Redis锁,允许用户重新发起支付
                redisTemplate.delete(redisKey);
                System.out.println("支付宝回调:订单 " + originalOrderNumber + " 交易关闭,释放锁。");
            }
            // 其他状态(如WAIT_BUYER_PAY)可以暂时不处理锁,等待最终状态

        } catch (Exception e) {
            e.printStackTrace();
            // 异常情况,返回fail,让支付宝重试
            return "fail";
        }

        return "success";
    }
}

3、PaymentAdapterFactory工厂类

为了方便后续多种支付方式的结合可以使用一个工厂类创建具体的支付适配器实例,这样就可以根据客户端选择的支付类型返回对应的适配器。代码如下:

package com.old.senior.payment;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @TODO
 * @Author yang
 * 2025/7/6
 */
@Component
public class PaymentAdapterFactory {
    @Autowired
    private AlipayAdapter alipayAdapter;
    @Autowired
    private WechatAdapter wechatAdapter;
    @Autowired
    private UnionPayAdapter unionPayAdapter;

    public PaymentAdapter getAdapter(String type) {
        switch (type) {
            case "alipay": return alipayAdapter;
            case "wechat": return wechatAdapter;
            case "unionpay": return unionPayAdapter;
            default: throw new IllegalArgumentException("不支持的支付方式");
        }
    }
}

4、PaymentAdapter接口

这个接口定义了支付系统的标准接口:pay和refund,所有支付方式都遵循此接口,确保代码一致性

package com.old.senior.payment;

import com.old.senior.entity.Order;

public interface PaymentAdapter {
    PaymentResult pay(Order order);
    PaymentResult refund(Order order);
}

5、PaymentResult支付返回的统一结果

PaymentResult是统一的支付结果返回对象,封装支付结果信息,包括成功标志、消息和表单等

package com.old.senior.payment;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @TODO
 * @Author yang
 * 2025/7/6
 */
@Data
@NoArgsConstructor
@ApiModel("支付结果集")
public class PaymentResult {
    private boolean success;
    private String message;
    private String thirdPartyOrderId;
    private String form; // 支付宝支付表单

}

6、订单支付接口

实现了 支付功能,并且利用Redis防止了订单重复提交。

@ApiOperation("订单支付")
@PostMapping("/{id}/pay")
public Result pay(@PathVariable Integer id, @RequestParam String payType) {
    String redisKey = "order:pay:" + id;

    // 1. 尝试加锁,这次的过期时间可以设置得长一些,比如30分钟,覆盖典型的支付窗口期
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(redisKey, "paying", 30, TimeUnit.MINUTES);

    if (lock == null || !lock) {
        // 如果锁已存在,检查锁的值。可以进一步区分是"paying"还是"paid"
        String lockValue = redisTemplate.opsForValue().get(redisKey);
        if ("paid".equals(lockValue)) {
            return new Result(CodeConstant.FAIL, "订单已支付成功,请勿重复支付", null);
        }
        return new Result(CodeConstant.FAIL, "订单正在支付中,请勿重复操作", null);
    }

    try {
        // 2. 查询订单 (这里可以加一步检查数据库状态,双重保险)
        Order order = orderService.getById(id);
        if (order == null) {
            redisTemplate.delete(redisKey); // 订单不存在,释放锁
            return new Result(CodeConstant.FAIL, "订单不存在", null);
        }
        if (order.getStatus() == 1) {
            // 如果数据库已是支付状态,更新Redis锁并返回
            redisTemplate.opsForValue().set(redisKey, "paid", 30, TimeUnit.MINUTES);
            return new Result(CodeConstant.FAIL, "订单已支付,请勿重复操作", null);
        }

        // 3. 发起支付逻辑
        PaymentResult paymentResult = paymentAdapterFactory.getAdapter(payType).pay(order);
        if (paymentResult.isSuccess()) {
            return new Result(CodeConstant.SUCCESS, "支付请求成功", paymentResult.getForm());
        } else {
            // 如果支付网关返回失败,要释放锁,允许用户重试
            redisTemplate.delete(redisKey);
            return new Result(CodeConstant.FAIL, paymentResult.getMessage(), null);
        }
    } catch (Exception e) {
        // 如果发生异常,也要释放锁,允许用户重试
        redisTemplate.delete(redisKey);
        e.printStackTrace();
        return new Result(CodeConstant.FAIL, "支付处理异常,请重试", null);
    }
    // 如果成功发起了支付,锁会一直保留,直到有异步通知
}

### 整合支付宝沙箱支付Spring Boot项目的教程 #### 一、准备工作 为了成功地将支付宝沙箱支付集成到Spring Boot项目中,需先完成一些必要的准备事项。这包括但不限于注册并登录支付宝开放平台账号,创建应用以获得`APPID`以及下载相应的SDK或依赖库文件。 对于所需使用的jar包或其他资源文件的获取方法,在相关资料中有提及可参照特定指南来操作[^1]。值得注意的是,这里强调了使用主账号来进行相应设置的重要性,并指出针对每一个应用程序仅能选定唯一的签名算法(即RSA私钥或是证书形式)用于安全校验过程之中[^2]。 #### 二、配置沙箱环境 进入支付宝开发者中心后,按照指引完成沙箱环境下的各项设定工作: - 设定好模拟交易的具体参数如金额大小等信息; - 获取公钥/私钥对以及其他必要凭证以便后续编码实现调用API接口功能时能够顺利完成身份认证步骤; 以上这些都可以在官方文档里找到详细的说明指导[^3]。 #### 三、编写代码逻辑 下面给出一段简单的Java代码片段作为示例展示如何利用Spring框架快速搭建起支持支付宝沙箱模式下网页版付款请求处理的服务端程序结构。此部分主要涉及到了几个核心类及其作用域定义还有服务层内的业务流程控制等内容。 ```java // 定义实体对象映射表单提交过来的数据模型 public class AlipayTradePagePayRequest { private String out_trade_no; private String total_amount; private String subject; // getter and setter methods... } // 自动装配AlipayClient Bean实例化组件供其他地方注入使用 @Configuration public class AlipayConfig { @Value("${alipay.appId}") private String appId; @Bean(name = "alipayClient") public DefaultAlipayClient alipayClient(@Value("${alipay.gatewayUrl}") String gatewayUrl, @Value("${alipay.privateKey}") String privateKey, @Value("${alipay.publicKey}") String publicKey){ return new DefaultAlipayClient(gatewayUrl,appId,"json","UTF-8",privateKey,publicKey,"RSA2"); } } ``` 接着就是具体发起支付请求的部分了: ```java @Service @Slf4j public class PaymentService { @Autowired private DefaultAlipayClient alipayClient; /** * 发送页面支付请求给支付宝服务器. */ public String pagePay(AlipayTradePagePayModel model)throws Exception{ // 构建API请求参数列表 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); JSONObject bizContentJsonObj=new JSONObject(); bizContentJsonObj.put("out_trade_no",model.getOutTradeNo()); bizContentJsonObj.put("total_amount",model.getTotalAmount()); bizContentJsonObj.put("subject",model.getSubject()); request.setBizContent(bizContentJsonObj.toJSONString()); try { // 执行远程HTTP POST调用返回重定向URL链接字符串 return alipayClient.pageExecute(request).getBody(); } catch (Exception e) { log.error(e.getMessage(),e); throw new RuntimeException("Failed to create payment link."); } } } ``` 最后一步则是控制器层面负责接收前端传来的数据并通过上述封装好的service层函数去实际执行具体的业务动作并向客户端反馈最终的结果状态码或者是跳转地址等等有用的信息。 ```java @RestController @RequestMapping("/api/v1/payments") public class PaymentController { @Autowired private PaymentService paymentService; @PostMapping("/create-payment-link") public ResponseEntity<String> createPaymentLink(@RequestBody AlipayTradePagePayRequest requestBody){ try { String redirectUrl=paymentService.pagePay(new AlipayTradePagePayModel( requestBody.getOut_trade_no(), requestBody.getTotal_amount(), requestBody.getSubject() )); return ResponseEntity.ok().body(redirectUrl); }catch(Exception ex){ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } } ``` 通过以上的介绍可以看出整个接入过程中涉及到的知识点还是比较多样的,不仅限于编程技术本身还包括了很多关于网络协议方面的常识性理解。希望这段描述可以帮助读者更好地理解和掌握这一知识点[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值