WechatService.php 30 KB

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