AlipayNotify.php 6.8 KB

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