Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master into beta #1616

Merged
merged 14 commits into from
Dec 7, 2023
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
## 13.6.0-beta.1 - 2023-11-30
* [#1610](https://github.com/stripe/stripe-php/pull/1610) Update generated code for beta

## 13.5.0 - 2023-11-30
* [#1611](https://github.com/stripe/stripe-php/pull/1611) Update generated code
* Add support for new resources `Climate.Order`, `Climate.Product`, and `Climate.Supplier`
* Add support for `all`, `cancel`, `create`, `retrieve`, and `update` methods on resource `Order`
* Add support for `all` and `retrieve` methods on resources `Product` and `Supplier`
* Add support for new value `financial_connections_account_inactive` on enum `StripeError.code`
* Add support for new values `climate_order_purchase` and `climate_order_refund` on enum `BalanceTransaction.type`
* Add support for new values `climate.order.canceled`, `climate.order.created`, `climate.order.delayed`, `climate.order.delivered`, `climate.order.product_substituted`, `climate.product.created`, and `climate.product.pricing_updated` on enum `Event.type`

## 13.5.0-beta.1 - 2023-11-21
* [#1606](https://github.com/stripe/stripe-php/pull/1606) Update generated code for beta
* Add support for `components` and `created` on `CustomerSession`
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,11 @@ an intermittent network problem:
[Idempotency keys][idempotency-keys] are added to requests to guarantee that
retries are safe.

### Request latency telemetry
### Telemetry

By default, the library sends request latency telemetry to Stripe. These
numbers help Stripe improve the overall latency of its API for all users.
By default, the library sends telemetry to Stripe regarding request latency and feature usage. These
numbers help Stripe improve the overall latency of its API for all users, and
improve popular features.

You can disable this behavior if you prefer:

Expand Down
18 changes: 11 additions & 7 deletions lib/ApiOperations/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ protected static function _validateParams($params = null)
* @param string $url URL for the request
* @param array $params list of parameters for the request
* @param null|array|string $options
* @param string[] $usage names of tracked behaviors associated with this request
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return array tuple containing (the JSON response, $options)
*/
protected function _request($method, $url, $params = [], $options = null)
protected function _request($method, $url, $params = [], $options = null, $usage = [])
{
$opts = $this->_opts->merge($options);
list($resp, $options) = static::_staticRequest($method, $url, $params, $opts);
list($resp, $options) = static::_staticRequest($method, $url, $params, $opts, $usage);
$this->setLastResponse($resp);

return [$resp->json, $options];
Expand All @@ -51,31 +52,33 @@ protected function _request($method, $url, $params = [], $options = null)
* @param callable $readBodyChunk function that will receive chunks of data from a successful request body
* @param array $params list of parameters for the request
* @param null|array|string $options
* @param string[] $usage names of tracked behaviors associated with this request
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*/
protected function _requestStream($method, $url, $readBodyChunk, $params = [], $options = null)
protected function _requestStream($method, $url, $readBodyChunk, $params = [], $options = null, $usage = [])
{
$opts = $this->_opts->merge($options);
static::_staticStreamingRequest($method, $url, $readBodyChunk, $params, $opts);
static::_staticStreamingRequest($method, $url, $readBodyChunk, $params, $opts, $usage);
}

/**
* @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.)
* @param string $url URL for the request
* @param array $params list of parameters for the request
* @param null|array|string $options
* @param string[] $usage names of tracked behaviors associated with this request
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return array tuple containing (the JSON response, $options)
*/
protected static function _staticRequest($method, $url, $params, $options)
protected static function _staticRequest($method, $url, $params, $options, $usage = [])
{
$opts = \Stripe\Util\RequestOptions::parse($options);
$baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl();
$requestor = new \Stripe\ApiRequestor($opts->apiKey, $baseUrl);
list($response, $opts->apiKey) = $requestor->request($method, $url, $params, $opts->headers);
list($response, $opts->apiKey) = $requestor->request($method, $url, $params, $opts->headers, 'standard', $usage);
$opts->discardNonPersistentHeaders();

return [$response, $opts];
Expand All @@ -87,10 +90,11 @@ protected static function _staticRequest($method, $url, $params, $options)
* @param callable $readBodyChunk function that will receive chunks of data from a successful request body
* @param array $params list of parameters for the request
* @param null|array|string $options
* @param string[] $usage names of tracked behaviors associated with this request
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*/
protected static function _staticStreamingRequest($method, $url, $readBodyChunk, $params, $options)
protected static function _staticStreamingRequest($method, $url, $readBodyChunk, $params, $options, $usage = [])
{
$opts = \Stripe\Util\RequestOptions::parse($options);
$baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl();
Expand Down
2 changes: 1 addition & 1 deletion lib/ApiOperations/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function save($opts = null)
$params = $this->serializeParameters();
if (\count($params) > 0) {
$url = $this->instanceUrl();
list($response, $opts) = $this->_request('post', $url, $params, $opts);
list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']);
$this->refreshFrom($response, $opts);
}

Expand Down
23 changes: 15 additions & 8 deletions lib/ApiRequestor.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ private static function _telemetryJson($requestTelemetry)
'request_duration_ms' => $requestTelemetry->requestDuration,
],
];
if (\count($requestTelemetry->usage) > 0) {
$payload['last_request_metrics']['usage'] = $requestTelemetry->usage;
}

$result = \json_encode($payload);
if (false !== $result) {
Expand Down Expand Up @@ -111,17 +114,18 @@ private static function _encodeObjects($d)
* @param null|array $params
* @param null|array $headers
* @param 'preview'|'standard' $apiMode
* @param string[] $usage
*
* @throws Exception\ApiErrorException
*
* @return array tuple containing (ApiReponse, API key)
*/
public function request($method, $url, $params = null, $headers = null, $apiMode = 'standard')
public function request($method, $url, $params = null, $headers = null, $apiMode = 'standard', $usage = [])
{
$params = $params ?: [];
$headers = $headers ?: [];
list($rbody, $rcode, $rheaders, $myApiKey) =
$this->_requestRaw($method, $url, $params, $headers, $apiMode);
$this->_requestRaw($method, $url, $params, $headers, $apiMode, $usage);
$json = $this->_interpretResponse($rbody, $rcode, $rheaders);
$resp = new ApiResponse($rbody, $rcode, $rheaders, $json);

Expand All @@ -135,16 +139,16 @@ public function request($method, $url, $params = null, $headers = null, $apiMode
* @param null|array $params
* @param null|array $headers
* @param 'preview'|'standard' $apiMode
* @param mixed $apiMode
* @param string[] $usage
*
* @throws Exception\ApiErrorException
*/
public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $apiMode = 'standard')
public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $apiMode = 'standard', $usage = [])
{
$params = $params ?: [];
$headers = $headers ?: [];
list($rbody, $rcode, $rheaders, $myApiKey) =
$this->_requestRawStreaming($method, $url, $params, $headers, $readBodyChunkCallable, $apiMode);
$this->_requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable);
if ($rcode >= 300) {
$this->_interpretResponse($rbody, $rcode, $rheaders);
}
Expand Down Expand Up @@ -451,13 +455,14 @@ function ($key) use ($params) {
* @param array $params
* @param array $headers
* @param 'preview'|'standard' $apiMode
* @param string[] $usage
*
* @throws Exception\AuthenticationException
* @throws Exception\ApiConnectionException
*
* @return array
*/
private function _requestRaw($method, $url, $params, $headers, $apiMode)
private function _requestRaw($method, $url, $params, $headers, $apiMode, $usage)
{
list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);

Expand All @@ -477,7 +482,8 @@ private function _requestRaw($method, $url, $params, $headers, $apiMode)
&& '' !== $rheaders['request-id']) {
self::$requestTelemetry = new RequestTelemetry(
$rheaders['request-id'],
Util\Util::currentTimeMillis() - $requestStartMs
Util\Util::currentTimeMillis() - $requestStartMs,
$usage
);
}

Expand All @@ -489,6 +495,7 @@ private function _requestRaw($method, $url, $params, $headers, $apiMode)
* @param string $url
* @param array $params
* @param array $headers
* @param string[] $usage
* @param callable $readBodyChunkCallable
* @param 'preview'|'standard' $apiMode
*
Expand All @@ -497,7 +504,7 @@ private function _requestRaw($method, $url, $params, $headers, $apiMode)
*
* @return array
*/
private function _requestRawStreaming($method, $url, $params, $headers, $readBodyChunkCallable, $apiMode)
private function _requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable)
{
list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);

Expand Down
4 changes: 2 additions & 2 deletions lib/BaseStripeClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public function request($method, $path, $params, $opts)
$opts = $this->defaultOpts->merge($opts, true);
$baseUrl = $opts->apiBase ?: $this->getApiBase();
$requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl);
list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers);
list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers, 'standard', ['stripe_client']);
$opts->discardNonPersistentHeaders();
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
$obj->setLastResponse($response);
Expand Down Expand Up @@ -219,7 +219,7 @@ public function requestStream($method, $path, $readBodyChunkCallable, $params, $
$opts = $this->defaultOpts->merge($opts, true);
$baseUrl = $opts->apiBase ?: $this->getApiBase();
$requestor = new \Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl);
list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers);
list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers, 'standard', ['stripe_client']);
}

