• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stripe / stripe-php / 11129599820

01 Oct 2024 04:33PM UTC coverage: 62.613% (-1.3%) from 63.944%
11129599820

push

github

web-flow
Support for APIs in the new API version 2024-09-30.acacia (#1756)

175 of 409 new or added lines in 26 files covered. (42.79%)

3 existing lines in 3 files now uncovered.

3547 of 5665 relevant lines covered (62.61%)

2.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.03
/lib/BaseStripeClient.php
1
<?php
2

3
namespace Stripe;
4

5
use Stripe\Util\Util;
6

7
class BaseStripeClient implements StripeClientInterface, StripeStreamingClientInterface
8
{
9
    /** @var string default base URL for Stripe's API */
10
    const DEFAULT_API_BASE = 'https://api.stripe.com';
11

12
    /** @var string default base URL for Stripe's OAuth API */
13
    const DEFAULT_CONNECT_BASE = 'https://connect.stripe.com';
14

15
    /** @var string default base URL for Stripe's Files API */
16
    const DEFAULT_FILES_BASE = 'https://files.stripe.com';
17

18
    /** @var string default base URL for Stripe's Meter Events API */
19
    const DEFAULT_METER_EVENTS_BASE = 'https://meter-events.stripe.com';
20

21
    /** @var array<string, null|string> */
22
    const DEFAULT_CONFIG = [
23
        'api_key' => null,
24
        'app_info' => null,
25
        'client_id' => null,
26
        'stripe_account' => null,
27
        'stripe_context' => null,
28
        'stripe_version' => \Stripe\Util\ApiVersion::CURRENT,
29
        'api_base' => self::DEFAULT_API_BASE,
30
        'connect_base' => self::DEFAULT_CONNECT_BASE,
31
        'files_base' => self::DEFAULT_FILES_BASE,
32
        'meter_events_base' => self::DEFAULT_METER_EVENTS_BASE,
33
    ];
34

35
    /** @var array<string, mixed> */
36
    private $config;
37

38
    /** @var \Stripe\Util\RequestOptions */
39
    private $defaultOpts;
40

41
    /**
42
     * Initializes a new instance of the {@link BaseStripeClient} class.
43
     *
44
     * The constructor takes a single argument. The argument can be a string, in which case it
45
     * should be the API key. It can also be an array with various configuration settings.
46
     *
47
     * Configuration settings include the following options:
48
     *
49
     * - api_key (null|string): the Stripe API key, to be used in regular API requests.
50
     * - app_info (null|array): information to identify a plugin that integrates Stripe using this library.
51
     *                          Expects: array{name: string, version?: string, url?: string, partner_id?: string}
52
     * - client_id (null|string): the Stripe client ID, to be used in OAuth requests.
53
     * - stripe_account (null|string): a Stripe account ID. If set, all requests sent by the client
54
     *   will automatically use the {@code Stripe-Account} header with that account ID.
55
     * - stripe_context (null|string): a Stripe account or compartment ID. If set, all requests sent by the client
56
     *   will automatically use the {@code Stripe-Context} header with that ID.
57
     * - stripe_version (null|string): a Stripe API version. If set, all requests sent by the client
58
     *   will include the {@code Stripe-Version} header with that API version.
59
     *
60
     * The following configuration settings are also available, though setting these should rarely be necessary
61
     * (only useful if you want to send requests to a mock server like stripe-mock):
62
     *
63
     * - api_base (string): the base URL for regular API requests. Defaults to
64
     *   {@link DEFAULT_API_BASE}.
65
     * - connect_base (string): the base URL for OAuth requests. Defaults to
66
     *   {@link DEFAULT_CONNECT_BASE}.
67
     * - files_base (string): the base URL for file creation requests. Defaults to
68
     *   {@link DEFAULT_FILES_BASE}.
69
     * - meter_events_base (string): the base URL for high throughput requests. Defaults to
70
     *   {@link DEFAULT_METER_EVENTS_BASE}.
71
     *
72
     * @param array<string, mixed>|string $config the API key as a string, or an array containing
73
     *   the client configuration settings
74
     */
75
    public function __construct($config = [])
40✔
76
    {
77
        if (\is_string($config)) {
40✔
78
            $config = ['api_key' => $config];
2✔
79
        } elseif (!\is_array($config)) {
38✔
80
            throw new \Stripe\Exception\InvalidArgumentException('$config must be a string or an array');
1✔
81
        }
82

83
        $config = \array_merge(self::DEFAULT_CONFIG, $config);
39✔
84
        $this->validateConfig($config);
39✔
85

86
        $this->config = $config;
34✔
87

88
        $this->defaultOpts = \Stripe\Util\RequestOptions::parse([
34✔
89
            'stripe_account' => $config['stripe_account'],
34✔
90
            'stripe_context' => $config['stripe_context'],
34✔
91
            'stripe_version' => $config['stripe_version'],
34✔
92
        ]);
34✔
93
    }
94

95
    /**
96
     * Gets the API key used by the client to send requests.
97
     *
98
     * @return null|string the API key used by the client to send requests
99
     */
100
    public function getApiKey()
29✔
101
    {
102
        return $this->config['api_key'];
29✔
103
    }
104

105
    /**
106
     * Gets the client ID used by the client in OAuth requests.
107
     *
108
     * @return null|string the client ID used by the client in OAuth requests
109
     */
110
    public function getClientId()
×
111
    {
112
        return $this->config['client_id'];
×
113
    }
114

115
    /**
116
     * Gets the base URL for Stripe's API.
117
     *
118
     * @return string the base URL for Stripe's API
119
     */
120
    public function getApiBase()
29✔
121
    {
122
        return $this->config['api_base'];
29✔
123
    }
124

125
    /**
126
     * Gets the base URL for Stripe's OAuth API.
127
     *
128
     * @return string the base URL for Stripe's OAuth API
129
     */
130
    public function getConnectBase()
×
131
    {
132
        return $this->config['connect_base'];
×
133
    }
134

135
    /**
136
     * Gets the base URL for Stripe's Files API.
137
     *
138
     * @return string the base URL for Stripe's Files API
139
     */
140
    public function getFilesBase()
×
141
    {
142
        return $this->config['files_base'];
×
143
    }
144

145
    /**
146
     * Gets the base URL for Stripe's Meter Events API.
147
     *
148
     * @return string the base URL for Stripe's Meter Events API
149
     */
NEW
150
    public function getMeterEventsBase()
×
151
    {
NEW
152
        return $this->config['meter_events_base'];
×
153
    }
154

155
    /**
156
     * Gets the app info for this client.
157
     *
158
     * @return null|array information to identify a plugin that integrates Stripe using this library
159
     */
160
    public function getAppInfo()
22✔
161
    {
162
        return $this->config['app_info'];
22✔
163
    }
164

165
    /**
166
     * Sends a request to Stripe's API.
167
     *
168
     * @param 'delete'|'get'|'post' $method the HTTP method
169
     * @param string $path the path of the request
170
     * @param array $params the parameters of the request
171
     * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
172
     *
173
     * @return \Stripe\StripeObject the object returned by Stripe's API
174
     */
175
    public function request($method, $path, $params, $opts)
25✔
176
    {
177
        $defaultRequestOpts = $this->defaultOpts;
25✔
178
        $apiMode = \Stripe\Util\Util::getApiMode($path);
25✔
179

180
        $opts = $defaultRequestOpts->merge($opts, true);
25✔
181

182
        $baseUrl = $opts->apiBase ?: $this->getApiBase();
23✔
183
        $requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
23✔
184
        list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers, $apiMode, ['stripe_client']);
22✔
185
        $opts->discardNonPersistentHeaders();
21✔
186
        $obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts, $apiMode);
21✔
187
        if (\is_array($obj)) {
21✔
188
            // Edge case for v2 endpoints that return empty/void response
189
            // Example: client->v2->billing->meterEventStream->create
190
            $obj = new \Stripe\StripeObject();
1✔
191
        }
192
        $obj->setLastResponse($response);
21✔
193

194
        return $obj;
21✔
195
    }
196

197
    /**
198
     * Sends a raw request to Stripe's API. This is the lowest level method for interacting
199
     * with the Stripe API. This method is useful for interacting with endpoints that are not
200
     * covered yet in stripe-php.
201
     *
202
     * @param 'delete'|'get'|'post' $method the HTTP method
203
     * @param string $path the path of the request
204
     * @param null|array $params the parameters of the request
205
     * @param array $opts the special modifiers of the request
206
     *
207
     * @return \Stripe\ApiResponse
208
     */
209
    public function rawRequest($method, $path, $params = null, $opts = [])
7✔
210
    {
211
        if ('post' !== $method && null !== $params) {
7✔
212
            throw new Exception\InvalidArgumentException('Error: rawRequest only supports $params on post requests. Please pass null and add your parameters to $path');
1✔
213
        }
214
        $apiMode = \Stripe\Util\Util::getApiMode($path);
6✔
215
        $headers = [];
6✔
216
        if (\is_array($opts) && \array_key_exists('headers', $opts)) {
6✔
NEW
217
            $headers = $opts['headers'] ?: [];
×
NEW
218
            unset($opts['headers']);
×
219
        }
220
        if (\is_array($opts) && \array_key_exists('stripe_context', $opts)) {
6✔
221
            $headers['Stripe-Context'] = $opts['stripe_context'];
1✔
222
            unset($opts['stripe_context']);
1✔
223
        }
224

225
        $defaultRawRequestOpts = $this->defaultOpts;
6✔
226

227
        $opts = $defaultRawRequestOpts->merge($opts, true);
6✔
228

229
        // Concatenate $headers to $opts->headers, removing duplicates.
230
        $opts->headers = \array_merge($opts->headers, $headers);
6✔
231
        $baseUrl = $opts->apiBase ?: $this->getApiBase();
6✔
232
        $requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl);
