BaseStripeClient.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. namespace Stripe;
  3. class BaseStripeClient implements StripeClientInterface, StripeStreamingClientInterface
  4. {
  5. /** @var string default base URL for Stripe's API */
  6. const DEFAULT_API_BASE = 'https://api.stripe.com';
  7. /** @var string default base URL for Stripe's OAuth API */
  8. const DEFAULT_CONNECT_BASE = 'https://connect.stripe.com';
  9. /** @var string default base URL for Stripe's Files API */
  10. const DEFAULT_FILES_BASE = 'https://files.stripe.com';
  11. /** @var array<string, null|string> */
  12. const DEFAULT_CONFIG = [
  13. 'api_key' => null,
  14. 'app_info' => null,
  15. 'client_id' => null,
  16. 'stripe_account' => null,
  17. 'stripe_version' => \Stripe\Util\ApiVersion::CURRENT,
  18. 'api_base' => self::DEFAULT_API_BASE,
  19. 'connect_base' => self::DEFAULT_CONNECT_BASE,
  20. 'files_base' => self::DEFAULT_FILES_BASE,
  21. ];
  22. /** @var array<string, mixed> */
  23. private $config;
  24. /** @var \Stripe\Util\RequestOptions */
  25. private $defaultOpts;
  26. /**
  27. * Initializes a new instance of the {@link BaseStripeClient} class.
  28. *
  29. * The constructor takes a single argument. The argument can be a string, in which case it
  30. * should be the API key. It can also be an array with various configuration settings.
  31. *
  32. * Configuration settings include the following options:
  33. *
  34. * - api_key (null|string): the Stripe API key, to be used in regular API requests.
  35. * - app_info (null|array): information to identify a plugin that integrates Stripe using this library.
  36. * Expects: array{name: string, version?: string, url?: string, partner_id?: string}
  37. * - client_id (null|string): the Stripe client ID, to be used in OAuth requests.
  38. * - stripe_account (null|string): a Stripe account ID. If set, all requests sent by the client
  39. * will automatically use the {@code Stripe-Account} header with that account ID.
  40. * - stripe_version (null|string): a Stripe API version. If set, all requests sent by the client
  41. * will include the {@code Stripe-Version} header with that API version.
  42. *
  43. * The following configuration settings are also available, though setting these should rarely be necessary
  44. * (only useful if you want to send requests to a mock server like stripe-mock):
  45. *
  46. * - api_base (string): the base URL for regular API requests. Defaults to
  47. * {@link DEFAULT_API_BASE}.
  48. * - connect_base (string): the base URL for OAuth requests. Defaults to
  49. * {@link DEFAULT_CONNECT_BASE}.
  50. * - files_base (string): the base URL for file creation requests. Defaults to
  51. * {@link DEFAULT_FILES_BASE}.
  52. *
  53. * @param array<string, mixed>|string $config the API key as a string, or an array containing
  54. * the client configuration settings
  55. */
  56. public function __construct($config = [])
  57. {
  58. if (\is_string($config)) {
  59. $config = ['api_key' => $config];
  60. } elseif (!\is_array($config)) {
  61. throw new \Stripe\Exception\InvalidArgumentException('$config must be a string or an array');
  62. }
  63. $config = \array_merge(self::DEFAULT_CONFIG, $config);
  64. $this->validateConfig($config);
  65. $this->config = $config;
  66. $this->defaultOpts = \Stripe\Util\RequestOptions::parse([
  67. 'stripe_account' => $config['stripe_account'],
  68. 'stripe_version' => $config['stripe_version'],
  69. ]);
  70. }
  71. /**
  72. * Gets the API key used by the client to send requests.
  73. *
  74. * @return null|string the API key used by the client to send requests
  75. */
  76. public function getApiKey()
  77. {
  78. return $this->config['api_key'];
  79. }
  80. /**
  81. * Gets the client ID used by the client in OAuth requests.
  82. *
  83. * @return null|string the client ID used by the client in OAuth requests
  84. */
  85. public function getClientId()
  86. {
  87. return $this->config['client_id'];
  88. }
  89. /**
  90. * Gets the base URL for Stripe's API.
  91. *
  92. * @return string the base URL for Stripe's API
  93. */
  94. public function getApiBase()
  95. {
  96. return $this->config['api_base'];
  97. }
  98. /**
  99. * Gets the base URL for Stripe's OAuth API.
  100. *
  101. * @return string the base URL for Stripe's OAuth API
  102. */
  103. public function getConnectBase()
  104. {
  105. return $this->config['connect_base'];
  106. }
  107. /**
  108. * Gets the base URL for Stripe's Files API.
  109. *
  110. * @return string the base URL for Stripe's Files API
  111. */
  112. public function getFilesBase()
  113. {
  114. return $this->config['files_base'];
  115. }
  116. /**
  117. * Gets the app info for this client.
  118. *
  119. * @return null|array information to identify a plugin that integrates Stripe using this library
  120. */
  121. public function getAppInfo()
  122. {
  123. return $this->config['app_info'];
  124. }
  125. /**
  126. * Sends a request to Stripe's API.
  127. *
  128. * @param 'delete'|'get'|'post' $method the HTTP method
  129. * @param string $path the path of the request
  130. * @param array $params the parameters of the request
  131. * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
  132. *
  133. * @return \Stripe\StripeObject the object returned by Stripe's API
  134. */
  135. public function request($method, $path, $params, $opts)
  136. {
  137. $opts = $this->defaultOpts->merge($opts, true);
  138. $baseUrl = $opts->apiBase ?: $this->getApiBase();
  139. $requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
  140. list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers, ['stripe_client']);
  141. $opts->discardNonPersistentHeaders();
  142. $obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
  143. $obj->setLastResponse($response);
  144. return $obj;
  145. }
  146. /**
  147. * Sends a request to Stripe's API, passing chunks of the streamed response
  148. * into a user-provided $readBodyChunkCallable callback.
  149. *
  150. * @param 'delete'|'get'|'post' $method the HTTP method
  151. * @param string $path the path of the request
  152. * @param callable $readBodyChunkCallable a function that will be called
  153. * @param array $params the parameters of the request
  154. * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
  155. * with chunks of bytes from the body if the request is successful
  156. */
  157. public function requestStream($method, $path, $readBodyChunkCallable, $params, $opts)
  158. {
  159. $opts = $this->defaultOpts->merge($opts, true);
  160. $baseUrl = $opts->apiBase ?: $this->getApiBase();
  161. $requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo());
  162. list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers, ['stripe_client']);
  163. }
  164. /**
  165. * Sends a request to Stripe's API.
  166. *
  167. * @param 'delete'|'get'|'post' $method the HTTP method
  168. * @param string $path the path of the request
  169. * @param array $params the parameters of the request
  170. * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
  171. *
  172. * @return \Stripe\Collection of ApiResources
  173. */
  174. public function requestCollection($method, $path, $params, $opts)
  175. {
  176. $obj = $this->request($method, $path, $params, $opts);
  177. if (!($obj instanceof \Stripe\Collection)) {
  178. $received_class = \get_class($obj);
  179. $msg = "Expected to receive `Stripe\\Collection` object from Stripe API. Instead received `{$received_class}`.";
  180. throw new \Stripe\Exception\UnexpectedValueException($msg);
  181. }
  182. $obj->setFilters($params);
  183. return $obj;
  184. }
  185. /**
  186. * Sends a request to Stripe's API.
  187. *
  188. * @param 'delete'|'get'|'post' $method the HTTP method
  189. * @param string $path the path of the request
  190. * @param array $params the parameters of the request
  191. * @param array|\Stripe\Util\RequestOptions $opts the special modifiers of the request
  192. *
  193. * @return \Stripe\SearchResult of ApiResources
  194. */
  195. public function requestSearchResult($method, $path, $params, $opts)
  196. {
  197. $obj = $this->request($method, $path, $params, $opts);
  198. if (!($obj instanceof \Stripe\SearchResult)) {
  199. $received_class = \get_class($obj);
  200. $msg = "Expected to receive `Stripe\\SearchResult` object from Stripe API. Instead received `{$received_class}`.";
  201. throw new \Stripe\Exception\UnexpectedValueException($msg);
  202. }
  203. $obj->setFilters($params);
  204. return $obj;
  205. }
  206. /**
  207. * @param \Stripe\Util\RequestOptions $opts
  208. *
  209. * @throws \Stripe\Exception\AuthenticationException
  210. *
  211. * @return string
  212. */
  213. private function apiKeyForRequest($opts)
  214. {
  215. $apiKey = $opts->apiKey ?: $this->getApiKey();
  216. if (null === $apiKey) {
  217. $msg = 'No API key provided. Set your API key when constructing the '
  218. . 'StripeClient instance, or provide it on a per-request basis '
  219. . 'using the `api_key` key in the $opts argument.';
  220. throw new \Stripe\Exception\AuthenticationException($msg);
  221. }
  222. return $apiKey;
  223. }
  224. /**
  225. * @param array<string, mixed> $config
  226. *
  227. * @throws \Stripe\Exception\InvalidArgumentException
  228. */
  229. private function validateConfig($config)
  230. {
  231. // api_key
  232. if (null !== $config['api_key'] && !\is_string($config['api_key'])) {
  233. throw new \Stripe\Exception\InvalidArgumentException('api_key must be null or a string');
  234. }
  235. if (null !== $config['api_key'] && ('' === $config['api_key'])) {
  236. $msg = 'api_key cannot be the empty string';
  237. throw new \Stripe\Exception\InvalidArgumentException($msg);
  238. }
  239. if (null !== $config['api_key'] && (\preg_match('/\s/', $config['api_key']))) {
  240. $msg = 'api_key cannot contain whitespace';
  241. throw new \Stripe\Exception\InvalidArgumentException($msg);
  242. }
  243. // client_id
  244. if (null !== $config['client_id'] && !\is_string($config['client_id'])) {
  245. throw new \Stripe\Exception\InvalidArgumentException('client_id must be null or a string');
  246. }
  247. // stripe_account
  248. if (null !== $config['stripe_account'] && !\is_string($config['stripe_account'])) {
  249. throw new \Stripe\Exception\InvalidArgumentException('stripe_account must be null or a string');
  250. }
  251. // stripe_version
  252. if (null !== $config['stripe_version'] && !\is_string($config['stripe_version'])) {
  253. throw new \Stripe\Exception\InvalidArgumentException('stripe_version must be null or a string');
  254. }
  255. // api_base
  256. if (!\is_string($config['api_base'])) {
  257. throw new \Stripe\Exception\InvalidArgumentException('api_base must be a string');
  258. }
  259. // connect_base
  260. if (!\is_string($config['connect_base'])) {
  261. throw new \Stripe\Exception\InvalidArgumentException('connect_base must be a string');
  262. }
  263. // files_base
  264. if (!\is_string($config['files_base'])) {
  265. throw new \Stripe\Exception\InvalidArgumentException('files_base must be a string');
  266. }
  267. // app info
  268. if (null !== $config['app_info'] && !\is_array($config['app_info'])) {
  269. throw new \Stripe\Exception\InvalidArgumentException('app_info must be an array');
  270. }
  271. $appInfoKeys = ['name', 'version', 'url', 'partner_id'];
  272. if (null !== $config['app_info'] && array_diff_key($config['app_info'], array_flip($appInfoKeys))) {
  273. $msg = 'app_info must be of type array{name: string, version?: string, url?: string, partner_id?: string}';
  274. throw new \Stripe\Exception\InvalidArgumentException($msg);
  275. }
  276. // check absence of extra keys
  277. $extraConfigKeys = \array_diff(\array_keys($config), \array_keys(self::DEFAULT_CONFIG));
  278. if (!empty($extraConfigKeys)) {
  279. // Wrap in single quote to more easily catch trailing spaces errors
  280. $invalidKeys = "'" . \implode("', '", $extraConfigKeys) . "'";
  281. throw new \Stripe\Exception\InvalidArgumentException('Found unknown key(s) in configuration array: ' . $invalidKeys);
  282. }
  283. }
  284. }