1. 程式人生 > >銀聯支付開發、使用的一些總結

銀聯支付開發、使用的一些總結

        現在的網頁支付(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