WechatService.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services\app;
  12. use app\services\message\wechat\MessageServices;
  13. use app\services\order\StoreOrderServices;
  14. use app\services\pay\PayServices;
  15. use app\services\wechat\WechatMessageServices;
  16. use app\services\wechat\WechatReplyServices;
  17. use crmeb\exceptions\AdminException;
  18. use app\services\pay\PayNotifyServices;
  19. use crmeb\exceptions\ApiException;
  20. use crmeb\services\easywechat\Application;
  21. use EasyWeChat\Message\Article;
  22. use EasyWeChat\Message\Image;
  23. use EasyWeChat\Message\Material;
  24. use EasyWeChat\Message\News;
  25. use EasyWeChat\Message\Text;
  26. use EasyWeChat\Message\Video;
  27. use EasyWeChat\Message\Voice;
  28. use EasyWeChat\Payment\Order;
  29. use EasyWeChat\Server\Guard;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use think\facade\Event;
  32. use think\facade\Log;
  33. use think\Response;
  34. use crmeb\utils\Hook;
  35. use think\facade\Cache;
  36. use crmeb\services\SystemConfigService;
  37. /**
  38. * 微信公众号
  39. * Class WechatService
  40. * @package crmeb\services\app
  41. */
  42. class WechatService
  43. {
  44. /**
  45. * @var Application
  46. */
  47. protected static $instance;
  48. /**
  49. * @return array
  50. */
  51. public static function options()
  52. {
  53. $wechat = SystemConfigService::more(['wechat_appid', 'wechat_app_appid', 'wechat_app_appsecret', 'wechat_appsecret', 'wechat_token', 'wechat_encodingaeskey', 'wechat_encode']);
  54. $payment = SystemConfigService::more(['pay_weixin_mchid', 'pay_weixin_client_cert', 'pay_weixin_client_key', 'pay_weixin_key', 'pay_weixin_open']);
  55. if (request()->isApp()) {
  56. $appId = isset($wechat['wechat_app_appid']) ? trim($wechat['wechat_app_appid']) : '';
  57. $appsecret = isset($wechat['wechat_app_appsecret']) ? trim($wechat['wechat_app_appsecret']) : '';
  58. } else {
  59. $appId = null;
  60. if (request()->isPc()) {
  61. $appId = sys_config('wechat_open_app_id');
  62. }
  63. if (!$appId) {
  64. $appId = isset($wechat['wechat_appid']) ? trim($wechat['wechat_appid']) : '';
  65. }
  66. $appsecret = isset($wechat['wechat_appsecret']) ? trim($wechat['wechat_appsecret']) : '';
  67. }
  68. $config = [
  69. 'app_id' => $appId,
  70. 'secret' => $appsecret,
  71. 'token' => isset($wechat['wechat_token']) ? trim($wechat['wechat_token']) : '',
  72. 'guzzle' => [
  73. 'timeout' => 10.0, // 超时时间(秒)
  74. 'verify' => false
  75. ],
  76. ];
  77. if (isset($wechat['wechat_encode']) && (int)$wechat['wechat_encode'] > 0 && isset($wechat['wechat_encodingaeskey']) && !empty($wechat['wechat_encodingaeskey']))
  78. $config['aes_key'] = $wechat['wechat_encodingaeskey'];
  79. if (isset($payment['pay_weixin_open']) && $payment['pay_weixin_open'] == 'weixin') {
  80. $config['payment'] = [
  81. 'app_id' => $appId,
  82. 'merchant_id' => trim($payment['pay_weixin_mchid']),
  83. 'key' => trim($payment['pay_weixin_key']),
  84. 'cert_path' => substr(public_path(parse_url($payment['pay_weixin_client_cert'])['path']), 0, strlen(public_path(parse_url($payment['pay_weixin_client_cert'])['path'])) - 1),
  85. 'key_path' => substr(public_path(parse_url($payment['pay_weixin_client_key'])['path']), 0, strlen(public_path(parse_url($payment['pay_weixin_client_key'])['path'])) - 1),
  86. 'notify_url' => trim(sys_config('site_url')) . '/api/pay/notify/wechat'
  87. ];
  88. }
  89. return $config;
  90. }
  91. /**
  92. * @param bool $cache
  93. * @return Application
  94. */
  95. public static function application($cache = false)
  96. {
  97. (self::$instance === null || $cache === true) && (self::$instance = new Application(self::options()));
  98. return self::$instance;
  99. }
  100. /**
  101. * @return Response
  102. * @throws \EasyWeChat\Server\BadRequestException
  103. */
  104. public static function serve(): Response
  105. {
  106. $wechat = self::application(true);
  107. $server = $wechat->server;
  108. self::hook($server);
  109. $response = $server->serve();
  110. return response($response->getContent());
  111. }
  112. /**
  113. * 监听行为(微信)
  114. * @param Guard $server
  115. * @throws \EasyWeChat\Core\Exceptions\InvalidArgumentException
  116. */
  117. private static function hook($server)
  118. {
  119. /** @var MessageServices $messageService */
  120. $messageService = app()->make(MessageServices::class);
  121. /** @var WechatReplyServices $wechatReplyService */
  122. $wechatReplyService = app()->make(WechatReplyServices::class);
  123. $server->setMessageHandler(function ($message) use ($messageService, $wechatReplyService) {
  124. /** @var WechatMessageServices $wechatMessage */
  125. $wechatMessage = app()->make(WechatMessageServices::class);
  126. $wechatMessage->wechatMessageBefore($message);
  127. switch ($message->MsgType) {
  128. case 'event':
  129. switch (strtolower($message->Event)) {
  130. case 'subscribe':
  131. $response = $messageService->wechatEventSubscribe($message);
  132. break;
  133. case 'unsubscribe':
  134. $messageService->wechatEventUnsubscribe($message);
  135. break;
  136. case 'scan':
  137. $response = $messageService->wechatEventScan($message);
  138. break;
  139. case 'location':
  140. $response = $messageService->wechatEventLocation($message);
  141. break;
  142. case 'click':
  143. $response = $wechatReplyService->reply($message->EventKey);
  144. break;
  145. case 'view':
  146. $response = $messageService->wechatEventView($message);
  147. break;
  148. case 'funds_order_pay':
  149. if (($count = strpos($message['order_info']['trade_no'], '_')) !== false) {
  150. $trade_no = substr($message['order_info']['trade_no'], $count + 1);
  151. } else {
  152. $trade_no = $message['order_info']['trade_no'];
  153. }
  154. $prefix = substr($trade_no, 0, 2);
  155. //处理一下参数
  156. switch ($prefix) {
  157. case 'cp':
  158. $data['attach'] = 'Product';
  159. break;
  160. case 'hy':
  161. $data['attach'] = 'Member';
  162. break;
  163. case 'cz':
  164. $data['attach'] = 'UserRecharge';
  165. break;
  166. }
  167. $data['out_trade_no'] = $message['order_info']['trade_no'];
  168. $data['transaction_id'] = $message['order_info']['transaction_id'];
  169. $data['opneid'] = $message['FromUserName'];
  170. if (Event::until('pay.notify', [$data, PayServices::WEIXIN_PAY])) {
  171. $response = 'success';
  172. } else {
  173. $response = 'faild';
  174. }
  175. Log::error(['data' => $data, 'res' => $response, 'message' => $message]);
  176. break;
  177. }
  178. break;
  179. case 'text':
  180. $response = $wechatReplyService->reply($message->Content, $message->FromUserName);
  181. break;
  182. case 'image':
  183. $response = $messageService->wechatMessageImage($message);
  184. break;
  185. case 'voice':
  186. $response = $messageService->wechatMessageVoice($message);
  187. break;
  188. case 'video':
  189. $response = $messageService->wechatMessageVideo($message);
  190. break;
  191. case 'location':
  192. $response = $messageService->wechatMessageLocation($message);
  193. break;
  194. case 'link':
  195. $response = $messageService->wechatMessageLink($message);
  196. break;
  197. // ... 其它消息
  198. default:
  199. $response = $messageService->wechatMessageOther($message);
  200. break;
  201. }
  202. return $response ?? false;
  203. });
  204. }
  205. /**
  206. * 多客服消息转发
  207. * @param string $account
  208. * @return \EasyWeChat\Message\Transfer
  209. */
  210. public static function transfer($account = '')
  211. {
  212. $transfer = new \EasyWeChat\Message\Transfer();
  213. return empty($account) ? $transfer : $transfer->to($account);
  214. }
  215. /**
  216. * 上传永久素材接口
  217. * @return \EasyWeChat\Material\Material
  218. */
  219. public static function materialService()
  220. {
  221. return self::application()->material;
  222. }
  223. /**
  224. * 上传临时素材接口
  225. * @return \EasyWeChat\Material\Temporary
  226. */
  227. public static function materialTemporaryService()
  228. {
  229. return self::application()->material_temporary;
  230. }
  231. /**
  232. * 用户接口
  233. * @return \EasyWeChat\User\User
  234. */
  235. public static function userService()
  236. {
  237. return self::application()->user;
  238. }
  239. /**
  240. * 客服消息接口
  241. * @param null $to
  242. * @param null $message
  243. */
  244. public static function staffService()
  245. {
  246. return self::application()->staff;
  247. }
  248. /**
  249. * 微信公众号菜单接口
  250. * @return \EasyWeChat\Menu\Menu
  251. */
  252. public static function menuService()
  253. {
  254. return self::application()->menu;
  255. }
  256. /**
  257. * 微信二维码生成接口
  258. * @return \EasyWeChat\QRCode\QRCode
  259. */
  260. public static function qrcodeService()
  261. {
  262. return self::application()->qrcode;
  263. }
  264. /**
  265. * 短链接生成接口
  266. * @return \EasyWeChat\Url\Url
  267. */
  268. public static function urlService()
  269. {
  270. return self::application()->url;
  271. }
  272. /**
  273. * 用户授权
  274. * @return \Overtrue\Socialite\Providers\WeChatProvider
  275. */
  276. public static function oauthService()
  277. {
  278. return self::application()->oauth;
  279. }
  280. /**
  281. * 网页授权
  282. * @return easywechat\oauth2\wechat\WechatOauth2Provider
  283. */
  284. public static function oauth2Service()
  285. {
  286. $request = app()->request;
  287. self::application()->oauth2->setRequest(new Request($request->get(), $request->post(), [], [], [], $request->server(), $request->getContent()));
  288. return self::application()->oauth2;
  289. }
  290. /**
  291. * 模板消息接口
  292. * @return \EasyWeChat\Notice\Notice
  293. */
  294. public static function noticeService()
  295. {
  296. return self::application()->notice;
  297. }
  298. public static function sendTemplate($openid, $templateId, array $data, $url = null, $defaultColor = null)
  299. {
  300. $notice = self::noticeService()->to($openid)->template($templateId)->andData($data);
  301. if ($url !== null) $notice->url($url);
  302. if ($defaultColor !== null) $notice->defaultColor($defaultColor);
  303. return $notice->send();
  304. }
  305. /**
  306. * 支付
  307. * @return \EasyWeChat\Payment\Payment
  308. */
  309. public static function paymentService()
  310. {
  311. return self::application()->payment;
  312. }
  313. public static function downloadBill($day, $type = 'ALL')
  314. {
  315. // $payment = self::paymentService();
  316. // $merchant = $payment->getMerchant();
  317. // $params = [
  318. // 'appid' => $merchant->app_id,
  319. // 'bill_date'=>$day,
  320. // 'bill_type'=>strtoupper($type),
  321. // 'mch_id'=> $merchant->merchant_id,
  322. // 'nonce_str' => uniqid()
  323. // ];
  324. // $params['sign'] = \EasyWeChat\Payment\generate_sign($params, $merchant->key, 'md5');
  325. // $xml = XML::build($params);
  326. // dump(self::paymentService()->downloadBill($day)->getContents());
  327. // dump($payment->getHttp()->request('https://api.mch.weixin.qq.com/pay/downloadbill','POST',[
  328. // 'body' => $xml,
  329. // 'stream'=>true
  330. // ])->getBody()->getContents());
  331. }
  332. public static function userTagService()
  333. {
  334. return self::application()->user_tag;
  335. }
  336. public static function userGroupService()
  337. {
  338. return self::application()->user_group;
  339. }
  340. /**
  341. * 企业付款到零钱
  342. * @param string $openid openid
  343. * @param string $orderId 订单号
  344. * @param string $amount 金额
  345. * @param string $desc 说明
  346. */
  347. public static function merchantPay(string $openid, string $orderId, string $amount, string $desc)
  348. {
  349. $options = self::options();
  350. if (!isset($options['payment']['cert_path'])) {
  351. throw new ApiException(410088);
  352. }
  353. if (!$options['payment']['cert_path']) {
  354. throw new ApiException(410088);
  355. }
  356. $merchantPayData = [
  357. 'partner_trade_no' => $orderId, //随机字符串作为订单号,跟红包和支付一个概念。
  358. 'openid' => $openid, //收款人的openid
  359. 'check_name' => 'NO_CHECK', //文档中有三种校验实名的方法 NO_CHECK OPTION_CHECK FORCE_CHECK
  360. 'amount' => (int)bcmul($amount, '100', 0), //单位为分
  361. 'desc' => $desc,
  362. 'spbill_create_ip' => request()->ip(), //发起交易的IP地址
  363. ];
  364. $result = self::application()->merchant_pay->send($merchantPayData);
  365. if ($result->return_code == 'SUCCESS' && $result->result_code != 'FAIL') {
  366. return true;
  367. } else {
  368. throw new ApiException(410089);
  369. }
  370. }
  371. /**
  372. * 生成支付订单对象
  373. * @param $openid
  374. * @param $out_trade_no
  375. * @param $total_fee
  376. * @param $attach
  377. * @param $body
  378. * @param string $detail
  379. * @param string $trade_type
  380. * @param array $options
  381. * @return Order
  382. */
  383. protected static function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  384. {
  385. $total_fee = bcmul($total_fee, 100, 0);
  386. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'), $options);
  387. if (!is_null($openid)) $order['openid'] = $openid;
  388. if ($order['detail'] == '') unset($order['detail']);
  389. return new Order($order);
  390. }
  391. /**
  392. * 获得下单ID
  393. * @param $openid
  394. * @param $out_trade_no
  395. * @param $total_fee
  396. * @param $attach
  397. * @param $body
  398. * @param string $detail
  399. * @param string $trade_type
  400. * @param array $options
  401. * @return mixed
  402. */
  403. public static function paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  404. {
  405. $key = 'pay_' . $out_trade_no;
  406. $result = Cache::get($key);
  407. if ($result) {
  408. return $result;
  409. } else {
  410. $order = self::paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  411. $result = self::paymentService()->prepare($order);
  412. if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
  413. Cache::set($key, $result, 7000);
  414. return $result;
  415. } else {
  416. if ($result->return_code == 'FAIL') {
  417. exception('微信支付错误返回:' . $result->return_msg);
  418. } else if (isset($result->err_code)) {
  419. exception('微信支付错误返回:' . $result->err_code_des);
  420. } else {
  421. exception('没有获取微信支付的预支付ID,请重新发起支付!');
  422. }
  423. exit;
  424. }
  425. }
  426. }
  427. /**
  428. * 获得下单ID 新小程序支付
  429. * @param $openid
  430. * @param $out_trade_no
  431. * @param $total_fee
  432. * @param $attach
  433. * @param $body
  434. * @param string $detail
  435. * @param string $trade_type
  436. * @param array $options
  437. * @return mixed
  438. */
  439. public static function newPaymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $options = [])
  440. {
  441. $key = 'pay_' . $out_trade_no;
  442. $result = Cache::get($key);
  443. if ($result) {
  444. return $result;
  445. } else {
  446. $order = self::paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $options);
  447. $result = self::application()->minipay->createorder($order);
  448. if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
  449. Cache::set($key, $result, 7000);
  450. return $result;
  451. } else {
  452. if ($result->return_code == 'FAIL') {
  453. exception('微信支付错误返回:' . $result->return_msg);
  454. } else if (isset($result->err_code)) {
  455. exception('微信支付错误返回:' . $result->err_code_des);
  456. } else {
  457. exception('没有获取微信支付的预支付ID,请重新发起支付!');
  458. }
  459. exit;
  460. }
  461. }
  462. }
  463. /**
  464. * 获得jsSdk支付参数
  465. * @param $openid
  466. * @param $out_trade_no
  467. * @param $total_fee
  468. * @param $attach
  469. * @param $body
  470. * @param string $detail
  471. * @param string $trade_type
  472. * @param array $options
  473. * @return array|string
  474. */
  475. public static function jsPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  476. {
  477. $paymentPrepare = self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  478. return self::paymentService()->configForJSSDKPayment($paymentPrepare->prepay_id);
  479. }
  480. /**
  481. * 获得jsSdk支付参数 新小程序支付
  482. * @param $openid
  483. * @param $out_trade_no
  484. * @param $total_fee
  485. * @param $attach
  486. * @param $body
  487. * @param string $detail
  488. * @param string $trade_type
  489. * @param array $options
  490. * @return array|string
  491. */
  492. public static function newJsPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $options = [])
  493. {
  494. $paymentPrepare = self::newPaymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $options);
  495. return self::paymentService()->configForJSSDKPayment($paymentPrepare->prepay_id);
  496. }
  497. /**
  498. * 获得APP付参数
  499. * @param $openid
  500. * @param $out_trade_no
  501. * @param $total_fee
  502. * @param $attach
  503. * @param $body
  504. * @param string $detail
  505. * @param string $trade_type
  506. * @param array $options
  507. * @return array|string
  508. */
  509. public static function appPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = Order::APP, $options = [])
  510. {
  511. $paymentPrepare = self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  512. return self::paymentService()->configForAppPayment($paymentPrepare->prepay_id);
  513. }
  514. /**
  515. * 获得native支付参数
  516. * @param $openid
  517. * @param $out_trade_no
  518. * @param $total_fee
  519. * @param $attach
  520. * @param $body
  521. * @param string $detail
  522. * @param string $trade_type
  523. * @param array $options
  524. * @return array|string
  525. */
  526. public static function nativePay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'NATIVE', $options = [])
  527. {
  528. $data = self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  529. if ($data) {
  530. $res['code_url'] = $data['code_url'];
  531. $res['invalid'] = time() + 60;
  532. $res['logo'] = sys_config('wap_login_logo');
  533. } else $res = [];
  534. return $res;
  535. }
  536. /**
  537. * 使用商户订单号退款
  538. * @param $orderNo
  539. * @param $refundNo
  540. * @param $totalFee
  541. * @param null $refundFee
  542. * @param null $opUserId
  543. * @param string $refundReason
  544. * @param string $type
  545. * @param string $refundAccount
  546. */
  547. public static function refund($orderNo, $refundNo, $totalFee, $refundFee = null, $opUserId = null, $refundReason = '', $type = 'out_trade_no', $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS')
  548. {
  549. $totalFee = floatval($totalFee);
  550. $refundFee = floatval($refundFee);
  551. if ($type == 'out_trade_no') {
  552. return self::paymentService()->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $type, $refundAccount, $refundReason);
  553. } else {
  554. return self::paymentService()->refundByTransactionId($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundAccount, $refundReason);
  555. }
  556. }
  557. public static function payOrderRefund($orderNo, array $opt)
  558. {
  559. if (!isset($opt['pay_price'])) throw new AdminException(400730);
  560. $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
  561. $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
  562. $refundReason = $opt['desc'] ?? '';
  563. $refundNo = $opt['refund_id'] ?? $orderNo;
  564. $opUserId = $opt['op_user_id'] ?? null;
  565. $type = $opt['type'] ?? 'out_trade_no';
  566. /*仅针对老资金流商户使用
  567. REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
  568. REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
  569. $refundAccount = $opt['refund_account'] ?? 'REFUND_SOURCE_UNSETTLED_FUNDS';
  570. try {
  571. $res = (self::refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount));
  572. if ($res->return_code == 'FAIL') throw new AdminException(400731, ['msg' => $res->return_msg]);
  573. if (isset($res->err_code)) throw new AdminException(400731, ['msg' => $res->err_code_des]);
  574. } catch (\Exception $e) {
  575. throw new AdminException($e->getMessage());
  576. }
  577. return true;
  578. }
  579. /**
  580. * 微信支付成功回调接口
  581. * @return \Symfony\Component\HttpFoundation\Response
  582. * @throws \EasyWeChat\Core\Exceptions\FaultException
  583. */
  584. public static function handleNotify()
  585. {
  586. return self::paymentService()->handleNotify(function ($notify, $successful) {
  587. if ($successful) {
  588. $data = [
  589. 'attach' => $notify->attach,
  590. 'out_trade_no' => $notify->out_trade_no,
  591. 'transaction_id' => $notify->transaction_id
  592. ];
  593. return Event::until('pay.notify', [$data, PayServices::WEIXIN_PAY]);
  594. }
  595. return false;
  596. });
  597. }
  598. /**
  599. * jsSdk
  600. * @return \EasyWeChat\Js\Js
  601. */
  602. public static function jsService()
  603. {
  604. return self::application()->js;
  605. }
  606. /**
  607. * 获取js的SDK
  608. * @param string $url
  609. * @return array|string
  610. */
  611. public static function jsSdk($url = '')
  612. {
  613. $apiList = ['openAddress', 'updateTimelineShareData', 'updateAppMessageShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone', 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 'uploadVoice', 'downloadVoice', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'closeWindow', 'scanQRCode', 'chooseWXPay', 'openProductSpecificView', 'addCard', 'chooseCard', 'openCard'];
  614. $jsService = self::jsService();
  615. if ($url) $jsService->setUrl($url);
  616. try {
  617. return $jsService->config($apiList);
  618. } catch (\Exception $e) {
  619. return '{}';
  620. }
  621. }
  622. /**
  623. * 回复文本消息
  624. * @param string $content 文本内容
  625. * @return Text
  626. */
  627. public static function textMessage($content)
  628. {
  629. return new Text(compact('content'));
  630. }
  631. /**
  632. * 回复图片消息
  633. * @param string $media_id 媒体资源 ID
  634. * @return Image
  635. */
  636. public static function imageMessage($media_id)
  637. {
  638. return new Image(compact('media_id'));
  639. }
  640. /**
  641. * 回复视频消息
  642. * @param string $media_id 媒体资源 ID
  643. * @param string $title 标题
  644. * @param string $description 描述
  645. * @param null $thumb_media_id 封面资源 ID
  646. * @return Video
  647. */
  648. public static function videoMessage($media_id, $title = '', $description = '...', $thumb_media_id = null)
  649. {
  650. return new Video(compact('media_id', 'title', 'description', 'thumb_media_id'));
  651. }
  652. /**
  653. * 回复声音消息
  654. * @param string $media_id 媒体资源 ID
  655. * @return Voice
  656. */
  657. public static function voiceMessage($media_id)
  658. {
  659. return new Voice(compact('media_id'));
  660. }
  661. /**
  662. * 回复图文消息
  663. * @param string|array $title 标题
  664. * @param string $description 描述
  665. * @param string $url URL
  666. * @param string $image 图片链接
  667. */
  668. public static function newsMessage($title, $description = '...', $url = '', $image = '')
  669. {
  670. if (is_array($title)) {
  671. if (isset($title[0]) && is_array($title[0])) {
  672. $newsList = [];
  673. foreach ($title as $news) {
  674. $newsList[] = self::newsMessage($news);
  675. }
  676. return $newsList;
  677. } else {
  678. $data = $title;
  679. }
  680. } else {
  681. $data = compact('title', 'description', 'url', 'image');
  682. }
  683. return new News($data);
  684. }
  685. /**
  686. * 回复文章消息
  687. * @param string|array $title 标题
  688. * @param string $thumb_media_id 图文消息的封面图片素材id(必须是永久 media_ID)
  689. * @param string $source_url 图文消息的原文地址,即点击“阅读原文”后的URL
  690. * @param string $content 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS
  691. * @param string $author 作者
  692. * @param string $digest 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空
  693. * @param int $show_cover_pic 是否显示封面,0为false,即不显示,1为true,即显示
  694. * @param int $need_open_comment 是否打开评论,0不打开,1打开
  695. * @param int $only_fans_can_comment 是否粉丝才可评论,0所有人可评论,1粉丝才可评论
  696. * @return Article
  697. */
  698. public static function articleMessage($title, $thumb_media_id, $source_url, $content = '', $author = '', $digest = '', $show_cover_pic = 0, $need_open_comment = 0, $only_fans_can_comment = 1)
  699. {
  700. $data = is_array($title) ? $title : compact('title', 'thumb_media_id', 'source_url', 'content', 'author', 'digest', 'show_cover_pic', 'need_open_comment', 'only_fans_can_comment');
  701. return new Article($data);
  702. }
  703. /**
  704. * 回复素材消息
  705. * @param string $type [mpnews、 mpvideo、voice、image]
  706. * @param string $media_id 素材 ID
  707. * @return Material
  708. */
  709. public static function materialMessage($type, $media_id)
  710. {
  711. return new Material($type, $media_id);
  712. }
  713. /**
  714. * 作为客服消息发送
  715. * @param $to
  716. * @param $message
  717. * @return bool
  718. */
  719. public static function staffTo($to, $message)
  720. {
  721. $staff = self::staffService();
  722. $staff = is_callable($message) ? $staff->message($message()) : $staff->message($message);
  723. $res = $staff->to($to)->send();
  724. return $res;
  725. }
  726. /**
  727. * 获得用户信息
  728. * @param array|string $openid
  729. * @return \EasyWeChat\Support\Collection
  730. */
  731. public static function getUserInfo($openid)
  732. {
  733. $userService = self::userService();
  734. $userInfo = [];
  735. try {
  736. if (is_array($openid)) {
  737. $res = $userService->batchGet($openid);
  738. if (isset($res['user_info_list'])) {
  739. $userInfo = $res['user_info_list'];
  740. } else {
  741. throw new AdminException(400732);
  742. }
  743. } else {
  744. $userInfo = $userService->get($openid);
  745. }
  746. } catch (\Throwable $e) {
  747. throw new AdminException(self::getMessage($e->getMessage()));
  748. }
  749. return $userInfo;
  750. }
  751. /**
  752. * 获取用户列表
  753. * @param null $next_openid
  754. * @return array
  755. */
  756. public static function getUsersList($next_openid = null)
  757. {
  758. $userService = self::userService();
  759. $list = [];
  760. try {
  761. $res = $userService->lists($next_openid);
  762. $list['data'] = $res['data']['openid'] ?? [];
  763. $list['next_openid'] = $res['next_openid'] ?? null;
  764. return $list;
  765. } catch (\Exception $e) {
  766. throw new AdminException(self::getMessage($e->getMessage()));
  767. }
  768. return $list;
  769. }
  770. /**
  771. * 处理返回错误信息友好提示
  772. * @param string $message
  773. * @return array|mixed|string
  774. */
  775. public static function getMessage(string $message)
  776. {
  777. if (strstr($message, 'Request AccessToken fail') !== false) {
  778. $message = str_replace('Request AccessToken fail. response:', '', $message);
  779. $message = json_decode($message, true) ?: [];
  780. $errcode = $message['errcode'] ?? false;
  781. if ($errcode) {
  782. $message = $errcode;
  783. }
  784. }
  785. return $message;
  786. }
  787. /**
  788. * 设置模版消息行业
  789. */
  790. public static function setIndustry($industryOne, $industryTwo)
  791. {
  792. return self::application()->notice->setIndustry($industryOne, $industryTwo);
  793. }
  794. /**
  795. * 获得添加模版ID
  796. * @param $template_id_short
  797. */
  798. public static function addTemplateId($template_id_short)
  799. {
  800. try {
  801. return self::application()->notice->addTemplate($template_id_short);
  802. } catch (\Exception $e) {
  803. throw new AdminException(self::getMessage($e->getMessage()));
  804. }
  805. }
  806. /**
  807. * 获取模板列表
  808. * @return \EasyWeChat\Support\Collection
  809. */
  810. public static function getPrivateTemplates()
  811. {
  812. try {
  813. return self::application()->notice->getPrivateTemplates();
  814. } catch (\Exception $e) {
  815. throw new AdminException(self::getMessage($e->getMessage()));
  816. }
  817. }
  818. /*
  819. * 根据模版ID删除模版
  820. */
  821. public static function deleleTemplate($template_id)
  822. {
  823. try {
  824. return self::application()->notice->deletePrivateTemplate($template_id);
  825. } catch (\Exception $e) {
  826. throw new AdminException(self::getMessage($e->getMessage()));
  827. }
  828. }
  829. }