WebAppService.php 26.0 KB
Newer Older
李光春's avatar
李光春 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
<?php

// +----------------------------------------------------------------------
// | ThinkLibrary 6.0 for ThinkPhP 6.0
// +----------------------------------------------------------------------
// | 版权所有 2017~2020 [ https://www.dtapp.net ]
// +----------------------------------------------------------------------
// | 官方网站: https://gitee.com/liguangchun/ThinkLibrary
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// +----------------------------------------------------------------------
// | gitee 仓库地址 :https://gitee.com/liguangchun/ThinkLibrary
// | github 仓库地址 :https://github.com/GC0202/ThinkLibrary
李光春's avatar
李光春 已提交
14 15 16
// | gitlab 仓库地址 :https://gitlab.com/liguangchun/thinklibrary
// | weixin 仓库地址 :https://git.weixin.qq.com/liguangchun/ThinkLibrary
// | huaweicloud 仓库地址 :https://codehub-cn-south-1.devcloud.huaweicloud.com/composer00001/ThinkLibrary.git
李光春's avatar
李光春 已提交
17 18 19
// | Packagist 地址 :https://packagist.org/packages/liguangchun/think-library
// +----------------------------------------------------------------------

李光春's avatar
李光春 已提交
20
namespace DtApp\ThinkLibrary\service\wechat;
李光春's avatar
李光春 已提交
21

李光春's avatar
李光春 已提交
22
use DtApp\ThinkLibrary\exception\DtaException;
李光春's avatar
李光春 已提交
23
use DtApp\ThinkLibrary\facade\Pregs;
李光春's avatar
李光春 已提交
24
use DtApp\ThinkLibrary\facade\Randoms;
李光春's avatar
李光春 已提交
25
use DtApp\ThinkLibrary\facade\Urls;
李光春's avatar
李光春 已提交
26
use DtApp\ThinkLibrary\facade\Xmls;
李光春's avatar
李光春 已提交
27
use DtApp\ThinkLibrary\Service;
李光春's avatar
李光春 已提交
28
use DtApp\ThinkLibrary\service\curl\HttpService;
李光春's avatar
李光春 已提交
29
use think\db\exception\DbException;
李光春's avatar
李光春 已提交
30 31

/**
李光春's avatar
李光春 已提交
32 33
 * 公众号
 * Class WebAppService
李光春's avatar
李光春 已提交
34 35
 * @package DtApp\ThinkLibrary\service\WeChat
 */
