WeixinServer.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <?php
  2. namespace App\Servers;
  3. use App\Models\ErrorRecord;
  4. use App\Servers\weixin\Unifiedorder;
  5. class WeixinServer
  6. {
  7. static private $server = null;
  8. public $mch_id = '';
  9. public $private_key = '';
  10. public $cert_file = '';
  11. public $key_file = '';
  12. public $appid = '';
  13. public $appsecret = '';
  14. public $appletId = '';
  15. public $appletKey = '';
  16. function __construct()
  17. {
  18. // $this->mch_id=env('WX_MCH_ID','');
  19. // $this->private_key=env('WX_PRIVATE_KEY','');
  20. // $this->appid=env('WX_APPID','');
  21. // $this->appsecret=env('WX_APPSECRET','');
  22. // $this->cert_file=env('WX_CERT_FILE','');
  23. // $this->key_file=env('WX_KEY_FILE','');
  24. // $this->appletId=env('APPLET_ID','');
  25. // $this->appletKey=env('APPLET_KEY','');
  26. $this->mch_id=config('weixin.mch_id','');
  27. $this->private_key=config('weixin.private_key','');
  28. $this->appid=config('weixin.app_id','');
  29. $this->appsecret=config('weixin.app_secret','');
  30. $this->cert_file=config('weixin.cert_file','');
  31. $this->key_file=config('weixin.key_file','');
  32. $this->appletId=config('weixin.applet_id','');
  33. $this->appletKey=config('weixin.applet_key','');
  34. }
  35. /**
  36. * 创建对象
  37. * @return WeixinServer
  38. */
  39. static function creatServer()
  40. {
  41. if (empty(self::$server)) self::$server = new WeixinServer();
  42. return self::$server;
  43. }
  44. /**
  45. * 小程序code获取openid等信息
  46. * @param $code
  47. * @param $invite_code
  48. * @return array|false
  49. */
  50. function getAppletsOpenId($code){
  51. $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$this->appletId}&secret={$this->appletKey}&js_code={$code}&grant_type=authorization_code";
  52. $result = $this->sendRequest($url);
  53. $result = json_decode($result, true);
  54. if ( !isset($result['openid'])) {//成功返回信息
  55. return ['code'=>0,'msg'=>$result['errmsg']];
  56. } else {
  57. return $result;
  58. }
  59. }
  60. /**
  61. * 获取小程序码
  62. * @param $page //页面路径
  63. * @param $scene //携带参数
  64. */
  65. function getAppletCode($page,$scene){
  66. $access_token = $this->getAccessToken(2);
  67. $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$access_token}";
  68. $data['page'] = $page;
  69. $data['scene'] = $scene;
  70. $data['check_path'] = false;
  71. $data['width'] = 300;
  72. // $data['env_version'] = 'trial';//要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
  73. $result = $this->sendRequest($url,'post',$data);
  74. return $result;
  75. }
  76. /**
  77. * 获取 access_token
  78. * @return bool
  79. */
  80. protected function getAccessToken($type = 1)
  81. {
  82. if($type == 1){
  83. $key = 'vouchername';
  84. $appId = $this->appid;
  85. $appsecret = $this->appsecret;
  86. }else{
  87. $key = 'applet_voucher';
  88. $appId = $this->appletId;
  89. $appsecret = $this->appletKey;
  90. }
  91. $data = RedisDataServer::creatServer()->getData($key, 'json');
  92. if (empty($data) || $data['endtime'] < time()) {
  93. $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appsecret}";
  94. $result = $this->sendRequest($url);
  95. $result = json_decode($result, true);
  96. // $bey = Cache::set('vouchername', array('access_token' => $result['access_token'], 'endtime' => time() + 7000), 7000);
  97. if ( isset($result['access_token'])) {
  98. RedisDataServer::creatServer()->setData($key, array('access_token' => $result['access_token'], 'endtime' => time() + 1800), 'json',1800);
  99. return $result['access_token'];
  100. } else {
  101. return false;
  102. }
  103. } else {
  104. return $data['access_token'];
  105. }
  106. }
  107. /**
  108. * 获取js授权信息
  109. * @return mixed
  110. */
  111. function getJsConfig($url = '')
  112. {
  113. $hash = '';
  114. $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz';
  115. $max = strlen($chars) - 1;
  116. for ($i = 0; $i < 16; $i++) {
  117. $hash .= $chars[mt_rand(0, $max)];
  118. }
  119. $data['noncestr'] = $hash;
  120. $data['jsapi_ticket'] = $this->getJsTicket();
  121. $data['timestamp'] = time();
  122. if (empty($url)) {
  123. $data['url'] = $this->getServerUrl();
  124. } else {
  125. $data['url'] = $url;
  126. }
  127. ksort($data);
  128. $str = '';
  129. foreach ($data as $key => $val) {
  130. $str .= '&';
  131. $str .= $key . '=' . $val;
  132. }
  133. $str = mb_substr($str, 1);
  134. $data['appId'] = $this->appid;
  135. $data['signature'] = sha1($str);
  136. unset($data['jsapi_ticket']);
  137. return $data;
  138. }
  139. /**
  140. * 获取当前连接
  141. * @return string
  142. */
  143. function getServerUrl()
  144. {
  145. // $http_host=request()->header('host');
  146. // $data=request()->all();
  147. return urlencode('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
  148. // return 'http://'.$http_host. '?' . http_build_query($data);;
  149. }
  150. /**
  151. * 获取Js调用凭证
  152. * @return bool
  153. */
  154. function getJsTicket()
  155. {
  156. // $data = Cache::get('js_ticket');
  157. $data = RedisDataServer::creatServer()->getData('js_ticket', 'json');
  158. if (empty($data) || $data['endtime'] < time()) {
  159. $access_token = $this->getAccessToken();
  160. $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$access_token}&type=jsapi";
  161. $result = $this->sendRequest($url);
  162. $result = json_decode($result, true);
  163. if ($result['errcode'] == 0) {
  164. //file_put_contents($this->js_ticket,serialize(array('access_token'=>$result['access_token'],'endtime'=>time()+7000)));
  165. // Cache::set('js_ticket', array('access_token' => $result['ticket'], 'endtime' => time() + 7000), 7000);
  166. RedisDataServer::creatServer()->setData('js_ticket', array('access_token' => $result['ticket'], 'endtime' => time() + 7000), 'json',7000);
  167. return $result['ticket'];
  168. }
  169. return false;
  170. } else {
  171. return $data['access_token'];
  172. }
  173. }
  174. /**
  175. * 文本内容安全识别
  176. * @param $openid
  177. * @param $content
  178. * @return bool
  179. */
  180. function msgSecCheck($openid,$content)
  181. {
  182. $access_token = $this->getAccessToken(2);
  183. $url = 'https://api.weixin.qq.com/wxa/msg_sec_check?access_token='.$access_token;
  184. $conf = array(
  185. 'content' => $content,
  186. 'version' => 2,
  187. 'openid' => $openid,
  188. 'scene' => 2,
  189. );
  190. // var_dump($conf);
  191. $result = $this->http_post_request($url,json_encode($conf, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
  192. // $result = json_decode($result, true);
  193. //var_dump($result);
  194. if ($result['errcode'] != 0) {
  195. return false;
  196. }elseif ($result['result']['label'] != 100) {
  197. return false;
  198. } else {
  199. return true;
  200. }
  201. }
  202. /**
  203. * 图片和视频内容安全识别
  204. * @param $openid
  205. * @param $media_url
  206. * @param $media_type
  207. * @return false|mixed
  208. */
  209. function mediaCheckAsync($openid,$media_url,$media_type=1)
  210. {
  211. $access_token = $this->getAccessToken(2);
  212. // var_dump($access_token);
  213. $url = 'https://api.weixin.qq.com/wxa/media_check_async?access_token='.$access_token;
  214. $conf = array(
  215. 'media_url' => $media_url,
  216. 'media_type' => $media_type,
  217. 'version' => 2,
  218. 'openid' => $openid,
  219. 'scene' => 1,
  220. );
  221. $result = $this->http_post_request($url,json_encode($conf, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
  222. // var_dump($result);
  223. if ($result['errcode'] != 0) {
  224. return false;
  225. }else{
  226. return $result['trace_id'];
  227. }
  228. }
  229. /**
  230. * 微信签名
  231. */
  232. function wxSign($data, $keys)
  233. {
  234. ksort($data);
  235. $str = '';
  236. foreach ($data as $key => $val) {
  237. if ($val) {
  238. if ($str != '') $str .= '&';
  239. $str .= "{$key}={$val}";
  240. }
  241. }
  242. $str .= '&key=' . $keys;
  243. $sign = strtoupper(MD5($str));
  244. return $sign;
  245. }
  246. /**
  247. * @param $url
  248. * @param string $type 请求方式
  249. * @param string $data 数据 数组格式
  250. * @return mixed
  251. */
  252. protected function sendRequest($url, $type = 'get', $data = '')
  253. {
  254. $ch = curl_init();
  255. if ($type == 'get' && $data) {
  256. $url = $url . '?' . http_build_query($data);
  257. }
  258. curl_setopt($ch, CURLOPT_URL, $url); //设置访问路径
  259. curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); //设置可以返回字符串
  260. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  261. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  262. $head = array('User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36');
  263. curl_setopt($ch, CURLOPT_HTTPHEADER, $head);
  264. if ($type == 'post') {
  265. curl_setopt($ch, CURLOPT_POST, TRUE);//post请求
  266. $data = json_encode($data, JSON_UNESCAPED_UNICODE);
  267. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//设置传递的参数
  268. }
  269. $request = curl_exec($ch);
  270. curl_close($ch);
  271. return $request;
  272. }
  273. public function http_post_request($url, $jsonStr)
  274. {
  275. $ch = curl_init();
  276. curl_setopt($ch, CURLOPT_POST, 1);// 发送一个常规的Post请求
  277. curl_setopt($ch, CURLOPT_URL, $url);// 要访问的地址
  278. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检测
  279. curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循
  280. curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr);
  281. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  282. curl_setopt($ch, CURLOPT_HTTPHEADER, array(
  283. 'Content-Type: application/json;charset=UTF-8',
  284. 'Content-Length: ' . strlen($jsonStr)
  285. )
  286. );
  287. $response = curl_exec($ch);
  288. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  289. $http_error = curl_error($ch);
  290. curl_close($ch);
  291. $response = json_decode($response, 1);
  292. //var_dump($response);
  293. return $response;
  294. }
  295. /**
  296. * JS支付签名
  297. * @param $order_sn
  298. * @param $total_fee
  299. * @param $notify_url
  300. * @param $body
  301. * @param $wx_openid
  302. * @return bool
  303. */
  304. public function wxJsPay($order_sn, $total_fee, $notify_url, $body, $wx_openid = '')
  305. {
  306. // $is_test=env('TEST_SERVE',false);
  307. $is_test=config('weixin.test_serve',false);
  308. if($is_test) {
  309. // PayServer::creatServer()->NotifyPay($order_sn, '147258369', $total_fee, ['data'=>'测试']);//暂时测试直接走回调
  310. $total_fee = 0.01;
  311. }
  312. $conf = array(
  313. 'appid' => $this->appletId,
  314. 'mch_id' => $this->mch_id,
  315. 'notify_url' => $notify_url,
  316. 'openid' => $wx_openid,
  317. 'out_trade_no' => $order_sn,
  318. 'total_fee' => $total_fee * 100,
  319. 'trade_type' => 'JSAPI',
  320. 'body' => $body,
  321. );
  322. // var_dump($conf);
  323. $order = new Unifiedorder($conf);
  324. $request = $order->getOrder($this->private_key);
  325. // var_dump($request);
  326. if ($request['code'] == 1) {
  327. $datas["appId"] = $request['data']['appid'];
  328. $datas["nonceStr"] = $order->getNonceStr();
  329. $datas["package"] = "prepay_id={$request['data']['prepay_id']}";
  330. $datas["signType"] = 'MD5';
  331. $datas["timeStamp"] = time();
  332. $s = $order->wxSign($datas, $this->private_key);
  333. $datas["paySign"] = $s;
  334. return $datas;
  335. } else {
  336. return false;
  337. }
  338. }
  339. /**
  340. * 微信验签
  341. * @param $data
  342. * @return string
  343. */
  344. function verifyCer($data)
  345. {
  346. if (isset($data['sign'])) unset($data['sign']);
  347. $conf = array(
  348. 'appid' => $this->appletId,
  349. 'mch_id' => $this->mch_id,
  350. );
  351. $order = new Unifiedorder($conf);
  352. $sign = $order->wxSign($data, $this->private_key);
  353. return $sign;
  354. }
  355. /**
  356. * 微信退款
  357. * @param $total_fee
  358. * @param $out_trade_no
  359. * @param string $body
  360. * @param int $order_money
  361. * @return array
  362. */
  363. function wxPayRefund($out_trade_no, $total_fee, $body = '',$order_money=0)
  364. {
  365. // $is_test=env('TEST_SERVE',false);
  366. $is_test=config('weixin.test_serve',false);
  367. // dd($is_test);
  368. if($is_test){
  369. $total_fee = 0.01;
  370. $order_money = 0.01;
  371. }
  372. $conf = array(
  373. 'appid' => $this->appletId,
  374. 'mch_id' => $this->mch_id,
  375. 'out_trade_no' => $out_trade_no,
  376. 'out_refund_no' => 'T'.$out_trade_no.time(),
  377. 'total_fee' => $order_money * 100,
  378. 'refund_fee' => $total_fee * 100,
  379. 'refund_desc' => $body
  380. );
  381. $order = new Unifiedorder($conf, ['cert_file' => base_path($this->cert_file), 'key_file' => base_path($this->key_file)]);
  382. $request = $order->wxPayRefund($this->private_key);
  383. // var_dump($request);
  384. if ($request['code'] == 1) {
  385. return ['code' => 1, 'msg' => '退款成功', 'data' => ['ret_sn' => $request['data']['refund_id']]];
  386. } else {
  387. ErrorRecord::create(['m_id' =>0, 'msg' => '微信退款失败', 'data' => json_encode($request)]);
  388. return ['code' => 0, 'msg' => '微信退款失败', 'data' => ['ret_sn' => '']];
  389. }
  390. }
  391. /**
  392. * 微信付款到零钱
  393. * @param $order_sn
  394. * @param $total_fee
  395. * @param $openid
  396. * @param $body
  397. * @return array
  398. */
  399. function setVerified($order_sn, $total_fee, $openid, $body)
  400. {
  401. $cert_file=base_path($this->cert_file);
  402. $key_file=base_path($this->key_file);
  403. $private_key=$this->private_key;
  404. $conf = array(
  405. 'mch_appid' => $this->appletId,
  406. 'mchid' => $this->mch_id,
  407. 'openid' => $openid,
  408. 'partner_trade_no' => $order_sn,
  409. 'amount' => $total_fee * 100,
  410. 'check_name' => 'NO_CHECK',
  411. 'desc' => $body,
  412. );
  413. $order = new Unifiedorder($conf, ['cert_file' => $cert_file, 'key_file' => $key_file]);
  414. $request = $order->setVerified($private_key);
  415. ErrorRecord::create(['m_id' =>0, 'msg' => '微信提现记录', 'data' => json_encode($request)]);
  416. return $request;
  417. }
  418. }