TP3.2.3 接入银联支付
TP3.2.3 接入银联支付
项目接入银联支付的过程, 在此记录下,希望能帮助开发盆友平坑。
银联SKD链接:https://open.unionpay.com/ajweb/product/newProDetail?proId=1&cataId=14
首先我们先下载官方提供的SDK ,下载好了解压选择版本 ,里面有PHP java .net 这里我们自然是选择PHP版本的,接入前我们先配环境,它要我们PHP
的版本 在5.3以上,并且需开启环境的curl、openssl功能。
然后就是它提供的测试证书了,默认在window系统是放在D:/certs ,意思是在你的电脑的D创建一个名为certs 的文件夹,然后将4个证书放进去,测试的名为
acp_test_enc.cer acp_test_middle.cer acp_test_root.cer acp_test_sign.pfx ,在创建一个名为logs文件夹D:/logs/ 放支付生成的日志文件,linux中请
修改成Linux中的路径。
如图:
你可以在assets文件夹中找到你要的证书,测试环境官方提供四个证书,生产环境官方提供三个,还个签名证书就是后缀为.pfx 的需要你去你的银联那里申请
下载对应的你还需要可以需要解签名证书的密码 和商户号 , 这些后面都会提到的。
在然后我们将名为SDK文件夹中的六个文件放到我们项目放到 ThinkPHP\Library\Vendor\Yunpay 的文件夹中
写逻辑代码前你还得前配置好你的acp_sdk.ini 文件 ,文件里面都写的很清楚,这里就不一一解释了,这里是我的支付代码
//银联充值操作 public function pay() { header ( 'Content-type:text/html;charset=utf-8' ); Vendor('Yunpay.acp_service'); //前台通知地址 $frontUrl = "http://".I("server.HTTP_HOST")."/Assets/rechargedetail"; //后台通知地址 $backUrl = "http://".I("server.HTTP_HOST"); $params = array( //以下信息非特殊情况不需要改动 'version' => \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'txnType' => '01', //交易类型 'txnSubType' => '01', //交易子类 'bizType' => '000201', //业务类型 'frontUrl' => $frontUrl, //前台通知地址 'backUrl' => $backUrl, //后台通知地址 'signMethod' => \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->signMethod, //签名方法 'channelType' => '08', //渠道类型,07-PC,08-手机 'accessType' => '0', //接入类型 'currencyCode' => '156', // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。 'payTimeout' => date('YmdHis', strtotime('+15 minutes')) //订单发送时间 ); $txnAmt = I('post.txnAmt'); //交易金额 $orderId = I('post.orderId'); //商户订单号 //加入商户参数 $params['txnAmt'] = $txnAmt*100; $params['merId'] = C('Yunpay.merId'); //商户号 $params['orderId'] = $orderId; $params['txnTime'] = date('YmdHis'); //商品描述,可空 $body = trim(I('post.WIDbody')); $ud = session('users.uid'); $data = array( 'uid'=>$ud, //用户id 'win_code'=>$orderId, //商户订单号 'winsubject'=>I('post.WIDsubject'), //订单名称 'wintotal_amount'=>$txnAmt, //付款金额 'winbody'=>I('post.WIDbody'), //商品描述 'state'=>'yl', //支付方式 'status'=>'0', //是否支付 'ordertime'=>time() //交易时间 ); M("pay_record")->add($data); // 保存交易信息 \com\unionpay\acp\sdk\AcpService::sign ( $params ); $uri = \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->frontTransUrl; $html_form = \com\unionpay\acp\sdk\AcpService::createAutoFormHtml( $params, $uri ); echo $html_form; }
这里我曾试过删除里面的命名空间用 new的方式去写,这样可以简洁代码,但是后面异步的时候报错,客服说不能删除命名空间,这样会导致方法名重复,所以还是乖乖的用demo
中提供的方法。
异步方法--------
1 //银联充值异步 2 public function xxx() 3 { 4 Vendor('Yunpay.acp_service'); 5 $logger = \com\unionpay\acp\sdk\LogUtil::getLogger(); 6 $logger->LogInfo("receive back notify: " . \com\unionpay\acp\sdk\createLinkString ( $_POST, false, true )); 7 if (isset ( $_POST ['signature'] )) { 8 // echo \com\unionpay\acp\sdk\AcpService::validate ( $_POST ) ? '验签成功' : '验签失败'; 9 $respCode = I('post.respCode'); 10 $orderId = I('post.orderId'); // 商户订单号 11 $total_amount = I('post.settleAmt'); //订单金额 12 $trade_no = I('post.queryId'); // queryId 银联唯一标识一笔交易 13 14 //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。 15 if( $respCode=='00' ){ 16 $this->unionpay($orderId,$total_amount,$trade_no); 17 }else{ 18 $res = $this->confirmpay($orderId,'1'); 19 if( $res == 'Successful' ){ 20 $this->unionpay($orderId,$total_amount,$trade_no); 21 } else { 22 echo '交易失败'; 23 } 24 } 25 26 } else { 27 echo '签名为空'; 28 } 29 30 31 } 32 33 34 //银联充值 35 public function unionpay($orderId,$total_amount,$trade_no) 36 { 37 $per = M("pay_record")->where('win_code='.$orderId)->find(); //查找该订单 38 if( $per['status']=='1' ){ 39 echo '已充值'; 40 return; 41 } 42 $Pay = M("pay"); 43 // 在Pay模型中启动事务 44 $Pay->startTrans(); 45 // 进行相关的业务逻辑操作 46 $res = $Pay->where('uid='.$per['uid'])->setInc('money',$total_amount/100); 47 //数据组合 48 $data = array( 49 'alipay_number'=>$trade_no, //银联唯一标识 50 'status'=>'1', //交易状态 51 'paytime'=>time() //交易时间 52 ); 53 M("pay_record")->where('win_code='.$orderId)->save($data); // 修改交易信息 54 55 if (!empty($res)){ 56 // 提交事务 57 $Pay->commit(); 58 }else{ 59 // 事务回滚 60 $Pay->rollback(); 61 } 62 } 63 64 65 //确定是否充值操作 66 public function confirmpay($orderId,$L) 67 { 68 header ( 'Content-type:text/html;charset=utf-8' ); 69 Vendor('Yunpay.acp_service'); 70 $params = array( 71 //以下信息非特殊情况不需要改动 72 'version' => \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->version, //版本号 73 'encoding' => 'utf-8', //编码方式 74 'signMethod' => \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->signMethod, //签名方法 75 'txnType' => '00', //交易类型 76 'txnSubType' => '00', //交易子类 77 'bizType' => '000000', //业务类型 78 'accessType' => '0', //接入类型 79 'channelType' => '07', //渠道类型 80 ); 81 if($L == '0'){ 82 $time = M("order_pay")->where('win_code = "'.$orderId.'"')->find()['addtime']; 83 }else{ 84 $time = M("pay_record")->where('win_code = "'.$orderId.'"')->find()['ordertime']; 85 } 86 $params['merId'] = C('Yunpay.merId'); //商户号 87 $params['orderId'] = $orderId; //交易的订单号 88 $params['txnTime'] = date('YmdHis',$time); //订单发送时间 89 90 \com\unionpay\acp\sdk\AcpService::sign ( $params ); // 签名 91 $url = \com\unionpay\acp\sdk\SDKConfig::getSDKConfig()->singleQueryUrl; 92 93 $result_arr = \com\unionpay\acp\sdk\AcpService::post ( $params, $url); 94 if(count($result_arr)<=0) { //没收到200应答的情况 95 return 'No200'; 96 } 97 if (!\com\unionpay\acp\sdk\AcpService::validate ($result_arr) ){ 98 return "应答报文验签失败"; 99 } 100 if ($result_arr["respCode"] == "00"){ 101 if ($result_arr["origRespCode"] == "00"){ 102 //交易成功 103 //TODO 104 return "Successful"; 105 } else if ($result_arr["origRespCode"] == "03" 106 || $result_arr["origRespCode"] == "04" 107 || $result_arr["origRespCode"] == "05"){ 108 //后续需发起交易状态查询交易确定交易状态 109 //TODO 110 return "交易处理中,请稍微查询"; 111 } else { 112 //其他应答码做以失败处理 113 //TODO 114 return "交易失败:" . $result_arr["origRespMsg"]; 115 } 116 } else if ($result_arr["respCode"] == "03" 117 || $result_arr["respCode"] == "04" 118 || $result_arr["respCode"] == "05" ){ 119 //后续需发起交易状态查询交易确定交易状态 120 //TODO 121 return "处理超时,请稍微查询"; 122 } else { 123 //其他应答码做以失败处理 124 //TODO 125 return "失败:" . $result_arr["respMsg"]; 126 } 127 128 }
这里第一个方法xxx 中的respCode等于00 就是支付成功 ,如果没有需要根据你生成的订单号在次查询在结果。这里客服说这种失败不好模拟,就不说了,但是这操作方法还是
建议写下,以防万一 。
最后说明下几个参数 queryId 银联唯一标识,需要保存, 还有银联支付是按 '分' 做单位的 所以支付跳转前 假如是1元,你得乘以100,它才可以识别为1元,要不然就是0.01元
然后异步到你的时候,如果你是元的单位在除于100,如果是分就不用了。
搞定收工 , 祝大家早日成为大牛