农行的支付网关提供了PHP的开发说明和相关的程序包。它是利用webservice实现php和java的TrustPayClient提供服务的交互。由于webservice需要运行在tomcat,无法跟php常见的lamp运行环境合用,增加的系统的复杂度,无论从开发还是维护上看都是不好的。还好农行的java包是做到非常棒的,所有关键点都做log4j做了记录,我们可以根据它的log文件得到它提交和返回的数据报文。我们从log中可以看到农行是采用SHA1withRSA的算法对数据进行签名和验签。用SHA1withRSA算法在php中我们可以用openssl扩展的openssl_sign()和openssl_verify()实现。下面详细说明如何用openssl简化农行接口的开发工作。 提交
发起交易的数据格式是这样的
1
<MSG> <Message> <Merchant> <ECMerchantType> B2C</ECMerchantType> <MerchantID> 233010300330A01</MerchantID> </Merchant> <TrxRequest> <TrxType> PayReq</TrxType> <Order> <OrderNo> ON200306300001</OrderNo> <OrderAmount> 280.0</OrderAmount> <OrderDesc> Game Card Order</OrderDesc> <OrderDate> 2003/11/12</OrderDate> <OrderTime> 23:55:30</OrderTime> <OrderURL> http://127.0.0.1/Merchant/MerchantQueryOrder.jsp?ON=ON200306300001&QueryType=1</OrderURL><OrderItems><OrderItem><ProductID>IP000001</ProductID><ProductName>中国移动IP卡</ProductName><UnitPrice>100.0</UnitPrice><Qty>1</Qty></OrderItem><OrderItem><ProductID>IP000002</ProductID><ProductName>网通IP卡</ProductName><UnitPrice>90.0</UnitPrice><Qty>2</Qty></OrderItem></OrderItems></Order><ProductType>1</ProductType><PaymentType>1</PaymentType><NotifyType>0</NotifyType><ResultNotifyURL>http://127.0.0.1/Merchant/MerchantResult.jsp</ResultNotifyURL><MerchantRemarks>Hi!</MerchantRemarks><PaymentLinkType>1</PaymentLinkType></TrxRequest></Message><Signature-Algorithm>SHA1withRSA</Signature-Algorithm><Signature>nfJAveUtLG1YHqsjUdopB8Jl9QX4ZtlQrUn+HoiCy0yS9An19z5IxTIVYOuQXjNnbMGgmZlCwK3dSSnRTLHxZMC3zJUiE58qEwxatOgHNFUhAHTBxkUMO5ikC7C5qm/9L67/Xp7kYvHK9Fo/8CyXckROb+w+eLYcPaYo6+Of2Dg=</Signature></MSG>
数据有以下几部分构成
1
<MSG> <Message> 订单信息</Message> <Signature-Algorithm> SHA1withRSA</Signature-Algorithm> <Signature> 订单信息的签名</Signature> </MSG>
订单信息的构造参考:
1
<Merchant> <ECMerchantType> B2C</ECMerchantType> <MerchantID> 233010300330A01</MerchantID> </Merchant> <TrxRequest> <TrxType> PayReq</TrxType> <Order> <OrderNo> ON200306300001</OrderNo> <OrderAmount> 280.0</OrderAmount> <OrderDesc> Game Card Order</OrderDesc> <OrderDate> 2003/11/12</OrderDate> <OrderTime> 23:55:30</OrderTime> <OrderURL> http://127.0.0.1/Merchant/MerchantQueryOrder.jsp?ON=ON200306300001&QueryType=1</OrderURL><OrderItems><OrderItem><ProductID>IP000001</ProductID><ProductName>中国移动IP卡</ProductName><UnitPrice>100.0</UnitPrice><Qty>1</Qty></OrderItem><OrderItem><ProductID>IP000002</ProductID><ProductName>网通IP卡</ProductName><UnitPrice>90.0</UnitPrice><Qty>2</Qty></OrderItem></OrderItems></Order><ProductType>1</ProductType><PaymentType>1</PaymentType><NotifyType>0</NotifyType><ResultNotifyURL>http://127.0.0.1/Merchant/MerchantResult.jsp</ResultNotifyURL><MerchantRemarks>Hi!</MerchantRemarks><PaymentLinkType>1</PaymentLinkType></TrxRequest>
整体上由Merchant商户信息段和TrxRequest请求信息段,两部分构成。
Signature是采用SHA1withRSA对订单信息进行签名在base64 encode后得到,在php中可以用openssl实现。参考代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$cert_file = "D:/user.pem" ;
$password = '666666' ;
$signature = null ;
$toSign = "<Merchant><ECMerchantType>B2C</ECMerchantType><MerchantID>233010300330A01</MerchantID></Merchant><TrxRequest><TrxType>PayReq</TrxType><Order><OrderNo>ON200306300001</OrderNo><OrderAmount>280.0</OrderAmount><OrderDesc>Game Card Order</OrderDesc><OrderDate>2003/11/12</OrderDate><OrderTime>23:55:30</OrderTime><OrderURL>http://127.0.0.1/Merchant/MerchantQueryOrder.jsp?ON=ON200306300001&QueryType=1</OrderURL><OrderItems><OrderItem><ProductID>IP000001</ProductID><ProductName>中国移动IP卡</ProductName><UnitPrice>100.0</UnitPrice><Qty>1</Qty></OrderItem><OrderItem><ProductID>IP000002</ProductID><ProductName>网通IP卡</ProductName><UnitPrice>90.0</UnitPrice><Qty>2</Qty></OrderItem></OrderItems></Order><ProductType>1</ProductType><PaymentType>1</PaymentType><NotifyType>0</NotifyType><ResultNotifyURL>http://127.0.0.1/Merchant/MerchantResult.jsp</ResultNotifyURL><MerchantRemarks>Hi!</MerchantRemarks><PaymentLinkType>1</PaymentLinkType></TrxRequest>" ;
// $toSign签名后的数据应该是nfJAveUtLG1YHqsjUdopB8Jl9QX4ZtlQrUn+HoiCy0yS9An19z5IxTIVYOuQXjNnbMGgmZlCwK3dSSnRTLHxZMC3zJUiE58qEwxatOgHNFUhAHTBxkUMO5ikC7C5qm/9L67/Xp7kYvHK9Fo/8CyXckROb+w+eLYcPaYo6+Of2Dg=
$fp = fopen ( $cert_file , "rb" ) ;
$priv_key = fread ( $fp , 8192 ) ;
fclose ( $fp ) ;
//获取pem文件中的私钥
$pkeyid = openssl_get_privatekey ( $priv_key , $password ) ;
//使用用户私钥对消息进行签名
openssl_sign ( $toSign , $signature , $pkeyid ) ;
// Free the key.
openssl_free_key ( $pkeyid ) ;
$b64 = base64_encode ( $signature ) ;
echo $b64 ;
?>
代码中用到的user.pem由农行网银后台下载的pfx或则p12文件转换而已,pfx转pem文件的方法是:
1
openssl pkcs12 -nocerts -nodes -in cert.p12 -out user.pem
返回
返回的数据是POST回来的key为msg,内容是用base64编码过的:
1
PE1TRz48TWVzc2FnZT48VHJ4UmVzcG9uc2U+ PFJldHVybkNvZGU+ MDAwMDwvUmV0dXJuQ29kZT48RXJyb3JNZXNzYWdlPjwvRXJyb3JNZXNzYWdlPjxFQ01lcmNoYW50VHlwZT5CMkM8L0VDTWVyY2hhbnRUeXBlPjxNZXJjaGFudElEPjIzMzAxMDMwMDMzMEEwMTwvTWVyY2hhbnRJRD48VHJ4VHlwZT5QYXlSZXN1bHQ8L1RyeFR5cGU+ PE9yZGVyTm8+ MTI5ODk1ODQyNTMxODU8L09yZGVyTm8+ PEFtb3VudD4wLjAzPC9BbW91bnQ+ PEJhdGNoTm8+ MDAwMDAxPC9CYXRjaE5vPjxWb3VjaGVyTm8+ MDAwMDEyPC9Wb3VjaGVyTm8+ PEhvc3REYXRlPjIwMTEvMDMvMDE8L0hvc3REYXRlPjxIb3N0VGltZT4xMzozNjozMDwvSG9zdFRpbWU+ PE1lcmNoYW50UmVtYXJrcz5IaSE8L01lcmNoYW50UmVtYXJrcz48UGF5VHlwZT5QQVkwMTwvUGF5VHlwZT48Tm90aWZ5VHlwZT4wPC9Ob3RpZnlUeXBlPjxQYXlJUD42MC4xOTAuNzMuMTIyPC9QYXlJUD48UGF5UmVmZXJlcj5sdXNlbi52aXAuaXNob3BleC5jbjwvUGF5UmVmZXJlcj48aVJzcFJlZj4zNjAzMDExMzkxNTM8L2lSc3BSZWY+ PC9UcnhSZXNwb25zZT48L01lc3NhZ2U+ PFNpZ25hdHVyZS1BbGdvcml0aG0+ U0hBMXdpdGhSU0E8L1NpZ25hdHVyZS1BbGdvcml0aG0+ PFNpZ25hdHVyZT5MM0VlQW1yMTdPYTloaHVxU0E5NEFXSEVhVjN5RnFOTHZZQ29qem0xVm5PMWtMSnVhVkZTekdyV0Y3NjhPMUtMelBaY1M1MVFMRko1dUFMUlFtUVM1Ylp3Tm5kZ1FHeUxPZGRaMUpjRGdjazh3RE9UYkJPSTJFQWRhdElGL0czR1QrRENoRkVyVlZMR01oa1VGRTRpVTVrM3YvdXMwM3pZQ1UxbUR4SzFtbGM9PC9TaWduYXR1cmU+ PC9NU0c+
base64解码后是这样:
1
2
3
4
5
6
7
8
9
10
11
< MSG>< Message>< TrxResponse>< ReturnCode> 0000 </ ReturnCode>< ErrorMessage></ ErrorMes
sage>< ECMerchantType> B2C</ ECMerchantType>< MerchantID> 233010300330A01</ MerchantID
>< TrxType> PayResult</ TrxType>< OrderNo> 12989584253185 </ OrderNo>< Amount> 0.03 </ Amou
nt>< BatchNo> 000001 </ BatchNo>< VoucherNo> 000012 </ VoucherNo>< HostDate> 2011 / 03 / 01 </ H
ostDate>< HostTime> 13 : 36 : 30 </ HostTime>< MerchantRemarks> Hi!</ MerchantRemarks>< PayT
ype> PAY01</ PayType>< NotifyType> 0 </ NotifyType>< PayIP> 60. 190. 73. 122</ PayIP>< PayRef
erer> lusen. vip. ishopex. cn</ PayReferer>< iRspRef> 360301139153 </ iRspRef></ TrxRespon
se></ Message>< Signature- Algorithm> SHA1withRSA</ Signature- Algorithm>< Signature> L3
EeAmr17Oa9hhuqSA94AWHEaV3yFqNLvYCojzm1VnO1kLJuaVFSzGrWF768O1KLzPZcS51QLFJ5uALRQm
QS5bZwNndgQGyLOddZ1JcDgck8wDOTbBOI2EAdatIF/ G3GT+ DChFErVVLGMhkUFE4iU5k3v/ us03zYCU
1mDxK1mlc=</ Signature></ MSG>
我们需要对Message段的数据进行和Signature段的数据进行验签。同提交请求时一样,php有openssl_verify这个函数可以对农行提交过来的数据进行验证。测试用例见下文:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$ret = array (
'MSG' => 'PE1TRz48TWVzc2FnZT48VHJ4UmVzcG9uc2U+PFJldHVybkNvZGU+MDAwMDwvUmV0dXJuQ29kZT48RXJyb3JNZXNzYWdlPjwvRXJyb3JNZXNzYWdlPjxFQ01lcmNoYW50VHlwZT5CMkM8L0VDTWVyY2hhbnRUeXBlPjxNZXJjaGFudElEPjIzMzAxMDMwMDMzMEEwMTwvTWVyY2hhbnRJRD48VHJ4VHlwZT5QYXlSZXN1bHQ8L1RyeFR5cGU+PE9yZGVyTm8+MTI5ODk1ODQyNTMxODU8L09yZGVyTm8+PEFtb3VudD4wLjAzPC9BbW91bnQ+PEJhdGNoTm8+MDAwMDAxPC9CYXRjaE5vPjxWb3VjaGVyTm8+MDAwMDEyPC9Wb3VjaGVyTm8+PEhvc3REYXRlPjIwMTEvMDMvMDE8L0hvc3REYXRlPjxIb3N0VGltZT4xMzozNjozMDwvSG9zdFRpbWU+PE1lcmNoYW50UmVtYXJrcz5IaSE8L01lcmNoYW50UmVtYXJrcz48UGF5VHlwZT5QQVkwMTwvUGF5VHlwZT48Tm90aWZ5VHlwZT4wPC9Ob3RpZnlUeXBlPjxQYXlJUD42MC4xOTAuNzMuMTIyPC9QYXlJUD48UGF5UmVmZXJlcj5sdXNlbi52aXAuaXNob3BleC5jbjwvUGF5UmVmZXJlcj48aVJzcFJlZj4zNjAzMDExMzkxNTM8L2lSc3BSZWY+PC9UcnhSZXNwb25zZT48L01lc3NhZ2U+PFNpZ25hdHVyZS1BbGdvcml0aG0+U0hBMXdpdGhSU0E8L1NpZ25hdHVyZS1BbGdvcml0aG0+PFNpZ25hdHVyZT5MM0VlQW1yMTdPYTloaHVxU0E5NEFXSEVhVjN5RnFOTHZZQ29qem0xVm5PMWtMSnVhVkZTekdyV0Y3NjhPMUtMelBaY1M1MVFMRko1dUFMUlFtUVM1Ylp3Tm5kZ1FHeUxPZGRaMUpjRGdjazh3RE9UYkJPSTJFQWRhdElGL0czR1QrRENoRkVyVlZMR01oa1VGRTRpVTVrM3YvdXMwM3pZQ1UxbUR4SzFtbGM9PC9TaWduYXR1cmU+PC9NU0c+' ,
) ;
$msg = base64_decode ( $ret [ 'MSG' ] ) ;
preg_match ( "/\<Message\>(.*)\<\/Message\>.*\<Signature\>(.*)\<\/Signature\>/i" , $msg , $match ) ;
$contents = $match [ 1 ] ;
$signature = $match [ 2 ] ;
$pub_cert_file = "TrustPay.pem" ;
verify( $contents , $signature , $pub_cert_file ) ;
function verify( $data , $signature , $cert_file ) {
$fp = fopen ( $cert_file , "r" ) ;
$cert = fread ( $fp , 8192 ) ;
fclose ( $fp ) ;
$pubkeyid = openssl_get_publickey ( $cert ) ;
$signature = base64_decode ( $signature ) ;
// state whether signature is okay or not
$ok = openssl_verify ( $data , $signature , $pubkeyid ) ;
openssl_free_key ( $pubkeyid ) ;
return $ok ;
}
?>
代码中TrustPay.pem可以这么生成
1
openssl x509 -inform DER -in TrustPay.cer -out TrustPay.pem
-EOF-