6✔
233
        list($response) = $requestor->request($method, $path, $params, $opts->headers, $apiMode, ['raw_request']);
6✔
234

235
        return $response;
6✔
236
    }
237

238
    /**
239
     * Sends a request to Stripe's API, passing chunks of the streamed response
240
     * into a user-provided $readBodyChunkCallable callback.
241
     *
242
     * @param 'delete'|'get'|'post' $method the HTTP method
243
     * @param string $path the path of the request
244
     * @param callable $readBodyChunkCallable a function that will be called
245
     * @param array $params the parameters of the request
246
     * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
247
     *
248
     * with chunks of bytes from the body if the request is successful
249
     */
250
    public function requestStream($method, $path, $readBodyChunkCallable, $params, $opts)
×
251
    {
252
        $opts = $this->defaultOpts->merge($opts, true);
×
253
        $baseUrl = $opts->apiBase ?: $this->getApiBase();
×
254
        $requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
×
NEW
255
        $apiMode = \Stripe\Util\Util::getApiMode($path);
×
NEW
256
        list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers, $apiMode, ['stripe_client']);
×
257
    }
258

259
    /**
260
     * Sends a request to Stripe's API.
261
     *
262
     * @param 'delete'|'get'|'post' $method the HTTP method
263
     * @param string $path the path of the request
264
     * @param array $params the parameters of the request
265
     * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
266
     *
267
     * @return \Stripe\Collection|\Stripe\V2\Collection of ApiResources
268
     */
269
    public function requestCollection($method, $path, $params, $opts)
2✔
270
    {
271
        $obj = $this->request($method, $path, $params, $opts);
2✔
272
        $apiMode = \Stripe\Util\Util::getApiMode($path);
2✔
273
        if ('v1' === $apiMode) {
2✔
274
            if (!($obj instanceof \Stripe\Collection)) {
2✔
275
                $received_class = \get_class($obj);
1✔
276
                $msg = "Expected to receive `Stripe\\Collection` object from Stripe API. Instead received `{$received_class}`.";
1✔
277

278
                throw new \Stripe\Exception\UnexpectedValueException($msg);
1✔
279
            }
280
            $obj->setFilters($params);
1✔
281
        } else {
NEW
282
            if (!($obj instanceof \Stripe\V2\Collection)) {
×
NEW
283
                $received_class = \get_class($obj);
×
NEW
284
                $msg = "Expected to receive `Stripe\\V2\\Collection` object from Stripe API. Instead received `{$received_class}`.";
×
285

NEW
286
                throw new \Stripe\Exception\UnexpectedValueException($msg);
×
287
            }
288
        }
289

290
        return $obj;
1✔
291
    }
292

293
    /**
294
     * Sends a request to Stripe's API.
295
     *
296
     * @param 'delete'|'get'|'post' $method the HTTP method
297
     * @param string $path the path of the request
298
     * @param array $params the parameters of the request
299
     * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
300
     *
301
     * @return \Stripe\SearchResult of ApiResources
302
     */
303
    public function requestSearchResult($method, $path, $params, $opts)
×
304
    {
305
        $obj = $this->request($method, $path, $params, $opts);
×
306
        if (!($obj instanceof \Stripe\SearchResult)) {
×
307
            $received_class = \get_class($obj);
×
308
            $msg = "Expected to receive `Stripe\\SearchResult` object from Stripe API. Instead received `{$received_class}`.";
×
309

310
            throw new \Stripe\Exception\UnexpectedValueException($msg);
×
311
        }
312
        $obj->setFilters($params);
×
313

314
        return $obj;
×
315
    }
316

317
    /**
318
     * @param \Stripe\Util\RequestOptions $opts
319
     *
320
     * @throws \Stripe\Exception\AuthenticationException
321
     *
322
     * @return string
323
     */
324
    private function apiKeyForRequest($opts)
29✔
325
    {
326
        $apiKey = $opts->apiKey ?: $this->getApiKey();
29✔
327

328
        if (null === $apiKey) {
29✔
329
            $msg = 'No API key provided. Set your API key when constructing the '
1✔
330
                . 'StripeClient instance, or provide it on a per-request basis '
1✔
331
                . 'using the `api_key` key in the $opts argument.';
1✔
332

333
            throw new \Stripe\Exception\AuthenticationException($msg);
1✔
334
        }
335

336
        return $apiKey;
28✔
337
    }
338

339
    /**
340
     * @param array<string, mixed> $config
341
     *
342
     * @throws \Stripe\Exception\InvalidArgumentException
343
     */
344
    private function validateConfig($config)
39✔
345
    {
346
        // api_key
347
        if (null !== $config['api_key'] && !\is_string($config['api_key'])) {
39✔
348
            throw new \Stripe\Exception\InvalidArgumentException('api_key must be null or a string');
1✔
349
        }
350

351
        if (null !== $config['api_key'] && ('' === $config['api_key'])) {
38✔
352
            $msg = 'api_key cannot be the empty string';
1✔
353

354
            throw new \Stripe\Exception\InvalidArgumentException($msg);
1✔
355
        }
356

357
        if (null !== $config['api_key'] && (\preg_match('/\s/', $config['api_key']))) {
37✔
358
            $msg = 'api_key cannot contain whitespace';
1✔
359

360
            throw new \Stripe\Exception\InvalidArgumentException($msg);
1✔
361
        }
362

363
        // client_id
364
        if (null !== $config['client_id'] && !\is_string($config['client_id'])) {
36✔
365
            throw new \Stripe\Exception\InvalidArgumentException('client_id must be null or a string');
×
366
        }
367

368
        // stripe_account
369
        if (null !== $config['stripe_account'] && !\is_string($config['stripe_account'])) {
36✔
370
            throw new \Stripe\Exception\InvalidArgumentException('stripe_account must be null or a string');
×
371
        }
372

373
        // stripe_context
374
        if (null !== $config['stripe_context'] && !\is_string($config['stripe_context'])) {
36✔
NEW
375
            throw new \Stripe\Exception\InvalidArgumentException('stripe_context must be null or a string');
×
376
        }
377

378
        // stripe_version
379
        if (null !== $config['stripe_version'] && !\is_string($config['stripe_version'])) {
36✔
380
            throw new \Stripe\Exception\InvalidArgumentException('stripe_version must be null or a string');
×
381
        }
382

383
        // api_base
384
        if (!\is_string($config['api_base'])) {
36✔
385
            throw new \Stripe\Exception\InvalidArgumentException('api_base must be a string');
×
386
        }
387

388
        // connect_base
389
        if (!\is_string($config['connect_base'])) {
36✔
390
            throw new \Stripe\Exception\InvalidArgumentException('connect_base must be a string');
×
391
        }
392

393
        // files_base
394
        if (!\is_string($config['files_base'])) {
36✔
395
            throw new \Stripe\Exception\InvalidArgumentException('files_base must be a string');
×
396
        }
397

398
        // app info
399
        if (null !== $config['app_info'] && !\is_array($config['app_info'])) {
36✔
400
            throw new \Stripe\Exception\InvalidArgumentException('app_info must be an array');
×
401
        }
402

403
        $appInfoKeys = ['name', 'version', 'url', 'partner_id'];
36✔
404
        if (null !== $config['app_info'] && array_diff_key($config['app_info'], array_flip($appInfoKeys))) {
36✔
405
            $msg = 'app_info must be of type array{name: string, version?: string, url?: string, partner_id?: string}';
1✔
406

407
            throw new \Stripe\Exception\InvalidArgumentException($msg);
1✔
408
        }
409

410
        // check absence of extra keys
411
        $extraConfigKeys = \array_diff(\array_keys($config), \array_keys(self::DEFAULT_CONFIG));
35✔
412
        if (!empty($extraConfigKeys)) {
35✔
413
            // Wrap in single quote to more easily catch trailing spaces errors
414
            $invalidKeys = "'" . \implode("', '", $extraConfigKeys) . "'";
1✔
415

416
            throw new \Stripe\Exception\InvalidArgumentException('Found unknown key(s) in configuration array: ' . $invalidKeys);
1✔
417
        }
418
    }
