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 文件引入:

com.chinapay.sdk

chinapay-sdk

1.5.0

两种方法选择一种即可

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 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 getPayStatus(String MerOrderNo) throws Exception {

Map 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 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 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 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 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 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 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 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 map) {

SecssUtil secssUtil = new SecssUtil();

secssUtil.init(config.propPath);

secssUtil.sign(map);

return secssUtil.getSign();

}

五、总结

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