checkout-intents 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,23 @@ import * as CheckoutIntentsAPI from './checkout-intents';
5
5
  import { APIPromise } from '../core/api-promise';
6
6
  import { RequestOptions } from '../internal/request-options';
7
7
  import { path } from '../internal/utils/path';
8
+ import { sleep } from '../internal/utils/sleep';
9
+ import { buildHeaders } from '../internal/headers';
10
+
11
+ /**
12
+ * Options for polling operations.
13
+ */
14
+ export interface PollOptions {
15
+ /**
16
+ * The interval in milliseconds between polling attempts.
17
+ */
18
+ pollIntervalMs?: number;
19
+
20
+ /**
21
+ * The maximum number of polling attempts before timing out.
22
+ */
23
+ maxAttempts?: number;
24
+ }
8
25
 
9
26
  export class CheckoutIntentsResource extends APIResource {
10
27
  /**
@@ -96,6 +113,210 @@ export class CheckoutIntentsResource extends APIResource {
96
113
  ): APIPromise<CheckoutIntent> {
97
114
  return this._client.post(path`/api/v1/checkout-intents/${id}/confirm`, { body, ...options });
98
115
  }
116
+
117
+ /**
118
+ * A helper to poll a checkout intent until a specific condition is met.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * // Poll until completed or failed
123
+ * const checkoutIntent = await client.checkoutIntents.pollUntil(
124
+ * 'id',
125
+ * (intent) => intent.state === 'completed' || intent.state === 'failed'
126
+ * );
127
+ * ```
128
+ */
129
+ private async pollUntil<T extends CheckoutIntent = CheckoutIntent>(
130
+ id: string,
131
+ condition: (intent: CheckoutIntent) => intent is T,
132
+ options?: RequestOptions & PollOptions,
133
+ ): Promise<T> {
134
+ const maxAttempts = options?.maxAttempts ?? 120; // Default: 120 attempts
135
+ const pollIntervalMs = options?.pollIntervalMs ?? 5000; // Default: 5 seconds
136
+ let attempts = 0;
137
+
138
+ const headers = buildHeaders([
139
+ options?.headers,
140
+ {
141
+ 'X-Stainless-Poll-Helper': 'true',
142
+ 'X-Stainless-Custom-Poll-Interval': pollIntervalMs.toString(),
143
+ },
144
+ ]);
145
+
146
+ while (attempts < maxAttempts) {
147
+ const { data: intent, response } = await this.retrieve(id, {
148
+ ...options,
149
+ headers: { ...options?.headers, ...headers.values },
150
+ }).withResponse();
151
+
152
+ // Check if condition is met
153
+ if (condition(intent)) {
154
+ return intent;
155
+ }
156
+
157
+ attempts++;
158
+
159
+ // If we've reached max attempts, throw an error
160
+ if (attempts >= maxAttempts) {
161
+ throw new Error(
162
+ `Polling timeout: condition not met after ${maxAttempts} attempts (${
163
+ (maxAttempts * pollIntervalMs) / 1000
164
+ }s)`,
165
+ );
166
+ }
167
+
168
+ // Check if server suggests a polling interval
169
+ let sleepInterval = pollIntervalMs;
170
+ const headerInterval = response.headers.get('retry-after-ms');
171
+ if (headerInterval) {
172
+ const headerIntervalMs = parseInt(headerInterval);
173
+ if (!isNaN(headerIntervalMs)) {
174
+ sleepInterval = headerIntervalMs;
175
+ }
176
+ }
177
+
178
+ await sleep(sleepInterval);
179
+ }
180
+
181
+ // This should never be reached due to the throw above, but TypeScript needs it
182
+ throw new Error(`Polling timeout: condition not met after ${maxAttempts} attempts`);
183
+ }
184
+
185
+ /**
186
+ * A helper to poll a checkout intent until it reaches a completed state
187
+ * (completed or failed).
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const checkoutIntent = await client.checkoutIntents.pollUntilCompleted('id');
192
+ * if (checkoutIntent.state === 'completed') {
193
+ * console.log('Order placed successfully!');
194
+ * } else if (checkoutIntent.state === 'failed') {
195
+ * console.log('Order failed:', checkoutIntent.failureReason);
196
+ * }
197
+ * ```
198
+ */
199
+ async pollUntilCompleted(
200
+ id: string,
201
+ options?: RequestOptions & PollOptions,
202
+ ): Promise<CheckoutIntent.CompletedCheckoutIntent | CheckoutIntent.FailedCheckoutIntent> {
203
+ return this.pollUntil(
204
+ id,
205
+ (intent): intent is CheckoutIntent.CompletedCheckoutIntent | CheckoutIntent.FailedCheckoutIntent =>
206
+ intent.state === 'completed' || intent.state === 'failed',
207
+ options,
208
+ );
209
+ }
210
+
211
+ /**
212
+ * A helper to poll a checkout intent until it's ready for confirmation
213
+ * (awaiting_confirmation state) or has failed. This is typically used after
214
+ * creating a checkout intent to wait for the offer to be retrieved from the merchant.
215
+ *
216
+ * The intent can reach awaiting_confirmation (success - ready to confirm) or failed
217
+ * (offer retrieval failed). Always check the state after polling.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const intent = await client.checkoutIntents.pollUntilAwaitingConfirmation('id');
222
+ *
223
+ * if (intent.state === 'awaiting_confirmation') {
224
+ * // Review the offer before confirming
225
+ * console.log('Total:', intent.offer.cost.total);
226
+ * } else if (intent.state === 'failed') {
227
+ * // Handle failure (e.g., offer retrieval failed, product out of stock)
228
+ * console.log('Failed:', intent.failureReason);
229
+ * }
230
+ * ```
231
+ */
232
+ async pollUntilAwaitingConfirmation(
233
+ id: string,
234
+ options?: RequestOptions & PollOptions,
235
+ ): Promise<CheckoutIntent.AwaitingConfirmationCheckoutIntent | CheckoutIntent.FailedCheckoutIntent> {
236
+ return this.pollUntil(
237
+ id,
238
+ (
239
+ intent,
240
+ ): intent is CheckoutIntent.AwaitingConfirmationCheckoutIntent | CheckoutIntent.FailedCheckoutIntent =>
241
+ intent.state === 'awaiting_confirmation' || intent.state === 'failed',
242
+ options,
243
+ );
244
+ }
245
+
246
+ /**
247
+ * A helper to create a checkout intent and poll until it's ready for confirmation.
248
+ * This follows the Rye documented flow: create → poll until awaiting_confirmation.
249
+ *
250
+ * After this method completes, you should review the offer (pricing, shipping, taxes)
251
+ * with the user before calling confirm().
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * // Phase 1: Create and wait for offer
256
+ * const intent = await client.checkoutIntents.createAndPoll({
257
+ * buyer: {
258
+ * address1: '123 Main St',
259
+ * city: 'New York',
260
+ * country: 'United States',
261
+ * email: 'john.doe@example.com',
262
+ * firstName: 'John',
263
+ * lastName: 'Doe',
264
+ * phone: '+1234567890',
265
+ * postalCode: '10001',
266
+ * province: 'NY',
267
+ * },
268
+ * productUrl: 'https://example.com/product',
269
+ * quantity: 1,
270
+ * });
271
+ *
272
+ * // Review the offer with the user
273
+ * console.log('Total:', intent.offer.cost.total);
274
+ *
275
+ * // Phase 2: Confirm with payment
276
+ * const completed = await client.checkoutIntents.confirmAndPoll(intent.id, {
277
+ * paymentMethod: { type: 'stripe_token', stripeToken: 'tok_visa' }
278
+ * });
279
+ * ```
280
+ */
281
+ async createAndPoll(
282
+ body: CheckoutIntentCreateParams,
283
+ options?: RequestOptions & PollOptions,
284
+ ): Promise<CheckoutIntent.AwaitingConfirmationCheckoutIntent | CheckoutIntent.FailedCheckoutIntent> {
285
+ const intent = await this.create(body, options);
286
+ return this.pollUntilAwaitingConfirmation(intent.id, options);
287
+ }
288
+
289
+ /**
290
+ * A helper to confirm a checkout intent and poll until it reaches a completed state
291
+ * (completed or failed).
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * const checkoutIntent = await client.checkoutIntents.confirmAndPoll(
296
+ * 'id',
297
+ * {
298
+ * paymentMethod: {
299
+ * stripeToken: 'tok_1RkrWWHGDlstla3f1Fc7ZrhH',
300
+ * type: 'stripe_token',
301
+ * },
302
+ * }
303
+ * );
304
+ *
305
+ * if (checkoutIntent.state === 'completed') {
306
+ * console.log('Order placed successfully!');
307
+ * } else if (checkoutIntent.state === 'failed') {
308
+ * console.log('Order failed:', checkoutIntent.failureReason);
309
+ * }
310
+ * ```
311
+ */
312
+ async confirmAndPoll(
313
+ id: string,
314
+ body: CheckoutIntentConfirmParams,
315
+ options?: RequestOptions & PollOptions,
316
+ ): Promise<CheckoutIntent.CompletedCheckoutIntent | CheckoutIntent.FailedCheckoutIntent> {
317
+ const intent = await this.confirm(id, body, options);
318
+ return this.pollUntilCompleted(intent.id, options);
319
+ }
99
320
  }
100
321
 
101
322
  export interface BaseCheckoutIntent {
@@ -301,6 +522,7 @@ export declare namespace CheckoutIntentsResource {
301
522
  type Money as Money,
302
523
  type Offer as Offer,
303
524
  type PaymentMethod as PaymentMethod,
525
+ type PollOptions as PollOptions,
304
526
  type VariantSelection as VariantSelection,
305
527
  type CheckoutIntentCreateParams as CheckoutIntentCreateParams,
306
528
  type CheckoutIntentAddPaymentParams as CheckoutIntentAddPaymentParams,
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.0'; // x-release-please-version
1
+ export const VERSION = '0.3.0'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.3.0";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.3.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.2.0'; // x-release-please-version
4
+ exports.VERSION = '0.3.0'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.2.0'; // x-release-please-version
1
+ export const VERSION = '0.3.0'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map