@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 +69 -15
- package/dist/index.cjs +223 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -5
- package/dist/index.d.ts +118 -5
- package/dist/index.js +220 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
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
|
|
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://
|
|
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({
|
|
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://
|
|
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
|
|
118
|
-
|
|
119
|
-
| `throttle.ready`
|
|
120
|
-
| `throttle.processing`
|
|
121
|
-
| `throttle.completed`
|
|
122
|
-
| `throttle.error`
|
|
123
|
-
| `throttle.cancelled`
|
|
124
|
-
| `throttle.step.changed` | (CheckoutEmbed) step transition | `onStepChanged({step})`
|
|
125
|
-
| `throttle.resize`
|
|
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
|
-
- [
|
|
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
|
|
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,
|
|
98
|
+
const iframeRef = (0, import_react3.useRef)(null);
|
|
71
99
|
const expectedOrigin = new URL(baseUrl).origin;
|
|
72
|
-
const
|
|
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?.({
|
|
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
|
|
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,
|
|
179
|
+
const iframeRef = (0, import_react4.useRef)(null);
|
|
139
180
|
const expectedOrigin = new URL(baseUrl).origin;
|
|
140
|
-
const
|
|
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?.({
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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?.({
|
|
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?.({
|
|
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":";AAAA,SAAS,aAAa,QAAQ,YAAAA,iBAAgB;;;ACA9C,SAAS,iBAAiC;AA4BnC,SAAS,kBAAkB,EAAE,WAAW,gBAAgB,UAAU,GAA6B;AACpG,YAAU,MAAM;AACd,aAAS,OAAO,IAAkB;AAChC,UAAI,GAAG,WAAW,eAAgB;AAClC,UAAI,UAAU,WAAW,GAAG,WAAW,UAAU,QAAQ,cAAe;AACxE,YAAM,OAAO,GAAG;AAChB,UAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,UAAI,KAAK,WAAW,cAAc,KAAK,YAAY,EAAG;AACtD,UAAI,OAAO,KAAK,SAAS,YAAY,CAAC,KAAK,KAAK,WAAW,WAAW,EAAG;AACzE,gBAAU,IAAuB;AAAA,IACnC;AACA,WAAO,iBAAiB,WAAW,MAAM;AACzC,WAAO,MAAM,OAAO,oBAAoB,WAAW,MAAM;AAAA,EAC3D,GAAG,CAAC,WAAW,gBAAgB,SAAS,CAAC;AAC3C;;;AC1CA,SAAS,aAAAC,YAAW,gBAAgC;AAmC7C,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,CAAC,UAAU,WAAW,IAAI,SAA8B,MAAS;AAIvE,EAAAA,WAAU,MAAM;AACd,QAAI,mBAAmB,OAAW;AAClC,UAAM,MAAM,UAAU,SAAS;AAC/B,QAAI,CAAC,IAAK;AAGV,QAAI,aAAa,kBAAkB,YAAa;AAChD,QAAI,CAAC,YAAa;AAElB,UAAM,UAAiC;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AACA,QAAI,YAAY,SAAS,YAAY;AACrC,gBAAY,cAAc;AAAA,EAC5B,GAAG,CAAC,WAAW,cAAc,gBAAgB,aAAa,QAAQ,CAAC;AACrE;;;AF4BI;AAlFJ,IAAM,mBAAmB;AAYlB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiD;AAC/C,QAAM,YAAY,OAA0B,IAAI;AAChD,QAAM,iBAAiB,IAAI,IAAI,OAAO,EAAE;AACxC,QAAM,CAAC,aAAa,cAAc,IAAIC,UAAS,KAAK;AAEpD,QAAM,WAAW;AAAA,IACf,CAAC,QAAyB;AACxB,cAAQ,IAAI,MAAM;AAAA,QAChB,KAAK;AACH,yBAAe,IAAI;AACnB,oBAAU;AACV;AAAA,QACF,KAAK;AACH,yBAAe;AACf;AAAA,QACF,KAAK;AACH,wBAAc;AAAA,YACZ,SAAS,IAAI;AAAA,YACb,WAAW,IAAI;AAAA,YACf,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,UACnF,CAAC;AACD;AAAA,QACF,KAAK;AACH,qBAAW,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,QAAQ,CAAC;AACnD;AAAA,QACF,KAAK;AACH,uBAAa;AACb;AAAA,QACF,KAAK;AACH,0BAAgB,EAAE,MAAM,IAAI,KAAK,CAAC;AAClC;AAAA,QACF,KAAK;AACH,cAAI,UAAU,QAAS,WAAU,QAAQ,MAAM,SAAS,GAAG,IAAI,MAAM;AACrE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,SAAS,cAAc,aAAa,UAAU,YAAY,aAAa;AAAA,EAC1E;AAEA,oBAAkB,EAAE,WAAW,gBAAgB,WAAW,SAAS,CAAC;AACpE,oBAAkB;AAAA,IAChB;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,SAAS,IAAI,gBAAgB,EAAE,OAAO,KAAK,MAAM,iBAAiB,aAAa,CAAC;AACtF,MAAI,QAAS,QAAO,IAAI,WAAW,OAAO;AAC1C,MAAI,KAAM,QAAO,IAAI,QAAQ,IAAI;AACjC,QAAM,YAAY,QAAQ,QAAQ,OAAO,EAAE;AAC3C,QAAM,MAAM,GAAG,SAAS,MAAM,mBAAmB,SAAS,CAAC,IAAI,OAAO,SAAS,CAAC;AAEhF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,EAAE,QAAQ,GAAG,OAAO,QAAQ,WAAW,eAAe,GAAG,MAAM;AAAA,MACtE,OAAM;AAAA,MACN,OAAM;AAAA;AAAA,EACR;AAEJ;;;AGpGA,SAAS,eAAAC,cAAa,UAAAC,SAAQ,YAAAC,iBAAgB;AAmF1C,gBAAAC,YAAA;AA9EJ,IAAMC,oBAAmB;AAYlB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,UAAUA;AAAA,EACV;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,YAAYC,QAA0B,IAAI;AAChD,QAAM,iBAAiB,IAAI,IAAI,OAAO,EAAE;AACxC,QAAM,CAAC,aAAa,cAAc,IAAIC,UAAS,KAAK;AAEpD,QAAM,WAAWC;AAAA,IACf,CAAC,QAAyB;AACxB,cAAQ,IAAI,MAAM;AAAA,QAChB,KAAK;AACH,yBAAe,IAAI;AACnB,oBAAU;AACV;AAAA,QACF,KAAK;AACH,yBAAe;AACf;AAAA,QACF,KAAK;AACH,wBAAc;AAAA,YACZ,SAAS,IAAI;AAAA,YACb,WAAW,IAAI;AAAA,YACf,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,UACnF,CAAC;AACD;AAAA,QACF,KAAK;AACH,qBAAW,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,QAAQ,CAAC;AACnD;AAAA,QACF,KAAK;AACH,uBAAa;AACb;AAAA,QACF,KAAK;AACH,cAAI,UAAU,QAAS,WAAU,QAAQ,MAAM,SAAS,GAAG,IAAI,MAAM;AACrE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,SAAS,cAAc,aAAa,UAAU,UAAU;AAAA,EAC3D;AAEA,oBAAkB,EAAE,WAAW,gBAAgB,WAAW,SAAS,CAAC;AACpE,oBAAkB;AAAA,IAChB;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,SAAS,IAAI,gBAAgB,EAAE,OAAO,KAAK,MAAM,gBAAgB,aAAa,CAAC;AACrF,MAAI,QAAS,QAAO,IAAI,WAAW,OAAO;AAC1C,MAAI,KAAM,QAAO,IAAI,QAAQ,IAAI;AACjC,QAAM,YAAY,QAAQ,QAAQ,OAAO,EAAE;AAC3C,QAAM,MAAM,GAAG,SAAS,MAAM,mBAAmB,SAAS,CAAC,IAAI,OAAO,SAAS,CAAC;AAEhF,SACE,gBAAAJ;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,EAAE,QAAQ,GAAG,OAAO,QAAQ,WAAW,eAAe,GAAG,MAAM;AAAA,MACtE,OAAM;AAAA,MACN,OAAM;AAAA;AAAA,EACR;AAEJ;;;AC5FA,SAAS,eAAAK,cAAa,aAAAC,YAAW,SAAS,YAAAC,iBAAgB;AAC1D;AAAA,EACE;AAAA,OAQK;AAEP,IAAM,sBAAsB;AA0C5B,SAAS,WAAW,KAA+C;AACjE,MAAI,CAAC,OAAO,OAAO,WAAW,YAAa,QAAO;AAClD,MAAI;AACF,WAAO,OAAO,aAAa,QAAQ,GAAG;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,KAAgC,OAA4B;AAC/E,MAAI,CAAC,OAAO,OAAO,WAAW,YAAa;AAC3C,MAAI;AACF,QAAI,UAAU,KAAM,QAAO,aAAa,WAAW,GAAG;AAAA,QACjD,QAAO,aAAa,QAAQ,KAAK,KAAK;AAAA,EAC7C,QAAQ;AAAA,EAER;AACF;AAiBO,SAAS,eAAe,SAAsD;AACnF,QAAM,EAAE,eAAe,eAAe,YAAY,QAAQ,IAAI;AAC9D,QAAM,aAAa,QAAQ,eAAe,SAAY,sBAAsB,QAAQ;AAEpF,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAsB,IAAI;AAClD,QAAM,CAAC,eAAe,gBAAgB,IAAIA,UAAwB,IAAI;AACtE,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAIrD,QAAM,EAAE,QAAQ,UAAU,IAAI,QAAQ,MAAM;AAC1C,QAAI;AACF,aAAO;AAAA,QACL,QAAQ,IAAI,kBAAkB,EAAE,eAAe,eAAe,YAAY,QAAQ,CAAC;AAAA,QACnF,WAAW;AAAA,MACb;AAAA,IACF,SAAS,GAAG;AACV,aAAO,EAAE,QAAQ,MAAkC,WAAW,EAAW;AAAA,IAC3E;AAAA,EACF,GAAG,CAAC,eAAe,eAAe,YAAY,OAAO,CAAC;AAEtD,EAAAD,WAAU,MAAM;AACd,QAAI,UAAW,UAAS,SAAS;AAAA,EACnC,GAAG,CAAC,SAAS,CAAC;AAGd,EAAAA,WAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,QAAQ,WAAW,UAAU;AACnC,QAAI,CAAC,MAAO;AACZ,QAAI,YAAY;AAChB,WAAO,OAAO,KAAK;AACnB,qBAAiB,KAAK;AACtB,eAAW,IAAI;AACf,WACG,IAAI,EACJ,KAAK,CAAC,MAAM;AACX,UAAI,UAAW;AACf,cAAQ,EAAE,IAAI;AAAA,IAChB,CAAC,EACA,MAAM,MAAM;AAEX,UAAI,UAAW;AACf,kBAAY,YAAY,IAAI;AAC5B,aAAO,OAAO,EAAE;AAChB,uBAAiB,IAAI;AACrB,cAAQ,IAAI;AAAA,IACd,CAAC,EACA,QAAQ,MAAM;AACb,UAAI,CAAC,UAAW,YAAW,KAAK;AAAA,IAClC,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAM,gBAAgBD;AAAA,IACpB,OAAO,UAAkD;AACvD,UAAI,CAAC,OAAQ,OAAM,aAAa,IAAI,MAAM,+BAA+B;AACzE,UAAI,OAAO,cAAe;AAC1B,YAAM,UAAU,MAAM,OAAO,OAAO,SAAS,CAAC,CAAC;AAC/C,kBAAY,YAAY,QAAQ,aAAa;AAC7C,uBAAiB,QAAQ,aAAa;AACtC,cAAQ,QAAQ,IAAI;AAAA,IACtB;AAAA,IACA,CAAC,QAAQ,WAAW,UAAU;AAAA,EAChC;AAGA,QAAM,MAAMA;AAAA,IACV,OAAO,IAAyB,OAA6B,CAAC,MAAqB;AACjF,UAAI,CAAC,QAAQ;AACX,iBAAS,aAAa,IAAI,MAAM,+BAA+B,CAAC;AAChE;AAAA,MACF;AACA,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,UAAI;AACF,YAAI,KAAK,OAAQ,OAAM,cAAc;AACrC,cAAM,OAAO,MAAM,GAAG;AACtB,gBAAQ,IAAI;AAAA,MACd,SAAS,GAAG;AACV,iBAAS,CAAU;AACnB,cAAM;AAAA,MACR,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,eAAe,SAAS;AAAA,EACnC;AAEA,QAAM,UAAUA;AAAA,IACd,CAAC,UAA4B,IAAI,MAAM,OAAQ,QAAQ,KAAK,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC/E,CAAC,QAAQ,GAAG;AAAA,EACd;AACA,QAAM,aAAaA;AAAA,IACjB,CAAC,QAAgB,UAA+B,IAAI,MAAM,OAAQ,WAAW,QAAQ,KAAK,CAAC;AAAA,IAC3F,CAAC,QAAQ,GAAG;AAAA,EACd;AACA,QAAM,aAAaA,aAAY,CAAC,WAAmB,IAAI,MAAM,OAAQ,WAAW,MAAM,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC;AACvG,QAAM,iBAAiBA;AAAA,IACrB,CAAC,UAA+B,IAAI,MAAM,OAAQ,eAAe,KAAK,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IACzF,CAAC,QAAQ,GAAG;AAAA,EACd;AACA,QAAM,gBAAgBA,aAAY,MAAM,IAAI,MAAM,OAAQ,cAAc,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC;AACzF,QAAM,gBAAgBA;AAAA,IACpB,CAAC,SAAiB,IAAI,MAAM,OAAQ,cAAc,IAAI,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IACzE,CAAC,QAAQ,GAAG;AAAA,EACd;AACA,QAAM,iBAAiBA,aAAY,MAAM,IAAI,MAAM,OAAQ,eAAe,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC;AAC3F,QAAM,UAAUA,aAAY,YAAY;AACtC,QAAI,CAAC,QAAQ,cAAe;AAC5B,UAAM,IAAI,aAAa,MAAM,OAAO,IAAI,GAAG,IAAI;AAAA,EACjD,GAAG,CAAC,QAAQ,GAAG,CAAC;AAEhB,QAAM,WAAWA;AAAA,IACf,OAAO,UAA0D;AAC/D,UAAI,CAAC,OAAQ,OAAM,aAAa,IAAI,MAAM,+BAA+B;AACzE,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,UAAI;AACF,cAAM,cAAc;AACpB,eAAO,MAAM,OAAO,SAAS,KAAK;AAAA,MACpC,SAAS,GAAG;AACV,iBAAS,CAAU;AACnB,cAAM;AAAA,MACR,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,eAAe,SAAS;AAAA,EACnC;AAEA,QAAM,QAAQA,aAAY,MAAM;AAC9B,gBAAY,YAAY,IAAI;AAC5B,YAAQ,OAAO,EAAE;AACjB,qBAAiB,IAAI;AACrB,YAAQ,IAAI;AACZ,aAAS,IAAI;AAAA,EACf,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","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.
|
|
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",
|