今天研究了一下银联的支付系统,关于银联的支付集成,我真的也是醉了,官方文档写的和屎一样,主要里面还有坑,官方页面展示的文档和官方下载的代码Demo在参数的解释上有明显不同(关于后台回调地址字段),里面还有错别字,还有注释写错的,我真是无言以对,不知道银联怎么想的~~~~
正题
银联开放的接口主要分两类,一类是针对pc端的,一类是针对移动端的。
简单解释下上面的几种类型
PC端
网关支付:主要针对B2C电商网站,用户发起支付需要跳转到银联页面完成支付。
无跳转支付:是银联为一些大型电商网站开的小灶,不需要跳转页面。
企业网银支付:多家银行企业网银统一支付(这个我没仔细看,有兴趣的可以自行研究)。
移动端
手机控件支付:类似APP集成支付宝SDK那种模式。
手机网页支付(wap):类似于PC端的网关支付。
银联云闪和Apple Pay我也没仔细看........
下面我们以 “手机控件支付”为例简单说下流程,然后看个小例子,
先看个流程图,
这时银联官网的流程图,我怀疑他是用word画的.....
交易步骤:
1、 用户通过商户App终端购买商品;
2、 商户后台接收商品购买请求,生成订单发送订单推送请求(签名)至银联;
3、 银联 同步返回对应该订单的交易流水号至商户后台(验签);
4、 商户后台转发交易流水号至商户App,并跳转至银联安全支付控件页面;
5、 用户输入支付相关支付信息(如密码),完成支付操作;
6、 支付成功完成后,控件通过前台通知的方式向商户系统发送交易结果;
7、 银联交易成功后,主动将支付结果以post方式通知商户后台,银联采用通知重发机制告知商户后台(需要验签)。
基本和集成支付宝和微信类似,但是银联比较烦的一点就是他有各种证书,签名和验签时都需要用到证书。
现在我们可以去银联的官网下载一个demo示例运行感受下。
地址:https://open.unionpay.com/ajweb/help/file/techFile?productId=3
导入eclipse后大概是这个样子,不知道为啥有个红叉,请忽略,懒得去解决了,只是看个源码而已。
其中 sdk 包下存放的是精华,是银联为我们封装好的一些工具类,比如签名和验签等方法,其余的大部分是示例代码,
assets文件夹下存放的是一些配置文件和相关证书,不一一解释了,在代码注释中有详细的解释。
现在看下支付的调用过程
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String merId = req.getParameter("merId");
String txnAmt = req.getParameter("txnAmt");
String orderId = req.getParameter("orderId");
String txnTime = req.getParameter("txnTime");
Map<String, String> contentData = new HashMap<String, String>();
/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
contentData.put("version", DemoBase.version); //版本号 全渠道默认值
contentData.put("encoding", DemoBase.encoding); //字符集编码 可以使用UTF-8,GBK两种方式
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
contentData.put("txnType", "01"); //交易类型 01:消费
contentData.put("txnSubType", "01"); //交易子类 01:消费
contentData.put("bizType", "000201"); //填写000201
contentData.put("channelType", "08"); //渠道类型 08手机
/***商户接入参数***/
contentData.put("merId", merId); //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
contentData.put("accessType", "0"); //接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户)
contentData.put("orderId", orderId); //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
contentData.put("txnTime", txnTime); //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
contentData.put("accType", "01"); //账号类型 01:银行卡02:存折03:IC卡帐号类型(卡介质)
contentData.put("txnAmt", txnAmt); //交易金额 单位为分,不能带小数点
contentData.put("currencyCode", "156"); //境内商户固定 156 人民币
// 请求方保留域,
// 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。
// 出现部分特殊字符时可能影响解析,请按下面建议的方式填写:
// 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。
// contentData.put("reqReserved", "透传信息1|透传信息2|透传信息3");
// 2. 内容可能出现&={}[]"'符号时:
// 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示);
// 2) 如果对账文件没有显示要求,可做一下base64(如下)。
// 注意控制数据长度,实际传输的数据长度不能超过1024位。
// 查询、通知等接口解析时使用new String(Base64.decodeBase64(reqReserved), DemoBase.encoding);解base64后再对数据做后续解析。
// contentData.put("reqReserved", Base64.encodeBase64String("任意格式的信息都可以".toString().getBytes(DemoBase.encoding)));
//后台通知地址(需设置为外网能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,【支付失败的交易银联不会发送后台通知】
//后台通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知
//注意:1.需设置为外网能访问,否则收不到通知 2.http https均可 3.收单后台通知后需要10秒内返回http200或302状态码
// 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200或302,那么银联会间隔一段时间再次发送。总共发送5次,银联后续间隔1、2、4、5 分钟后会再次通知。
// 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
contentData.put("backUrl", DemoBase.backUrl);
/**对请求参数进行签名并发送http post请求,接收同步应答报文**/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl(); //交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData,requestAppUrl,DemoBase.encoding); //发送请求报文并接受同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
//应答码规范参考open.unionpay.com帮助中心 下载 产品接口规范 《平台接入接口规范-第5部分-附录》
if(!rspData.isEmpty()){
if(AcpService.validate(rspData, DemoBase.encoding)){
LogUtil.writeLog("验证签名成功");
String respCode = rspData.get("respCode") ;
if(("00").equals(respCode)){
//成功,获取tn号
//String tn = resmap.get("tn");
//TODO
}else{
//其他应答码为失败请排查原因或做失败处理
//TODO
}
}else{
LogUtil.writeErrorLog("验证签名失败");
//TODO 检查验证签名失败的原因
}
}else{
//未返回正确的http状态
LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
}
String reqMessage = DemoBase.genHtmlResult(reqData);
String rspMessage = DemoBase.genHtmlResult(rspData);
resp.getWriter().write("请求报文:<br/>"+reqMessage+"<br/>" + "应答报文:</br>"+rspMessage+"");
}
详细解释代码中都有,
注意!!这里面说下遇到的一个比较严重的坑,,,,
在从官方下载的demo中,就是上面看的那个代码,有如下配置文件
这里面有两个参数,他的解释分别是
前台通知地址backUrl(流程图 6) 和 后台通知地址frontUrl(流程图7)
但是官方网页文档上有如下解释
backUrl 代码中说他是前台通知地址,,网页文档说他是后台通知地址,不知道银联在想什么。。。
我搞了2个小时,终于找到的银联的官方 pdf版文档,有如下解释
说因为兼容控件第一期未采用 frontUrl 方式来做 支付控件给 商户APP返回结果,
我个人觉得还是这个比较靠谱一点,这两个参数正确的解释是
后台通知地址backUrl(对应流程图 7)
前台通知地址frontUrl(对应流程图6)
代码中的注释是错误的,真是太坑了,
现在把项目跑起来看看吧
点击消费
成功,nice!
还有个后台异步通知,就不演示了,可以去官网下代码搞一搞,里面有配置好的测试环境,还算好用,就是注释有些错的,切记以官网pdf版为准!