Skip to content

Commit 6666cdc

Browse files
Merge pull request #2686 from stripe/latest-codegen-private-preview
Update generated code for private-preview
2 parents 8c4dccf + 3b5ca7e commit 6666cdc

17 files changed

+965
-44
lines changed

CODEGEN_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4cd98860940da75ae8dfe7384cea405859800e9c
1+
a527d1f955ea1eab1b7ccdb63001a69d1786fc37

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ stripe.off('request', onRequest);
364364
idempotency_key: 'abc123', // Only present if provided
365365
method: 'POST',
366366
path: '/v1/customers',
367+
body: {name: 'test'}, // Only present if emitEventBodies is true
367368
request_start_time: 1565125303932 // Unix timestamp in milliseconds
368369
}
369370
```
@@ -373,15 +374,16 @@ stripe.off('request', onRequest);
373374
```js
374375
{
375376
api_version: 'latest',
376-
account: 'acct_TEST', // Only present if provided
377-
idempotency_key: 'abc123', // Only present if provided
377+
account: 'acct_TEST', // Only present if provided
378+
idempotency_key: 'abc123', // Only present if provided
378379
method: 'POST',
379380
path: '/v1/customers',
380-
status: 402,
381+
status: 200,
381382
request_id: 'req_Ghc9r26ts73DRf',
382-
elapsed: 445, // Elapsed time in milliseconds
383-
request_start_time: 1565125303932, // Unix timestamp in milliseconds
384-
request_end_time: 1565125304377 // Unix timestamp in milliseconds
383+
body: {id: 'cus_123', object: 'customer'}, // Only present if emitEventBodies is true
384+
elapsed: 445, // Elapsed time in milliseconds
385+
request_start_time: 1565125303932, // Unix timestamp in milliseconds
386+
request_end_time: 1565125304377 // Unix timestamp in milliseconds
385387
}
386388
```
387389

src/RequestSender.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,15 @@ export class RequestSender {
161161
statusCode,
162162
headers
163163
);
164-
this._stripe._emitter.emit('response', responseEvent);
165164

166165
res
167166
.toJSON()
168167
.then(
169168
(jsonResponse) => {
169+
if (this._stripe.getEmitEventBodiesEnabled()) {
170+
responseEvent.body = jsonResponse;
171+
}
172+
170173
if (jsonResponse.error) {
171174
const isOAuth = typeof jsonResponse.error === 'string';
172175

@@ -197,6 +200,12 @@ export class RequestSender {
197200
return jsonResponse;
198201
},
199202
(e: Error) => {
203+
if (
204+
this._stripe.getEmitEventBodiesEnabled() &&
205+
(e as any).rawBody
206+
) {
207+
responseEvent.body = (e as any).rawBody;
208+
}
200209
throw new StripeAPIError({
201210
message: 'Invalid JSON received from the Stripe API',
202211
exception: e,
@@ -206,6 +215,8 @@ export class RequestSender {
206215
)
207216
.then(
208217
(jsonResponse) => {
218+
this._stripe._emitter.emit('response', responseEvent);
219+
209220
this._recordRequestMetrics(requestId, responseEvent.elapsed, usage);
210221

211222
// Expose raw response object.
@@ -219,7 +230,10 @@ export class RequestSender {
219230

220231
callback(null, jsonResponse);
221232
},
222-
(e) => callback(e, null)
233+
(e) => {
234+
this._stripe._emitter.emit('response', responseEvent);
235+
callback(e, null);
236+
}
223237
);
224238
};
225239
}
@@ -631,6 +645,9 @@ export class RequestSender {
631645
),
632646
method,
633647
path,
648+
body: this._stripe.getEmitEventBodiesEnabled()
649+
? data ?? undefined
650+
: undefined,
634651
request_start_time: requestStartTime,
635652
});
636653

src/Types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export type RequestEvent = {
102102
idempotency_key?: string;
103103
method?: string;
104104
path?: string;
105+
body?: RequestData;
105106
request_start_time: number;
106107
usage?: Array<string>;
107108
};
@@ -139,6 +140,7 @@ export type ResponseEvent = {
139140
path?: string;
140141
status?: number;
141142
request_id?: string;
143+
body?: Record<string, any> | string;
142144
elapsed: number;
143145
request_start_time?: number;
144146
request_end_time?: number;
@@ -221,5 +223,6 @@ export type UserProvidedConfig = {
221223
stripeContext?: string | StripeContext;
222224
typescript?: boolean;
223225
telemetry?: boolean;
226+
emitEventBodies?: boolean;
224227
appInfo?: AppInfo;
225228
};

src/Webhooks.ts

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ type WebhookParsedEvent = {
2828
suspectPayloadType: boolean;
2929
};
3030
type WebhookTestHeaderOptions = {
31-
timestamp: number;
31+
timestamp?: number;
3232
payload: string;
3333
secret: string;
34-
scheme: string;
35-
signature: string;
36-
cryptoProvider: CryptoProvider;
34+
scheme?: string;
35+
signature?: string;
36+
cryptoProvider?: CryptoProvider;
3737
};
3838

3939
// export type WebhookEvent = Record<string, unknown>;
@@ -43,16 +43,16 @@ type WebhookSignatureObject = {
4343
encodedPayload: WebhookPayload,
4444
encodedHeader: WebhookHeader,
4545
secret: string,
46-
tolerance: number,
47-
cryptoProvider: CryptoProvider,
46+
tolerance?: number,
47+
cryptoProvider?: CryptoProvider,
4848
receivedAt?: number
4949
) => boolean;
5050
verifyHeaderAsync: (
5151
encodedPayload: WebhookPayload,
5252
encodedHeader: WebhookHeader,
5353
secret: string,
54-
tolerance: number,
55-
cryptoProvider: CryptoProvider,
54+
tolerance?: number,
55+
cryptoProvider?: CryptoProvider,
5656
receivedAt?: number
5757
) => Promise<boolean>;
5858
};
@@ -159,7 +159,7 @@ export function createWebhooks(
159159
: JSON.parse(payload);
160160
if (jsonPayload && jsonPayload.object === 'v2.core.event') {
161161
throw new Error(
162-
'You passed an event notification to stripe.webhooks.constructEvent, which expects a webhook payload. Use stripe.parseEventNotification instead.'
162+
'You passed an event notification to stripe.webhooks.constructEvent, which expects a webhook payload. Use stripe.parseEventNotificationAsync instead.'
163163
);
164164
}
165165
return jsonPayload;
@@ -211,8 +211,8 @@ export function createWebhooks(
211211
encodedPayload: WebhookPayload,
212212
encodedHeader: WebhookHeader,
213213
secret: string,
214-
tolerance: number,
215-
cryptoProvider: CryptoProvider,
214+
tolerance?: number,
215+
cryptoProvider?: CryptoProvider,
216216
receivedAt?: number
217217
): boolean {
218218
const {
@@ -233,12 +233,17 @@ export function createWebhooks(
233233
secret
234234
);
235235

236+
/**
237+
* TODO(MAJOR): https://go/j/DEVSDK-3087
238+
* Passing in 0 by default skips timestamp tolerance verifications. Although it is mostly used in test,
239+
* we should change the default behavior to pass DEFAULT_TOLERANCE instead of 0 in the next major.
240+
*/
236241
validateComputedSignature(
237242
payload,
238243
header,
239244
details,
240245
expectedSignature,
241-
tolerance,
246+
tolerance || 0,
242247
suspectPayloadType,
243248
secretContainsWhitespace,
244249
receivedAt
@@ -251,8 +256,8 @@ export function createWebhooks(
251256
encodedPayload: WebhookPayload,
252257
encodedHeader: WebhookHeader,
253258
secret: string,
254-
tolerance: number,
255-
cryptoProvider: CryptoProvider,
259+
tolerance?: number,
260+
cryptoProvider?: CryptoProvider,
256261
receivedAt?: number
257262
): Promise<boolean> {
258263
const {
@@ -274,12 +279,17 @@ export function createWebhooks(
274279
secret
275280
);
276281

282+
/**
283+
* TODO(MAJOR): https://go/j/DEVSDK-3087
284+
* Passing in 0 by default skips timestamp tolerance verifications. Although it is mostly used in test,
285+
* we should change the default behavior to pass DEFAULT_TOLERANCE instead of 0 in the next major.
286+
*/
277287
return validateComputedSignature(
278288
payload,
279289
header,
280290
details,
281291
expectedSignature,
282-
tolerance,
292+
tolerance || 0,
283293
suspectPayloadType,
284294
secretContainsWhitespace,
285295
receivedAt
@@ -374,6 +384,31 @@ export function createWebhooks(
374384
};
375385
}
376386

387+
/**
388+
* Validates that at least one signature in the parsed header matches the
389+
* expected signature, and that the event timestamp is within the allowed
390+
* {@link tolerance} window (in seconds). Set `tolerance` to `0` to skip
391+
* timestamp verification.
392+
*
393+
* TODO(MAJOR): https://go/j/DEVSDK-3087 - Change this default behavior to use DEFAULT_TOLERANCE instead of 0.
394+
* By default, validateComputedSignature doesn't perform timestamp verification.
395+
*
396+
* This method is mostly meant for tests or offline processing where the delivery time
397+
* of the event isn't important.
398+
* Integrations that process webhooks as they come in should use constructEvent method instead.
399+
*
400+
* @param payload The decoded webhook payload string.
401+
* @param header The decoded `stripe-signature` header value.
402+
* @param details Parsed header containing timestamp and signatures.
403+
* @param expectedSignature HMAC signature computed from the payload and secret.
404+
* @param tolerance Maximum allowed age of the event in seconds. Use 0 to skip timestamp tolerance verification.
405+
* @param suspectPayloadType Whether the payload was not a string or Buffer.
406+
* @param secretContainsWhitespace Whether the signing secret contains whitespace.
407+
* @param receivedAt - Timestamp for age calculation
408+
* @returns `true` if the signature and timestamp are valid.
409+
*
410+
* @throws {StripeSignatureVerificationError} If verification fails.
411+
*/
377412
function validateComputedSignature(
378413
payload: string,
379414
header: string,
@@ -436,11 +471,12 @@ export function createWebhooks(
436471

437472
function parseHeader(
438473
header: WebhookHeader,
439-
scheme: string
474+
scheme?: string
440475
): WebhookParsedHeader | null {
441476
if (typeof header !== 'string') {
442477
return null;
443478
}
479+
scheme = scheme || signature.EXPECTED_SCHEME;
444480

445481
return header.split(',').reduce<WebhookParsedHeader>(
446482
(accum, item) => {
@@ -478,7 +514,13 @@ export function createWebhooks(
478514

479515
function prepareOptions(
480516
opts: WebhookTestHeaderOptions
481-
): WebhookTestHeaderOptions & {
517+
): Omit<
518+
WebhookTestHeaderOptions,
519+
'timestamp' | 'scheme' | 'cryptoProvider'
520+
> & {
521+
timestamp: number;
522+
scheme: string;
523+
cryptoProvider: CryptoProvider;
482524
payloadString: string;
483525
generateHeaderString: (signature: string) => string;
484526
} {
@@ -489,7 +531,8 @@ export function createWebhooks(
489531
}
490532

491533
const timestamp =
492-
Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000);
534+
(opts.timestamp && Math.floor(opts.timestamp)) ||
535+
Math.floor(Date.now() / 1000);
493536
const scheme = opts.scheme || signature.EXPECTED_SCHEME;
494537
const cryptoProvider = opts.cryptoProvider || getCryptoProvider();
495538
const payloadString = `${timestamp}.${opts.payload}`;

src/lib.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Agent} from 'http';
22

33
import {RequestAuthenticator} from './Types.js';
44
import {ApiVersion} from './apiVersion.js';
5-
import {HttpClient} from './net/HttpClient.js';
5+
import {HttpClientInterface} from './net/HttpClient.js';
66
import {StripeContext} from './StripeContext.js';
77

88
export declare class StripeResource {
@@ -59,7 +59,7 @@ export interface StripeConfig {
5959
* Useful for making requests in contexts other than NodeJS (eg. using
6060
* `fetch`).
6161
*/
62-
httpClient?: HttpClient;
62+
httpClient?: HttpClientInterface;
6363

6464
/**
6565
* Request timeout in milliseconds.
@@ -89,6 +89,13 @@ export interface StripeConfig {
8989
*/
9090
telemetry?: boolean;
9191

92+
/**
93+
* Pass `emitEventBodies: true` to include request and response bodies
94+
* in the `request` and `response` events emitted by the Stripe client.
95+
* Bodies may contain sensitive data. Defaults to false.
96+
*/
97+
emitEventBodies?: boolean;
98+
9299
/**
93100
* For plugin authors to identify their code.
94101
* @docs https://stripe.com/docs/building-plugins?lang=node#setappinfo

src/net/FetchHttpClient.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,16 @@ export class FetchHttpClientResponse extends HttpClientResponse
183183
}
184184

185185
toJSON(): Promise<any> {
186-
return this._res.json();
186+
return this._res.text().then((text) => {
187+
try {
188+
return JSON.parse(text);
189+
} catch (e) {
190+
if (e instanceof Error) {
191+
(e as any).rawBody = text;
192+
}
193+
throw e;
194+
}
195+
});
187196
}
188197

189198
static _transformHeadersToObject(headers: Headers): ResponseHeaders {

src/net/NodeHttpClient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ export class NodeHttpClientResponse extends HttpClientResponse
137137
try {
138138
resolve(JSON.parse(response));
139139
} catch (e) {
140+
if (e instanceof Error) {
141+
(e as any).rawBody = response;
142+
}
140143
reject(e);
141144
}
142145
});

src/resources/V2/Billing/LicenseFees.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ export interface LicenseFee {
218218
*/
219219
display_name: string;
220220

221+
/**
222+
* The ID of the license fee's most recently created version.
223+
*/
224+
latest_version: string;
225+
221226
/**
222227
* A Licensed Item represents a billable item whose pricing is based on license fees. You can use license fees
223228
* to specify the pricing and create subscriptions to these items.
@@ -249,6 +254,17 @@ export interface LicenseFee {
249254
*/
250255
service_cycle: V2.Billing.LicenseFee.ServiceCycle;
251256

257+
/**
258+
* The interval for assessing service.
259+
*/
260+
service_interval: V2.Billing.LicenseFee.ServiceInterval;
261+
262+
/**
263+
* The length of the interval for assessing service. For example, set this to 3 and `service_interval` to `"month"` in
264+
* order to specify quarterly service.
265+
*/
266+
service_interval_count: number;
267+
252268
/**
253269
* The Stripe Tax tax behavior - whether the license fee is inclusive or exclusive of tax.
254270
*/
@@ -293,6 +309,8 @@ export namespace V2 {
293309
interval_count: number;
294310
}
295311

312+
export type ServiceInterval = 'day' | 'month' | 'week' | 'year';
313+
296314
export type TaxBehavior = 'exclusive' | 'inclusive';
297315

298316
export type TieringMode = 'graduated' | 'volume';

0 commit comments

Comments
 (0)