Passed
Push — main ( e868ec...9f95cc )
by smiley
12:00
created

PayPal::parseTokenResponse()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 39
rs 8.6666
cc 7
nc 6
nop 1
1
<?php
2
/**
3
 * Class PayPal
4
 *
5
 * @created      29.07.2019
6
 * @author       smiley <[email protected]>
7
 * @copyright    2019 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\OAuth\Providers;
12
13
use chillerlan\HTTP\Utils\MessageUtil;
14
use chillerlan\HTTP\Utils\QueryUtil;
15
use chillerlan\OAuth\Core\{AccessToken, ClientCredentials, CSRFToken, OAuth2Provider, ProviderException, TokenRefresh};
16
use Psr\Http\Message\ResponseInterface;
17
use function array_column;
18
use function base64_encode;
19
use function implode;
20
use function is_array;
21
use function json_decode;
22
use function sprintf;
23
use const PHP_QUERY_RFC1738;
24
25
/**
26
 * @see https://developer.paypal.com/docs/connect-with-paypal/integrate/
27
 */
28
class PayPal extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenRefresh{
29
30
	public const SCOPE_BASIC_AUTH     = 'openid';
31
	public const SCOPE_FULL_NAME      = 'profile';
32
	public const SCOPE_EMAIL          = 'email';
33
	public const SCOPE_ADDRESS        = 'address';
34
	public const SCOPE_ACCOUNT        = 'https://uri.paypal.com/services/paypalattributes';
35
36
	protected string  $accessTokenURL = 'https://api.paypal.com/v1/oauth2/token';
37
	protected string  $authURL        = 'https://www.paypal.com/connect';
38
	protected string  $apiURL         = 'https://api.paypal.com';
39
	protected ?string $applicationURL = 'https://developer.paypal.com/developer/applications/';
40
	protected ?string $apiDocs        = 'https://developer.paypal.com/docs/connect-with-paypal/reference/';
41
42
	protected array $defaultScopes    = [
43
		self::SCOPE_BASIC_AUTH,
44
		self::SCOPE_EMAIL,
45
	];
46
47
	/**
48
	 * @inheritDoc
49
	 */
50
	protected function parseTokenResponse(ResponseInterface $response):AccessToken{
51
		$data = json_decode(MessageUtil::decompress($response), true);
52
53
		if(!is_array($data)){
54
			throw new ProviderException('unable to parse token response');
55
		}
56
57
		if(isset($data['error'])){
58
			throw new ProviderException(sprintf('error retrieving access token: "%s"',  $data['error']));
59
		}
60
61
		// @codeCoverageIgnoreStart
62
		if(isset($data['name'], $data['message'])){
63
			$msg = sprintf('error retrieving access token: "%s" [%s]', $data['message'], $data['name']);
64
65
			if(isset($data['links']) && is_array($data['links'])){
66
				$msg .= "\n".implode("\n", array_column($data['links'], 'href'));
67
			}
68
69
			throw new ProviderException($msg);
70
		}
71
		// @codeCoverageIgnoreEnd
72
73
		if(!isset($data['access_token'])){
74
			throw new ProviderException('token missing');
75
		}
76
77
		$token = new AccessToken;
78
79
		$token->provider     = $this->serviceName;
80
		$token->accessToken  = $data['access_token'];
81
		$token->expires      = $data['expires_in'] ?? AccessToken::EOL_NEVER_EXPIRES;
82
		$token->refreshToken = $data['refresh_token'] ?? null;
83
84
		unset($data['expires_in'], $data['refresh_token'], $data['access_token']);
85
86
		$token->extraParams = $data;
87
88
		return $token;
89
	}
90
91
	/**
92
	 * @inheritDoc
93
	 */
94
	public function getAccessToken(string $code, string $state = null):AccessToken{
95
		$this->checkState($state); // we're an instance of CSRFToken
96
97
		$body = [
98
			'code'          => $code,
99
			'grant_type'    => 'authorization_code',
100
			'redirect_uri'  => $this->options->callbackURL,
101
		];
102
103
		$request = $this->requestFactory
104
			->createRequest('POST', $this->accessTokenURL)
105
			->withHeader('Content-Type', 'application/x-www-form-urlencoded')
106
			->withHeader('Accept-Encoding', 'identity')
107
			->withHeader('Authorization', 'Basic '.base64_encode($this->options->key.':'.$this->options->secret))
108
			->withBody($this->streamFactory->createStream(QueryUtil::build($body, PHP_QUERY_RFC1738)));
109
110
		$token = $this->parseTokenResponse($this->http->sendRequest($request));
111
112
		$this->storage->storeAccessToken($token, $this->serviceName);
113
114
		return $token;
115
	}
116
117
	/**
118
	 * @inheritDoc
119
	 */
120
	public function me():ResponseInterface{
121
		$response = $this->request('/v1/identity/oauth2/userinfo', ['schema' => 'paypalv1.1']);
122
		$status   = $response->getStatusCode();
123
124
		if($status === 200){
125
			return $response;
126
		}
127
128
		$json = MessageUtil::decodeJSON($response);
129
130
		if(isset($json->error, $json->error_description)){
131
			throw new ProviderException($json->error_description);
132
		}
133
134
		throw new ProviderException(sprintf('user info error error HTTP/%s', $status));
135
	}
136
137
}
138