這段時間把支付基本搞完了,因為做的過程中遇到許多問題,特地記錄下來,同時友善其他java coder,廢話少說,下面開始。
整體思路:在背景,根據參數建立支付寶用戶端AlipayClient,發送參數到支付寶,支付寶直接傳回一個表單,我們隻需要将表單輸出到頁面上,後續支付寶異步通知,比較重要是驗簽,支付寶也提供的工具,比較友善。
(jar包或maven的引入這裡省略)
1、申請支付寶支付,這裡大家自己研究,網上很多教程。
2、建立支付
/**
* 調用支付寶支付alipay.trade.page.pay
* 商戶系統請求支付寶接口alipay.trade.page.pay,支付寶對商戶請求參數進行校驗,而後重定向至使用者登入頁面。
*
* @param model
* @return
* @throws Exception
*/
public String createAlipay(Model model, String order_no, BigDecimal amount, Integer resource_trad_id, String trad_type, HttpServletResponse response) throws Exception {
String form = "";
User user = (User) model.asMap().get("user");
//生成一筆預付訂單流水
String trad_no = "PC_ALIPAY" + OrderNoUtil.leadsNo();//訂單流水号
ShareUserTrad trad = new ShareUserTrad();
trad.setResourceTradId(resource_trad_id);
trad.setUserId(user.getId());
trad.setCreatedBy(user.getId());
trad.setLastUpdBy(user.getId());
trad.setOnlineOfflineFlag("0");//線上
trad.setOrderNo(order_no);
trad.setUserTradAmount(amount);
trad.setTradMethod("3");//支付寶
trad.setPayReceiveFlag("2");//支出
trad.setSuccessFlag("0");//交易進行中
trad.setTradType("1");//訂單支付
trad.setTradNo(trad_no);
trad.setModifyNum(0);
shareUserTradMapper.insertSelective(trad);
try {
//初始化用戶端
AlipayClient alipayClient = new DefaultAlipayClient(Config.alipay_url, Config.alipay_appid, Config.alipay_app_private_key, Config.alipay_format, Config.alipay_charset, Config.alipay_app_public_key, Config.alipay_sign_type);
//建立API對應的request
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl("");//回退到訂單清單頁面
alipayRequest.setNotifyUrl("");//在公共參數中設定回跳和通知位址
alipayRequest.setBizContent("{" +
" \"out_trade_no\":\"" + trad_no + "\"," +
" \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," +
//" \"total_amount\":" + amount.toString() + "," +
"\"total_amount\":\"0.01\"," +
" \"subject\":\"訂單支付\"," +
" \"body\":\"訂單:" + order_no + "支付\"," +
" \"passback_params\":\"" + order_no + "\"" +
" }");//填充業務參數
form = alipayClient.pageExecute(alipayRequest).getBody(); //調用SDK生成表單
} catch (Exception e) {
e.printStackTrace();
String sOut = "";
StackTraceElement[] trace = e.getStackTrace();
for (StackTraceElement s : trace) {
sOut += "\tat " + s + "\r\n";
}
model.addAttribute("failMsg", sOut + "alipay_url:" + Config.alipay_url);
return "/pay/payFail";
}
response.setContentType("text/html;charset=" + Config.alipay_charset);
response.getWriter().write(form);//直接将完整的表單html輸出到頁面
response.getWriter().flush();
response.getWriter().close();
return null;
}
這裡注意幾點:
①上面那個建立預付訂單流水,主要用于後面支付寶異步通知,訂單流水号需要傳給支付寶,這樣支付寶回調通知的時候,會把這個參數傳回來。
②
alipayRequest.setReturnUrl("");//回退到訂單清單頁面
這個位址為使用者掃碼成功後,支付寶會在五秒後從支付頁面跳轉到的頁面。
③
alipayRequest.setNotifyUrl("");//在公共參數中設定回跳和通知位址
這個位址為支付寶支付異步通知的位址。這個位址必須要是外網可通路的,如果大家在本地測試,需要把本機映射到外網上去,這裡推薦大家使用ngrok,注冊後是可以免費使用的。
上面兩個位址出于隐私考慮我這裡是空的,大家要加上。
3、支付寶異步通知
/**
* 接受支付寶異步通知
*/
@RequestMapping(value = "/notify", method = RequestMethod.POST)
@Transactional(readOnly = false)
public void alipayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> map = 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");
map.put(name, valueStr);
}
System.out.println("支付結果---:" + map.toString());
//調用SDK驗證簽名
boolean signVerified = false;
try {
signVerified = AlipaySignature.rsaCheckV1(map, Config.alipay_app_public_key, Config.alipay_charset, Config.alipay_sign_type);
} catch (Exception e) {
e.printStackTrace();
}
if (signVerified) {
System.out.println("支付結果---:" + map.toString());
String trad_no = map.get("out_trade_no");
//根據交易流水号查詢交易資訊
if ("TRADE_SUCCESS".equals(map.get("trade_status"))) {//交易成功
DealUserTradModel dealUserTradModel = new DealUserTradModel();
dealUserTradModel.setUser_account_name("");
dealUserTradModel.setUser_account(map.get("buyer_id"));
dealUserTradModel.setTrad_no(trad_no);
dealUserTradModel.setPay_amount(new BigDecimal(map.get("total_amount")));
dealUserTradModel.setCompany_amount(new BigDecimal(map.get("receipt_amount")));
dealUserTradModel.setOut_trad_no(map.get("trade_no"));
payService.dealTrad(dealUserTradModel);
}
try {
PrintWriter out = response.getWriter();
out.print("success");
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
// TODO 驗簽成功後,按照支付結果異步通知中的描述,對支付結果中的業務内容進行二次校驗,校驗成功後在response中傳回success并繼續商戶自身業務處理,校驗失敗傳回failure
} else {
// TODO 驗簽失敗則記錄異常日志,并在response中傳回failure.
try {
PrintWriter out = response.getWriter();
out.print("failure");
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
收到通知,将交易流水的狀态更新就可以了,這裡用到支付寶回傳的交易流水号。
4、幾個參數
Config.alipay_url
這個參數是支付寶API位址,如果大家是在沙箱上面測試,則位址為:https://openapi.alipaydev.com/gateway.do,如果是正式環境,為:https://openapi.alipay.com/gateway.do.
Config.alipay_appid
這個參數是支付寶配置設定的APP ID,如果是沙箱環境,則在https://openhome.alipay.com/platform/appDaily.htm?tab=info這裡看:
如果是正式環境,則在螞蟻金服開方平台-->開發者中心-->網頁&移動應用,選擇應用點檢視,左上角的icon旁邊.
Config.alipay_app_public_key
這個參數是支付寶公鑰,如果是沙箱環境,在這裡看:
正式環境,地方跟上面的差不多。
Config.alipay_charset
這個參數是編碼格式,推薦跟你的項目編碼一緻。
Config.alipay_sign_type
這個參數是簽名類型,推薦使用RSA2。
Config.alipay_app_private_key
這個參數是你的應用私鑰,用支付寶提供的秘鑰生成工具生成的應用私鑰。
Config.alipay_format
這個參數就是字元串json。