银联支付接口接入–Java

in Java with 0 comment

应业务需求,需要接入银联在线支付和微信在线支付。这里暂且做个备忘吧。这里我们要做的是利用银联支付提供的API发起支付请求。当然,由于涉及在线支付,安全问题是不可避免的,这就需要数字签名和加、解密等等,这我们可以不考虑。

入网前准备

https://merchant.unionpay.com/portal/login.jsp这是银联商户网站,申请商户号,获取商户相关信息。

http://www.cfca.com.cn/ 这是中国金融认证中心,可以下载证书,需要填写商户的证书序列号、授权码和CSP;然后利用IE浏览器导出证书,再次,登录商户系统,上传安全证书,并启用这证书。下载步骤,可以先下载证书下载支持手册参考。

入网测试

https://open.unionpay.com/ajweb/index这是银联支付商家接入网址,可以下载网关支付的demo,一般都有测试商户和测试证书。

消费类交易
/**设置请求参数————->**/
        Map<String, String> requestData = new HashMap<String, String>();
        String orderId = new SimpleDateFormat(“yyyyMMddHHmmss”).format(new Date());
        String txnTime = new SimpleDateFormat(“yyyyMMddHHmmss”).format(new Date());
        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        requestData.put(“version”, DemoBase.version);   //版本号 全渠道默认值
        requestData.put(“encoding”, DemoBase.encoding); //字符集编码 可以使用UTF-8,GBK两种方式
        requestData.put(“signMethod”, “01”);            //签名方法 目前只支持01:RSA方式证书加密
        requestData.put(“txnType”, “01”);               //交易类型 01:消费,02:预授权
        requestData.put(“txnSubType”, “01”);            //交易子类型 01:自助消费,03:分期付款
        requestData.put(“bizType”, “000201”);           //业务类型 B2C网关支付,手机wap支付
        requestData.put(“channelType”, “07”);           //渠道类型 07:PC,08:移动端
        /***商户接入参数***/
        requestData.put(“merId”, “777290058110097”);    //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
        requestData.put(“accessType”, “0”);             //接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户)
        requestData.put(“orderId”,orderId);             //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则       
        requestData.put(“txnTime”, txnTime);            //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
        requestData.put(“currencyCode”, “156”);         //交易币种(境内商户一般是156 人民币)      
        requestData.put(“txnAmt”, “1000”);              //交易金额,单位分,不要带小数点
        requestData.put(“reqReserved”, “透传字段”);        //请求方保留域,透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节        
        //前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
        //如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
        //异步通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
        requestData.put(“frontUrl”, DemoBase.frontUrl);
        //后台通知地址(需设置为外网能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
        //后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
        //注意:1.需设置为外网能访问,否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码 
        //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200或302,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
        //    5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
        requestData.put(“backUrl”, DemoBase.backUrl);

上述是向银联网关的消费交易请求参数。需要填写自己的商户号、自定义订单号、前台通知地址(跳转银联网关,成功支付后返回商户的URL)和后台通知地址,透传字段可以忽略。可以将商户号、前台通知地址和后台通知地址通过配置文件的方式传入。

下载对账单
boolean flag = true;
        String settleDate = jyrq.substring(4);
        String merId = SDKConfig.getConfig().getMerId();
        //String merId = “700000000000001”;
        Map<String, String> data = new HashMap<String, String>();
        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        data.put(“version”, DemoBase.version);               //版本号 全渠道默认值
        data.put(“encoding”, DemoBase.encoding);             //字符集编码 可以使用UTF-8,GBK两种方式
        data.put(“signMethod”, “01”);                        //签名方法 目前只支持01-RSA方式证书加密
        data.put(“txnType”, “76”);                           //交易类型 76-对账文件下载
        data.put(“txnSubType”, “01”);                        //交易子类型 01-对账文件下载
        data.put(“bizType”, “000000”);                       //业务类型,固定
        /***商户接入参数***/
        data.put(“accessType”, “0”);                         //接入类型,商户接入填0,不需修改
        data.put(“merId”, merId);                            //商户代码,请替换正式商户号测试,如使用的是自助化平台注册的777开头的商户号,该商户号没有权限测文件下载接口的,请使用测试参数里写的文件下载的商户号和日期测。如需777商户号的真实交易的对账文件,请使用自助化平台下载文件。
        data.put(“settleDate”, settleDate);                  //清算日期,如果使用正式商户号测试则要修改成自己想要获取对账文件的日期, 测试环境如果使用700000000000001商户号则固定填写0119
        data.put(“txnTime”,DemoBase.getCurrentTime());       //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
        data.put(“fileType”, “00”);                          //文件类型,一般商户填写00即可