李光春's avatar
李光春 已提交
36
class WebAppService extends Service
李光春's avatar
李光春 已提交
37
{
李光春's avatar
李光春 已提交
38 39
    private $open_url = "https://open.weixin.qq.com/";
    private $api_url = "https://api.weixin.qq.com/";
李光春's avatar
李光春 已提交
40 41 42 43 44 45 46

    /**
     * 公众号的唯一标识
     * @var
     */
    private $app_id;

李光春's avatar
李光春 已提交
47 48 49 50 51 52
    /**
     * 公众号的appsecret
     * @var
     */
    private $app_secret;

李光春's avatar
李光春 已提交
53 54 55 56 57 58 59 60 61 62 63 64 65
    /**
     * 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
     * @var
     */
    private $redirect_uri;

    /**
     * 返回类型,请填写code
     * @var string
     */
    private $response_type = 'code';
    private $scope = "snsapi_base";
    private $state = "";
李光春's avatar
李光春 已提交
66
    private $grant_type = "authorization_code";
李光春's avatar
李光春 已提交
67

李光春's avatar
李光春 已提交
68 69 70 71 72 73
    /**
     * 驱动方式
     * @var string
     */
    private $cache = "file";

李光春's avatar
李光春 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    /**
     * 商户平台设置的密钥key
     * @var
     */
    private $mch_key;

    /**
     * @param string $mchKey
     * @return $this
     */
    public function mchKey(string $mchKey)
    {
        $this->mch_key = $mchKey;
        return $this;
    }

    /**
     * 商户号
     * @var
     */
    private $mch_id;

    /**
     * 商户号
     * @param string $mchId
     * @return $this
     */
    public function mchId(string $mchId)
    {
        $this->mch_id = $mchId;
        return $this;
    }

李光春's avatar
李光春 已提交
107 108 109 110 111 112 113 114 115 116 117
    /**
     * 公众号的唯一标识
     * @param string $appId
     * @return $this
     */
    public function appId(string $appId)
    {
        $this->app_id = $appId;
        return $this;
    }

李光春's avatar
李光春 已提交
118 119 120 121 122 123 124 125 126 127 128
    /**
     * 公众号的appsecret
     * @param string $appSecret
     * @return $this
     */
    public function appSecret(string $appSecret)
    {
        $this->app_secret = $appSecret;
        return $this;
    }

李光春's avatar
李光春 已提交
129 130 131 132 133 134
    /**
     * 获取配置信息
     * @return $this
     */
    private function getConfig()
    {
李光春's avatar
李光春 已提交
135 136 137
        $this->cache = config('dtapp.wechat.webapp.cache');
        $this->app_id = config('dtapp.wechat.webapp.app_id');
        $this->app_secret = config('dtapp.wechat.webapp.app_secret');
李光春's avatar
李光春 已提交
138 139 140
        return $this;
    }

李光春's avatar
李光春 已提交
141 142 143 144
    /**
     * 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
     * @param string $redirectUri
     * @return $this
李光春's avatar
李光春 已提交
145
     * @throws DtaException
李光春's avatar
李光春 已提交
146 147 148
     */
    public function redirectUri(string $redirectUri)
    {
李光春's avatar
李光春 已提交
149 150 151
        if (empty(Pregs::isLink($redirectUri))) {
            throw new DtaException("请检查redirectUri,是否正确");
        }
李光春's avatar
李光春 已提交
152 153 154 155 156 157 158 159
        $this->redirect_uri = Urls::lenCode($redirectUri);
        return $this;
    }

    /**
     * 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
     * @param string $scope
     * @return $this
李光春's avatar
李光春 已提交
160
     * @throws DtaException
李光春's avatar
李光春 已提交
161 162 163
     */
    public function scope(string $scope)
    {
李光春's avatar
李光春 已提交
164 165 166 167 168 169 170
        if ($scope === "snsapi_base") {
            $this->scope = $scope;
        } elseif ($scope === "snsapi_userinfo") {
            $this->scope = $scope;
        } else {
            throw new DtaException("请检查scope参数");
        }
李光春's avatar
李光春 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183 184
        return $this;
    }

    /**
     * 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
     * @param string $state
     * @return $this
     */
    public function state(string $state)
    {
        $this->state = $state;
        return $this;
    }

李光春's avatar
李光春 已提交
185 186 187 188 189 190 191 192 193 194 195
    /**
     * 驱动方式
     * @param string $cache
     * @return $this
     */
    public function cache(string $cache)
    {
        $this->cache = $cache;
        return $this;
    }

李光春's avatar
李光春 已提交
196 197 198
    /**
     * 网页授权
     * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
李光春's avatar
李光春 已提交
199 200
     * @return void
     * @throws DtaException
李光春's avatar
李光春 已提交
201 202 203
     */
    public function oauth2()
    {
李光春's avatar
李光春 已提交
204 205 206 207 208 209
        if (empty($this->app_id)) {
            $this->getConfig();
        }
        if (strlen($this->state) > 128) {
            throw new DtaException("请检查state参数,最多128字节");
        }
李光春's avatar
李光春 已提交
210 211 212 213 214 215 216
        $params = Urls::toParams([
            'appid' => $this->app_id,
            'redirect_uri' => $this->redirect_uri,
            'response_type' => $this->response_type,
            'scope' => $this->scope,
            'state' => $this->state
        ]);
李光春's avatar
李光春 已提交
217
        return header("Location:{$this->open_url}connect/oauth2/authorize?$params#wechat_redirect");
李光春's avatar
李光春 已提交
218 219 220 221 222
    }

    /**
     * 通过code换取网页授权access_token
     * @param string $code
李光春's avatar
李光春 已提交
223
     * @param bool $is
李光春's avatar
李光春 已提交
224
     * @return array|bool|mixed|string
李光春's avatar
李光春 已提交
225
     * @throws DtaException
李光春's avatar
李光春 已提交
226
     */
李光春's avatar
李光春 已提交
227
    public function accessToken(string $code, bool $is = true)
李光春's avatar
李光春 已提交
228
    {
李光春's avatar
李光春 已提交
229 230 231 232 233 234 235 236 237
        if (empty($this->app_id) || empty($this->app_secret)) {
            $this->getConfig();
        }
        if (empty($this->app_id)) {
            throw new DtaException('请检查app_id参数');
        }
        if (empty($this->app_secret)) {
            throw new DtaException('请检查app_secret参数');
        }
李光春's avatar
李光春 已提交
238
        return HttpService::instance()
李光春's avatar
李光春 已提交
239
            ->url("{$this->api_url}sns/oauth2/access_token?appid={$this->app_id}&secret={$this->app_secret}&code={$code}&grant_type={$this->grant_type}")
李光春's avatar
李光春 已提交
240
            ->toArray($is);
李光春's avatar
李光春 已提交
241 242 243 244 245
    }

    /**
     * 刷新access_token(如果需要)
     * @param string $refreshToken
李光春's avatar
李光春 已提交
246
     * @param bool $is
李光春's avatar
李光春 已提交
247
     * @return array|bool|mixed|string
李光春's avatar
李光春 已提交
248
     * @throws DtaException
李光春's avatar
李光春 已提交
249
     */
李光春's avatar
李光春 已提交
250
    public function refreshToken(string $refreshToken, bool $is = true)
李光春's avatar
李光春 已提交
251
    {
李光春's avatar
李光春 已提交
252 253 254 255 256 257
        if (empty($this->app_id)) {
            $this->getConfig();
        }
        if (empty($this->app_id)) {
            throw new DtaException('请检查app_id参数');
        }
李光春's avatar
李光春 已提交
258 259
        $this->grant_type = "refresh_token";
        return HttpService::instance()
李光春's avatar
李光春 已提交
260
            ->url("{$this->api_url}sns/oauth2/refresh_token?appid={$this->app_id}&grant_type={$this->grant_type}&refresh_token={$refreshToken}")
李光春's avatar
李光春 已提交
261
            ->toArray($is);
李光春's avatar
李光春 已提交
262 263 264 265 266 267 268
    }

    /**
     * 拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $accessToken
     * @param string $openid
     * @param string $lang
李光春's avatar
李光春 已提交
269
     * @param bool $is
李光春's avatar
李光春 已提交
270 271
     * @return array|bool|mixed|string
     */
李光春's avatar
李光春 已提交
272
    public function useInfo(string $accessToken, string $openid, $lang = "zh_CN", bool $is = true)
李光春's avatar
李光春 已提交
273
    {
李光春's avatar
李光春 已提交
274 275 276
        if (empty($this->app_id) || empty($this->app_secret)) {
            $this->getConfig();
        }
李光春's avatar
李光春 已提交
277
        return HttpService::instance()
李光春's avatar
李光春 已提交
278
            ->url("{$this->api_url}sns/userinfo?access_token={$accessToken}&openid={$openid}&lang={$lang}")
李光春's avatar
李光春 已提交
279
            ->toArray($is);
李光春's avatar
李光春 已提交
280 281 282 283 284 285
    }

    /**
     * 检验授权凭证(access_token)是否有效
     * @param string $accessToken
     * @param string $openid
李光春's avatar
李光春 已提交
286
     * @param bool $is
李光春's avatar
李光春 已提交
287 288
     * @return array|bool|mixed|string
     */
李光春's avatar
李光春 已提交
289
    public function auth(string $accessToken, string $openid, bool $is = true)
李光春's avatar
李光春 已提交
290
    {
李光春's avatar
李光春 已提交
291 292 293
        if (empty($this->app_id) || empty($this->app_secret)) {
            $this->getConfig();
        }
李光春's avatar
李光春 已提交
294
        return HttpService::instance()
李光春's avatar
李光春 已提交
295
            ->url("{$this->api_url}sns/auth?access_token={$accessToken}&openid={$openid}")
李光春's avatar
李光春 已提交
296
            ->toArray($is);
李光春's avatar
李光春 已提交
297
    }
李光春's avatar
李光春 已提交
298 299 300 301

    /**
     * 分享
     * @return array
李光春's avatar
李光春 已提交
302
     * @throws DbException
李光春's avatar
李光春 已提交
303
     * @throws DtaException
李光春's avatar
李光春 已提交
304
     */
李光春's avatar
李光春 已提交
305
    public function share()
李光春's avatar
李光春 已提交
306
    {
李光春's avatar
李光春 已提交
307 308 309 310 311 312
        if (empty($this->app_id)) {
            $this->getConfig();
        }
        if (empty($this->app_id)) {
            throw new DtaException('请检查app_id参数');
        }
李光春's avatar
李光春 已提交
313
        // 获取数据
李光春's avatar
李光春 已提交
314
        $accessToken = $this->getAccessToken();
李光春's avatar
李光春 已提交
315 316 317
        if (!isset($accessToken['access_token'])) {
            throw  new DtaException("获取access_token错误," . $accessToken['errmsg']);
        }
李光春's avatar
李光春 已提交
318
        $res = HttpService::instance()
李光春's avatar
李光春 已提交
319
            ->url("{$this->api_url}cgi-bin/ticket/getticket?access_token={$accessToken['access_token']}&type=jsapi")
李光春's avatar
李光春 已提交
320
            ->toArray();
李光春's avatar
李光春 已提交
321 322 323
        if (!empty($res['errcode'])) {
            // 获取数据
            $accessToken = $this->getAccessToken();
李光春's avatar
李光春 已提交
324 325 326
            if (!isset($accessToken['access_token'])) {
                throw  new DtaException("获取access_token错误," . $accessToken['errmsg']);
            }
李光春's avatar
李光春 已提交
327 328 329
            $res = HttpService::instance()
                ->url("{$this->api_url}cgi-bin/ticket/getticket?access_token={$accessToken['access_token']}&type=jsapi")
                ->toArray();
李光春's avatar
李光春 已提交
330 331 332
            if (!empty($res['errcode'])) {
                throw new DtaException('accessToken已过期');
            }
李光春's avatar
李光春 已提交
333
        }
李光春's avatar
李光春 已提交
334 335 336 337 338 339 340 341 342 343
        // 注意 URL 一定要动态获取,不能 hardcode.
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
        $timestamp = time();
        $nonceStr = $this->createNonceStr();
        // 获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。
        $jsapiTicket = $res['ticket'];
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
        return [
李光春's avatar
李光春 已提交
344
            "appId" => $this->app_id,
李光春's avatar
李光春 已提交
345 346 347 348 349 350 351 352 353 354 355 356
            "nonceStr" => $nonceStr,
            "timestamp" => $timestamp,
            "url" => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }

    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
李光春's avatar
李光春 已提交
357 358 359
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
李光春's avatar
李光春 已提交
360 361
        return $str;
    }
李光春's avatar
李光春 已提交
362 363 364 365 366

    /**
     * 生成二维码
     * @param array $data
     * @return array|bool|mixed|string
李光春's avatar
李光春 已提交
367
     * @throws DbException
李光春's avatar
李光春 已提交
368
     * @throws DtaException
李光春's avatar
李光春 已提交
369 370 371 372 373 374 375 376 377 378 379 380
     */
    public function qrCode(array $data)
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        return HttpService::instance()
            ->url("{$this->api_url}cgi-bin/qrcode/create?access_token={$accessToken['access_token']}")
            ->data($data)
            ->post()
            ->toArray();
    }