/**
Expand Down
8 changes: 7 additions & 1 deletion lib/RequestTelemetry.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@
*/
class RequestTelemetry
{
/** @var string */
public $requestId;
/** @var int */
public $requestDuration;
/** @var string[] */
public $usage;

/**
* Initialize a new telemetry object.
*
* @param string $requestId the request's request ID
* @param int $requestDuration the request's duration in milliseconds
* @param string[] $usage names of tracked behaviors associated with this request
*/
public function __construct($requestId, $requestDuration)
public function __construct($requestId, $requestDuration, $usage = [])
{
$this->requestId = $requestId;
$this->requestDuration = $requestDuration;
$this->usage = $usage;
}
}
52 changes: 50 additions & 2 deletions tests/Stripe/StripeTelemetryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function testNoTelemetrySentIfNotEnabled()
ApiRequestor::setHttpClient(null);
}

public function testTelemetrySetIfEnabled()
public function testTelemetrySetIfEnabledGlobalInterface()
{
Stripe::setEnableTelemetry(true);

Expand Down Expand Up @@ -105,8 +105,10 @@ public function testTelemetrySetIfEnabled()

ApiRequestor::setHttpClient($stub);

$cus = new \Stripe\Customer('cus_xyz');
$cus->description = 'test';
// make one request to capture its result
Charge::all();
$cus->save();
static::assertArrayNotHasKey('X-Stripe-Client-Telemetry', $requestheaders);

// make another request to send the previous
Expand All @@ -115,6 +117,52 @@ public function testTelemetrySetIfEnabled()

$data = \json_decode($requestheaders['X-Stripe-Client-Telemetry'], true);
static::assertSame('req_123', $data['last_request_metrics']['request_id']);
static::assertSame(['save'], $data['last_request_metrics']['usage']);
static::assertNotNull($data['last_request_metrics']['request_duration_ms']);

ApiRequestor::setHttpClient(null);
}

