支付寶APP支付 ---伺服器開發
阿新 • • 發佈:2019-01-28
寫過微信支付再寫支付寶支付就好理解了很多。而且支付寶提供的sdk很好用,幾行程式碼就可以了~~,寫的程式碼還沒有測試,應該問題不大,如果有錯誤希望各位指正。
對幾個容易混淆的引數進行說明:
1. 應用私鑰: 通過工具生成,生成之後請儲存好,在支付寶開發平臺找不到。
2. 應用公鑰:通過工具生成,生成之後需要填寫到支付寶開發平臺中。
3. 支付寶公鑰:支付寶生成的,可以在支付寶開發平臺中看到。
如果對非對稱加密演算法很熟悉的應該很好理解這幾個引數。
這個圖是APP支付的一個流程說明,圖畫的比較詳細就不再敘述了。
開始寫程式碼,因為我只幫別人寫一部分,所以只完成工具類,具體controller沒有,下面是引數封裝的工具類。
/**
* 請求引數組裝
*
* @param outTradeNo 商戶網站唯一訂單號
* @param totalAmount 訂單總金額,單位為元,精確到小數點後兩位,取值範圍[0.01,100000000]
* @param subject 商品的標題/交易標題/訂單標題/訂單關鍵字等。
* @param notifyUrl 回撥地址
* @throws UnsupportedEncodingException
*/
public static Map<String, String> setParam(String outTradeNo, String totalAmount, String subject,
String notifyUrl) throws UnsupportedEncodingException {
Map<String, String> map = new HashMap<String, String>();
map.put("app_id", Config.APP_ID);
map.put("method", Config.PAY_METHOD);
map.put("charset" , Config.CHARSET);
map.put("sign_type", Config.SIGN_TYPE);
map.put("timestamp", Config.geTtimestamp());
map.put("version", "1.0");
map.put("notify_url", notifyUrl);
// 業務請求引數封裝
Map<String, Object> biz_content = new HashMap<>();
biz_content.put("subject", URLEncoder.encode(subject, "UTF-8"));
biz_content.put("out_trade_no", outTradeNo);
biz_content.put("total_amount", totalAmount);
biz_content.put("product_code", "QUICK_MSECURITY_PAY");
map.put("biz_content", biz_content.toString());
String sign = null;
try {
//呼叫支付寶SDK獲取簽名 RSA簽名params 待簽名引數map privateKey 私鑰 charset 簽名編碼格式
sign = AlipaySignature.rsaSign(map, Config.APP_PRIVATE_KEY, Config.CHARSET);
} catch (AlipayApiException e) {
e.printStackTrace();
System.out.println(e.getMessage());
logger.error("======簽名錯誤=====" + e.getMessage());
}
map.put("sign", URLEncoder.encode(sign, "UTF-8"));
return map;
}
注意(很重要)
- 商戶在請求引數中,自己附屬的一些額外引數,不要和支付寶系統中約定的key(下表中 公共請求引數\請求引數)重名,否則將可能導致未知的異常。需要對內容進行encode編碼處理。
商戶支付請求引數的安全注意點: - 請求引數的sign欄位請務必在服務端完成簽名生成(不要在客戶端本地簽名);
- 支付請求中的訂單金額total_amount,請務必依賴服務端,不要輕信客戶端上行的資料(客戶端本地上行資料在使用者手機環境中無法確保一定安全)。
伺服器接收非同步支付通知
對於App支付產生的交易,支付寶會根據原始支付API中傳入的非同步通知地址notify_url,通過POST請求的形式將支付結果作為引數通知到商戶系統。
另外需要注意的:
1.必須保證伺服器非同步通知頁面(notify_url)上無任何字元,如空格、HTML標籤、開發系統自帶丟擲的異常提示資訊等,
2.程式執行完後必須列印輸出“success”(不包含引號)。如果商戶反饋給支付寶的字元不是success這7個字元,支付寶伺服器會不斷重發通知,直到超過24小時22分鐘。
在收到支付寶的通知時需要進行驗籤和業務處理,驗籤支付寶的SDK也提供了 ,是不是很方便,
/**
* 驗籤
* 校驗支付寶發過來的簽名是否正確,使用支付寶的公鑰驗籤
* @param request
* @return true 驗籤通過
*/
public static boolean signVerified(HttpServletRequest request) {
Enumeration<?> pNames = request.getParameterNames();
Map<String, String> param = new HashMap<String, String>();
try {
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
param.put(pName, request.getParameter(pName));
}
boolean signVerified = AlipaySignature.rsaCheckV1(param, Config.ALIPAY_PUBLIC_KEY, Config.CHARSET); // 校驗簽名是否正確
return signVerified;
} catch (Exception e) {
logger.error("============驗籤錯誤=============" + e.getMessage());
System.out.println(e.getMessage());
return false;
}
}
回撥的controller 只寫了一部分,僅供參考
@RequestMapping(value = "/pay/notify", method = RequestMethod.POST)
public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) {
logger.info("==============收到支付寶的非同步通知==========================");
// 獲取到返回的所有引數 先判斷是否交易成功trade_status 再做簽名校驗
if ("TRADE_SUCCESS".equals(request.getParameter("trade_status"))) {
try {
boolean signVerified = PayUtils.signVerified(request);
if (signVerified) {
// 驗籤成功後
// 1、商戶需要驗證該通知資料中的out_trade_no是否為商戶系統中建立的訂單號,
// 2、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額),
// 3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email),
// 4、驗證app_id是否為該商戶本身。上述1、2、3、4有任何一個驗證不通過,則表明本次通知是異常通知,務必忽略。在上述驗證通過後商戶必須根據支付寶不同型別的業務通知,正確的進行不同的業務處理,並且過濾重複的通知結果資料。在支付寶的業務通知中,只有交易通知狀態為TRADE_SUCCESS或TRADE_FINISHED時,支付寶才會認定為買家付款成功。
// 對支付結果中的業務內容進行1\2\3\4二次校驗,校驗成功後在response中返回success,
logger.info("=====訂單支付成功:====" + request.getParameterMap().toString());
//通知支付寶後臺,已收到通知
response.getWriter().println("success");
} else {
// TODO 驗籤失敗則記錄異常日誌,
logger.info("======驗籤失敗=======");
}
} catch (Exception e) {
e.printStackTrace();
logger.info("========通知處理失敗=========="+e.getMessage());
}
}else{
logger.info("========交易失敗==========");
}
}
伺服器寫好了 ~