//应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if(“00”.equals(resmap.get(“respCode”))){
            //交易成功,解析返回报文中的fileContent并落地
            String filePath = null;
            String root = SystemConifg.billFilePath;
            if (SDKUtil.isEmpty(resmap.get(“fileName”))) {
                filePath = root + “YL” + jyrq + “.txt”;
            } else {
                filePath = root + resmap.get(“fileName”);
            }
            String tarName = “YL” + jyrq + “.txt”;
            DemoBase.deCodeFileContent(resmap,filePath);
            try {
                ZipUtil.unzip(filePath, root, tarName, DemoBase.encoding);
                flag = true;
                //String file = SystemConifg.billFilePath + tarName;
                //List<Map> parseZMFile = DemoBase.parseZMFile(file);
            } catch (Exception e) {
                flag = false;
            }
        }else{
            //其他应答码为失败请排查原因
            flag = false;
        }

上述是向银联网关的消费交易请求参数。清算日期的格式"MMDD",印象中,消费类交易和文件传输交易所测试的商户号并不是相同商户。下载后还要解压,最好用demo上的类对文件解析,并插入表中,方便对账。

解压
/**
     * 解压缩zip包 (用于银联支付对账下载)
     * 
     * @param srcName
     *          zip文件
     * @param tarPath
     *          目标路径
     * @param tarName
     *          目标文件名
     * @param encoding
     *          编码方式
     * @throws Exception
     */
    public static void unzip(String srcName,String tarPath,String tarName,String encoding) throws Exception {
        File file = new File(srcName);//压缩文件
        ZipFile zipFile = new ZipFile(file);//实例化ZipFile,每一个zip压缩文件都可以表示为一个ZipFile
        //实例化一个Zip压缩文件的ZipInputStream对象,可以利用该类的getNextEntry()方法依次拿到每一个ZipEntry对象
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file), Charset.forName(encoding));
        ZipEntry zipEntry = null;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            String fileName = zipEntry.getName();
            if(fileName.startsWith(“INN”)){
                File temp = new File(tarPath + tarName);
                if (! temp.getParentFile().exists())
                    temp.getParentFile().mkdirs();
                OutputStream os = new FileOutputStream(temp);
                //通过ZipFile的getInputStream方法拿到具体的ZipEntry的输入流
                InputStream is = zipFile.getInputStream(zipEntry);
                int len = 0;
                while ((len = is.read()) != –1){
                    os.write(len);
                }
                os.close();
                is.close();
            }
        }
        zipInputStream.close();
    }

上述,是解压ZIP工具类的方法。

解析账单文件
if(FileTranser.downloadBill(jyrq)){
                String file = SystemConifg.billFilePath + “YL” + jyrq + “.txt”;
                List<Map> parseZMFile = DemoBase.parseZMFile(file);
                for(int i=0;i<parseZMFile.size();i++){
                    Map map = parseZMFile.get(i);
                    Map<String,String> bill=new HashMap<String,String>();
                    bill.put(“QID”, (String)map.get(9)); //查询流水号
                    bill.put(“JYRQ”, jyrq); //交易日期
                    String ddh = (String)map.get(11);
                    if(StringUtils.isNotBlank(ddh)){
                        ddh = ddh.trim();
                    }
                    bill.put(“DDH”, ddh); //商户订单号
                    bill.put(“ZFLX”, “1”); //支付类型
                    String jysj = (String)map.get(4);
                    if(StringUtils.isNotBlank(jysj)){
                        jysj = jyrq.substring(0,4) + jysj;
                    }
                    bill.put(“JYSJ”, jysj); //交易时间
                    String je = (String)map.get(6);
                    //je = je.replaceAll(“^(0+)”, “”);
                    bill.put(“JE”, je); //金额
                    bill.put(“JYLX”, (String)map.get(19)); //交易类型
                    bill.put(“JYZT”, “1”); //交易状态
                    bill.put(“YSBW”, (String)map.get(50)); //原始报文
                    billList.add(bill);
                }
                if(billList!=null && billList.size()>0){
                    flag = srv.insertBill(billList);
                }
                else{
                    flag = false;
                }
            }
            else {
                flag = false;
            }

