今天研究了一下銀聯(lián)的支付系統(tǒng),關(guān)于銀聯(lián)的支付集成,我真的也是醉了,官方文檔寫的和屎一樣,主要里面還有坑,官方頁面展示的文檔和官方下載的代碼Demo在參數(shù)的解釋上有明顯不同(關(guān)于后臺回調(diào)地址字段),里面還有錯別字,還有注釋寫錯的,我真是無言以對,不知道銀聯(lián)怎么想的~~~~
正題
銀聯(lián)開放的接口主要分兩類,一類是針對pc端的,一類是針對移動端的。

簡單解釋下上面的幾種類型
PC端
網(wǎng)關(guān)支付:主要針對B2C電商網(wǎng)站,用戶發(fā)起支付需要跳轉(zhuǎn)到銀聯(lián)頁面完成支付。
無跳轉(zhuǎn)支付:是銀聯(lián)為一些大型電商網(wǎng)站開的小灶,不需要跳轉(zhuǎn)頁面。
企業(yè)網(wǎng)銀支付:多家銀行企業(yè)網(wǎng)銀統(tǒng)一支付(這個我沒仔細(xì)看,有興趣的可以自行研究)。
移動端
手機控件支付:類似APP集成支付寶SDK那種模式。
手機網(wǎng)頁支付(wap):類似于PC端的網(wǎng)關(guān)支付。
銀聯(lián)云閃和Apple Pay我也沒仔細(xì)看........
下面我們以 “手機控件支付”為例簡單說下流程,然后看個小例子,
先看個流程圖,

這時銀聯(lián)官網(wǎng)的流程圖,我懷疑他是用word畫的.....
交易步驟:
1、 用戶通過商戶App終端購買商品;
2、 商戶后臺接收商品購買請求,生成訂單發(fā)送訂單推送請求(簽名)至銀聯(lián);
3、 銀聯(lián) 同步返回對應(yīng)該訂單的交易流水號至商戶后臺(驗簽);
4、 商戶后臺轉(zhuǎn)發(fā)交易流水號至商戶App,并跳轉(zhuǎn)至銀聯(lián)安全支付控件頁面;
5、 用戶輸入支付相關(guān)支付信息(如密碼),完成支付操作;
6、 支付成功完成后,控件通過前臺通知的方式向商戶系統(tǒng)發(fā)送交易結(jié)果;
7、 銀聯(lián)交易成功后,主動將支付結(jié)果以post方式通知商戶后臺,銀聯(lián)采用通知重發(fā)機制告知商戶后臺(需要驗簽)。
基本和集成支付寶和微信類似,但是銀聯(lián)比較煩的一點就是他有各種證書,簽名和驗簽時都需要用到證書。
現(xiàn)在我們可以去銀聯(lián)的官網(wǎng)下載一個demo示例運行感受下。
地址:https://open.unionpay.com/ajweb/help/file/techFile?productId=3
導(dǎo)入eclipse后大概是這個樣子,不知道為啥有個紅叉,請忽略,懶得去解決了,只是看個源碼而已。

