銀聯支付開發、使用的一些總結
現在的網頁支付(PC和微信H5)和app支付,用的比較多的是微信支付、銀聯支付和支付寶支付,其餘的是這些支付的第三方支付,我目前瞭解的只有這麼多。我目前做了銀聯支付和微信支付,這裡說一些銀聯支付的開發的一些介紹吧。
根據我們公司的應用經驗,銀聯支付時費率最低的,如果和銀聯商務談判的好,自身的交易量比較大,費率可能更低(具體不能透露了)。銀聯支付一種是按百分比收取手續費,另一種按筆數收取手續費,例如,每筆0.9元。我們公司用了2種銀聯支付,一開始用的是銀聯支付,後來因費率問題,使用銀聯支付第三方平臺--千引支付,實質上也是銀聯支付。
我們申請的銀聯支付,在網上營業廳、微信服務號、Android和iOS平臺都在使用,是“手機WAP支付產品”。主要用了2個介面,第一個是“消費類交易”,即銀聯支付申請介面。它也可以分為兩部分,(1)通過拼接報文,簽名加密,生成html網頁,瀏覽器跳轉請求到銀聯支付頁面。此時,訂單生成,使用者可以在銀聯頁面完成支付。(2)支付成功後,銀聯會根據請求引數裡的配置,主動回撥前臺、後臺通知地址,通知使用者支付成功。第二個介面是“交易狀態查詢交易”,即查詢訂單交易結果。
現在具體講一下程式碼:
消費類交易介面:官方文件地址:https://open.unionpay.com/ajweb/help/api
(1)交易申請。初始化證書,拼接報文,資料簽名,建立表單或取得tn號。呼叫此介面,微信、網廳可得到一個html的https post表單,瀏覽器執行js自動提交,訪問銀聯支付申請介面,跳轉銀聯支付頁面。此時,服務端只是拼接報文,簽名加密,請求的動作,是使用者的瀏覽器做的,可以保證銀聯支付的安全性。app端則是,呼叫介面,取得tn號,呼叫app已安裝的銀聯控制元件跳往銀聯支付頁面。
private String pc2UnionPay(String orderId, String incMoney,
String txnTime) {
/**
* 初始化證書
*/
SDKConfig.getConfig().loadPropertiesFromSrc();// 從classpath載入acp_sdk.properties檔案
/**
* 交易請求url 從配置檔案讀取
*/
String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();
/**
* 組裝請求報文
*/
Map<String, String> data = new HashMap<String, String>();
// 版本號
data.put("version", "5.0.0");
// 字符集編碼 預設"UTF-8"
String encoding = "UTF-8";
data.put("encoding", encoding);
// 簽名方法 01 RSA
data.put("signMethod", "01");
// 交易型別 01-消費
data.put("txnType", "01");
// 交易子型別 01:自助消費 02:訂購 03:分期付款
data.put("txnSubType", "01");
// 業務型別 000201 B2C閘道器支付
data.put("bizType", "000201");
// 渠道型別 07-網際網路渠道
data.put("channelType", "07");
// 商戶/收單前臺接收地址 選送
// 後臺服務對應的寫法參照 FrontRcvResponse.java
data.put("frontUrl", frontUrl);
// 商戶/收單後臺接收地址 必送
// 後臺服務對應的寫法參照 BackRcvResponse.java
data.put("backUrl", backUrl);
// 接入型別:商戶接入填0 0- 商戶 , 1: 收單, 2:平臺商戶
data.put("accessType", "0");
// 商戶號碼
data.put("merId", merId);// 898310049004002
// 訂單號 商戶根據自己規則定義生成,每訂單日期內不重複
data.put("orderId", orderId);
// 訂單傳送時間 格式: YYYYMMDDhhmmss 商戶傳送交易時間,根據自己系統或平臺生成
data.put("txnTime", txnTime);
// 交易金額 分
data.put("txnAmt", incMoney);
// 交易幣種
data.put("currencyCode", "156");
// 若報文中的資料元標識的key對應的value為空,不上送該報文域
/**
* 建立表單
*/
String html = null;
for (int i = 1; i <= 3; i++) {
html = createHtml(requestFrontUrl, signData(data));
if (html != null) {
return html;
}
}
return "伺服器忙";
}
public static String createHtml(String action, Map<String, String> hiddens) {
StringBuffer sf = new StringBuffer();
sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/></head><body>");
sf.append("<form id = \"pay_form\" action=\"" + action
+ "\" method=\"post\">");
if (null != hiddens && 0 != hiddens.size()) {
Set<Entry<String, String>> set = hiddens.entrySet();
Iterator<Entry<String, String>> it = set.iterator();
while (it.hasNext()) {
Entry<String, String> ey = it.next();
String key = ey.getKey();
String value = ey.getValue();
sf.append("<input type=\"hidden\" name=\"" + key + "\" id=\""
+ key + "\" value=\"" + value + "\"/>");
}
}
sf.append("</form>");
sf.append("</body>");
sf.append("<script type=\"text/javascript\">");
sf.append("document.all.pay_form.submit();");
sf.append("</script>");
sf.append("</html>");
return sf.toString();
}
(2)支付成功通知商戶。根據申請時取得的前後臺通知地址,銀聯在支付成功後,會主動通知商戶交易結果,但應以後臺通知為準。前臺應答是通過 瀏覽器,使用者點選“返回商戶”按鈕傳送給商戶的,後臺通知是銀聯絡統非同步把交易狀態為成功的交易通知給商戶,失敗交易不傳送。前臺應答及後臺通知,商戶都需要驗籤。
public class BackNoticeServlet extends HttpServlet {
private static final long serialVersionUID = -900467945307979675L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
LogUtil.writeLog("後臺接收銀行通知開始……");
BankService bService = (BankService) PayCache.getInstance().getBean("bankService");
RechargeService service = (RechargeService)PayCache.getInstance().getBean("rechargeService");
// request.setCharacterEncoding("ISO-8859-1");
String encoding = request.getParameter(SDKConstants.param_encoding);
// 獲取請求引數中所有的資訊
Map<String, String> reqParam = getAllRequestParam(request);
// 列印請求報文
LogUtil.printRequestLog(reqParam);
Map<String, String> valideData = null;
if (null != reqParam && !reqParam.isEmpty()) {
Iterator<Entry<String, String>> it = reqParam.entrySet()
.iterator();
valideData = new HashMap<String, String>(reqParam.size());
while (it.hasNext()) {
Entry<String, String> e = it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
value = new String(value.getBytes("ISO-8859-1"), encoding);
valideData.put(key, value);
}
Map<String, String> res = new HashMap<String, String>();
// 驗證簽名
if (!SDKUtil.validate(valideData, encoding)) {
LogUtil.writeLog("驗證簽名結果[失敗].");
} else {
LogUtil.writeLog("驗證簽名結果[成功].");
// 持久化銀行返回的資料
LogUtil.writeLog("持久化銀行返回的資料");
String settleDate = reqParam.get("settleDate");
Calendar cal=Calendar.getInstance();
settleDate = String.valueOf(cal.get(Calendar.YEAR)) + settleDate;//加上年份
reqParam.put("settleDate", settleDate);
service.addPayresult(reqParam);
if("00".equals(reqParam.get("respCode"))){
service.updOrder(reqParam.get("orderId"),settleDate);
}
LogUtil.writeLog("返回銀行資料給商戶開始……");
res.put("respCode", reqParam.get("respCode"));
res.put("respMsg", reqParam.get("respMsg"));
res.put("orderId", reqParam.get("orderId"));
res.put("txnTime", reqParam.get("txnTime"));
res.put("txnAmt", reqParam.get("txnAmt"));
res.put("queryId", reqParam.get("queryId"));
res.put("settleDate", reqParam.get("settleDate"));
res.put("settleAmt", reqParam.get("settleAmt"));
res.put("txnType", reqParam.get("txnType"));
String statusCode = "";
ProfitApplyOrder order = bService.queryOrder(reqParam.get("orderId"));
for(int i=0;i<5;i++){
statusCode = XmlUtil.invokeMethod(res,order.getBackUrl());
if(statusCode.equals("success")){
LogUtil.writeLog("返回銀行資料給商戶結束[成功].");
break;
}
}
LogUtil.writeLog("銀行資料接受完成!");
}
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.writeLog("接受銀行資料失敗:" + e.getMessage());
} finally {
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.print(System.currentTimeMillis());
writer.flush();
}
}
public static Map<String, String> getAllRequestParam(
final HttpServletRequest request) {
Map<String, String> res = new HashMap<String, String>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
// 在報文上送時,如果欄位的值為空,則不上送<下面的處理為在獲取所有引數資料時,判斷若值為空,則刪除這個欄位>
// System.out.println(" temp資料的鍵=="+en+" 值==="+value);
if (null == res.get(en) || "".equals(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
}
交易狀態查詢交易:(以下摘自銀聯官方文件)
商戶收到後臺通知後,應根據通知報文中訂單號等關鍵資訊,更新系統中的訂單支付狀態。
商戶在以下情況下,可主動發起交易狀態查詢,並根據查詢結果更新交易狀態。
(1)前臺交易,在5分鐘內未收到後臺通知。
(2)後臺交易,在1秒內未收到後臺通知 。
(3)商戶在特殊情況下沒有正確處理後臺通知,需要重新判定交易狀態。
商戶應注意對同一筆交易,對不同時間點可能先後收到的查詢結果及後臺通知進行合適的處理。
以上,此介面呼叫查詢訂單交易結果,是商戶主動發起的,沒什麼好說的,把官方文件貼了一下。附上交易應答碼地址:https://open.unionpay.com/ajweb/help?id=262