public function testTelemetrySetIfEnabledStripeClient()
{
Stripe::setEnableTelemetry(true);

$requestheaders = null;

$stub = $this
->getMockBuilder('\\Stripe\\HttpClient\\ClientInterface')
->setMethods(['request'])
->getMock()
;

$stub->expects(static::any())
->method('request')
->with(
static::anything(),
static::anything(),
static::callback(function ($headers) use (&$requestheaders) {
// capture the requested headers and format back to into an assoc array
foreach ($headers as $index => $header) {
$components = \explode(': ', $header, 2);
$requestheaders[$components[0]] = $components[1];
}

return true;
}),
static::anything(),
static::anything()
)->willReturn([self::FAKE_VALID_RESPONSE, 200, ['request-id' => 'req_123']]);

ApiRequestor::setHttpClient($stub);

$client = new \Stripe\StripeClient('sk_test_123');
$client->customers->create(['description' => 'test']);
static::assertArrayNotHasKey('X-Stripe-Client-Telemetry', $requestheaders);
$client->customers->create(['description' => 'test2']);
static::assertArrayHasKey('X-Stripe-Client-Telemetry', $requestheaders);
$data = \json_decode($requestheaders['X-Stripe-Client-Telemetry'], true);
static::assertSame('req_123', $data['last_request_metrics']['request_id']);
static::assertSame(['stripe_client'], $data['last_request_metrics']['usage']);
static::assertNotNull($data['last_request_metrics']['request_duration_ms']);

ApiRequestor::setHttpClient(null);
Expand Down
Loading