李光春's avatar
李光春 已提交
381 382 383 384
    /**
     * 发送模板消息
     * @param array $data
     * @return array|bool|mixed|string
李光春's avatar
李光春 已提交
385
     * @throws DbException
李光春's avatar
李光春 已提交
386
     * @throws DtaException
李光春's avatar
李光春 已提交
387 388 389 390 391
     */
    public function messageTemplateSend(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
李光春's avatar
李光春 已提交
392
        $url = "{$this->api_url}cgi-bin/message/template/send?access_token={$accessToken['access_token']}";
李光春's avatar
李光春 已提交
393 394 395 396 397 398
        return HttpService::instance()
            ->url($url)
            ->data($data)
            ->toArray();
    }

李光春's avatar
李光春 已提交
399 400 401 402 403 404
    /**
     * 设置所属行业
     * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#0
     * @param string $access_token
     * @param array $data
     * @return bool|mixed|string
李光春's avatar
李光春 已提交
405
     * @throws DbException
李光春's avatar
李光春 已提交
406
     * @throws DtaException
李光春's avatar
李光春 已提交
407 408 409 410 411 412 413 414 415 416 417 418
     */
    public function setIndustry(string $access_token, array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/template/api_set_industry?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->data($data)
            ->toArray();
    }

419 420
    /**
     * 将一条长链接转成短链接
李光春's avatar
李光春 已提交
421
     * @param string $long_url
422
     * @return bool
李光春's avatar
李光春 已提交
423
     * @throws DbException
李光春's avatar
李光春 已提交
424
     * @throws DtaException
425
     */
