@usethrottle/checkout-react 1.0.1 → 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.
@@ -31,7 +36,7 @@ Or via API:
31
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
 
@@ -109,6 +114,53 @@ export function MyEmbed({ sessionId }: { sessionId: string }) {
109
114
  }
110
115
  ```
111
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
+
112
164
  ## Events
113
165
 
114
166
  Both `<PaymentEmbed/>` and `<CheckoutEmbed/>` fire these events to the
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __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);
@@ -232,11 +233,173 @@ function PaymentEmbed({
232
233
  }
233
234
  );
234
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
+ }
235
397
  // Annotate the CommonJS export names for ESM import in node:
236
398
  0 && (module.exports = {
237
399
  CheckoutEmbed,
238
400
  PaymentEmbed,
239
401
  ThrottleCheckout,
402
+ useCartSession,
240
403
  useThrottleEvents
241
404
  });
242
405
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/useParentCommands.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 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"],"mappings":"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","names":["import_react","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
@@ -177,4 +178,59 @@ interface UseThrottleEventsOptions {
177
178
  */
178
179
  declare function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions): void;
179
180
 
180
- export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, type RecurringIntent, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, 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
@@ -177,4 +178,59 @@ interface UseThrottleEventsOptions {
177
178
  */
178
179
  declare function useThrottleEvents({ iframeRef, expectedOrigin, onMessage }: UseThrottleEventsOptions): void;
179
180
 
180
- export { CheckoutEmbed, type CheckoutEmbedExtraProps, PaymentEmbed, type RecurringIntent, CheckoutEmbed as ThrottleCheckout, type ThrottleEmbedProps, type ThrottleEnvelope, type ThrottleEvent, type ThrottleMessage, type ThrottleParentCommand, type ThrottleParentEnvelope, type ThrottleParentMessage, 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
@@ -203,10 +203,174 @@ function PaymentEmbed({
203
203
  }
204
204
  );
205
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
+ }
206
369
  export {
207
370
  CheckoutEmbed,
208
371
  PaymentEmbed,
209
372
  CheckoutEmbed as ThrottleCheckout,
373
+ useCartSession,
210
374
  useThrottleEvents
211
375
  };
212
376
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/CheckoutEmbed.tsx","../src/useThrottleEvents.ts","../src/useParentCommands.ts","../src/PaymentEmbed.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"],"mappings":"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","names":["useState","useEffect","useState","useCallback","useRef","useState","jsx","DEFAULT_BASE_URL","useRef","useState","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.1",
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",