WechatService.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <?php
  2. namespace ln\services;
  3. use app\common\repositories\store\product\ProductAssistSetRepository;
  4. use app\common\repositories\store\product\ProductGroupBuyingRepository;
  5. use app\common\repositories\store\product\ProductGroupRepository;
  6. use app\common\repositories\store\product\ProductPresellRepository;
  7. use app\common\repositories\store\product\ProductRepository;
  8. use app\common\repositories\system\config\ConfigValueRepository;
  9. use app\common\repositories\system\merchant\MerchantRepository;
  10. use app\common\repositories\wechat\WechatQrcodeRepository;
  11. use app\common\repositories\wechat\WechatReplyRepository;
  12. use app\common\repositories\wechat\WechatUserRepository;
  13. use ln\exceptions\WechatException;
  14. use EasyWeChat\Core\Exceptions\FaultException;
  15. use EasyWeChat\Core\Exceptions\InvalidArgumentException;
  16. use EasyWeChat\Core\Exceptions\RuntimeException;
  17. use EasyWeChat\Foundation\Application;
  18. use EasyWeChat\Message\Article;
  19. use EasyWeChat\Message\Image;
  20. use EasyWeChat\Message\Material;
  21. use EasyWeChat\Message\News;
  22. use EasyWeChat\Message\Text;
  23. use EasyWeChat\Message\Video;
  24. use EasyWeChat\Message\Voice;
  25. use EasyWeChat\Payment\Order;
  26. use EasyWeChat\Server\BadRequestException;
  27. use EasyWeChat\Support\Collection;
  28. use Exception;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use think\exception\ValidateException;
  31. use think\facade\Cache;
  32. use think\facade\Log;
  33. use think\facade\Route;
  34. use think\Response;
  35. /**
  36. * Class WechatService
  37. * @package ln\services
  38. * @author xaboy
  39. * @day 2020-04-20
  40. */
  41. class WechatService
  42. {
  43. /**
  44. * @var Application
  45. */
  46. protected $application;
  47. protected $config;
  48. /**
  49. * WechatService constructor.
  50. * @param $config
  51. */
  52. public function __construct(array $config)
  53. {
  54. $this->config = $config;
  55. $this->application = new Application($config);
  56. $this->application->register(new \ln\services\easywechat\certficates\ServiceProvider());
  57. $this->application->register(new \ln\services\easywechat\merchant\ServiceProvider);
  58. $this->application->register(new \ln\services\easywechat\combinePay\ServiceProvider);
  59. }
  60. /**
  61. * @return array
  62. * @author xaboy
  63. * @day 2020-04-24
  64. */
  65. public static function getConfig($isApp)
  66. {
  67. /** @var ConfigValueRepository $make */
  68. $make = app()->make(ConfigValueRepository::class);
  69. $wechat = $make->more([
  70. 'wechat_appid', 'wechat_appsecret', 'wechat_token', 'wechat_encodingaeskey', 'wechat_encode', 'wecaht_app_appid', 'wechat_app_appsecret'
  71. ], 0);
  72. if ($isApp ?? request()->isApp()) {
  73. $wechat['wechat_appid'] = trim($wechat['wecaht_app_appid']);
  74. $wechat['wechat_appsecret'] = trim($wechat['wechat_app_appsecret']);
  75. }
  76. $payment = $make->more(['site_url', 'pay_weixin_mchid', 'pay_weixin_client_cert', 'pay_weixin_client_key', 'pay_weixin_key', 'pay_weixin_open',
  77. 'wechat_service_merid', 'wechat_service_key', 'wechat_service_v3key', 'wechat_service_client_cert', 'wechat_service_client_key', 'wechat_service_serial_no'], 0);
  78. $config = [
  79. 'app_id' => trim($wechat['wechat_appid']),
  80. 'secret' => trim($wechat['wechat_appsecret']),
  81. 'token' => trim($wechat['wechat_token']),
  82. 'guzzle' => [
  83. 'timeout' => 10.0, // 超时时间(秒)
  84. 'verify' => false
  85. ],
  86. 'debug' => false,
  87. ];
  88. if ($wechat['wechat_encode'] > 0 && $wechat['wechat_encodingaeskey'])
  89. $config['aes_key'] = trim($wechat['wechat_encodingaeskey']);
  90. if (isset($payment['pay_weixin_open']) && $payment['pay_weixin_open'] == 1) {
  91. $config['payment'] = [
  92. 'merchant_id' => trim($payment['pay_weixin_mchid']),
  93. 'key' => trim($payment['pay_weixin_key']),
  94. 'cert_path' => (app()->getRootPath() . 'public' . $payment['pay_weixin_client_cert']),
  95. 'key_path' => (app()->getRootPath() . 'public' . $payment['pay_weixin_client_key']),
  96. 'notify_url' => $payment['site_url'] . Route::buildUrl('wechatNotify')->build(),
  97. 'pay_weixin_client_cert' => $payment['pay_weixin_client_cert'],
  98. 'pay_weixin_client_key' => $payment['pay_weixin_client_key'],
  99. ];
  100. }
  101. $config['service_payment'] = [
  102. 'merchant_id' => trim($payment['wechat_service_merid']),
  103. 'type' => 'wechat',
  104. 'key' => trim($payment['wechat_service_key']),
  105. 'cert_path' => (app()->getRootPath() . 'public' . $payment['wechat_service_client_cert']),
  106. 'key_path' => (app()->getRootPath() . 'public' . $payment['wechat_service_client_key']),
  107. 'pay_weixin_client_cert' => $payment['wechat_service_client_cert'],
  108. 'pay_weixin_client_key' => $payment['wechat_service_client_key'],
  109. 'serial_no' => trim($payment['wechat_service_serial_no']),
  110. 'apiv3_key' => trim($payment['wechat_service_v3key']),
  111. ];
  112. return $config;
  113. }
  114. /**
  115. * @return self
  116. * @author xaboy
  117. * @day 2020-04-24
  118. */
  119. public static function create($isApp = null)
  120. {
  121. return new self(self::getConfig($isApp));
  122. }
  123. /**
  124. * @return Application
  125. * @author xaboy
  126. * @day 2020-04-20
  127. */
  128. public function getApplication()
  129. {
  130. return $this->application;
  131. }
  132. /**
  133. * @param \think\Request $request
  134. * @return Response
  135. * @throws BadRequestException
  136. * @throws InvalidArgumentException
  137. * @author xaboy
  138. * @day 2020-04-26
  139. */
  140. public function serve(\think\Request $request)
  141. {
  142. $this->serverRequest($request);
  143. $this->wechatEventHook();
  144. return response($this->application->server->serve()->getContent());
  145. }
  146. protected function serverRequest(\think\Request $request)
  147. {
  148. $this->application->server->setRequest(new Request($request->get(), $request->post(), [], [], [], $request->server(), $request->getContent()));
  149. }
  150. /**
  151. * @throws InvalidArgumentException
  152. * @author xaboy
  153. * @day 2020-04-20
  154. */
  155. public function wechatEventHook()
  156. {
  157. $this->application->server->setMessageHandler(function ($message) {
  158. $openId = $message->FromUserName;
  159. $message->EventKey = str_replace('qrscene_','',$message->EventKey);
  160. $userInfo = $this->getUserInfo($openId);
  161. /** @var WechatUserRepository $wechatUserRepository */
  162. $wechatUserRepository = app()->make(WechatUserRepository::class);
  163. $users = $wechatUserRepository->syncUser($openId, $userInfo, true);
  164. $scanLogin = function () use ($message, $users) {
  165. $ticket = $message->EventKey;
  166. if (strpos($ticket, '_sys_scan_login.') === 0) {
  167. $key = str_replace('_sys_scan_login.', '', $ticket);
  168. Cache::set('_scan_login' . $key, $users[1]['uid']);
  169. }
  170. };
  171. $response = null;
  172. /** @var WechatReplyRepository $make * */
  173. $make = app()->make(WechatReplyRepository::class);
  174. event('WechatMessage', $message);
  175. switch ($message->MsgType) {
  176. case 'event':
  177. switch (strtolower($message->Event)) {
  178. case 'subscribe':
  179. $scanLogin();
  180. $response = $this->qrKeyByMessage($message->EventKey) ?: $make->reply('subscribe');
  181. if (isset($message->EventKey) && $message->EventKey) {
  182. /** @var WechatQrcodeRepository $qr */
  183. $qr = app()->make(WechatQrcodeRepository::class);
  184. if ($qrInfo = $qr->ticketByQrcode($message->Ticket)) {
  185. $qrInfo->incTicket();
  186. if (strtolower($qrInfo['third_type']) == 'spread' && $users) {
  187. $spreadUid = $qrInfo['third_id'];
  188. if ($users[1]['uid'] == $spreadUid)
  189. return '自己不能推荐自己';
  190. else if ($users[1]['spread_uid'])
  191. return '已有推荐人!';
  192. try {
  193. $users[1]->setSpread($spreadUid);
  194. } catch (Exception $e) {
  195. return '绑定推荐人失败';
  196. }
  197. }
  198. }
  199. }
  200. event('WechatEventSubscribe', $message);
  201. break;
  202. case 'unsubscribe':
  203. $wechatUserRepository->unsubscribe($openId);
  204. event('WechatEventUnsubscribe', $message);
  205. break;
  206. case 'scan':
  207. $scanLogin();
  208. $response = $this->qrKeyByMessage($message->EventKey) ?: $make->reply('subscribe');
  209. /** @var WechatQrcodeRepository $qr */
  210. $qr = app()->make(WechatQrcodeRepository::class);
  211. if ($message->EventKey && ($qrInfo = $qr->ticketByQrcode($message->Ticket))) {
  212. $qrInfo->incTicket();
  213. if (strtolower($qrInfo['third_type']) == 'spread' && $users) {
  214. $spreadUid = $qrInfo['third_id'];
  215. if ($users[1]['uid'] == $spreadUid)
  216. return '自己不能推荐自己';
  217. else if ($users[1]['spread_uid'])
  218. return '已有推荐人!';
  219. try {
  220. $users[1]->setSpread($spreadUid);
  221. } catch (Exception $e) {
  222. return '绑定推荐人失败';
  223. }
  224. }
  225. }
  226. event('WechatEventScan', $message);
  227. break;
  228. case 'location':
  229. event('wechatEventLocation', $message);
  230. break;
  231. case 'click':
  232. $response = $make->reply($message->EventKey);
  233. break;
  234. case 'view':
  235. event('wechatEventView', $message);
  236. break;
  237. }
  238. break;
  239. case 'text':
  240. $response = $make->reply($message->Content);
  241. event('wechatMessageText', $message);
  242. break;
  243. case 'image':
  244. event('wechatMessageImage', $message);
  245. break;
  246. case 'voice':
  247. event('wechatMessageVoice', $message);
  248. break;
  249. case 'video':
  250. event('wechatMessageVideo', $message);
  251. break;
  252. case 'location':
  253. event('wechatMessageLocation', $message);
  254. break;
  255. case 'link':
  256. event('wechatMessageLink', $message);
  257. break;
  258. // ... 其它消息
  259. default:
  260. event('WechatMessageOther', $message);
  261. break;
  262. }
  263. return $response ?? false;
  264. });
  265. }
  266. /**
  267. * @param $value
  268. * @return Collection
  269. * @author xaboy
  270. * @day 2020-04-20
  271. */
  272. public function qrcodeForever($value)
  273. {
  274. return $this->application->qrcode->forever($value);
  275. }
  276. /**
  277. * @param $value
  278. * @param int $expireSeconds
  279. * @return Collection
  280. * @author xaboy
  281. * @day 2020-04-20
  282. */
  283. public function qrcodeTemp($value, $expireSeconds = 2592000)
  284. {
  285. return $this->application->qrcode->temporary($value, $expireSeconds);
  286. }
  287. /**
  288. * @param string $openid
  289. * @param string $templateId
  290. * @param array $data
  291. * @param null $url
  292. * @param null $defaultColor
  293. * @return mixed
  294. * @author xaboy
  295. * @day 2020-04-20
  296. */
  297. public function sendTemplate(string $openid, string $templateId, array $data, $url = null, $defaultColor = null)
  298. {
  299. $notice = $this->application->notice->to($openid)->template($templateId)->andData($data);
  300. if ($url !== null) $notice->url($url);
  301. if ($defaultColor !== null) $notice->defaultColor($defaultColor);
  302. return $notice->send();
  303. }
  304. /**
  305. * @param $openid
  306. * @param $out_trade_no
  307. * @param $total_fee
  308. * @param $attach
  309. * @param $body
  310. * @param string $detail
  311. * @param string $trade_type
  312. * @param array $options
  313. * @return Order
  314. * @author xaboy
  315. * @day 2020-04-20
  316. */
  317. protected function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  318. {
  319. $total_fee = bcmul($total_fee, 100, 0);
  320. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'), $options);
  321. if (!is_null($openid)) $order['openid'] = $openid;
  322. if ($order['detail'] == '') unset($order['detail']);
  323. $order['spbill_create_ip'] = \request()->ip();
  324. return new Order($order);
  325. }
  326. /**
  327. * @param $openid
  328. * @param $out_trade_no
  329. * @param $total_fee
  330. * @param $attach
  331. * @param $body
  332. * @param string $detail
  333. * @param string $trade_type
  334. * @param array $options
  335. * @return Collection
  336. * @author xaboy
  337. * @day 2020-04-20
  338. */
  339. public function paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  340. {
  341. $order = $this->paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  342. $result = $this->application->payment->prepare($order);
  343. if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
  344. return $result;
  345. } else {
  346. if ($result->return_code == 'FAIL') {
  347. throw new WechatException('微信支付错误返回:' . $result->return_msg);
  348. } else if (isset($result->err_code)) {
  349. throw new WechatException('微信支付错误返回:' . $result->err_code_des);
  350. } else {
  351. throw new WechatException('没有获取微信支付的预支付ID,请重新发起支付!');
  352. }
  353. }
  354. }
  355. /**
  356. * @param $openid
  357. * @param $out_trade_no
  358. * @param $total_fee
  359. * @param $attach
  360. * @param $body
  361. * @param string $detail
  362. * @param string $trade_type
  363. * @param array $options
  364. * @return array|string
  365. * @author xaboy
  366. * @day 2020-04-20
  367. */
  368. public function jsPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  369. {
  370. $paymentPrepare = $this->paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  371. return $trade_type === 'APP'
  372. ? $this->application->payment->configForAppPayment($paymentPrepare->prepay_id)
  373. : $this->application->payment->configForJSSDKPayment($paymentPrepare->prepay_id);
  374. }
  375. /**
  376. * @param $orderNo
  377. * @param $refundNo
  378. * @param $totalFee
  379. * @param null $refundFee
  380. * @param null $opUserId
  381. * @param string $refundReason
  382. * @param string $type
  383. * @param string $refundAccount
  384. * @return Collection
  385. * @author xaboy
  386. * @day 2020-04-20
  387. */
  388. public function refund($orderNo, $refundNo, $totalFee, $refundFee = null, $opUserId = null, $refundReason = '', $type = 'out_trade_no', $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS')
  389. {
  390. if (empty($this->config['payment']['pay_weixin_client_cert']) || empty($this->config['payment']['pay_weixin_client_key'])) {
  391. throw new \Exception('请配置微信支付证书');
  392. }
  393. $totalFee = floatval($totalFee);
  394. $refundFee = floatval($refundFee);
  395. return $this->application->payment->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $type, $refundAccount, $refundReason);
  396. }
  397. /**
  398. * @param $orderNo
  399. * @param array $opt
  400. * @author xaboy
  401. * @day 2020-04-20
  402. */
  403. public function payOrderRefund($orderNo, array $opt)
  404. {
  405. if (!isset($opt['pay_price'])) throw new WechatException('缺少pay_price');
  406. $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
  407. $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
  408. $refundReason = isset($opt['desc']) ? $opt['desc'] : '';
  409. $refundNo = isset($opt['refund_id']) ? $opt['refund_id'] : $orderNo;
  410. $opUserId = isset($opt['op_user_id']) ? $opt['op_user_id'] : null;
  411. $type = isset($opt['type']) ? $opt['type'] : 'out_trade_no';
  412. /*仅针对老资金流商户使用
  413. REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
  414. REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
  415. $refundAccount = isset($opt['refund_account']) ? $opt['refund_account'] : 'REFUND_SOURCE_UNSETTLED_FUNDS';
  416. try {
  417. $res = ($this->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount));
  418. if ($res->return_code == 'FAIL') throw new WechatException('退款失败:' . $res->return_msg);
  419. if (isset($res->err_code)) throw new WechatException('退款失败:' . $res->err_code_des);
  420. } catch (Exception $e) {
  421. throw new WechatException($e->getMessage());
  422. }
  423. }
  424. /**
  425. * array (
  426. * 'appid' => '****',
  427. * 'attach' => 'user_recharge',
  428. * 'bank_type' => 'COMM_CREDIT',
  429. * 'cash_fee' => '1',
  430. * 'fee_type' => 'CNY',
  431. * 'is_subscribe' => 'Y',
  432. * 'mch_id' => ''*****'',
  433. * 'nonce_str' => '5ee9dac1bc302',
  434. * 'openid' => '*****',
  435. * 'out_trade_no' => ''*****'',
  436. * 'result_code' => 'SUCCESS',
  437. * 'return_code' => 'SUCCESS',
  438. * 'sign' => '51'*****'',
  439. * 'time_end' => '20200617165651',
  440. * 'total_fee' => '1',
  441. * 'trade_type' => 'JSAPI',
  442. * 'transaction_id' => ''*****'',
  443. * )
  444. *
  445. * @throws FaultException
  446. * @author xaboy
  447. * @day 2020-04-20
  448. */
  449. public function handleNotify()
  450. {
  451. $this->application->payment = new PaymentService($this->application->merchant);
  452. //TODO 微信支付
  453. return $this->application->payment->handleNotify(function ($notify, $successful) {
  454. Log::info('微信支付成功回调' . var_export($notify, 1));
  455. if (!$successful) return false;
  456. try {
  457. event('pay_success_' . $notify['attach'], ['order_sn' => $notify['out_trade_no'], 'data' => $notify, 'is_combine' => 0]);
  458. } catch (\Exception $e) {
  459. Log::info('微信支付回调失败:' . $e->getMessage());
  460. return false;
  461. }
  462. return true;
  463. });
  464. }
  465. public function handleCombinePayNotify($type)
  466. {
  467. $this->application->combinePay->handleNotify(function ($notify, $successful) use ($type) {
  468. Log::info('微信支付成功回调' . var_export($notify, 1));
  469. if (!$successful) return false;
  470. try {
  471. event('pay_success_' . $type, ['order_sn' => $notify['combine_out_trade_no'], 'data' => $notify, 'is_combine' => 1]);
  472. } catch (\Exception $e) {
  473. Log::info('微信支付回调失败:' . $e->getMessage());
  474. return false;
  475. }
  476. return true;
  477. });
  478. }
  479. /**
  480. * @param string $url
  481. * @return array|string
  482. * @author xaboy
  483. * @day 2020-04-20
  484. */
  485. public function jsSdk($url)
  486. {
  487. $apiList = ['editAddress', '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'];
  488. $jsService = $this->application->js;
  489. $jsService->setUrl($url);
  490. try {
  491. return $jsService->config($apiList, false, false, false);
  492. } catch (Exception $e) {
  493. Log::info('微信参数获取失败' . $e->getMessage());
  494. return [];
  495. }
  496. }
  497. /**
  498. * 回复文本消息
  499. * @param string $content 文本内容
  500. * @return Text
  501. * @author xaboy
  502. * @day 2020-04-20
  503. */
  504. public static function textMessage($content)
  505. {
  506. return new Text(compact('content'));
  507. }
  508. /**
  509. * 回复图片消息
  510. * @param string $media_id 媒体资源 ID
  511. * @return Image
  512. * @author xaboy
  513. * @day 2020-04-20
  514. */
  515. public static function imageMessage($media_id)
  516. {
  517. return new Image(compact('media_id'));
  518. }
  519. /**
  520. * 回复视频消息
  521. * @param string $media_id 媒体资源 ID
  522. * @param string $title 标题
  523. * @param string $description 描述
  524. * @param null $thumb_media_id 封面资源 ID
  525. * @return Video
  526. * @author xaboy
  527. * @day 2020-04-20
  528. */
  529. public static function videoMessage($media_id, $title = '', $description = '...', $thumb_media_id = null)
  530. {
  531. return new Video(compact('media_id', 'title', 'description', 'thumb_media_id'));
  532. }
  533. /**
  534. * 回复声音消息
  535. * @param string $media_id 媒体资源 ID
  536. * @return Voice
  537. * @author xaboy
  538. * @day 2020-04-20
  539. */
  540. public static function voiceMessage($media_id)
  541. {
  542. return new Voice(compact('media_id'));
  543. }
  544. /**
  545. * 回复图文消息
  546. * @param string|array $title 标题
  547. * @param string $description 描述
  548. * @param string $url URL
  549. * @param string $image 图片链接
  550. * @return News|array<News>
  551. * @author xaboy
  552. * @day 2020-04-20
  553. */
  554. public static function newsMessage($title, $description = '...', $url = '', $image = '')
  555. {
  556. if (is_array($title)) {
  557. if (isset($title[0]) && is_array($title[0])) {
  558. $newsList = [];
  559. foreach ($title as $news) {
  560. $newsList[] = self::newsMessage($news);
  561. }
  562. return $newsList;
  563. } else {
  564. $data = $title;
  565. }
  566. } else {
  567. $data = compact('title', 'description', 'url', 'image');
  568. }
  569. return new News($data);
  570. }
  571. /**
  572. * 回复文章消息
  573. * @param string|array $title 标题
  574. * @param string $thumb_media_id 图文消息的封面图片素材id(必须是永久 media_ID)
  575. * @param string $source_url 图文消息的原文地址,即点击“阅读原文”后的URL
  576. * @param string $content 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS
  577. * @param string $author 作者
  578. * @param string $digest 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空
  579. * @param int $show_cover_pic 是否显示封面,0为false,即不显示,1为true,即显示
  580. * @param int $need_open_comment 是否打开评论,0不打开,1打开
  581. * @param int $only_fans_can_comment 是否粉丝才可评论,0所有人可评论,1粉丝才可评论
  582. * @return Article
  583. * @author xaboy
  584. * @day 2020-04-20
  585. */
  586. 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)
  587. {
  588. $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');
  589. return new Article($data);
  590. }
  591. /**
  592. * 回复素材消息
  593. * @param string $type [mpnews、 mpvideo、voice、image]
  594. * @param string $media_id 素材 ID
  595. * @return Material
  596. * @author xaboy
  597. * @day 2020-04-20
  598. */
  599. public static function materialMessage($type, $media_id)
  600. {
  601. return new Material($type, $media_id);
  602. }
  603. /**
  604. * @param $to
  605. * @param $message
  606. * @return bool
  607. * @throws InvalidArgumentException
  608. * @throws RuntimeException
  609. * @author xaboy
  610. * @day 2020-04-20
  611. */
  612. public function staffTo($to, $message)
  613. {
  614. $staff = $this->application->staff;
  615. $staff = is_callable($message) ? $staff->message($message()) : $staff->message($message);
  616. $res = $staff->to($to)->send();
  617. return $res;
  618. }
  619. /**
  620. * @param $openid
  621. * @return array
  622. * @author xaboy
  623. * @day 2020-04-20
  624. */
  625. public function getUserInfo($openid)
  626. {
  627. $userService = $this->application->user;
  628. $userInfo = is_array($openid) ? $userService->batchGet($openid) : $userService->get($openid);
  629. return $userInfo->toArray();
  630. }
  631. /**
  632. * 模板消息接口
  633. * @return \EasyWeChat\Notice\Notice
  634. */
  635. public function noticeService()
  636. {
  637. return $this->application->notice;
  638. }
  639. /**
  640. * 微信二维码生成接口
  641. * @return \EasyWeChat\QRCode\QRCode
  642. */
  643. public function qrcodeService()
  644. {
  645. return $this->application->qrcode;
  646. }
  647. public function merchantPay($data)
  648. {
  649. $ret = [
  650. 'partner_trade_no' => $data['sn'], //随机字符串作为订单号,跟红包和支付一个概念。
  651. 'openid' => $data['openid'], //收款人的openid
  652. 'check_name' => 'NO_CHECK', //文档中有三种校验实名的方法 NO_CHECK OPTION_CHECK FORCE_CHECK
  653. //'re_user_name'=>'张三', //OPTION_CHECK FORCE_CHECK 校验实名的时候必须提交
  654. 'amount' => $data['price'] * 100, //单位为分
  655. 'desc' => '企业付款',
  656. 'spbill_create_ip' => request()->ip(), //发起交易的IP地址
  657. ];
  658. $result = $this->application->merchant_pay->send($ret);
  659. if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
  660. return $result;
  661. } else {
  662. if ($result->return_code == 'FAIL') {
  663. throw new WechatException('微信支付错误返回:' . $result->return_msg);
  664. } else if (isset($result->err_code)) {
  665. throw new WechatException('微信支付错误返回:' . $result->err_code_des);
  666. } else {
  667. throw new WechatException('微信支付错误返回:' . $result->return_msg);
  668. }
  669. }
  670. }
  671. /**
  672. * TODO 分账商户
  673. * @return mixed
  674. * @author Qinii
  675. * @day 6/24/21
  676. */
  677. public function applyments()
  678. {
  679. return $this->application->sub_merchant;
  680. }
  681. /**
  682. * TODO 上传图片
  683. * @param array $filed
  684. * @return mixed
  685. * @author Qinii
  686. * @day 6/21/21
  687. */
  688. public function uploadImages(array $filed)
  689. {
  690. foreach ($filed as $file) {
  691. $item = $this->application->sub_merchant->upload($file['path'], $file['name']);
  692. $data[] = [
  693. 'dir' => $file['dir'],
  694. 'media_id' => $item['media_id'],
  695. ];
  696. }
  697. return $data;
  698. }
  699. public function qrKeyByMessage($key)
  700. {
  701. if (strpos($key, '_scan_url_') === 0) {
  702. $key = str_replace('_scan_url_', '', $key);
  703. $data = explode('_', $key);
  704. $siteUrl = rtrim(systemConfig('site_url'), '/');
  705. $make = app()->make(ProductRepository::class);
  706. if ($data[0] === 'home') {
  707. $share = systemConfig(['share_title', 'share_info', 'share_pic']);
  708. $share['url'] = $siteUrl . '?spid=' . $data[1];
  709. } else if ($data[0] === 'mer') {
  710. $ret = app()->make(MerchantRepository::class)->get($data[1]);
  711. if (!$ret) return;
  712. $share = [
  713. 'share_title' => $ret['mer_name'],
  714. 'share_info' => $ret['mer_info'],
  715. 'share_pic' => $ret['mer_avatar'],
  716. 'url' => $siteUrl . '/pages/store/home/index?id=' . $data[1],
  717. ];
  718. } else if ($data[0] === 'p0') {
  719. $ret = $make->get($data[1]);
  720. if (!$ret) return;
  721. $share = [
  722. 'share_title' => $ret['store_name'],
  723. 'share_info' => $ret['store_info'],
  724. 'share_pic' => $ret['image'],
  725. 'url' => $siteUrl . '/pages/goods_details/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  726. ];
  727. } else if ($data[0] === 'p1') {
  728. $ret = $make->get($data[1]);
  729. if (!$ret) return;
  730. $share = [
  731. 'share_title' => $ret['store_name'],
  732. 'share_info' => $ret['store_info'],
  733. 'share_pic' => $ret['image'],
  734. 'url' => $siteUrl . '/pages/activity/goods_seckill_details/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  735. ];
  736. } else if ($data[0] === 'p2') {
  737. $ret = app()->make(ProductPresellRepository::class)->search(['product_presell_id' => $data[1]])->find();
  738. $res = $make->get($ret['product_id']);
  739. if (!$ret) return;
  740. $share = [
  741. 'share_title' => $ret['store_name'],
  742. 'share_info' => $ret['store_info'],
  743. 'share_pic' => $res['image'],
  744. 'url' => $siteUrl . '/pages/activity/presell_details/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  745. ];
  746. } else if ($data[0] === 'p3') {
  747. $ret = app()->make(ProductAssistSetRepository::class)->getSearch(['product_assist_set_id' => $data[1]])->find();
  748. $res = $make->get($ret['product_id']);
  749. if (!$ret) return;
  750. $share = [
  751. 'share_title' => $res['store_name'],
  752. 'share_info' => $res['store_info'],
  753. 'share_pic' => $res['image'],
  754. 'url' => $siteUrl . '/pages/activity/assist_detail/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  755. ];
  756. } else if ($data[0] === 'p4') {
  757. $ret = app()->make(ProductGroupRepository::class)->get($data[1]);
  758. $res = $make->get($ret['product_id']);
  759. if (!$ret) return;
  760. $share = [
  761. 'share_title' => $res['store_name'],
  762. 'share_info' => $res['store_info'],
  763. 'share_pic' => $res['image'],
  764. 'url' => $siteUrl . '/pages/activity/combination_details/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  765. ];
  766. } else if ($data[0] === 'p40') {
  767. $res = app()->make(ProductGroupBuyingRepository::class)->getSearch(['group_buying_id' => $data[1]])->find();
  768. $ret = $make->get($res->productGroup['product_id']);
  769. if (!$ret) return;
  770. $share = [
  771. 'share_title' => $ret['store_name'],
  772. 'share_info' => $ret['store_info'],
  773. 'share_pic' => $ret['image'],
  774. 'url' => $siteUrl . '/pages/activity/combination_status/index?id=' . $data[1] . '&spid=' . ($data[2] ?? 0),
  775. ];
  776. } else {
  777. return;
  778. }
  779. return self::newsMessage($share['share_title'], $share['share_info'], $share['url'], $share['share_pic']);
  780. }
  781. }
  782. /**
  783. * @return easywechat\combinePay\Client
  784. */
  785. public function combinePay()
  786. {
  787. return $this->application->combinePay;
  788. }
  789. }