李光春's avatar
李光春 已提交
426
    public function shortUrl(string $long_url)
427 428 429
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
李光春's avatar
李光春 已提交
430
        $url = "{$this->api_url}cgi-bin/shorturl?access_token={$accessToken['access_token']}";
431 432 433 434
        return HttpService::instance()
            ->url($url)
            ->data([
                'action' => 'long2short',
李光春's avatar
李光春 已提交
435
                'long_url' => $long_url
436 437 438 439
            ])
            ->toArray();
    }

李光春's avatar
李光春 已提交
440 441 442 443 444
    /**
     * 连Wi-Fi完成页跳转小程序
     * https://developers.weixin.qq.com/doc/offiaccount/WiFi_via_WeChat/WiFi_mini_programs.html
     * @param array $data
     * @return array|bool|mixed|string
李光春's avatar
李光春 已提交
445
     * @throws DbException
李光春's avatar
李光春 已提交
446
     * @throws DtaException
李光春's avatar
李光春 已提交
447 448 449 450 451 452 453 454 455 456 457 458 459
     */
    public function fiNihPageSet(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}bizwifi/finishpage/set?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->post()
            ->data($data)
            ->toArray();
    }

460 461 462 463 464
    /**
     * 自定义菜单 获取自定义菜单配置
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Getting_Custom_Menu_Configurations.html
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
465
     * @throws DtaException
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
     */
    public function menuGet()
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/get?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->toArray();
    }

    /**
     * 自定义菜单 创建个性化菜单
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Personalized_menu_interface.html
     * @param array $data
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
483
     * @throws DtaException
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
     */
    public function menuAddConditional(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/addconditional?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->post()
            ->data($data)
            ->toArray();
    }

    /**
     * 自定义菜单 删除个性化菜单
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Personalized_menu_interface.html
     * @param array $data
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
503
     * @throws DtaException
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
     */
    public function menuDelConditional(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/delconditional?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->post()
            ->data($data)
            ->toArray();
    }

    /**
     * 自定义菜单 测试个性化菜单匹配结果
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Personalized_menu_interface.html
     * @param array $data
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
523
     * @throws DtaException
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
     */
    public function menuTryMatch(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/trymatch?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->post()
            ->data($data)
            ->toArray();
    }

    /**
     * 自定义菜单 删除接口
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Deleting_Custom-Defined_Menu.html
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
542
     * @throws DtaException
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
     */
    public function menuDelete()
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/delete?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->toArray();
    }

    /**
     * 自定义菜单 查询接口
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Querying_Custom_Menus.html
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
559
     * @throws DtaException
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
     */
    public function getCurrentSelfmenuInfo()
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/get_current_selfmenu_info?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->toArray();
    }

    /**
     * 自定义菜单 创建接口
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
     * @param array $data
     * @return array|bool|mixed|string
     * @throws DbException
李光春's avatar
李光春 已提交
577
     * @throws DtaException
578 579 580 581 582 583 584 585 586 587 588 589 590
     */
    public function menuCreate(array $data = [])
    {
        // 获取数据
        $accessToken = $this->getAccessToken();
        $url = "{$this->api_url}cgi-bin/menu/create?access_token={$accessToken['access_token']}";
        return HttpService::instance()
            ->url($url)
            ->post()
            ->data($data)
            ->toArray();
    }

