PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.15.2
GiveWP – Donation Plugin and Fundraising Platform v4.15.2
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / vendor / stripe / stripe-php / lib / ApiRequestor.php

ApiRequestor.php in GiveWP – Donation Plugin and Fundraising Platform 4.15.2, at vendor/stripe/stripe-php/lib/ApiRequestor.php

620 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Stripe;
4
5 /**
6 * Class ApiRequestor.
7 */
8 class ApiRequestor
9 {
10 /**
11 * @var null|string
12 */
13 private $_apiKey;
14
15 /**
16 * @var string
17 */
18 private $_apiBase;
19
20 /**
21 * @var HttpClient\ClientInterface
22 */
23 private static $_httpClient;
24 /**
25 * @var HttpClient\StreamingClientInterface
26 */
27 private static $_streamingHttpClient;
28
29 /**
30 * @var RequestTelemetry
31 */
32 private static $requestTelemetry;
33
34 private static $OPTIONS_KEYS = ['api_key', 'idempotency_key', 'stripe_account', 'stripe_version', 'api_base'];
35
36 /**
37 * ApiRequestor constructor.
38 *
39 * @param null|string $apiKey
40 * @param null|string $apiBase
41 */
42 public function __construct($apiKey = null, $apiBase = null)
43 {
44 $this->_apiKey = $apiKey;
45 if (!$apiBase) {
46 $apiBase = Stripe::$apiBase;
47 }
48 $this->_apiBase = $apiBase;
49 }
50
51 /**
52 * Creates a telemetry json blob for use in 'X-Stripe-Client-Telemetry' headers.
53 *
54 * @static
55 *
56 * @param RequestTelemetry $requestTelemetry
57 *
58 * @return string
59 */
60 private static function _telemetryJson($requestTelemetry)
61 {
62 $payload = [
63 'last_request_metrics' => [
64 'request_id' => $requestTelemetry->requestId,
65 'request_duration_ms' => $requestTelemetry->requestDuration,
66 ],
67 ];
68
69 $result = \json_encode($payload);
70 if (false !== $result) {
71 return $result;
72 }
73 Stripe::getLogger()->error('Serializing telemetry payload failed!');
74
75 return '{}';
76 }
77
78 /**
79 * @static
80 *
81 * @param ApiResource|array|bool|mixed $d
82 *
83 * @return ApiResource|array|mixed|string
84 */
85 private static function _encodeObjects($d)
86 {
87 if ($d instanceof ApiResource) {
88 return Util\Util::utf8($d->id);
89 }
90 if (true === $d) {
91 return 'true';
92 }
93 if (false === $d) {
94 return 'false';
95 }
96 if (\is_array($d)) {
97 $res = [];
98 foreach ($d as $k => $v) {
99 $res[$k] = self::_encodeObjects($v);
100 }
101
102 return $res;
103 }
104
105 return Util\Util::utf8($d);
106 }
107
108 /**
109 * @param string $method
110 * @param string $url
111 * @param null|array $params
112 * @param null|array $headers
113 *
114 * @throws Exception\ApiErrorException
115 *
116 * @return array tuple containing (ApiReponse, API key)
117 */
118 public function request($method, $url, $params = null, $headers = null)
119 {
120 $params = $params ?: [];
121 $headers = $headers ?: [];
122 list($rbody, $rcode, $rheaders, $myApiKey) =
123 $this->_requestRaw($method, $url, $params, $headers);
124 $json = $this->_interpretResponse($rbody, $rcode, $rheaders);
125 $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
126
127 return [$resp, $myApiKey];
128 }
129
130 /**
131 * @param string $method
132 * @param string $url
133 * @param callable $readBodyChunkCallable
134 * @param null|array $params
135 * @param null|array $headers
136 *
137 * @throws Exception\ApiErrorException
138 */
139 public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null)
140 {
141 $params = $params ?: [];
142 $headers = $headers ?: [];
143 list($rbody, $rcode, $rheaders, $myApiKey) =
144 $this->_requestRawStreaming($method, $url, $params, $headers, $readBodyChunkCallable);
145 if ($rcode >= 300) {
146 $this->_interpretResponse($rbody, $rcode, $rheaders);
147 }
148 }
149
150 /**
151 * @param string $rbody a JSON string
152 * @param int $rcode
153 * @param array $rheaders
154 * @param array $resp
155 *
156 * @throws Exception\UnexpectedValueException
157 * @throws Exception\ApiErrorException
158 */
159 public function handleErrorResponse($rbody, $rcode, $rheaders, $resp)
160 {
161 if (!\is_array($resp) || !isset($resp['error'])) {
162 $msg = "Invalid response object from API: {$rbody} "
163 . "(HTTP response code was {$rcode})";
164
165 throw new Exception\UnexpectedValueException($msg);
166 }
167
168 $errorData = $resp['error'];
169
170 $error = null;
171 if (\is_string($errorData)) {
172 $error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData);
173 }
174 if (!$error) {
175 $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData);
176 }
177
178 throw $error;
179 }
180
181 /**
182 * @static
183 *
184 * @param string $rbody
185 * @param int $rcode
186 * @param array $rheaders
187 * @param array $resp
188 * @param array $errorData
189 *
190 * @return Exception\ApiErrorException
191 */
192 private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData)
193 {
194 $msg = isset($errorData['message']) ? $errorData['message'] : null;
195 $param = isset($errorData['param']) ? $errorData['param'] : null;
196 $code = isset($errorData['code']) ? $errorData['code'] : null;
197 $type = isset($errorData['type']) ? $errorData['type'] : null;
198 $declineCode = isset($errorData['decline_code']) ? $errorData['decline_code'] : null;
199
200 switch ($rcode) {
201 case 400:
202 // 'rate_limit' code is deprecated, but left here for backwards compatibility
203 // for API versions earlier than 2015-09-08
204 if ('rate_limit' === $code) {
205 return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
206 }
207 if ('idempotency_error' === $type) {
208 return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
209 }
210
211 // no break
212 case 404:
213 return Exception\InvalidRequestException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
214
215 case 401:
216 return Exception\AuthenticationException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
217
218 case 402:
219 return Exception\CardException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $declineCode, $param);
220
221 case 403:
222 return Exception\PermissionException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
223
224 case 429:
225 return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
226
227 default:
228 return Exception\UnknownApiErrorException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
229 }
230 }
231
232 /**
233 * @static
234 *
235 * @param bool|string $rbody
236 * @param int $rcode
237 * @param array $rheaders
238 * @param array $resp
239 * @param string $errorCode
240 *
241 * @return Exception\OAuth\OAuthErrorException
242 */
243 private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode)
244 {
245 $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
246
247 switch ($errorCode) {
248 case 'invalid_client':
249 return Exception\OAuth\InvalidClientException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
250
251 case 'invalid_grant':
252 return Exception\OAuth\InvalidGrantException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
253
254 case 'invalid_request':
255 return Exception\OAuth\InvalidRequestException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
256
257 case 'invalid_scope':
258 return Exception\OAuth\InvalidScopeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
259
260 case 'unsupported_grant_type':
261 return Exception\OAuth\UnsupportedGrantTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
262
263 case 'unsupported_response_type':
264 return Exception\OAuth\UnsupportedResponseTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
265
266 default:
267 return Exception\OAuth\UnknownOAuthErrorException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
268 }
269 }
270
271 /**
272 * @static
273 *
274 * @param null|array $appInfo
275 *
276 * @return null|string
277 */
278 private static function _formatAppInfo($appInfo)
279 {
280 if (null !== $appInfo) {
281 $string = $appInfo['name'];
282 if (null !== $appInfo['version']) {
283 $string .= '/' . $appInfo['version'];
284 }
285 if (null !== $appInfo['url']) {
286 $string .= ' (' . $appInfo['url'] . ')';
287 }
288
289 return $string;
290 }
291
292 return null;
293 }
294
295 /**
296 * @static
297 *
298 * @param string $disabledFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions')
299 * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled
300 * @param mixed $disableFunctionsOutput
301 *
302 * @return bool
303 */
304 private static function _isDisabled($disableFunctionsOutput, $functionName)
305 {
306 $disabledFunctions = \explode(',', $disableFunctionsOutput);
307 foreach ($disabledFunctions as $disabledFunction) {
308 if (\trim($disabledFunction) === $functionName) {
309 return true;
310 }
311 }
312
313 return false;
314 }
315
316 /**
317 * @static
318 *
319 * @param string $apiKey
320 * @param null $clientInfo
321 *
322 * @return array
323 */
324 private static function _defaultHeaders($apiKey, $clientInfo = null)
325 {
326 $uaString = 'Stripe/v1 PhpBindings/' . Stripe::VERSION;
327
328 $langVersion = \PHP_VERSION;
329 $uname_disabled = static::_isDisabled(\ini_get('disable_functions'), 'php_uname');
330 $uname = $uname_disabled ? '(disabled)' : \php_uname();
331
332 $appInfo = Stripe::getAppInfo();
333 $ua = [
334 'bindings_version' => Stripe::VERSION,
335 'lang' => 'php',
336 'lang_version' => $langVersion,
337 'publisher' => 'stripe',
338 'uname' => $uname,
339 ];
340 if ($clientInfo) {
341 $ua = \array_merge($clientInfo, $ua);
342 }
343 if (null !== $appInfo) {
344 $uaString .= ' ' . self::_formatAppInfo($appInfo);
345 $ua['application'] = $appInfo;
346 }
347
348 return [
349 'X-Stripe-Client-User-Agent' => \json_encode($ua),
350 'User-Agent' => $uaString,
351 'Authorization' => 'Bearer ' . $apiKey,
352 ];
353 }
354
355 private function _prepareRequest($method, $url, $params, $headers)
356 {
357 $myApiKey = $this->_apiKey;
358 if (!$myApiKey) {
359 $myApiKey = Stripe::$apiKey;
360 }
361
362 if (!$myApiKey) {
363 $msg = 'No API key provided. (HINT: set your API key using '
364 . '"Stripe::setApiKey(<API-KEY>)". You can generate API keys from '
365 . 'the Stripe web interface. See https://stripe.com/api for '
366 . 'details, or email support@stripe.com if you have any questions.';
367
368 throw new Exception\AuthenticationException($msg);
369 }
370
371 // Clients can supply arbitrary additional keys to be included in the
372 // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
373 // method
374 $clientUAInfo = null;
375 if (\method_exists($this->httpClient(), 'getUserAgentInfo')) {
376 $clientUAInfo = $this->httpClient()->getUserAgentInfo();
377 }
378
379 if ($params && \is_array($params)) {
380 $optionKeysInParams = \array_filter(
381 static::$OPTIONS_KEYS,
382 function ($key) use ($params) {
383 return \array_key_exists($key, $params);
384 }
385 );
386 if (\count($optionKeysInParams) > 0) {
387 $message = \sprintf('Options found in $params: %s. Options should '
388 . 'be passed in their own array after $params. (HINT: pass an '
389 . 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams));
390 \trigger_error($message, \E_USER_WARNING);
391 }
392 }
393
394 $absUrl = $this->_apiBase . $url;
395 $params = self::_encodeObjects($params);
396 $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo);
397 if (Stripe::$apiVersion) {
398 $defaultHeaders['Stripe-Version'] = Stripe::$apiVersion;
399 }
400
401 if (Stripe::$accountId) {
402 $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
403 }
404
405 if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
406 $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
407 }
408
409 $hasFile = false;
410 foreach ($params as $k => $v) {
411 if (\is_resource($v)) {
412 $hasFile = true;
413 $params[$k] = self::_processResourceParam($v);
414 } elseif ($v instanceof \CURLFile) {
415 $hasFile = true;
416 }
417 }
418
419 if ($hasFile) {
420 $defaultHeaders['Content-Type'] = 'multipart/form-data';
421 } else {
422 $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
423 }
424
425 $combinedHeaders = \array_merge($defaultHeaders, $headers);
426 $rawHeaders = [];
427
428 foreach ($combinedHeaders as $header => $value) {
429 $rawHeaders[] = $header . ': ' . $value;
430 }
431
432 return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey];
433 }
434
435 /**
436 * @param string $method
437 * @param string $url
438 * @param array $params
439 * @param array $headers
440 *
441 * @throws Exception\AuthenticationException
442 * @throws Exception\ApiConnectionException
443 *
444 * @return array
445 */
446 private function _requestRaw($method, $url, $params, $headers)
447 {
448 list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers);
449
450 $requestStartMs = Util\Util::currentTimeMillis();
451
452 list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
453 $method,
454 $absUrl,
455 $rawHeaders,
456 $params,
457 $hasFile
458 );
459
460 if (isset($rheaders['request-id'])
461 && \is_string($rheaders['request-id'])
462 && '' !== $rheaders['request-id']) {
463 self::$requestTelemetry = new RequestTelemetry(
464 $rheaders['request-id'],
465 Util\Util::currentTimeMillis() - $requestStartMs
466 );
467 }
468
469 return [$rbody, $rcode, $rheaders, $myApiKey];
470 }
471
472 /**
473 * @param string $method
474 * @param string $url
475 * @param array $params
476 * @param array $headers
477 * @param callable $readBodyChunk
478 * @param mixed $readBodyChunkCallable
479 *
480 * @throws Exception\AuthenticationException
481 * @throws Exception\ApiConnectionException
482 *
483 * @return array
484 */
485 private function _requestRawStreaming($method, $url, $params, $headers, $readBodyChunkCallable)
486 {
487 list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers);
488
489 $requestStartMs = Util\Util::currentTimeMillis();
490
491 list($rbody, $rcode, $rheaders) = $this->streamingHttpClient()->requestStream(
492 $method,
493 $absUrl,
494 $rawHeaders,
495 $params,
496 $hasFile,
497 $readBodyChunkCallable
498 );
499
500 if (isset($rheaders['request-id'])
501 && \is_string($rheaders['request-id'])
502 && '' !== $rheaders['request-id']) {
503 self::$requestTelemetry = new RequestTelemetry(
504 $rheaders['request-id'],
505 Util\Util::currentTimeMillis() - $requestStartMs
506 );
507 }
508
509 return [$rbody, $rcode, $rheaders, $myApiKey];
510 }
511
512 /**
513 * @param resource $resource
514 *
515 * @throws Exception\InvalidArgumentException
516 *
517 * @return \CURLFile|string
518 */
519 private function _processResourceParam($resource)
520 {
521 if ('stream' !== \get_resource_type($resource)) {
522 throw new Exception\InvalidArgumentException(
523 'Attempted to upload a resource that is not a stream'
524 );
525 }
526
527 $metaData = \stream_get_meta_data($resource);
528 if ('plainfile' !== $metaData['wrapper_type']) {
529 throw new Exception\InvalidArgumentException(
530 'Only plainfile resource streams are supported'
531 );
532 }
533
534 // We don't have the filename or mimetype, but the API doesn't care
535 return new \CURLFile($metaData['uri']);
536 }
537
538 /**
539 * @param string $rbody
540 * @param int $rcode
541 * @param array $rheaders
542 *
543 * @throws Exception\UnexpectedValueException
544 * @throws Exception\ApiErrorException
545 *
546 * @return array
547 */
548 private function _interpretResponse($rbody, $rcode, $rheaders)
549 {
550 $resp = \json_decode($rbody, true);
551 $jsonError = \json_last_error();
552 if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
553 $msg = "Invalid response body from API: {$rbody} "
554 . "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
555
556 throw new Exception\UnexpectedValueException($msg, $rcode);
557 }
558
559 if ($rcode < 200 || $rcode >= 300) {
560 $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
561 }
562
563 return $resp;
564 }
565
566 /**
567 * @static
568 *
569 * @param HttpClient\ClientInterface $client
570 */
571 public static function setHttpClient($client)
572 {
573 self::$_httpClient = $client;
574 }
575
576 /**
577 * @static
578 *
579 * @param HttpClient\StreamingClientInterface $client
580 */
581 public static function setStreamingHttpClient($client)
582 {
583 self::$_streamingHttpClient = $client;
584 }
585
586 /**
587 * @static
588 *
589 * Resets any stateful telemetry data
590 */
591 public static function resetTelemetry()
592 {
593 self::$requestTelemetry = null;
594 }
595
596 /**
597 * @return HttpClient\ClientInterface
598 */
599 private function httpClient()
600 {
601 if (!self::$_httpClient) {
602 self::$_httpClient = HttpClient\CurlClient::instance();
603 }
604
605 return self::$_httpClient;
606 }
607
608 /**
609 * @return HttpClient\StreamingClientInterface
610 */
611 private function streamingHttpClient()
612 {
613 if (!self::$_streamingHttpClient) {
614 self::$_streamingHttpClient = HttpClient\CurlClient::instance();
615 }
616
617 return self::$_streamingHttpClient;
618 }
619 }
620