接入准備
在整合之前,需要你已經注冊成為支付寶開發者。我在 jsp對接支付寶支付接口,實現網站在線支付 這篇文章中有寫到接入流程,可供參考。
支付流程
在整合之前,先來看一下支付寶的支付流程,如下圖所示:

調用順序如下:
-
商戶系統請求支付寶接口 alipay.trade.page.pay,支付寶對商戶請求參數進行校驗,而后重新定向至用戶登錄頁面。
-
用戶確認支付后,支付寶通過 get 請求 returnUrl(商戶入參傳入),返回同步返回參數。
-
交易成功后,支付寶通過 post 請求 notifyUrl(商戶入參傳入),返回異步通知參數。
-
若由於網絡等問題異步通知沒有到達,商戶可自行調用交易查詢接口 alipay.trade.query 進行查詢,根據查詢接口獲取交易以及支付信息(商戶也可以直接調用查詢接口,不需要依賴異步通知)。
支付結果有兩種通知方式:
- 同步通知
簡單來講,同步通知就是通知給用戶的。同步通知將支付結果通過界面的方式通知給用戶,如下圖所示的就是同步通知:

- 異步通知
異步通知是將支付結果通知給服務器,在我們掃碼支付時,支付寶服務器會每隔一段時間調用這個異步通知接口,直到我們支付完成。
所以,由於同步返回的不可靠性,支付結果必須以異步通知或查詢接口返回為准,不能依賴同步跳轉。
請見官方文檔:支付寶異步通知
了解了這些,接下來就可以整合支付接口了。
Spring Boot 整合
- 添加 Maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 支付寶 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.7.26.ALL</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 配置
import java.io.FileWriter;
import java.io.IOException;
public class AlipayConfig {
// 應用ID,您的APPID,收款賬號既是您的APPID對應支付寶賬號
public static String app_id = "";
// 商戶私鑰,您的PKCS8格式RSA2私鑰
public static String merchant_private_key = "";
// 支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。
public static String alipay_public_key = "";
// 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
public static String notify_url = "";
// 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
// 即支付成功之后,需要跳轉到的頁面,一般為網站的首頁
public static String return_url = "http://www.baidu.com";
// 簽名方式
public static String sign_type = "RSA2";
// 字符編碼格式
public static String charset = "utf-8";
// 支付寶網關
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
// 日志存儲路徑
public static String log_path = "C:\\";
/**
* 寫日志,方便測試(看網站需求,也可以改成把記錄存入數據庫)
* @param sWord 要寫入日志里的文本內容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
也可以將這些配置信息放入 application.properties 配置文件中,通過
@ConfigurationProperties(prefix = "xxx")或者@Value的方式進行賦值。
- 去付款界面
index.html
<form action="/pay/topay">
<button type="submit">付款</button>
</form>
- controller
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private AlipayService alipayService;
@GetMapping("/hello")
public String hello() {
return "index";
}
/**
* 跳轉到支付界面
* @return
* @throws Exception
*/
@GetMapping("/topay")
@ResponseBody
public String pay() throws Exception {
String form = alipayService.toPay(String.valueOf(new Date().getTime()),
720.0, "易購商城", "訂單描述");
return form;
}
}
- service
@Service
public class AlipayService {
public String toPay(String orderId, double price, String orderName, String orderDesc) throws Exception{
//獲得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
//設置請求參數
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
//商戶訂單號,商戶網站訂單系統中唯一訂單號,必填
String out_trade_no = orderId;
//付款金額,必填
String total_amount = String.valueOf(price);
//訂單名稱,必填
String subject = orderName;
//商品描述,可空
String body = orderDesc;
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}")
String form = "";
AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest);
if (response.isSuccess()) {
form = alipayClient.pageExecute(alipayRequest).getBody();
}
// 這里返回的 form 是一個字符串,里面封裝了支付的表單信息
//(即 html 標簽 和 javascript 代碼),直接將這個 form 輸出到頁面即可。
return form;
}
}
然后啟動項目,訪問 localhost:8080,如下:

點擊付款,就可以到支付寶的支付界面了,如下:

掃碼(使用沙箱環境的支付寶,不是真實的支付寶)付款即可。
通過上面的步驟,已經可以進行支付了。但是,這只是同步通知,真實的支付結果需要使用異步通知。
支付結果異步通知
首先,我們需要一個提供異步通知的 URL,並且,這個 URL 必須可以通過 外網 訪問(如果沒有服務器,可以使用內網穿透)。這里推薦一款內網穿透工具 NATAPP。
異步通知的特性:
- 支付寶是用 POST 方式發送通知信息,所有請求信息被保存在 request 中。
- 服務器間的交互,不像頁面跳轉同步通知可以在頁面上顯示出來,這種交互方式是不可見的
- 程序執行完后必須打印輸出“success”(不包含引號)。如果商戶反饋給支付寶的字符不是 success 這7個字符,支付寶服務器會不斷重發通知,直到超過24小時22分鍾。一般情況下,25小時以內完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h)
- 程序執行完成后,該頁面不能執行頁面跳轉。如果執行頁面跳轉,支付寶會收不到 success 字符,會被支付寶服務器判定為該頁面程序運行出現異常,而重發處理結果通知
- cookies、session 等在此頁面會失效,即無法獲取這些數據;
- 該方式的調試與運行必須在服務器上,即互聯網上能訪問;
- 該方式的作用主要防止訂單丟失,即頁面跳轉同步通知沒有處理訂單更新,所以應該在異步通知中更新訂單;
驗簽:
在異步通知中,有一個驗簽的過程,這個過程就是為了保證我們這個接口的調用方是支付寶的服務器,而不是其他服務器。 驗簽過程如下:
-
第一步: 在通知返回參數列表中,除去 sign、sign_type 兩個參數外,凡是通知返回回來的參數皆是待驗簽的參數。
-
第二步: 將剩下參數進行 url_decode,然后進行字典排序,組成字符串,得到待簽名字符串:
-
第三步: 將簽名參數(sign)使用 base64 解碼為字節碼串。
-
第四步: 使用 RSA 的驗簽方法,通過簽名字符串、簽名參數(經過 base64 解碼)及支付寶公鑰驗證簽名。
看似復雜,其實只需調用驗簽的 API 即可,如下:
boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET, SIGN_TYPE) //調用SDK驗證簽名
前面介紹了異步通知的流程,接下來就是進行異步通知接口的開發。
- 配置
在支付寶配置類 AlipayConfig.java中配置 notify_url 屬性,這個屬性就是異步通知的 URL。
// 比如
public static String notify_url = "http://xxx.top/pay/callback";
// xxx.top 為你的域名或者外網可訪問的 ip 地址。
// pay/callback 為后台請求路徑,即 controller 中的 RequestMapping。
同時,需要在沙箱環境中配置授權回調地址為異步通知的 URL:

