Java接入银联支付ChinaPay

一、介绍

        中国银联是中国银行卡联合组织,通过银联跨行交易清算系统,实现商业银行系统间的互联互通和资源共享,保证银行卡跨行、跨地区和跨境的使用。

        效果如下:

二、接入准备

        当了解了银联支付后,首先需要和银联方联系,会有相关的工作人员提供相关支持,工作人员会发送邮件提供开发文档和相关文件。

  • 开发文档:

        邮件会提供中英两种文档,ChinaPay_新一代_商户接入手册_20240307.pdf 和ChinaPay Merchants Integration Guidence 20190815.pdf,详细的对接相关介绍、流程图、入参介绍、返回值介绍等都会在文档中给出。

  • 网关公钥:

        **********.cer,邮件中会给出网关公钥

  • 交易证书和相关密码:

        ********.pfx和密码,交易证书需要在银联商务商户服务管理系统申请,银联的对接相关工作人员会给出具体的申请文档,文档中步骤很详细,这里就不赘述了

  • 私钥:

        *******.sm2,私钥在申请交易证书后导出时一同导出

  • 插件包:

        邮件中会给出多种语言对接所需的相关插件,这里仅描述Java的NetPayClient for JAVA.zip,在解压后里面会有相关jar包和securitySM.properties的一个文件

  • 测试账号

       可以在 FAQ列表- 中国银联开放平台里获取

  • IP白名单

        需要和银联工作人员联系,发送邮件给出相关商户号和出口IP进行配置

  • 开发工具

        这里我使用的是idea进行的开发,后续示例也仅以idea的操作示例

三、B2C、B2B、无卡支付交易流程 

 支付流程说明

1、 用户在商户的页面下单,选择支付时选择银联 ChinaPay 的支付方式进行支付

2、 商户支付系统按照银联支付接口提供入参组织报文,拼接请求地址返回到前端跳转到银联支付页面

3、 银联ChinaPay 收到商户提交的报文之后,首先进行验签;在验签通过后,跳到银联页面选择网银支付

5、 用户输入信息进行交易

6、 交易成功后,银联ChinaPay 通知商户交易结果

7、 商户收到交易成功之后,显示购买结果给用户

 查询流程说明

1、 商户调用查询订单状态接口查询

2、 银联同步返回订单支付状态给商户

退款流程说明

1、 用户在商户页面选择订单发起退款

2、 商户调用退款接口发起退款

3、 银联ChinaPay 收到商户的退款请求之后,返回商户“1003”状态,代表退款接收成功

4、 银联ChinaPay 在 T+1 日进行轧差,如果为正,则向银行或者银联发起退款,如果为负,则在下一日继续轧差

5、 银行或银联收到 ChinaPay 的退款指令之后进行退款操作

6、 银行或银联通知 ChinaPay 退款结果

7、 银联ChinaPay 通知商户退款结果

8、 商户更新退款结果

9、 用户收到退款

此处相关流程图和支付流程说明在开发文档中会有给出,在这仅简单描述

四、接入示例

1、首先本地开发需要导入银联ChinaPay提供的jar包,jar包会在银联ChinaPay邮件中的插件包里的NetPayClient for JAVA.zip中给出,这里给出两种本地开发导入jar包操作方式,后续上线的话则需要将jar包导入到公司对应的maven仓库,这个就另说了。

第一种是选择File------>Project Structure------>Project Settings下的Modules------>Dependencies,点击右侧或是下侧的加号,也可以用快捷键Alt+Insert,选择JARs or directories,选择相应的jar包导入,点击Apply后就是一直ok就可以了,下面是截图示例。

选择File------>Project Structure

 Project Settings下的Modules------>Dependencies

点击加号后选择JARs or directories

选择下载好的jar包 

之后就是点击Apply后一直ok就可以了

第二种导入方式是通过maven的命令导入

命令如下:

install:install-file
-Dfile=D:\chinapay\chinapaysecure1_5.jar
-DgroupId=com.chinapay.secure
-DartifactId=chinapay-sdk
-Dversion=1.5.0
-Dpackaging=jar

pom 文件引入:

<dependency>
    <groupId>com.chinapay.sdk</groupId>
    <artifactId>chinapay-sdk</artifactId>
    <version>1.5.0</version>
</dependency>

两种方法选择一种即可

2、将提前准备好的网关公钥,私钥和交易证书添加到项目中,一般来说是在resources下创建一个文件夹,文件夹中添加这些材料,然后创建一个*******.properties文件,后续创建支付、退款和查询状态的操作签名时都需要读取这个文件,通过这个文件读取相关的证书等材料。

这里的路径可以自己随意定义, *******.properties就是为了读取证书和一些配置,所以其他证书的路径写在了*******.properties文件中

3、示例代码

创建支付

