最近在做一个项目,里面涉及到了支付功能,使用的是银联的在线支付功能(网关跳转支付)。银联给的例子很坑爹,简单的代码注释了一大堆,关键的部分一点儿注释都没有,很多工具类还没有源码。所以学习起来比较吃力。而网上这方面的资料有相对比较少,仅有的一些资料也比较陈旧。所以我打算记录一下我的学习记录,说不定会对别人也有一些帮助。
下载银联Demo
我做的是银联的网关跳转支付功能,用户在我的网页上点击跳转按钮以后会跳转到银联在线支付页面,支付完用户可以点击返回商家回到我们的网站(但是他可以不点,直接关闭页面),然后我们的服务器后台会接受银联的用户付款的通知,再更新自己数据库的功能。
银联技术服务的首页地址是https://open.unionpay.com/ajweb/index 很多资料可以在这里找到。
我下载的Demo在这里https://open.unionpay.com/ajweb/help/file/techFile?cateLog=Sample_code 这里有很多资料都可以选择。但是其实很多是重复的,很多是用不到的(对于我做的功能来说)。所以选择自己需要的就可以了。我选择的是 下载资源类别里的开发包菜单(左侧菜单) 下面的 跳转网关支付产品技术开发包。
我写这个项目的时候银联版本是2015-07-29的1.11版本(够新了吧(⊙﹏⊙))
里面乱糟糟的一大堆东西。。。。。PHP啊Java啊ASP啊啥都有。。。。
我选择Java版本的。
然后把示例代码下面的src目录下面的东西全部导入到自己建的web项目下。(是全部导入,所以除了.java以外还要导入acp_sdk.properties)
然后把依赖包下面的jar和upacp_sdk-1.0.0-20150703140550.jar导入到lib目录下(我没有导入json可选包)
给张图说明一切问题
然后项目就搭建好了。。是不是超级容易呀。。然后我会将如何配置项目。。
配置Properties文件
然后我来说下如何配置properties文件。
properties文件打开注释全部是unicode编码。。。简直坑爹。。看不懂可以去网上把unicode转成中文看看注释。。其实也没有必要。后面我会介绍比较有用的几个配置项。。其他没啥用处。
acp_sdk.properties这个文件大家不要重命名。
因为这个文件名是银联写死在配置类中的。改了名字会读取不到。
前面各种Url配置大家不需要去改动,下载下来都是配好的测试地址。只有到了正式上线了才需要去配置成正式的地址(但是我的项目也没上线。。所以我也不知道正式地址是啥。。但是银联官网上我看到是有资料写的。大家可以去自己搜搜看)
后面的配置是和证书相关的,另外插一句。properties里少了几行配置。。。坑了我几天时间。。
把证书Cert文件夹里的入网测试环境的3个文件夹全部拷到桌面上。因为会用到。(我放桌面上是为了方便配置)
修改properties里的acpsdk.signCert.path的值为C:\\Users\\Administrator\\desktop\\入网测试环境\\商户私钥证书(签名)PM_700000000000001_acp.pfx
大家对照路径可以自己改,注意这里700000000000001这个数字。后面会用到(又被坑了几天。。。)
这个证书在正式上线以后需要改为正式的证书,这个证书是测试使用。
acpsdk.signCert.pwd并不需要改,因为测试证书的密码就是6个0。
同理,正式上线之后要修改成自己的密码
acpsdk.signCert.type不需要修改,就是PKCS12
acpsdk.validateCert.dir的值修改为C:\\Users\\Administrator\\desktop\\入网测试环境\\银联公钥证书(验签)
validateCert是用在银联给你发消息的时候判断真伪用的(别人可能会伪造银联的信息,所以需要用这个证书验证信息真伪),所以对于创建订单付款来说没有什么用处。但是后面银联通知你用户付款的时候你要用这个证书验证消息是不是真的来自银联。
增加acpsdk.encryptCert.path这个key,value为C:\\Users\\Administrator\\desktop\\入网测试环境\\加密证书 (根据业务需求选用)\\encrypt.cer
银联居然默认没有这个配置。。坑爹啊。。。这个是你给银联发消息的时候银联要判断这个订单是不是指定商户(你)发过来的。也是验证真伪用的(加密)
完成这些配置就可以愉快(剁手)的和银联做交易了。
创建订单
创建订单方面其实可以直接参考Form_6_2_FrontConsume这个类。
SDKConfig.getConfig().loadPropertiesFromSrc();是去加载properties配置文件,但是要求配置文件放在.class的根目录。就是和包文件com同级。
这个方法并不好用,因为你自己测试的时候和项目部署上去位置可能会经常变化。所以可以使用更好用的方法SDKConfig.getConfig().loadPropertiesFromPath("C:/Users/Administrator/desktop");
loadPropertiesFromPath的参数是properties文件所在的目录,不用写properties的文件名,原因我前面已经说了,名字被写死在银联工具类里了。
另外银联的工具包都没有源码,大家可以反编译一下看看源代码,我记得还有一个读取properties文件的方法可以使用。但是我是使用loadPropertiesFromPath这个方法的。
后面各种put参数我就说几个重要的。
frontUrl是前端通知地址,就是客户付完款以后银联会给一个返回商户的按钮,用户点击按钮以后就到了你配置的这个url映射的网页。同时会提交很多订单数据给你。
backUrl是后台通知地址,客户付完款以后银联会给你配置的这个后台url对应的Action发扣款的消息过来。数据和前端通知发送过来的消息是一样的。
我觉得前端通知地址就用来展示订单数据给用户看就行了,用户不一定会去点那个按钮。
后台通知用来更新自己数据库里的订单数据。
然后是merId这个不是随便写的,因为你前面的证书是PM_700000000000001_acp.pfx。所以这里必须写700000000000001。就是证书对应的商户号,这点很重要。银联下载来的demo默认是888888888888888。坑爹哪这是。
然后就没啥好说的了,运行Form_6_2_FrontConsume类以后会生成一段html代码。比如:
1 <html> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 4 head> 5 <body> 6 <form id="pay_form" 7 action="https://101.231.204.80:5000/gateway/api/frontTransReq.do" 8 method="post"> 9 <input type="hidden" name="txnType" id="txnType" value="01" /><input 10 type="hidden" name="frontUrl" id="frontUrl" 11 value="http://localhost:8080/ACPTest/acp_front_url.do" /><input 12 type="hidden" name="currencyCode" id="currencyCode" value="156" /><input 13 type="hidden" name="channelType" id="channelType" value="08" /><input 14 type="hidden" name="merId" id="merId" value="700000000000001" /><input 15 type="hidden" name="txnSubType" id="txnSubType" value="01" /><input 16 type="hidden" name="txnAmt" id="txnAmt" value="1" /><input 17 type="hidden" name="version" id="version" value="5.0.0" /><input 18 type="hidden" name="signMethod" id="signMethod" value="01" /><input 19 type="hidden" name="backUrl" id="backUrl" 20 value="http://222.222.222.222:8080/ACPTest/acp_back_url.do" /><input 21 type="hidden" name="certId" id="certId" 22 value="124876885185794726986301355951670452718" /><input 23 type="hidden" name="encoding" id="encoding" value="UTF-8" /><input 24 type="hidden" name="bizType" id="bizType" value="000201" /><input 25 type="hidden" name="signature" id="signature" 26 value="UfR4EHEma3rnwsQPIQOsdzEiVgEyT/tEza6vXiy1AjnlSS5C2qVVeV1TWClTXVA6OPh5Y9E7OyOwQYbWeuK+a4hCreY/R7oESNiF9gZs7KCphGp54daKOjRV1zNRcL2pDT2YhleBGQPgH/dwdhrOuprrFtvJBhi0TbRxFX2LSBU=" /><input 27 type="hidden" name="orderId" id="orderId" value="20150829171948" /><input 28 type="hidden" name="accessType" id="accessType" value="0" /><input 29 type="hidden" name="txnTime" id="txnTime" value="20150829171948" /> 30 form> 31 body> 32 <script type="text/javascript"> 33 document.all.pay_form.submit(); 34 script> 35 html>
把这段代码刷到用户浏览器上用户就会跳转到银联界面上去了。
然后测试付款可以使用demo里readMe.txt里的银行卡号和密码。
用户付完款点了返回商户按钮以后会跳转到你写的前台通知地址上去。
我觉得原理和你让客户跳转到银联去的方法差不多,可能也是银联刷了一个html在客户机器上,然后客户提交了一个post请求到你的前台通知地址。(不过我知道银联是怎么实现的,我觉得也是这个原理)
然后就完成付款了。
值得注意的是银联给你后台的通知可能有N次。。。反正我经常受到不只一次的同一订单付款了的提醒。
至于通知会返回什么数据。大家自己试一次就OK了,数据还是很多的。
有朋友可能会想:要是坏人给我后台通知的地址发假冒的银联信息怎么办呢。这个不用担心,银联发送回来的数据也是用证书加密过的,你也需要先验证真伪,是真的再更新数据库。假的就无视好啦。
至于怎么验证真伪,BackRcvResponse这个类,这个类就是模拟后台接受银联信息用的。
查询订单情况
有时候可能自己服务器有问题或者网络不好,会收不到银联后台通知地址。而前台通知地址又不可靠,因为用户可能会不去点击它。
这种情况下你也要去更新订单的情况,那么可以使用主动去银联查询订单情况的方法。
可以参考Form_6_5_Query这个类。
put的大部分数据大家不要更改,只需要更改merId,orderId,txnTime为前面提交时候的信息就可以完成查询工作啦。
我这里就贴出一个我前面完成的订单的查询结果好了(就是前面那段html对应的订单)
打印返回报文:accNo=6216***********0018&accessType=0&bizType=000000¤cyCode=156&encoding=UTF-8&issuerIdentifyMode=0&merId=700000000000001&orderId=20150829171948&origRespCode=00&origRespMsg=成功[0000000]&queryId=201508291719485608648&respCode=00&respMsg=成功[0000000]&settleAmt=1&settleCurrencyCode=156&settleDate=0829&signMethod=01&traceNo=560864&traceTime=0829171948&txnAmt=1&txnSubType=01&txnTime=20150829171948&txnType=01&version=5.0.0&certId=3474813271258769001041842579301293446&signature=VREVdo+eEtKaNY2HpVsGiEVab711cxwfjx6OHbb6wX3XdisIyftVRz1DAlboN34G/OhsDMFfy1HHKZvpSb8A+dVbVe58VKm1ZWpa6VaXTssBtNBj9WNozatJKk7Df7CmodEcBmye7oUaB8diYc0yqqdODBllWDdw6GlEzKe3rerheo92p9Q7RxmfV6CEXsCeNhlWL550OMKE1cvJYV/lBLqEMVQ6ec5ED2QglVBasGZO+ehFKKAb7TzgY6y9D/MWGhgLegDN7Sl7TSkTsjqbYGbA31dsZX3YcsdTLSqCbhOdC+yD9buFMM9Y29XehSEhyAp/7J5PQu5mi71E6LvUDQ== 请求报文=[{txnType=00, channelType=08, merId=700000000000001, txnSubType=00, version=5.0.0, signMethod=01, certId=124876885185794726986301355951670452718, encoding=UTF-8, bizType=000000, signature=PuT1JKdoa2NXxAC4LSVeSdQLJy5ZIo7OfBVDDIR3NxtACsK3KBkjrwcuM0JyZA8AOegFSwWv9gMWuIuyBpSjusc1GTXKTd3uJy9whun3QIYSJKSNNj9Zoe+m088+N6QjzQ7CCx4ncIdaMlo3rcdaDnIxEKW/Dji2OFRZroT3v9k=, orderId=20150829171948, txnTime=20150829171948, accessType=0}] 应答报文=[{respCode=00, origRespMsg=成功[0000000], origRespCode=00, txnSubType=01, txnAmt=1, version=5.0.0, signMethod=01, settleAmt=1, encoding=UTF-8, traceTime=0829171948, respMsg=成功[0000000], queryId=201508291719485608648, signature=VREVdo+eEtKaNY2HpVsGiEVab711cxwfjx6OHbb6wX3XdisIyftVRz1DAlboN34G/OhsDMFfy1HHKZvpSb8A+dVbVe58VKm1ZWpa6VaXTssBtNBj9WNozatJKk7Df7CmodEcBmye7oUaB8diYc0yqqdODBllWDdw6GlEzKe3rerheo92p9Q7RxmfV6CEXsCeNhlWL550OMKE1cvJYV/lBLqEMVQ6ec5ED2QglVBasGZO+ehFKKAb7TzgY6y9D/MWGhgLegDN7Sl7TSkTsjqbYGbA31dsZX3YcsdTLSqCbhOdC+yD9buFMM9Y29XehSEhyAp/7J5PQu5mi71E6LvUDQ==, orderId=20150829171948, txnType=01, currencyCode=156, merId=700000000000001, settleDate=0829, accNo=6216***********0018, certId=3474813271258769001041842579301293446, settleCurrencyCode=156, bizType=000000, traceNo=560864, issuerIdentifyMode=0, accessType=0, txnTime=20150829171948}]
研究一下应答报文就知道哪些功能是能做的那些是不能做的了。比如就不能知道客户的完整付款账户号,其中多的位数被打*了。
差不多这些就是我在这个项目中对银联跳转网关支付功能的理解啦。