- controller
/**
* 異步通知支付結果
* @param request
* @return
* @throws AlipayApiException
*/
@PostMapping("/callback")
public String alipayNotify(HttpServletRequest request) throws AlipayApiException {
String success = "success";
String failure = "failure";
//獲取支付寶的請求信息
Map<String,String> params = new HashMap<>();
Map<String,String[]> requestParams = request.getParameterMap();
// 將 Map<String,String[]> 轉為 Map<String,String>
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
params.remove("sign_type");
// 驗簽
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
// 驗簽通過
if (signVerified) {
System.out.println("通過驗簽");
// 更新訂單信息
String result = alipayService.updateOrder(params);
if ("success".equals(result)) {
System.out.println("controller支付成功");
return success;
}
}
return failure;
}
- service
public String updateOrder(Map<String, String> params) {
if (params == null || params.isEmpty()){
return "success";
}
String orderId = params.get("out_trade_no");
System.out.println("service訂單id:" + orderId);
// PayOrderDTO order = orderService.getOrderById(orderId);
// 如果訂單不存在,則支付操作無意義
// 不讓支付寶再繼續調用異步通知(返回為 SUCCESS 后,支付寶將不再調用)。
// if (order == null) {
// return "success";
// }
// 判斷訂單狀態是否已經被修改
// int orderStatus = orderService.getOrderStatus(orderId);
// if (orderStatus == 1){
// return "success";
// }
String tradeStatus = params.get("trade_status");
// 支付成功
if ("TRADE_SUCCESS".equals(tradeStatus)){
// 更新訂單信息
// ...
System.out.println("訂單支付成功service");
return "success";
}
return "failure";
}
到此,Spring Boot 就簡單的整合了支付寶。
完整代碼
AlipayConfig.java
public class AlipayConfig {
// 應用ID,您的APPID,收款賬號既是您的APPID對應支付寶賬號
public static String app_id = "";
// 商戶私鑰,您的PKCS8格式RSA2私鑰
public static String merchant_private_key = "";
// 支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。
public static String alipay_public_key = "";
// 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
public static String notify_url = "";
// 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
// 即支付成功之后,需要跳轉到的頁面,一般為網站的首頁
// 便於測試,直接使用了 baidu
public static String return_url = "http://www.baidu.com";
// 簽名方式
public static String sign_type = "RSA2";
// 字符編碼格式
public static String charset = "utf-8";
// 支付寶網關
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
// 日志存儲路徑
public static String log_path = "C:\\";
/**
* 寫日志,方便測試(看網站需求,也可以改成把記錄存入數據庫)
* @param sWord 要寫入日志里的文本內容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
AlipayService.java
@Service
public class AlipayService {
// 跳轉到支付界面
public String toPay(String orderId, double price, String orderName, String orderDesc) throws Exception{
//獲得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
//設置請求參數
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
//商戶訂單號,商戶網站訂單系統中唯一訂單號,必填
String out_trade_no = orderId;
//付款金額,必填
String total_amount = String.valueOf(price);
//訂單名稱,必填
String subject = orderName;
//商品描述,可空
String body = orderDesc;
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}")
String form = "";
AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest);
if (response.isSuccess()) {
form = alipayClient.pageExecute(alipayRequest).getBody();
}
return form;
}
// 更新訂單
public String updateOrder(Map<String, String> params) {
if (params == null || params.isEmpty()){
return "success";
}
String orderId = params.get("out_trade_no");
System.out.println("service訂單id:" + orderId);
// PayOrderDTO order = orderService.getOrderById(orderId);
// 如果訂單不存在,則支付操作無意義
// 不讓支付寶再繼續調用異步通知(返回為 SUCCESS 后,支付寶將不再調用)。
// if (order == null) {
// return "success";
// }
// 判斷訂單狀態是否已經被修改
// int orderStatus = orderService.getOrderStatus(orderId);
// if (orderStatus == 1){
// return "success";
// }
String tradeStatus = params.get("trade_status");
// 支付成功
if ("TRADE_SUCCESS".equals(tradeStatus)){
// 更新訂單信息
// ...
System.out.println("訂單支付成功service");
return "success";
}
return "failure";
}
}
PayController.java
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private AlipayService alipayService;
@GetMapping("/hello")
public String hello() {
return "index";
}
/**
* 跳轉到支付界面
* @return
* @throws Exception
*/
@GetMapping("/topay")
@ResponseBody
public String pay() throws Exception {
String form = alipayService.toPay(String.valueOf(new Date().getTime()),
720.0, "易購商城", "訂單描述");
return form;
}
/**
* 異步通知支付結果
* @param request
* @return
* @throws AlipayApiException
*/
@PostMapping("/callback")
public String alipayNotify(HttpServletRequest request) throws AlipayApiException {
String success = "success";
String failure = "failure";
//獲取支付寶的請求信息
Map<String,String> params = new HashMap<>();
Map<String,String[]> requestParams = request.getParameterMap();
// 將 Map<String,String[]> 轉為 Map<String,String>
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
params.remove("sign_type");
// 驗簽
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
// 驗簽通過
if (signVerified) {
System.out.println("通過驗簽");
// 更新訂單信息
String result = alipayService.updateOrder(params);
if ("success".equals(result)) {
System.out.println("controller支付成功");
return success;
}
}
return failure;
}
}
index.html
<form action="/pay/topay">
<button type="submit">付款</button>
</form>
官方文檔:
https://opendocs.alipay.com/open/270/105899
https://opendocs.alipay.com/open/270/105902
