參考阿里雲文檔:https://docs.open.alipay.com/203/105285/
1.調用流程
手機網站支付產品包含兩類API:
- 頁面跳轉類:需要從前端頁面以Form表單的形式發起請求,瀏覽器會自動跳轉至支付寶的相關頁面(一般是收銀台或簽約頁面),用戶在該頁面完成相關業務操作后再回跳到商戶指定頁面。例如本產品中的手機網站支付接口alipay.trade.wap.pay。
- 系統調用類:直接從服務端發起HTTP請求,支付寶會同步返回請求結果。例如本產品中的交易查詢等配套API。
調用流程:
- 如上圖所示,用戶在商戶的H5網站下單支付后,商戶系統按照手機網站支付接口alipay.trade.wap.payAPI的參數規范生成訂單數據,然后在前端頁面通過Form表單的形式請求到支付寶。此時支付寶會自動將頁面跳轉至支付寶H5收銀台頁面,如果用戶手機上安裝了支付寶APP,則自動喚起支付寶APP。開發者需要關注安裝了支付寶和未安裝支付寶的兩種測試場景,對於在手機瀏覽器喚起H5頁面的模式下,如果安裝了支付寶卻沒有喚起,大部分原因是當前瀏覽器不在支付寶配置的白名單內。
- 對於商戶app內嵌webview中的支付場景,建議集成支付寶App支付產品。或者您可以使用手機網站支付轉Native支付的方案,不建議在您的APP中直接接入手機網站支付。
- 用戶在支付寶APP或H5收銀台完成支付后,會根據商戶在手機網站支付API中傳入的前台回跳地址return_url自動跳轉回商戶頁面,同時在URL請求中以Query String的形式附帶上支付結果參數,詳細回跳參數見“手機網站支付接口alipay.trade.wap.pay”前台回跳參數。
注意:在ios系統中,喚起支付寶App支付完成后,不會自動回到瀏覽器或商戶APP。用戶可手工切回到瀏覽器或商戶APP;支付寶H5收銀台會自動跳轉回商戶return_url指定的頁面。
- 支付寶還會根據原始支付API中傳入的異步通知地址notify_url,通過POST請求的形式將支付結果作為參數通知到商戶系統,詳情見支付結果異步通知。
- 除了正向支付流程外,支付寶也提供交易查詢、關閉、退款、退款查詢以及對賬等配套API。
特別注意:
- 由於前台回跳的不可靠性,前台回跳只能作為商戶支付結果頁的入口,最終支付結果必須以異步通知或查詢接口返回為准,不能依賴前台回跳。
- 商戶系統接收到異步通知以后,必須通過驗簽(驗證通知中的sign參數)來確保支付通知是由支付寶發送的。詳細驗簽規則參考異步通知驗簽。
- 接受到異步通知並驗簽通過后,一定要檢查通知內容,包括通知中的app_id, out_trade_no, total_amount是否與請求中的一致,並根據trade_status進行后續業務處理
2.環境接入
2.1SDK包下載:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.3.0.ALL</version>
</dependency>
2.2公私鑰文件配置
3.沙箱測試
沙箱環境提供測試賬戶:買家賬號,商戶賬戶。可自行充值
3.1java后端發送支付請求:
@RequestMapping("/pay")
public void pay(HttpServletResponse response, String amount) {
String form = null;
try {
form = PayUtil.pay(amount);
} catch (AlipayApiException e) {
form = "pay error!";
e.printStackTrace();
}
response.setContentType("text/html;charset=" + "utf-8");
try {
response.getWriter().write(form);//直接將完整的表單html輸出到頁面
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static String pay(String amount) throws AlipayApiException {
AlipayClient alipay_client = new DefaultAlipayClient(server_url, app_id, private_key, format, charset, alipay_pubic_key, sign_type);
AlipayTradeWapPayRequest alipay_request = new AlipayTradeWapPayRequest();
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(getOutOrderNo());
model.setSubject(subject);
model.setTotalAmount(amount);
model.setTimeoutExpress(timeout_express);
model.setProductCode(product_code);
alipay_request.setBizModel(model);
// 設置異步通知地址
alipay_request.setNotifyUrl(notify_url);
// 設置同步地址
alipay_request.setReturnUrl(return_url);
// 調用SDK生成表單
AlipayResponse alipay_response = alipay_client.pageExecute(alipay_request);
String form = alipay_response.getBody();
System.out.println(alipay_response.isSuccess() + ":" + form);
return form;
}
后端向支付寶發送支付請求,SDK將根據私鑰內容對請求進行簽名,支付寶根據提前配置的公鑰進行驗簽通過后響應。返回的字符內容前端以H5的形式展示,依據內容來看form表單自動提交至支付寶付款界面。登錄之前的沙箱買家賬戶進行支付即可。
3.2支付回調接口和 結果頁面回調接口
@RequestMapping("/notify")
public void alipayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
//獲取支付寶POST過來反饋信息
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] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
params.put(name, valueStr);
}
//獲取支付寶的通知返回參數,可參考技術文檔中頁面跳轉同步通知參數列表(以下僅供參考)//
String out_trade_no = "";
String trade_no = "";
String trade_status = "";
try {
//商戶訂單號
out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
//支付寶交易號
trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
//交易狀態
trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//獲取支付寶的通知返回參數,可參考技術文檔中頁面跳轉同步通知參數列表(以上僅供參考)//
//計算得出通知驗證結果
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean verify_result = false;
try {
verify_result = AlipaySignature.rsaCheckV1(params, PayUtil.alipay_pubic_key, PayUtil.charset, "RSA2");
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (verify_result) {//驗證成功
//////////////////////////////////////////////////////////////////////////////////////////
//請在這里加上商戶的業務邏輯程序代碼
System.out.println("商戶的業務邏輯程序代碼-notify:" + out_trade_no + " trade_status:" + trade_status);
//——請根據您的業務邏輯來編寫程序(以下代碼僅作參考)——
if (trade_status.equals("TRADE_FINISHED")) {
//判斷該筆訂單是否在商戶網站中已經做過處理
//如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序
//請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的
//如果有做過處理,不執行商戶的業務程序
System.out.println("TRADE_FINISHED");
//注意:
//如果簽約的是可退款協議,退款日期超過可退款期限后(如三個月可退款),支付寶系統發送該交易狀態通知
//如果沒有簽約可退款協議,那么付款完成后,支付寶系統發送該交易狀態通知。
} else if (trade_status.equals("TRADE_SUCCESS")) {
//判斷該筆訂單是否在商戶網站中已經做過處理
//如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序
//請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的
//如果有做過處理,不執行商戶的業務程序
System.out.println("TRADE_SUCCESS");
//注意:
//如果簽約的是可退款協議,那么付款完成后,支付寶系統發送該交易狀態通知。
}
//——請根據您的業務邏輯來編寫程序(以上代碼僅作參考)——
//writer.clear();
writer.println("success"); //請不要修改或刪除
//////////////////////////////////////////////////////////////////////////////////////////
} else {//驗證失敗
writer.println("fail");
}
}
支付回調時,首先進行驗簽操作,驗證成功后根據提供的訂單號,支付狀態做相應的業務處理。
@RequestMapping("/returnUrl")
public void alipayReturn(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
//獲取支付寶GET過來反饋信息
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] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉化
try {
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
params.put(name, valueStr);
}
String out_trade_no = "";
String trade_no = "";
try {
//獲取支付寶的通知返回參數,可參考技術文檔中頁面跳轉同步通知參數列表(以下僅供參考)//
//商戶訂單號
out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
//支付寶交易號
trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("商戶的業務邏輯程序代碼-return_url:" + out_trade_no);
//獲取支付寶的通知返回參數,可參考技術文檔中頁面跳轉同步通知參數列表(以上僅供參考)//
//計算得出通知驗證結果
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean verify_result = false;
try {
verify_result = AlipaySignature.rsaCheckV1(params, PayUtil.alipay_pubic_key, PayUtil.charset, "RSA2");
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (verify_result) {//驗證成功
//////////////////////////////////////////////////////////////////////////////////////////
//請在這里加上商戶的業務邏輯程序代碼
//該頁面可做頁面美工編輯
//out.clear();
writer.println("check success<br />");
//——請根據您的業務邏輯來編寫程序(以上代碼僅作參考)——
//////////////////////////////////////////////////////////////////////////////////////////
} else {
//該頁面可做頁面美工編輯
//out.clear();
writer.println("check fail");
}
}
結果頁面回調時,首先進行驗簽操作,驗證成功后根據提供的訂單號,做相應的業務處理,並可以控制跳轉的結果頁面。