PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / vendor / stripe / stripe-php / lib / ApiRequestor.php

ApiRequestor.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at vendor/stripe/stripe-php/lib/ApiRequestor.php

618 lines 18.6 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 $disableFunctionsOutput - 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 *
301 * @return bool
302 */
303 private static function _isDisabled($disableFunctionsOutput, $functionName)
304 {
305 $disabledFunctions = \explode(',', $disableFunctionsOutput);
306 foreach ($disabledFunctions as $disabledFunction) {
307 if (\trim($disabledFunction) === $functionName) {
308 return true;
309 }
310 }
311
312 return false;
313 }
314
315 /**
316 * @static
317 *
318 * @param string $apiKey
319 * @param null $clientInfo
320 *
321 * @return array
322 */
323 private static function _defaultHeaders($apiKey, $clientInfo = null)
324 {
325 $uaString = 'Stripe/v1 PhpBindings/' . Stripe::VERSION;
326
327 $langVersion = \PHP_VERSION;
328 $uname_disabled = static::_isDisabled(\ini_get('disable_functions'), 'php_uname');
329 $uname = $uname_disabled ? '(disabled)' : \php_uname();
330
331 $appInfo = Stripe::getAppInfo();
332 $ua = [
333 'bindings_version' => Stripe::VERSION,
334 'lang' => 'php',
335 'lang_version' => $langVersion,
336 'publisher' => 'stripe',
337 'uname' => $uname,
338 ];
339 if ($clientInfo) {
340 $ua = \array_merge($clientInfo, $ua);
341 }
342 if (null !== $appInfo) {
343 $uaString .= ' ' . self::_formatAppInfo($appInfo);
344 $ua['application'] = $appInfo;
345 }
346
347 return [
348 'X-Stripe-Client-User-Agent' => \json_encode($ua),
349 'User-Agent' => $uaString,
350 'Authorization' => 'Bearer ' . $apiKey,
351 ];
352 }
353
354 private function _prepareRequest($method, $url, $params, $headers)
355 {
356 $myApiKey = $this->_apiKey;
357 if (!$myApiKey) {
358 $myApiKey = Stripe::$apiKey;
359 }
360
361 if (!$myApiKey) {
362 $msg = 'No API key provided. (HINT: set your API key using '
363 . '"Stripe::setApiKey(<API-KEY>)". You can generate API keys from '
364 . 'the Stripe web interface. See https://stripe.com/api for '
365 . 'details, or email [email protected] if you have any questions.';
366
367 throw new Exception\AuthenticationException($msg);
368 }
369
370 // Clients can supply arbitrary additional keys to be included in the
371 // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
372 // method
373 $clientUAInfo = null;
374 if (\method_exists($this->httpClient(), 'getUserAgentInfo')) {
375 $clientUAInfo = $this->httpClient()->getUserAgentInfo();
376 }
377
378 if ($params && \is_array($params)) {
379 $optionKeysInParams = \array_filter(
380 static::$OPTIONS_KEYS,
381 function ($key) use ($params) {
382 return \array_key_exists($key, $params);
383 }
384 );
385 if (\count($optionKeysInParams) > 0) {
386 $message = \sprintf('Options found in $params: %s. Options should '
387 . 'be passed in their own array after $params. (HINT: pass an '
388 . 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams));
389 \trigger_error($message, \E_USER_WARNING);
390 }
391 }
392
393 $absUrl = $this->_apiBase . $url;
394 $params = self::_encodeObjects($params);
395 $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo);
396 if (Stripe::$apiVersion) {
397 $defaultHeaders['Stripe-Version'] = Stripe::$apiVersion;
398 }
399
400 if (Stripe::$accountId) {
401 $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
402 }
403
404 if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
405 $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
406 }
407
408 $hasFile = false;
409 foreach ($params as $k => $v) {
410 if (\is_resource($v)) {
411 $hasFile = true;
412 $params[$k] = self::_processResourceParam($v);
413 } elseif ($v instanceof \CURLFile) {
414 $hasFile = true;
415 }
416 }
417
418 if ($hasFile) {
419 $defaultHeaders['Content-Type'] = 'multipart/form-data';
420 } else {
421 $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
422 }
423
424 $combinedHeaders = \array_merge($defaultHeaders, $headers);
425 $rawHeaders = [];
426
427 foreach ($combinedHeaders as $header => $value) {
428 $rawHeaders[] = $header . ': ' . $value;
429 }
430
431 return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey];
432 }
433
434 /**
435 * @param string $method
436 * @param string $url
437 * @param array $params
438 * @param array $headers
439 *
440 * @throws Exception\AuthenticationException
441 * @throws Exception\ApiConnectionException
442 *
443 * @return array
444 */
445 private function _requestRaw($method, $url, $params, $headers)
446 {
447 list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers);
448
449 $requestStartMs = Util\Util::currentTimeMillis();
450
451 list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
452 $method,
453 $absUrl,
454 $rawHeaders,
455 $params,
456 $hasFile
457 );
458
459 if (isset($rheaders['request-id'])
460 && \is_string($rheaders['request-id'])
461 && '' !== $rheaders['request-id']) {
462 self::$requestTelemetry = new RequestTelemetry(
463 $rheaders['request-id'],
464 Util\Util::currentTimeMillis() - $requestStartMs
465 );
466 }
467
468 return [$rbody, $rcode, $rheaders, $myApiKey];
469 }
470
471 /**
472 * @param string $method
473 * @param string $url
474 * @param array $params
475 * @param array $headers
476 * @param callable $readBodyChunkCallable
477 *
478 * @throws Exception\AuthenticationException
479 * @throws Exception\ApiConnectionException
480 *
481 * @return array
482 */
483 private function _requestRawStreaming($method, $url, $params, $headers, $readBodyChunkCallable)
484 {
485 list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers);
486
487 $requestStartMs = Util\Util::currentTimeMillis();
488
489 list($rbody, $rcode, $rheaders) = $this->streamingHttpClient()->requestStream(
490 $method,
491 $absUrl,
492 $rawHeaders,
493 $params,
494 $hasFile,
495 $readBodyChunkCallable
496 );
497
498 if (isset($rheaders['request-id'])
499 && \is_string($rheaders['request-id'])
500 && '' !== $rheaders['request-id']) {
501 self::$requestTelemetry = new RequestTelemetry(
502 $rheaders['request-id'],
503 Util\Util::currentTimeMillis() - $requestStartMs
504 );
505 }
506
507 return [$rbody, $rcode, $rheaders, $myApiKey];
508 }
509
510 /**
511 * @param resource $resource
512 *
513 * @throws Exception\InvalidArgumentException
514 *
515 * @return \CURLFile|string
516 */
517 private function _processResourceParam($resource)
518 {
519 if ('stream' !== \get_resource_type($resource)) {
520 throw new Exception\InvalidArgumentException(
521 'Attempted to upload a resource that is not a stream'
522 );
523 }
524
525 $metaData = \stream_get_meta_data($resource);
526 if ('plainfile' !== $metaData['wrapper_type']) {
527 throw new Exception\InvalidArgumentException(
528 'Only plainfile resource streams are supported'
529 );
530 }
531
532 // We don't have the filename or mimetype, but the API doesn't care
533 return new \CURLFile($metaData['uri']);
534 }
535
536 /**
537 * @param string $rbody
538 * @param int $rcode
539 * @param array $rheaders
540 *
541 * @throws Exception\UnexpectedValueException
542 * @throws Exception\ApiErrorException
543 *
544 * @return array
545 */
546 private function _interpretResponse($rbody, $rcode, $rheaders)
547 {
548 $resp = \json_decode($rbody, true);
549 $jsonError = \json_last_error();
550 if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
551 $msg = "Invalid response body from API: {$rbody} "
552 . "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
553
554 throw new Exception\UnexpectedValueException($msg, $rcode);
555 }
556
557 if ($rcode < 200 || $rcode >= 300) {
558 $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
559 }
560
561 return $resp;
562 }
563
564 /**
565 * @static
566 *
567 * @param HttpClient\ClientInterface $client
568 */
569 public static function setHttpClient($client)
570 {
571 self::$_httpClient = $client;
572 }
573
574 /**
575 * @static
576 *
577 * @param HttpClient\StreamingClientInterface $client
578 */
579 public static function setStreamingHttpClient($client)
580 {
581 self::$_streamingHttpClient = $client;
582 }
583
584 /**
585 * @static
586 *
587 * Resets any stateful telemetry data
588 */
589 public static function resetTelemetry()
590 {
591 self::$requestTelemetry = null;
592 }
593
594 /**
595 * @return HttpClient\ClientInterface
596 */
597 private function httpClient()
598 {
599 if (!self::$_httpClient) {
600 self::$_httpClient = HttpClient\CurlClient::instance();
601 }
602
603 return self::$_httpClient;
604 }
605
606 /**
607 * @return HttpClient\StreamingClientInterface
608 */
609 private function streamingHttpClient()
610 {
611 if (!self::$_streamingHttpClient) {
612 self::$_streamingHttpClient = HttpClient\CurlClient::instance();
613 }
614
615 return self::$_streamingHttpClient;
616 }
617 }
618