composer package updates
[openemr.git] / vendor / stripe / stripe-php / lib / ApiRequestor.php
blob4a147d01adb3607f530b1e7c7bd98f2e9d15ee3b
1 <?php
3 namespace Stripe;
5 /**
6 * Class ApiRequestor
8 * @package Stripe
9 */
10 class ApiRequestor
12 /**
13 * @var string|null
15 private $_apiKey;
17 /**
18 * @var string
20 private $_apiBase;
22 /**
23 * @var HttpClient\ClientInterface
25 private static $_httpClient;
27 /**
28 * ApiRequestor constructor.
30 * @param string|null $apiKey
31 * @param string|null $apiBase
33 public function __construct($apiKey = null, $apiBase = null)
35 $this->_apiKey = $apiKey;
36 if (!$apiBase) {
37 $apiBase = Stripe::$apiBase;
39 $this->_apiBase = $apiBase;
42 /**
43 * @static
45 * @param ApiResource|bool|array|mixed $d
47 * @return ApiResource|array|string|mixed
49 private static function _encodeObjects($d)
51 if ($d instanceof ApiResource) {
52 return Util\Util::utf8($d->id);
53 } elseif ($d === true) {
54 return 'true';
55 } elseif ($d === false) {
56 return 'false';
57 } elseif (is_array($d)) {
58 $res = [];
59 foreach ($d as $k => $v) {
60 $res[$k] = self::_encodeObjects($v);
62 return $res;
63 } else {
64 return Util\Util::utf8($d);
68 /**
69 * @param string $method
70 * @param string $url
71 * @param array|null $params
72 * @param array|null $headers
74 * @return array An array whose first element is an API response and second
75 * element is the API key used to make the request.
76 * @throws Error\Api
77 * @throws Error\Authentication
78 * @throws Error\Card
79 * @throws Error\InvalidRequest
80 * @throws Error\OAuth\InvalidClient
81 * @throws Error\OAuth\InvalidGrant
82 * @throws Error\OAuth\InvalidRequest
83 * @throws Error\OAuth\InvalidScope
84 * @throws Error\OAuth\UnsupportedGrantType
85 * @throws Error\OAuth\UnsupportedResponseType
86 * @throws Error\Permission
87 * @throws Error\RateLimit
88 * @throws Error\Idempotency
89 * @throws Error\ApiConnection
91 public function request($method, $url, $params = null, $headers = null)
93 $params = $params ?: [];
94 $headers = $headers ?: [];
95 list($rbody, $rcode, $rheaders, $myApiKey) =
96 $this->_requestRaw($method, $url, $params, $headers);
97 $json = $this->_interpretResponse($rbody, $rcode, $rheaders);
98 $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
99 return [$resp, $myApiKey];
103 * @param string $rbody A JSON string.
104 * @param int $rcode
105 * @param array $rheaders
106 * @param array $resp
108 * @throws Error\InvalidRequest if the error is caused by the user.
109 * @throws Error\Authentication if the error is caused by a lack of
110 * permissions.
111 * @throws Error\Permission if the error is caused by insufficient
112 * permissions.
113 * @throws Error\Card if the error is the error code is 402 (payment
114 * required)
115 * @throws Error\InvalidRequest if the error is caused by the user.
116 * @throws Error\Idempotency if the error is caused by an idempotency key.
117 * @throws Error\OAuth\InvalidClient
118 * @throws Error\OAuth\InvalidGrant
119 * @throws Error\OAuth\InvalidRequest
120 * @throws Error\OAuth\InvalidScope
121 * @throws Error\OAuth\UnsupportedGrantType
122 * @throws Error\OAuth\UnsupportedResponseType
123 * @throws Error\Permission if the error is caused by insufficient
124 * permissions.
125 * @throws Error\RateLimit if the error is caused by too many requests
126 * hitting the API.
127 * @throws Error\Api otherwise.
129 public function handleErrorResponse($rbody, $rcode, $rheaders, $resp)
131 if (!is_array($resp) || !isset($resp['error'])) {
132 $msg = "Invalid response object from API: $rbody "
133 . "(HTTP response code was $rcode)";
134 throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders);
137 $errorData = $resp['error'];
139 $error = null;
140 if (is_string($errorData)) {
141 $error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData);
143 if (!$error) {
144 $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData);
147 throw $error;
151 * @static
153 * @param string $rbody
154 * @param int $rcode
155 * @param array $rheaders
156 * @param array $resp
157 * @param array $errorData
159 * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api
161 private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData)
163 $msg = isset($errorData['message']) ? $errorData['message'] : null;
164 $param = isset($errorData['param']) ? $errorData['param'] : null;
165 $code = isset($errorData['code']) ? $errorData['code'] : null;
166 $type = isset($errorData['type']) ? $errorData['type'] : null;
168 switch ($rcode) {
169 case 400:
170 // 'rate_limit' code is deprecated, but left here for backwards compatibility
171 // for API versions earlier than 2015-09-08
172 if ($code == 'rate_limit') {
173 return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders);
175 if ($type == 'idempotency_error') {
176 return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders);
179 // intentional fall-through
180 case 404:
181 return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders);
182 case 401:
183 return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders);
184 case 402:
185 return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders);
186 case 403:
187 return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders);
188 case 429:
189 return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders);
190 default:
191 return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders);
196 * @static
198 * @param string|bool $rbody
199 * @param int $rcode
200 * @param array $rheaders
201 * @param array $resp
202 * @param string $errorCode
204 * @return null|Error\OAuth\InvalidClient|Error\OAuth\InvalidGrant|Error\OAuth\InvalidRequest|Error\OAuth\InvalidScope|Error\OAuth\UnsupportedGrantType|Error\OAuth\UnsupportedResponseType
206 private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode)
208 $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
210 switch ($errorCode) {
211 case 'invalid_client':
212 return new Error\OAuth\InvalidClient($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
213 case 'invalid_grant':
214 return new Error\OAuth\InvalidGrant($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
215 case 'invalid_request':
216 return new Error\OAuth\InvalidRequest($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
217 case 'invalid_scope':
218 return new Error\OAuth\InvalidScope($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
219 case 'unsupported_grant_type':
220 return new Error\OAuth\UnsupportedGrantType($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
221 case 'unsupported_response_type':
222 return new Error\OAuth\UnsupportedResponseType($errorCode, $description, $rcode, $rbody, $resp, $rheaders);
225 return null;
229 * @static
231 * @param null|array $appInfo
233 * @return null|string
235 private static function _formatAppInfo($appInfo)
237 if ($appInfo !== null) {
238 $string = $appInfo['name'];
239 if ($appInfo['version'] !== null) {
240 $string .= '/' . $appInfo['version'];
242 if ($appInfo['url'] !== null) {
243 $string .= ' (' . $appInfo['url'] . ')';
245 return $string;
246 } else {
247 return null;
252 * @static
254 * @param string $apiKey
255 * @param null $clientInfo
257 * @return array
259 private static function _defaultHeaders($apiKey, $clientInfo = null)
261 $uaString = 'Stripe/v1 PhpBindings/' . Stripe::VERSION;
263 $langVersion = phpversion();
264 $uname = php_uname();
266 $appInfo = Stripe::getAppInfo();
267 $ua = [
268 'bindings_version' => Stripe::VERSION,
269 'lang' => 'php',
270 'lang_version' => $langVersion,
271 'publisher' => 'stripe',
272 'uname' => $uname,
274 if ($clientInfo) {
275 $ua = array_merge($clientInfo, $ua);
277 if ($appInfo !== null) {
278 $uaString .= ' ' . self::_formatAppInfo($appInfo);
279 $ua['application'] = $appInfo;
282 $defaultHeaders = [
283 'X-Stripe-Client-User-Agent' => json_encode($ua),
284 'User-Agent' => $uaString,
285 'Authorization' => 'Bearer ' . $apiKey,
287 return $defaultHeaders;
291 * @param string $method
292 * @param string $url
293 * @param array $params
294 * @param array $headers
296 * @return array
297 * @throws Error\Api
298 * @throws Error\ApiConnection
299 * @throws Error\Authentication
301 private function _requestRaw($method, $url, $params, $headers)
303 $myApiKey = $this->_apiKey;
304 if (!$myApiKey) {
305 $myApiKey = Stripe::$apiKey;
308 if (!$myApiKey) {
309 $msg = 'No API key provided. (HINT: set your API key using '
310 . '"Stripe::setApiKey(<API-KEY>)". You can generate API keys from '
311 . 'the Stripe web interface. See https://stripe.com/api for '
312 . 'details, or email support@stripe.com if you have any questions.';
313 throw new Error\Authentication($msg);
316 // Clients can supply arbitrary additional keys to be included in the
317 // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
318 // method
319 $clientUAInfo = null;
320 if (method_exists($this->httpClient(), 'getUserAgentInfo')) {
321 $clientUAInfo = $this->httpClient()->getUserAgentInfo();
324 $absUrl = $this->_apiBase.$url;
325 $params = self::_encodeObjects($params);
326 $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo);
327 if (Stripe::$apiVersion) {
328 $defaultHeaders['Stripe-Version'] = Stripe::$apiVersion;
331 if (Stripe::$accountId) {
332 $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
335 $hasFile = false;
336 $hasCurlFile = class_exists('\CURLFile', false);
337 foreach ($params as $k => $v) {
338 if (is_resource($v)) {
339 $hasFile = true;
340 $params[$k] = self::_processResourceParam($v, $hasCurlFile);
341 } elseif ($hasCurlFile && $v instanceof \CURLFile) {
342 $hasFile = true;
346 if ($hasFile) {
347 $defaultHeaders['Content-Type'] = 'multipart/form-data';
348 } else {
349 $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
352 $combinedHeaders = array_merge($defaultHeaders, $headers);
353 $rawHeaders = [];
355 foreach ($combinedHeaders as $header => $value) {
356 $rawHeaders[] = $header . ': ' . $value;
359 list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
360 $method,
361 $absUrl,
362 $rawHeaders,
363 $params,
364 $hasFile
366 return [$rbody, $rcode, $rheaders, $myApiKey];
370 * @param resource $resource
371 * @param bool $hasCurlFile
373 * @return \CURLFile|string
374 * @throws Error\Api
376 private function _processResourceParam($resource, $hasCurlFile)
378 if (get_resource_type($resource) !== 'stream') {
379 throw new Error\Api(
380 'Attempted to upload a resource that is not a stream'
384 $metaData = stream_get_meta_data($resource);
385 if ($metaData['wrapper_type'] !== 'plainfile') {
386 throw new Error\Api(
387 'Only plainfile resource streams are supported'
391 if ($hasCurlFile) {
392 // We don't have the filename or mimetype, but the API doesn't care
393 return new \CURLFile($metaData['uri']);
394 } else {
395 return '@'.$metaData['uri'];
400 * @param string $rbody
401 * @param int $rcode
402 * @param array $rheaders
404 * @return mixed
405 * @throws Error\Api
406 * @throws Error\Authentication
407 * @throws Error\Card
408 * @throws Error\InvalidRequest
409 * @throws Error\OAuth\InvalidClient
410 * @throws Error\OAuth\InvalidGrant
411 * @throws Error\OAuth\InvalidRequest
412 * @throws Error\OAuth\InvalidScope
413 * @throws Error\OAuth\UnsupportedGrantType
414 * @throws Error\OAuth\UnsupportedResponseType
415 * @throws Error\Permission
416 * @throws Error\RateLimit
417 * @throws Error\Idempotency
419 private function _interpretResponse($rbody, $rcode, $rheaders)
421 $resp = json_decode($rbody, true);
422 $jsonError = json_last_error();
423 if ($resp === null && $jsonError !== JSON_ERROR_NONE) {
424 $msg = "Invalid response body from API: $rbody "
425 . "(HTTP response code was $rcode, json_last_error() was $jsonError)";
426 throw new Error\Api($msg, $rcode, $rbody);
429 if ($rcode < 200 || $rcode >= 300) {
430 $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
432 return $resp;
436 * @static
438 * @param HttpClient\ClientInterface $client
440 public static function setHttpClient($client)
442 self::$_httpClient = $client;
446 * @return HttpClient\ClientInterface
448 private function httpClient()
450 if (!self::$_httpClient) {
451 self::$_httpClient = HttpClient\CurlClient::instance();
453 return self::$_httpClient;