OpenPlat.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <?php
  2. namespace App\Servers\Wechat;
  3. use EasyWeChat\Factory;
  4. use App\Models\OpenPlat\Component;
  5. use Illuminate\Support\Facades\Http;
  6. use EasyWeChat\Kernel\AccessToken;
  7. use Ixudra\Curl\Facades\Curl;
  8. use App\Facades\Servers\Logs\Log;
  9. /**
  10. * 微信第三方平台
  11. *
  12. */
  13. class OpenPlat
  14. {
  15. // 应用实例
  16. private \EasyWeChat\OpenPlatform\Application $app;
  17. // 返回结果
  18. public function __construct()
  19. {
  20. // 应用实例
  21. $this->app = $this->getApp();
  22. }
  23. /**
  24. * 获取应用实例
  25. *
  26. * @return \EasyWeChat\OpenPlatform\Application
  27. *
  28. */
  29. public function getApp()
  30. {
  31. // 返回应用实例
  32. $app = Factory::openPlatform(config('wechat.openplat', []));
  33. $appEnv = config('app.env');
  34. // 如果非正式服
  35. if ($appEnv != 'production') {
  36. // 获取线上的代码
  37. $result = Curl::to(config('wechat.openplat.release_host_url','').'openplat/install/get_component_access_token')->asJsonResponse(true)->get();
  38. // 设置
  39. if( !empty($result['data']['component_access_token']) ) $app->access_token->setToken($result['data']['component_access_token'],3600);
  40. }
  41. // 返回结果
  42. return $app;
  43. }
  44. /**
  45. * 获取授权账号刷新令牌
  46. *
  47. * @param string $appId appid
  48. *
  49. * @return string
  50. *
  51. */
  52. public function getRefreshToken($appId)
  53. {
  54. // 获取授权账号刷新令牌
  55. $refreshToken = (new Component())->getOne($appId,'refresh_token');
  56. // $refreshToken = (new Component())->where([['authorizer_appid','=',$appId]])->value('authorizer_refresh_token');
  57. // 返回令牌
  58. return (string) $refreshToken;
  59. }
  60. /**
  61. * 获取授权公众号实例
  62. *
  63. * @param string $appId appid
  64. *
  65. * @return \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application
  66. *
  67. */
  68. public function getOfficial(string $appId)
  69. {
  70. $refreshToken = $this->getRefreshToken($appId);
  71. // 返回应用实例
  72. $official = $this->app->officialAccount($appId, $refreshToken);
  73. $appEnv = config('app.env');
  74. if ($appEnv != 'production') {
  75. $response = Http::post(config('wechat.openplat.release_host_url','').'openplat/install/get_official_access_token',[
  76. 'app_id' => $appId,
  77. ]);
  78. $body = json_decode($response->body(),true);
  79. $data = $body['data'];
  80. $official->access_token->setToken($data['authorizer_access_token'],7200);
  81. }
  82. // 返回应用实例
  83. return $official;
  84. }
  85. /**
  86. * 获取授权小程序实例
  87. *
  88. * @param string $appId appid
  89. *
  90. * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application
  91. *
  92. */
  93. public function getMini(string $appId)
  94. {
  95. $appEnv = config('app.env');
  96. $refreshToken = $this->getRefreshToken($appId);
  97. // 返回应用实例
  98. $mini = $this->app->miniProgram($appId, $refreshToken);
  99. if ($appEnv != 'production') {
  100. $response = Http::post(config('wechat.openplat.release_host_url','').'openplat/install/get_access_token',[
  101. 'app_id' => $appId,
  102. ]);
  103. $body = json_decode($response->body(),true);
  104. $data = $body['data'];
  105. $setResult = $mini->access_token->setToken($data['authorizer_access_token'],7200);
  106. }
  107. // 返回应用实例
  108. return $mini;
  109. }
  110. /**
  111. * 手机号授权
  112. * @param string $code 授权码
  113. *
  114. */
  115. public function getUserPhone($code,$appId){
  116. $Mini = $this->getMini($appId);
  117. // 获取手机号
  118. $result = $Mini->phone_number->getUserPhoneNumber($code);
  119. // 判断结果
  120. if( !empty($result['errcode']) ) return ['error'=>$result['errcode'].'=>'.$result['errmsg']];
  121. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  122. return $result['phone_info'];
  123. }
  124. /**
  125. * 获取用户openid
  126. * @param string $code 授权码
  127. *
  128. */
  129. public function jscode2session($code,$appId){
  130. $token = $this->app->access_token->getToken();
  131. $params = [
  132. 'appid' => $appId,
  133. 'component_access_token'=>$token['component_access_token'],
  134. 'grant_type' => 'authorization_code',
  135. 'component_appid' => config('wechat.openplat.app_id',''),
  136. 'js_code' => $code,
  137. ];
  138. $params = http_build_query($params);
  139. $result = Curl::to('https://api.weixin.qq.com/sns/component/jscode2session?'.$params)->asJsonResponse(true)->get();
  140. // 判断结果
  141. if( !empty($result['errcode']) ) return ['error'=>$result['errcode'].'=>'.$result['errmsg']];
  142. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  143. return $result;
  144. }
  145. /**
  146. * 提交小程序体验版
  147. * @param string $appId app_id
  148. *
  149. */
  150. public function commit($appId,$templateId, $extJson, $version, $description){
  151. $Mini = $this->getMini($appId);
  152. // 获取手机号
  153. $result = $Mini->code->commit($templateId, $extJson, $version, $description);
  154. // 判断结果
  155. if( !empty($result['errcode']) ) return ['error'=>$result['errcode'].'=>'.$result['errmsg']];
  156. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  157. return $result;
  158. }
  159. /**
  160. * 生产小程序体验版二维码
  161. * @param string $code 授权码
  162. *
  163. */
  164. public function getTrialQRCode($appId,$fileName,$path=null){
  165. $Mini = $this->getMini($appId);
  166. // 获取手机号
  167. $result = $Mini->code->getQrCode($path);
  168. file_put_contents($fileName, $result);
  169. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  170. return $result;
  171. }
  172. /**
  173. * 获取user 公众号
  174. * @param string $code 通过 前端授权code获取用户openid
  175. *
  176. *
  177. */
  178. public function userFromCode($code,$appId){
  179. $Mini = $this->getOfficial($appId);
  180. // 获取手机号
  181. $result = $Mini->oauth->userFromCode($code);
  182. // 判断结果
  183. if( !empty($result['errcode']) ) return ['error'=>$result['errcode'].'=>'.$result['errmsg']];
  184. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  185. return $result;
  186. }
  187. /**
  188. * 配置小程序服务器域名
  189. * @param array $params 配置数据
  190. * @param string $appId app_id
  191. */
  192. public function modify($appId,$params){
  193. $Mini = $this->getMini($appId);
  194. // 配置小程序服务器域名
  195. $result = $Mini->domain->modify($params);
  196. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  197. return $result;
  198. }
  199. /**
  200. * 获取UrlLink
  201. * @param string $path 通过 URL Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query 。path 为空时会跳转小程序主页
  202. * @param string $query 通过 URL Link 进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~%
  203. *
  204. */
  205. public function getUrlLink($appId,$path,$query=''){
  206. $Mini = $this->getMini($appId);
  207. // 组合参数
  208. $params['path'] = $path;
  209. $params['query'] = $query;
  210. $params['expire_type'] = 1; // 默认值0.小程序 URL Link 失效类型,失效时间:0,失效间隔天数:1
  211. $params['expire_interval'] = 30; // 到期失效的URL Link的失效间隔天数
  212. $params['env_version'] = config('app.env','production') == 'production' ? 'release' : 'trial';
  213. // 获取结果
  214. $result = $Mini->url_link->generate($params);
  215. // 成功返回
  216. if( $result['errcode'] == 0 ) return $result['url_link'];
  217. // 失败
  218. return '';
  219. }
  220. /**
  221. * 获取小程序 scheme 链接
  222. * @author 唐远望
  223. * @version 1.0
  224. * @date 2025-08-28
  225. *
  226. */
  227. public function getScheme($appId,$path, $query = '')
  228. {
  229. $Mini = $this->getMini($appId);
  230. $token = $Mini->access_token->getToken();
  231. $params = [
  232. 'jump_wxa' => [
  233. 'path' => $path,
  234. 'query' => $query,
  235. ],
  236. 'expire_time' => time() + 86400 * 30 //最长有效期为 30 天
  237. ];
  238. // 直接发送 JSON 数据
  239. $result = Curl::to('https://api.weixin.qq.com/wxa/generatescheme?access_token=' . $token['authorizer_access_token'])
  240. ->withContentType('application/json') // 设置 Content-Type
  241. ->withData(json_encode($params)) // 将数组转换为 JSON 字符串
  242. ->asJsonResponse(true)
  243. ->post();
  244. // 判断结果
  245. if (!empty($result['errcode'])) {
  246. //写入日志
  247. Log::error('getScheme','获取小程序 scheme 码失败',['errcode' => $result['errcode'], 'errmsg' => $result['errmsg']]);
  248. return '';
  249. }
  250. // 返回结果
  251. return $result['openlink'];
  252. }
  253. /**
  254. * 获取小程序码
  255. *
  256. * @param string $scene 小程序码的场景值
  257. * @param array $optional 其他参数
  258. * page 小程序码的页面路径
  259. * width 小程序码的宽度
  260. *
  261. */
  262. public function getUnlimit($appId,string $scene, array $optional = []){
  263. $Mini = $this->getMini($appId);
  264. // 小程序版本,体验还是正式版
  265. $optional['env_version'] = config('app.env') == 'production' ? 'release' : 'trial';
  266. // 执行生成
  267. $result = $Mini->app_code->getUnlimit($scene, $optional);
  268. // 判断结果
  269. if( $result instanceof \EasyWeChat\Kernel\Http\StreamResponse ) return $result->getBody()->getContents();
  270. // 错误
  271. return ['error'=>$result['errcode'].'=>'.$result['errmsg']];
  272. }
  273. /**
  274. * 发布最后一个审核通过的小程序代码版本
  275. *
  276. * @param string $appId app_id
  277. */
  278. public function release($appId){
  279. $Mini = $this->getMini($appId);
  280. // 配置小程序服务器域名
  281. $result = $Mini->code->release();
  282. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  283. return $result;
  284. }
  285. /**
  286. * 发布最后一个审核通过的小程序代码版本
  287. *
  288. * @param string $appId app_id
  289. */
  290. public function submitAudit($appId,$data){
  291. $Mini = $this->getMini($appId);
  292. // 配置小程序服务器域名
  293. $result = $Mini->code->submitAudit($data);
  294. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  295. return $result;
  296. }
  297. /**
  298. * 获取类目名称信息
  299. *
  300. * @param string $appId app_id
  301. */
  302. public function getAllCategoryName($appId){
  303. $Mini = $this->getMini($appId);
  304. // 配置小程序服务器域名
  305. $result = $Mini->code->getCategory();
  306. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  307. return $result;
  308. }
  309. /**
  310. * 获取永久素材
  311. *
  312. * @param string $appId app_id
  313. * @param int $mediaId 素材id
  314. */
  315. public function getMaterial($appId,$mediaId){
  316. $Mini = $this->getMini($appId);
  317. // 配置小程序服务器域名
  318. $result = $Mini->material->get($mediaId);
  319. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  320. return $result;
  321. }
  322. /**
  323. * 设置小程序用户隐私保护指引
  324. *
  325. * @param string $appId app_id
  326. * @param array $params params
  327. */
  328. public function setPrivacySetting($appId,$params){
  329. $Mini = $this->getMini($appId);
  330. // 设置小程序用户隐私保护指引
  331. $result = $Mini->privacy->set($params);
  332. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  333. return $result;
  334. }
  335. /**
  336. * 获取小程序信息
  337. *
  338. * @param string $appId app_id
  339. */
  340. public function getAccountBasicInfo($appId){
  341. $Mini = $this->getMini($appId);
  342. // 设置小程序用户隐私保护指引
  343. $result = $Mini->account->getBasicInfo();
  344. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  345. return $result;
  346. }
  347. /**
  348. * 获取小程序版本信息
  349. *
  350. * @param string $appId app_id
  351. */
  352. public function getVersionInfo($appId){
  353. $Mini = $this->getMini($appId);
  354. // 设置小程序用户隐私保护指引
  355. $result = $Mini->base->getVersionInfo();
  356. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  357. return $result;
  358. }
  359. /**
  360. * 配置小程序业务域名
  361. * @param array $params 配置数据
  362. * @param string $appId app_id
  363. */
  364. public function modifyJumpDomain($appId,$params){
  365. $Mini = $this->getMini($appId);
  366. // 配置小程序服务器域名
  367. $result = $Mini->domain->setWebviewDomain($params['setWebviewDomain'],$params['action']);
  368. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  369. return $result;
  370. }
  371. /**
  372. * 查询最后一个审核结果
  373. *
  374. * @param string $appId app_id
  375. */
  376. public function getLatestAuditStatus($appId){
  377. $Mini = $this->getMini($appId);
  378. // 配置小程序服务器域名
  379. $result = $Mini->code->getLatestAuditStatus();
  380. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  381. return $result;
  382. }
  383. /**
  384. * 小程序获取缓存的AccessToken
  385. *
  386. */
  387. public function getAccessToken($appId){
  388. // 返回应用实例
  389. $Mini = $this->getMini($appId);
  390. // 获取access_token
  391. $accessToken = $Mini->access_token->getToken();
  392. return $accessToken;
  393. }
  394. /**
  395. * 公众号获取缓存的AccessToken
  396. *
  397. */
  398. public function getOfficialAccessToken($appId){
  399. // 返回应用实例
  400. $official = $this->getOfficial($appId);
  401. // 获取access_token
  402. $accessToken = $official->access_token->getToken();
  403. return $accessToken;
  404. }
  405. /**
  406. * 代小程序上传发货物流信息
  407. *
  408. */
  409. public function deliver($appId,$data=[]){
  410. $Mini = $this->getMini($appId);
  411. // 获取access_token
  412. $accessToken = $Mini->access_token->getToken();
  413. $params = [
  414. 'order_key' =>[
  415. "order_number_type" => $data['order_number_type'],
  416. "mchid" => $data['mchid'],
  417. "transaction_id" => $data['transaction_id'] ?? null,
  418. ],
  419. 'delivery_mode' => $data['delivery_mode'],
  420. 'logistics_type' => $data['logistics_type'],
  421. 'shipping_list' =>[
  422. [
  423. "tracking_no" => $data['tracking_no'],
  424. "express_company" => $data['express_company'],
  425. "item_desc" => $data['item_desc'],
  426. 'contact' => [
  427. 'consignor_contact' => $data['consignor_contact'] ?? null,
  428. 'receiver_contact' => $data['receiver_contact'] ?? null,
  429. ],
  430. ]
  431. ],
  432. 'upload_time' => date('Y-m-d\TH:i:s.vP',$data['upload_time']),
  433. 'payer' => [
  434. 'openid' => $data['openid'],
  435. ],
  436. ];
  437. // 上传物流信息
  438. //$result = $Mini->shipping->uploadShippingInfo($params);
  439. $token = [
  440. 'access_token' => $accessToken['authorizer_access_token']
  441. ];
  442. try {
  443. $token = http_build_query($token);
  444. $params = json_encode($params, JSON_UNESCAPED_UNICODE);
  445. $result = Curl::to('https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?'.$token)->withData($params)->asJsonResponse(true)->post();
  446. // 判断结果
  447. if( !empty($result['errcode']) ){
  448. Log::error('wechat/deliver','发货失败,出现错误'.json_encode($result).'参数'.json_encode($params));
  449. return ['code'=>'error','msg'=>'发货失败','data'=>$result];
  450. }
  451. } catch (\Exception $e) {
  452. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  453. $r = $e->getResponse();
  454. // 记录错误信息
  455. Log::error('wechat/deliver','发货失败,出现错误'.$r->getBody().'参数'.json_encode($params));
  456. }
  457. return ['code'=>'error','msg'=>'发货失败','data'=>['msg'=>$e->getMessage(),'params'=>$params]];
  458. }
  459. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  460. return ['code'=>'success','msg'=>'发货成功'];
  461. }
  462. /**
  463. * 申请物流信息
  464. *
  465. */
  466. public function applyMsgPlugin($appId,$data=[]){
  467. // 返回应用实例
  468. $Mini = $this->getMini($appId);
  469. // 获取access_token
  470. $accessToken = $Mini->access_token->getToken();
  471. $params = [
  472. 'access_token' => $accessToken['authorizer_access_token'],
  473. ];
  474. $params = http_build_query($params);
  475. $result = Curl::to('https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/open_openmsg?'.$params)->asJsonResponse(true)->get();
  476. // 判断结果
  477. if( !empty($result['errcode']) ) return ['code'=>'error','msg'=>$result['errcode'].'=>'.$result['errmsg']];
  478. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  479. return ['code'=>'success','msg'=>'申请成功'];
  480. }
  481. /**
  482. * 获取快递公司列表
  483. *
  484. */
  485. public function getExpressCompanyList($appId){
  486. // 返回应用实例
  487. $Mini = $this->getMini($appId);
  488. // 获取access_token
  489. $accessToken = $Mini->access_token->getToken();
  490. $params = [
  491. 'access_token' => $accessToken['authorizer_access_token'],
  492. ];
  493. $data = $params;
  494. $params = http_build_query($params);
  495. $data = json_encode($data, JSON_UNESCAPED_UNICODE);
  496. $result = Curl::to('https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/get_delivery_list?'.$params)->withData($data)->asJsonResponse(true)->post();
  497. // 判断结果
  498. if( !empty($result['errcode']) ) return ['code'=>'error','msg'=>$result['errcode'].'=>'.$result['errmsg']];
  499. // 获取不包含区号的手机号(因为绑定手机号字段会有国际区号)
  500. return ['code'=>'success','msg'=>'成功','data'=>$result['delivery_list']];
  501. }
  502. }