@usethrottle/cart 3.3.0 → 3.5.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.
- package/README.md +43 -7
- package/dist/index.cjs +109 -0
- package/dist/index.d.cts +103 -1
- package/dist/index.d.ts +103 -1
- package/dist/index.js +108 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<img src="https://raw.githubusercontent.com/Epic-Design-Labs/app-throttle/main/packages/brand/assets/throttle-logo.png" alt="Throttle" height="56" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
# @usethrottle/cart
|
|
7
6
|
|
|
8
7
|
Typed Node.js REST client for the Throttle Cart API.
|
|
@@ -62,6 +61,43 @@ are rejected by the API.
|
|
|
62
61
|
|
|
63
62
|
All monetary values are integers in the smallest currency unit (cents for USD).
|
|
64
63
|
|
|
64
|
+
## Browser carts (backend-less)
|
|
65
|
+
|
|
66
|
+
`CartClient` carries a secret `sk_` key and must run on your server. For
|
|
67
|
+
JAMstack / frontend-only storefronts that have no backend, use
|
|
68
|
+
`CartSessionClient` instead — it creates and owns a Throttle cart directly from
|
|
69
|
+
the browser, authorized by a publishable `pk_` quote token (the same token used
|
|
70
|
+
by `StorefrontQuoteClient`) plus your application's origin allowlist.
|
|
71
|
+
|
|
72
|
+
Set your allowed origins first (`PUT /api/v1/embed-config`), then:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { CartSessionClient } from '@usethrottle/cart';
|
|
76
|
+
|
|
77
|
+
const cart = new CartSessionClient({
|
|
78
|
+
applicationId: '7f9d4c8a-5b2e-4f16-9a73-2d1e5c8b6f40',
|
|
79
|
+
environmentId: 'a1b2c3d4-...',
|
|
80
|
+
quoteToken: 'pk_live_...',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create a new session (store cart.cartSessionId in localStorage),
|
|
84
|
+
// or resume one you saved: cart.resume(savedId)
|
|
85
|
+
await cart.create({ currency: 'USD' });
|
|
86
|
+
|
|
87
|
+
await cart.addItem({ name: 'Premium Widget', unitPrice: 2999, quantity: 2 });
|
|
88
|
+
await cart.applyDiscount('SAVE10');
|
|
89
|
+
await cart.selectShipping({ methodId: 'fedex_ground', displayName: 'FedEx Ground', rateAmount: 599 });
|
|
90
|
+
|
|
91
|
+
// Hand off to hosted/embedded checkout and redirect the buyer.
|
|
92
|
+
const { checkoutUrl } = await cart.checkout({
|
|
93
|
+
returnUrl: 'https://shop.example.com/thanks',
|
|
94
|
+
cancelUrl: 'https://shop.example.com/cart',
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The opaque `cart_…` session id is scoped to exactly one cart. See the
|
|
99
|
+
[cart sessions guide](https://docs.usethrottle.dev/developers/cart-sessions).
|
|
100
|
+
|
|
65
101
|
## Net-N invoice terms
|
|
66
102
|
|
|
67
103
|
Cart-level invoice terms are optional. Pass `netN` on cart create or update to
|
|
@@ -223,12 +259,12 @@ resolvable base returns `400 image_url_unresolvable`.
|
|
|
223
259
|
|
|
224
260
|
## Client options
|
|
225
261
|
|
|
226
|
-
| Option | Default | Description
|
|
227
|
-
| ----------- | ----------------------------- |
|
|
228
|
-
| `apiKey` | — | **Required.** Your Throttle secret key (`sk_…`).
|
|
229
|
-
| `baseUrl` | `https://api.usethrottle.dev` | Optional override for
|
|
230
|
-
| `timeoutMs` | `30000` | Per-request abort timeout in ms.
|
|
231
|
-
| `fetch` | `globalThis.fetch` | Custom fetch implementation (e.g. `node-fetch`).
|
|
262
|
+
| Option | Default | Description |
|
|
263
|
+
| ----------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
264
|
+
| `apiKey` | — | **Required.** Your Throttle secret key (`sk_…`). |
|
|
265
|
+
| `baseUrl` | `https://api.usethrottle.dev` | Optional Throttle API host override for self-hosted/proxy setups. Workspace environment selection comes from the API key. |
|
|
266
|
+
| `timeoutMs` | `30000` | Per-request abort timeout in ms. |
|
|
267
|
+
| `fetch` | `globalThis.fetch` | Custom fetch implementation (e.g. `node-fetch`). |
|
|
232
268
|
|
|
233
269
|
## See also
|
|
234
270
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CartClient: () => CartClient,
|
|
24
|
+
CartSessionClient: () => CartSessionClient,
|
|
24
25
|
StorefrontQuoteClient: () => StorefrontQuoteClient,
|
|
25
26
|
ThrottleApiError: () => ThrottleApiError,
|
|
26
27
|
ThrottleError: () => import_errors2.ThrottleError,
|
|
@@ -187,9 +188,117 @@ var StorefrontQuoteClient = class {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
};
|
|
191
|
+
|
|
192
|
+
// src/cart-session.ts
|
|
193
|
+
var CartSessionClient = class {
|
|
194
|
+
applicationId;
|
|
195
|
+
environmentId;
|
|
196
|
+
quoteToken;
|
|
197
|
+
baseUrl;
|
|
198
|
+
fetchImpl;
|
|
199
|
+
timeoutMs;
|
|
200
|
+
origin;
|
|
201
|
+
sessionId = null;
|
|
202
|
+
constructor(opts) {
|
|
203
|
+
if (!opts.applicationId) throw new Error("CartSessionClient: applicationId is required");
|
|
204
|
+
if (!opts.environmentId) throw new Error("CartSessionClient: environmentId is required");
|
|
205
|
+
if (!opts.quoteToken?.startsWith("pk_")) {
|
|
206
|
+
throw new Error("CartSessionClient: quoteToken must be a publishable pk_ token");
|
|
207
|
+
}
|
|
208
|
+
this.applicationId = opts.applicationId;
|
|
209
|
+
this.environmentId = opts.environmentId;
|
|
210
|
+
this.quoteToken = opts.quoteToken;
|
|
211
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.usethrottle.dev").replace(/\/+$/, "");
|
|
212
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
213
|
+
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
214
|
+
this.origin = opts.origin;
|
|
215
|
+
}
|
|
216
|
+
/** The current bound session id (after `create()`/`resume()`), or null. */
|
|
217
|
+
get cartSessionId() {
|
|
218
|
+
return this.sessionId;
|
|
219
|
+
}
|
|
220
|
+
/** Bind to an existing session id — e.g. one restored from `localStorage`. */
|
|
221
|
+
resume(cartSessionId) {
|
|
222
|
+
this.sessionId = cartSessionId;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
/** Create a new cart session and bind this client to it. */
|
|
226
|
+
async create(input = {}) {
|
|
227
|
+
const data = await this.request("POST", "/api/v1/cart-sessions", {
|
|
228
|
+
applicationId: this.applicationId,
|
|
229
|
+
environmentId: this.environmentId,
|
|
230
|
+
quoteToken: this.quoteToken,
|
|
231
|
+
...input
|
|
232
|
+
});
|
|
233
|
+
this.sessionId = data.cartSessionId;
|
|
234
|
+
return data;
|
|
235
|
+
}
|
|
236
|
+
/** Read the current session + cart snapshot. */
|
|
237
|
+
get() {
|
|
238
|
+
return this.request("GET", this.basePath());
|
|
239
|
+
}
|
|
240
|
+
async addItem(input) {
|
|
241
|
+
return (await this.request("POST", `${this.basePath()}/items`, input)).cart;
|
|
242
|
+
}
|
|
243
|
+
async updateItem(itemId, input) {
|
|
244
|
+
return (await this.request("PATCH", `${this.basePath()}/items/${encodeURIComponent(itemId)}`, input)).cart;
|
|
245
|
+
}
|
|
246
|
+
async removeItem(itemId) {
|
|
247
|
+
return (await this.request("DELETE", `${this.basePath()}/items/${encodeURIComponent(itemId)}`)).cart;
|
|
248
|
+
}
|
|
249
|
+
async selectShipping(input) {
|
|
250
|
+
return (await this.request("POST", `${this.basePath()}/shipping`, input)).cart;
|
|
251
|
+
}
|
|
252
|
+
async clearShipping() {
|
|
253
|
+
return (await this.request("DELETE", `${this.basePath()}/shipping`)).cart;
|
|
254
|
+
}
|
|
255
|
+
async applyDiscount(code) {
|
|
256
|
+
return (await this.request("POST", `${this.basePath()}/discount`, { code })).cart;
|
|
257
|
+
}
|
|
258
|
+
async removeDiscount() {
|
|
259
|
+
return (await this.request("DELETE", `${this.basePath()}/discount`)).cart;
|
|
260
|
+
}
|
|
261
|
+
/** Hand off to hosted/embedded checkout. Marks the session converted (no more edits). */
|
|
262
|
+
checkout(input) {
|
|
263
|
+
return this.request("POST", `${this.basePath()}/checkout-session`, input);
|
|
264
|
+
}
|
|
265
|
+
basePath() {
|
|
266
|
+
if (!this.sessionId) {
|
|
267
|
+
throw new Error("CartSessionClient: no active session \u2014 call create() or resume(id) first");
|
|
268
|
+
}
|
|
269
|
+
return `/api/v1/cart-sessions/${encodeURIComponent(this.sessionId)}`;
|
|
270
|
+
}
|
|
271
|
+
async request(method, path, body) {
|
|
272
|
+
const ctrl = new AbortController();
|
|
273
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
274
|
+
try {
|
|
275
|
+
const headers = { "content-type": "application/json" };
|
|
276
|
+
if (this.origin) headers.origin = this.origin;
|
|
277
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
278
|
+
method,
|
|
279
|
+
headers,
|
|
280
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
281
|
+
signal: ctrl.signal
|
|
282
|
+
});
|
|
283
|
+
const json = await res.json().catch(() => ({}));
|
|
284
|
+
if (!res.ok) {
|
|
285
|
+
throw new ThrottleApiError({
|
|
286
|
+
code: json?.error?.code ?? "unknown_error",
|
|
287
|
+
message: json?.error?.message ?? `HTTP ${res.status}`,
|
|
288
|
+
statusCode: res.status,
|
|
289
|
+
details: json?.error?.details
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return json.data;
|
|
293
|
+
} finally {
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
190
298
|
// Annotate the CommonJS export names for ESM import in node:
|
|
191
299
|
0 && (module.exports = {
|
|
192
300
|
CartClient,
|
|
301
|
+
CartSessionClient,
|
|
193
302
|
StorefrontQuoteClient,
|
|
194
303
|
ThrottleApiError,
|
|
195
304
|
ThrottleError,
|
package/dist/index.d.cts
CHANGED
|
@@ -119,6 +119,13 @@ interface AddLineItemInput {
|
|
|
119
119
|
count: number | null;
|
|
120
120
|
} | null;
|
|
121
121
|
imageUrl?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Whether this item is physically shippable. For external/ad-hoc catalogs
|
|
124
|
+
* with no Throttle product behind the item. Omit to infer from `type`
|
|
125
|
+
* (everything except `service` ships); set explicitly to force-include or
|
|
126
|
+
* force-exclude the item from `calculated` shipping.
|
|
127
|
+
*/
|
|
128
|
+
requiresShipping?: boolean;
|
|
122
129
|
metadata?: Record<string, unknown>;
|
|
123
130
|
}
|
|
124
131
|
interface UpdateLineItemInput {
|
|
@@ -199,6 +206,14 @@ interface ShippingTaxCartCalculateInput {
|
|
|
199
206
|
kind?: 'cart_estimate' | 'checkout_final';
|
|
200
207
|
selectedShippingMethodId?: string | null;
|
|
201
208
|
persist?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Inline shipping address. When set it is persisted to the cart (like
|
|
211
|
+
* `carts.update`) before calculation, so you can set the destination and get
|
|
212
|
+
* rates in a single request instead of a separate update + calculate.
|
|
213
|
+
*/
|
|
214
|
+
shippingAddress?: ShippingTaxAddress | null;
|
|
215
|
+
/** Inline billing address; persisted to the cart before calculation. */
|
|
216
|
+
billingAddress?: ShippingTaxAddress | null;
|
|
202
217
|
}
|
|
203
218
|
interface ShippingTaxMethod {
|
|
204
219
|
id: string;
|
|
@@ -362,6 +377,93 @@ declare class StorefrontQuoteClient {
|
|
|
362
377
|
quote(input: ShippingTaxQuoteInput): Promise<ShippingTaxCalculationResponse>;
|
|
363
378
|
}
|
|
364
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Browser client for backend-less cart ownership. Unlike {@link CartClient}
|
|
382
|
+
* (which needs a server-side `sk_` key), `CartSessionClient` runs in the browser:
|
|
383
|
+
* it creates and mutates a Throttle cart authorized by a publishable quote token
|
|
384
|
+
* (`pk_…`, the same token used by {@link StorefrontQuoteClient}) plus the
|
|
385
|
+
* application origin allowlist, and an opaque `cart_…` session id scoped to that
|
|
386
|
+
* one cart.
|
|
387
|
+
*
|
|
388
|
+
* Typical use:
|
|
389
|
+
* ```ts
|
|
390
|
+
* const cart = new CartSessionClient({ applicationId, environmentId, quoteToken });
|
|
391
|
+
* await cart.create(); // or cart.resume(savedId)
|
|
392
|
+
* await cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 });
|
|
393
|
+
* const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
interface CartSessionClientOptions {
|
|
397
|
+
/** Application UUID. */
|
|
398
|
+
applicationId: string;
|
|
399
|
+
/** Workspace environment UUID the publishable token was minted for. */
|
|
400
|
+
environmentId: string;
|
|
401
|
+
/** Publishable storefront quote token (`pk_…`). */
|
|
402
|
+
quoteToken: string;
|
|
403
|
+
baseUrl?: string;
|
|
404
|
+
fetch?: typeof globalThis.fetch;
|
|
405
|
+
timeoutMs?: number;
|
|
406
|
+
/**
|
|
407
|
+
* Origin to send. Browsers set the `Origin` header automatically (and it can't
|
|
408
|
+
* be overridden from JS), so this is only needed for non-browser callers
|
|
409
|
+
* (SSR, tests). It must be on the application's allowlist.
|
|
410
|
+
*/
|
|
411
|
+
origin?: string;
|
|
412
|
+
}
|
|
413
|
+
interface CreateCartSessionInput {
|
|
414
|
+
currency?: string;
|
|
415
|
+
netN?: number;
|
|
416
|
+
metadata?: Record<string, unknown>;
|
|
417
|
+
}
|
|
418
|
+
interface CartSession {
|
|
419
|
+
cartSessionId: string;
|
|
420
|
+
status: string;
|
|
421
|
+
expiresAt: string;
|
|
422
|
+
cart: Cart;
|
|
423
|
+
}
|
|
424
|
+
interface CheckoutHandoffInput {
|
|
425
|
+
returnUrl: string;
|
|
426
|
+
cancelUrl: string;
|
|
427
|
+
customerEmail?: string;
|
|
428
|
+
allowedMethods?: string[];
|
|
429
|
+
}
|
|
430
|
+
interface CheckoutHandoff {
|
|
431
|
+
cartSessionId: string;
|
|
432
|
+
checkoutSessionId: string;
|
|
433
|
+
checkoutUrl: string;
|
|
434
|
+
expiresAt: string;
|
|
435
|
+
}
|
|
436
|
+
declare class CartSessionClient {
|
|
437
|
+
private readonly applicationId;
|
|
438
|
+
private readonly environmentId;
|
|
439
|
+
private readonly quoteToken;
|
|
440
|
+
private readonly baseUrl;
|
|
441
|
+
private readonly fetchImpl;
|
|
442
|
+
private readonly timeoutMs;
|
|
443
|
+
private readonly origin?;
|
|
444
|
+
private sessionId;
|
|
445
|
+
constructor(opts: CartSessionClientOptions);
|
|
446
|
+
/** The current bound session id (after `create()`/`resume()`), or null. */
|
|
447
|
+
get cartSessionId(): string | null;
|
|
448
|
+
/** Bind to an existing session id — e.g. one restored from `localStorage`. */
|
|
449
|
+
resume(cartSessionId: string): this;
|
|
450
|
+
/** Create a new cart session and bind this client to it. */
|
|
451
|
+
create(input?: CreateCartSessionInput): Promise<CartSession>;
|
|
452
|
+
/** Read the current session + cart snapshot. */
|
|
453
|
+
get(): Promise<CartSession>;
|
|
454
|
+
addItem(input: AddLineItemInput): Promise<Cart>;
|
|
455
|
+
updateItem(itemId: string, input: UpdateLineItemInput): Promise<Cart>;
|
|
456
|
+
removeItem(itemId: string): Promise<Cart>;
|
|
457
|
+
selectShipping(input: SelectShippingInput): Promise<Cart>;
|
|
458
|
+
clearShipping(): Promise<Cart>;
|
|
459
|
+
applyDiscount(code: string): Promise<Cart>;
|
|
460
|
+
removeDiscount(): Promise<Cart>;
|
|
461
|
+
/** Hand off to hosted/embedded checkout. Marks the session converted (no more edits). */
|
|
462
|
+
checkout(input: CheckoutHandoffInput): Promise<CheckoutHandoff>;
|
|
463
|
+
private basePath;
|
|
464
|
+
private request;
|
|
465
|
+
}
|
|
466
|
+
|
|
365
467
|
/**
|
|
366
468
|
* Thrown on a non-2xx response from the Throttle Cart API.
|
|
367
469
|
*
|
|
@@ -379,4 +481,4 @@ declare class ThrottleApiError extends ThrottleError {
|
|
|
379
481
|
});
|
|
380
482
|
}
|
|
381
483
|
|
|
382
|
-
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
|
484
|
+
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CartSession, CartSessionClient, type CartSessionClientOptions, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
package/dist/index.d.ts
CHANGED
|
@@ -119,6 +119,13 @@ interface AddLineItemInput {
|
|
|
119
119
|
count: number | null;
|
|
120
120
|
} | null;
|
|
121
121
|
imageUrl?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Whether this item is physically shippable. For external/ad-hoc catalogs
|
|
124
|
+
* with no Throttle product behind the item. Omit to infer from `type`
|
|
125
|
+
* (everything except `service` ships); set explicitly to force-include or
|
|
126
|
+
* force-exclude the item from `calculated` shipping.
|
|
127
|
+
*/
|
|
128
|
+
requiresShipping?: boolean;
|
|
122
129
|
metadata?: Record<string, unknown>;
|
|
123
130
|
}
|
|
124
131
|
interface UpdateLineItemInput {
|
|
@@ -199,6 +206,14 @@ interface ShippingTaxCartCalculateInput {
|
|
|
199
206
|
kind?: 'cart_estimate' | 'checkout_final';
|
|
200
207
|
selectedShippingMethodId?: string | null;
|
|
201
208
|
persist?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Inline shipping address. When set it is persisted to the cart (like
|
|
211
|
+
* `carts.update`) before calculation, so you can set the destination and get
|
|
212
|
+
* rates in a single request instead of a separate update + calculate.
|
|
213
|
+
*/
|
|
214
|
+
shippingAddress?: ShippingTaxAddress | null;
|
|
215
|
+
/** Inline billing address; persisted to the cart before calculation. */
|
|
216
|
+
billingAddress?: ShippingTaxAddress | null;
|
|
202
217
|
}
|
|
203
218
|
interface ShippingTaxMethod {
|
|
204
219
|
id: string;
|
|
@@ -362,6 +377,93 @@ declare class StorefrontQuoteClient {
|
|
|
362
377
|
quote(input: ShippingTaxQuoteInput): Promise<ShippingTaxCalculationResponse>;
|
|
363
378
|
}
|
|
364
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Browser client for backend-less cart ownership. Unlike {@link CartClient}
|
|
382
|
+
* (which needs a server-side `sk_` key), `CartSessionClient` runs in the browser:
|
|
383
|
+
* it creates and mutates a Throttle cart authorized by a publishable quote token
|
|
384
|
+
* (`pk_…`, the same token used by {@link StorefrontQuoteClient}) plus the
|
|
385
|
+
* application origin allowlist, and an opaque `cart_…` session id scoped to that
|
|
386
|
+
* one cart.
|
|
387
|
+
*
|
|
388
|
+
* Typical use:
|
|
389
|
+
* ```ts
|
|
390
|
+
* const cart = new CartSessionClient({ applicationId, environmentId, quoteToken });
|
|
391
|
+
* await cart.create(); // or cart.resume(savedId)
|
|
392
|
+
* await cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 });
|
|
393
|
+
* const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
interface CartSessionClientOptions {
|
|
397
|
+
/** Application UUID. */
|
|
398
|
+
applicationId: string;
|
|
399
|
+
/** Workspace environment UUID the publishable token was minted for. */
|
|
400
|
+
environmentId: string;
|
|
401
|
+
/** Publishable storefront quote token (`pk_…`). */
|
|
402
|
+
quoteToken: string;
|
|
403
|
+
baseUrl?: string;
|
|
404
|
+
fetch?: typeof globalThis.fetch;
|
|
405
|
+
timeoutMs?: number;
|
|
406
|
+
/**
|
|
407
|
+
* Origin to send. Browsers set the `Origin` header automatically (and it can't
|
|
408
|
+
* be overridden from JS), so this is only needed for non-browser callers
|
|
409
|
+
* (SSR, tests). It must be on the application's allowlist.
|
|
410
|
+
*/
|
|
411
|
+
origin?: string;
|
|
412
|
+
}
|
|
413
|
+
interface CreateCartSessionInput {
|
|
414
|
+
currency?: string;
|
|
415
|
+
netN?: number;
|
|
416
|
+
metadata?: Record<string, unknown>;
|
|
417
|
+
}
|
|
418
|
+
interface CartSession {
|
|
419
|
+
cartSessionId: string;
|
|
420
|
+
status: string;
|
|
421
|
+
expiresAt: string;
|
|
422
|
+
cart: Cart;
|
|
423
|
+
}
|
|
424
|
+
interface CheckoutHandoffInput {
|
|
425
|
+
returnUrl: string;
|
|
426
|
+
cancelUrl: string;
|
|
427
|
+
customerEmail?: string;
|
|
428
|
+
allowedMethods?: string[];
|
|
429
|
+
}
|
|
430
|
+
interface CheckoutHandoff {
|
|
431
|
+
cartSessionId: string;
|
|
432
|
+
checkoutSessionId: string;
|
|
433
|
+
checkoutUrl: string;
|
|
434
|
+
expiresAt: string;
|
|
435
|
+
}
|
|
436
|
+
declare class CartSessionClient {
|
|
437
|
+
private readonly applicationId;
|
|
438
|
+
private readonly environmentId;
|
|
439
|
+
private readonly quoteToken;
|
|
440
|
+
private readonly baseUrl;
|
|
441
|
+
private readonly fetchImpl;
|
|
442
|
+
private readonly timeoutMs;
|
|
443
|
+
private readonly origin?;
|
|
444
|
+
private sessionId;
|
|
445
|
+
constructor(opts: CartSessionClientOptions);
|
|
446
|
+
/** The current bound session id (after `create()`/`resume()`), or null. */
|
|
447
|
+
get cartSessionId(): string | null;
|
|
448
|
+
/** Bind to an existing session id — e.g. one restored from `localStorage`. */
|
|
449
|
+
resume(cartSessionId: string): this;
|
|
450
|
+
/** Create a new cart session and bind this client to it. */
|
|
451
|
+
create(input?: CreateCartSessionInput): Promise<CartSession>;
|
|
452
|
+
/** Read the current session + cart snapshot. */
|
|
453
|
+
get(): Promise<CartSession>;
|
|
454
|
+
addItem(input: AddLineItemInput): Promise<Cart>;
|
|
455
|
+
updateItem(itemId: string, input: UpdateLineItemInput): Promise<Cart>;
|
|
456
|
+
removeItem(itemId: string): Promise<Cart>;
|
|
457
|
+
selectShipping(input: SelectShippingInput): Promise<Cart>;
|
|
458
|
+
clearShipping(): Promise<Cart>;
|
|
459
|
+
applyDiscount(code: string): Promise<Cart>;
|
|
460
|
+
removeDiscount(): Promise<Cart>;
|
|
461
|
+
/** Hand off to hosted/embedded checkout. Marks the session converted (no more edits). */
|
|
462
|
+
checkout(input: CheckoutHandoffInput): Promise<CheckoutHandoff>;
|
|
463
|
+
private basePath;
|
|
464
|
+
private request;
|
|
465
|
+
}
|
|
466
|
+
|
|
365
467
|
/**
|
|
366
468
|
* Thrown on a non-2xx response from the Throttle Cart API.
|
|
367
469
|
*
|
|
@@ -379,4 +481,4 @@ declare class ThrottleApiError extends ThrottleError {
|
|
|
379
481
|
});
|
|
380
482
|
}
|
|
381
483
|
|
|
382
|
-
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
|
484
|
+
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CartSession, CartSessionClient, type CartSessionClientOptions, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
package/dist/index.js
CHANGED
|
@@ -157,8 +157,116 @@ var StorefrontQuoteClient = class {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
|
+
|
|
161
|
+
// src/cart-session.ts
|
|
162
|
+
var CartSessionClient = class {
|
|
163
|
+
applicationId;
|
|
164
|
+
environmentId;
|
|
165
|
+
quoteToken;
|
|
166
|
+
baseUrl;
|
|
167
|
+
fetchImpl;
|
|
168
|
+
timeoutMs;
|
|
169
|
+
origin;
|
|
170
|
+
sessionId = null;
|
|
171
|
+
constructor(opts) {
|
|
172
|
+
if (!opts.applicationId) throw new Error("CartSessionClient: applicationId is required");
|
|
173
|
+
if (!opts.environmentId) throw new Error("CartSessionClient: environmentId is required");
|
|
174
|
+
if (!opts.quoteToken?.startsWith("pk_")) {
|
|
175
|
+
throw new Error("CartSessionClient: quoteToken must be a publishable pk_ token");
|
|
176
|
+
}
|
|
177
|
+
this.applicationId = opts.applicationId;
|
|
178
|
+
this.environmentId = opts.environmentId;
|
|
179
|
+
this.quoteToken = opts.quoteToken;
|
|
180
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.usethrottle.dev").replace(/\/+$/, "");
|
|
181
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
182
|
+
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
183
|
+
this.origin = opts.origin;
|
|
184
|
+
}
|
|
185
|
+
/** The current bound session id (after `create()`/`resume()`), or null. */
|
|
186
|
+
get cartSessionId() {
|
|
187
|
+
return this.sessionId;
|
|
188
|
+
}
|
|
189
|
+
/** Bind to an existing session id — e.g. one restored from `localStorage`. */
|
|
190
|
+
resume(cartSessionId) {
|
|
191
|
+
this.sessionId = cartSessionId;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/** Create a new cart session and bind this client to it. */
|
|
195
|
+
async create(input = {}) {
|
|
196
|
+
const data = await this.request("POST", "/api/v1/cart-sessions", {
|
|
197
|
+
applicationId: this.applicationId,
|
|
198
|
+
environmentId: this.environmentId,
|
|
199
|
+
quoteToken: this.quoteToken,
|
|
200
|
+
...input
|
|
201
|
+
});
|
|
202
|
+
this.sessionId = data.cartSessionId;
|
|
203
|
+
return data;
|
|
204
|
+
}
|
|
205
|
+
/** Read the current session + cart snapshot. */
|
|
206
|
+
get() {
|
|
207
|
+
return this.request("GET", this.basePath());
|
|
208
|
+
}
|
|
209
|
+
async addItem(input) {
|
|
210
|
+
return (await this.request("POST", `${this.basePath()}/items`, input)).cart;
|
|
211
|
+
}
|
|
212
|
+
async updateItem(itemId, input) {
|
|
213
|
+
return (await this.request("PATCH", `${this.basePath()}/items/${encodeURIComponent(itemId)}`, input)).cart;
|
|
214
|
+
}
|
|
215
|
+
async removeItem(itemId) {
|
|
216
|
+
return (await this.request("DELETE", `${this.basePath()}/items/${encodeURIComponent(itemId)}`)).cart;
|
|
217
|
+
}
|
|
218
|
+
async selectShipping(input) {
|
|
219
|
+
return (await this.request("POST", `${this.basePath()}/shipping`, input)).cart;
|
|
220
|
+
}
|
|
221
|
+
async clearShipping() {
|
|
222
|
+
return (await this.request("DELETE", `${this.basePath()}/shipping`)).cart;
|
|
223
|
+
}
|
|
224
|
+
async applyDiscount(code) {
|
|
225
|
+
return (await this.request("POST", `${this.basePath()}/discount`, { code })).cart;
|
|
226
|
+
}
|
|
227
|
+
async removeDiscount() {
|
|
228
|
+
return (await this.request("DELETE", `${this.basePath()}/discount`)).cart;
|
|
229
|
+
}
|
|
230
|
+
/** Hand off to hosted/embedded checkout. Marks the session converted (no more edits). */
|
|
231
|
+
checkout(input) {
|
|
232
|
+
return this.request("POST", `${this.basePath()}/checkout-session`, input);
|
|
233
|
+
}
|
|
234
|
+
basePath() {
|
|
235
|
+
if (!this.sessionId) {
|
|
236
|
+
throw new Error("CartSessionClient: no active session \u2014 call create() or resume(id) first");
|
|
237
|
+
}
|
|
238
|
+
return `/api/v1/cart-sessions/${encodeURIComponent(this.sessionId)}`;
|
|
239
|
+
}
|
|
240
|
+
async request(method, path, body) {
|
|
241
|
+
const ctrl = new AbortController();
|
|
242
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
243
|
+
try {
|
|
244
|
+
const headers = { "content-type": "application/json" };
|
|
245
|
+
if (this.origin) headers.origin = this.origin;
|
|
246
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
247
|
+
method,
|
|
248
|
+
headers,
|
|
249
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
250
|
+
signal: ctrl.signal
|
|
251
|
+
});
|
|
252
|
+
const json = await res.json().catch(() => ({}));
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
throw new ThrottleApiError({
|
|
255
|
+
code: json?.error?.code ?? "unknown_error",
|
|
256
|
+
message: json?.error?.message ?? `HTTP ${res.status}`,
|
|
257
|
+
statusCode: res.status,
|
|
258
|
+
details: json?.error?.details
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return json.data;
|
|
262
|
+
} finally {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
160
267
|
export {
|
|
161
268
|
CartClient,
|
|
269
|
+
CartSessionClient,
|
|
162
270
|
StorefrontQuoteClient,
|
|
163
271
|
ThrottleApiError,
|
|
164
272
|
ThrottleError2 as ThrottleError,
|