银联支付开发、使用的一些总结

 

        现在的网页支付(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);
        // 订单号 商户根据自己规则定义生成,每订单日期内不重复
        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

相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页