| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- <?php
- namespace Overtrue\Socialite\Providers;
- use Overtrue\Socialite\Exceptions\InvalidArgumentException;
- use Overtrue\Socialite\User;
- use Psr\Http\Message\ResponseInterface;
- /**
- * @see http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html [WeChat - 公众平台OAuth文档]
- * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
- * [网站应用微信登录开发指南]
- */
- class WeChat extends Base
- {
- public const NAME = 'wechat';
- protected string $baseUrl = 'https://api.weixin.qq.com/sns';
- protected array $scopes = ['snsapi_login'];
- protected bool $withCountryCode = false;
- protected ?array $component = null;
- protected ?string $openid = null;
- public function __construct(array $config)
- {
- parent::__construct($config);
- if ($this->getConfig()->has('component')) {
- $this->prepareForComponent((array) $this->getConfig()->get('component'));
- }
- }
- /**
- * @param string $openid
- *
- * @return $this
- */
- public function withOpenid(string $openid): self
- {
- $this->openid = $openid;
- return $this;
- }
- public function withCountryCode()
- {
- $this->withCountryCode = true;
- return $this;
- }
- /**
- * @param string $code
- *
- * @return array
- * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException
- */
- public function tokenFromCode(string $code): array
- {
- $response = $this->getTokenFromCode($code);
- return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
- }
- /**
- * @param array $componentConfig ['id' => xxx, 'token' => xxx]
- *
- * @return \Overtrue\Socialite\Providers\WeChat
- * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
- */
- public function withComponent(array $componentConfig)
- {
- $this->prepareForComponent($componentConfig);
- return $this;
- }
- public function getComponent()
- {
- return $this->component;
- }
- protected function getAuthUrl(): string
- {
- $path = 'oauth2/authorize';
- if (in_array('snsapi_login', $this->scopes)) {
- $path = 'qrconnect';
- }
- return $this->buildAuthUrlFromBase("https://open.weixin.qq.com/connect/{$path}");
- }
- /**
- * @param string $url
- *
- * @return string
- */
- protected function buildAuthUrlFromBase(string $url): string
- {
- $query = http_build_query($this->getCodeFields(), '', '&', $this->encodingType);
- return $url . '?' . $query . '#wechat_redirect';
- }
- protected function getCodeFields(): array
- {
- if (!empty($this->component)) {
- $this->with(array_merge($this->parameters, ['component_appid' => $this->component['id']]));
- }
- return array_merge([
- 'appid' => $this->getClientId(),
- 'redirect_uri' => $this->redirectUrl,
- 'response_type' => 'code',
- 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
- 'state' => $this->state ?: md5(uniqid()),
- 'connect_redirect' => 1,
- ], $this->parameters);
- }
- protected function getTokenUrl(): string
- {
- if (!empty($this->component)) {
- return $this->baseUrl . '/oauth2/component/access_token';
- }
- return $this->baseUrl . '/oauth2/access_token';
- }
- /**
- * @param string $code
- *
- * @return \Overtrue\Socialite\User
- * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException
- */
- public function userFromCode(string $code): User
- {
- if (in_array('snsapi_base', $this->scopes)) {
- return $this->mapUserToObject(\json_decode($this->getTokenFromCode($code)->getBody()->getContents(), true) ?? []);
- }
- $token = $this->tokenFromCode($code);
- $this->withOpenid($token['openid']);
- $user = $this->userFromToken($token[$this->accessTokenKey]);
- return $user->setRefreshToken($token['refresh_token'])
- ->setExpiresIn($token['expires_in']);
- }
- /**
- * @param string $token
- *
- * @return array
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- protected function getUserByToken(string $token): array
- {
- $language = $this->withCountryCode ? null : (isset($this->parameters['lang']) ? $this->parameters['lang'] : 'zh_CN');
- $response = $this->getHttpClient()->get($this->baseUrl . '/userinfo', [
- 'query' => array_filter([
- 'access_token' => $token,
- 'openid' => $this->openid,
- 'lang' => $language,
- ]),
- ]);
- return \json_decode($response->getBody()->getContents(), true) ?? [];
- }
- /**
- * @param array $user
- *
- * @return \Overtrue\Socialite\User
- */
- protected function mapUserToObject(array $user): User
- {
- return new User([
- 'id' => $user['openid'] ?? null,
- 'name' => $user['nickname'] ?? null,
- 'nickname' => $user['nickname'] ?? null,
- 'avatar' => $user['headimgurl'] ?? null,
- 'email' => null,
- ]);
- }
- /**
- * @param string $code
- *
- * @return array
- */
- protected function getTokenFields(string $code): array
- {
- if (!empty($this->component)) {
- return [
- 'appid' => $this->getClientId(),
- 'component_appid' => $this->component['id'],
- 'component_access_token' => $this->component['token'],
- 'code' => $code,
- 'grant_type' => 'authorization_code',
- ];
- }
- return [
- 'appid' => $this->getClientId(),
- 'secret' => $this->getClientSecret(),
- 'code' => $code,
- 'grant_type' => 'authorization_code',
- ];
- }
- /**
- * @param string $code
- *
- * @return \Psr\Http\Message\ResponseInterface
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- protected function getTokenFromCode(string $code): ResponseInterface
- {
- return $this->getHttpClient()->get($this->getTokenUrl(), [
- 'headers' => ['Accept' => 'application/json'],
- 'query' => $this->getTokenFields($code),
- ]);
- }
- protected function prepareForComponent(array $component)
- {
- $config = [];
- foreach ($component as $key => $value) {
- if (\is_callable($value)) {
- $value = \call_user_func($value, $this);
- }
- switch ($key) {
- case 'id':
- case 'app_id':
- case 'component_app_id':
- $config['id'] = $value;
- break;
- case 'token':
- case 'app_token':
- case 'access_token':
- case 'component_access_token':
- $config['token'] = $value;
- break;
- }
- }
- if (2 !== count($config)) {
- throw new InvalidArgumentException('Please check your config arguments is available.');
- }
- if (1 === count($this->scopes) && in_array('snsapi_login', $this->scopes)) {
- $this->scopes = ['snsapi_base'];
- }
- $this->component = $config;
- }
- }
|