1 <?php 2 /* 3 支付宝封装类 4 采用 rsa 生成签名模式 5 */ 6 class Alipay { 7 private $_config = array(); 8 private $_alipay_gateway_new = 'https://mapi.alipay.com/gateway.do?'; //支付宝网关地址(新) 9 private $_https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&'; //校验用到 10 private $_fix_config = array(); 11 12 13 public function __construct() { 14 $this->_makeFixconfig(); 15 } 16 17 //读取固定参数 18 private function _makeFixconfig() { 19 $this->_fix_config = array( 20 'service' => 'alipay.wap.create.direct.pay.by.user', 21 '_input_charset' => 'utf-8', 22 'sign_type' => 'RSA', 23 'seller_id' => '', 24 'private_key_path' => VENDOR_PATH . '/Alipay/rsa_private_key.pem',//私密key 25 'ali_public_key_path' => VENDOR_PATH . '/Alipay/alipay_public_key.pem',//公开key 26 'cacert' => VENDOR_PATH . '/Alipay/cacert.pem',//https请求用到验证 27 'transport' => 'http', 28 'payment_type' => 1,//固定参数 29 ); 30 } 31 32 /* 33 config 需要参数 34 partner //合作身份者id,以2088开头的16位纯数字 35 notify_url //阿里处理后,异步处理url地址,http开头 36 return_url //阿里处理后,同步处理url地址 37 38 out_trade_no //订单号 39 subject //商品名称 40 total_fee //商品金额 41 42 43 */ 44 //生成配置文件,并请求 45 public function run($config) { 46 foreach ($config as $value) { 47 if(empty($value)) 48 exit('缺少必要参数'); 49 } 50 51 $this->_fix_config['seller_id'] = $config['partner']; 52 53 $this->_config = array_merge($this->_fix_config,$config); 54 55 //签名,请求 56 $this->_makeSign(); 57 } 58 59 60 //生成签名信息,并发送请求 61 private function _makeSign() { 62 //获取签名需要的参数 63 $param = array( 64 '_input_charset' => trim($this->_config['_input_charset']), 65 'service' => trim($this->_config['service']), 66 'partner' => trim($this->_config['partner']), 67 'seller_id' => trim($this->_config['seller_id']), 68 'payment_type' => trim($this->_config['payment_type']), 69 'notify_url' => trim($this->_config['notify_url']), 70 'return_url' => trim($this->_config['return_url']), 71 'out_trade_no' => trim($this->_config['out_trade_no']), 72 'subject' => trim($this->_config['subject']), 73 'total_fee' => trim($this->_config['total_fee']), 74 ); 75 76 //对数组进行排序 77 ksort($param); 78 reset($param); 79 80 //读取pem信息,生成key 81 if(!file_exists($this->_config['private_key_path']) || !file_exists($this->_config['ali_public_key_path'])) 82 exit('缺少pem必要参数,请检查'); 83 84 if(!function_exists('openssl_pkey_get_private')) 85 exit('不支持openssl'); 86 87 $query_string = ''; 88 $query_string = $this->_makeSignString($param); 89 90 $private_key_info = file_get_contents($this->_config['private_key_path']); 91 92 $private_key_res = openssl_pkey_get_private($private_key_info); 93 94 $sign = ''; 95 openssl_sign($query_string, $sign, $private_key_res); 96 97 openssl_free_key($private_key_res); 98 99 //base64编码 100 $param['sign'] = base64_encode($sign); 101 102 //验证签名 103 // $this->_checkSign($query_string,$param['sign']); 104 105 $param['sign_type'] = trim($this->_config['sign_type']); 106 107 //触发表单请求,使用get模拟请求会失败 108 $this->_formPush($param,'确认'); 109 } 110 111 /* 112 --不能用模拟请求 113 发送get请求 114 $url 请求url地址 115 */ 116 private function _makeGetHttp($url) { 117 $curl = curl_init($url); 118 119 // curl_setopt($curl, CURLOPT_POST, 1); 120 curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头 121 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 122 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证 123 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 124 curl_setopt($curl, CURLOPT_CAINFO,$this->_fix_config['cacert']);//证书地址 125 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); 126 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 127 curl_setopt($curl, CURLOPT_USERAGENT,"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"); 128 129 $result = curl_exec($curl); 130 131 $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); 132 $curlErrNo = curl_errno($curl); 133 $curlErr = curl_error($curl); 134 135 curl_close($curl); 136 137 if ($httpCode == "0") 138 die("Curl error number:" . $curlErrNo . " , Curl error details:" . $curlErr . " "); 139 else if ($httpCode != "200") 140 die("Http code:" . $httpCode . " details:" . $result . " "); 141 142 return $result; 143 } 144 145 /* 146 采用表单提交 147 */ 148 private function _formPush($para_temp,$button_name) { 149 $sHtml = '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>支付宝通信中...</title><link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css"></head><body>'; 150 151 $sHtml .= '<div class="container-fluid"><div class="row" style="margin-top:200px;"><div class="col-xs-10 col-md-10 col-xs-offset-1 col-md-offset-1"><span class="btn btn-success btn-block">请稍后,连接支付宝...</span></div></div></div>'; 152 153 $sHtml .= "<div style='display:none;'><form id='alipaysubmit' name='alipaysubmit' action='".$this->_alipay_gateway_new."_input_charset=".trim(strtolower($this->_config['_input_charset']))."' method='get'>"; 154 while (list ($key, $val) = each ($para_temp)) { 155 $sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>"; 156 } 157 158 //submit按钮控件请不要含有name属性 159 $sHtml = $sHtml."<input type='submit' value='".$button_name."'></form></div></body></html>"; 160 161 $sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>"; 162 163 164 echo $sHtml; 165 } 166 167 168 /* 169 构造签名字符串 170 */ 171 private function _makeSignString($arr,$type=false) { 172 $tmp_str = ''; 173 foreach ($arr as $key => $value) { 174 if($key == 'sign' || $key == 'sign_type') 175 continue; 176 $tmp_str .= $key . '=' . $value . '&'; 177 } 178 179 return trim($tmp_str,'&'); 180 } 181 182 /* 183 验证rsa签名 184 @param $data 返回的get或者post的总数组, 185 $sign get['sign'] 或者 post['sign'] 186 @return bool 187 */ 188 public function checkSign($data,$sign) { 189 190 //处理数据 191 $new_data = array(); 192 foreach ($data as $key => $value) { 193 if($key == 'sign' || $key == 'sign_type' || $key == '_URL_' || $value == '') 194 continue; 195 $new_data[$key] = $value; 196 } 197 198 ksort($new_data); 199 reset($new_data); 200 201 $new_data_str = ''; 202 $new_data_str = $this->_makeSignString($new_data); 203 204 $ali_public_key_info = file_get_contents($this->_fix_config['ali_public_key_path']); 205 $ali_public_key_res = openssl_pkey_get_public($ali_public_key_info); 206 207 $ali_public_key_verify = openssl_verify($new_data_str, base64_decode($sign), $ali_public_key_res); 208 openssl_free_key($ali_public_key_res); 209 210 if(!$ali_public_key_verify) 211 return false; 212 return true; 213 } 214 215 216 /* 217 验证url来源是否为支付宝 218 @param partner 合作账号 219 notify_id get|post返回的 220 */ 221 public function checkUrlFrom($partner,$notify_id) { 222 $url = $this->_https_verify_url . 'partner=' . $partner . '¬ify_id=' . $notify_id; 223 return $this->_makeGetHttp($url); 224 } 225 226 227 228 229 230 231 232 233 234 235 236 }
调用案例:
通知页面不能有阻拦,比如说要登录才能进入之类的
1 /* 2 测试支付宝 3 */ 4 public function test_alipay() { 5 //引入 6 Vendor('Alipay.Alipay'); 7 8 //生成参数 9 $param = array( 10 'partner' => C('PARTENR'),//2088开头的支付宝信息 11 'notify_url' => 'http://' . $_SERVER['HTTP_HOST'] . U('Leasegoods/alipay_notify_url'),//异步通知页面 12 'return_url' => 'http://' . $_SERVER['HTTP_HOST'] . U('Leasegoods/alipay_return_url'),//同步通知页面 13 'out_trade_no' => $this->_dealOrderNum(),//订单号 14 'subject' => 'lxd',//商品名称 15 'total_fee' => '0.01',//商品价格 16 17 ); 18 19 $alipay_obj = new Alipay(); 20 $alipay_obj->run($param); 21 }
通知页面验证:
同步异步都类似,第一 校验签名;第二 校验来源;其余的就是业务之类的东西了
1 public function alipay_return_url() { 2 $info = serialize($_GET); 3 4 $data = array(); 5 //判断状态 6 if(isset($_GET) && $_GET['is_success'] == 'T') { 7 $order_index = $_GET['out_trade_no']; 8 9 //校验签名 10 Vendor('Alipay.Alipay'); 11 $alipay_obj = new Alipay(); 12 $res = $alipay_obj->checkSign($_GET,$_GET['sign']); 13 14 if(!$res){ 15 $data['type'] = 'error'; 16 $data['info'] = '警告:服务器校验数据失败'; 17 $this->assign('data',$data); 18 $this->display('info');die; 19 } 20 21 //校验来源 22 $res = null; 23 $res = $alipay_obj->checkUrlFrom(C('PARTENR'),$_GET['notify_id']); 24 if($res == 'false'){ 25 $data['type'] = 'error'; 26 $data['info'] = '错误:服务器校验来源错误'; 27 $this->assign('data',$data); 28 $this->display('info');die; 29 } 30 31 //记录流水 32 $alipay_log_model = M('alipay_log'); 33 $insert_res = $alipay_log_model->add(array('order_index' => $order_index,'type' => 'return','info' => $info)); 34 if(!$insert_res){ 35 $data['type'] = 'error'; 36 $data['info'] = '警告:用户支付成功,服务器流水记录失败'; 37 $this->assign('data',$data); 38 $this->display('info');die; 39 } 40 41 42 //接口请求成功 43 if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') { 44 45 //交易成功 46 $data['type'] = 'success'; 47 $data['info'] = '成功:交易成功,请返回'; 48 }else { 49 50 59 //交易失败 60 $data['type'] = 'error'; 61 $data['info'] = '失败:支付失败,请重试'; 62 } 63 }else { 64 //接口请求失败 65 $data['type'] = 'error'; 66 $data['info'] = '失败:服务器调用支付宝失败'; 67 } 68 $this->assign('data',$data); 69 $this->display('info'); 70 }