419

420
    /**
421
     * Deserializes the raw JSON string returned by rawRequest into a similar class.
422
     *
423
     * @param string $json
424
     * @param 'v1'|'v2' $apiMode
425
     *
426
     * @return \Stripe\StripeObject
427
     * */
428
    public function deserialize($json, $apiMode = 'v1')
1✔
429
    {
430
        return \Stripe\Util\Util::convertToStripeObject(\json_decode($json, true), [], $apiMode);
1✔
431
    }
432

433
    /**
434
     * Returns a V2\Events instance using the provided JSON payload. Throws an
435
     * Exception\UnexpectedValueException if the payload is not valid JSON, and
436
     * an Exception\SignatureVerificationException if the signature
437
     * verification fails for any reason.
438
     *
439
     * @param string $payload the payload sent by Stripe
440
     * @param string $sigHeader the contents of the signature header sent by
441
     *  Stripe
442
     * @param string $secret secret used to generate the signature
443
     * @param int $tolerance maximum difference allowed between the header's
444
     *  timestamp and the current time. Defaults to 300 seconds (5 min)
445
     *
446
     * @throws Exception\SignatureVerificationException if the verification fails
447
     * @throws Exception\UnexpectedValueException if the payload is not valid JSON,
448
     *
449
     * @return \Stripe\ThinEvent
450
     */
451
    public function parseThinEvent($payload, $sigHeader, $secret, $tolerance = Webhook::DEFAULT_TOLERANCE)
1✔
452
    {
453
        $eventData = Util::utf8($payload);
1✔
454
        WebhookSignature::verifyHeader($payload, $sigHeader, $secret, $tolerance);
1✔
455

456
        try {
457
            return Util::json_decode_thin_event_object(
1✔
458
                $eventData,
1✔
459
                '\Stripe\ThinEvent'
1✔
460
            );
1✔
NEW
461
        } catch (\ReflectionException $e) {
×
462
            // Fail gracefully
NEW
463
            return new \Stripe\ThinEvent();
×
464
        }
465
    }
466

467
    /**
468
     * Returns an Events instance using the provided JSON payload. Throws an
469
     * Exception\UnexpectedValueException if the payload is not valid JSON, and
470
     * an Exception\SignatureVerificationException if the signature
471
     * verification fails for any reason.
472
     *
473
     * @param string $payload the payload sent by Stripe
474
     * @param string $sigHeader the contents of the signature header sent by
475
     *  Stripe
476
     * @param string $secret secret used to generate the signature
477
     * @param int $tolerance maximum difference allowed between the header's
478
     *  timestamp and the current time
479
     *
480
     * @throws Exception\UnexpectedValueException if the payload is not valid JSON,
481
     * @throws Exception\SignatureVerificationException if the verification fails
482
     *
483
     * @return Event the Event instance
484
     */
NEW
485
    public function parseSnapshotEvent($payload, $sigHeader, $secret, $tolerance = Webhook::DEFAULT_TOLERANCE)
×
486
    {
NEW
487
        WebhookSignature::verifyHeader($payload, $sigHeader, $secret, $tolerance);
×
488

NEW
489
        $data = \json_decode($payload, true);
×
NEW
490
        $jsonError = \json_last_error();
×
NEW
491
        if (null === $data && \JSON_ERROR_NONE !== $jsonError) {
×
NEW
492
            $msg = "Invalid payload: {$payload} "
×
NEW
493
              . "(json_last_error() was {$jsonError})";
×
494

NEW
495
            throw new Exception\UnexpectedValueException($msg);
×
496
        }
497

NEW
498
        return Event::constructFrom($data);
×
499
    }
500
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc