AlipayNotify.php 7.8 KB

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