java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot整合支付宝沙箱支付

SpringBoot整合支付宝沙箱支付的完整案例

作者:张老师技术栈

支付是电商系统的核心功能,接入支付宝需要企业资质,但支付宝提供了沙箱环境——模拟真实支付流程,不需要真实商户号,适合开发测试和学习,本文给大家介绍了SpringBoot整合支付宝沙箱支付的完整案例,需要的朋友可以参考下

支付是电商系统的核心功能。接入支付宝需要企业资质,但支付宝提供了沙箱环境——模拟真实支付流程,不需要真实商户号,适合开发测试和学习。

一、沙箱环境准备

1. 登录开放平台

打开 open.alipay.com → 控制台 → 沙箱应用

2. 获取配置参数

APP_ID: 9021000122691234(沙箱专用)
应用私钥: 生成的 RSA2 私钥
支付宝公钥: 生成的 RSA2 公钥
服务器异步通知地址: http://你的地址/pay/notify

3. 沙箱账号

沙箱环境提供测试买家账号:

买家账号: rixwby5818@sandbox.com
登录密码: 111111
支付密码: 111111

二、SpringBoot 集成

1. 引入依赖

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

2. 配置

alipay:
  app-id: 9021000122691234
  app-private-key: |
    -----BEGIN RSA PRIVATE KEY-----
    MIIEowIBAAKCAQ...
    (你的应用私钥,不要写在配置里!建议用Jasypt加密)
    -----END RSA PRIVATE KEY-----
  alipay-public-key: |
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgk...
    (支付宝公钥)
    -----END PUBLIC KEY-----
  notify-url: http://你的公网IP/pay/notify
  return-url: http://localhost:9090/pay/return
  gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do

3. 配置类

@Configuration
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayConfig {
    private String appId;
    private String appPrivateKey;
    private String alipayPublicKey;
    private String notifyUrl;
    private String returnUrl;
    private String gatewayUrl;

    @Bean
    public AlipayClient alipayClient() {
        return new DefaultAlipayClient(
            gatewayUrl, appId, appPrivateKey,
            "json", "utf-8", alipayPublicKey, "RSA2"
        );
    }
}

三、统一下单接口

@Service
public class AlipayService {

    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private AlipayConfig alipayConfig;

    /**
     * 创建支付订单,返回支付页面表单
     */
    public String createPay(String orderNo, BigDecimal amount, String subject) {
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

        // 同步回调地址(用户支付完成后跳转)
        request.setReturnUrl(alipayConfig.getReturnUrl());
        // 异步回调地址(支付宝服务器通知)
        request.setNotifyUrl(alipayConfig.getNotifyUrl());

        JSONObject biz = new JSONObject();
        biz.put("out_trade_no", orderNo);       // 商户订单号
        biz.put("total_amount", amount.toString()); // 支付金额
        biz.put("subject", subject);            // 订单标题
        biz.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 电脑网站支付

        request.setBizContent(biz.toJSONString());

        try {
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
            if (response.isSuccess()) {
                return response.getBody();  // 返回 HTML 表单
            }
        } catch (AlipayApiException e) {
            throw new RuntimeException("支付宝下单失败", e);
        }
        return null;
    }
}

四、Controller

@RestController
@RequestMapping("/pay")
public class PayController {

    @Autowired
    private AlipayService alipayService;

    /**
     * 发起支付
     */
    @PostMapping("/create")
    public ResultVO<String> createPay(@RequestBody PayDTO dto) {
        // 生成订单号
        String orderNo = "ORD" + IdUtil.simpleUUID().toUpperCase();
        String form = alipayService.createPay(orderNo, dto.getAmount(), dto.getSubject());
        return ResultVO.success(form);
    }

    /**
     * 同步回调(用户支付完成后跳转到此页面)
     */
    @GetMapping("/return")
    public String returnUrl(HttpServletRequest request) {
        // 获取支付宝返回的参数
        Map<String, String> params = getParamsFromRequest(request);
        try {
            // 验签
            AlipaySignature.rsaCheckV1(
                params, alipayConfig.getAlipayPublicKey(), "utf-8", "RSA2");
            String orderNo = params.get("out_trade_no");
            String tradeNo = params.get("trade_no");
            return "支付成功!订单号: " + orderNo;
        } catch (AlipayApiException e) {
            return "验签失败";
        }
    }

    /**
     * 异步回调(支付宝服务器通知,必须返回 success)
     */
    @PostMapping("/notify")
    public String notifyUrl(HttpServletRequest request) {
        Map<String, String> params = getParamsFromRequest(request);
        try {
            boolean signVerified = AlipaySignature.rsaCheckV1(
                params, alipayConfig.getAlipayPublicKey(), "utf-8", "RSA2");

            if (signVerified) {
                String orderNo = params.get("out_trade_no");
                String tradeNo = params.get("trade_no");
                String tradeStatus = params.get("trade_status");

                // 支付成功
                if ("TRADE_SUCCESS".equals(tradeStatus)) {
                    // 更新订单状态、归还库存等业务逻辑
                    updateOrderStatus(orderNo, tradeNo);
                }
                return "success";  // 必须返回 success,否则支付宝会重复通知
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return "fail";
    }

    private Map<String, String> getParamsFromRequest(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            params.put(name, request.getParameter(name));
        }
        return params;
    }
}

五、秒杀系统集成支付

支付完成后更新订单状态并归还库存:

@Service
public class PayService {

    @Autowired
    private OrderService orderService;

    @Autowired
    private ProductService productService;

    /**
     * 支付成功回调处理
     */
    @Transactional
    public void handlePaySuccess(String orderNo, String tradeNo) {
        // 1. 查询订单
        SeckillOrder order = orderService.lambdaQuery()
            .eq(SeckillOrder::getOrderNo, orderNo)
            .one();

        if (order == null || order.getStatus() != 0) {
            return;  // 订单不存在或已处理
        }

        // 2. 更新订单状态为已支付
        order.setStatus(1);
        order.setPayTime(LocalDateTime.now());
        order.setTradeNo(tradeNo);
        orderService.updateById(order);

        // 3. 记录支付日志
        log.info("支付成功: 订单={}, 支付宝交易号={}", orderNo, tradeNo);
    }
}

六、前端调用

<button onclick="pay()">支付宝支付</button>
<script>
async function pay() {
    const resp = await fetch('/pay/create', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            amount: 99.99,
            subject: '秒杀商品 - iPhone 15'
        })
    });
    const result = await resp.json();
    if (result.code === 200) {
        // 把返回的 HTML 表单写到页面并自动提交
        document.body.innerHTML = result.data;
        document.forms[0].submit();  // 自动跳转到支付宝
    }
}
</script>

七、注意事项

  1. 异步通知必须返回 “success”(全小写),否则支付宝会重复通知 8 次
  2. 验签必不可少,防止伪造回调
  3. 幂等处理——支付宝通知可能重复,判断订单状态防止重复更新
  4. 沙箱金额随便填,不会真实扣款
  5. 上生产环境只需把 gateway-url 换成生产地址,其他代码不变

到此这篇关于SpringBoot整合支付宝沙箱支付的完整案例的文章就介绍到这了,更多相关SpringBoot整合支付宝沙箱支付内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文