public String getPayUrl(PayArguments payArgument) throws Exception {
    NumberFormat format = NumberFormat.getInstance();
    format.setMaximumFractionDigits(0);
    String amount = format.format(payArgument.Amount * 100);
    amount = amount.replace(",", "");
    Map<String, String> map = new HashMap<>();
    map.put("Version", "20150922");
    map.put("MerId", config.merId);
    map.put("MerOrderNo", payArgument.PayInfoId + "");
    map.put("TranDate", TimeUtil.DateToString(new Date(), "yyyyMMdd"));
    map.put("TranTime", TimeUtil.DateToString(new Date(), "HHmmss"));
    map.put("OrderAmt", amount);
    map.put("BusiType", "0001");
    map.put("MerBgUrl", config.notifyUrl);
    map.put("Signature", this.sign(map));
    return config.payUrl + "?" + getPara(map);
}

 查询状态

public Map<String, String> getPayStatus(String MerOrderNo) throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("Version", "20140728");
    map.put("MerId", config.merId);
    map.put("MerOrderNo", MerOrderNo);
    map.put("TranDate", TimeUtil.DateToString(new Date(), "yyyyMMdd"));
    map.put("TranType", "0502");
    map.put("BusiType ", "0001");
    map.put("Signature", this.sign(map));
    FormBody.Builder builder = new FormBody.Builder();
    for (Map.Entry<String, String> temp : map.entrySet()) {
        builder.add(temp.getKey(), temp.getValue());
    }
    OkHttpClient client = new OkHttpClient().newBuilder().readTimeout(60, TimeUnit.SECONDS).build();
    FormBody formBody = builder.build();
    Request request = new Request.Builder()
            .url(config.payQueryUrl)
            .post(formBody)
            .build();
    Response response = client.newCall(request).execute();
    byte[] responseBytes = response.body().bytes();
    String resStr = new String(responseBytes, "UTF-8");
    Map<String, String> resMap = new HashMap<>();
    String[] resArr = resStr.split("&");
    for (int i = 0; i < resArr.length; i++) {
        String[] kv = resArr[i].split("=");
        resMap.put(kv[0], kv[1]);
    }
    return resMap;
}

 退款

public Map<String, String> refund(double refundAmount, String merOrderNo, 
                String oriOrderNo, Date oriTranDate) throws Exception {
    NumberFormat format = NumberFormat.getInstance();
    format.setMaximumFractionDigits(0);
    String amount = format.format(refundAmount * 100);
    amount = amount.replace(",", "");
    Map<String, String> map = new HashMap<>();
    map.put("Version", "20140728");
    map.put("MerId", config.merId);
    map.put("MerOrderNo", merOrderNo);
    map.put("TranDate", TimeUtil.DateToString(new Date(), "yyyyMMdd"));
    map.put("TranTime", TimeUtil.DateToString(new Date(), "HHmmss"));
    map.put("OriOrderNo", oriOrderNo);
    map.put("OriTranDate", TimeUtil.DateToString(oriTranDate, "yyyyMMdd"));
    map.put("TranType", "0401");
    map.put("RefundAmt", amount);
    map.put("BusiType ", "0001");
    map.put("Signature", this.sign(map));
    OkHttpClient client = new OkHttpClient().newBuilder().readTimeout(60, TimeUnit.SECONDS).build();
    FormBody.Builder formBodyBuilder = new FormBody.Builder();
    for (Map.Entry<String, String> temp : map.entrySet()) {
        formBodyBuilder.add(temp.getKey(), temp.getValue());
    }
    FormBody formBody = formBodyBuilder.build();
    Request request = new Request.Builder()
            .url(config.refundUrl)
            .post(formBody)
            .build();
    Response response = client.newCall(request).execute();
    byte[] responseBytes = response.body().bytes();
    String resStr = new String(responseBytes, "UTF-8");
    Map<String, String> resMap = new HashMap<>();
    String[] resArr = resStr.split("&");
    for (int i = 0; i < resArr.length; i++) {
        String[] kv = resArr[i].split("=");
        resMap.put(kv[0], kv[1]);
    }
    return resMap;
}
private String getPara(Map<String, String> paraMap) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry item : paraMap.entrySet()) {
        sb.append(item.getKey());
        sb.append("=");
        sb.append(item.getValue());
        sb.append("&");
    }
    sb.deleteCharAt(sb.length() - 1);
    return sb.toString();
}

 此处propPath为*******.properties的文件路径

private String sign(Map<String, String> map) {
    SecssUtil secssUtil = new SecssUtil();
    secssUtil.init(config.propPath);
    secssUtil.sign(map);
    return secssUtil.getSign();
}

五、总结

        总结一下,在接入银联支付的话需要准备相应的各种材料和参数,否则在对接的时候会因为各种问题导致进度缓慢,例如缺少证书始终签名异常,文档一些字段在某些情况下是否是必填的,让银联先关工作人员配合排查原因,但是因为沟通理解错误导致效率低下等等原因,另外开发时也应先询问过是否需要绑定出口IP白名单,否则开发完调试发现,导致申请绑定IP白名单走流程又耽误好几天,总之接入银联支付我所想到或经历的大概就是这些了,希望看见这篇的人可以给出一些参考,进而避免踩一些坑,如果我哪里存在错误也欢迎评论指正。

  • 24
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值