可以利用demo里的工具方法解析特定格式的方法。

退货类交易
/**
     * 
     * @param ddh
     *      新订单号
     * @param txnAmt
     *      退货金额
     * @param origQryId
     *      原反馈号
     * @return
     */
    public String refond(String ddh,String txnAmt,String origQryId) {
        String result = “”;
        Map<String, String> data = new HashMap<String, String>();
        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        data.put(“version”, DemoBase.version);               //版本号
        data.put(“encoding”, DemoBase.encoding);             //字符集编码 可以使用UTF-8,GBK两种方式
        data.put(“signMethod”, “01”);                        //签名方法 目前只支持01-RSA方式证书加密
        data.put(“txnType”, “04”);                           //交易类型 04-退货       
        data.put(“txnSubType”, “00”);                        //交易子类型  默认00      
        data.put(“bizType”, “000201”);                       //业务类型 B2C网关支付,手机wap支付 
        data.put(“channelType”, “07”);                       //渠道类型,07-PC,08-手机
        /***商户接入参数***/
        String merId = SDKConfig.getConfig().getMerId();
        data.put(“merId”, merId);                            //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
        data.put(“accessType”, “0”);                         //接入类型,商户接入固定填0,不需修改       
        data.put(“orderId”, ddh);                            //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生,不同于原消费      
        data.put(“txnTime”, DemoBase.getCurrentTime());      //订单发送时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效       
        data.put(“currencyCode”, “156”);                     //交易币种(境内商户一般是156 人民币)     
        data.put(“txnAmt”, txnAmt);                          //****退货金额,单位分,不要带小数点。退货金额小于等于原消费金额,当小于的时候可以多次退货至退货累计金额等于原消费金额     
        data.put(“reqReserved”, “”);                    //请求方保留域,透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节       
        data.put(“backUrl”,  SystemConifg.backUrl);               //后台通知地址,后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 退货交易 商户通知,其他说明同消费交易的后台通知
        /***要调通交易以下字段必须修改***/
        data.put(“origQryId”, origQryId);      //****原消费交易返回的的queryId,可以从消费交易后台通知接口中或者交易状态查询接口中获取
        /**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文————->**/
        Map<String, String> submitFromData  = DemoBase.signData(data);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
        //多证书使用方法:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false) 
        //Map<String, String> submitFromData = DemoBase.signData(data,”D:\\certs\\PM_700000000000001_acp.pfx”, “000000”);
        String url = SDKConfig.getConfig().getBackRequestUrl();//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        //如果这里通讯读超时(30秒),需发起交易状态查询交易
        Map<String, String> resmap = DemoBase.submitUrl(submitFromData, url);//这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
        /**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考————->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if(resmap.get(“respCode”).equals(“00”)){
            //交易已受理,等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。
            result = “<errcode>0</errcode><errmsg></errmsg><ddh>”+ddh+“</ddh>”
                    +“<returnmsg><![CDATA[“+resmap.toString()+“]]></returnmsg>”;
        }else if(resmap.get(“respCode”).equals(“03”) ||
                 resmap.get(“respCode”).equals(“04”) ||
                 resmap.get(“respCode”).equals(“05”)){
            //后续需发起交易状态查询交易确定交易状态
            result = “<errcode>0</errcode><errmsg>hangdling</errmsg><ddh>”+ddh+“</ddh>”;
        }else{
            //其他应答码为失败请排查原因
            result = “<errcode>1</errcode><errmsg>”+resmap.get(“respCode”)+resmap.get(“respMsg”)+“</errmsg>”;
        }
        return result;
    }

上述是银联退款请求参数,需要填写商户号、后台通知地址、新订单号、退款金额和原订单号。

这些交易都要和自己的具体业务结合。

测试环境配置文件

######SDK配置文件   证书的存放路径根据实际情况配置,交易地址和证书根据PM环境、生产环境配套配置#####
##########################入网测试环境交易发送地址(线上测试需要使用生产环境交易请求地址)#############################
##前台交易请求地址
acpsdk.frontTransUrl=https://101.231.204.80:5000/gateway/api/frontTransReq.do
##后台交易请求地址
acpsdk.backTransUrl=https://101.231.204.80:5000/gateway/api/backTransReq.do
##交易状态查询请求地址
acpsdk.singleQueryUrl=https://101.231.204.80:5000/gateway/api/queryTrans.do
##批量交易请求地址
acpsdk.batchTransUrl=https://101.231.204.80:5000/gateway/api/batchTransReq.do
##文件传输类交易地址(对账文件下载接口)
acpsdk.fileTransUrl=https://101.231.204.80:9080/
#########################入网测试环境签名证书配置 ################################
##签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到
#windows下
acpsdk.signCert.path=D\:\\certs\\acp_test_sign.pfx
#linux下(注意:在linux下读取证书需要保证证书有被应用读的权限)
#acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp700000000000001.pfx
##签名证书密码,测试环境固定000000,生产环境请修改为从cfca下载的正式证书的密码,正式环境证书密码位数需小于等于6位,否则上传到商户服务网站会失败
acpsdk.signCert.pwd=000000
##签名证书类型,固定不需要修改
acpsdk.signCert.type=PKCS12
##########################验签证书配置################################
##验证签名证书目录,只配置到目录即可,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试验证证书,生产环境所有商户共用开发包中的生产验签证书
#windows下
acpsdk.validateCert.dir=D\:\\certs\\
#linux下(注意:在linux下读取证书需要保证证书有被应用读的权限)
#acpsdk.validateCert.dir=/SERVICE01/usr/ac_frnas/conf/ACPtest/
##########################加密证书配置################################
##密码加密证书路径(有业务需求时才需要配置此项,本产品不需要使用)
acpsdk.encryptCert.path=d:\\certs\\encrypt.cer
##是否启用多证书模式(true:单证书|false:多证书—没有配置此项时,默认为单证书模式)
acpsdk.singleMode=true
##开发测试777290058110097
merId=

正式环境配置文件

acpsdk.frontTransUrl=https://gateway.95516.com/gateway/api/frontTransReq.do
acpsdk.backTransUrl=https://gateway.95516.com/gateway/api/backTransReq.do
acpsdk.singleQueryUrl=https://gateway.95516.com/gateway/api/queryTrans.do
acpsdk.batchTransUrl=https://gateway.95516.com/gateway/api/batchTransReq.do
acpsdk.fileTransUrl=https://filedownload.95516.com/
acpsdk.signCert.path=D\:\\certs\\自己的私钥.pfx
#acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp700000000000001.pfx
acpsdk.signCert.pwd=自己的私钥密码
acpsdk.signCert.type=PKCS12
acpsdk.validateCert.dir=D\:\\certs\\
#acpsdk.validateCert.dir=/SERVICE01/usr/ac_frnas/conf/ACPtest/
#acpsdk.encryptCert.path=d:\\certs\\encrypt.cer
acpsdk.singleMode=true
merId=自己的商户号,原demo不是写在这边

需要填写自己的证书路径,密码和商户入网测试成功后,切换到正式环境,调试下能否支付成功,最低消费1毛钱。

可以利用帮助中心,查看FAQ https://open.unionpay.com/ajweb/help/faq/list

这是测试环境的测试卡信息 https://open.unionpay.com/ajweb/help/faq/list?id=4&level=0&from=0