其中 sdk 包下存放的是精華,是銀聯(lián)為我們封裝好的一些工具類,比如簽名和驗簽等方法,其余的大部分是示例代碼,
assets文件夾下存放的是一些配置文件和相關(guān)證書,不一一解釋了,在代碼注釋中有詳細(xì)的解釋。
現(xiàn)在看下支付的調(diào)用過程
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String merId = req.getParameter("merId");
String txnAmt = req.getParameter("txnAmt");
String orderId = req.getParameter("orderId");
String txnTime = req.getParameter("txnTime");
Map<String, String> contentData = new HashMap<String, String>();
/***銀聯(lián)全渠道系統(tǒng),產(chǎn)品參數(shù),除了encoding自行選擇外其他不需修改***/
contentData.put("version", DemoBase.version); //版本號 全渠道默認(rèn)值
contentData.put("encoding", DemoBase.encoding); //字符集編碼 可以使用UTF-8,GBK兩種方式
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //簽名方法
contentData.put("txnType", "01"); //交易類型 01:消費
contentData.put("txnSubType", "01"); //交易子類 01:消費
contentData.put("bizType", "000201"); //填寫000201
contentData.put("channelType", "08"); //渠道類型 08手機
/***商戶接入?yún)?shù)***/
contentData.put("merId", merId); //商戶號碼,請改成自己申請的商戶號或者open上注冊得來的777商戶號測試
contentData.put("accessType", "0"); //接入類型,商戶接入填0 ,不需修改(0:直連商戶, 1: 收單機構(gòu) 2:平臺商戶)
contentData.put("orderId", orderId); //商戶訂單號,8-40位數(shù)字字母,不能含“-”或“_”,可以自行定制規(guī)則
contentData.put("txnTime", txnTime); //訂單發(fā)送時間,取系統(tǒng)時間,格式為YYYYMMDDhhmmss,必須取當(dāng)前時間,否則會報txnTime無效
contentData.put("accType", "01"); //賬號類型 01:銀行卡02:存折03:IC卡帳號類型(卡介質(zhì))
contentData.put("txnAmt", txnAmt); //交易金額 單位為分,不能帶小數(shù)點
contentData.put("currencyCode", "156"); //境內(nèi)商戶固定 156 人民幣
// 請求方保留域,
// 透傳字段,查詢、通知、對賬文件中均會原樣出現(xiàn),如有需要請啟用并修改自己希望透傳的數(shù)據(jù)。
// 出現(xiàn)部分特殊字符時可能影響解析,請按下面建議的方式填寫:
// 1. 如果能確定內(nèi)容不會出現(xiàn)&={}[]"'等符號時,可以直接填寫數(shù)據(jù),建議的方法如下。
// contentData.put("reqReserved", "透傳信息1|透傳信息2|透傳信息3");
// 2. 內(nèi)容可能出現(xiàn)&={}[]"'符號時:
// 1) 如果需要對賬文件里能顯示,可將字符替換成全角&={}【】“‘字符(自己寫代碼,此處不演示);
// 2) 如果對賬文件沒有顯示要求,可做一下base64(如下)。
// 注意控制數(shù)據(jù)長度,實際傳輸?shù)臄?shù)據(jù)長度不能超過1024位。
// 查詢、通知等接口解析時使用new String(Base64.decodeBase64(reqReserved), DemoBase.encoding);解base64后再對數(shù)據(jù)做后續(xù)解析。
// contentData.put("reqReserved", Base64.encodeBase64String("任意格式的信息都可以".toString().getBytes(DemoBase.encoding)));
//后臺通知地址(需設(shè)置為外網(wǎng)能訪問 http https均可),支付成功后銀聯(lián)會自動將異步通知報文post到商戶上送的該地址,【支付失敗的交易銀聯(lián)不會發(fā)送后臺通知】
//后臺通知參數(shù)詳見open.unionpay.com幫助中心 下載 產(chǎn)品接口規(guī)范 網(wǎng)關(guān)支付產(chǎn)品接口規(guī)范 消費交易 商戶通知
//注意:1.需設(shè)置為外網(wǎng)能訪問,否則收不到通知 2.http https均可 3.收單后臺通知后需要10秒內(nèi)返回http200或302狀態(tài)碼
// 4.如果銀聯(lián)通知服務(wù)器發(fā)送通知后10秒內(nèi)未收到返回狀態(tài)碼或者應(yīng)答碼非http200或302,那么銀聯(lián)會間隔一段時間再次發(fā)送??偣舶l(fā)送5次,銀聯(lián)后續(xù)間隔1、2、4、5 分鐘后會再次通知。
// 5.后臺通知地址如果上送了帶有?的參數(shù),例如:http://abc/web?a=b&c=d 在后臺通知處理程序驗證簽名之前需要編寫邏輯將這些字段去掉再驗簽,否則將會驗簽失敗
contentData.put("backUrl", DemoBase.backUrl);
/**對請求參數(shù)進行簽名并發(fā)送http post請求,接收同步應(yīng)答報文**/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding); //報文中certId,signature的值是在signData方法中獲取并自動賦值的,只要證書配置正確即可。
String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl(); //交易請求url從配置文件讀取對應(yīng)屬性文件acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData,requestAppUrl,DemoBase.encoding); //發(fā)送請求報文并接受同步應(yīng)答(默認(rèn)連接超時時間30秒,讀取返回結(jié)果超時時間30秒);這里調(diào)用signData之后,調(diào)用submitUrl之前不能對submitFromData中的鍵值對做任何修改,如果修改會導(dǎo)致驗簽不通過
/**對應(yīng)答碼的處理,請根據(jù)您的業(yè)務(wù)邏輯來編寫程序,以下應(yīng)答碼處理邏輯僅供參考------------->**/
//應(yīng)答碼規(guī)范參考o(jì)pen.unionpay.com幫助中心 下載 產(chǎn)品接口規(guī)范 《平臺接入接口規(guī)范-第5部分-附錄》
if(!rspData.isEmpty()){
if(AcpService.validate(rspData, DemoBase.encoding)){
LogUtil.writeLog("驗證簽名成功");
String respCode = rspData.get("respCode") ;
if(("00").equals(respCode)){
//成功,獲取tn號
//String tn = resmap.get("tn");
//TODO
}else{
//其他應(yīng)答碼為失敗請排查原因或做失敗處理
//TODO
}
}else{
LogUtil.writeErrorLog("驗證簽名失敗");
//TODO 檢查驗證簽名失敗的原因
}
}else{
//未返回正確的http狀態(tài)
LogUtil.writeErrorLog("未獲取到返回報文或返回http狀態(tài)碼非200");
}
String reqMessage = DemoBase.genHtmlResult(reqData);
String rspMessage = DemoBase.genHtmlResult(rspData);
resp.getWriter().write("請求報文:<br/>"+reqMessage+"<br/>" + "應(yīng)答報文:</br>"+rspMessage+"");
}
詳細(xì)解釋代碼中都有,
注意??!這里面說下遇到的一個比較嚴(yán)重的坑,,,,
在從官方下載的demo中,就是上面看的那個代碼,有如下配置文件

這里面有兩個參數(shù),他的解釋分別是
前臺通知地址backUrl(流程圖 6) 和 后臺通知地址frontUrl(流程圖7)
但是官方網(wǎng)頁文檔上有如下解釋

backUrl 代碼中說他是前臺通知地址,,網(wǎng)頁文檔說他是后臺通知地址,不知道銀聯(lián)在想什么。。。
我搞了2個小時,終于找到的銀聯(lián)的官方 pdf版文檔,有如下解釋

說因為兼容控件第一期未采用 frontUrl 方式來做 支付控件給 商戶APP返回結(jié)果,
我個人覺得還是這個比較靠譜一點,這兩個參數(shù)正確的解釋是
后臺通知地址backUrl(對應(yīng)流程圖 7)
前臺通知地址frontUrl(對應(yīng)流程圖6)
代碼中的注釋是錯誤的,真是太坑了,
現(xiàn)在把項目跑起來看看吧

點擊消費

成功,nice!
還有個后臺異步通知,就不演示了,可以去官網(wǎng)下代碼搞一搞,里面有配置好的測試環(huán)境,還算好用,就是注釋有些錯的,切記以官網(wǎng)pdf版為準(zhǔn)!