diff --git a/README.md b/README.md index e25ea5a..cf11299 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ $socialite = new SocialiteManager($config); $url = $socialite->create('github')->redirect(); -return redirect($url); +return redirect($url); ``` `callback.php`: @@ -122,7 +122,7 @@ $config = [ 'client_secret' => 'your-app-secret', 'redirect' => 'http://localhost/socialite/callback.php', ], - + // 另外一个名字叫做 bar 的 github 应用 'bar' => [ 'provider' => 'github', // <-- provider name @@ -130,7 +130,7 @@ $config = [ 'client_secret' => 'your-app-secret', 'redirect' => 'http://localhost/socialite/callback.php', ], - + //... ]; @@ -159,7 +159,7 @@ $config = [ ]; $socialite = new SocialiteManager($config); - + $socialite->extend('myprovider', function(array $config) { return new MyCustomProvider($config); }); @@ -172,7 +172,7 @@ $app = $socialite->create('foo'); >👋🏻 你的自定义服务提供类必须实现`Overtrue\Socialite\Contracts\ProviderInterface` 接口 ```php -class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface +class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface { //... } @@ -208,8 +208,8 @@ $app = $socialite->create('foo'); $config = [ 'alipay' => [ // 这个键名还能像官方文档那样叫做 'app_id' - 'client_id' => 'your-app-id', - + 'client_id' => 'your-app-id', + // 请根据官方文档,在官方管理后台配置 RSA2 // 注意: 这是你自己的私钥 // 注意: 不允许私钥内容有其他字符 @@ -219,7 +219,7 @@ $config = [ // 确保这里的值与你在服务后台绑定的地址值一致 // 这个键名还能像官方文档那样叫做 'redirect_url' 'redirect' => 'http://localhost/socialite/callback.php', - + // 沙箱模式接入地址见 https://opendocs.alipay.com/open/220/105337#%E5%85%B3%E4%BA%8E%E6%B2%99%E7%AE%B1 'sandbox' => false, ] @@ -252,7 +252,7 @@ $config = [ // or 'app_id' 'client_id' => 'your app id', - // or 'app_secret' + // or 'app_secret' 'client_secret' => 'your app secret', // or 'redirect_url' @@ -361,7 +361,7 @@ $config = [ // or 'app_id' 'client_id' => 'your app id', - // or 'app_secret' + // or 'app_secret' 'client_secret' => 'your app secret', // or 'redirect_url' @@ -391,7 +391,7 @@ $config = [ // or 'app_id' 'client_id' => 'your app id', - // or 'app_secret' + // or 'app_secret' 'client_secret' => 'your app secret', // or 'redirect_url' @@ -413,7 +413,7 @@ $larkDriver->withDefaultMode()->withAppTicket('app_ticket')->userFromCode('here ### [淘宝](https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search) -其他配置与其他平台的一样,你能选择你想要展示的重定向页面类型通过使用 `withView()` +其他配置与其他平台的一样,你能选择你想要展示的重定向页面类型通过使用 `withView()` ```php $authUrl = $socialite->create('taobao')->withView('wap')->redirect(); @@ -456,7 +456,7 @@ $authUrl = $socialite->create('taobao')->withView('wap')->redirect(); ```php $config = [ 'coding' => [ - 'team_url' => 'https://{your-team}.coding.net', + 'team_url' => 'https://{your-team}.coding.net', 'client_id' => 'your app id', 'client_secret' => 'your app secret', 'redirect' => 'redirect URL', @@ -464,6 +464,24 @@ $config = [ ]; ``` +### [PayPal](https://developer.paypal.com/docs/log-in-with-paypal/) + +您可能需要设置responseType,可以使用`withResponseType`函数进行设置,默认是`code` 还可以设置为`id_token` 或`code` & `id_token` + +> https://developer.paypal.com/docs/log-in-with-paypal/integrate/generate-button/ + + +```php +$config = [ + 'paypal' => [ + 'client_id' => 'AT******************', + 'client_secret' => 'EK**************', + 'sandbox' => false, + 'redirect_url'=>"nativexo://paypalpay", + ], +]; +``` + ## 其他一些技巧 ### Scopes @@ -498,7 +516,7 @@ $socialite->withRedirectUrl($url)->redirect(); ```php create('github')->withState($state)->redirect(); -return redirect($url); +return redirect($url); ``` ### 检验回调的 `state` @@ -520,10 +538,10 @@ return redirect($url); ```php query('state'); $code = request()->query('code'); - + // Check the state received with current session id if ($state != hash('sha256', session_id())) { exit('State does not match!'); @@ -596,7 +614,7 @@ mixed $user->getId(); ?string $user->getEmail(); ?string $user->getAvatar(); ?string $user->getRaw(); -?string $user->getAccessToken(); +?string $user->getAccessToken(); ?string $user->getRefreshToken(); ?int $user->getExpiresIn(); ?array $user->getTokenResponse(); @@ -648,6 +666,7 @@ $user = $socialite->userFromToken($accessToken); - [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093) - [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/) - [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/) +- [PayPal - OAuth文档](https://developer.paypal.com/docs/log-in-with-paypal/) diff --git a/README_EN.md b/README_EN.md index c653435..0363c05 100644 --- a/README_EN.md +++ b/README_EN.md @@ -443,6 +443,26 @@ $config = [ ]; ``` +### [PayPal](https://developer.paypal.com/docs/log-in-with-paypal/) + +You may need to set the responseType, which can be set using the `withResponseType` function. The default is `code` and can also be set to `id_token` or `code` & `id_token` + +> https://developer.paypal.com/docs/log-in-with-paypal/integrate/generate-button/ + + +```php +$config = [ + 'paypal' => [ + 'client_id' => 'AT******************', + 'client_secret' => 'EK**************', + 'sandbox' => false, + 'redirect_url'=>"nativexo://paypalpay", + ], +]; +``` + + + ## Some Skill ### Scopes @@ -627,6 +647,7 @@ $user = $socialite->userFromToken($accessToken); - [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093) - [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/) - [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/) +- [PayPal - OAuth文档](https://developer.paypal.com/docs/log-in-with-paypal/) [![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me.svg?raw=true)](https://github.com/sponsors/overtrue) diff --git a/src/Providers/PayPal.php b/src/Providers/PayPal.php new file mode 100644 index 0000000..3ee65aa --- /dev/null +++ b/src/Providers/PayPal.php @@ -0,0 +1,169 @@ +sandbox = (bool)$this->config->get('sandbox', false); + if ($this->sandbox) { + $this->authUrl = 'https://www.sandbox.paypal.com/signin/authorize'; + $this->tokenURL = 'https://api-m.sandbox.paypal.com/v1/oauth2/token'; + $this->userinfoURL = 'https://api-m.sandbox.paypal.com/v1/identity/openidconnect/userinfo'; + } + } + + /** + * @param string|null $responseType + * @return $this + * @see https://developer.paypal.com/docs/log-in-with-paypal/integrate/generate-button/ + */ + public function withResponseType(?string $responseType) + { + $this->responseType = $responseType; + return $this; + } + + protected function getAuthUrl(): string + { + return $this->buildAuthUrlFromBase($this->authUrl); + } + + protected function getCodeFields(): array + { + $fields = \array_merge( + [ + 'flowEntry' => $this->flowEntry, + Contracts\RFC6749_ABNF_CLIENT_ID => $this->getClientId(), + Contracts\RFC6749_ABNF_RESPONSE_TYPE => $this->responseType, + Contracts\RFC6749_ABNF_SCOPE => $this->formatScopes($this->scopes, $this->scopeSeparator), + Contracts\RFC6749_ABNF_REDIRECT_URI => $this->redirectUrl, + ], + $this->parameters + ); + + if ($this->state) { + $fields[Contracts\RFC6749_ABNF_STATE] = $this->state; + } + return $fields; + } + + + protected function getTokenUrl(): string + { + return $this->tokenURL; + } + + /** + * @param string $code + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @see https://developer.paypal.com/docs/log-in-with-paypal/integrate/#link-getaccesstoken + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->post( + $this->getTokenUrl(), + [ + 'form_params' => [ + Contracts\RFC6749_ABNF_GRANT_TYPE => Contracts\RFC6749_ABNF_AUTHORATION_CODE, + Contracts\RFC6749_ABNF_CODE => $code, + ], + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret())), + ], + ] + ); + return $this->normalizeAccessTokenResponse((string)$response->getBody()); + } + + /** + * @param string $refreshToken + * @return mixed + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @see https://developer.paypal.com/docs/log-in-with-paypal/integrate/#link-exchangerefreshtokenforaccesstoken + */ + public function refreshToken(string $refreshToken): mixed + { + $response = $this->getHttpClient()->post( + $this->getTokenUrl(), + [ + 'form_params' => [ + Contracts\RFC6749_ABNF_GRANT_TYPE => Contracts\RFC6749_ABNF_REFRESH_TOKEN, + Contracts\RFC6749_ABNF_REFRESH_TOKEN => $refreshToken, + ], + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret())), + ], + ] + ); + return $this->normalizeAccessTokenResponse((string)$response->getBody()); + } + + /** + * @param string $token + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @see https://developer.paypal.com/docs/api/identity/v1/#userinfo_get + */ + protected function getUserByToken(string $token): array + { + $response = $this->getHttpClient()->get( + $this->userinfoURL, + [ + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Authorization' => 'Bearer ' . $token, + ], + ] + ); + return $this->fromJsonBody($response); + } + + #[Pure] + protected function mapUserToObject(array $user): Contracts\UserInterface + { + $user[Contracts\ABNF_ID] = $user['user_id'] ?? null; + $user[Contracts\ABNF_NICKNAME] = $user['given_name'] ?? $user['family_name'] ?? $user['middle_name'] ?? null; + $user[Contracts\ABNF_NAME] = $user['name'] ?? ''; + $user[Contracts\ABNF_EMAIL] = $user[Contracts\ABNF_EMAIL] ?? null; + $user[Contracts\ABNF_AVATAR] = $user['picture'] ?? null; + return new User($user); + } +} diff --git a/src/SocialiteManager.php b/src/SocialiteManager.php index f804d2b..4fc1f7f 100644 --- a/src/SocialiteManager.php +++ b/src/SocialiteManager.php @@ -40,6 +40,7 @@ class SocialiteManager implements Contracts\FactoryInterface Providers\WeWork::NAME => Providers\WeWork::class, Providers\Weibo::NAME => Providers\Weibo::class, Providers\XiGua::NAME => Providers\XiGua::class, + Providers\PayPal::NAME => Providers\PayPal::class, ]; #[Pure]