支付寶支付、查詢訂單、退款
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閘道器對應的支付寶公鑰是不一樣的。檢視地址如下:
檢視到正確的公鑰後,更換正確的支付寶公鑰後即可成功。
參考文獻: