Spring Boot 集成支付宝沙箱支付
关键词:Spring Boot、支付宝、沙箱、PC 网页支付、Java17、Maven
源码仓库:https://github.com/yourname/spring-alipay-demo(文中附目录结构)
一、为什么选择“沙箱”先跑通?
- 0 成本:支付宝提供永久免费的沙箱环境,无需企业认证。
- 全链路:从下单 → 支付 → 异步通知 → 同步回调,和线上流程 100% 一致。
- 好调试:沙箱支持“买家余额”固定 100,000,可随意“付款”测试。
二、5 步完成接入
| 步骤 | 内容 | 预计耗时 |
|---|---|---|
| ① | 注册沙箱账号,获取 3 秘钥 | 2 min |
| ② | 新建 Spring Boot 项目,引入 SDK | 2 min |
| ③ | 写配置文件 & 启动类 | 2 min |
| ④ | 3 个接口:下单 / 同步回调 / 异步通知 | 3 min |
| ⑤ | 启动 & 体验支付 | 1 min |
三、详细实战
3.1 沙箱账号 & 密钥
登录 支付宝开放平台 → 开发者中心 → 沙箱环境
记录下面 3 个值,后面 application.yml 会用到:
- APP_ID:
9021000132681525(举例) - 商户私钥:用官方工具生成,保存为
appPrivateKey.txt - 支付宝公钥:平台自动给出,复制即可
3.2 项目骨架
使用 Spring Initializr 勾选 Spring Web + Java17,得到:
spring-alipay
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ └─ com.example.alipay
│ │ │ ├─ SpringAlipayApplication.java
│ │ │ ├─ controller
│ │ │ ├─ service
│ │ │ └─ config
│ │ └─ resources
│ │ └─ application.yml
└─ pom.xml3.3 Maven 依赖(完整 pom)
xml
<project ...>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-alipay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 支付宝官方 SDK(推荐组合) -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.110.ALL</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
</project>3.4 application.yml
yaml
alipay:
appId: 9021000132681525
appPrivateKey: MIIEvQIBADANB... # 一行私钥,去掉头尾和换行
alipayPublicKey: MIIBIjANBgkqhk... # 一行公钥
notifyUrl: http://localhost:8080/notify
returnUrl: http://localhost:8080/callback
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
charset: utf-8
signType: RSA23.5 启动类
java
@SpringBootApplication
public class SpringAlipayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAlipayApplication.class, args);
}
}3.6 Service 层
java
@Service
public class AlipayService {
@Value("${alipay.appId}") private String appId;
@Value("${alipay.appPrivateKey}") private String appPrivateKey;
@Value("${alipay.alipayPublicKey}") private String alipayPublicKey;
@Value("${alipay.notifyUrl}") private String notifyUrl;
@Value("${alipay.returnUrl}") private String returnUrl;
@Value("${alipay.gatewayUrl}") private String gatewayUrl;
@Value("${alipay.charset}") private String charset;
@Value("${alipay.signType}") private String signType;
public String pay(String outTradeNo, String subject, String totalAmount) throws AlipayApiException {
AlipayClient client = new DefaultAlipayClient(
gatewayUrl, appId, appPrivateKey, "json", charset, alipayPublicKey, signType);
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
request.setBizContent("{" +
"\"out_trade_no\":\"" + outTradeNo + "\"," +
"\"total_amount\":\"" + totalAmount + "\"," +
"\"subject\":\"" + subject + "\"," +
"\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
return client.pageExecute(request).getBody(); // 返回自动提交的 form 表单
}
}3.7 Controller 层
① 下单接口(GET)
java
@RestController
@RequiredArgsConstructor
public class AlipayController {
private final AlipayService alipayService;
@GetMapping("/pay")
public void pay(@RequestParam String outTradeNo,
@RequestParam String subject,
@RequestParam String totalAmount,
HttpServletResponse response) throws Exception {
String form = alipayService.pay(outTradeNo, subject, totalAmount);
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(form);
response.getWriter().flush();
}
}浏览器访问:http://localhost:8080/pay?outTradeNo=202601031200&subject=VIP1个月&totalAmount=0.01
② 同步回调(return_url)
java
@RestController
public class CallbackController {
@GetMapping("/callback")
public String callback() {
return "支付成功(同步)";
}
}③ 异步通知(notify_url)
java
@RestController
public class NotifyController {
@PostMapping("/notify")
public String notify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));
// 使用 EasySDK 验签
if (Factory.Payment.Common().verifyNotify(params)) {
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
// TODO: 更新订单、发货、加积分...
System.out.println("---- 异步通知 ----");
System.out.println("商户订单号:" + params.get("out_trade_no"));
System.out.println("支付宝交易号:" + params.get("trade_no"));
System.out.println("交易金额:" + params.get("total_amount"));
}
return "success"; // 必须返回 success,否则支付宝会重发 25h
}
return "fail";
}
}四、启动 & 测试
- 启动 Spring Boot,控制台出现
Started SpringAlipayApplication。 - 浏览器访问第 3.7 节的
/pay链接,页面自动跳转到支付宝沙箱收银台。 - 用沙箱买家账号(平台给出)登录 → 输入支付密码 → 支付成功。
- 观察 IDEA 控制台:
- 同步回调:
支付成功(同步) - 异步通知:打印订单号、金额等信息。
- 同步回调:
五、常见坑汇总
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 验签失败 | 公钥 / 私钥弄反;换行符未去掉 | 复制成一行,私钥=你自己的,公钥=支付宝的 |
| notify 不进来 | 内网穿透失效;返回不是 success | 用 https://natapp.cn 或 https://localhost.run,确保外网能 POST |
| 沙箱提示“参数异常” | 金额格式带了人民币符号 | totalAmount 传纯数字,如 0.01 |
| 同步回调没数据 | return_url 只负责展示,业务写 notify | ✅ 正确 |
六、拓展:如何切到线上?
- 替换
gatewayUrl为https://openapi.alipay.com/gateway.do - 把
appId、appPrivateKey、alipayPublicKey换成线上值。 notifyUrl、returnUrl换成 https 可外网访问的域名。- 上线前务必打开“签名验证”和“AES 密钥”增强安全。