Alipay.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. namespace Lib\Payment;
  3. /**
  4. * 支付宝接口类
  5. */
  6. class Alipay{
  7. /**
  8. *支付宝网关地址(新)
  9. */
  10. private $alipay_gateway_new = 'https://mapi.alipay.com/gateway.do?';
  11. /**
  12. * 消息验证地址
  13. *
  14. * @var string
  15. */
  16. private $alipay_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
  17. /**
  18. * 支付接口标识
  19. *
  20. * @var string
  21. */
  22. private $code = 'alipay';
  23. /**
  24. * 支付接口配置信息
  25. *
  26. * @var array
  27. */
  28. private $payment;
  29. /**
  30. * 订单信息
  31. *
  32. * @var array
  33. */
  34. private $order;
  35. /**
  36. * 发送至支付宝的参数
  37. *
  38. * @var array
  39. */
  40. private $parameter;
  41. /**
  42. * 订单类型 product_buy商品购买,predeposit预存款充值
  43. * @var unknown
  44. */
  45. private $order_type;
  46. public function __construct($payment_info=array(),$order_info=array()){
  47. // if (!extension_loaded('openssl')) $this->alipay_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
  48. if(!empty($payment_info) and !empty($order_info)){
  49. $this->payment = $payment_info;
  50. $this->order = $order_info;
  51. }else{
  52. $this->payment = $payment_info;
  53. }
  54. }
  55. /**
  56. * 获取支付接口的请求地址
  57. *
  58. * @return string
  59. */
  60. public function get_payurl(){
  61. $this->parameter = array(
  62. 'service' => $this->payment['alipay_service'], //服务名
  63. 'partner' => $this->payment['alipay_partner'], //合作伙伴ID
  64. 'key' => $this->payment['alipay_key'],
  65. '_input_charset' => strtolower('utf-8'), //网站编码
  66. 'notify_url' => $this->order['notify_url'], //通知URL
  67. 'sign_type' => 'MD5', //签名方式
  68. 'return_url' => $this->order['return_url'], //返回URL
  69. 'subject' => $this->order['subject'], //商品名称
  70. 'out_trade_no' => $this->order['pay_order_no'], //外部交易编号
  71. 'payment_type' => 1, //支付类型
  72. 'logistics_type' => 'EXPRESS', //物流配送方式:POST(平邮)、EMS(EMS)、EXPRESS(其他快递)
  73. 'logistics_payment' => 'BUYER_PAY', //物流费用付款方式:SELLER_PAY(卖家支付)、BUYER_PAY(买家支付)、BUYER_PAY_AFTER_RECEIVE(货到付款)
  74. 'logistics_fee' => '0.00',
  75. 'receive_name' => $this->order['name'],//收货人姓名
  76. 'seller_email' => $this->payment['alipay_account'], //卖家邮箱
  77. 'price' => $this->order['pay_total'],//订单总价
  78. 'quantity' => 1,//商品数量
  79. );
  80. $this->parameter['sign'] = $this->sign($this->parameter);
  81. return $this->create_url();
  82. }
  83. /**
  84. * 通知地址验证
  85. *
  86. * @return bool
  87. */
  88. public function notify_verify() {
  89. $param = $_POST;
  90. $param['key'] = $this->payment['alipay_key'];
  91. $veryfy_url = $this->alipay_verify_url. "partner=" .$this->payment['alipay_partner']. "&notify_id=".$param["notify_id"];
  92. $veryfy_result = $this->getHttpResponse($veryfy_url);
  93. $mysign = $this->sign($param);
  94. if (preg_match("/true$/i",$veryfy_result) && $mysign == $param["sign"]) {
  95. // $this->order_type = $param['extra_common_param'];
  96. return true;
  97. } else {
  98. return false;
  99. }
  100. }
  101. /**
  102. * 返回地址验证
  103. *
  104. * @return bool
  105. */
  106. public function return_verify() {
  107. //生成签名结果
  108. $isSign = $this->getSignVeryfy($_GET, $_GET["sign"]);
  109. //获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息)
  110. $responseTxt = 'true';
  111. if (! empty($_GET["notify_id"])) {
  112. $responseTxt = $this->getResponse($_GET["notify_id"]);
  113. }
  114. if (preg_match("/true$/i",$responseTxt) && $isSign) {
  115. return true;
  116. } else {
  117. return false;
  118. }
  119. }
  120. /**
  121. * 除去数组中的空值和签名参数
  122. * @param $para 签名参数组
  123. * return 去掉空值与签名参数后的新签名参数组
  124. */
  125. function paraFilter($para) {
  126. $para_filter = array();
  127. while (list ($key, $val) = each ($para)) {
  128. if($key == "sign" || $key == "sign_type" || $val == "")continue;
  129. else $para_filter[$key] = $para[$key];
  130. }
  131. return $para_filter;
  132. }
  133. /**
  134. * 对数组排序
  135. * @param $para 排序前的数组
  136. * return 排序后的数组
  137. */
  138. function argSort($para) {
  139. ksort($para);
  140. reset($para);
  141. return $para;
  142. }
  143. /**
  144. * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  145. * @param $para 需要拼接的数组
  146. * return 拼接完成以后的字符串
  147. */
  148. function createLinkstring($para) {
  149. $arg = "";
  150. while (list ($key, $val) = each ($para)) {
  151. $arg.=$key."=".$val."&";
  152. }
  153. //去掉最后一个&字符
  154. $arg = substr($arg,0,count($arg)-2);
  155. //如果存在转义字符,那么去掉转义
  156. if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
  157. return $arg;
  158. }
  159. /**
  160. * 签名字符串
  161. * @param $prestr 需要签名的字符串
  162. * @param $key 私钥
  163. * return 签名结果
  164. */
  165. function md5Sign($prestr, $key) {
  166. $prestr = $prestr . $key;
  167. return md5($prestr);
  168. }
  169. /**
  170. * 验证签名
  171. * @param $prestr 需要签名的字符串
  172. * @param $sign 签名结果
  173. * @param $key 私钥
  174. * return 签名结果
  175. */
  176. function md5Verify($prestr, $sign, $key) {
  177. $prestr = $prestr . $key;
  178. $mysgin = md5($prestr);
  179. if($mysgin == $sign) {
  180. return true;
  181. }
  182. else {
  183. return false;
  184. }
  185. }
  186. function getHttpResponseGET($url,$cacert_url) {
  187. $curl = curl_init($url);
  188. curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
  189. curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
  190. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
  191. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
  192. curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
  193. $responseText = curl_exec($curl);
  194. curl_close($curl);
  195. return $responseText;
  196. }
  197. /**
  198. * 获取远程服务器ATN结果,验证返回URL
  199. * @param $notify_id 通知校验ID
  200. * @return 服务器ATN结果
  201. * 验证结果集:
  202. * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
  203. * true 返回正确信息
  204. * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
  205. */
  206. function getResponse($notify_id) {
  207. $transport = strtolower(trim('http'));
  208. $partner = trim($this->payment['alipay_partner']);
  209. $veryfy_url = '';
  210. if($transport == 'https') {
  211. $veryfy_url = $this->https_verify_url;
  212. }
  213. else {
  214. $veryfy_url = $this->alipay_verify_url;
  215. }
  216. $veryfy_url = $this->alipay_verify_url."partner=" . $partner . "&notify_id=" . $notify_id;
  217. $cacert=ROOT_PATH.'Public/pay/cacert.pem';
  218. $responseTxt = $this->getHttpResponseGET($veryfy_url, $cacert);
  219. return $responseTxt;
  220. }
  221. function getSignVeryfy($para_temp, $sign) {
  222. //除去待签名参数数组中的空值和签名参数
  223. $para_filter = $this->paraFilter($para_temp);
  224. //对待签名参数数组排序
  225. $para_sort = $this->argSort($para_filter);
  226. //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  227. $prestr = $this->createLinkstring($para_sort);
  228. $isSgin = false;
  229. switch (strtoupper(trim(strtoupper('MD5')))) {
  230. case "MD5" :
  231. $isSgin = $this->md5Verify($prestr, $sign, $this->payment['alipay_key']);
  232. break;
  233. default :
  234. $isSgin = false;
  235. }
  236. return $isSgin;
  237. }
  238. /**
  239. * 针对notify_url验证消息是否是支付宝发出的合法消息
  240. * @return 验证结果
  241. */
  242. function verifyNotify(){
  243. if(empty($_POST)) {//判断POST来的数组是否为空
  244. return false;
  245. }
  246. else {
  247. //生成签名结果
  248. $isSign = $this->getSignVeryfy($_POST, $_POST["sign"]);
  249. //获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息)
  250. $responseTxt = 'false';
  251. if (! empty($_POST["notify_id"])) {$responseTxt = $this->getResponse($_POST["notify_id"]);}
  252. //验证
  253. //$responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
  254. //isSign的结果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关
  255. if (preg_match("/true$/i",$responseTxt) && $isSign) {
  256. return true;
  257. } else {
  258. return false;
  259. }
  260. }
  261. }
  262. /**
  263. *
  264. * 取得订单支付状态,成功或失败
  265. * @param array $param
  266. * @return array
  267. */
  268. public function getPayResult($param){
  269. return $param['trade_status'] == 'TRADE_SUCCESS';
  270. }
  271. /**
  272. *
  273. *
  274. * @param string $name
  275. * @return
  276. */
  277. public function __get($name){
  278. return $this->$name;
  279. }
  280. /**
  281. * 远程获取数据
  282. * $url 指定URL完整路径地址
  283. * @param $time_out 超时时间。默认值:60
  284. * return 远程输出的数据
  285. */
  286. private function getHttpResponse($url,$time_out = "60") {
  287. $urlarr = parse_url($url);
  288. $errno = "";
  289. $errstr = "";
  290. $transports = "http";
  291. $responseText = "";
  292. if($urlarr["scheme"] == "https") {
  293. $transports = "ssl://";
  294. $urlarr["port"] = "443";
  295. } else {
  296. $transports = "tcp://";
  297. $urlarr["port"] = "80";
  298. }
  299. $fp=@fsockopen($transports . $urlarr['host'],$urlarr['port'],$errno,$errstr,$time_out);
  300. if(!$fp) {
  301. die("ERROR: $errno - $errstr<br />\n");
  302. } else {
  303. if (trim(CHARSET) == '') {
  304. fputs($fp, "POST ".$urlarr["path"]." HTTP/1.1\r\n");
  305. } else {
  306. fputs($fp, "POST ".$urlarr["path"].'?_input_charset='.CHARSET." HTTP/1.1\r\n");
  307. }
  308. fputs($fp, "Host: ".$urlarr["host"]."\r\n");
  309. fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
  310. fputs($fp, "Content-length: ".strlen($urlarr["query"])."\r\n");
  311. fputs($fp, "Connection: close\r\n\r\n");
  312. fputs($fp, $urlarr["query"] . "\r\n\r\n");
  313. while(!feof($fp)) {
  314. $responseText .= @fgets($fp, 1024);
  315. }
  316. fclose($fp);
  317. $responseText = trim(stristr($responseText,"\r\n\r\n"),"\r\n");
  318. return $responseText;
  319. }
  320. }
  321. /**
  322. * 制作支付接口的请求地址
  323. *
  324. * @return string
  325. */
  326. private function create_url() {
  327. $url = $this->alipay_gateway_new;
  328. $filtered_array = $this->para_filter($this->parameter);
  329. $sort_array = $this->arg_sort($filtered_array);
  330. $arg = "";
  331. while (list ($key, $val) = each ($sort_array)) {
  332. $arg.=$key."=".urlencode($val)."&";
  333. }
  334. $url.= $arg."sign=" .$this->parameter['sign'] ."&sign_type=".$this->parameter['sign_type'];
  335. return $url;
  336. }
  337. /**
  338. * 取得支付宝签名
  339. *
  340. * @return string
  341. */
  342. private function sign($parameter) {
  343. $mysign = "";
  344. $filtered_array = $this->para_filter($parameter);
  345. $sort_array = $this->arg_sort($filtered_array);
  346. $arg = "";
  347. while (list ($key, $val) = each ($sort_array)) {
  348. $arg .= $key."=".$this->charset_encode($val,(empty($parameter['_input_charset'])?"UTF-8":$parameter['_input_charset']),(empty($parameter['_input_charset'])?"UTF-8":$parameter['_input_charset']))."&";
  349. }
  350. $prestr = substr($arg,0,-1); //去掉最后一个&号
  351. $prestr .= $parameter['key'];
  352. if($parameter['sign_type'] == 'MD5') {
  353. $mysign = md5($prestr);
  354. }elseif($parameter['sign_type'] =='DSA') {
  355. //DSA 签名方法待后续开发
  356. die("DSA 签名方法待后续开发,请先使用MD5签名方式");
  357. }else {
  358. die("支付宝暂不支持".$parameter['sign_type']."类型的签名方式");
  359. }
  360. return $mysign;
  361. }
  362. /**
  363. * 除去数组中的空值和签名模式
  364. *
  365. * @param array $parameter
  366. * @return array
  367. */
  368. private function para_filter($parameter) {
  369. $para = array();
  370. while (list ($key, $val) = each ($parameter)) {
  371. if($key == "sign" || $key == "sign_type" || $key == "key" || $val == "")continue;
  372. else $para[$key] = $parameter[$key];
  373. }
  374. return $para;
  375. }
  376. /**
  377. * 重新排序参数数组
  378. *
  379. * @param array $array
  380. * @return array
  381. */
  382. private function arg_sort($array) {
  383. ksort($array);
  384. reset($array);
  385. return $array;
  386. }
  387. /**
  388. * 实现多种字符编码方式
  389. */
  390. private function charset_encode($input,$_output_charset,$_input_charset="UTF-8") {
  391. $output = "";
  392. if(!isset($_output_charset))$_output_charset = $this->parameter['_input_charset'];
  393. if($_input_charset == $_output_charset || $input == null) {
  394. $output = $input;
  395. } elseif (function_exists("mb_convert_encoding")){
  396. $output = mb_convert_encoding($input,$_output_charset,$_input_charset);
  397. } elseif(function_exists("iconv")) {
  398. $output = iconv($_input_charset,$_output_charset,$input);
  399. } else die("sorry, you have no libs support for charset change.");
  400. return $output;
  401. }
  402. }