做爲開放式的B/S架構程序,不管所屬電商,金融,機械製造,企業OA,ERP,CRM,CMS等等行業或系統中,第三方支付以及銀聯支付的業務必定是客戶關心所在,也是保證客戶系統盈利運營的一個重要保障。一般這種B2C或者C2C系統的開發,商戶用戶所關注的支付平臺大多離不開「阿里支付寶,快錢,騰訊財付通,易寶支付這種第三方支付平臺以及中國銀聯UnionPay....等等」這些方式。
最近某項目中涉及到支付的模塊與涉及流程,在此和你們分享一下。
1,名詞釋義html
商戶網站:好比淘寶,聚美,惟品會這種B2C/C2C的網站及後臺的管理系統,統稱爲商戶網站;主要負責對買家訂單數據的封裝,加密,
及支付平臺回調的訂單處理。
支付平臺:咱們須要開發的支付平臺,支付接口,支付模擬的Servlet,暴露出來的WebService接口url等;主要負責對買家請求來的
加密後的訂單數據進行解密,構造請求的URL,拼接參數,對Sign進行加密,對支付機構異步(或同步)請求回調的數據
進行封裝,解密回傳給商戶網站。
支付機構:好比阿里支付寶,快錢,騰訊財付通,易寶支付這種第三方支付平臺等支付機構。
Sign:支付機構爲商戶分配的一把「密鑰」與」合做者ID「同時分配,用作調用Base64,MD5等加密算法在加密解密時的一種私鑰,一般
與此相關聯的還有signType,就是加密方式。
回調:對上次請求端request中的url或指定的url進行http請求,或https請求
git
支付平臺請求,響應,及回調流程圖:web
2,業務流設計(本文只介紹alipay的即時到帳接口:"create_direct_pay_by_user")算法
2.1 商戶網站對數據封裝加密,調用支付接口:json
2.1.1)商戶網站後臺對買家的訂單進行封裝,插入商戶網站db中的訂單表(好比:xxx_order);api
PayReturnVovo = new PayReturnVo();服務器
vo.setOrderId("kuaiqian00232"); 架構
vo.setOrderAmount("20"); app
vo.setOrderTime("20140504121020"); dom
vo.setProductName("3M網線,送水晶頭");
vo.setProductId("2213229319378");
vo.setProductNum("2");
vo.setPayType("00");*/
// 把模擬的表單數據轉成Json
StringorderJson= PaymentJsonUtil.beanToJson(vo);
// 經過db獲取商家key密鑰
Stringkey = dao.getKeyByUserId(userId);
// 根據key使用base64加密算法對訂單信息進行加密
StringSignedJson = CryptUtil.encryptBase64Des(orderJson, key);
2.1.2)於此同時調用dao層查詢買家用戶平臺帳戶餘額,並進行鎖表:在SQL的select後加入 forupdate wait n(最好
爲1-5秒,此處的 數值爲httpclient請求超時時長)爲防止訂單被多用戶修改。
2.2 支付平臺響應請求及解密,調用支付機構接口:
2.2.1)支付平臺響應請求,對數據進行解密;
//獲取輸入參數
InputStreamis = request.getInputStream();
//把接收的加密流轉成String類型
StringpayMsgJson = IOUtils.toString(is, "utf-8");
//base64進行解密
byte[]byteJson = CryptUtil.decryptBASE64payMsgJson
StringstrJson = new String(byteJson,"UTF-8");
//把解密後的json轉換成實體vo
try{
pVo = (BankPayVo)PaymentJsonUtil.jsonToBean(strJson,BankPayVo.class);
}catch (Exception e) {
e.printStackTrace();
throw(e);
}
2.2.2)從db查詢商戶協議信息,構造不一樣方式的支付機構所需請求的url;
publicString CreateUrl(PayBankEntity payBankEntity) throws BankpayException,SQLException{
StringwebPartentId = payBankEntity.getWebPartentId();
//經過DB獲取阿里支付Config信息
AliPayAccountDaoImplaccount = new AliPayAccountDaoImpl();
AliPayAccountVoaccVo = account.getAccountInfo(webPartentId);
//根據訂單號區別b2a和b2c對partner參數設置
StringstrOrderNo = payBankEntity.getOrderNo();
//阿里支付合做夥伴ID
Stringpartner = accVo.getPaPartner();
//阿里支付key
Stringkey= accVo.getPaKey();
//阿里支付接口
Stringpaygateway = accVo.getPaPayGateWay();
//阿里支付服務名
Stringservice = accVo.getPaService();
//阿里支付簽名Sign加密方式
Stringsign_type = accVo.getPaSignType();
//賣家帳號,郵箱
Stringseller_email = accVo.getPaSellerEmail();
//###### Form Web ###### 商戶網站訂單
Stringout_trade_no = payBankEntity.getOrderNo();
//###### Form Web ###### 交易總額
Stringtotal_fee = payBankEntity.getMoney();
//###### Form Web ###### 商品名稱
String subject= payBankEntity.getProductId();
//###### Form Web ###### 商品展現地址
StringinputCharset = accVo.getPaInputCharset();
//###### Form Web ###### 支付類型
Stringpayment_type = payBankEntity.getPaymentType();
//超時時長
Stringit_b_pay = accVo.getPaItBBay();
//!!! 在此修改參數爲異步notify_url可是vo和db中顯示爲return_url
Stringreturn_url = accVo.getPaReturnUrl();
StringItemUrl="";
2.2.2.temp) PS: 下行代碼的CreateUrl()是根據請求參數首字母降序排列,把參數從新構形成新的url。
ItemUrl= Payment.CreateUrl(paygateway,service,sign_type,inputCharset,payment_type,
partner,key,out_trade_no,total_fee,return_url,seller_email,subject,it_b_pay);
System.out.println("異步通知返回agbpay地址:"+ return_url);
returnItemUrl;
}
2.2.3)StringBuffer繪製跳轉請求的html dom元素,把參數請求到支付機構;
publicString getBankHtml(PayBankEntity payBankEntity) throws BankpayException {
StringBuffer sbHtml = new StringBuffer();
try {
sbHtml.append("");
sbHtml.append("
支付網關");
sbHtml.append("
sbHtml.append("
");
}catch (Exception e) {
throw new BankpayException("系統異常,錯誤描述:" + e.getMessage());
}
return sbHtml.toString();
}
2.2.4)切記不要忘記設置支付機構回調支付平臺的回調url,大多數支付機構的參數爲同步和異步兩種,設置支付機構的
回調url目的在於它進行了咱們的請求。處理以後對訂單數據及訂單等狀態的回寫,進而支付平臺能夠封裝,
加密成json串,繼續調用商戶網站,對此次支付的信息進行更改,執行具體業務。
下面是阿里的api,同步和異步回調路徑不能同時爲空。
notify_url 服務器異步通知頁面路徑 String(160) 支付寶服務器主動通知商戶網站裏指定的頁面Http路徑 可空
returl_url 服務器同步通知頁面路徑 String(160) 支付寶完成處理後當前頁面自動跳轉到商戶網站的Http路徑 可空
下面是快錢的api,同步和異步回調路徑不能同時爲空。
pageUrl 接受支付結果的頁面地址 String(256) 須要是絕對地址,與bgUrl不能同時爲空,當bgUrl爲空時,生效 可空
bgUrl 接受支付結果後臺代碼地址 String(256) 須要是絕對地址,與pageUrl不能同時爲空,當pageUrl爲空時,生效 可空
2.3 支付平臺響應支付機構回調:被支付機構接收的訂單支付成功或失敗以後,回調咱們支付平臺的接口。
1)把支付寶的請求輸入流轉成咱們須要的vo對象,調用2)中的performTask()。
//獲取輸入參數
InputStreamis = request.getInputStream();
//轉成String類型
String payMsgJson =IOUtils.toString(is, "utf-8");
PayReturnVovos = PaymentJsonUtil.jsonToBean(payMsgJson, PayReturnVo.class);
request.setAttribute("returnStr",vos);
newAliPayReturnBo().performTask(request, response);
2)把支付寶的請求輸入流轉成咱們須要的vo對象,調用2)中的performTask()。
@SuppressWarnings("unused")
publicstatic String performTask(HttpServletRequest request,
HttpServletResponseresponse) throws IOException, ServletException {
StringreturnStr = "";
StringwebPartentId = "";
try{
Stringsign = request.getParameter("sign");
//支付狀態:TRADE_FINISHED(普通即時到帳的交易成功狀態)||TRADE_SUCCESS(開通
了高級即時到帳或機票分銷產品後的交易成功狀態)
StringtradeStatus = request.getParameter("trade_status");
//訂單編號
StringorderNo = request.getParameter("out_trade_no");
//通知類型
Stringnotify_type = request.getParameter("notify_type");
//支付寶交易流水號
Stringtrade_no = "";
//訂單總價
Stringamount = request.getParameter("total_fee");
if(request.getParameter("trade_no") != null) {
trade_no= request.getParameter("trade_no");
}
StringalipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?"
+"partner="
+partner
+"¬ify_id="
+request.getParameter("notify_id");
//獲取支付寶ATN返回結果,true是正確的訂單信息,false 是無效的
//StringresponseTxt = CheckURL.check(alipayNotifyURL);
Mapparams = new HashMap();
//得到POST 過來參數設置到新的params中
for(Iterator iter = requestParams.keySet().iterator(); iter
.hasNext();){
Stringname = (String) iter.next();
String[]values = (String[]) requestParams.get(name);
StringvalueStr = "";
for(int i = 0; i < values.length; i++) {
valueStr= (i == values.length - 1) ? valueStr + values[i] :valueStr + values[i] + ",";
}
params.put(name,valueStr);
}
//二、校驗支付結果
StringpayStatus = "1";
Stringmysign = com.alipay.util.SignatureHelper.sign(params,privateKey);
//驗證
booleanverifySuccess = mysign.equalsIgnoreCase(sign);
//獲取支付交易狀態
booleantradeFinished = tradeStatus
.equalsIgnoreCase("TRADE_SUCCESS")
||tradeStatus.equalsIgnoreCase("TRADE_FINISHED");
if(verifySuccess&& tradeFinished)
{
//TODO 調用agbweb接口告知支付結果
PayReturnVovos = (PayReturnVo) request.getAttribute("returnStr");
StringwebPartengId = vos.getWebPartentId();
//經過DB獲取阿里支付Config信息
AliPayAccountDaoImplaccount = new AliPayAccountDaoImpl();
AliPayAccountVoaccVo = account.getAccountInfo(webPartengId);
Stringkey = accVo.getWebKey();
vos.setOutTradeNo(vos.getBillNo());
vos.setTotal_free(vos.getTotal_free());
vos.setPrivate_key(key);
StringnotifyType = vos.getNotifyType();
StringpayStatuss = vos.getPay_status();
// 支付銀行
if(notifyType.equals("trade_status_sync")) {
vos.setBankName("ALIPAY");
}else
vos.setBankName("QUICKMONEY");
// 支付結果
if(payStatuss.equals("TEADE_SUCCESS")|| payStatuss.equals("TEADE_FINISHED")){
// 阿里-支付成功
vos.setTradeFlag("ALIPAY_T");
}
returnStr= PaymentJsonUtil.beanToJson(vos);
// 原封Json+key
StringreturnStrWithKey = key + returnStr;
// MD5加密
StringbyteMD5 = MD5Util.MD5Encode(returnStrWithKey);
returnMsg(request,response, returnStr , byteMD5);
}else if (!verifySuccess) { // "AliPay返回的結果信息認證沒有經過"
//}else if (false) { // "AliPay返回的結果信息認證沒有經過"
thrownew BankpayException("Alipay支付返回失敗");
}else { // AliPay返回沒有TRADE_FINISHED
thrownew BankpayException("Alipay支付返回失敗");
}
}catch (Exception e) {
e.printStackTrace();
}
return returnStr;
}
3)回調商戶網站的接口,告知支付狀態以及回調的訂單信息。
publicstatic void returnMsg(HttpServletRequest request,
HttpServletResponseresponse, String strMsg , String strMD5)
try{
URLurl = new URL(
"http://10.1.126.10:8080/agb/payResponse.servlet?str="+ strMsg + "&strMD5=" + strMD5);
HttpURLConnectionhttp = (HttpURLConnection) url.openConnection();
http.setRequestMethod("POST");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout","30000");// 鏈接超時30秒
System.setProperty("sun.net.client.defaultReadTimeout","30000"); // 讀取超時30秒
http.connect();
//TODO 把數據回寫到agbweb
OutputStreamos = http.getOutputStream();
//os.write(strMsg.getBytes("UTF-8"));//傳入參數
os.flush();
os.close();
InputStreamis = http.getInputStream();
}catch (IOException e) {
e.printStackTrace();
throw(e);
}
}
4)被支付機構接收的訂單有可能存在回調失敗等狀況,雖然這種狀況是百萬分之一的機會,但爲了防止交易過程沒有
進行回調,也能夠經過Spring的定時任務註解:@Scheduled註解進行「對帳接口」的定時對帳,在此不進行詳細
介紹,接口名爲「Sign_trade_query」。
2.4 商戶網站響應支付平臺回調:
1)流獲取,轉換String UTF-8;
2)解密,Json轉化爲Vo;
3)執行某個Service/Bo;
4)更新DB,訂單表等;
5)回寫頁面,告知用戶支付結果。
本篇日誌僅大體描述了支付寶交易的一次請求流程:
1)商戶網站(訂單加密)
2)訂單解密)支付平臺(構造url)
3)阿里接口
4)封裝訂單vo -- 支付平臺 -- 訂單加密,模擬請求
5)商戶網站(db操做訂單)的操做流程。其中包括其中的4次加密以及2次回調和兩次模擬的http請求。其餘第三方或銀聯支付平臺與此結構大體同樣,只是API中的參數或構造URL的方式,加密算法有個別差別。