準備工作
- 企業支付寶賬號(PID)+ 應用(AppID)
- 配置密鑰:商戶私鑰(PKCS8)、商戶公鑰、支付寶公鑰
- 簽約「當面付」功能,開發期使用沙箱環境
依賴引入
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>2023xxxx</version>
</dependency>
配置文件 alipay.properties(沙箱示例)
open_api_domain=https://openapi.alipaydev.com/gateway.do
pid=2088102172329883
appid=2016082000300485
private_key=MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAMKXZrFR...
public_key=MIGfMA0GCSqGSIbDQEBAQUAA4GNADCBiQKBgQDCl2axUfq572IAbPas9nBNZgkiAUXmqmp...
alipay_public_key=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB
核心 Controller:統一下單
@PostMapping("/pay/alipay")
public Map<String, String> alipay(@RequestParam String amount, @RequestParam int userId) {
String outTradeNo = "ORDER" + System.currentTimeMillis() + (long)(Math.random()*1e7);
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject("商品付款")
.setTotalAmount(amount)
.setOutTradeNo(outTradeNo)
.setStoreId("2088102172329883")
.setTimeoutExpress("120m")
.setNotifyUrl("https://your.domain/pay/notify");
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
Map<String, String> res = new HashMap<>();
switch (result.getTradeStatus()) {
case SUCCESS:
String qrCode = result.getResponse().getQrCode();
// 保存訂單到 DB
orderService.save(new Order(userId, outTradeNo, amount));
res.put("status", "true");
res.put("qrcode", qrCode);
res.put("outtradeno", outTradeNo);
break;
case FAILED: case UNKNOWN: default:
res.put("status", "false");
res.put("msg", "系統異常,請稍後再試");
}
return res;
}
異步通知處理
@PostMapping("/pay/notify")
public String notify(HttpServletRequest req) {
Map<String, String> params = getParams(req);
if (!AlipaySignature.rsaCheckV1(params, Configs.getAlipayPublicKey(), "UTF-8")) {
return "failed";
}
String outTradeNo = params.get("out_trade_no");
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order == null || order.getAmount() != Double.parseDouble(params.get("total_amount"))) {
return "failed";
}
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
orderService.updateStatus(outTradeNo, Order.SUCCESS);
}
// TODO: 推送支付成功通知給前端(WebSocket/輪詢)
return "success";
}
常見坑點
- 二維碼需用 ZXing 生成圖片,不可直接用瀏覽器打開支付寶返回的 QR 碼串
- 沙箱環境必須用沙箱版支付寶 APP 掃碼
- notify_url 必須可被公網訪問,內網測試用 ngrok/frp 穿透
- 權限不足通常是未簽約或應用未上線,開發期用沙箱應用