AlipayNotify.php 6.7 KB

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