@usethrottle/checkout-react 1.0.0 → 1.1.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 CHANGED
@@ -1,9 +1,14 @@
1
+ <p align="left">
2
+ <img src="https://raw.githubusercontent.com/Epic-Design-Labs/app-throttle/main/packages/brand/assets/throttle-logo.png" alt="Throttle" height="56" />
3
+ </p>
4
+
5
+
1
6
  # @usethrottle/checkout-react
2
7
 
3
8
  React components for embedding Throttle checkout in your site. Two embed
4
9
  products + one helper hook:
5
10
 
6
- - **`<PaymentEmbed/>`** — payment-only iframe. Drops a Gr4vy-backed
11
+ - **`<PaymentEmbed/>`** — payment-only iframe. Drops a provider-backed
7
12
  card form into your existing checkout page.
8
13
  - **`<CheckoutEmbed/>`** — full-checkout iframe. Address + shipping +
9
14
  payment. Single iframe, single drop-in.
@@ -28,10 +33,10 @@ throttle embed-config set --origins https://shop.example.com
28
33
  Or via API:
29
34
 
30
35
  ```ts
31
- fetch('https://throttle-api-gff1.onrender.com/api/v1/embed-config', {
36
+ fetch('https://api.usethrottle.dev/api/v1/embed-config', {
32
37
  method: 'PUT',
33
38
  headers: { 'x-api-key': 'sk_...', 'content-type': 'application/json' },
34
- body: JSON.stringify({ allowed_origins: ['https://shop.example.com'] }),
39
+ body: JSON.stringify({ allowedOrigins: ['https://shop.example.com'] }),
35
40
  });
36
41
  ```
37
42
 
