银联在线支付接口开发日志
1、 登录银联自助化测试平台(登陆地址:open.unionpay.com),登录后,点击我的产品,如下:点击右方需要测试的接口,本例以 手机网页支付(WAP支付)为例。
2、 点击左侧菜单 测试参数,即可看到测试过程所需要的参数,如下图:点击测试证书,下载两个证书,一个是以后缀 .pfx 的私钥证书,一个是后缀 .cer 的公钥证书,将其下载后,私钥证书文件名称修改为 acp_test_sign.pfx 。或者你不下载的话,直接用本例子的也可以。TP3.2例子 里面 Public/cer 里面已有所有证书文件。
3、 TP3.2例子里面已有相关代码可以用来测试,测试的时候请使用测试环境的参数,代码中均有注释。在开始之前要确保你的环境PHP版本基于5.3,需开启curl、openssl功能,还有测试要放在线上测试,本地的虚拟域名是不行的。如遇到什么问题,可以参考官方的说明,本文件夹里面有个 PHP Version SDK 是官方的文档,参考里面的说明就好了,他们那个例子我测试的时候也跑不起来,不知道什么鬼原因。
4、 切换到生产环境,注意以下问题:
4.1 首先根据你收到的商户开通邮件里面的指示,访问网站 http://cs.cfca.com.cn/
下载生产证书文件:
点击下载后,完成下载操作后,页面会出现下载成功的提示。下载的证书自动存放在IE中,下一步就要进行证书的导出。
4.2 导出证书文件:打开IE浏览器,点击右上角的齿轮,打开工具=》Internet选项=》内容=》证书,如图:
点击证书后,找到刚刚下载的那个证书,你可以根据名称去辨别,商户邮件中有标注:
上图中红色标注的名称应该跟你下载的那个名称一样。
找到它,然后点击导出:一路的下一步,在以下几步需要注意
以上密码就是 config.php 里面生产环境要设置的那个密码,请设置为六位数字(仅限数字,请勿设置字母及符号)
指定导出证书的文件名,名称就设置为:acp_prod_sign,并选择目录存放证书,点击下一步,设置导出到桌面,完成后将在桌面看到一个 acp_prod_sign.pfx 的文件。这个就是生产环境要用到的私钥文件,将它复制一份到证书目录 /Public/cer 。下一步就要上传这个证书到商户服务网站。
4.2 上传证书到商户服务网站。登录 https://merchant.unionpay.com/portal/login.jsp
上传刚刚导出的那个 acp_prod_sign.pfx 文件,点击上传。
下一步,启用证书,点击安全证书管理,启用即可。
下一步,下载银联公钥
解压文件,把里面的两个证书同样放到 /Public/cer 里面。然后就去 config.php 里面根据文件注释切换到生产环境即可。
以下是TP3.2的代码资料:
/App/Home/Conf/config.php
<?php
return array(
//'配置项'=>'配置值'
'UNIONPAY' => array(// 银联配置
//测试环境参数
'frontUrl' => 'https://101.231.204.80:5000/gateway/api/frontTransReq.do', //测试环境前台交易请求地址
//'frontUrl' => 'https://gateway.95516.com/gateway/api/frontTransReq.do', //生产环境前台交易请求地址
'singleQueryUrl' => 'https://101.231.204.80:5000/gateway/api/backTransReq.do', //测试环境单笔查询请求地址
//'singleQueryUrl' => 'https://gateway.95516.com/gateway/api/queryTrans.do', //生产环境单笔查询请求地址
'signCertPath' =>getcwd().'/Public/cer/acp_test_sign.pfx', //签名证书路径 这个证书就是你在https://open.unionpay.com/ajweb/account/testPara 上面下载的那个商户私钥证书 供你测试使用
//'signCertPath' =>getcwd().'/Public/cer/acp_prod_sign.pfx', //签名证书路径 这个证书就是你在 商户开通邮件里面叫你去 http://cs.cfca.com.cn/ 下载并把私钥上传至商户服务网站并启用的那个私钥文件
'signCertPwd' => '000000', //测试环境签名证书密码
//'signCertPwd' => '135246', //生产环境证书签名证书密码 这个密码是你在IE导出上述私钥文件时候你自己定义的6位数字密码
//'verifyCertPath' => getcwd().'/Public/cer/verify_sign_acp.cer', //测试环境验签证书路径
'verifyCertPath' => getcwd().'/Public/cer/acp_prod_verify_sign.cer', //验签证书路径
'merId' => '777290058138754', //测试商户代码
//'merId' => '8024400481****', //生产环境商户代码 从你的商户开通邮件里面有
),
'UNIONPAY_CONFIG'=>array(// 银联配置
'version' => '5.0.0', //版本号
'encoding' => 'GBK', //编码方式
'signMethod' => '01', //签名方式
'txnType' => '01', //交易类型
'txnSubType' => '01', //交易子类
'bizType' => '000201', //产品类型
'channelType' => '07',//渠道类型
'frontUrl' => "http://win2.qbt8.com/ase_admin/index.php/Home/ypay/pay_success", //前台通知地址
'backUrl' => "http://win2.qbt8.com/ase_admin/index.php/Home/ypay/notify", //后台通知地址
'frontFailUrl' => "http://win2.qbt8.com/ase_admin/index.php/Home/ypay/pay_fail", //失败交易前台跳转地址
'accessType' => '0', //接入类型
'merId' => '777290058138754', //测试商户代码
//'merId' => '8024400481*****', //生产环境商户代码
'txnTime' => date('YmdHis'), //订单发送时间
'currencyCode' => '156', //交易币种
),
);
?>
- 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
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
控制器代码如下:
<?php
/*
|-------------------------------------------------------------|
| 银联在线支付控制器
|author:shuguang date:2016-11-16
|-------------------------------------------------------------|
*/
namespace Home\Controller;
use Think\Controller;
class YpayController extends Controller
{
/**
* 支付配置
* @var array
*/
public $config = array();
/**
* 支付参数,提交到银联对应接口的所有参数
* @var array
*/
public $params = array();
/**
* 自动提交表单模板
* @var string
*/
private $formTemplate = <<<HTML
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>支付</title>
</head>
<body>
<div style="text-align:center">跳转中...</div>
<form id="pay_form" name="pay_form" action="%s" method="post">
%s
</form>
<script type="text/javascript">
document.onreadystatechange = function(){
if(document.readyState == "complete") {
document.pay_form.submit();
}
};
</script>
</body>
</html>
HTML;
public function index(){
//前台表单
$this->display();
}
/*支付成功后 前台通知地址*/
public function pay_success(){
echo "<h1>支付成功!</h1>";
}
/*失败交易前台跳转地址*/
public function pay_fail(){
echo "<h1>支付失败!</h1>";
}
/*生产支付参数 提交支付 */
function usespay(){
$this->config = C('UNIONPAY');//从配置里读取
$config = C('UNIONPAY_CONFIG');
$config['certId'] = $this->getSignCertId(); //证书ID
$config['orderId'] = mt_rand(111111111,999999999);//订单号 自定义
$config['txnAmt'] = I("post.money")*100; //交易金额,单位分
$this->params = $config;
// $_SESSION['ceshi']=$config;
/* 以下是自己的业务逻辑操作 生产支付记录到本地数据库
$money = I("post.money");;
$user_id = $this->user_id;
$OrderId = $config['orderId'];//生成随机订单号
$pay_type = "银联";//支付方式 1余额 2支付宝
$pay_fee = M('handfee')->find(2);
if ($pay_fee['type'] == 1){
$fee=$pay_fee['rate']*$money;
}else {
$fee=$pay_fee['fee'];
}
//订单表数据
$order = array(
"order_id"=>$OrderId,
"uid"=>$user_id,
"pay_mode"=>1,
"pay_channels"=>2,
"fee"=>$fee,
"status"=>0,//待审核
"beizhu"=>"银联在线充值",
"ent_money"=>$money-$fee,
"time"=>time(),
"sub_time"=>time(),
"pay_money"=>$money,
"pay_type"=>$pay_type,//1余额支付 2支付宝支付
//"type"=>2
);*/
//$Ord=M('pay');
//$Ord->add($order);
$html = $this->createPostForm();//构建自动提交HTML表单
echo $html;
}
function ceshi(){
dump($_SESSION);
}
function usernotify(){// 付款后返回商家
}
function notify(){//后台通知路径
/*付款后业务逻辑代码 */
$orderId = $_POST ['orderId']; //其他字段也可用类似方式获取
$respCode = $_POST ['respCode']; //判断respCode=00或A6即可认为交易成功
if ($respCode=='00'||$respCode=='A6'){
/*通过写入文件的方式记录返回的订单号等 */
$str = "--------- ".date('Y-m-d H:i:s')." ---------";
$str .= "orderId:".$orderId."\r\n";
$str .= "respCode:".$respCode."\r\n";
$str .= "--------- END -----------"."\r\n";
file_put_contents('unionpay_notify_log.log', $str);
/* 以下是支付成功后的数据库操作 请根据需要自行操作
$order['status']=1;
$order['check_time']=time();
M('pay')->where(array('order_id'=>$orderId))->save($order);
$order_info = M('pay')->where(array('order_id'=>$orderId))->find();
$log['user_id']=$order_info['uid'];
$log['user_money']=$order_info['pay_money'];
$log['change_time']=time();
$log['desc']="银联在线充值";
M('account_log')->add($log);
M('users')->where('user_id='.$order_info['uid'])->setInc('user_money',$order_info['ent_money']);
*/
}
}
function unionpayfail(){
}
/* function orderPay($orderinfo,$state){
$filename = 'Log/yapy';
file_put_contents($filename.'/'.$orderinfo['orderId'].'.txt', json_encode($_POST), FILE_APPEND);
//$order = D('order');
//$payment = D('payment');
//$where['order_sn'] = array('in', array($orderinfo['orderId']));
//$orinfo = $order->where($where)->find();
$rs = $payment->where($where)->find();
if (empty($rs) && $orinfo['order_state'] < 2 ) {
$where1['udb_user.user_id'] = array('eq', session('id'));
$userinfo1 = json_decode(req_api("api_key", C("API_KEY"), C('USER_API') ."user/api/GetSomeuser/", array('where' => json_encode($where1))), true);
$data1['order_state'] = (int) $state;
//$orderwhere['order_sn'] = array('in', array($orderinfo['orderId']));
//$order->where($orderwhere)->save($data1);
if($orinfo['balance'] >0 && $orinfo['isblance'] == 1){
if($userinfo1[0]['balance']-$orinfo['balance']>=0){
$total1 = $total1-$data['balance'];
$istrue = req_api("api_key", C("API_KEY"), C('USER_API') . "user/api/removeBalance/", array('user' =>session('id'),'count'=>$orinfo['balance'],'type'=>'d'));
//$this->BanlanceRecord(2,$orinfo['balance'],'购物消费',session('id'));
}
}
if ($orinfo['jindou'] >0 && $orinfo['isjindou'] == 1) {
if($userinfo1[0]['user_wealth']-$orinfo['jindou']>=0){
$istrue = req_api("api_key", C("API_KEY"), C('USER_API') . "user/api/AddJindou/", array('user' =>session('id'),'count'=>$orinfo['jindou'],'type'=>'d'));
$this->ChangeRecord(2,$orinfo['jindou'],'购物抵消',session('id'));
$total1 = $total1-($orinfo['jindou']/100);
}
}
$data['order_sn'] = $orderinfo['orderId'];
$data['buyer_id'] = $orderinfo['certId'];
$data['buyer_user'] = '银联支付';
$data['is_success'] = 'T';
$data['notify_time'] = substr($orderinfo['txnTime'],0,4)."-".substr($orderinfo['txnTime'],4,2).'-'.substr($orderinfo['txnTime'],6,2).' '.substr($orderinfo['txnTime'],8,2).':'.substr($orderinfo['txnTime'],10,2).':'.substr($orderinfo['txnTime'],12,2);
$data['trade_no'] = $orderinfo['queryId'];
$data['seller_id'] = $orderinfo['merId'];
$data['total_fee'] = $orderinfo['txnAmt']*100;
$data['sign'] = $orderinfo['signature'];
$data['user_id'] = $orinfo['user_id'];
$data['order_state'] = (int) $state;
$data['status'] = 0;
$payment->data($data)->filter('strip_tags')->add();
}
$record = A('Shop/Orderrecord');
$shuju['order_state'] = (string) $state;
$shuju['action_user_id'] = session('id');
$shuju['action_descrption'] = $type.'支付宝付款' . $orinfo['payable_total'];
$record->ChangeOrderRecords($orinfo['_id'], $shuju);
$orderrecord = A('Shop/Order');
$orderrecord->CashMoneyRecord(2, $orinfo['payable_total'], '购物消费--订单(' . $orderinfo['out_trade_no'] . ')', session('id'));
layout(false);
$this->assign('orderinfo', $orinfo);
$this->display('Order:PaySuccess6');
} */
/**
* 构建自动提交HTML表单
* @return string
*/
public function createPostForm()
{
$this->params['signature'] = $this->sign();
$input = '';
foreach($this->params as $key => $item) {
$input .= "\t\t<input type=\"hidden\" name=\"{$key}\" value=\"{$item}\">\n";
}
return sprintf($this->formTemplate, $this->config['frontUrl'], $input);
}
/**
* 验证签名
* 验签规则:
* 除signature域之外的所有项目都必须参加验签
* 根据key值按照字典排序,然后用&拼接key=value形式待验签字符串;
* 然后对待验签字符串使用sha1算法做摘要;
* 用银联公钥对摘要和签名信息做验签操作
*
* @throws \Exception
* @return bool
*/
public function verifySign()
{
$publicKey = $this->getVerifyPublicKey();
$verifyArr = $this->filterBeforSign();
ksort($verifyArr);
$verifyStr = $this->arrayToString($verifyArr);
$verifySha1 = sha1($verifyStr);
$signature = base64_decode($this->params['signature']);
$result = openssl_verify($verifySha1, $signature, $publicKey);
if($result === -1) {
// throw new \Exception('Verify Error:'.openssl_error_string());
echo 'Verify Error:'.openssl_error_string();
}
return $result === 1 ? true : false;
}
/**
* 取签名证书ID(SN)
* @return string
*/
public function getSignCertId()
{
return $this->getCertIdPfx($this->config['signCertPath']);
}
/**
* 签名数据
* 签名规则:
* 除signature域之外的所有项目都必须参加签名
* 根据key值按照字典排序,然后用&拼接key=value形式待签名字符串;
* 然后对待签名字符串使用sha1算法做摘要;
* 用银联颁发的私钥对摘要做RSA签名操作
* 签名结果用base64编码后放在signature域
*
* @throws \InvalidArgumentException
* @return multitype|string
*/
private function sign() {
$signData = $this->filterBeforSign();
ksort($signData);
$signQueryString = $this->arrayToString($signData);
if($this->params['signMethod'] == 01) {
//签名之前先用sha1处理
//echo $signQueryString;exit;
$datasha1 = sha1($signQueryString);
$signed = $this->rsaSign($datasha1);
} else {
//throw new \InvalidArgumentException('Nonsupport Sign Method');
echo 'Nonsupport Sign Method';
}
return $signed;
}
/**
* 数组转换成字符串
* @param array $arr
* @return string
*/
private function arrayToString($arr)
{
$str = '';
foreach($arr as $key => $value) {
$str .= $key.'='.$value.'&';
}
return substr($str, 0, strlen($str) - 1);
}
/**
* 过滤待签名数据
* signature域不参加签名
*
* @return array
*/
private function filterBeforSign()
{
$tmp = $this->params;
unset($tmp['signature']);
return $tmp;
}
/**
* RSA签名数据,并base64编码
* @param string $data 待签名数据
* @return mixed
*/
private function rsaSign($data)
{
$privatekey = $this->getSignPrivateKey();
$result = openssl_sign($data, $signature, $privatekey);
if($result) {
return base64_encode($signature);
}
return false;
}
/**
* 取.pfx格式证书ID(SN)
* @return string
*/
private function getCertIdPfx($path)
{
$data = fopen($path);
$pkcs12certdata = file_get_contents($path);
openssl_pkcs12_read($pkcs12certdata, $certs, $this->config['signCertPwd']);
$x509data = $certs['cert'];
openssl_x509_read($x509data);
$certdata = openssl_x509_parse($x509data);
return $certdata['serialNumber'];
}
/**
* 取.cer格式证书ID(SN)
* @return string
*/
private function getCertIdCer($path)
{
$x509data = file_get_contents($path);
openssl_x509_read($x509data);
$certdata = openssl_x509_parse($x509data);
return $certdata['serialNumber'];
}
/**
* 取签名证书私钥
* @return resource
*/
private function getSignPrivateKey()
{
$pkcs12 = file_get_contents($this->config['signCertPath']);
openssl_pkcs12_read($pkcs12, $certs, $this->config['signCertPwd']);
return $certs['pkey'];
}
/**
* 取验证签名证书
* @throws \InvalidArgumentException
* @return string
*/
private function getVerifyPublicKey()
{
//先判断配置的验签证书是否银联返回指定的证书是否一致
if($this->getCertIdCer($this->config['verifyCertPath']) != $this->params['certId']) {
// throw new \InvalidArgumentException('Verify sign cert is incorrect');
echo 'Verify sign cert is incorrect';
}
return file_get_contents($this->config['verifyCertPath']);
}
}
- 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
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
视图文件内容如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>银联支付测试</title>
</head>
<body>
<h1>银联支付测试</h1>
<form action="{:U('Ypay/usespay')}" method="post">
支付金额:<input type="text" name="money" value="0.1" />
<input type="submit" value="确定支付" />
</form>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
证书文件这里上传不了,放个截图好了,都可以自己去下载的: