SdkPayment.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. <?php
  2. namespace app\common\components\alipay\Wap;
  3. use app\common\events\PayLog;
  4. use app\common\services\alipay\WapAlipay;
  5. class SdkPayment
  6. {
  7. private $__gateway_new = 'https://mapi.alipay.com/gateway.do?';
  8. private $__https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
  9. private $__http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
  10. private $service = 'alipay.wap.create.direct.pay.by.user';
  11. private $partner;
  12. private $_input_charset = 'UTF-8';
  13. private $sign_type = 'MD5';
  14. private $notify_url;
  15. private $return_url;
  16. private $out_trade_no;
  17. private $payment_type = 1;
  18. private $seller_id;
  19. private $total_fee;
  20. private $subject;
  21. private $body;
  22. private $it_b_pay;
  23. private $show_url;
  24. private $exter_invoke_ip;
  25. private $app_pay = 'Y';
  26. private $key;
  27. private $transport;
  28. private $cacert;
  29. public function __construct()
  30. {
  31. $this->cacert = getcwd() . '/../cacert.pem';
  32. }
  33. /**
  34. * 取得支付链接
  35. */
  36. public function getPayLink()
  37. {
  38. $parameter = array(
  39. 'service' => $this->service,
  40. 'partner' => $this->partner,
  41. 'payment_type' => $this->payment_type,
  42. 'notify_url' => $this->notify_url,
  43. 'return_url' => $this->return_url,
  44. 'seller_id' => $this->seller_id,
  45. 'out_trade_no' => $this->out_trade_no,
  46. 'subject' => $this->subject,
  47. 'total_fee' => $this->total_fee,
  48. 'body' => $this->body,
  49. 'it_b_pay' => $this->it_b_pay,
  50. 'show_url' => $this->show_url,
  51. 'exter_invoke_ip' => $this->exter_invoke_ip,
  52. 'app_pay' => $this->app_pay,
  53. '_input_charset' => strtolower($this->_input_charset)
  54. );
  55. //请求数据日志
  56. event(new PayLog($parameter, new WapAlipay()));
  57. $para = $this->buildRequestPara($parameter);
  58. return $this->__gateway_new . $this->createLinkstringUrlencode($para);
  59. }
  60. /**
  61. * 验证消息是否是支付宝发出的合法消息
  62. */
  63. public function verify()
  64. {
  65. // 判断请求是否为空
  66. if (empty($_POST) && empty($_GET)) {
  67. return false;
  68. }
  69. $data = $_POST ?: $_GET;
  70. // 生成签名结果
  71. $is_sign = $this->getSignVeryfy($data, $data['sign']);
  72. // 获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息)
  73. $response_txt = 'true';
  74. if (! empty($data['notify_id'])) {
  75. $response_txt = $this->getResponse($data['notify_id']);
  76. }
  77. // 验证
  78. // $response_txt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
  79. // isSign的结果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关
  80. if (preg_match('/true$/i', $response_txt) && $is_sign) {
  81. return true;
  82. } else {
  83. return false;
  84. }
  85. }
  86. public function setPartner($partner)
  87. {
  88. $this->partner = $partner;
  89. return $this;
  90. }
  91. public function setNotifyUrl($notify_url)
  92. {
  93. $this->notify_url = $notify_url;
  94. return $this;
  95. }
  96. public function setReturnUrl($return_url)
  97. {
  98. $this->return_url = $return_url;
  99. return $this;
  100. }
  101. public function setOutTradeNo($out_trade_no)
  102. {
  103. $this->out_trade_no = $out_trade_no;
  104. return $this;
  105. }
  106. public function setKey($key)
  107. {
  108. $this->key = $key;
  109. return $this;
  110. }
  111. public function setSellerId($seller_id)
  112. {
  113. $this->seller_id = $seller_id;
  114. return $this;
  115. }
  116. public function setTotalFee($total_fee)
  117. {
  118. $this->total_fee = $total_fee;
  119. return $this;
  120. }
  121. public function setSubject($subject)
  122. {
  123. $this->subject = $subject;
  124. return $this;
  125. }
  126. public function setBody($body)
  127. {
  128. $this->body = $body;
  129. return $this;
  130. }
  131. public function setItBPay($it_b_pay)
  132. {
  133. $this->it_b_pay = $it_b_pay;
  134. return $this;
  135. }
  136. public function setShowUrl($show_url)
  137. {
  138. $this->show_url = $show_url;
  139. return $this;
  140. }
  141. public function setSignType($sign_type)
  142. {
  143. $this->sign_type = $sign_type;
  144. return $this;
  145. }
  146. public function setExterInvokeIp($exter_invoke_ip)
  147. {
  148. $this->exter_invoke_ip = $exter_invoke_ip;
  149. return $this;
  150. }
  151. public function setAppPay($app_pay)
  152. {
  153. $this->app_pay = $app_pay;
  154. return $this;
  155. }
  156. /**
  157. * 生成要请求给支付宝的参数数组
  158. * @param $para_temp 请求前的参数数组
  159. * @return 要请求的参数数组
  160. */
  161. private function buildRequestPara($para_temp)
  162. {
  163. //除去待签名参数数组中的空值和签名参数
  164. $para_filter = $this->paraFilter($para_temp);
  165. //对待签名参数数组排序
  166. $para_sort = $this->argSort($para_filter);
  167. //生成签名结果
  168. $mysign = $this->buildRequestMysign($para_sort);
  169. //签名结果与签名方式加入请求提交参数组中
  170. $para_sort['sign'] = $mysign;
  171. $para_sort['sign_type'] = strtoupper(trim($this->sign_type));
  172. return $para_sort;
  173. }
  174. /**
  175. * 除去数组中的空值和签名参数
  176. * @param $para 签名参数组
  177. * return 去掉空值与签名参数后的新签名参数组
  178. */
  179. private function paraFilter($para)
  180. {
  181. $para_filter = array();
  182. while ((list ($key, $val) = each($para)) == true) {
  183. if ($key == 'sign' || $key == 'sign_type' || $val == '') {
  184. continue;
  185. } else {
  186. $para_filter[$key] = $para[$key];
  187. }
  188. }
  189. return $para_filter;
  190. }
  191. /**
  192. * 对数组排序
  193. * @param $para 排序前的数组
  194. * return 排序后的数组
  195. */
  196. private function argSort($para)
  197. {
  198. ksort($para);
  199. reset($para);
  200. return $para;
  201. }
  202. /**
  203. * 生成签名结果
  204. * @param $para_sort 已排序要签名的数组
  205. * return 签名结果字符串
  206. */
  207. private function buildRequestMysign($para_sort)
  208. {
  209. //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  210. $prestr = $this->createLinkstring($para_sort);
  211. $mysign = '';
  212. switch (strtoupper(trim($this->sign_type))) {
  213. case 'MD5':
  214. $mysign = $this->md5Sign($prestr, $this->key);
  215. break;
  216. default:
  217. $mysign = '';
  218. }
  219. return $mysign;
  220. }
  221. /**
  222. * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  223. * @param $para 需要拼接的数组
  224. * return 拼接完成以后的字符串
  225. */
  226. private function createLinkstring($para)
  227. {
  228. $arg = '';
  229. while ((list ($key, $val) = each($para)) == true) {
  230. $arg .= $key . '=' . $val . '&';
  231. }
  232. //去掉最后一个&字符
  233. $arg = substr($arg, 0, count($arg) - 2);
  234. //如果存在转义字符,那么去掉转义
  235. if (get_magic_quotes_gpc()) {
  236. $arg = stripslashes($arg);
  237. }
  238. return $arg;
  239. }
  240. /**
  241. * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串,并对字符串做urlencode编码
  242. * @param $para 需要拼接的数组
  243. * return 拼接完成以后的字符串
  244. */
  245. private function createLinkstringUrlencode($para)
  246. {
  247. $arg = '';
  248. while ((list ($key, $val) = each($para)) == true) {
  249. $arg .= $key . '=' . urlencode($val) . '&';
  250. }
  251. //去掉最后一个&字符
  252. $arg = substr($arg, 0, count($arg) - 2);
  253. //如果存在转义字符,那么去掉转义
  254. if (get_magic_quotes_gpc()) {
  255. $arg = stripslashes($arg);
  256. }
  257. return $arg;
  258. }
  259. /**
  260. * 签名字符串
  261. * @param $prestr 需要签名的字符串
  262. * @param $key 私钥
  263. * return 签名结果
  264. */
  265. private function md5Sign($prestr, $key)
  266. {
  267. $prestr = $prestr . $key;
  268. return md5($prestr);
  269. }
  270. /**
  271. * 验证签名
  272. * @param $prestr 需要签名的字符串
  273. * @param $sign 签名结果
  274. * @param $key 私钥
  275. * return 签名结果
  276. */
  277. private function md5Verify($prestr, $sign, $key)
  278. {
  279. $prestr = $prestr . $key;
  280. $mysgin = md5($prestr);
  281. if ($mysgin == $sign) {
  282. return true;
  283. } else {
  284. return false;
  285. }
  286. }
  287. /**
  288. * 获取返回时的签名验证结果
  289. * @param $para_temp 通知返回来的参数数组
  290. * @param $sign 返回的签名结果
  291. * @return 签名验证结果
  292. */
  293. private function getSignVeryfy($para_temp, $sign)
  294. {
  295. //除去待签名参数数组中的空值和签名参数
  296. $para_filter = $this->paraFilter($para_temp);
  297. //对待签名参数数组排序
  298. $para_sort = $this->argSort($para_filter);
  299. //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  300. $prestr = $this->createLinkstring($para_sort);
  301. $is_sgin = false;
  302. switch (strtoupper(trim($this->sign_type))) {
  303. case 'MD5':
  304. $is_sgin = $this->md5Verify($prestr, $sign, $this->key);
  305. break;
  306. default:
  307. $is_sgin = false;
  308. }
  309. return $is_sgin;
  310. }
  311. /**
  312. * 获取远程服务器ATN结果,验证返回URL
  313. * @param $notify_id 通知校验ID
  314. * @return 服务器ATN结果
  315. * 验证结果集:
  316. * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
  317. * true 返回正确信息
  318. * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
  319. */
  320. private function getResponse($notify_id)
  321. {
  322. $transport = strtolower(trim($this->transport));
  323. $partner = trim($this->partner);
  324. $veryfy_url = '';
  325. if ($transport == 'https') {
  326. $veryfy_url = $this->__https_verify_url;
  327. } else {
  328. $veryfy_url = $this->__http_verify_url;
  329. }
  330. $veryfy_url = $veryfy_url . 'partner=' . $partner . '&notify_id=' . $notify_id;
  331. $response_txt = $this->getHttpResponseGET($veryfy_url, $this->cacert);
  332. return $response_txt;
  333. }
  334. /**
  335. * 远程获取数据,GET模式
  336. * 注意:
  337. * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
  338. * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
  339. * @param $url 指定URL完整路径地址
  340. * @param $cacert_url 指定当前工作目录绝对路径
  341. * return 远程输出的数据
  342. */
  343. private function getHttpResponseGET($url, $cacert_url)
  344. {
  345. $curl = curl_init($url);
  346. curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头
  347. curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 显示输出结果
  348. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //SSL证书认证
  349. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格认证
  350. curl_setopt($curl, CURLOPT_CAINFO, $cacert_url); //证书地址
  351. $responseText = curl_exec($curl);
  352. //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
  353. curl_close($curl);
  354. return $responseText;
  355. }
  356. }