|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace App\Components; |
| 4 | + |
| 5 | +class AlipayNotify { |
| 6 | + /** |
| 7 | + * HTTPS形式消息验证地址 |
| 8 | + */ |
| 9 | + var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&'; |
| 10 | + /** |
| 11 | + * HTTP形式消息验证地址 |
| 12 | + */ |
| 13 | + var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?'; |
| 14 | + |
| 15 | + |
| 16 | + var $sign_type = "MD5"; |
| 17 | + var $partner = ""; |
| 18 | + var $md5_key = ""; |
| 19 | + var $private_key = ""; |
| 20 | + var $alipay_public_key = ""; |
| 21 | + var $transport = "http"; |
| 22 | + |
| 23 | + /** |
| 24 | + * 构造函数 |
| 25 | + * @param $sign_type 加密方式 MD5/RSA |
| 26 | + * return |
| 27 | + */ |
| 28 | + function __construct($sign_type, $partner, $md5_key, $private_key, $alipay_public_key, $transport){ |
| 29 | + $this->sign_type = $sign_type; |
| 30 | + $this->partner = $partner; |
| 31 | + $this->md5_key = $md5_key; |
| 32 | + $this->private_key = $private_key; |
| 33 | + $this->alipay_public_key = $alipay_public_key; |
| 34 | + $this->transport = $transport; |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * 针对notify_url验证消息是否是支付宝发出的合法消息 |
| 39 | + * @return 验证结果 |
| 40 | + */ |
| 41 | + public function verifyNotify(){ |
| 42 | + if(empty($_POST)) {//判断POST来的数组是否为空 |
| 43 | + //Tools::Log("POST来的数组为空"); |
| 44 | + return false; |
| 45 | + } |
| 46 | + else { |
| 47 | + //生成签名结果 |
| 48 | + $isSign = $this->getSignVeryfy($_POST, $_POST["sign"]); |
| 49 | + //Tools::Log($isSign); |
| 50 | + $converted_res = ($isSign) ? 'true' : 'false'; |
| 51 | + //获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息) |
| 52 | + $responseTxt = 'false'; |
| 53 | + if (! empty($_POST["notify_id"])) { |
| 54 | + $responseTxt = $this->getResponse($_POST["notify_id"]); |
| 55 | + } |
| 56 | + //Tools::Log($responseTxt); |
| 57 | + //验证 |
| 58 | + //$responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关 |
| 59 | + //isSign的结果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关 |
| 60 | + if (preg_match("/true$/i",$responseTxt) && $isSign) { |
| 61 | + return true; |
| 62 | + } else { |
| 63 | + return false; |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * 获取返回时的签名验证结果 |
| 70 | + * @param $para_temp 通知返回来的参数数组 |
| 71 | + * @param $sign 返回的签名结果 |
| 72 | + * @return 签名验证结果 |
| 73 | + */ |
| 74 | + function getSignVeryfy($para_temp, $sign) { |
| 75 | + //除去待签名参数数组中的空值和签名参数 |
| 76 | + $para_filter = $this->paraFilter($para_temp); |
| 77 | + |
| 78 | + //对待签名参数数组排序 |
| 79 | + $para_sort = $this->argSort($para_filter); |
| 80 | + |
| 81 | + //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 |
| 82 | + $prestr = $this->createLinkstring($para_sort); |
| 83 | + |
| 84 | + $isSgin = false; |
| 85 | + switch (strtoupper(trim($this->sign_type))) { |
| 86 | + case "RSA" : |
| 87 | + $isSgin = $this->rsaVerify($prestr, trim($this->alipay_public_key), $sign); |
| 88 | + break; |
| 89 | + case "MD5" : |
| 90 | + $isSgin = $this->md5Verify($prestr, $sign, trim($this->md5_key)); |
| 91 | + break; |
| 92 | + default : |
| 93 | + $isSgin = false; |
| 94 | + } |
| 95 | + return $isSgin; |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * 获取远程服务器ATN结果,验证返回URL |
| 100 | + * @param $notify_id 通知校验ID |
| 101 | + * @return 服务器ATN结果 |
| 102 | + * 验证结果集: |
| 103 | + * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 |
| 104 | + * true 返回正确信息 |
| 105 | + * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟 |
| 106 | + */ |
| 107 | + function getResponse($notify_id) { |
| 108 | + $transport = strtolower(trim($this->transport)); |
| 109 | + $partner = trim($this->partner); |
| 110 | + $veryfy_url = ''; |
| 111 | + if($transport == 'https') { |
| 112 | + $veryfy_url = $this->https_verify_url; |
| 113 | + } |
| 114 | + else { |
| 115 | + $veryfy_url = $this->http_verify_url; |
| 116 | + } |
| 117 | + $veryfy_url = $veryfy_url."partner=" . $partner . "¬ify_id=" . $notify_id; |
| 118 | + $responseTxt = $this->getHttpResponseGET($veryfy_url, base_path('ca/cacert_alipay.pem')); |
| 119 | + |
| 120 | + return $responseTxt; |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * RSA验签 |
| 125 | + * @param $data 待签名数据 |
| 126 | + * @param $alipay_public_key 支付宝的公钥字符串 |
| 127 | + * @param $sign 要校对的的签名结果 |
| 128 | + * return 验证结果 |
| 129 | + */ |
| 130 | + function rsaVerify($data, $alipay_public_key, $sign) { |
| 131 | + //以下为了初始化私钥,保证在您填写私钥时不管是带格式还是不带格式都可以通过验证。 |
| 132 | + $alipay_public_key=str_replace("-----BEGIN PUBLIC KEY-----","",$alipay_public_key); |
| 133 | + $alipay_public_key=str_replace("-----END PUBLIC KEY-----","",$alipay_public_key); |
| 134 | + $alipay_public_key=str_replace("\n","",$alipay_public_key); |
| 135 | + |
| 136 | + $alipay_public_key='-----BEGIN PUBLIC KEY-----'.PHP_EOL.wordwrap($alipay_public_key, 64, "\n", true) .PHP_EOL.'-----END PUBLIC KEY-----'; |
| 137 | + $res=openssl_get_publickey($alipay_public_key); |
| 138 | + if($res) |
| 139 | + { |
| 140 | + $result = (bool)openssl_verify($data, base64_decode($sign), $res); |
| 141 | + } |
| 142 | + else { |
| 143 | + //Tools::Log("您的支付宝公钥格式不正确!"."<br/>"."The format of your alipay_public_key is incorrect!"); |
| 144 | + exit(); |
| 145 | + } |
| 146 | + openssl_free_key($res); |
| 147 | + return $result; |
| 148 | + } |
| 149 | + |
| 150 | + /** |
| 151 | + * 验证签名 sign verify |
| 152 | + * @param $prestr 需要签名的字符串pre-sign string |
| 153 | + * @param $sign 签名结果 |
| 154 | + * @param $key 私钥 |
| 155 | + * return 签名结果sign generated |
| 156 | + */ |
| 157 | + function md5Verify($prestr, $sign, $key) { |
| 158 | + $prestr = $prestr . $key; |
| 159 | + $mysgin = md5($prestr); |
| 160 | + |
| 161 | + if($mysgin == $sign) { |
| 162 | + return true; |
| 163 | + } |
| 164 | + else { |
| 165 | + return false; |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + /** |
| 170 | + * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 |
| 171 | + * @param $para 需要拼接的数组 |
| 172 | + * return 拼接完成以后的字符串 |
| 173 | + */ |
| 174 | + function createLinkstring($para) { |
| 175 | + $arg = ""; |
| 176 | + while (list ($key, $val) = each ($para)) { |
| 177 | + $arg.=$key."=".$val."&"; |
| 178 | + } |
| 179 | + //去掉最后一个&字符 |
| 180 | + $arg = substr($arg,0,count($arg)-2); |
| 181 | + |
| 182 | + //如果存在转义字符,那么去掉转义 |
| 183 | + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} |
| 184 | + |
| 185 | + return $arg; |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * 远程获取数据,GET模式 |
| 190 | + * 注意: |
| 191 | + * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了 |
| 192 | + * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem' |
| 193 | + * @param $url 指定URL完整路径地址 |
| 194 | + * @param $cacert_url 指定当前工作目录绝对路径 |
| 195 | + * return 远程输出的数据 |
| 196 | + */ |
| 197 | + function getHttpResponseGET($url,$cacert_url) { |
| 198 | + $curl = curl_init($url); |
| 199 | + curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头 |
| 200 | + curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 |
| 201 | + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证 |
| 202 | + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 |
| 203 | + curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址 |
| 204 | + $responseText = curl_exec($curl); |
| 205 | + //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容 |
| 206 | + curl_close($curl); |
| 207 | + |
| 208 | + return $responseText; |
| 209 | + } |
| 210 | + |
| 211 | + /** |
| 212 | + * 除去数组中的空值和签名参数 |
| 213 | + * @param $para 签名参数组 |
| 214 | + * return 去掉空值与签名参数后的新签名参数组 |
| 215 | + */ |
| 216 | + function paraFilter($para) { |
| 217 | + $para_filter = array(); |
| 218 | + while (list ($key, $val) = each ($para)) { |
| 219 | + if($key == "sign" || $key == "sign_type" || $val == "")continue; |
| 220 | + else $para_filter[$key] = $para[$key]; |
| 221 | + } |
| 222 | + return $para_filter; |
| 223 | + } |
| 224 | + |
| 225 | + /** |
| 226 | + * 对数组排序 |
| 227 | + * @param $para 排序前的数组 |
| 228 | + * return 排序后的数组 |
| 229 | + */ |
| 230 | + function argSort($para) { |
| 231 | + ksort($para); |
| 232 | + reset($para); |
| 233 | + return $para; |
| 234 | + } |
| 235 | +} |
| 236 | +?> |
0 commit comments