最近某項(xiàng)目中涉及到支付的模塊與涉及流程,在此和大家分享一下。
1,名詞釋義
商戶網(wǎng)站:比如淘寶,聚美,唯品會這種B2C/C2C的網(wǎng)站及后臺的管理系統(tǒng),統(tǒng)稱為商戶網(wǎng)站;主要負(fù)責(zé)對買家訂單數(shù)據(jù)的封裝,加密,
及支付平臺回調(diào)的訂單處理。
支付平臺:我們需要開發(fā)的支付平臺,支付接口,支付模擬的Servlet,暴露出來的WebService接口url等;主要負(fù)責(zé)對買家請求來的
加密后的訂單數(shù)據(jù)進(jìn)行解密,構(gòu)造請求的URL,拼接參數(shù),對Sign進(jìn)行加密,對支付機(jī)構(gòu)異步(或同步)請求回調(diào)的數(shù)據(jù)
進(jìn)行封裝,解密回傳給商戶網(wǎng)站。
支付機(jī)構(gòu):比如阿里支付寶,快錢,騰訊財(cái)付通,易寶支付這種第三方支付平臺等支付機(jī)構(gòu)。
Sign:支付機(jī)構(gòu)為商戶分配的一把“密鑰”與”合作者ID“同時(shí)分配,用做調(diào)用Base64,MD5等加密算法在加密解密時(shí)的一種私鑰,通常
與此相關(guān)聯(lián)的還有signType,就是加密方式。
回調(diào):對上次請求端request中的url或指定的url進(jìn)行http請求,或https請求
支付平臺請求,響應(yīng),及回調(diào)流程圖:
2,業(yè)務(wù)流設(shè)計(jì)(本文只介紹alipay的即時(shí)到賬接口:"create_direct_pay_by_user")
2.1商戶網(wǎng)站對數(shù)據(jù)封裝加密,調(diào)用支付接口:
2.1.1)商戶網(wǎng)站后臺對買家的訂單進(jìn)行封裝,插入商戶網(wǎng)站db中的訂單表(比如:xxx_order);
PayReturnVovo= new PayReturnVo();
vo.setOrderId("kuaiqian00232");
vo.setOrderAmount("20");
vo.setOrderTime("20140504121020");
vo.setProductName("3M網(wǎng)線,送水晶頭");
vo.setProductId("2213229319378");
vo.setProductNum("2");
vo.setPayType("00");*/
//把模擬的表單數(shù)據(jù)轉(zhuǎn)成Json
StringorderJson=PaymentJsonUtil.beanToJson(vo);
//通過db獲取商家key密鑰
Stringkey = dao.getKeyByUserId(userId);
//根據(jù)key使用base64加密算法對訂單信息進(jìn)行加密
StringSignedJson = CryptUtil.encryptBase64Des(orderJson,key);
2.1.2)于此同時(shí)調(diào)用dao層查詢買家用戶平臺賬戶余額,并進(jìn)行鎖表:在SQL的select后加入forupdatewait n(最好
為1-5秒,此處的數(shù)值為httpclient請求超時(shí)時(shí)長)為防止訂單被多用戶修改。
2.2支付平臺響應(yīng)請求及解密,調(diào)用支付機(jī)構(gòu)接口:
2.2.1)支付平臺響應(yīng)請求,對數(shù)據(jù)進(jìn)行解密;
//獲取輸入?yún)?shù)
InputStreamis= request.getInputStream();
//把接收的加密流轉(zhuǎn)成String類型
StringpayMsgJson= IOUtils.toString(is, "utf-8");
//base64進(jìn)行解密
byte[]byteJson= CryptUtil.decryptBASE64payMsgJson
StringstrJson= new String(byteJson,"UTF-8");
//把解密后的json轉(zhuǎn)換成實(shí)體vo
try{
pVo =(BankPayVo)PaymentJsonUtil.jsonToBean(strJson,BankPayVo.class);
}catch (Exception e){
e.printStackTrace();
throw(e);
}
2.2.2)從db查詢商戶協(xié)議信息,構(gòu)造不同方式的支付機(jī)構(gòu)所需請求的url;
publicString CreateUrl(PayBankEntity payBankEntity) throwsBankpayException,SQLException{
StringwebPartentId = payBankEntity.getWebPartentId();
//通過DB獲取阿里支付Config信息
AliPayAccountDaoImplaccount= new AliPayAccountDaoImpl();
AliPayAccountVoaccVo=account.getAccountInfo(webPartentId);
//根據(jù)訂單號區(qū)別b2a和b2c對partner參數(shù)設(shè)置
StringstrOrderNo=payBankEntity.getOrderNo();
//阿里支付合作伙伴ID
Stringpartner =accVo.getPaPartner();
//阿里支付key
Stringkey=accVo.getPaKey();
//阿里支付接口
Stringpaygateway= accVo.getPaPayGateWay();
//阿里支付服務(wù)名
Stringservice= accVo.getPaService();
//阿里支付簽名Sign加密方式
Stringsign_type= accVo.getPaSignType();
//賣家賬號,郵箱
Stringseller_email =accVo.getPaSellerEmail();
//######Form Web ######商戶網(wǎng)站訂單
Stringout_trade_no =payBankEntity.getOrderNo();
//###### Form Web ######交易總額
Stringtotal_fee= payBankEntity.getMoney();
//######Form Web######商品名稱
Stringsubject=payBankEntity.getProductId();
//######Form Web######商品展示地址
StringinputCharset=accVo.getPaInputCharset();
//######Form Web ######支付類型
Stringpayment_type=payBankEntity.getPaymentType();
//超時(shí)時(shí)長
Stringit_b_pay= accVo.getPaItBBay();
//!!!在此修改參數(shù)為異步notify_url但是vo和db中顯示為return_url
Stringreturn_url= accVo.getPaReturnUrl();
StringItemUrl="";
2.2.2.temp)PS:下行代碼的CreateUrl()是根據(jù)請求參數(shù)首字母降序排列,把參數(shù)重新構(gòu)造成新的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繪制跳轉(zhuǎn)請求的htmldom元素,把參數(shù)請求到支付機(jī)構(gòu);
publicString getBankHtml(PayBankEntity payBankEntity) throwsBankpayException {
StringBuffersbHtml = new StringBuffer();
try{
sbHtml.append("");
sbHtml.append("
支付網(wǎng)關(guān)");sbHtml.append("
sbHtml.append("
");}catch (Exception e) {
throw new BankpayException("系統(tǒng)異常,錯(cuò)誤描述:"+ e.getMessage());
}
returnsbHtml.toString();
}
2.2.4)切記不要忘記設(shè)置支付機(jī)構(gòu)回調(diào)支付平臺的回調(diào)url,大多數(shù)支付機(jī)構(gòu)的參數(shù)為同步和異步兩種,設(shè)置支付機(jī)構(gòu)的
回調(diào)url目的在于它進(jìn)行了我們的請求。處理之后對訂單數(shù)據(jù)及訂單等狀態(tài)的回寫,進(jìn)而支付平臺可以封裝,
加密成json串,繼續(xù)調(diào)用商戶網(wǎng)站,對這次支付的信息進(jìn)行更改,執(zhí)行具體業(yè)務(wù)。
下面是阿里的api,同步和異步回調(diào)路徑不能同時(shí)為空。
notify_url服務(wù)器異步通知頁面路徑String(160)支付寶服務(wù)器主動(dòng)通知商戶網(wǎng)站里指定的頁面Http路徑 可空
returl_url服務(wù)器同步通知頁面路徑String(160)支付寶完成處理后當(dāng)前頁面自動(dòng)跳轉(zhuǎn)到商戶網(wǎng)站的Http路徑 可空
下面是快錢的api,同步和異步回調(diào)路徑不能同時(shí)為空。
pageUrl接受支付結(jié)果的頁面地址String(256)需要是絕對地址,與bgUrl不能同時(shí)為空,當(dāng)bgUrl為空時(shí),生效 可空
bgUrl接受支付結(jié)果后臺代碼地址String(256)需要是絕對地址,與pageUrl不能同時(shí)為空,當(dāng)pageUrl為空時(shí),生效可空
2.3支付平臺響應(yīng)支付機(jī)構(gòu)回調(diào):被支付機(jī)構(gòu)接收的訂單支付成功或失敗之后,回調(diào)我們支付平臺的接口。
1)把支付寶的請求輸入流轉(zhuǎn)成我們需要的vo對象,調(diào)用2)中的performTask()。
//獲取輸入?yún)?shù)
InputStreamis = request.getInputStream();
//轉(zhuǎn)成String類型
StringpayMsgJson =IOUtils.toString(is, "utf-8");
PayReturnVovos = PaymentJsonUtil.jsonToBean(payMsgJson,PayReturnVo.class);
request.setAttribute("returnStr",vos);
newAliPayReturnBo().performTask(request,response);
2)把支付寶的請求輸入流轉(zhuǎn)成我們需要的vo對象,調(diào)用2)中的performTask()。
@SuppressWarnings("unused")
publicstatic String performTask(HttpServletRequestrequest,
HttpServletResponseresponse) throws IOException, ServletException{
StringreturnStr = "";
StringwebPartentId = "";
try{
Stringsign = request.getParameter("sign");
//支付狀態(tài):TRADE_FINISHED(普通即時(shí)到賬的交易成功狀態(tài))||TRADE_SUCCESS(開通
了高級即時(shí)到賬或機(jī)票分銷產(chǎn)品后的交易成功狀態(tài))
StringtradeStatus =request.getParameter("trade_status");
//訂單編號
StringorderNo = request.getParameter("out_trade_no");
//通知類型
Stringnotify_type = request.getParameter("notify_type");
//支付寶交易流水號
Stringtrade_no = "";
//訂單總價(jià)
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返回結(jié)果,true是正確的訂單信息,false是無效的
//StringresponseTxt = CheckURL.check(alipayNotifyURL);
Mapparams = new HashMap();
//獲得POST過來參數(shù)設(shè)置到新的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);
}
//2、校驗(yàn)支付結(jié)果
StringpayStatus = "1";
Stringmysign =com.alipay.util.SignatureHelper.sign(params,privateKey);
//驗(yàn)證
booleanverifySuccess = mysign.equalsIgnoreCase(sign);
//獲取支付交易狀態(tài)
booleantradeFinished = tradeStatus
.equalsIgnoreCase("TRADE_SUCCESS")
||tradeStatus.equalsIgnoreCase("TRADE_FINISHED");
if(verifySuccess&& tradeFinished)
{
//TODO調(diào)用agbweb接口告知支付結(jié)果
PayReturnVovos = (PayReturnVo)request.getAttribute("returnStr");
StringwebPartengId = vos.getWebPartentId();
//通過DB獲取阿里支付Config信息
AliPayAccountDaoImplaccount = newAliPayAccountDaoImpl();
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");
//支付結(jié)果
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返回的結(jié)果信息認(rèn)證沒有通過"
//}elseif (false) { // "AliPay返回的結(jié)果信息認(rèn)證沒有通過"
thrownew BankpayException("Alipay支付返回失敗");
}else { // AliPay返回沒有TRADE_FINISHED
thrownew BankpayException("Alipay支付返回失敗");
}
}catch (Exception e) {
e.printStackTrace();
}
return returnStr;
}
3)回調(diào)商戶網(wǎng)站的接口,告知支付狀態(tài)以及回調(diào)的訂單信息。
publicstatic void returnMsg(HttpServletRequest request,
HttpServletResponseresponse, String strMsg , StringstrMD5)
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");//連接超時(shí)30秒
System.setProperty("sun.net.client.defaultReadTimeout","30000");//讀取超時(shí)30秒
http.connect();
//TODO把數(shù)據(jù)回寫到agbweb
OutputStreamos = http.getOutputStream();
//os.write(strMsg.getBytes("UTF-8"));//傳入?yún)?shù)
os.flush();
os.close();
InputStreamis = http.getInputStream();
}catch (IOException e) {
e.printStackTrace();
throw(e);
}
}
4)被支付機(jī)構(gòu)接收的訂單有可能存在回調(diào)失敗等情況,雖然這種情況是百萬分之一的機(jī)會,但為了防止交易過程沒有
進(jìn)行回調(diào),也可以通過Spring的定時(shí)任務(wù)注解:@Scheduled注解進(jìn)行“對賬接口”的定時(shí)對賬,在此不進(jìn)行詳細(xì)
介紹,接口名為“Sign_trade_query”。
2.4商戶網(wǎng)站響應(yīng)支付平臺回調(diào):
1)流獲取,轉(zhuǎn)換StringUTF-8;
2)解密,Json轉(zhuǎn)化為Vo;
3)執(zhí)行某個(gè)Service/Bo;
4)更新DB,訂單表等;
5)回寫頁面,告知用戶支付結(jié)果。
本篇日志僅大致描述了支付寶交易的一次請求流程:
1)商戶網(wǎng)站(訂單加密)
2)訂單解密)支付平臺(構(gòu)造url)
3)阿里接口
4)封裝訂單vo--支付平臺 --訂單加密,模擬請求
5)商戶網(wǎng)站(db操作訂單)的操作流程。
其中包括其中的4次加密以及2次回調(diào)和兩次模擬的http請求。其他第三方或銀聯(lián)支付平臺與此結(jié)構(gòu)大致一樣,只是API中的參數(shù)或構(gòu)造URL的方式,加密算法有個(gè)別差異。
僅供參考,個(gè)人覺得bo中的業(yè)務(wù)邏輯處理得還不夠細(xì)致,歡迎大家提出最寶貴的意見,一起探討學(xué)習(xí)。
以上。
愛華網(wǎng)


