1. 前言
對接了好長一段時間的支付,期間涉及到支付寶相關工作,這里將支付寶相關部分整理一下。
環境配置,主要是在pom文件中添加如下依賴:
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>3.1.0</version> </dependency>
支付寶的支付api文檔比較完整,對接起來也比較順利,下面就開始吧‘(*>﹏<*)′
2. 電腦網站支付
其實是統一收單下單並支付頁面接口(alipay.trade.page.pay),PC場景下單並支付,接口調用成功后會返回一個form表單,直接提交就可以了,跳過去的頁面是二維碼,掃碼支付,成功后會回跳到指定的頁面(這里是一個重定向,重定向地址是在發起調用時以接口參數的形式指定的)。
接口發起示例代碼,詳見統一收單下單並支付頁面接口。
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", app_id, private_key, "json", "UTF-8", alipay_public_key, "RSA2");
//設置請求參數
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(returnUrl); // 支付成功后回跳地址
alipayRequest.setNotifyUrl(notifyUrl); // 支付后的異步通知地址
//商戶訂單號,商戶網站訂單系統中唯一訂單號,必填
String out_trade_no = "";
//付款金額,必填
String total_amount = "";
//訂單名稱,必填
String subject = "";
//商品描述,可空
String body = "";
// 銷售產品碼 必填
String product_code="FAST_INSTANT_TRADE_PAY";
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"timeout_express\":\"10m\","
+ "\"product_code\":\""+product_code+"\"}");
//請求
String form = "";
try{
form = alipayClient.pageExecute(alipayRequest).getBody();//調用SDK生成表單
}catch (AlipayApiException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
return form ;
這里拿到的是一個字符串,其實是一個form表單,前端收到這個之后直接跳過去就可以了,剩下的就交給支付寶了,厲害吧!示例如下:
-<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Document</title> </head> <body> <form ...> // 這里就是返回的form字符串 </form> <script>document.forms[0].submit();</script> </body> <script> </script> </html>
關於異步回調,后台需要提供一個外網可訪問的接口,供支付寶回調,示例代碼:
//獲取支付寶POST過來反饋信息 logger.debug("----------------------------------->pay notify start--------------^_^"); Map<String, String> params = new HashMap<String, String>(); Map requestParams = request.getParameterMap(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = (String[]) 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); } // 驗簽 boolean result = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, "utf-8" ,"RSA2"); if (result) { // 驗簽成功 if (trade_status.equals("TRADE_FINISHED")) { response.getWriter().print("success"); } else if (trade_status.equals("TRADE_SUCCESS")) { // 支付成功 // 業務邏輯 } } else {//驗證失敗 response.getWriter().print("failure"); } }
3. 手機網站支付
這個是用於手機端,在手機瀏覽器上發起的,接口名稱(alipay.trade.wap.pay),這個接口和電腦網站支付接口返回的結果類似,也是一個form表單,相關操作類似,跳轉過去之后會檢測手機上是否安裝支付寶客戶端,如果有則打開客戶端支付,沒有則在網頁上登陸進行支付,示例代碼,詳見手機網站支付接口2.0:
String out_trade_no = ""; // 商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 String subject = ""; // 訂單名稱,必填 String total_amount=""; // 付款金額,必填 String body = ""; // 商品描述,可空 String timeout_express="30m"; // 超時時間 可空 // SDK 公共請求類,包含公共請求參數,以及封裝了簽名與驗簽,開發者無需關注簽名與驗簽 //調用RSA簽名方式 AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",appId, privateKey, "json", "UTF-8", aliPayPublicKey, "RSA2"); // 移動端封裝請求支付信息 String product_code="QUICK_WAP_PAY"; //銷售產品碼 必填 AlipayTradeWapPayRequest alipay_request=new AlipayTradeWapPayRequest(); AlipayTradeWapPayModel model=new AlipayTradeWapPayModel(); model.setOutTradeNo(out_trade_no); model.setSubject(subject); model.setTotalAmount(total_amount); model.setBody(body); model.setTimeoutExpress(timeout_express); model.setProductCode(product_code); alipay_request.setBizModel(model); alipay_request.setNotifyUrl(notifyUrl);// 設置異步通知地址,支付成功后支付寶回掉通知地址 alipay_request.setReturnUrl(returnUrl);// 設置同步地址,支付后頁面跳轉的地址 // form表單生產 String form = ""; try { // 調用SDK生成表單 form = alipayClient.pageExecute(alipay_request).getBody(); } catch (AlipayApiException e) { } return form;
4. 交易查詢
通過調用alipay.trade.query(統一收單線下交易查詢)接口來實現,該接口提供所有支付寶支付訂單的查詢,商戶可以通過該接口主動查詢訂單狀態,完成下一步的業務邏輯。
需要調用查詢接口的情況:
- 當商戶后台、網絡、服務器等出現異常,商戶系統最終未接收到支付通知;
- 調用支付接口后,返回系統錯誤或未知交易狀態情況;
- 調用alipay.trade.pay,返回INPROCESS的狀態;
- 調用alipay.trade.cancel之前,需確認支付狀態;
示例代碼,詳見支付寶文檔:
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","utf8","alipay_public_key","RSA2"); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizContent("{" + "\"out_trade_no\":\"20150320010101001\"," + "\"trade_no\":\"2014112611001004680073956707\"," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeQueryResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("調用成功");
// 業務邏輯
} else {
System.out.println("調用失敗");
}
5. 退款
通過調用alipay.trade.refund(統一收單交易退款接口)接口實現,當交易發生之后一段時間內,由於買家或者賣家的原因需要退款時,賣家可以通過退款接口將支付款退還給買家,支付寶將在收到退款請求並且驗證成功之后,按照退款規則將支付款按原路退到買家帳號上。 交易超過約定時間(簽約時設置的可退款時間)的訂單無法進行退款 支付寶退款支持單筆交易分多次退款,多次退款需要提交原支付訂單的商戶訂單號和設置不同的退款單號。一筆退款失敗后重新提交,要采用原來的退款單號。總退款金額不能超過用戶實際支付金額。
關於退款的異步通知,這和微信是不一樣的,微信的異步通知地址是在發起退款接口時指定的,而支付寶呢,比較厲害,分兩種情況:
- 全額退款,交易狀態變為交易關閉(TRADE_CLOSED),具體是否會觸發異步通知根據接口中的通知觸發條件判斷,我的測試是沒有回調通知;
- 部分退款都會觸發異步通知,異步通知的地址是支付時指定的異步回調地址;
那既然退款的回調地址和支付時的回調地址是一致的,我如何區分一次回調是支付還是退款呢?退款的異步通知參數中一定會返回out_biz_no(商戶業務號)、refund_fee(總退款金額)、gmt_refund(交易退款時間)。
關於退款期限,支付寶一般是3個月,這個可以在開通的服務簽約信息中查看。
示例代碼,詳見退款文檔:
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","GBK","alipay_public_key","RSA2"); AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); request.setBizContent("{" + "\"out_trade_no\":\"20150320010101001\"," + "\"trade_no\":\"2014112611001004680073956707\"," + "\"refund_amount\":200.12," + "\"refund_currency\":\"USD\"," + "\"refund_reason\":\"正常退款\"," + "\"out_request_no\":\"HZ01RF001\"," + "\"operator_id\":\"OP001\"," + "\"store_id\":\"NJ_S_001\"," + "\"terminal_id\":\"NJ_T_001\"," + " \"goods_detail\":[{" + " \"goods_id\":\"apple-01\"," + "\"alipay_goods_id\":\"20010001\"," + "\"goods_name\":\"ipad\"," + "\"quantity\":1," + "\"price\":2000," + "\"goods_category\":\"34543238\"," + "\"categories_tree\":\"124868003|126232002|126252004\"," + "\"body\":\"特價手機\"," + "\"show_url\":\"http://www.alipay.com/xxx.jpg\"" + " }]," + " \"refund_royalty_parameters\":[{" + " \"royalty_type\":\"transfer\"," + "\"trans_out\":\"2088101126765726\"," + "\"trans_out_type\":\"userId\"," + "\"trans_in_type\":\"userId\"," + "\"trans_in\":\"2088101126708402\"," + "\"amount\":0.1," + "\"amount_percentage\":100," + "\"desc\":\"分賬給2088101126708402\"" + " }]," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeRefundResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("調用成功");
// 業務邏輯 } else { System.out.println("調用失敗"); }
最后退款成功后可以在手機支付寶--朋友--服務提醒中查看,如下所示:
6. 退款查詢
通過調用alipay.trade.fastpay.refund.query接口完成退款查詢功能,該接口的返回碼10000,僅代表本次查詢操作成功,不代表退款成功。如果該接口返回了查詢數據,則代表退款成功,如果沒有查詢到則代表未退款成功,可以調用退款接口進行重試。
關於如何確定退款是否成功,可以通過主動調用查詢接口來確定,也可通過如上的回調接口等待支付寶的異步通知,但是如果未收到支付寶的異步通知並且確定不是參數以及回調地址的問題,這時應該主動調用查詢接口來同步退款狀態。
示例代碼,詳細見統一收單交易退款查詢。
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","GBK","alipay_public_key","RSA2"); AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); request.setBizContent("{" + "\"trade_no\":\"20150320010101001\"," + "\"out_trade_no\":\"2014112611001004680073956707\"," + "\"out_request_no\":\"2014112611001004680073956707\"," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("調用成功"); } else { System.out.println("調用失敗"); }
7. 遇到的問題
1. 訂單查詢時報:com.alipay.api.AlipayApiException: sign check fail: check Sign and Data Fail
原因:報這個錯誤是因為支付寶公鑰(alipay_public_key)使用錯誤導致的!很多開發者容易把自己生成的應用公鑰和支付寶公鑰搞混淆,從而配置錯誤導致這個錯誤,自己生成的是應用公鑰和應用私鑰!
- RSA支付寶公鑰對於所有商戶都是唯一的相同值
- RSA2對於所有商戶都是單獨一對一的,並且只支持開發平台密鑰管理和沙箱RSA2支付寶公鑰,只能您的appid下面商戶公鑰上傳才會顯示,並且獲取只能從這個位置獲取, 所有商戶一個賬號下的RSA2支付寶公鑰是相同的。
具體查看可參考下圖,螞蟻金服開放平台登陸--開發者中心--網頁&移動應用--應用列表--查看詳情--應用信息。
如何設置公私鑰,可參考:上傳應用公鑰並獲取支付寶公鑰
解決辦法:
確認使用的支付寶公鑰是否正確,不同的環境使用的支付寶公鑰不同,如沙箱環境、線上openapi網關和mapi網關對應的支付寶公鑰是不一樣的。查看地址如下:
查看到正確的公鑰后,更換正確的支付寶公鑰后即可成功。
參考文獻: