Weixin.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. namespace Ycpay;
  3. class Weixin implements PayInterface
  4. {
  5. private $orderParam;
  6. private $orderH5Param;
  7. private $appid;
  8. private $secret;
  9. private $mch_id;
  10. private $mch_key;
  11. private $notify_url;
  12. private $trade_type;
  13. private $key_pem;
  14. private $cert_pem;
  15. private $openid;
  16. private $codedUrl = 'https://api.weixin.qq.com/sns/jscode2session?';
  17. private $appCodeUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?";
  18. private $tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=';
  19. protected $payUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //支付
  20. protected $query = 'https://api.mch.weixin.qq.com/pay/orderquery'; //查询
  21. protected $refundUrl = 'https://api.mch.weixin.qq.com/secapi/pay/refund'; //退款
  22. protected $sendMsgUrl = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=';
  23. protected $payOrder;
  24. public static function init($config)
  25. {
  26. if (empty($config['appid'])) {
  27. throw new \Exception('not empty appid');
  28. }
  29. if (empty($config['secret'])) {
  30. throw new \Exception('not empty secret');
  31. }
  32. $class = new self();
  33. $class->secret = $config['secret'];
  34. $class->appid = $config['appid'];
  35. $class->mch_id = isset($config['mch_id']) ? $config['mch_id'] : "";
  36. $class->mch_key = isset($config['mch_key']) ? $config['mch_key'] : "";
  37. $class->notify_url = isset($config['notify_url']) ? $config['notify_url'] : "";
  38. $class->trade_type = isset($config['trade_type']) ? $config['trade_type'] : "JSAPI";
  39. $class->key_pem = isset($config['key_pem']) ? $config['key_pem'] : "";
  40. $class->cert_pem = isset($config['cert_pem']) ? $config['cert_pem'] : "";
  41. return $class;
  42. }
  43. public function create_nonce_str($length = 32)
  44. {
  45. $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
  46. $str = "";
  47. for ($i = 0; $i < $length; $i++) {
  48. $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
  49. }
  50. return $str;
  51. }
  52. public function getParam()
  53. {
  54. return $this->orderParam;
  55. }
  56. public function orderH5Param()
  57. {
  58. return $this->orderH5Param;
  59. }
  60. public function getNotifyOrder()
  61. {
  62. $xml = file_get_contents("php://input");
  63. return $this->xmlToArray($xml);
  64. }
  65. /**
  66. * 设置订单号 金额 描述
  67. * @param string $rder_no 平台订单号
  68. * @param int $money 订单金额
  69. * @param string $title 描述
  70. * @param string $openid 描述
  71. *
  72. */
  73. public function set($out_trade_no, $total_fee, $title, $openid = '')
  74. {
  75. $order['appid'] = $this->appid;
  76. $order['mch_id'] = $this->mch_id;
  77. $order['trade_type'] = $this->trade_type;
  78. $order['nonce_str'] = $this->create_nonce_str();
  79. $order['body'] = $title;
  80. $order['out_trade_no'] = $out_trade_no;
  81. $order['total_fee'] = $total_fee;
  82. $order['notify_url'] = $this->notify_url;
  83. if ($this->trade_type == "JSAPI") {
  84. $order['openid'] = $openid; //设置用户openid
  85. }
  86. $order['spbill_create_ip'] = $_SERVER['REMOTE_ADDR'];
  87. $order['sign'] = $this->sign($order);
  88. $data = $this->arrayToXml($order);
  89. $xml_data = $this->curl_post($this->payUrl, $data);
  90. $prepay_id = $this->xmlToArray($xml_data);
  91. if ($this->trade_type == "MWEB") {
  92. $this->orderH5Param = $prepay_id;
  93. } else {
  94. $this->orderParam = array(
  95. 'appId' => $this->appid, //小程序ID
  96. 'timeStamp' => '' . time() . '', //时间戳
  97. 'nonceStr' => $this->create_nonce_str(), //随机串
  98. 'package' => 'prepay_id=' . $prepay_id['prepay_id'], //数据包
  99. 'signType' => 'MD5', //签名方式
  100. );
  101. $this->orderParam['paySign'] = $this->sign($this->orderParam);
  102. }
  103. return $this;
  104. }
  105. public function get_client_ip()
  106. {
  107. $cip = "unknown";
  108. if ($_SERVER['REMOTE_ADDR']) {
  109. $cip = $_SERVER['REMOTE_ADDR'];
  110. } else if (getenv("REMOTE_ADDR")) {
  111. $cip = getenv("REMOTE_ADDR");
  112. }
  113. return $cip;
  114. }
  115. /**
  116. * 作用:将xml转为array
  117. */
  118. public function xmlToArray($xml)
  119. {
  120. //将XML转为array
  121. $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
  122. return $array_data;
  123. }
  124. /**
  125. * @param array $map
  126. * @return string
  127. */
  128. public function sign(array $map)
  129. {
  130. foreach ($map as $k => $v) {
  131. $Parameters[$k] = $v;
  132. }
  133. //签名步骤一:按字典序排序参数
  134. ksort($Parameters);
  135. $String = $this->formatBizQueryParaMap($Parameters, false);
  136. //签名步骤二:在string后加入KEY
  137. $String = $String . "&key=" . $this->mch_key;
  138. //签名步骤三:MD5加密
  139. $String = md5($String);
  140. //签名步骤四:所有字符转为大写
  141. $result_ = strtoupper($String);
  142. return $result_;
  143. }
  144. /**
  145. * 作用:格式化参数,签名过程需要使用
  146. */
  147. public function formatBizQueryParaMap($paraMap, $urlencode)
  148. {
  149. $buff = "";
  150. ksort($paraMap);
  151. foreach ($paraMap as $k => $v) {
  152. if ($urlencode) {
  153. $v = urlencode($v);
  154. }
  155. //$buff .= strtolower($k) . "=" . $v . "&";
  156. $buff .= $k . "=" . $v . "&";
  157. }
  158. $reqPar = '';
  159. if (strlen($buff) > 0) {
  160. $reqPar = substr($buff, 0, strlen($buff) - 1);
  161. }
  162. return $reqPar;
  163. }
  164. /**
  165. * 作用:array转xml
  166. */
  167. public function arrayToXml($arr)
  168. {
  169. $xml = "<xml>";
  170. foreach ($arr as $key => $val) {
  171. if (is_numeric($val)) {
  172. $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
  173. } else {
  174. $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
  175. }
  176. }
  177. $xml .= "</xml>";
  178. return $xml;
  179. }
  180. /**
  181. * 获取token
  182. */
  183. public function getToken()
  184. {
  185. $url = $this->tokenUrl . $this->appid . "&secret=" . $this->secret;
  186. return json_decode($this->curl_get($url), true);
  187. }
  188. /**
  189. * 获取openid
  190. * @param string $code
  191. * @return array 成功返回数组 失败为空
  192. */
  193. public function getOpenid($code)
  194. {
  195. $url = $this->codedUrl . "appid=" . $this->appid . "&secret=" . $this->secret . "&js_code=" . $code . "&grant_type=authorization_code";
  196. return json_decode($this->curl_get($url), true);
  197. }
  198. /**
  199. * 获取微信app openid
  200. *
  201. * @param [type] $code
  202. * @return void
  203. */
  204. public function getAppOpenid($code)
  205. {
  206. $url = $this->appCodeUrl . "appid=" . $this->appid . "&secret=" . $this->secret . "&code=" . $code . "&grant_type=authorization_code";
  207. return json_decode($this->curl_get($url), true);
  208. }
  209. /**
  210. * 发送模版消息
  211. *
  212. * @param [type] $data
  213. * @param [type] $token
  214. * @return void
  215. */
  216. public function sendMsg($data, $token)
  217. {
  218. return json_decode($this->curl_post($this->sendMsgUrl . $token, json_encode($data)), true);
  219. }
  220. /**
  221. * 异步回调
  222. * @param array $order 回调数据
  223. * @return bool true 验签通过|false 验签不通过
  224. */
  225. public function notifyCheck()
  226. {
  227. $order = $this->getNotifyOrder();
  228. $uorder = $order;
  229. unset($uorder['sign']);
  230. $sign = $this->sign($uorder); //本地签名
  231. if ($order['sign'] == $sign) {
  232. if (isset($order['return_code']) && $order['return_code'] == "SUCCESS" && $order['result_code'] && $order['result_code'] = "SUCCESS") {
  233. return true;
  234. } else {
  235. return false;
  236. }
  237. }
  238. return false;
  239. }
  240. /**
  241. * 申请退款
  242. * @param array $order
  243. * @param string $out_trade_no 平台订单号
  244. * @param int $total_fee 订单金额
  245. * @param string $out_refund_no 自定义退款单号
  246. * @parma int @refund_fee 退款金额
  247. */
  248. public function applyOrderRefund($order)
  249. {
  250. $order['appid'] = $this->appid;
  251. $order['mch_id'] = $this->mch_id;
  252. $order['nonce_str'] = $this->create_nonce_str();
  253. $order['sign'] = $this->sign($order);
  254. $xml_data = $this->arrayToXml($order);
  255. $data = $this->curl_post_ssl($this->refundUrl, $xml_data);
  256. return $this->xmlToArray($data);
  257. }
  258. /**
  259. * 订单查询
  260. * @param string $out_trade_no 订单号
  261. * @return array 订单信息
  262. */
  263. public function findOrder($out_trade_no)
  264. {
  265. $order['out_trade_no'] = $out_trade_no;
  266. $order['appid'] = $this->appid;
  267. $order['mch_id'] = $this->mch_id;
  268. $order['nonce_str'] = $this->create_nonce_str();
  269. $order['sign'] = $this->sign($order);
  270. $xml_data = $this->arrayToXml($order);
  271. $data = $this->curl_post_ssl($this->query, $xml_data);
  272. return $this->xmlToArray($data);
  273. }
  274. protected static function curl_get($url)
  275. {
  276. $headerArr = array("Content-type:application/x-www-form-urlencoded");
  277. $ch = curl_init();
  278. curl_setopt($ch, CURLOPT_URL, $url);
  279. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  280. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  281. curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);
  282. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  283. $output = curl_exec($ch);
  284. if (!$output) {
  285. throw new \Exception(curl_error($ch));
  286. }
  287. curl_close($ch);
  288. return $output;
  289. }
  290. /**
  291. * @desc post 用于退款
  292. */
  293. protected static function curl_post($url, $data)
  294. {
  295. $ch = curl_init();
  296. curl_setopt($ch, CURLOPT_URL, $url);
  297. if (!empty($data)) {
  298. curl_setopt($ch, CURLOPT_POST, 1);
  299. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  300. }
  301. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  302. curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/xml'));
  303. $output = curl_exec($ch);
  304. if (!$output) {
  305. throw new \Exception(curl_error($ch));
  306. }
  307. curl_close($ch);
  308. return $output;
  309. }
  310. public function curl_post_ssl($url, $vars, $second = 30, $aHeader = array())
  311. {
  312. $ch = curl_init();
  313. //超时时间
  314. curl_setopt($ch, CURLOPT_TIMEOUT, $second);
  315. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  316. curl_setopt($ch, CURLOPT_URL, $url);
  317. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  318. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  319. curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
  320. curl_setopt($ch, CURLOPT_SSLCERT, $this->cert_pem);
  321. curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
  322. curl_setopt($ch, CURLOPT_SSLKEY, $this->key_pem);
  323. if (count($aHeader) >= 1) {
  324. curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
  325. }
  326. curl_setopt($ch, CURLOPT_POST, 1);
  327. curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
  328. $data = curl_exec($ch);
  329. if ($data) {
  330. curl_close($ch);
  331. return $data;
  332. } else {
  333. $error = curl_errno($ch);
  334. curl_close($ch);
  335. return false;
  336. }
  337. }
  338. /**
  339. * 解密手机号
  340. *
  341. * @param string $session_key 前端传递的session_key
  342. * @param string $iv 前端传递的iv
  343. * @param string $encryptedData 前端传递的encryptedData
  344. */
  345. public function decryptPhone($session_key, $iv, $encryptedData)
  346. {
  347. if (strlen($session_key) != 24) {
  348. return false;
  349. }
  350. $aesKey = base64_decode($session_key);
  351. if (strlen($iv) != 24) {
  352. return false;
  353. }
  354. $aesIV = base64_decode($iv);
  355. $aesCipher = base64_decode($encryptedData);
  356. $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
  357. $dataObj = json_decode($result);
  358. if ($dataObj == null) {
  359. return false;
  360. }
  361. if ($dataObj->watermark->appid != $this->appid) {
  362. return false;
  363. }
  364. return json_decode($result, true);
  365. }
  366. }