李光春's avatar
李光春 已提交
591 592 593
    /**
     * 获取access_token信息
     * @return array|bool|mixed|string|string[]
李光春's avatar
李光春 已提交
594
     * @throws DbException
李光春's avatar
李光春 已提交
595
     * @throws DtaException
李光春's avatar
李光春 已提交
596 597 598
     */
    private function getAccessToken()
    {
李光春's avatar
李光春 已提交
599 600 601 602 603 604 605 606 607 608 609 610
        if (empty($this->cache) || empty($this->app_id) || empty($this->app_secret)) {
            $this->getConfig();
        }
        if (empty($this->cache)) {
            throw new DtaException('请检查cache参数');
        }
        if (empty($this->app_id)) {
            throw new DtaException('请检查app_id参数');
        }
        if (empty($this->app_secret)) {
            throw new DtaException('请检查app_secret参数');
        }
李光春's avatar
李光春 已提交
611

李光春's avatar
李光春 已提交
612
        $this->grant_type = "client_credential";
李光春's avatar
李光春 已提交
613 614 615 616 617
        if ($this->cache == "file") {
            // 文件名
            $file = "{$this->app->getRootPath()}runtime/{$this->app_id}_access_token.json";
            // 获取数据
            $accessToken = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
李光春's avatar
李光春 已提交
618 619 620 621 622 623 624
            if (empty($accessToken) || !is_array($accessToken)) {
                $accessToken = [
                    'access_token' => '',
                    'expires_in' => '',
                    'expires_time' => '',
                ];
            }
李光春's avatar
李光春 已提交
625
            if (empty($accessToken['expires_time'])) {
李光春's avatar
李光春 已提交
626
                // 文件不存在
李光春's avatar
李光春 已提交
627 628 629 630 631 632 633
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
                $accessToken_res['expires_time'] = time() + 6000;
                file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
                $accessToken = $accessToken_res;
            } else if (!isset($accessToken['access_token'])) {
李光春's avatar
李光春 已提交
634
                // 内容不存在
李光春's avatar
李光春 已提交
635 636 637 638 639 640 641 642 643 644 645 646 647 648
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
                $accessToken_res['expires_time'] = time() + 6000;
                file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
                $accessToken = $accessToken_res;
            } else if ($accessToken['expires_time'] <= time()) {
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
                $accessToken_res['expires_time'] = time() + 6000;
                file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
                $accessToken = $accessToken_res;
            }
李光春's avatar
李光春 已提交
649 650 651
            $judge = HttpService::instance()
                ->url("{$this->api_url}cgi-bin/getcallbackip?access_token={$accessToken['access_token']}")
                ->toArray();
652
            if (!isset($judge['ip_list'])) {
李光春's avatar
李光春 已提交
653 654 655 656 657 658 659
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
                $accessToken_res['expires_time'] = time() + 6000;
                file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
                $accessToken = $accessToken_res;
            }
李光春's avatar
李光春 已提交
660 661 662 663 664 665
            return $accessToken;
        } else if ($this->cache == "mysql") {
            $access_token = [];
            // 文件名
            $file = "{$this->app_id}_access_token";
            // 获取数据
李光春's avatar
李光春 已提交
666
            $cache_mysql_value = dtacache($file);
李光春's avatar
李光春 已提交
667 668 669 670 671 672
            if (!empty($cache_mysql_value)) {
                $access_token['access_token'] = $cache_mysql_value;
            } else {
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
李光春's avatar
李光春 已提交
673
                dtacache($file, $accessToken_res['access_token'], 6000);
李光春's avatar
李光春 已提交
674
                $access_token['access_token'] = $accessToken_res['access_token'];
李光春's avatar
李光春 已提交
675 676
            }
            $judge = HttpService::instance()
677
                ->url("{$this->api_url}cgi-bin/getcallbackip?access_token={$access_token['access_token']}")
李光春's avatar
李光春 已提交
678
                ->toArray();
679
            if (!isset($judge['ip_list'])) {
李光春's avatar
李光春 已提交
680 681 682 683 684
                $accessToken_res = HttpService::instance()
                    ->url("{$this->api_url}cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
                    ->toArray();
                dtacache($file, $accessToken_res['access_token'], 6000);
                $access_token['access_token'] = $accessToken_res['access_token'];
李光春's avatar
李光春 已提交
685 686
            }
            return $access_token;
李光春's avatar
李光春 已提交
687 688 689
        } else {
            throw new DtaException("驱动方式错误");
        }
李光春's avatar
李光春 已提交
690
    }
李光春's avatar
李光春 已提交
691 692 693 694 695

    /**
     * 微信支付
     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
     * @param array $array
李光春's avatar
李光春 已提交
696
     * @return bool|string
李光春's avatar
李光春 已提交
697 698 699 700 701 702
     */
    public function payUnfIedOrder(array $array)
    {
        $array['appid'] = $this->app_id;
        $array['mch_id'] = $this->mch_id;
        $array['nonce_str'] = Randoms::generate(32, 3);
李光春's avatar
李光春 已提交
703 704 705 706
        $array['sign_type'] = 'HMAC-SHA256';
        $array['sign'] = $this->paySign($array);
        $res = $this->postXmlCurl(Xmls::toXml($array));
        return Xmls::toArray($res);
李光春's avatar
李光春 已提交
707 708
    }

李光春's avatar
李光春 已提交
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
    /**
     * 微信内H5调起支付
     * @param string $prepay_id
     * @return array
     */
    public function h5Pay(string $prepay_id)
    {
        $array['appId'] = $this->app_id;
        $array['timeStamp'] = time();
        $array['nonceStr'] = Randoms::generate(32, 3);
        $array['package'] = "prepay_id={$prepay_id}";
        $array['signType'] = 'HMAC-SHA256';
        $array['paySign'] = $this->paySign($array);
        return $array;
    }

李光春's avatar
李光春 已提交
725 726 727 728 729 730 731 732 733 734 735
    /**
     * 生成支付签名
     * @param array $array 参与签名的内容组成的数组
     * @param bool $hmacsha256 是否使用 HMAC-SHA256算法,否则使用MD5
     * @return string
     */
    private function paySign(array $array, bool $hmacsha256 = true)
    {
        // 排序
        ksort($array);
        // 转成字符串
李光春's avatar
李光春 已提交
736
        $stringA = Urls::toParams($array);
李光春's avatar
李光春 已提交
737
        // 在字符串接商户支付秘钥
李光春's avatar
李光春 已提交
738
        $stringSignTemp = "{$stringA}&key=" . $this->mch_key;
李光春's avatar
李光春 已提交
739
        //步骤四:MD5或HMAC-SHA256C加密
李光春's avatar
李光春 已提交
740 741 742 743 744
        if ($hmacsha256) {
            $str = hash_hmac("sha256", $stringSignTemp, $this->mch_key);
        } else {
            $str = md5($stringSignTemp);
        }
李光春's avatar
李光春 已提交
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
        //符转大写
        return strtoupper($str);
    }

    private function postXmlCurl($xml)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        curl_setopt($ch, CURLOPT_URL, "https://api.mch.weixin.qq.com/pay/unifiedorder");
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        //试试手气新增,增加之后 curl 不报 60# 错误,可以请求到微信的响应
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  //不验证 SSL 证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//不验证 SSL 证书域名
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            return "curl error, error code " . $error;
            //throw new WxPayException("curl出错,错误码:$error");
        }
    }
李光春's avatar
李光春 已提交
780
}