@@ -75,7 +80,9 @@ export function CheckoutPage({ sessionId }: { sessionId: string }) {
75
80
  parentOrigin="https://shop.example.com"
76
81
  primary="#ff6b00"
77
82
  logo="https://shop.example.com/logo.png"
78
- onSucceeded={({ orderId, paymentId }) => { /* ... */ }}
83
+ onSucceeded={({ orderId, paymentId }) => {
84
+ /* ... */
85
+ }}
79
86
  onStepChanged={({ step }) => {
80
87
  // step: 'cart' | 'address' | 'shipping' | 'payment'
81
88
  }}
@@ -92,7 +99,7 @@ import { useRef } from 'react';
92
99
 
93
100
  export function MyEmbed({ sessionId }: { sessionId: string }) {
94
101
  const iframeRef = useRef<HTMLIFrameElement>(null);
95
- const baseUrl = 'https://throttle-checkout.vercel.app';
102
+ const baseUrl = 'https://checkout.usethrottle.dev';
96
103
 
97
104
  useThrottleEvents({
98
105
  iframeRef,
@@ -107,6 +114,53 @@ export function MyEmbed({ sessionId }: { sessionId: string }) {
107
114
  }
108
115
  ```
109
116
 
117
+ ## `useCartSession` — backend-less cart
118
+
119
+ Own a Throttle cart directly from the browser — no backend, no secret key. The
120
+ hook wraps `@usethrottle/cart`'s `CartSessionClient`: it creates the cart lazily
121
+ on the first `addItem`, persists the session id to `localStorage`, resumes it on
122
+ the next visit, and exposes reactive `cart` state. Authorize with a publishable
123
+ `pk_` quote token + your application's origin allowlist (set origins via
124
+ `PUT /api/v1/embed-config`).
125
+
126
+ ```tsx
127
+ import { useCartSession } from '@usethrottle/checkout-react';
128
+
129
+ function Cart() {
130
+ const cart = useCartSession({
131
+ applicationId: '7f9d4c8a-5b2e-4f16-9a73-2d1e5c8b6f40',
132
+ environmentId: 'a1b2c3d4-...',
133
+ quoteToken: 'pk_live_...',
134
+ });
135
+
136
+ return (
137
+ <>
138
+ <button onClick={() => cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 })}>
139
+ Add to cart
140
+ </button>
141
+ {cart.cart && <p>Total: {cart.cart.total}</p>}
142
+ <button
143
+ disabled={cart.loading}
144
+ onClick={async () => {
145
+ const { checkoutUrl } = await cart.checkout({
146
+ returnUrl: 'https://shop.example.com/thanks',
147
+ cancelUrl: 'https://shop.example.com/cart',
148
+ });
149
+ window.location.href = checkoutUrl;
150
+ }}
151
+ >
152
+ Checkout
153
+ </button>
154
+ {cart.error && <p role="alert">{cart.error.message}</p>}
155
+ </>
156
+ );
157
+ }
158
+ ```
159
+
160
+ Also exposes `updateItem`, `removeItem`, `selectShipping`, `clearShipping`,
161
+ `applyDiscount`, `removeDiscount`, `refresh`, and `reset`. See the
162
+ [cart sessions guide](https://docs.usethrottle.dev/developers/cart-sessions).
163
+
110
164
  ## Events
111
165
 
112
166
  Both `<PaymentEmbed/>` and `<CheckoutEmbed/>` fire these events to the
@@ -114,15 +168,15 @@ parent page over `postMessage`. They're complementary to Throttle's
114
168
  outbound webhooks (which fire from Throttle's servers to your backend
115
169
  over signed HTTP).
116
170
 
117
- | Event | When | Callback |
118
- |---|---|---|
119
- | `throttle.ready` | iframe mounted | `onReady()` |
120
- | `throttle.processing` | user clicked Pay; awaiting auth | `onProcessing()` |
121
- | `throttle.completed` | payment captured | `onSucceeded({orderId, paymentId})` |
122
- | `throttle.error` | card declined / network failure | `onFailed({code, message})` |
123
- | `throttle.cancelled` | user dismissed | `onCanceled()` |
124
- | `throttle.step.changed` | (CheckoutEmbed) step transition | `onStepChanged({step})` |
125
- | `throttle.resize` | iframe content resized | (auto: updates iframe height) |
171
+ | Event | When | Callback |
172
+ | ----------------------- | ------------------------------- | ----------------------------------- |
173
+ | `throttle.ready` | iframe mounted | `onReady()` |
174
+ | `throttle.processing` | user clicked Pay; awaiting auth | `onProcessing()` |
175
+ | `throttle.completed` | payment captured | `onSucceeded({orderId, paymentId})` |
176
+ | `throttle.error` | card declined / network failure | `onFailed({code, message})` |
177
+ | `throttle.cancelled` | user dismissed | `onCanceled()` |
178
+ | `throttle.step.changed` | (CheckoutEmbed) step transition | `onStepChanged({step})` |
179
+ | `throttle.resize` | iframe content resized | (auto: updates iframe height) |
126
180
 
127
181
  Every message is wrapped in
128
182
  `{ source: 'throttle', version: 1, ...event }` so your page can
@@ -139,4 +193,4 @@ is now `onFailed({code, message})`. Update call sites accordingly.
139
193
  ## See also
140
194
 
141
195
  - [CLI: `throttle embed-config`](https://www.npmjs.com/package/@usethrottle/cli)
142
- - [API client: `@usethrottle/api-client`](https://www.npmjs.com/package/@usethrottle/api-client)
196
+ - [Checkout SDK: `@usethrottle/checkout-sdk`](https://www.npmjs.com/package/@usethrottle/checkout-sdk)
package/dist/index.cjs CHANGED
@@ -23,12 +23,13 @@ __export(index_exports, {
23
23
  CheckoutEmbed: () => CheckoutEmbed,
24
24
  PaymentEmbed: () => PaymentEmbed,
25
25
  ThrottleCheckout: () => CheckoutEmbed,
26
+ useCartSession: () => useCartSession,
26
27
  useThrottleEvents: () => useThrottleEvents
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
29
30
 
30
31
  // src/CheckoutEmbed.tsx
31
- var import_react2 = require("react");
32
+ var import_react3 = require("react");
32
33
 
33
34
  // src/useThrottleEvents.ts
34
35
  var import_react = require("react");
@@ -48,6 +49,32 @@ function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }) {
48
49
  }, [iframeRef, expectedOrigin, onMessage]);
49
50
  }
50
51
 
52
+ // src/useParentCommands.ts
53
+ var import_react2 = require("react");
54
+ function useParentCommands({
55
+ iframeRef,
56
+ targetOrigin,
57
+ submitDisabled,
58
+ iframeReady
59
+ }) {
60
+ const [lastSent, setLastSent] = (0, import_react2.useState)(void 0);
61
+ (0, import_react2.useEffect)(() => {
62
+ if (submitDisabled === void 0) return;
63
+ const win = iframeRef.current?.contentWindow;
64
+ if (!win) return;
65
+ if (lastSent === submitDisabled && iframeReady) return;
66
+ if (!iframeReady) return;
67
+ const message = {
68
+ source: "throttle-parent",
69
+ version: 1,
70
+ type: "set-submit-disabled",
71
+ disabled: submitDisabled
72
+ };
73
+ win.postMessage(message, targetOrigin);
74
+ setLastSent(submitDisabled);
75
+ }, [iframeRef, targetOrigin, submitDisabled, iframeReady, lastSent]);
76
+ }
77
+
51
78
  // src/CheckoutEmbed.tsx
52
79
  var import_jsx_runtime = require("react/jsx-runtime");
53
80
  var DEFAULT_BASE_URL = "https://checkout.usethrottle.dev";
@@ -60,6 +87,7 @@ function CheckoutEmbed({
60
87
  initialHeight = 600,
61
88
  className,
62
89
  style,
90
+ submitDisabled,
63
91
  onReady,
64
92
  onProcessing,
65
93
  onSucceeded,
@@ -67,19 +95,25 @@ function CheckoutEmbed({
67
95
  onCanceled,
68
96
  onStepChanged
69
97
  }) {
70
- const iframeRef = (0, import_react2.useRef)(null);
98
+ const iframeRef = (0, import_react3.useRef)(null);
71
99
  const expectedOrigin = new URL(baseUrl).origin;
72
- const dispatch = (0, import_react2.useCallback)(
100
+ const [iframeReady, setIframeReady] = (0, import_react3.useState)(false);
101
+ const dispatch = (0, import_react3.useCallback)(
73
102
  (msg) => {
74
103
  switch (msg.type) {
75
104
  case "throttle.ready":
105
+ setIframeReady(true);
76
106
  onReady?.();
77
107
  break;
78
108
  case "throttle.processing":
79
109
  onProcessing?.();
80
110
  break;
81
111
  case "throttle.completed":
82
- onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });
112
+ onSucceeded?.({
113
+ orderId: msg.orderId,
114
+ paymentId: msg.paymentId,
115
+ ...msg.subscriptionId !== void 0 ? { subscriptionId: msg.subscriptionId } : {}
116
+ });
83
117
  break;
84
118
  case "throttle.error":
85
119
  onFailed?.({ code: msg.code, message: msg.message });
@@ -98,6 +132,12 @@ function CheckoutEmbed({
98
132
  [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged]
99
133
  );
100
134
  useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });
135
+ useParentCommands({
136
+ iframeRef,
137
+ targetOrigin: expectedOrigin,
138
+ submitDisabled,
139
+ iframeReady
140
+ });
101
141
  const params = new URLSearchParams({ embed: "1", mode: "checkout-full", parentOrigin });
102
142
  if (primary) params.set("primary", primary);
103
143
  if (logo) params.set("logo", logo);
@@ -117,7 +157,7 @@ function CheckoutEmbed({
117
157
  }
118
158
 
119
159
  // src/PaymentEmbed.tsx
120
- var import_react3 = require("react");
160
+ var import_react4 = require("react");
121
161
  var import_jsx_runtime2 = require("react/jsx-runtime");
122
162
  var DEFAULT_BASE_URL2 = "https://checkout.usethrottle.dev";
123
163
  function PaymentEmbed({
@@ -129,25 +169,32 @@ function PaymentEmbed({
129
169
  initialHeight = 480,
130
170
  className,
131
171
  style,
172
+ submitDisabled,
132
173
  onReady,
133
174
  onProcessing,
134
175
  onSucceeded,
135
176
  onFailed,
136
177
  onCanceled
137
178
  }) {
138
- const iframeRef = (0, import_react3.useRef)(null);
179
+ const iframeRef = (0, import_react4.useRef)(null);
139
180
  const expectedOrigin = new URL(baseUrl).origin;
140
- const dispatch = (0, import_react3.useCallback)(
181
+ const [iframeReady, setIframeReady] = (0, import_react4.useState)(false);
182
+ const dispatch = (0, import_react4.useCallback)(
141
183
  (msg) => {
142
184
  switch (msg.type) {
143
185
  case "throttle.ready":
186
+ setIframeReady(true);
144
187
  onReady?.();
145
188
  break;
146
189
  case "throttle.processing":
147
190
  onProcessing?.();
148
191
  break;
149
192
  case "throttle.completed":
150
- onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });
193
+ onSucceeded?.({
194
+ orderId: msg.orderId,
195
+ paymentId: msg.paymentId,
196
+ ...msg.subscriptionId !== void 0 ? { subscriptionId: msg.subscriptionId } : {}
197
+ });
151
198
  break;
152
199
  case "throttle.error":
153
200
  onFailed?.({ code: msg.code, message: msg.message });
@@ -163,6 +210,12 @@ function PaymentEmbed({
163
210
  [onReady, onProcessing, onSucceeded, onFailed, onCanceled]
164
211
  );
165
212
  useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });
213
+ useParentCommands({
214
+ iframeRef,
215
+ targetOrigin: expectedOrigin,
216
+ submitDisabled,
217
+ iframeReady
218
+ });
166
219
  const params = new URLSearchParams({ embed: "1", mode: "payment-only", parentOrigin });
167
220
  if (primary) params.set("primary", primary);
168
221
  if (logo) params.set("logo", logo);
@@ -180,11 +233,173 @@ function PaymentEmbed({
180
233
  }
181
234
  );
182
235
  }
236
+
237
+ // src/useCartSession.tsx
238
+ var import_react5 = require("react");
239
+ var import_cart = require("@usethrottle/cart");
240
+ var DEFAULT_STORAGE_KEY = "throttle.cartSessionId";
241
+ function readStored(key) {
242
+ if (!key || typeof window === "undefined") return null;
243
+ try {
244
+ return window.localStorage.getItem(key);
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+ function writeStored(key, value) {
250
+ if (!key || typeof window === "undefined") return;
251
+ try {
252
+ if (value === null) window.localStorage.removeItem(key);
253
+ else window.localStorage.setItem(key, value);
254
+ } catch {
255
+ }
256
+ }
257
+ function useCartSession(options) {
258
+ const { applicationId, environmentId, quoteToken, baseUrl } = options;
259
+ const storageKey = options.storageKey === void 0 ? DEFAULT_STORAGE_KEY : options.storageKey;
260
+ const [cart, setCart] = (0, import_react5.useState)(null);
261
+ const [cartSessionId, setCartSessionId] = (0, import_react5.useState)(null);
262
+ const [loading, setLoading] = (0, import_react5.useState)(false);
263
+ const [error, setError] = (0, import_react5.useState)(null);
264
+ const { client, initError } = (0, import_react5.useMemo)(() => {
265
+ try {
266
+ return {
267
+ client: new import_cart.CartSessionClient({ applicationId, environmentId, quoteToken, baseUrl }),
268
+ initError: null
269
+ };
270
+ } catch (e) {
271
+ return { client: null, initError: e };
272
+ }
273
+ }, [applicationId, environmentId, quoteToken, baseUrl]);
274
+ (0, import_react5.useEffect)(() => {
275
+ if (initError) setError(initError);
276
+ }, [initError]);
277
+ (0, import_react5.useEffect)(() => {
278
+ if (!client) return;
279
+ const saved = readStored(storageKey);
280
+ if (!saved) return;
281
+ let cancelled = false;
282
+ client.resume(saved);
283
+ setCartSessionId(saved);
284
+ setLoading(true);
285
+ client.get().then((s) => {
286
+ if (cancelled) return;
287
+ setCart(s.cart);
288
+ }).catch(() => {
289
+ if (cancelled) return;
290
+ writeStored(storageKey, null);
291
+ client.resume("");
292
+ setCartSessionId(null);
293
+ setCart(null);
294
+ }).finally(() => {
295
+ if (!cancelled) setLoading(false);
296
+ });
297
+ return () => {
298
+ cancelled = true;
299
+ };
300
+ }, [client, storageKey]);
301
+ const ensureSession = (0, import_react5.useCallback)(
302
+ async (input) => {
303
+ if (!client) throw initError ?? new Error("CartSessionClient unavailable");
304
+ if (client.cartSessionId) return;
305
+ const session = await client.create(input ?? {});
306
+ writeStored(storageKey, session.cartSessionId);
307
+ setCartSessionId(session.cartSessionId);
308
+ setCart(session.cart);
309
+ },
310
+ [client, initError, storageKey]
311
+ );
312
+ const run = (0, import_react5.useCallback)(
313
+ async (fn, opts = {}) => {
314
+ if (!client) {
315
+ setError(initError ?? new Error("CartSessionClient unavailable"));
316
+ return;
317
+ }
318
+ setLoading(true);
319
+ setError(null);
320
+ try {
321
+ if (opts.create) await ensureSession();
322
+ const next = await fn();
323
+ setCart(next);
324
+ } catch (e) {
325
+ setError(e);
326
+ throw e;
327
+ } finally {
328
+ setLoading(false);
329
+ }
330
+ },
331
+ [client, ensureSession, initError]
332
+ );
333
+ const addItem = (0, import_react5.useCallback)(
334
+ (input) => run(() => client.addItem(input), { create: true }),
335
+ [client, run]
336
+ );
337
+ const updateItem = (0, import_react5.useCallback)(
338
+ (itemId, input) => run(() => client.updateItem(itemId, input)),
339
+ [client, run]
340
+ );
341
+ const removeItem = (0, import_react5.useCallback)((itemId) => run(() => client.removeItem(itemId)), [client, run]);
342
+ const selectShipping = (0, import_react5.useCallback)(
343
+ (input) => run(() => client.selectShipping(input), { create: true }),
344
+ [client, run]
345
+ );
346
+ const clearShipping = (0, import_react5.useCallback)(() => run(() => client.clearShipping()), [client, run]);
347
+ const applyDiscount = (0, import_react5.useCallback)(
348
+ (code) => run(() => client.applyDiscount(code), { create: true }),
349
+ [client, run]
350
+ );
351
+ const removeDiscount = (0, import_react5.useCallback)(() => run(() => client.removeDiscount()), [client, run]);
352
+ const refresh = (0, import_react5.useCallback)(async () => {
353
+ if (!client?.cartSessionId) return;
354
+ await run(async () => (await client.get()).cart);
355
+ }, [client, run]);
356
+ const checkout = (0, import_react5.useCallback)(
357
+ async (input) => {
358
+ if (!client) throw initError ?? new Error("CartSessionClient unavailable");
359
+ setLoading(true);
360
+ setError(null);
361
+ try {
362
+ await ensureSession();
363
+ return await client.checkout(input);
364
+ } catch (e) {
365
+ setError(e);
366
+ throw e;
367
+ } finally {
368
+ setLoading(false);
369
+ }
370
+ },
371
+ [client, ensureSession, initError]
372
+ );
373
+ const reset = (0, import_react5.useCallback)(() => {
374
+ writeStored(storageKey, null);
375
+ client?.resume("");
376
+ setCartSessionId(null);
377
+ setCart(null);
378
+ setError(null);
379
+ }, [client, storageKey]);
380
+ return {
381
+ cart,
382
+ cartSessionId,
383
+ loading,
384
+ error,
385
+ addItem,
386
+ updateItem,
387
+ removeItem,
388
+ selectShipping,
389
+ clearShipping,
390
+ applyDiscount,
391
+ removeDiscount,
392
+ checkout,
393
+ refresh,
394
+ reset
395
+ };
396
+ }
183
397
  // Annotate the CommonJS export names for ESM import in node:
184
398
  0 && (module.exports = {
185
399
  CheckoutEmbed,
186
400
  PaymentEmbed,
187
401
  ThrottleCheckout,
402
+ useCartSession,
188
403
  useThrottleEvents
189
404
  });
190
405
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/PaymentEmbed.tsx"],"sourcesContent":["export { CheckoutEmbed } from './CheckoutEmbed.js';\nexport { PaymentEmbed } from './PaymentEmbed.js';\nexport { useThrottleEvents } from './useThrottleEvents.js';\nexport type {\n ThrottleEvent,\n ThrottleMessage,\n ThrottleEnvelope,\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n} from './types.js';\n\n// v0.1.x compat alias. v0.1's <ThrottleCheckout> shape (positional\n// onCompleted/onError args, optional baseUrl, no parentOrigin) is\n// gone in v0.2 — the alias resolves to <CheckoutEmbed> which now\n// requires `parentOrigin`. Update call sites accordingly.\nexport { CheckoutEmbed as ThrottleCheckout } from './CheckoutEmbed.js';\n","import { useCallback, useRef } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport type {\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n ThrottleMessage,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Full-checkout iframe embed. Renders the entire Throttle hosted\n * checkout (address / shipping / payment) inside an iframe and\n * surfaces lifecycle + terminal events through callbacks. The\n * iframe auto-resizes on `throttle.resize` events.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list\n * (`throttle embed-config set --origins ...`). Mismatches render\n * an in-iframe \"Embed not authorized\" panel.\n */\nexport function CheckoutEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 600,\n className,\n style,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n onStepChanged,\n}: ThrottleEmbedProps & CheckoutEmbedExtraProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.step.changed':\n onStepChanged?.({ step: msg.step });\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n\n const params = new URLSearchParams({ embed: '1', mode: 'checkout-full', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle checkout\"\n />\n );\n}\n","import { useEffect, type RefObject } from 'react';\nimport type { ThrottleMessage } from './types.js';\n\nexport interface UseThrottleEventsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /**\n * The Throttle origin (e.g. https://checkout.usethrottle.dev).\n * Strict-equals check against `MessageEvent.origin`. Anything else\n * is silently dropped.\n */\n expectedOrigin: string;\n onMessage: (msg: ThrottleMessage) => void;\n}\n\n/**\n * Subscribe to validated postMessage events from a Throttle iframe.\n *\n * Validation:\n * - event.origin === expectedOrigin (strict string match)\n * - event.source === iframeRef.current.contentWindow (defends against\n * other iframes on the same origin spoofing events)\n * - event.data.source === 'throttle' && event.data.version === 1\n * - event.data.type starts with 'throttle.'\n *\n * Anything failing validation is silently dropped — the parent page\n * may receive postMessages from analytics SDKs, browser extensions,\n * other iframes, etc. We must never throw on those.\n */\nexport function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions) {\n useEffect(() => {\n function handle(ev: MessageEvent) {\n if (ev.origin !== expectedOrigin) return;\n if (iframeRef.current && ev.source !== iframeRef.current.contentWindow) return;\n const data = ev.data as Partial<ThrottleMessage> | null | undefined;\n if (!data || typeof data !== 'object') return;\n if (data.source !== 'throttle' || data.version !== 1) return;\n if (typeof data.type !== 'string' || !data.type.startsWith('throttle.')) return;\n onMessage(data as ThrottleMessage);\n }\n window.addEventListener('message', handle);\n return () => window.removeEventListener('message', handle);\n }, [iframeRef, expectedOrigin, onMessage]);\n}\n","import { useCallback, useRef } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport type { ThrottleEmbedProps, ThrottleMessage } from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Payment-only iframe embed. Renders just the Gr4vy provider iframe\n * (no address / shipping / cart UI). Email is collected by the Gr4vy\n * iframe in proxy-mode sessions. Used when the merchant has built\n * their own checkout UI and only needs Throttle to handle payment\n * authorization + capture.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list.\n * The iframe auto-resizes on `throttle.resize` events.\n */\nexport function PaymentEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 480,\n className,\n style,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n}: ThrottleEmbedProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n\n const params = new URLSearchParams({ embed: '1', mode: 'payment-only', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle payment\"\n />\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/useParentCommands.ts","../src/PaymentEmbed.tsx","../src/useCartSession.tsx"],"sourcesContent":["export { CheckoutEmbed } from './CheckoutEmbed.js';\nexport { PaymentEmbed } from './PaymentEmbed.js';\nexport { useThrottleEvents } from './useThrottleEvents.js';\nexport { useCartSession } from './useCartSession.js';\nexport type { UseCartSessionOptions, UseCartSessionResult } from './useCartSession.js';\nexport type {\n ThrottleEvent,\n ThrottleMessage,\n ThrottleEnvelope,\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n RecurringIntent,\n ThrottleParentCommand,\n ThrottleParentEnvelope,\n ThrottleParentMessage,\n} from './types.js';\n\n// v0.1.x compat alias. v0.1's <ThrottleCheckout> shape (positional\n// onCompleted/onError args, optional baseUrl, no parentOrigin) is\n// gone in v0.2 — the alias resolves to <CheckoutEmbed> which now\n// requires `parentOrigin`. Update call sites accordingly.\nexport { CheckoutEmbed as ThrottleCheckout } from './CheckoutEmbed.js';\n","import { useCallback, useRef, useState } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport { useParentCommands } from './useParentCommands.js';\nimport type {\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n ThrottleMessage,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Full-checkout iframe embed. Renders the entire Throttle hosted\n * checkout (address / shipping / payment) inside an iframe and\n * surfaces lifecycle + terminal events through callbacks. The\n * iframe auto-resizes on `throttle.resize` events.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list\n * (`throttle embed-config set --origins ...`). Mismatches render\n * an in-iframe \"Embed not authorized\" panel.\n */\nexport function CheckoutEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 600,\n className,\n style,\n submitDisabled,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n onStepChanged,\n}: ThrottleEmbedProps & CheckoutEmbedExtraProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n const [iframeReady, setIframeReady] = useState(false);\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n setIframeReady(true);\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({\n orderId: msg.orderId,\n paymentId: msg.paymentId,\n ...(msg.subscriptionId !== undefined ? { subscriptionId: msg.subscriptionId } : {}),\n });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.step.changed':\n onStepChanged?.({ step: msg.step });\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n useParentCommands({\n iframeRef,\n targetOrigin: expectedOrigin,\n submitDisabled,\n iframeReady,\n });\n\n const params = new URLSearchParams({ embed: '1', mode: 'checkout-full', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle checkout\"\n />\n );\n}\n","import { useEffect, type RefObject } from 'react';\nimport type { ThrottleMessage } from './types.js';\n\nexport interface UseThrottleEventsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /**\n * The Throttle origin (e.g. https://checkout.usethrottle.dev).\n * Strict-equals check against `MessageEvent.origin`. Anything else\n * is silently dropped.\n */\n expectedOrigin: string;\n onMessage: (msg: ThrottleMessage) => void;\n}\n\n/**\n * Subscribe to validated postMessage events from a Throttle iframe.\n *\n * Validation:\n * - event.origin === expectedOrigin (strict string match)\n * - event.source === iframeRef.current.contentWindow (defends against\n * other iframes on the same origin spoofing events)\n * - event.data.source === 'throttle' && event.data.version === 1\n * - event.data.type starts with 'throttle.'\n *\n * Anything failing validation is silently dropped — the parent page\n * may receive postMessages from analytics SDKs, browser extensions,\n * other iframes, etc. We must never throw on those.\n */\nexport function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions) {\n useEffect(() => {\n function handle(ev: MessageEvent) {\n if (ev.origin !== expectedOrigin) return;\n if (iframeRef.current && ev.source !== iframeRef.current.contentWindow) return;\n const data = ev.data as Partial<ThrottleMessage> | null | undefined;\n if (!data || typeof data !== 'object') return;\n if (data.source !== 'throttle' || data.version !== 1) return;\n if (typeof data.type !== 'string' || !data.type.startsWith('throttle.')) return;\n onMessage(data as ThrottleMessage);\n }\n window.addEventListener('message', handle);\n return () => window.removeEventListener('message', handle);\n }, [iframeRef, expectedOrigin, onMessage]);\n}\n","import { useEffect, useState, type RefObject } from 'react';\nimport type { ThrottleParentMessage } from './types.js';\n\ninterface UseParentCommandsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /** The Throttle origin to address messages to. */\n targetOrigin: string;\n /**\n * The current value of `submitDisabled` from the embed props. Posts a\n * `set-submit-disabled` parent command whenever it changes — and once\n * after the iframe reports `throttle.ready` so the iframe sees the\n * intent even if it boots after the prop is already set.\n *\n * `undefined` means the parent never explicitly enabled/disabled — we\n * never post in that case. Backward-compat with embeds that don't\n * pass the prop.\n */\n submitDisabled?: boolean;\n /**\n * Set to `true` once the iframe posts `throttle.ready`. Until then,\n * commands are buffered (we still try to post but the iframe may not\n * have its listener attached yet — and we re-post on ready as a\n * belt-and-braces guard).\n */\n iframeReady: boolean;\n}\n\n/**\n * Post parent → iframe commands when controlled props change. v1.2 adds\n * `set-submit-disabled`; future commands extend the same channel.\n *\n * postMessage is fire-and-forget — we don't wait for an ack from the\n * iframe. The iframe's listener is idempotent (setting the same value\n * twice is a no-op).\n */\nexport function useParentCommands({\n iframeRef,\n targetOrigin,\n submitDisabled,\n iframeReady,\n}: UseParentCommandsOptions) {\n const [lastSent, setLastSent] = useState<boolean | undefined>(undefined);\n\n // Post when the prop value changes OR when the iframe transitions to\n // ready (so the first state lands).\n useEffect(() => {\n if (submitDisabled === undefined) return;\n const win = iframeRef.current?.contentWindow;\n if (!win) return;\n // Skip if we already sent this value AND the iframe was already ready\n // — avoids spam on unrelated re-renders.\n if (lastSent === submitDisabled && iframeReady) return;\n if (!iframeReady) return; // Defer until ready\n\n const message: ThrottleParentMessage = {\n source: 'throttle-parent',\n version: 1,\n type: 'set-submit-disabled',\n disabled: submitDisabled,\n };\n win.postMessage(message, targetOrigin);\n setLastSent(submitDisabled);\n }, [iframeRef, targetOrigin, submitDisabled, iframeReady, lastSent]);\n}\n","import { useCallback, useRef, useState } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport { useParentCommands } from './useParentCommands.js';\nimport type { ThrottleEmbedProps, ThrottleMessage } from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Payment-only iframe embed. Renders just the Gr4vy provider iframe\n * (no address / shipping / cart UI). Email is collected by the Gr4vy\n * iframe in proxy-mode sessions. Used when the merchant has built\n * their own checkout UI and only needs Throttle to handle payment\n * authorization + capture.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list.\n * The iframe auto-resizes on `throttle.resize` events.\n */\nexport function PaymentEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 480,\n className,\n style,\n submitDisabled,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n}: ThrottleEmbedProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n const [iframeReady, setIframeReady] = useState(false);\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n setIframeReady(true);\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({\n orderId: msg.orderId,\n paymentId: msg.paymentId,\n ...(msg.subscriptionId !== undefined ? { subscriptionId: msg.subscriptionId } : {}),\n });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n useParentCommands({\n iframeRef,\n targetOrigin: expectedOrigin,\n submitDisabled,\n iframeReady,\n });\n\n const params = new URLSearchParams({ embed: '1', mode: 'payment-only', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle payment\"\n />\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n CartSessionClient,\n type Cart,\n type AddLineItemInput,\n type UpdateLineItemInput,\n type SelectShippingInput,\n type CreateCartSessionInput,\n type CheckoutHandoffInput,\n type CheckoutHandoff,\n} from '@usethrottle/cart';\n\nconst DEFAULT_STORAGE_KEY = 'throttle.cartSessionId';\n\nexport interface UseCartSessionOptions {\n /** Application UUID. */\n applicationId: string;\n /** Workspace environment UUID the publishable token was minted for. */\n environmentId: string;\n /** Publishable storefront quote token (`pk_…`). */\n quoteToken: string;\n baseUrl?: string;\n /**\n * localStorage key for persisting the session id across page loads. Default\n * `'throttle.cartSessionId'`. Pass `null` to disable persistence (in-memory\n * only).\n */\n storageKey?: string | null;\n}\n\nexport interface UseCartSessionResult {\n /** Current cart snapshot, or null before the first item / resume. */\n cart: Cart | null;\n /** Current opaque session id, or null before the first item. */\n cartSessionId: string | null;\n /** True while a request is in flight. */\n loading: boolean;\n /** Last error, or null. */\n error: Error | null;\n addItem: (input: AddLineItemInput) => Promise<void>;\n updateItem: (itemId: string, input: UpdateLineItemInput) => Promise<void>;\n removeItem: (itemId: string) => Promise<void>;\n selectShipping: (input: SelectShippingInput) => Promise<void>;\n clearShipping: () => Promise<void>;\n applyDiscount: (code: string) => Promise<void>;\n removeDiscount: () => Promise<void>;\n /** Hand off to checkout. Returns the hand-off (redirect the buyer to `checkoutUrl`). */\n checkout: (input: CheckoutHandoffInput) => Promise<CheckoutHandoff>;\n /** Re-fetch the current cart. */\n refresh: () => Promise<void>;\n /** Forget the current session (a new cart is created on the next item). */\n reset: () => void;\n}\n\nfunction readStored(key: string | null | undefined): string | null {\n if (!key || typeof window === 'undefined') return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeStored(key: string | null | undefined, value: string | null): void {\n if (!key || typeof window === 'undefined') return;\n try {\n if (value === null) window.localStorage.removeItem(key);\n else window.localStorage.setItem(key, value);\n } catch {\n /* ignore quota / privacy-mode errors */\n }\n}\n\n/**\n * React hook for backend-less cart ownership. Wraps {@link CartSessionClient}:\n * creates the Throttle cart lazily on the first `addItem` (so empty carts\n * aren't created on every page load), persists the session id to localStorage,\n * and resumes it on the next visit. Mutations update reactive `cart` state.\n *\n * ```tsx\n * const cart = useCartSession({ applicationId, environmentId, quoteToken });\n * <button onClick={() => cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 })}>Add</button>\n * <button onClick={async () => {\n * const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });\n * window.location.href = checkoutUrl;\n * }}>Checkout</button>\n * ```\n */\nexport function useCartSession(options: UseCartSessionOptions): UseCartSessionResult {\n const { applicationId, environmentId, quoteToken, baseUrl } = options;\n const storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n\n const [cart, setCart] = useState<Cart | null>(null);\n const [cartSessionId, setCartSessionId] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Stable client across renders for a given config; construction validates the\n // token, so capture any constructor error instead of throwing during render.\n const { client, initError } = useMemo(() => {\n try {\n return {\n client: new CartSessionClient({ applicationId, environmentId, quoteToken, baseUrl }),\n initError: null as Error | null,\n };\n } catch (e) {\n return { client: null as CartSessionClient | null, initError: e as Error };\n }\n }, [applicationId, environmentId, quoteToken, baseUrl]);\n\n useEffect(() => {\n if (initError) setError(initError);\n }, [initError]);\n\n // Resume a persisted session on mount (and whenever the client identity changes).\n useEffect(() => {\n if (!client) return;\n const saved = readStored(storageKey);\n if (!saved) return;\n let cancelled = false;\n client.resume(saved);\n setCartSessionId(saved);\n setLoading(true);\n client\n .get()\n .then((s) => {\n if (cancelled) return;\n setCart(s.cart);\n })\n .catch(() => {\n // Expired / unknown / origin-rejected → drop the stale id and start fresh.\n if (cancelled) return;\n writeStored(storageKey, null);\n client.resume('');\n setCartSessionId(null);\n setCart(null);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [client, storageKey]);\n\n const ensureSession = useCallback(\n async (input?: CreateCartSessionInput): Promise<void> => {\n if (!client) throw initError ?? new Error('CartSessionClient unavailable');\n if (client.cartSessionId) return;\n const session = await client.create(input ?? {});\n writeStored(storageKey, session.cartSessionId);\n setCartSessionId(session.cartSessionId);\n setCart(session.cart);\n },\n [client, initError, storageKey],\n );\n\n /** Run a mutation that returns the updated cart, managing loading/error + creating the session first. */\n const run = useCallback(\n async (fn: () => Promise<Cart>, opts: { create?: boolean } = {}): Promise<void> => {\n if (!client) {\n setError(initError ?? new Error('CartSessionClient unavailable'));\n return;\n }\n setLoading(true);\n setError(null);\n try {\n if (opts.create) await ensureSession();\n const next = await fn();\n setCart(next);\n } catch (e) {\n setError(e as Error);\n throw e;\n } finally {\n setLoading(false);\n }\n },\n [client, ensureSession, initError],\n );\n\n const addItem = useCallback(\n (input: AddLineItemInput) => run(() => client!.addItem(input), { create: true }),\n [client, run],\n );\n const updateItem = useCallback(\n (itemId: string, input: UpdateLineItemInput) => run(() => client!.updateItem(itemId, input)),\n [client, run],\n );\n const removeItem = useCallback((itemId: string) => run(() => client!.removeItem(itemId)), [client, run]);\n const selectShipping = useCallback(\n (input: SelectShippingInput) => run(() => client!.selectShipping(input), { create: true }),\n [client, run],\n );\n const clearShipping = useCallback(() => run(() => client!.clearShipping()), [client, run]);\n const applyDiscount = useCallback(\n (code: string) => run(() => client!.applyDiscount(code), { create: true }),\n [client, run],\n );\n const removeDiscount = useCallback(() => run(() => client!.removeDiscount()), [client, run]);\n const refresh = useCallback(async () => {\n if (!client?.cartSessionId) return;\n await run(async () => (await client.get()).cart);\n }, [client, run]);\n\n const checkout = useCallback(\n async (input: CheckoutHandoffInput): Promise<CheckoutHandoff> => {\n if (!client) throw initError ?? new Error('CartSessionClient unavailable');\n setLoading(true);\n setError(null);\n try {\n await ensureSession();\n return await client.checkout(input);\n } catch (e) {\n setError(e as Error);\n throw e;\n } finally {\n setLoading(false);\n }\n },\n [client, ensureSession, initError],\n );\n\n const reset = useCallback(() => {\n writeStored(storageKey, null);\n client?.resume('');\n setCartSessionId(null);\n setCart(null);\n setError(null);\n }, [client, storageKey]);\n\n return {\n cart,\n cartSessionId,\n loading,\n error,\n addItem,\n updateItem,\n removeItem,\n selectShipping,\n clearShipping,\n applyDiscount,\n removeDiscount,\n checkout,\n refresh,\n reset,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_jsx_runtime","DEFAULT_BASE_URL","import_react"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CSSProperties, RefObject } from 'react';
3
+ import { Cart, AddLineItemInput, UpdateLineItemInput, SelectShippingInput, CheckoutHandoffInput, CheckoutHandoff } from '@usethrottle/cart';
3
4
 
4
5
  /**
5
6
  * Envelope every Throttle iframe message carries. Lets parents
@@ -21,6 +22,8 @@ type ThrottleEvent = {
21
22
  type: 'throttle.completed';
22
23
  orderId: string;
23
24
  paymentId: string;
25
+ paymentStatus?: string;
26
+ subscriptionId?: string;
24
27
  } | {
25
28
  type: 'throttle.cancelled';
26
29
  } | {
@@ -29,9 +32,46 @@ type ThrottleEvent = {
29
32
  message: string;
30
33
  } | {
31
34
  type: 'throttle.step.changed';
32
- step: 'cart' | 'address' | 'shipping' | 'payment';
35
+ step: 'cart' | 'address' | 'shipping' | 'billing' | 'payment';
33
36
  };
34
37
  type ThrottleMessage = ThrottleEvent & ThrottleEnvelope;
38
+ /**
39
+ * Parent → iframe commands. The SDK posts these when a controlled prop
40
+ * (like `submitDisabled`) changes on the React component. Apps building
41
+ * directly against postMessage (e.g., vanilla JS storefronts) can post
42
+ * the same shape themselves.
43
+ */
44
+ type ThrottleParentCommand = {
45
+ type: 'set-submit-disabled';
46
+ disabled: boolean;
47
+ };
48
+ interface ThrottleParentEnvelope {
49
+ source: 'throttle-parent';
50
+ version: 1;
51
+ }
52
+ type ThrottleParentMessage = ThrottleParentCommand & ThrottleParentEnvelope;
53
+ /**
54
+ * Recurring subscription intent for an embed-driven subscribe flow.
55
+ *
56
+ * The authoritative recurring metadata lives on the checkout session
57
+ * (set when calling POST /api/v1/checkout/sessions). This prop is a
58
+ * type-level hint that signals subscription intent in your TSX without
59
+ * requiring a runtime read of the session.
60
+ *
61
+ * - `create: 'auto'` (default) — server creates the subscription on
62
+ * vault success. `onSucceeded` payload includes `subscriptionId`.
63
+ * - `create: 'manual'` — server only vaults; merchant calls
64
+ * `subscriptions.create` from their backend after `payment.vaulted`
65
+ * webhook or `onSucceeded` callback.
66
+ */
67
+ interface RecurringIntent {
68
+ plan: string;
69
+ interval: 'weekly' | 'monthly' | 'quarterly' | 'yearly';
70
+ amount?: number;
71
+ trialDays?: number;
72
+ planName?: string;
73
+ create?: 'auto' | 'manual';
74
+ }
35
75
  interface ThrottleEmbedProps {
36
76
  /** Throttle checkout session id (cs_*). Required. */
37
77
  sessionId: string;
@@ -52,11 +92,29 @@ interface ThrottleEmbedProps {
52
92
  initialHeight?: number;
53
93
  className?: string;
54
94
  style?: CSSProperties;
95
+ /**
96
+ * Subscription intent. Type-level hint — when set, this signals to readers
97
+ * of your code that the embed seeds a subscription on success. The
98
+ * authoritative data lives on the session itself; this prop is purely
99
+ * declarative. See {@link RecurringIntent} for field semantics.
100
+ */
101
+ recurring?: RecurringIntent;
102
+ /**
103
+ * Disable the submit affordance inside the iframe. Use this to gate
104
+ * payment behind parent-side form validity (e.g., a shipping address
105
+ * form rendered above the embed must be filled in first).
106
+ *
107
+ * The buyer can still see and interact with the card form fields. Only
108
+ * the path from "buyer submits" → "throttle captures" is blocked.
109
+ * Default `false` for backward compatibility.
110
+ */
111
+ submitDisabled?: boolean;
55
112
  onReady?: () => void;
56
113
  onProcessing?: () => void;
57
114
  onSucceeded?: (evt: {
58
115
  orderId: string;
59
116
  paymentId: string;
117
+ subscriptionId?: string;
60
118
  }) => void;
61
119
  onFailed?: (evt: {
62
120
  code: string;
@@ -66,7 +124,7 @@ interface ThrottleEmbedProps {
66
124
  }
67
125
  interface CheckoutEmbedExtraProps {
68
126
  onStepChanged?: (evt: {
69
- step: 'cart' | 'address' | 'shipping' | 'payment';
127
+ step: 'cart' | 'address' | 'shipping' | 'billing' | 'payment';
70
128
  }) => void;
71
129
  }
72
130
 
@@ -80,7 +138,7 @@ interface CheckoutEmbedExtraProps {
80
138
  * (`throttle embed-config set --origins ...`). Mismatches render
81
139
  * an in-iframe "Embed not authorized" panel.
82
140
  */
83
- declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged, }: ThrottleEmbedProps & CheckoutEmbedExtraProps): react_jsx_runtime.JSX.Element;
141
+ declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, submitDisabled, onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged, }: ThrottleEmbedProps & CheckoutEmbedExtraProps): react_jsx_runtime.JSX.Element;
84
142
 
85
143
  /**
86
144
  * Payment-only iframe embed. Renders just the Gr4vy provider iframe
@@ -92,7 +150,7 @@ declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo
92
150
  * `parentOrigin` must match an entry in the merchant's allow-list.
93
151
  * The iframe auto-resizes on `throttle.resize` events.
94
152
  */
95
- declare function PaymentEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, onReady, onProcessing, onSucceeded, onFailed, onCanceled, }: ThrottleEmbedProps): react_jsx_runtime.JSX.Element;
153
+ declare function PaymentEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, submitDisabled, onReady, onProcessing, onSucceeded, onFailed, onCanceled, }: ThrottleEmbedProps): react_jsx_runtime.JSX.Element;
96
154
 
97
155
  interface UseThrottleEventsOptions {
98
156
  iframeRef: RefObject<HTMLIFrameElement | null>;
@@ -120,4 +178,59 @@ interface UseThrottleEventsOptions {
120
178
  */
121
179
  declare function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions): void;
122
180
 
123
- export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, useThrottleEvents };
181
+ interface UseCartSessionOptions {
182
+ /** Application UUID. */
183
+ applicationId: string;
184
+ /** Workspace environment UUID the publishable token was minted for. */
185
+ environmentId: string;
186
+ /** Publishable storefront quote token (`pk_…`). */
187
+ quoteToken: string;
188
+ baseUrl?: string;
189
+ /**
190
+ * localStorage key for persisting the session id across page loads. Default
191
+ * `'throttle.cartSessionId'`. Pass `null` to disable persistence (in-memory
192
+ * only).
193
+ */
194
+ storageKey?: string | null;
195
+ }
196
+ interface UseCartSessionResult {
197
+ /** Current cart snapshot, or null before the first item / resume. */
198
+ cart: Cart | null;
199
+ /** Current opaque session id, or null before the first item. */
200
+ cartSessionId: string | null;
201
+ /** True while a request is in flight. */
202
+ loading: boolean;
203
+ /** Last error, or null. */
204
+ error: Error | null;
205
+ addItem: (input: AddLineItemInput) => Promise<void>;
206
+ updateItem: (itemId: string, input: UpdateLineItemInput) => Promise<void>;
207
+ removeItem: (itemId: string) => Promise<void>;
208
+ selectShipping: (input: SelectShippingInput) => Promise<void>;
209
+ clearShipping: () => Promise<void>;
210
+ applyDiscount: (code: string) => Promise<void>;
211
+ removeDiscount: () => Promise<void>;
212
+ /** Hand off to checkout. Returns the hand-off (redirect the buyer to `checkoutUrl`). */
213
+ checkout: (input: CheckoutHandoffInput) => Promise<CheckoutHandoff>;
214
+ /** Re-fetch the current cart. */
215
+ refresh: () => Promise<void>;
216
+ /** Forget the current session (a new cart is created on the next item). */
217
+ reset: () => void;
218
+ }
219
+ /**
220
+ * React hook for backend-less cart ownership. Wraps {@link CartSessionClient}:
221
+ * creates the Throttle cart lazily on the first `addItem` (so empty carts
222
+ * aren't created on every page load), persists the session id to localStorage,
223
+ * and resumes it on the next visit. Mutations update reactive `cart` state.
224
+ *
225
+ * ```tsx
226
+ * const cart = useCartSession({ applicationId, environmentId, quoteToken });
227
+ * <button onClick={() => cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 })}>Add</button>
228
+ * <button onClick={async () => {
229
+ * const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });
230
+ * window.location.href = checkoutUrl;
231
+ * }}>Checkout</button>
232
+ * ```
233
+ */
234
+ declare function useCartSession(options: UseCartSessionOptions): UseCartSessionResult;
235
+
236
+ export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, type RecurringIntent, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, type UseCartSessionOptions, type UseCartSessionResult, useCartSession, useThrottleEvents };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CSSProperties, RefObject } from 'react';
3
+ import { Cart, AddLineItemInput, UpdateLineItemInput, SelectShippingInput, CheckoutHandoffInput, CheckoutHandoff } from '@usethrottle/cart';
3
4
 
4
5
  /**
5
6
  * Envelope every Throttle iframe message carries. Lets parents
@@ -21,6 +22,8 @@ type ThrottleEvent = {
21
22
  type: 'throttle.completed';
22
23
  orderId: string;
23
24
  paymentId: string;
25
+ paymentStatus?: string;
26
+ subscriptionId?: string;
24
27
  } | {
25
28
  type: 'throttle.cancelled';
26
29
  } | {
@@ -29,9 +32,46 @@ type ThrottleEvent = {
29
32
  message: string;
30
33
  } | {
31
34
  type: 'throttle.step.changed';
32
- step: 'cart' | 'address' | 'shipping' | 'payment';
35
+ step: 'cart' | 'address' | 'shipping' | 'billing' | 'payment';
33
36
  };
34
37
  type ThrottleMessage = ThrottleEvent & ThrottleEnvelope;
38
+ /**
39
+ * Parent → iframe commands. The SDK posts these when a controlled prop
40
+ * (like `submitDisabled`) changes on the React component. Apps building
41
+ * directly against postMessage (e.g., vanilla JS storefronts) can post
42
+ * the same shape themselves.
43
+ */
44
+ type ThrottleParentCommand = {
45
+ type: 'set-submit-disabled';
46
+ disabled: boolean;
47
+ };
48
+ interface ThrottleParentEnvelope {
49
+ source: 'throttle-parent';
50
+ version: 1;
51
+ }
52
+ type ThrottleParentMessage = ThrottleParentCommand & ThrottleParentEnvelope;
53
+ /**
54
+ * Recurring subscription intent for an embed-driven subscribe flow.
55
+ *
56
+ * The authoritative recurring metadata lives on the checkout session
57
+ * (set when calling POST /api/v1/checkout/sessions). This prop is a
58
+ * type-level hint that signals subscription intent in your TSX without
59
+ * requiring a runtime read of the session.
60
+ *
61
+ * - `create: 'auto'` (default) — server creates the subscription on
62
+ * vault success. `onSucceeded` payload includes `subscriptionId`.
63
+ * - `create: 'manual'` — server only vaults; merchant calls
64
+ * `subscriptions.create` from their backend after `payment.vaulted`
65
+ * webhook or `onSucceeded` callback.
66
+ */
67
+ interface RecurringIntent {
68
+ plan: string;
69
+ interval: 'weekly' | 'monthly' | 'quarterly' | 'yearly';
70
+ amount?: number;
71
+ trialDays?: number;
72
+ planName?: string;
73
+ create?: 'auto' | 'manual';
74
+ }
35
75
  interface ThrottleEmbedProps {
36
76
  /** Throttle checkout session id (cs_*). Required. */
37
77
  sessionId: string;
@@ -52,11 +92,29 @@ interface ThrottleEmbedProps {
52
92
  initialHeight?: number;
53
93
  className?: string;
54
94
  style?: CSSProperties;
95
+ /**
96
+ * Subscription intent. Type-level hint — when set, this signals to readers
97
+ * of your code that the embed seeds a subscription on success. The
98
+ * authoritative data lives on the session itself; this prop is purely
99
+ * declarative. See {@link RecurringIntent} for field semantics.
100
+ */
101
+ recurring?: RecurringIntent;
102
+ /**
103
+ * Disable the submit affordance inside the iframe. Use this to gate
104
+ * payment behind parent-side form validity (e.g., a shipping address
105
+ * form rendered above the embed must be filled in first).
106
+ *
107
+ * The buyer can still see and interact with the card form fields. Only
108
+ * the path from "buyer submits" → "throttle captures" is blocked.
109
+ * Default `false` for backward compatibility.
110
+ */
111
+ submitDisabled?: boolean;
55
112
  onReady?: () => void;
56
113
  onProcessing?: () => void;
57
114
  onSucceeded?: (evt: {
58
115
  orderId: string;
59
116
  paymentId: string;
117
+ subscriptionId?: string;
60
118
  }) => void;
61
119
  onFailed?: (evt: {
62
120
  code: string;
@@ -66,7 +124,7 @@ interface ThrottleEmbedProps {
66
124
  }
67
125
  interface CheckoutEmbedExtraProps {
68
126
  onStepChanged?: (evt: {
69
- step: 'cart' | 'address' | 'shipping' | 'payment';
127
+ step: 'cart' | 'address' | 'shipping' | 'billing' | 'payment';
70
128
  }) => void;
71
129
  }
72
130
 
@@ -80,7 +138,7 @@ interface CheckoutEmbedExtraProps {
80
138
  * (`throttle embed-config set --origins ...`). Mismatches render
81
139
  * an in-iframe "Embed not authorized" panel.
82
140
  */
83
- declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged, }: ThrottleEmbedProps & CheckoutEmbedExtraProps): react_jsx_runtime.JSX.Element;
141
+ declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, submitDisabled, onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged, }: ThrottleEmbedProps & CheckoutEmbedExtraProps): react_jsx_runtime.JSX.Element;
84
142
 
85
143
  /**
86
144
  * Payment-only iframe embed. Renders just the Gr4vy provider iframe
@@ -92,7 +150,7 @@ declare function CheckoutEmbed({ sessionId, parentOrigin, baseUrl, primary, logo
92
150
  * `parentOrigin` must match an entry in the merchant's allow-list.
93
151
  * The iframe auto-resizes on `throttle.resize` events.
94
152
  */
95
- declare function PaymentEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, onReady, onProcessing, onSucceeded, onFailed, onCanceled, }: ThrottleEmbedProps): react_jsx_runtime.JSX.Element;
153
+ declare function PaymentEmbed({ sessionId, parentOrigin, baseUrl, primary, logo, initialHeight, className, style, submitDisabled, onReady, onProcessing, onSucceeded, onFailed, onCanceled, }: ThrottleEmbedProps): react_jsx_runtime.JSX.Element;
96
154
 
97
155
  interface UseThrottleEventsOptions {
98
156
  iframeRef: RefObject<HTMLIFrameElement | null>;
@@ -120,4 +178,59 @@ interface UseThrottleEventsOptions {
120
178
  */
121
179
  declare function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions): void;
122
180
 
123
- export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, useThrottleEvents };
181
+ interface UseCartSessionOptions {
182
+ /** Application UUID. */
183
+ applicationId: string;
184
+ /** Workspace environment UUID the publishable token was minted for. */
185
+ environmentId: string;
186
+ /** Publishable storefront quote token (`pk_…`). */
187
+ quoteToken: string;
188
+ baseUrl?: string;
189
+ /**
190
+ * localStorage key for persisting the session id across page loads. Default
191
+ * `'throttle.cartSessionId'`. Pass `null` to disable persistence (in-memory
192
+ * only).
193
+ */
194
+ storageKey?: string | null;
195
+ }
196
+ interface UseCartSessionResult {
197
+ /** Current cart snapshot, or null before the first item / resume. */
198
+ cart: Cart | null;
199
+ /** Current opaque session id, or null before the first item. */
200
+ cartSessionId: string | null;
201
+ /** True while a request is in flight. */
202
+ loading: boolean;
203
+ /** Last error, or null. */
204
+ error: Error | null;
205
+ addItem: (input: AddLineItemInput) => Promise<void>;
206
+ updateItem: (itemId: string, input: UpdateLineItemInput) => Promise<void>;
207
+ removeItem: (itemId: string) => Promise<void>;
208
+ selectShipping: (input: SelectShippingInput) => Promise<void>;
209
+ clearShipping: () => Promise<void>;
210
+ applyDiscount: (code: string) => Promise<void>;
211
+ removeDiscount: () => Promise<void>;
212
+ /** Hand off to checkout. Returns the hand-off (redirect the buyer to `checkoutUrl`). */
213
+ checkout: (input: CheckoutHandoffInput) => Promise<CheckoutHandoff>;
214
+ /** Re-fetch the current cart. */
215
+ refresh: () => Promise<void>;
216
+ /** Forget the current session (a new cart is created on the next item). */
217
+ reset: () => void;
218
+ }
219
+ /**
220
+ * React hook for backend-less cart ownership. Wraps {@link CartSessionClient}:
221
+ * creates the Throttle cart lazily on the first `addItem` (so empty carts
222
+ * aren't created on every page load), persists the session id to localStorage,
223
+ * and resumes it on the next visit. Mutations update reactive `cart` state.
224
+ *
225
+ * ```tsx
226
+ * const cart = useCartSession({ applicationId, environmentId, quoteToken });
227
+ * <button onClick={() => cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 })}>Add</button>
228
+ * <button onClick={async () => {
229
+ * const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });
230
+ * window.location.href = checkoutUrl;
231
+ * }}>Checkout</button>
232
+ * ```
233
+ */
234
+ declare function useCartSession(options: UseCartSessionOptions): UseCartSessionResult;
235
+
236
+ export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, type RecurringIntent, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, type UseCartSessionOptions, type UseCartSessionResult, useCartSession, useThrottleEvents };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/CheckoutEmbed.tsx
2
- import { useCallback, useRef } from "react";
2
+ import { useCallback, useRef, useState as useState2 } from "react";
3
3
 
4
4
  // src/useThrottleEvents.ts
5
5
  import { useEffect } from "react";
@@ -19,6 +19,32 @@ function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }) {
19
19
  }, [iframeRef, expectedOrigin, onMessage]);
20
20
  }
21
21
 
22
+ // src/useParentCommands.ts
23
+ import { useEffect as useEffect2, useState } from "react";
24
+ function useParentCommands({
25
+ iframeRef,
26
+ targetOrigin,
27
+ submitDisabled,
28
+ iframeReady
29
+ }) {
30
+ const [lastSent, setLastSent] = useState(void 0);
31
+ useEffect2(() => {
32
+ if (submitDisabled === void 0) return;
33
+ const win = iframeRef.current?.contentWindow;
34
+ if (!win) return;
35
+ if (lastSent === submitDisabled && iframeReady) return;
36
+ if (!iframeReady) return;
37
+ const message = {
38
+ source: "throttle-parent",
39
+ version: 1,
40
+ type: "set-submit-disabled",
41
+ disabled: submitDisabled
42
+ };
43
+ win.postMessage(message, targetOrigin);
44
+ setLastSent(submitDisabled);
45
+ }, [iframeRef, targetOrigin, submitDisabled, iframeReady, lastSent]);
46
+ }
47
+
22
48
  // src/CheckoutEmbed.tsx
23
49
  import { jsx } from "react/jsx-runtime";
24
50
  var DEFAULT_BASE_URL = "https://checkout.usethrottle.dev";
@@ -31,6 +57,7 @@ function CheckoutEmbed({
31
57
  initialHeight = 600,
32
58
  className,
33
59
  style,
60
+ submitDisabled,
34
61
  onReady,
35
62
  onProcessing,
36
63
  onSucceeded,
@@ -40,17 +67,23 @@ function CheckoutEmbed({
40
67
  }) {
41
68
  const iframeRef = useRef(null);
42
69
  const expectedOrigin = new URL(baseUrl).origin;
70
+ const [iframeReady, setIframeReady] = useState2(false);
43
71
  const dispatch = useCallback(
44
72
  (msg) => {
45
73
  switch (msg.type) {
46
74
  case "throttle.ready":
75
+ setIframeReady(true);
47
76
  onReady?.();
48
77
  break;
49
78
  case "throttle.processing":
50
79
  onProcessing?.();
51
80
  break;
52
81
  case "throttle.completed":
53
- onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });
82
+ onSucceeded?.({
83
+ orderId: msg.orderId,
84
+ paymentId: msg.paymentId,
85
+ ...msg.subscriptionId !== void 0 ? { subscriptionId: msg.subscriptionId } : {}
86
+ });
54
87
  break;
55
88
  case "throttle.error":
56
89
  onFailed?.({ code: msg.code, message: msg.message });
@@ -69,6 +102,12 @@ function CheckoutEmbed({
69
102
  [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged]
70
103
  );
71
104
  useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });
105
+ useParentCommands({
106
+ iframeRef,
107
+ targetOrigin: expectedOrigin,
108
+ submitDisabled,
109
+ iframeReady
110
+ });
72
111
  const params = new URLSearchParams({ embed: "1", mode: "checkout-full", parentOrigin });
73
112
  if (primary) params.set("primary", primary);
74
113
  if (logo) params.set("logo", logo);
@@ -88,7 +127,7 @@ function CheckoutEmbed({
88
127
  }
89
128
 
90
129
  // src/PaymentEmbed.tsx
91
- import { useCallback as useCallback2, useRef as useRef2 } from "react";
130
+ import { useCallback as useCallback2, useRef as useRef2, useState as useState3 } from "react";
92
131
  import { jsx as jsx2 } from "react/jsx-runtime";
93
132
  var DEFAULT_BASE_URL2 = "https://checkout.usethrottle.dev";
94
133
  function PaymentEmbed({
@@ -100,6 +139,7 @@ function PaymentEmbed({
100
139
  initialHeight = 480,
101
140
  className,
102
141
  style,
142
+ submitDisabled,
103
143
  onReady,
104
144
  onProcessing,
105
145
  onSucceeded,
@@ -108,17 +148,23 @@ function PaymentEmbed({
108
148
  }) {
109
149
  const iframeRef = useRef2(null);
110
150
  const expectedOrigin = new URL(baseUrl).origin;
151
+ const [iframeReady, setIframeReady] = useState3(false);
111
152
  const dispatch = useCallback2(
112
153
  (msg) => {
113
154
  switch (msg.type) {
114
155
  case "throttle.ready":
156
+ setIframeReady(true);
115
157
  onReady?.();
116
158
  break;
117
159
  case "throttle.processing":
118
160
  onProcessing?.();
119
161
  break;
120
162
  case "throttle.completed":
121
- onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });
163
+ onSucceeded?.({
164
+ orderId: msg.orderId,
165
+ paymentId: msg.paymentId,
166
+ ...msg.subscriptionId !== void 0 ? { subscriptionId: msg.subscriptionId } : {}
167
+ });
122
168
  break;
123
169
  case "throttle.error":
124
170
  onFailed?.({ code: msg.code, message: msg.message });
@@ -134,6 +180,12 @@ function PaymentEmbed({
134
180
  [onReady, onProcessing, onSucceeded, onFailed, onCanceled]
135
181
  );
136
182
  useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });
183
+ useParentCommands({
184
+ iframeRef,
185
+ targetOrigin: expectedOrigin,
186
+ submitDisabled,
187
+ iframeReady
188
+ });
137
189
  const params = new URLSearchParams({ embed: "1", mode: "payment-only", parentOrigin });
138
190
  if (primary) params.set("primary", primary);
139
191
  if (logo) params.set("logo", logo);
@@ -151,10 +203,174 @@ function PaymentEmbed({
151
203
  }
152
204
  );
153
205
  }
206
+
207
+ // src/useCartSession.tsx
208
+ import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useState as useState4 } from "react";
209
+ import {
210
+ CartSessionClient
211
+ } from "@usethrottle/cart";
212
+ var DEFAULT_STORAGE_KEY = "throttle.cartSessionId";
213
+ function readStored(key) {
214
+ if (!key || typeof window === "undefined") return null;
215
+ try {
216
+ return window.localStorage.getItem(key);
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function writeStored(key, value) {
222
+ if (!key || typeof window === "undefined") return;
223
+ try {
224
+ if (value === null) window.localStorage.removeItem(key);
225
+ else window.localStorage.setItem(key, value);
226
+ } catch {
227
+ }
228
+ }
229
+ function useCartSession(options) {
230
+ const { applicationId, environmentId, quoteToken, baseUrl } = options;
231
+ const storageKey = options.storageKey === void 0 ? DEFAULT_STORAGE_KEY : options.storageKey;
232
+ const [cart, setCart] = useState4(null);
233
+ const [cartSessionId, setCartSessionId] = useState4(null);
234
+ const [loading, setLoading] = useState4(false);
235
+ const [error, setError] = useState4(null);
236
+ const { client, initError } = useMemo(() => {
237
+ try {
238
+ return {
239
+ client: new CartSessionClient({ applicationId, environmentId, quoteToken, baseUrl }),
240
+ initError: null
241
+ };
242
+ } catch (e) {
243
+ return { client: null, initError: e };
244
+ }
245
+ }, [applicationId, environmentId, quoteToken, baseUrl]);
246
+ useEffect3(() => {
247
+ if (initError) setError(initError);
248
+ }, [initError]);
249
+ useEffect3(() => {
250
+ if (!client) return;
251
+ const saved = readStored(storageKey);
252
+ if (!saved) return;
253
+ let cancelled = false;
254
+ client.resume(saved);
255
+ setCartSessionId(saved);
256
+ setLoading(true);
257
+ client.get().then((s) => {
258
+ if (cancelled) return;
259
+ setCart(s.cart);
260
+ }).catch(() => {
261
+ if (cancelled) return;
262
+ writeStored(storageKey, null);
263
+ client.resume("");
264
+ setCartSessionId(null);
265
+ setCart(null);
266
+ }).finally(() => {
267
+ if (!cancelled) setLoading(false);
268
+ });
269
+ return () => {
270
+ cancelled = true;
271
+ };
272
+ }, [client, storageKey]);
273
+ const ensureSession = useCallback3(
274
+ async (input) => {
275
+ if (!client) throw initError ?? new Error("CartSessionClient unavailable");
276
+ if (client.cartSessionId) return;
277
+ const session = await client.create(input ?? {});
278
+ writeStored(storageKey, session.cartSessionId);
279
+ setCartSessionId(session.cartSessionId);
280
+ setCart(session.cart);
281
+ },
282
+ [client, initError, storageKey]
283
+ );
284
+ const run = useCallback3(
285
+ async (fn, opts = {}) => {
286
+ if (!client) {
287
+ setError(initError ?? new Error("CartSessionClient unavailable"));
288
+ return;
289
+ }
290
+ setLoading(true);
291
+ setError(null);
292
+ try {
293
+ if (opts.create) await ensureSession();
294
+ const next = await fn();
295
+ setCart(next);
296
+ } catch (e) {
297
+ setError(e);
298
+ throw e;
299
+ } finally {
300
+ setLoading(false);
301
+ }
302
+ },
303
+ [client, ensureSession, initError]
304
+ );
305
+ const addItem = useCallback3(
306
+ (input) => run(() => client.addItem(input), { create: true }),
307
+ [client, run]
308
+ );
309
+ const updateItem = useCallback3(
310
+ (itemId, input) => run(() => client.updateItem(itemId, input)),
311
+ [client, run]
312
+ );
313
+ const removeItem = useCallback3((itemId) => run(() => client.removeItem(itemId)), [client, run]);
314
+ const selectShipping = useCallback3(
315
+ (input) => run(() => client.selectShipping(input), { create: true }),
316
+ [client, run]
317
+ );
318
+ const clearShipping = useCallback3(() => run(() => client.clearShipping()), [client, run]);
319
+ const applyDiscount = useCallback3(
320
+ (code) => run(() => client.applyDiscount(code), { create: true }),
321
+ [client, run]
322
+ );
323
+ const removeDiscount = useCallback3(() => run(() => client.removeDiscount()), [client, run]);
324
+ const refresh = useCallback3(async () => {
325
+ if (!client?.cartSessionId) return;
326
+ await run(async () => (await client.get()).cart);
327
+ }, [client, run]);
328
+ const checkout = useCallback3(
329
+ async (input) => {
330
+ if (!client) throw initError ?? new Error("CartSessionClient unavailable");
331
+ setLoading(true);
332
+ setError(null);
333
+ try {
334
+ await ensureSession();
335
+ return await client.checkout(input);
336
+ } catch (e) {
337
+ setError(e);
338
+ throw e;
339
+ } finally {
340
+ setLoading(false);
341
+ }
342
+ },
343
+ [client, ensureSession, initError]
344
+ );
345
+ const reset = useCallback3(() => {
346
+ writeStored(storageKey, null);
347
+ client?.resume("");
348
+ setCartSessionId(null);
349
+ setCart(null);
350
+ setError(null);
351
+ }, [client, storageKey]);
352
+ return {
353
+ cart,
354
+ cartSessionId,
355
+ loading,
356
+ error,
357
+ addItem,
358
+ updateItem,
359
+ removeItem,
360
+ selectShipping,
361
+ clearShipping,
362
+ applyDiscount,
363
+ removeDiscount,
364
+ checkout,
365
+ refresh,
366
+ reset
367
+ };
368
+ }
154
369
  export {
155
370
  CheckoutEmbed,
156
371
  PaymentEmbed,
157
372
  CheckoutEmbed as ThrottleCheckout,
373
+ useCartSession,
158
374
  useThrottleEvents
159
375
  };
160
376
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/PaymentEmbed.tsx"],"sourcesContent":["import { useCallback, useRef } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport type {\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n ThrottleMessage,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Full-checkout iframe embed. Renders the entire Throttle hosted\n * checkout (address / shipping / payment) inside an iframe and\n * surfaces lifecycle + terminal events through callbacks. The\n * iframe auto-resizes on `throttle.resize` events.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list\n * (`throttle embed-config set --origins ...`). Mismatches render\n * an in-iframe \"Embed not authorized\" panel.\n */\nexport function CheckoutEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 600,\n className,\n style,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n onStepChanged,\n}: ThrottleEmbedProps & CheckoutEmbedExtraProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.step.changed':\n onStepChanged?.({ step: msg.step });\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n\n const params = new URLSearchParams({ embed: '1', mode: 'checkout-full', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle checkout\"\n />\n );\n}\n","import { useEffect, type RefObject } from 'react';\nimport type { ThrottleMessage } from './types.js';\n\nexport interface UseThrottleEventsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /**\n * The Throttle origin (e.g. https://checkout.usethrottle.dev).\n * Strict-equals check against `MessageEvent.origin`. Anything else\n * is silently dropped.\n */\n expectedOrigin: string;\n onMessage: (msg: ThrottleMessage) => void;\n}\n\n/**\n * Subscribe to validated postMessage events from a Throttle iframe.\n *\n * Validation:\n * - event.origin === expectedOrigin (strict string match)\n * - event.source === iframeRef.current.contentWindow (defends against\n * other iframes on the same origin spoofing events)\n * - event.data.source === 'throttle' && event.data.version === 1\n * - event.data.type starts with 'throttle.'\n *\n * Anything failing validation is silently dropped — the parent page\n * may receive postMessages from analytics SDKs, browser extensions,\n * other iframes, etc. We must never throw on those.\n */\nexport function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions) {\n useEffect(() => {\n function handle(ev: MessageEvent) {\n if (ev.origin !== expectedOrigin) return;\n if (iframeRef.current && ev.source !== iframeRef.current.contentWindow) return;\n const data = ev.data as Partial<ThrottleMessage> | null | undefined;\n if (!data || typeof data !== 'object') return;\n if (data.source !== 'throttle' || data.version !== 1) return;\n if (typeof data.type !== 'string' || !data.type.startsWith('throttle.')) return;\n onMessage(data as ThrottleMessage);\n }\n window.addEventListener('message', handle);\n return () => window.removeEventListener('message', handle);\n }, [iframeRef, expectedOrigin, onMessage]);\n}\n","import { useCallback, useRef } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport type { ThrottleEmbedProps, ThrottleMessage } from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Payment-only iframe embed. Renders just the Gr4vy provider iframe\n * (no address / shipping / cart UI). Email is collected by the Gr4vy\n * iframe in proxy-mode sessions. Used when the merchant has built\n * their own checkout UI and only needs Throttle to handle payment\n * authorization + capture.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list.\n * The iframe auto-resizes on `throttle.resize` events.\n */\nexport function PaymentEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 480,\n className,\n style,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n}: ThrottleEmbedProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({ orderId: msg.orderId, paymentId: msg.paymentId });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n\n const params = new URLSearchParams({ embed: '1', mode: 'payment-only', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle payment\"\n />\n );\n}\n"],"mappings":"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","names":["useCallback","useRef","jsx","DEFAULT_BASE_URL","useRef","useCallback"]}
1
+ {"version":3,"sources":["../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/useParentCommands.ts","../src/PaymentEmbed.tsx","../src/useCartSession.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport { useParentCommands } from './useParentCommands.js';\nimport type {\n ThrottleEmbedProps,\n CheckoutEmbedExtraProps,\n ThrottleMessage,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Full-checkout iframe embed. Renders the entire Throttle hosted\n * checkout (address / shipping / payment) inside an iframe and\n * surfaces lifecycle + terminal events through callbacks. The\n * iframe auto-resizes on `throttle.resize` events.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list\n * (`throttle embed-config set --origins ...`). Mismatches render\n * an in-iframe \"Embed not authorized\" panel.\n */\nexport function CheckoutEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 600,\n className,\n style,\n submitDisabled,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n onStepChanged,\n}: ThrottleEmbedProps & CheckoutEmbedExtraProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n const [iframeReady, setIframeReady] = useState(false);\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n setIframeReady(true);\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({\n orderId: msg.orderId,\n paymentId: msg.paymentId,\n ...(msg.subscriptionId !== undefined ? { subscriptionId: msg.subscriptionId } : {}),\n });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.step.changed':\n onStepChanged?.({ step: msg.step });\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled, onStepChanged],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n useParentCommands({\n iframeRef,\n targetOrigin: expectedOrigin,\n submitDisabled,\n iframeReady,\n });\n\n const params = new URLSearchParams({ embed: '1', mode: 'checkout-full', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle checkout\"\n />\n );\n}\n","import { useEffect, type RefObject } from 'react';\nimport type { ThrottleMessage } from './types.js';\n\nexport interface UseThrottleEventsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /**\n * The Throttle origin (e.g. https://checkout.usethrottle.dev).\n * Strict-equals check against `MessageEvent.origin`. Anything else\n * is silently dropped.\n */\n expectedOrigin: string;\n onMessage: (msg: ThrottleMessage) => void;\n}\n\n/**\n * Subscribe to validated postMessage events from a Throttle iframe.\n *\n * Validation:\n * - event.origin === expectedOrigin (strict string match)\n * - event.source === iframeRef.current.contentWindow (defends against\n * other iframes on the same origin spoofing events)\n * - event.data.source === 'throttle' && event.data.version === 1\n * - event.data.type starts with 'throttle.'\n *\n * Anything failing validation is silently dropped — the parent page\n * may receive postMessages from analytics SDKs, browser extensions,\n * other iframes, etc. We must never throw on those.\n */\nexport function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions) {\n useEffect(() => {\n function handle(ev: MessageEvent) {\n if (ev.origin !== expectedOrigin) return;\n if (iframeRef.current && ev.source !== iframeRef.current.contentWindow) return;\n const data = ev.data as Partial<ThrottleMessage> | null | undefined;\n if (!data || typeof data !== 'object') return;\n if (data.source !== 'throttle' || data.version !== 1) return;\n if (typeof data.type !== 'string' || !data.type.startsWith('throttle.')) return;\n onMessage(data as ThrottleMessage);\n }\n window.addEventListener('message', handle);\n return () => window.removeEventListener('message', handle);\n }, [iframeRef, expectedOrigin, onMessage]);\n}\n","import { useEffect, useState, type RefObject } from 'react';\nimport type { ThrottleParentMessage } from './types.js';\n\ninterface UseParentCommandsOptions {\n iframeRef: RefObject<HTMLIFrameElement | null>;\n /** The Throttle origin to address messages to. */\n targetOrigin: string;\n /**\n * The current value of `submitDisabled` from the embed props. Posts a\n * `set-submit-disabled` parent command whenever it changes — and once\n * after the iframe reports `throttle.ready` so the iframe sees the\n * intent even if it boots after the prop is already set.\n *\n * `undefined` means the parent never explicitly enabled/disabled — we\n * never post in that case. Backward-compat with embeds that don't\n * pass the prop.\n */\n submitDisabled?: boolean;\n /**\n * Set to `true` once the iframe posts `throttle.ready`. Until then,\n * commands are buffered (we still try to post but the iframe may not\n * have its listener attached yet — and we re-post on ready as a\n * belt-and-braces guard).\n */\n iframeReady: boolean;\n}\n\n/**\n * Post parent → iframe commands when controlled props change. v1.2 adds\n * `set-submit-disabled`; future commands extend the same channel.\n *\n * postMessage is fire-and-forget — we don't wait for an ack from the\n * iframe. The iframe's listener is idempotent (setting the same value\n * twice is a no-op).\n */\nexport function useParentCommands({\n iframeRef,\n targetOrigin,\n submitDisabled,\n iframeReady,\n}: UseParentCommandsOptions) {\n const [lastSent, setLastSent] = useState<boolean | undefined>(undefined);\n\n // Post when the prop value changes OR when the iframe transitions to\n // ready (so the first state lands).\n useEffect(() => {\n if (submitDisabled === undefined) return;\n const win = iframeRef.current?.contentWindow;\n if (!win) return;\n // Skip if we already sent this value AND the iframe was already ready\n // — avoids spam on unrelated re-renders.\n if (lastSent === submitDisabled && iframeReady) return;\n if (!iframeReady) return; // Defer until ready\n\n const message: ThrottleParentMessage = {\n source: 'throttle-parent',\n version: 1,\n type: 'set-submit-disabled',\n disabled: submitDisabled,\n };\n win.postMessage(message, targetOrigin);\n setLastSent(submitDisabled);\n }, [iframeRef, targetOrigin, submitDisabled, iframeReady, lastSent]);\n}\n","import { useCallback, useRef, useState } from 'react';\nimport { useThrottleEvents } from './useThrottleEvents.js';\nimport { useParentCommands } from './useParentCommands.js';\nimport type { ThrottleEmbedProps, ThrottleMessage } from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://checkout.usethrottle.dev';\n\n/**\n * Payment-only iframe embed. Renders just the Gr4vy provider iframe\n * (no address / shipping / cart UI). Email is collected by the Gr4vy\n * iframe in proxy-mode sessions. Used when the merchant has built\n * their own checkout UI and only needs Throttle to handle payment\n * authorization + capture.\n *\n * `parentOrigin` must match an entry in the merchant's allow-list.\n * The iframe auto-resizes on `throttle.resize` events.\n */\nexport function PaymentEmbed({\n sessionId,\n parentOrigin,\n baseUrl = DEFAULT_BASE_URL,\n primary,\n logo,\n initialHeight = 480,\n className,\n style,\n submitDisabled,\n onReady,\n onProcessing,\n onSucceeded,\n onFailed,\n onCanceled,\n}: ThrottleEmbedProps) {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const expectedOrigin = new URL(baseUrl).origin;\n const [iframeReady, setIframeReady] = useState(false);\n\n const dispatch = useCallback(\n (msg: ThrottleMessage) => {\n switch (msg.type) {\n case 'throttle.ready':\n setIframeReady(true);\n onReady?.();\n break;\n case 'throttle.processing':\n onProcessing?.();\n break;\n case 'throttle.completed':\n onSucceeded?.({\n orderId: msg.orderId,\n paymentId: msg.paymentId,\n ...(msg.subscriptionId !== undefined ? { subscriptionId: msg.subscriptionId } : {}),\n });\n break;\n case 'throttle.error':\n onFailed?.({ code: msg.code, message: msg.message });\n break;\n case 'throttle.cancelled':\n onCanceled?.();\n break;\n case 'throttle.resize':\n if (iframeRef.current) iframeRef.current.style.height = `${msg.height}px`;\n break;\n }\n },\n [onReady, onProcessing, onSucceeded, onFailed, onCanceled],\n );\n\n useThrottleEvents({ iframeRef, expectedOrigin, onMessage: dispatch });\n useParentCommands({\n iframeRef,\n targetOrigin: expectedOrigin,\n submitDisabled,\n iframeReady,\n });\n\n const params = new URLSearchParams({ embed: '1', mode: 'payment-only', parentOrigin });\n if (primary) params.set('primary', primary);\n if (logo) params.set('logo', logo);\n const cleanBase = baseUrl.replace(/\\/$/, '');\n const src = `${cleanBase}/c/${encodeURIComponent(sessionId)}?${params.toString()}`;\n\n return (\n <iframe\n ref={iframeRef}\n src={src}\n className={className}\n style={{ border: 0, width: '100%', minHeight: initialHeight, ...style }}\n allow=\"payment *\"\n title=\"Throttle payment\"\n />\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n CartSessionClient,\n type Cart,\n type AddLineItemInput,\n type UpdateLineItemInput,\n type SelectShippingInput,\n type CreateCartSessionInput,\n type CheckoutHandoffInput,\n type CheckoutHandoff,\n} from '@usethrottle/cart';\n\nconst DEFAULT_STORAGE_KEY = 'throttle.cartSessionId';\n\nexport interface UseCartSessionOptions {\n /** Application UUID. */\n applicationId: string;\n /** Workspace environment UUID the publishable token was minted for. */\n environmentId: string;\n /** Publishable storefront quote token (`pk_…`). */\n quoteToken: string;\n baseUrl?: string;\n /**\n * localStorage key for persisting the session id across page loads. Default\n * `'throttle.cartSessionId'`. Pass `null` to disable persistence (in-memory\n * only).\n */\n storageKey?: string | null;\n}\n\nexport interface UseCartSessionResult {\n /** Current cart snapshot, or null before the first item / resume. */\n cart: Cart | null;\n /** Current opaque session id, or null before the first item. */\n cartSessionId: string | null;\n /** True while a request is in flight. */\n loading: boolean;\n /** Last error, or null. */\n error: Error | null;\n addItem: (input: AddLineItemInput) => Promise<void>;\n updateItem: (itemId: string, input: UpdateLineItemInput) => Promise<void>;\n removeItem: (itemId: string) => Promise<void>;\n selectShipping: (input: SelectShippingInput) => Promise<void>;\n clearShipping: () => Promise<void>;\n applyDiscount: (code: string) => Promise<void>;\n removeDiscount: () => Promise<void>;\n /** Hand off to checkout. Returns the hand-off (redirect the buyer to `checkoutUrl`). */\n checkout: (input: CheckoutHandoffInput) => Promise<CheckoutHandoff>;\n /** Re-fetch the current cart. */\n refresh: () => Promise<void>;\n /** Forget the current session (a new cart is created on the next item). */\n reset: () => void;\n}\n\nfunction readStored(key: string | null | undefined): string | null {\n if (!key || typeof window === 'undefined') return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeStored(key: string | null | undefined, value: string | null): void {\n if (!key || typeof window === 'undefined') return;\n try {\n if (value === null) window.localStorage.removeItem(key);\n else window.localStorage.setItem(key, value);\n } catch {\n /* ignore quota / privacy-mode errors */\n }\n}\n\n/**\n * React hook for backend-less cart ownership. Wraps {@link CartSessionClient}:\n * creates the Throttle cart lazily on the first `addItem` (so empty carts\n * aren't created on every page load), persists the session id to localStorage,\n * and resumes it on the next visit. Mutations update reactive `cart` state.\n *\n * ```tsx\n * const cart = useCartSession({ applicationId, environmentId, quoteToken });\n * <button onClick={() => cart.addItem({ name: 'Widget', unitPrice: 2999, quantity: 1 })}>Add</button>\n * <button onClick={async () => {\n * const { checkoutUrl } = await cart.checkout({ returnUrl, cancelUrl });\n * window.location.href = checkoutUrl;\n * }}>Checkout</button>\n * ```\n */\nexport function useCartSession(options: UseCartSessionOptions): UseCartSessionResult {\n const { applicationId, environmentId, quoteToken, baseUrl } = options;\n const storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n\n const [cart, setCart] = useState<Cart | null>(null);\n const [cartSessionId, setCartSessionId] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Stable client across renders for a given config; construction validates the\n // token, so capture any constructor error instead of throwing during render.\n const { client, initError } = useMemo(() => {\n try {\n return {\n client: new CartSessionClient({ applicationId, environmentId, quoteToken, baseUrl }),\n initError: null as Error | null,\n };\n } catch (e) {\n return { client: null as CartSessionClient | null, initError: e as Error };\n }\n }, [applicationId, environmentId, quoteToken, baseUrl]);\n\n useEffect(() => {\n if (initError) setError(initError);\n }, [initError]);\n\n // Resume a persisted session on mount (and whenever the client identity changes).\n useEffect(() => {\n if (!client) return;\n const saved = readStored(storageKey);\n if (!saved) return;\n let cancelled = false;\n client.resume(saved);\n setCartSessionId(saved);\n setLoading(true);\n client\n .get()\n .then((s) => {\n if (cancelled) return;\n setCart(s.cart);\n })\n .catch(() => {\n // Expired / unknown / origin-rejected → drop the stale id and start fresh.\n if (cancelled) return;\n writeStored(storageKey, null);\n client.resume('');\n setCartSessionId(null);\n setCart(null);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [client, storageKey]);\n\n const ensureSession = useCallback(\n async (input?: CreateCartSessionInput): Promise<void> => {\n if (!client) throw initError ?? new Error('CartSessionClient unavailable');\n if (client.cartSessionId) return;\n const session = await client.create(input ?? {});\n writeStored(storageKey, session.cartSessionId);\n setCartSessionId(session.cartSessionId);\n setCart(session.cart);\n },\n [client, initError, storageKey],\n );\n\n /** Run a mutation that returns the updated cart, managing loading/error + creating the session first. */\n const run = useCallback(\n async (fn: () => Promise<Cart>, opts: { create?: boolean } = {}): Promise<void> => {\n if (!client) {\n setError(initError ?? new Error('CartSessionClient unavailable'));\n return;\n }\n setLoading(true);\n setError(null);\n try {\n if (opts.create) await ensureSession();\n const next = await fn();\n setCart(next);\n } catch (e) {\n setError(e as Error);\n throw e;\n } finally {\n setLoading(false);\n }\n },\n [client, ensureSession, initError],\n );\n\n const addItem = useCallback(\n (input: AddLineItemInput) => run(() => client!.addItem(input), { create: true }),\n [client, run],\n );\n const updateItem = useCallback(\n (itemId: string, input: UpdateLineItemInput) => run(() => client!.updateItem(itemId, input)),\n [client, run],\n );\n const removeItem = useCallback((itemId: string) => run(() => client!.removeItem(itemId)), [client, run]);\n const selectShipping = useCallback(\n (input: SelectShippingInput) => run(() => client!.selectShipping(input), { create: true }),\n [client, run],\n );\n const clearShipping = useCallback(() => run(() => client!.clearShipping()), [client, run]);\n const applyDiscount = useCallback(\n (code: string) => run(() => client!.applyDiscount(code), { create: true }),\n [client, run],\n );\n const removeDiscount = useCallback(() => run(() => client!.removeDiscount()), [client, run]);\n const refresh = useCallback(async () => {\n if (!client?.cartSessionId) return;\n await run(async () => (await client.get()).cart);\n }, [client, run]);\n\n const checkout = useCallback(\n async (input: CheckoutHandoffInput): Promise<CheckoutHandoff> => {\n if (!client) throw initError ?? new Error('CartSessionClient unavailable');\n setLoading(true);\n setError(null);\n try {\n await ensureSession();\n return await client.checkout(input);\n } catch (e) {\n setError(e as Error);\n throw e;\n } finally {\n setLoading(false);\n }\n },\n [client, ensureSession, initError],\n );\n\n const reset = useCallback(() => {\n writeStored(storageKey, null);\n client?.resume('');\n setCartSessionId(null);\n setCart(null);\n setError(null);\n }, [client, storageKey]);\n\n return {\n cart,\n cartSessionId,\n loading,\n error,\n addItem,\n updateItem,\n removeItem,\n selectShipping,\n clearShipping,\n applyDiscount,\n removeDiscount,\n checkout,\n refresh,\n reset,\n };\n}\n"],"mappings":"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","names":["useState","useEffect","useState","useCallback","useRef","useState","jsx","DEFAULT_BASE_URL","useRef","useState","useCallback","useCallback","useEffect","useState"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/checkout-react",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "React component for embedded Throttle checkout",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,6 +34,9 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
+ "dependencies": {
38
+ "@usethrottle/cart": "^3.4.0"
39
+ },
37
40
  "scripts": {
38
41
  "build": "tsup",
39
42
  "dev": "tsup --watch",