银联在线支付在2014年重建了一个新的系统(全渠道acp)代替老系统(upop),并2014-2015期间将所有商户从upop切换到了acp。本文简要介绍一下银联在线支付的两种主要支付方式:前台网关支付(用户的浏览器从商户页面跳转到银联在线页面进行支付)和后台网关支付(用户在商户页面进行支付,商户后台系统调用银联系统完成支付)的切换方式。
这次切换有几个特点:
-
灰度切换。不是所有交易一次性全部切换到acp,而是按照商户维度分批切换。这就意味着upop和acp两个系统要同时保持运行。
-
acp的报文格式变了,与upop的报文不兼容,包括请求报文,响应报文,交易成功的异步通知报文,这意味着切换时需要对报文进行转换。
-
upop保持原url,acp采用新的url。
以下是切换过程中3种类型的商户的处理方式。
-
还没有切换的upop时期入网的老商户,使用upop url,请求由upop系统处理。
-
已经切换的upop时期入网的老商户,使用upop url,请求由upop做报文转换后转发到acp,由acp系统处理。
-
Acp上线后新入网的商户,使用acp url,请求由acp系统直接处理。
下图为前台网关支付的3种类型商户的处理流程
这里我们主要关注已经切换的upop时期入网的老商户的处理流程。
以壹基金网站为例。
-
首先,用户来到壹基金的捐款页面
http://www.onefoundation.cn/index.php?g=home&m=page&a=index&id=73
-
输入金额1元,点击”银联在线支付”按钮这时浏览器会访问http://www.onefoundation.cn/unionPay/action/front.php
壹基金在后台对”我要捐款1元给壹基金” 生成发送给upop的订单请求报文,并用商户私钥进行签名, 然后生成包含订单报文和商户签名的自动提交表单,表单的地址为upop前台网关地址https://unionpaysecure.com/api/Pay.action ,随后浏览器自动提交表单,访问upop前台网关。
<body onload="javascript:document.pay_form.submit();"> <form id="pay_form" name="pay_form" action="https://unionpaysecure.com/api/Pay.action" method="post"> <input type="hidden" name="origQid" id="origQid" value="" /> <input type="hidden" name="acqCode" id="acqCode" value="" /> <input type="hidden" name="merCode" id="merCode" value="" /> <input type="hidden" name="commodityUrl" id="commodityUrl" value="http://dev.one-foundation.com/" /> <input type="hidden" name="commodityName" id="commodityName" value="壹基金爱心捐赠" /> <input type="hidden" name="commodityUnitPrice" id="commodityUnitPrice" value="100" /> <input type="hidden" name="commodityQuantity" id="commodityQuantity" value="1" /> <input type="hidden" name="commodityDiscount" id="commodityDiscount" value="" /> <input type="hidden" name="transferFee" id="transferFee" value="" /> <input type="hidden" name="customerName" id="customerName" value="" /> <input type="hidden" name="defaultPayType" id="defaultPayType" value="" /> <input type="hidden" name="defaultBankNumber" id="defaultBankNumber" value="" /> <input type="hidden" name="transTimeout" id="transTimeout" value="" /> <input type="hidden" name="merReserved" id="merReserved" value="" /> <input type="hidden" name="version" id="version" value="1.0.0" /> <input type="hidden" name="charset" id="charset" value="UTF-8" /> <input type="hidden" name="merId" id="merId" value="210440383989452" /> <input type="hidden" name="merAbbr" id="merAbbr" value="壹基金" /> <input type="hidden" name="transType" id="transType" value="01" /> <input type="hidden" name="orderAmount" id="orderAmount" value="100" /> <input type="hidden" name="orderNumber" id="orderNumber" value="20151015154414354" /> <input type="hidden" name="orderTime" id="orderTime" value="20151015154414" /> <input type="hidden" name="orderCurrency" id="orderCurrency" value="156" /> <input type="hidden" name="customerIp" id="customerIp" value="58.246.226.97" /> <input type="hidden" name="frontEndUrl" id="frontEndUrl" value="http://dev.one-foundation.com/html/cn/beneficence_T_UPay.html" /> <input type="hidden" name="backEndUrl" id="backEndUrl" value="http://dev.one-foundation.com/unionPay/action/back_notify.php" /> <input type="hidden" name="signature" id="signature" value="41f5a7e35baf221c079279bcb5cb7037" /> <input type="hidden" name="signMethod" id="signMethod" value="md5" /> <input type="submit" type="hidden" style="display:none;"> </form>
-
Upop在收到浏览器发来的订单报文后。先对用商户公钥对报文进行验签,确保这笔订单报文确实是由这个商户发来的,验签成功后根据商户号判断该商户是否已经切换到acp,如果商户还没切换,则继续在upop进行后续处理;如果商户已经切换,则对报文进行转换,转换成acp格式的报文,并用银联公钥进行签名,然后生成包含acp格式报文和银联签名的自动提交表单,表单地址为acp前台网关的地址 https://cashier.95516.com/b2c/api/Pay.action,随后浏览器自动提交表单,访问acp前台网关。
<form id="defaultForm" action="https://cashier.95516.com/b2c/api/Pay.action" method="post"><input type="hidden" id="accNo" name="accNo" value="" /> <input type="hidden" id="accType" name="accType" value="" /> <input type="hidden" id="accessType" name="accessType" value="0" /> <input type="hidden" id="acqInsCode" name="acqInsCode" value="" /> <input type="hidden" id="backUrl" name="backUrl" value="http://dev.one-foundation.com/unionPay/action/back_notify.php" /> <input type="hidden" id="bindId" name="bindId" value="" /> <input type="hidden" id="bizType" name="bizType" value="000201" /> <input type="hidden" id="cardTransData" name="cardTransData" value="" /> <input type="hidden" id="certId" name="certId" value="0" /> <input type="hidden" id="channelType" name="channelType" value="07" /> <input type="hidden" id="currencyCode" name="currencyCode" value="156" /> <input type="hidden" id="customerInfo" name="customerInfo" value="" /> <input type="hidden" id="customerIp" name="customerIp" value="58.246.226.97" /> <input type="hidden" id="defaultPayType" name="defaultPayType" value="" /> <input type="hidden" id="encoding" name="encoding" value="UTF-8" /> <input type="hidden" id="encryptCertId" name="encryptCertId" value="" /> <input type="hidden" id="frontFailUrl" name="frontFailUrl" value="" /> <input type="hidden" id="frontUrl" name="frontUrl" value="http://dev.one-foundation.com/html/cn/beneficence_T_UPay.html" /> <input type="hidden" id="instalTransInfo" name="instalTransInfo" value="" /> <input type="hidden" id="internal.logId" name="internal.logId" value="1500120122" /> <input type="hidden" id="internal.merReferer" name="internal.merReferer" value="http://www.onefoundation.cn/unionPay/action/front.php" /> <input type="hidden" id="internal.origReqInfo" name="internal.origReqInfo" value="9001|07|0101" /> <input type="hidden" id="issInsCode" name="issInsCode" value="" /> <input type="hidden" id="merAbbr" name="merAbbr" value="壹基金" /> <input type="hidden" id="merCatCode" name="merCatCode" value="" /> <input type="hidden" id="merId" name="merId" value="210440383989452" /> <input type="hidden" id="merName" name="merName" value="壹基金" /> <input type="hidden" id="orderDesc" name="orderDesc" value="" /> <input type="hidden" id="orderId" name="orderId" value="20151015154414354" /> <input type="hidden" id="orderTimeout" name="orderTimeout" value="" /> <input type="hidden" id="payCardType" name="payCardType" value="" /> <input type="hidden" id="payTimeout" name="payTimeout" value="" /> <input type="hidden" id="reqReserved" name="reqReserved" value="" /> <input type="hidden" id="reserved" name="reserved" value="" /> <input type="hidden" id="riskRateInfo" name="riskRateInfo" value="{commodityQty=1&commodityName=壹基金爱心捐赠&commodityUrl=http://dev.one-foundation.com/&commodityUnitPrice=100}" /> <input type="hidden" id="securityType" name="securityType" value="" /> <input type="hidden" id="signMethod" name="signMethod" value="02" /> <input type="hidden" id="signature" name="signature" value="130519e5c6df6053a1410e0e87f099ad" /> <input type="hidden" id="subMerAbbr" name="subMerAbbr" value="" /> <input type="hidden" id="subMerId" name="subMerId" value="" /> <input type="hidden" id="subMerName" name="subMerName" value="" /> <input type="hidden" id="supPayType" name="supPayType" value="" /> <input type="hidden" id="termId" name="termId" value="" /> <input type="hidden" id="tn" name="tn" value="" /> <input type="hidden" id="txnAmt" name="txnAmt" value="100" /> <input type="hidden" id="txnSubType" name="txnSubType" value="01" /> <input type="hidden" id="txnTime" name="txnTime" value="20151015154414" /> <input type="hidden" id="txnType" name="txnType" value="01" /> <input type="hidden" id="userMac" name="userMac" value="" /> <input type="hidden" id="version" name="version" value="1.0.0" /> <input type="hidden" id="vpcTransData" name="vpcTransData" value="" /> </form> <script type="text/javascript"> document.getElementById("defaultForm").submit(); </script>
-
Acp前台网关在收到浏览器发来的订单报文后,先用银联公钥对报文进行验签,确保这笔订单报文确实是由银联(这里是upop)生成的。然后对订单进行验证,存库,展示等处理,返回给浏览器银联在线支付的首页https://cashier.95516.com/b2c/index.action?transNumber=xxxxxx
<form id="defaultForm" action="https://cashier.95516.com/b2c/index.action?transNumber=201510151544144032128" method="post"> </form> <script type="text/javascript"> document.getElementById("defaultForm").submit(); </script>
-
用户在银联在线的支付页面输入卡号,密码,短信验证码等信息,并点击支付,浏览器便访问acp前台网关进行支付。
-
Acp前台网关进行一系列支付相关的处理后,成功完成了这笔支付。然后判断出这笔订单的商户是一个”已经切换的upop时期入网的老商户”,于是生成一个upop格式的后台通知报文并用银联私钥签名,然后异步发送给商户的后台系统(backendurl),通知商户这笔交易已经成功完成。同时生成一个upop格式前台通知报文并用银联私钥签名,然后把这个前台通知报文放入支付成功结果页面的”返回商户”的按钮的链接(frontendurl)。商户在收到前台通知或者后台通知报文后,对报文用银联公钥进行验签,验签成功后则将这笔订单认定为成功。
接下来我们来看后台网关的交易流程,后台网关的报文是由商户系统直接发送给银联系统的下图为后台网关支付的3种类型商户的处理流程
![](http://static.oschina.net/uploads/space/2015/1030/103907_j4Wu_1452390.png)
和前台网关支付相同,这里我们主要关注已经切换的upop时期入网的老商户的处理流程。
- 商户系统组装订单报文,并用商户私钥签名,然后将订单报文和签名发给upop后台网关。
- upop后台网关用商户公钥对报文进行验签,确保这笔订单报文确实是由这个商户发来的,验签成功后根据商户号判断该商户是否已经切换到acp,如果商户还没切换,则继续在upop进行后续处理;如果商户已经切换,则对报文进行转换,转换成acp格式的报文,然后将转换后的报文发送给acp后台网关。
- Acp后台网关在收到来自upop后台网关的请求后,直接进行订单处理(由于两者之间的通信是通过银联内网进行的,可以相信报文确实来自upop,无需验签),并将处理结果报文返回给upop后台网关。如果是资金类交易(消费,预授权,退货等),最终的交易结果还将通过通知报文(加上银联私钥的签名)异步发送给商户系统(backendurl)。
- Upop后台网关把收到的acp结果报文转换为upop格式的报文,并用银联公钥进行签名,然后把结果报文和签名返回给商户系统。
- 商户系统对返回的报文进行验签,验签成功后,商户根据报文内容进行相关处理。如果是资金类交易(消费,预授权,退货等),商户还会对收到的通知报文用银联公钥进行验签,验签成功后则将这笔订单认定为成功。