SpringBoot 整合支付宝沙箱支付——从接入到回调

📅 2026/7/6 13:16:25 👁️ 阅读次数 📝 编程学习
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:9021000122691234app-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/notifyreturn-url:http://localhost:9090/pay/returngateway-url:https://openapi-sandbox.dl.alipaydev.com/gateway.do

3. 配置类

@Configuration@ConfigurationProperties(prefix="alipay")@DatapublicclassAlipayConfig{privateStringappId;privateStringappPrivateKey;privateStringalipayPublicKey;privateStringnotifyUrl;privateStringreturnUrl;privateStringgatewayUrl;@BeanpublicAlipayClientalipayClient(){returnnewDefaultAlipayClient(gatewayUrl,appId,appPrivateKey,"json","utf-8",alipayPublicKey,"RSA2");}}

三、统一下单接口

@ServicepublicclassAlipayService{@AutowiredprivateAlipayClientalipayClient;@AutowiredprivateAlipayConfigalipayConfig;/** * 创建支付订单,返回支付页面表单 */publicStringcreatePay(StringorderNo,BigDecimalamount,Stringsubject){AlipayTradePagePayRequestrequest=newAlipayTradePagePayRequest();// 同步回调地址(用户支付完成后跳转)request.setReturnUrl(alipayConfig.getReturnUrl());// 异步回调地址(支付宝服务器通知)request.setNotifyUrl(alipayConfig.getNotifyUrl());JSONObjectbiz=newJSONObject();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{AlipayTradePagePayResponseresponse=alipayClient.pageExecute(request);if(response.isSuccess()){returnresponse.getBody();// 返回 HTML 表单}}catch(AlipayApiExceptione){thrownewRuntimeException("支付宝下单失败",e);}returnnull;}}

四、Controller

@RestController@RequestMapping("/pay")publicclassPayController{@AutowiredprivateAlipayServicealipayService;/** * 发起支付 */@PostMapping("/create")publicResultVO<String>createPay(@RequestBodyPayDTOdto){// 生成订单号StringorderNo="ORD"+IdUtil.simpleUUID().toUpperCase();Stringform=alipayService.createPay(orderNo,dto.getAmount(),dto.getSubject());returnResultVO.success(form);}/** * 同步回调(用户支付完成后跳转到此页面) */@GetMapping("/return")publicStringreturnUrl(HttpServletRequestrequest){// 获取支付宝返回的参数Map<String,String>params=getParamsFromRequest(request);try{// 验签AlipaySignature.rsaCheckV1(params,alipayConfig.getAlipayPublicKey(),"utf-8","RSA2");StringorderNo=params.get("out_trade_no");StringtradeNo=params.get("trade_no");return"支付成功!订单号: "+orderNo;}catch(AlipayApiExceptione){return"验签失败";}}/** * 异步回调(支付宝服务器通知,必须返回 success) */@PostMapping("/notify")publicStringnotifyUrl(HttpServletRequestrequest){Map<String,String>params=getParamsFromRequest(request);try{booleansignVerified=AlipaySignature.rsaCheckV1(params,alipayConfig.getAlipayPublicKey(),"utf-8","RSA2");if(signVerified){StringorderNo=params.get("out_trade_no");StringtradeNo=params.get("trade_no");StringtradeStatus=params.get("trade_status");// 支付成功if("TRADE_SUCCESS".equals(tradeStatus)){// 更新订单状态、归还库存等业务逻辑updateOrderStatus(orderNo,tradeNo);}return"success";// 必须返回 success,否则支付宝会重复通知}}catch(AlipayApiExceptione){e.printStackTrace();}return"fail";}privateMap<String,String>getParamsFromRequest(HttpServletRequestrequest){Map<String,String>params=newHashMap<>();Map<String,String[]>requestParams=request.getParameterMap();for(Stringname:requestParams.keySet()){params.put(name,request.getParameter(name));}returnparams;}}

五、秒杀系统集成支付

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

@ServicepublicclassPayService{@AutowiredprivateOrderServiceorderService;@AutowiredprivateProductServiceproductService;/** * 支付成功回调处理 */@TransactionalpublicvoidhandlePaySuccess(StringorderNo,StringtradeNo){// 1. 查询订单SeckillOrderorder=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);}}

六、前端调用

<buttononclick="pay()">支付宝支付</button><script>asyncfunctionpay(){constresp=awaitfetch('/pay/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({amount:99.99,subject:'秒杀商品 - iPhone 15'})});constresult=awaitresp.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换成生产地址,其他代码不变

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。