@appfunnel-dev/sdk 0.6.0 → 0.8.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 +2 -80
- package/dist/{chunk-BUF5FDKC.cjs → chunk-EVUYCLVY.cjs} +42 -19
- package/dist/chunk-EVUYCLVY.cjs.map +1 -0
- package/dist/{chunk-E6KSJ5UI.js → chunk-H3KHXZSI.js} +42 -20
- package/dist/chunk-H3KHXZSI.js.map +1 -0
- package/dist/chunk-P4SLDMWY.js +104 -0
- package/dist/chunk-P4SLDMWY.js.map +1 -0
- package/dist/chunk-XP44I2MU.cjs +108 -0
- package/dist/chunk-XP44I2MU.cjs.map +1 -0
- package/dist/elements/index.cjs +12217 -0
- package/dist/elements/index.cjs.map +1 -0
- package/dist/elements/index.d.cts +620 -0
- package/dist/elements/index.d.ts +620 -0
- package/dist/elements/index.js +12181 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/index.cjs +958 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -5
- package/dist/index.d.ts +75 -5
- package/dist/index.js +928 -131
- package/dist/index.js.map +1 -1
- package/dist/{internal-BlgQ9C2d.d.cts → internal-C9MOEdND.d.cts} +12 -12
- package/dist/{internal-BlgQ9C2d.d.ts → internal-C9MOEdND.d.ts} +12 -12
- package/dist/internal.cjs +2 -2
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/package.json +17 -2
- package/dist/chunk-BUF5FDKC.cjs.map +0 -1
- package/dist/chunk-E6KSJ5UI.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
1
|
+
import { useNavigation, useResponses } from './chunk-P4SLDMWY.js';
|
|
2
|
+
export { useNavigation, useResponse, useResponses } from './chunk-P4SLDMWY.js';
|
|
3
|
+
import { useFunnelContext } from './chunk-H3KHXZSI.js';
|
|
4
|
+
export { FunnelProvider, registerIntegration } from './chunk-H3KHXZSI.js';
|
|
5
|
+
import { forwardRef, useMemo, useRef, useEffect, useCallback, useImperativeHandle, useState, useSyncExternalStore } from 'react';
|
|
4
6
|
import { loadStripe } from '@stripe/stripe-js';
|
|
5
7
|
import { useStripe, useElements, PaymentElement, EmbeddedCheckoutProvider, EmbeddedCheckout, Elements } from '@stripe/react-stripe-js';
|
|
6
|
-
import {
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
9
|
|
|
8
10
|
// src/config.ts
|
|
9
11
|
function defineConfig(config) {
|
|
@@ -41,8 +43,104 @@ function useVariables() {
|
|
|
41
43
|
);
|
|
42
44
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
43
45
|
}
|
|
46
|
+
|
|
47
|
+
// src/utils/date.ts
|
|
48
|
+
function toISODate(input) {
|
|
49
|
+
if (!input || !input.trim()) return "";
|
|
50
|
+
const s = input.trim();
|
|
51
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
|
|
52
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(s)) return s.slice(0, 10);
|
|
53
|
+
const sepMatch = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
|
|
54
|
+
if (sepMatch) {
|
|
55
|
+
const a = parseInt(sepMatch[1], 10);
|
|
56
|
+
const b = parseInt(sepMatch[2], 10);
|
|
57
|
+
const year = sepMatch[3];
|
|
58
|
+
let month;
|
|
59
|
+
let day;
|
|
60
|
+
if (a > 12 && b <= 12) {
|
|
61
|
+
day = a;
|
|
62
|
+
month = b;
|
|
63
|
+
} else if (b > 12 && a <= 12) {
|
|
64
|
+
month = a;
|
|
65
|
+
day = b;
|
|
66
|
+
} else {
|
|
67
|
+
month = a;
|
|
68
|
+
day = b;
|
|
69
|
+
}
|
|
70
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
71
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const ymdSlash = s.match(/^(\d{4})[/\-.](\d{1,2})[/\-.](\d{1,2})$/);
|
|
75
|
+
if (ymdSlash) {
|
|
76
|
+
const year = ymdSlash[1];
|
|
77
|
+
const month = parseInt(ymdSlash[2], 10);
|
|
78
|
+
const day = parseInt(ymdSlash[3], 10);
|
|
79
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
80
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (/^\d{8}$/.test(s)) {
|
|
84
|
+
const a = parseInt(s.slice(0, 2), 10);
|
|
85
|
+
const b = parseInt(s.slice(2, 4), 10);
|
|
86
|
+
const year = s.slice(4, 8);
|
|
87
|
+
let month;
|
|
88
|
+
let day;
|
|
89
|
+
if (a > 12 && b <= 12) {
|
|
90
|
+
day = a;
|
|
91
|
+
month = b;
|
|
92
|
+
} else {
|
|
93
|
+
month = a;
|
|
94
|
+
day = b;
|
|
95
|
+
}
|
|
96
|
+
if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
97
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error(
|
|
101
|
+
`[AppFunnel] Invalid date format: "${input}". Expected a date string like MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD, or MMDDYYYY.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
function toISODateWithFormat(input, format) {
|
|
105
|
+
if (!input || !input.trim()) return "";
|
|
106
|
+
const s = input.trim();
|
|
107
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(s) || /^\d{4}-\d{2}-\d{2}T/.test(s)) {
|
|
108
|
+
return s.slice(0, 10);
|
|
109
|
+
}
|
|
110
|
+
const digits = s.replace(/[^\d]/g, "");
|
|
111
|
+
if (format === "YYYY-MM-DD") {
|
|
112
|
+
const m = s.match(/^(\d{4})[/\-.](\d{1,2})[/\-.](\d{1,2})$/);
|
|
113
|
+
if (m) return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
|
|
114
|
+
if (digits.length === 8) {
|
|
115
|
+
return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (format === "MM/DD/YYYY") {
|
|
119
|
+
const m = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
|
|
120
|
+
if (m) return `${m[3]}-${m[1].padStart(2, "0")}-${m[2].padStart(2, "0")}`;
|
|
121
|
+
if (digits.length === 8) {
|
|
122
|
+
return `${digits.slice(4, 8)}-${digits.slice(0, 2)}-${digits.slice(2, 4)}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (format === "DD/MM/YYYY") {
|
|
126
|
+
const m = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
|
|
127
|
+
if (m) return `${m[3]}-${m[2].padStart(2, "0")}-${m[1].padStart(2, "0")}`;
|
|
128
|
+
if (digits.length === 8) {
|
|
129
|
+
return `${digits.slice(4, 8)}-${digits.slice(2, 4)}-${digits.slice(0, 2)}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
throw new Error(
|
|
133
|
+
`[AppFunnel] Invalid date format: "${input}". Expected format ${format} (e.g. ${{
|
|
134
|
+
"MM/DD/YYYY": "03/15/1990 or 03151990",
|
|
135
|
+
"DD/MM/YYYY": "15/03/1990 or 15031990",
|
|
136
|
+
"YYYY-MM-DD": "1990-03-15 or 19900315"
|
|
137
|
+
}[format]}).`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/hooks/useUser.ts
|
|
44
142
|
function useUser() {
|
|
45
|
-
const { variableStore } = useFunnelContext();
|
|
143
|
+
const { variableStore, tracker } = useFunnelContext();
|
|
46
144
|
const subscribe = useCallback(
|
|
47
145
|
(cb) => variableStore.subscribe(cb, { prefix: "user." }),
|
|
48
146
|
[variableStore]
|
|
@@ -59,6 +157,7 @@ function useUser() {
|
|
|
59
157
|
stripeCustomerId: variables["user.stripeCustomerId"] || "",
|
|
60
158
|
paddleCustomerId: variables["user.paddleCustomerId"] || "",
|
|
61
159
|
dateOfBirth: variables["user.dateOfBirth"] || "",
|
|
160
|
+
marketingConsent: variables["user.marketingConsent"] === true,
|
|
62
161
|
setEmail(email) {
|
|
63
162
|
variableStore.set("user.email", email);
|
|
64
163
|
},
|
|
@@ -66,10 +165,16 @@ function useUser() {
|
|
|
66
165
|
variableStore.set("user.name", name);
|
|
67
166
|
},
|
|
68
167
|
setDateOfBirth(dateOfBirth) {
|
|
69
|
-
variableStore.set("user.dateOfBirth", dateOfBirth);
|
|
168
|
+
variableStore.set("user.dateOfBirth", toISODate(dateOfBirth));
|
|
169
|
+
},
|
|
170
|
+
setMarketingConsent(consent) {
|
|
171
|
+
variableStore.set("user.marketingConsent", consent);
|
|
172
|
+
},
|
|
173
|
+
identify(email) {
|
|
174
|
+
tracker.identify(email);
|
|
70
175
|
}
|
|
71
176
|
}),
|
|
72
|
-
[variables, variableStore]
|
|
177
|
+
[variables, variableStore, tracker]
|
|
73
178
|
);
|
|
74
179
|
}
|
|
75
180
|
function useUserProperty(field) {
|
|
@@ -90,45 +195,24 @@ function useUserProperty(field) {
|
|
|
90
195
|
);
|
|
91
196
|
return [value, setValue];
|
|
92
197
|
}
|
|
93
|
-
function
|
|
198
|
+
function useDateOfBirth(format = "MM/DD/YYYY") {
|
|
94
199
|
const { variableStore } = useFunnelContext();
|
|
95
|
-
const
|
|
200
|
+
const key = "user.dateOfBirth";
|
|
96
201
|
const subscribe = useCallback(
|
|
97
|
-
(cb) => variableStore.subscribe(cb, { keys: [
|
|
98
|
-
[variableStore
|
|
202
|
+
(cb) => variableStore.subscribe(cb, { keys: [key] }),
|
|
203
|
+
[variableStore]
|
|
99
204
|
);
|
|
100
205
|
const getSnapshot = useCallback(
|
|
101
|
-
() => variableStore.get(
|
|
102
|
-
[variableStore
|
|
206
|
+
() => variableStore.get(key) || "",
|
|
207
|
+
[variableStore]
|
|
103
208
|
);
|
|
104
209
|
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
105
210
|
const setValue = useCallback(
|
|
106
|
-
(v) => variableStore.set(
|
|
107
|
-
[variableStore,
|
|
211
|
+
(v) => variableStore.set(key, toISODateWithFormat(v, format)),
|
|
212
|
+
[variableStore, format]
|
|
108
213
|
);
|
|
109
214
|
return [value, setValue];
|
|
110
215
|
}
|
|
111
|
-
function useResponses() {
|
|
112
|
-
const { variableStore } = useFunnelContext();
|
|
113
|
-
const subscribe = useCallback(
|
|
114
|
-
(cb) => variableStore.subscribe(cb, { prefix: "answers." }),
|
|
115
|
-
[variableStore]
|
|
116
|
-
);
|
|
117
|
-
const getSnapshot = useCallback(
|
|
118
|
-
() => variableStore.getState(),
|
|
119
|
-
[variableStore]
|
|
120
|
-
);
|
|
121
|
-
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
122
|
-
return useMemo(() => {
|
|
123
|
-
const result = {};
|
|
124
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
125
|
-
if (key.startsWith("answers.")) {
|
|
126
|
-
result[key.slice(8)] = value;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return result;
|
|
130
|
-
}, [variables]);
|
|
131
|
-
}
|
|
132
216
|
function useQueryParams() {
|
|
133
217
|
const { variableStore } = useFunnelContext();
|
|
134
218
|
const subscribe = useCallback(
|
|
@@ -239,64 +323,6 @@ function useTranslation() {
|
|
|
239
323
|
const availableLocales = i18n.getAvailableLocales();
|
|
240
324
|
return { t, locale, setLocale, availableLocales };
|
|
241
325
|
}
|
|
242
|
-
function useNavigation() {
|
|
243
|
-
const { router, variableStore, tracker } = useFunnelContext();
|
|
244
|
-
useSyncExternalStore(
|
|
245
|
-
useCallback((cb) => router.subscribe(cb), [router]),
|
|
246
|
-
useCallback(() => router.getSnapshot(), [router]),
|
|
247
|
-
useCallback(() => router.getSnapshot(), [router])
|
|
248
|
-
);
|
|
249
|
-
const afterNavigate = useCallback((key) => {
|
|
250
|
-
const page = router.getCurrentPage();
|
|
251
|
-
if (page) {
|
|
252
|
-
tracker.track("page.view", {
|
|
253
|
-
pageId: page.key,
|
|
254
|
-
pageKey: page.key,
|
|
255
|
-
pageName: page.name
|
|
256
|
-
});
|
|
257
|
-
tracker.startPageTracking(page.key);
|
|
258
|
-
}
|
|
259
|
-
variableStore.setMany({
|
|
260
|
-
"page.currentId": key,
|
|
261
|
-
"page.currentIndex": router.getPageHistory().length,
|
|
262
|
-
"page.current": router.getPageHistory().length + 1,
|
|
263
|
-
"page.startedAt": Date.now()
|
|
264
|
-
});
|
|
265
|
-
}, [router, tracker, variableStore]);
|
|
266
|
-
const goToNextPage = useCallback(() => {
|
|
267
|
-
const previousPage = router.getCurrentPage();
|
|
268
|
-
if (previousPage) {
|
|
269
|
-
tracker.stopPageTracking();
|
|
270
|
-
}
|
|
271
|
-
const variables = variableStore.getState();
|
|
272
|
-
const nextKey = router.goToNextPage(variables);
|
|
273
|
-
if (nextKey) {
|
|
274
|
-
afterNavigate(nextKey);
|
|
275
|
-
}
|
|
276
|
-
}, [router, tracker, variableStore, afterNavigate]);
|
|
277
|
-
const goBack = useCallback(() => {
|
|
278
|
-
tracker.stopPageTracking();
|
|
279
|
-
const prevKey = router.goBack();
|
|
280
|
-
if (prevKey) {
|
|
281
|
-
afterNavigate(prevKey);
|
|
282
|
-
}
|
|
283
|
-
}, [router, tracker, afterNavigate]);
|
|
284
|
-
const goToPage = useCallback((pageKey) => {
|
|
285
|
-
tracker.stopPageTracking();
|
|
286
|
-
const key = router.goToPage(pageKey);
|
|
287
|
-
if (key) {
|
|
288
|
-
afterNavigate(key);
|
|
289
|
-
}
|
|
290
|
-
}, [router, tracker, afterNavigate]);
|
|
291
|
-
return {
|
|
292
|
-
goToNextPage,
|
|
293
|
-
goBack,
|
|
294
|
-
goToPage,
|
|
295
|
-
currentPage: router.getCurrentPage(),
|
|
296
|
-
pageHistory: router.getPageHistory(),
|
|
297
|
-
progress: router.getProgress()
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
326
|
function useProducts() {
|
|
301
327
|
const { products, variableStore, selectProduct: ctxSelect } = useFunnelContext();
|
|
302
328
|
const subscribe = useCallback(
|
|
@@ -322,25 +348,12 @@ function useTracking() {
|
|
|
322
348
|
},
|
|
323
349
|
[tracker]
|
|
324
350
|
);
|
|
325
|
-
|
|
326
|
-
(email) => {
|
|
327
|
-
tracker.identify(email);
|
|
328
|
-
},
|
|
329
|
-
[tracker]
|
|
330
|
-
);
|
|
331
|
-
return { track, identify };
|
|
351
|
+
return { track };
|
|
332
352
|
}
|
|
333
|
-
var
|
|
334
|
-
|
|
335
|
-
"card.brand",
|
|
336
|
-
"card.expMonth",
|
|
337
|
-
"card.expYear",
|
|
338
|
-
"payment.loading",
|
|
339
|
-
"payment.error",
|
|
340
|
-
"payment.customerId"
|
|
341
|
-
];
|
|
353
|
+
var API_BASE_URL = "https://api.appfunnel.net";
|
|
354
|
+
var PAYMENT_KEYS = ["payment.loading", "payment.error"];
|
|
342
355
|
function usePayment() {
|
|
343
|
-
const { variableStore } = useFunnelContext();
|
|
356
|
+
const { variableStore, products, campaignId, tracker } = useFunnelContext();
|
|
344
357
|
const subscribe = useCallback(
|
|
345
358
|
(cb) => variableStore.subscribe(cb, { keys: PAYMENT_KEYS }),
|
|
346
359
|
[variableStore]
|
|
@@ -350,20 +363,258 @@ function usePayment() {
|
|
|
350
363
|
[variableStore]
|
|
351
364
|
);
|
|
352
365
|
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
const purchase = useCallback(
|
|
367
|
+
async (productId, options) => {
|
|
368
|
+
console.log("[Purchase] Starting purchase for product:", productId);
|
|
369
|
+
if (globalThis.__APPFUNNEL_DEV__) {
|
|
370
|
+
console.log(
|
|
371
|
+
"[Purchase] Dev mode \u2014 simulating success (500ms delay)"
|
|
372
|
+
);
|
|
373
|
+
variableStore.set("payment.loading", true);
|
|
374
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
375
|
+
variableStore.set("payment.error", "");
|
|
376
|
+
console.log("[Purchase] Dev mode \u2014 success");
|
|
377
|
+
options?.onSuccess?.();
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
const customerId = variableStore.get(
|
|
381
|
+
"user.stripeCustomerId"
|
|
382
|
+
);
|
|
383
|
+
console.log("[Purchase] Customer ID:", customerId || "(none)");
|
|
384
|
+
if (!customerId) {
|
|
385
|
+
const msg = "Please complete payment authorization first";
|
|
386
|
+
console.error("[Purchase] Failed:", msg);
|
|
387
|
+
variableStore.set("payment.error", msg);
|
|
388
|
+
options?.onError?.(msg);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
const product = products.find((p) => p.id === productId);
|
|
392
|
+
console.log(
|
|
393
|
+
"[Purchase] Product found:",
|
|
394
|
+
product ? {
|
|
395
|
+
id: product.id,
|
|
396
|
+
name: product.name,
|
|
397
|
+
stripePriceId: product.stripePriceId
|
|
398
|
+
} : "(not found)"
|
|
399
|
+
);
|
|
400
|
+
if (!product?.stripePriceId) {
|
|
401
|
+
const msg = "Product not found or missing Stripe price";
|
|
402
|
+
console.error("[Purchase] Failed:", msg);
|
|
403
|
+
variableStore.set("payment.error", msg);
|
|
404
|
+
options?.onError?.(msg);
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
const trialPeriodDays = product.hasTrial ? product.trialDays : void 0;
|
|
408
|
+
const trialChargePriceId = product.paidTrial && product.trialStorePriceId ? product.trialStorePriceId : void 0;
|
|
409
|
+
console.log("[Purchase] Trial config:", {
|
|
410
|
+
trialPeriodDays,
|
|
411
|
+
trialChargePriceId,
|
|
412
|
+
hasTrial: product.hasTrial,
|
|
413
|
+
paidTrial: product.paidTrial
|
|
414
|
+
});
|
|
415
|
+
variableStore.set("payment.loading", true);
|
|
416
|
+
variableStore.set("payment.error", "");
|
|
417
|
+
const requestBody = {
|
|
418
|
+
campaignId,
|
|
419
|
+
sessionId: tracker.getSessionId(),
|
|
420
|
+
stripePriceId: product.stripePriceId,
|
|
421
|
+
trialPeriodDays,
|
|
422
|
+
trialChargePriceId
|
|
423
|
+
};
|
|
424
|
+
console.log(
|
|
425
|
+
"[Purchase] Sending request:",
|
|
426
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
|
|
427
|
+
requestBody
|
|
428
|
+
);
|
|
429
|
+
try {
|
|
430
|
+
const response = await fetch(
|
|
431
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
|
|
432
|
+
{
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: { "Content-Type": "application/json" },
|
|
435
|
+
body: JSON.stringify(requestBody)
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
console.log("[Purchase] Response status:", response.status);
|
|
439
|
+
let result = await response.json();
|
|
440
|
+
console.log("[Purchase] Response body:", result);
|
|
441
|
+
if (!result.success && result.requiresAction) {
|
|
442
|
+
console.log(
|
|
443
|
+
"[Purchase] 3DS authentication required, loading Stripe..."
|
|
444
|
+
);
|
|
445
|
+
const { loadStripe: loadStripe2 } = await import('@stripe/stripe-js');
|
|
446
|
+
const stripeInstance = await loadStripe2(
|
|
447
|
+
result.publishableKey
|
|
448
|
+
);
|
|
449
|
+
if (!stripeInstance) {
|
|
450
|
+
const msg = "Failed to load payment processor";
|
|
451
|
+
console.error("[Purchase] Failed:", msg);
|
|
452
|
+
variableStore.set("payment.error", msg);
|
|
453
|
+
options?.onError?.(msg);
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
console.log(
|
|
457
|
+
"[Purchase] Confirming card payment with 3DS..."
|
|
458
|
+
);
|
|
459
|
+
const { error: confirmError, paymentIntent: confirmedPi } = await stripeInstance.confirmCardPayment(
|
|
460
|
+
result.clientSecret,
|
|
461
|
+
{
|
|
462
|
+
payment_method: result.paymentMethodId
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
if (confirmError || !confirmedPi || confirmedPi.status !== "succeeded") {
|
|
466
|
+
const msg = confirmError?.message || "Payment authentication failed";
|
|
467
|
+
console.error("[Purchase] 3DS failed:", msg);
|
|
468
|
+
variableStore.set("payment.error", msg);
|
|
469
|
+
options?.onError?.(msg);
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
console.log(
|
|
473
|
+
"[Purchase] 3DS succeeded, retrying purchase with confirmed PI:",
|
|
474
|
+
confirmedPi.id
|
|
475
|
+
);
|
|
476
|
+
const retryResponse = await fetch(
|
|
477
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
|
|
478
|
+
{
|
|
479
|
+
method: "POST",
|
|
480
|
+
headers: { "Content-Type": "application/json" },
|
|
481
|
+
body: JSON.stringify({
|
|
482
|
+
campaignId,
|
|
483
|
+
sessionId: tracker.getSessionId(),
|
|
484
|
+
stripePriceId: product.stripePriceId,
|
|
485
|
+
trialPeriodDays,
|
|
486
|
+
trialChargePriceId,
|
|
487
|
+
onSessionPiId: confirmedPi.id
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
result = await retryResponse.json();
|
|
492
|
+
console.log("[Purchase] Retry response:", result);
|
|
493
|
+
}
|
|
494
|
+
if (result.success) {
|
|
495
|
+
console.log("[Purchase] Success! Type:", result.type);
|
|
496
|
+
variableStore.set("payment.error", "");
|
|
497
|
+
if (result.type === "validate_only") {
|
|
498
|
+
console.log(
|
|
499
|
+
"[Purchase] Validate-only \u2014 setting card variables"
|
|
500
|
+
);
|
|
501
|
+
if (result.card) {
|
|
502
|
+
variableStore.setMany({
|
|
503
|
+
"card.last4": result.card.last4,
|
|
504
|
+
"card.brand": result.card.brand,
|
|
505
|
+
"card.expMonth": result.card.expMonth,
|
|
506
|
+
"card.expYear": result.card.expYear,
|
|
507
|
+
"card.funding": result.card.funding
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (result.eventId) {
|
|
511
|
+
console.log(
|
|
512
|
+
"[Purchase] Tracking purchase.complete (validate_only), eventId:",
|
|
513
|
+
result.eventId
|
|
514
|
+
);
|
|
515
|
+
tracker.track("purchase.complete", {
|
|
516
|
+
eventId: result.eventId,
|
|
517
|
+
amount: product.rawPrice ? product.rawPrice / 100 : 0,
|
|
518
|
+
currency: product.currencyCode || "USD"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
} else if (result.type === "one_time") {
|
|
522
|
+
console.log(
|
|
523
|
+
"[Purchase] One-time charge \u2014 paymentIntentId:",
|
|
524
|
+
result.paymentIntentId
|
|
525
|
+
);
|
|
526
|
+
variableStore.set(
|
|
527
|
+
"stripe.paymentIntentId",
|
|
528
|
+
result.paymentIntentId
|
|
529
|
+
);
|
|
530
|
+
variableStore.set("payment.status", result.status);
|
|
531
|
+
if (result.eventId) {
|
|
532
|
+
console.log(
|
|
533
|
+
"[Purchase] Tracking purchase.complete (one_time), eventId:",
|
|
534
|
+
result.eventId
|
|
535
|
+
);
|
|
536
|
+
tracker.track("purchase.complete", {
|
|
537
|
+
eventId: result.eventId,
|
|
538
|
+
amount: product.rawPrice ? product.rawPrice / 100 : 0,
|
|
539
|
+
currency: product.currencyCode || "USD"
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
console.log(
|
|
544
|
+
"[Purchase] Subscription \u2014 subscriptionId:",
|
|
545
|
+
result.subscriptionId,
|
|
546
|
+
"status:",
|
|
547
|
+
result.status
|
|
548
|
+
);
|
|
549
|
+
variableStore.set(
|
|
550
|
+
"stripe.subscriptionId",
|
|
551
|
+
result.subscriptionId
|
|
552
|
+
);
|
|
553
|
+
variableStore.set("subscription.status", result.status);
|
|
554
|
+
if (result.trialCharge) {
|
|
555
|
+
console.log(
|
|
556
|
+
"[Purchase] Tracking trial charge purchase.complete"
|
|
557
|
+
);
|
|
558
|
+
tracker.track("purchase.complete", {
|
|
559
|
+
eventId: result.eventIds?.trialCharge,
|
|
560
|
+
amount: result.trialCharge.amount / 100,
|
|
561
|
+
currency: "USD"
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (result.eventIds?.subscription) {
|
|
565
|
+
console.log(
|
|
566
|
+
"[Purchase] Tracking subscription.created, eventId:",
|
|
567
|
+
result.eventIds.subscription
|
|
568
|
+
);
|
|
569
|
+
tracker.track("subscription.created", {
|
|
570
|
+
eventId: result.eventIds.subscription,
|
|
571
|
+
subscriptionId: result.subscriptionId,
|
|
572
|
+
status: result.status
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (result.eventIds?.firstPeriod) {
|
|
576
|
+
console.log(
|
|
577
|
+
"[Purchase] Tracking first-period purchase.complete, eventId:",
|
|
578
|
+
result.eventIds.firstPeriod
|
|
579
|
+
);
|
|
580
|
+
tracker.track("purchase.complete", {
|
|
581
|
+
eventId: result.eventIds.firstPeriod,
|
|
582
|
+
amount: product.rawPrice ? product.rawPrice / 100 : 0,
|
|
583
|
+
currency: product.currencyCode || "USD"
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
console.log("[Purchase] Calling onSuccess callback");
|
|
588
|
+
options?.onSuccess?.();
|
|
589
|
+
return true;
|
|
590
|
+
} else {
|
|
591
|
+
const msg = result.error || "Purchase failed";
|
|
592
|
+
console.error("[Purchase] Failed:", msg);
|
|
593
|
+
variableStore.set("payment.error", msg);
|
|
594
|
+
options?.onError?.(msg);
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
const msg = err instanceof Error ? err.message : "Purchase failed";
|
|
599
|
+
console.error("[Purchase] Exception:", err);
|
|
600
|
+
variableStore.set("payment.error", msg);
|
|
601
|
+
options?.onError?.(msg);
|
|
602
|
+
return false;
|
|
603
|
+
} finally {
|
|
604
|
+
console.log("[Purchase] Done \u2014 setting loading to false");
|
|
605
|
+
variableStore.set("payment.loading", false);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
[variableStore, products, campaignId, tracker]
|
|
609
|
+
);
|
|
610
|
+
return useMemo(
|
|
611
|
+
() => ({
|
|
362
612
|
loading: !!variables["payment.loading"],
|
|
363
613
|
error: variables["payment.error"] || null,
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
614
|
+
purchase
|
|
615
|
+
}),
|
|
616
|
+
[variables, purchase]
|
|
617
|
+
);
|
|
367
618
|
}
|
|
368
619
|
var DEVICE_KEYS = [
|
|
369
620
|
"os.name",
|
|
@@ -421,6 +672,95 @@ function useDeviceInfo() {
|
|
|
421
672
|
}
|
|
422
673
|
}), [variables]);
|
|
423
674
|
}
|
|
675
|
+
function useSafeArea() {
|
|
676
|
+
const [insets, setInsets] = useState({ top: 0, right: 0, bottom: 0, left: 0 });
|
|
677
|
+
useEffect(() => {
|
|
678
|
+
if (typeof window === "undefined") return;
|
|
679
|
+
const el = document.createElement("div");
|
|
680
|
+
el.style.cssText = [
|
|
681
|
+
"position:fixed",
|
|
682
|
+
"top:env(safe-area-inset-top,0px)",
|
|
683
|
+
"right:env(safe-area-inset-right,0px)",
|
|
684
|
+
"bottom:env(safe-area-inset-bottom,0px)",
|
|
685
|
+
"left:env(safe-area-inset-left,0px)",
|
|
686
|
+
"pointer-events:none",
|
|
687
|
+
"visibility:hidden",
|
|
688
|
+
"z-index:-1"
|
|
689
|
+
].join(";");
|
|
690
|
+
document.body.appendChild(el);
|
|
691
|
+
function read() {
|
|
692
|
+
const style = getComputedStyle(el);
|
|
693
|
+
setInsets({
|
|
694
|
+
top: parseFloat(style.top) || 0,
|
|
695
|
+
right: parseFloat(style.right) || 0,
|
|
696
|
+
bottom: parseFloat(style.bottom) || 0,
|
|
697
|
+
left: parseFloat(style.left) || 0
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
read();
|
|
701
|
+
const observer = new ResizeObserver(read);
|
|
702
|
+
observer.observe(el);
|
|
703
|
+
window.addEventListener("resize", read);
|
|
704
|
+
return () => {
|
|
705
|
+
observer.disconnect();
|
|
706
|
+
window.removeEventListener("resize", read);
|
|
707
|
+
el.remove();
|
|
708
|
+
};
|
|
709
|
+
}, []);
|
|
710
|
+
return insets;
|
|
711
|
+
}
|
|
712
|
+
function useKeyboard() {
|
|
713
|
+
const [state, setState] = useState({ isOpen: false, height: 0 });
|
|
714
|
+
const timeoutRef = useRef();
|
|
715
|
+
useEffect(() => {
|
|
716
|
+
if (typeof window === "undefined") return;
|
|
717
|
+
if ("virtualKeyboard" in navigator) {
|
|
718
|
+
const vk = navigator.virtualKeyboard;
|
|
719
|
+
vk.overlaysContent = true;
|
|
720
|
+
const handler = () => {
|
|
721
|
+
const h = vk.boundingRect.height;
|
|
722
|
+
setState((prev) => {
|
|
723
|
+
if (prev.height === h && prev.isOpen === h > 0) return prev;
|
|
724
|
+
return { isOpen: h > 0, height: h };
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
vk.addEventListener("geometrychange", handler);
|
|
728
|
+
handler();
|
|
729
|
+
return () => vk.removeEventListener("geometrychange", handler);
|
|
730
|
+
}
|
|
731
|
+
const vv = window.visualViewport;
|
|
732
|
+
if (!vv) return;
|
|
733
|
+
let layoutHeight = window.innerHeight;
|
|
734
|
+
function compute() {
|
|
735
|
+
const kbHeight = Math.max(0, layoutHeight - vv.height);
|
|
736
|
+
const h = kbHeight > 40 ? Math.round(kbHeight) : 0;
|
|
737
|
+
setState((prev) => {
|
|
738
|
+
if (prev.height === h && prev.isOpen === h > 0) return prev;
|
|
739
|
+
return { isOpen: h > 0, height: h };
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
function onViewportResize() {
|
|
743
|
+
compute();
|
|
744
|
+
clearTimeout(timeoutRef.current);
|
|
745
|
+
timeoutRef.current = setTimeout(compute, 1e3);
|
|
746
|
+
}
|
|
747
|
+
function onWindowResize() {
|
|
748
|
+
layoutHeight = window.innerHeight;
|
|
749
|
+
compute();
|
|
750
|
+
}
|
|
751
|
+
vv.addEventListener("resize", onViewportResize);
|
|
752
|
+
window.addEventListener("resize", onWindowResize);
|
|
753
|
+
window.addEventListener("orientationchange", onWindowResize);
|
|
754
|
+
compute();
|
|
755
|
+
return () => {
|
|
756
|
+
clearTimeout(timeoutRef.current);
|
|
757
|
+
vv.removeEventListener("resize", onViewportResize);
|
|
758
|
+
window.removeEventListener("resize", onWindowResize);
|
|
759
|
+
window.removeEventListener("orientationchange", onWindowResize);
|
|
760
|
+
};
|
|
761
|
+
}, []);
|
|
762
|
+
return state;
|
|
763
|
+
}
|
|
424
764
|
var PAGE_KEYS = [
|
|
425
765
|
"page.currentId",
|
|
426
766
|
"page.currentIndex",
|
|
@@ -467,7 +807,456 @@ function useFunnel() {
|
|
|
467
807
|
payment: usePayment()
|
|
468
808
|
};
|
|
469
809
|
}
|
|
470
|
-
var
|
|
810
|
+
var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
811
|
+
var inputStyle = (focused) => ({
|
|
812
|
+
width: "100%",
|
|
813
|
+
padding: "12px",
|
|
814
|
+
border: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
|
|
815
|
+
borderRadius: "6px",
|
|
816
|
+
fontSize: "14px",
|
|
817
|
+
fontFamily: FONT,
|
|
818
|
+
outline: "none",
|
|
819
|
+
backgroundColor: "#fff",
|
|
820
|
+
boxSizing: "border-box",
|
|
821
|
+
transition: "border-color 0.15s"
|
|
822
|
+
});
|
|
823
|
+
var labelStyle = {
|
|
824
|
+
display: "block",
|
|
825
|
+
fontSize: "14px",
|
|
826
|
+
fontWeight: 500,
|
|
827
|
+
color: "#333",
|
|
828
|
+
marginBottom: "6px",
|
|
829
|
+
fontFamily: FONT
|
|
830
|
+
};
|
|
831
|
+
function CardBrandBadges() {
|
|
832
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "4px", alignItems: "center" }, children: [
|
|
833
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#1a1f71", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "VISA" }),
|
|
834
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#eb001b", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "MC" }),
|
|
835
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#ff6000", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "DISC" })
|
|
836
|
+
] });
|
|
837
|
+
}
|
|
838
|
+
function CvcIcon() {
|
|
839
|
+
return /* @__PURE__ */ jsxs("svg", { width: "20", height: "16", viewBox: "0 0 20 16", fill: "none", style: { opacity: 0.4 }, children: [
|
|
840
|
+
/* @__PURE__ */ jsx("rect", { x: "0.5", y: "0.5", width: "19", height: "15", rx: "2", stroke: "#888" }),
|
|
841
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "4", width: "20", height: "3", fill: "#888" }),
|
|
842
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "10", width: "8", height: "2", rx: "1", fill: "#ccc" }),
|
|
843
|
+
/* @__PURE__ */ jsx("text", { x: "14", y: "12", fontSize: "6", fill: "#888", fontFamily: FONT, children: "123" })
|
|
844
|
+
] });
|
|
845
|
+
}
|
|
846
|
+
function DemoElementsForm({ onReady }) {
|
|
847
|
+
const [cardNumber, setCardNumber] = useState("");
|
|
848
|
+
const [expiry, setExpiry] = useState("");
|
|
849
|
+
const [cvc, setCvc] = useState("");
|
|
850
|
+
const [country, setCountry] = useState("Denmark");
|
|
851
|
+
const [focusedField, setFocusedField] = useState(null);
|
|
852
|
+
const readyFired = useRef(false);
|
|
853
|
+
useEffect(() => {
|
|
854
|
+
if (!readyFired.current) {
|
|
855
|
+
readyFired.current = true;
|
|
856
|
+
onReady?.();
|
|
857
|
+
}
|
|
858
|
+
}, [onReady]);
|
|
859
|
+
const formatCardNumber = (val) => {
|
|
860
|
+
const digits = val.replace(/\D/g, "").slice(0, 16);
|
|
861
|
+
return digits.replace(/(.{4})/g, "$1 ").trim();
|
|
862
|
+
};
|
|
863
|
+
const formatExpiry = (val) => {
|
|
864
|
+
const digits = val.replace(/\D/g, "").slice(0, 4);
|
|
865
|
+
if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
|
|
866
|
+
return digits;
|
|
867
|
+
};
|
|
868
|
+
return /* @__PURE__ */ jsxs("div", { style: { fontFamily: FONT }, children: [
|
|
869
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
|
|
870
|
+
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Card number" }),
|
|
871
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
872
|
+
/* @__PURE__ */ jsx(
|
|
873
|
+
"input",
|
|
874
|
+
{
|
|
875
|
+
type: "text",
|
|
876
|
+
value: cardNumber,
|
|
877
|
+
onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
|
|
878
|
+
onFocus: () => setFocusedField("card"),
|
|
879
|
+
onBlur: () => setFocusedField(null),
|
|
880
|
+
placeholder: "1234 1234 1234 1234",
|
|
881
|
+
style: inputStyle(focusedField === "card")
|
|
882
|
+
}
|
|
883
|
+
),
|
|
884
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
|
|
885
|
+
] })
|
|
886
|
+
] }),
|
|
887
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "14px" }, children: [
|
|
888
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
889
|
+
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Expiry date" }),
|
|
890
|
+
/* @__PURE__ */ jsx(
|
|
891
|
+
"input",
|
|
892
|
+
{
|
|
893
|
+
type: "text",
|
|
894
|
+
value: expiry,
|
|
895
|
+
onChange: (e) => setExpiry(formatExpiry(e.target.value)),
|
|
896
|
+
onFocus: () => setFocusedField("expiry"),
|
|
897
|
+
onBlur: () => setFocusedField(null),
|
|
898
|
+
placeholder: "MM / YY",
|
|
899
|
+
style: inputStyle(focusedField === "expiry")
|
|
900
|
+
}
|
|
901
|
+
)
|
|
902
|
+
] }),
|
|
903
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
904
|
+
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Security code" }),
|
|
905
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
906
|
+
/* @__PURE__ */ jsx(
|
|
907
|
+
"input",
|
|
908
|
+
{
|
|
909
|
+
type: "text",
|
|
910
|
+
value: cvc,
|
|
911
|
+
onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
|
|
912
|
+
onFocus: () => setFocusedField("cvc"),
|
|
913
|
+
onBlur: () => setFocusedField(null),
|
|
914
|
+
placeholder: "CVC",
|
|
915
|
+
style: inputStyle(focusedField === "cvc")
|
|
916
|
+
}
|
|
917
|
+
),
|
|
918
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
|
|
919
|
+
] })
|
|
920
|
+
] })
|
|
921
|
+
] }),
|
|
922
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
|
|
923
|
+
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Country" }),
|
|
924
|
+
/* @__PURE__ */ jsxs(
|
|
925
|
+
"select",
|
|
926
|
+
{
|
|
927
|
+
value: country,
|
|
928
|
+
onChange: (e) => setCountry(e.target.value),
|
|
929
|
+
onFocus: () => setFocusedField("country"),
|
|
930
|
+
onBlur: () => setFocusedField(null),
|
|
931
|
+
style: {
|
|
932
|
+
...inputStyle(focusedField === "country"),
|
|
933
|
+
appearance: "none",
|
|
934
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
|
|
935
|
+
backgroundRepeat: "no-repeat",
|
|
936
|
+
backgroundPosition: "right 12px center",
|
|
937
|
+
paddingRight: "32px"
|
|
938
|
+
},
|
|
939
|
+
children: [
|
|
940
|
+
/* @__PURE__ */ jsx("option", { children: "Australia" }),
|
|
941
|
+
/* @__PURE__ */ jsx("option", { children: "Austria" }),
|
|
942
|
+
/* @__PURE__ */ jsx("option", { children: "Belgium" }),
|
|
943
|
+
/* @__PURE__ */ jsx("option", { children: "Brazil" }),
|
|
944
|
+
/* @__PURE__ */ jsx("option", { children: "Canada" }),
|
|
945
|
+
/* @__PURE__ */ jsx("option", { children: "Denmark" }),
|
|
946
|
+
/* @__PURE__ */ jsx("option", { children: "Finland" }),
|
|
947
|
+
/* @__PURE__ */ jsx("option", { children: "France" }),
|
|
948
|
+
/* @__PURE__ */ jsx("option", { children: "Germany" }),
|
|
949
|
+
/* @__PURE__ */ jsx("option", { children: "Ireland" }),
|
|
950
|
+
/* @__PURE__ */ jsx("option", { children: "Italy" }),
|
|
951
|
+
/* @__PURE__ */ jsx("option", { children: "Japan" }),
|
|
952
|
+
/* @__PURE__ */ jsx("option", { children: "Netherlands" }),
|
|
953
|
+
/* @__PURE__ */ jsx("option", { children: "New Zealand" }),
|
|
954
|
+
/* @__PURE__ */ jsx("option", { children: "Norway" }),
|
|
955
|
+
/* @__PURE__ */ jsx("option", { children: "Poland" }),
|
|
956
|
+
/* @__PURE__ */ jsx("option", { children: "Portugal" }),
|
|
957
|
+
/* @__PURE__ */ jsx("option", { children: "Spain" }),
|
|
958
|
+
/* @__PURE__ */ jsx("option", { children: "Sweden" }),
|
|
959
|
+
/* @__PURE__ */ jsx("option", { children: "Switzerland" }),
|
|
960
|
+
/* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
|
|
961
|
+
/* @__PURE__ */ jsx("option", { children: "United States" })
|
|
962
|
+
]
|
|
963
|
+
}
|
|
964
|
+
)
|
|
965
|
+
] }),
|
|
966
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: "12px", color: "#6b7280", lineHeight: 1.5, margin: 0, fontFamily: FONT }, children: "By providing your card information, you allow the merchant to charge your card for future payments in accordance with their terms." })
|
|
967
|
+
] });
|
|
968
|
+
}
|
|
969
|
+
function DemoEmbeddedForm({ product, onReady }) {
|
|
970
|
+
const readyFired = useRef(false);
|
|
971
|
+
const [email, setEmail] = useState("");
|
|
972
|
+
const [cardNumber, setCardNumber] = useState("");
|
|
973
|
+
const [expiry, setExpiry] = useState("");
|
|
974
|
+
const [cvc, setCvc] = useState("");
|
|
975
|
+
const [name, setName] = useState("");
|
|
976
|
+
const [country, setCountry] = useState("Denmark");
|
|
977
|
+
const [focusedField, setFocusedField] = useState(null);
|
|
978
|
+
useEffect(() => {
|
|
979
|
+
if (!readyFired.current) {
|
|
980
|
+
readyFired.current = true;
|
|
981
|
+
onReady?.();
|
|
982
|
+
}
|
|
983
|
+
}, [onReady]);
|
|
984
|
+
const formatCardNumber = (val) => {
|
|
985
|
+
const digits = val.replace(/\D/g, "").slice(0, 16);
|
|
986
|
+
return digits.replace(/(.{4})/g, "$1 ").trim();
|
|
987
|
+
};
|
|
988
|
+
const formatExpiry = (val) => {
|
|
989
|
+
const digits = val.replace(/\D/g, "").slice(0, 4);
|
|
990
|
+
if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
|
|
991
|
+
return digits;
|
|
992
|
+
};
|
|
993
|
+
const embeddedInputStyle = (focused) => ({
|
|
994
|
+
width: "100%",
|
|
995
|
+
padding: "10px 12px",
|
|
996
|
+
border: "none",
|
|
997
|
+
borderBottom: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
|
|
998
|
+
fontSize: "14px",
|
|
999
|
+
fontFamily: FONT,
|
|
1000
|
+
outline: "none",
|
|
1001
|
+
backgroundColor: "#fff",
|
|
1002
|
+
boxSizing: "border-box"
|
|
1003
|
+
});
|
|
1004
|
+
const displayPrice = product?.hasTrial ? product.trialPrice : product?.price;
|
|
1005
|
+
return /* @__PURE__ */ jsxs("div", { style: { fontFamily: FONT, border: "1px solid #e0e0e0", borderRadius: "12px", overflow: "hidden", background: "#fff" }, children: [
|
|
1006
|
+
product && /* @__PURE__ */ jsxs("div", { style: { padding: "24px 20px", borderBottom: "1px solid #e0e0e0" }, children: [
|
|
1007
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
|
|
1008
|
+
"Pay ",
|
|
1009
|
+
product.displayName || product.name
|
|
1010
|
+
] }),
|
|
1011
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: "28px", fontWeight: 700, color: "#333", marginBottom: "4px" }, children: [
|
|
1012
|
+
product.currencyCode?.toUpperCase(),
|
|
1013
|
+
displayPrice
|
|
1014
|
+
] }),
|
|
1015
|
+
product.hasTrial && /* @__PURE__ */ jsxs("div", { style: { fontSize: "13px", color: "#666" }, children: [
|
|
1016
|
+
"Then ",
|
|
1017
|
+
/* @__PURE__ */ jsxs("span", { style: { textDecoration: "underline" }, children: [
|
|
1018
|
+
product.currencyCode?.toUpperCase(),
|
|
1019
|
+
product.price
|
|
1020
|
+
] }),
|
|
1021
|
+
" every ",
|
|
1022
|
+
product.period
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ jsx(
|
|
1025
|
+
"button",
|
|
1026
|
+
{
|
|
1027
|
+
type: "button",
|
|
1028
|
+
style: {
|
|
1029
|
+
marginTop: "12px",
|
|
1030
|
+
padding: "6px 14px",
|
|
1031
|
+
borderRadius: "6px",
|
|
1032
|
+
border: "1px solid #e0e0e0",
|
|
1033
|
+
backgroundColor: "#fff",
|
|
1034
|
+
color: "#333",
|
|
1035
|
+
fontSize: "13px",
|
|
1036
|
+
cursor: "default",
|
|
1037
|
+
fontFamily: FONT
|
|
1038
|
+
},
|
|
1039
|
+
children: "View details \u25BE"
|
|
1040
|
+
}
|
|
1041
|
+
)
|
|
1042
|
+
] }),
|
|
1043
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "20px" }, children: [
|
|
1044
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
1045
|
+
/* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Email" }),
|
|
1046
|
+
/* @__PURE__ */ jsx(
|
|
1047
|
+
"input",
|
|
1048
|
+
{
|
|
1049
|
+
type: "email",
|
|
1050
|
+
value: email,
|
|
1051
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1052
|
+
onFocus: () => setFocusedField("email"),
|
|
1053
|
+
onBlur: () => setFocusedField(null),
|
|
1054
|
+
placeholder: "you@example.com",
|
|
1055
|
+
style: embeddedInputStyle(focusedField === "email")
|
|
1056
|
+
}
|
|
1057
|
+
)
|
|
1058
|
+
] }),
|
|
1059
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
1060
|
+
/* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Card information" }),
|
|
1061
|
+
/* @__PURE__ */ jsxs("div", { style: { border: "1px solid #e0e0e0", borderRadius: "6px", overflow: "hidden" }, children: [
|
|
1062
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
1063
|
+
/* @__PURE__ */ jsx(
|
|
1064
|
+
"input",
|
|
1065
|
+
{
|
|
1066
|
+
type: "text",
|
|
1067
|
+
value: cardNumber,
|
|
1068
|
+
onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
|
|
1069
|
+
onFocus: () => setFocusedField("card"),
|
|
1070
|
+
onBlur: () => setFocusedField(null),
|
|
1071
|
+
placeholder: "1234 1234 1234 1234",
|
|
1072
|
+
style: { ...embeddedInputStyle(focusedField === "card"), padding: "12px" }
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
|
|
1076
|
+
] }),
|
|
1077
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr" }, children: [
|
|
1078
|
+
/* @__PURE__ */ jsx(
|
|
1079
|
+
"input",
|
|
1080
|
+
{
|
|
1081
|
+
type: "text",
|
|
1082
|
+
value: expiry,
|
|
1083
|
+
onChange: (e) => setExpiry(formatExpiry(e.target.value)),
|
|
1084
|
+
onFocus: () => setFocusedField("expiry"),
|
|
1085
|
+
onBlur: () => setFocusedField(null),
|
|
1086
|
+
placeholder: "MM / YY",
|
|
1087
|
+
style: { ...embeddedInputStyle(focusedField === "expiry"), borderRight: "1px solid #e0e0e0", padding: "12px" }
|
|
1088
|
+
}
|
|
1089
|
+
),
|
|
1090
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
1091
|
+
/* @__PURE__ */ jsx(
|
|
1092
|
+
"input",
|
|
1093
|
+
{
|
|
1094
|
+
type: "text",
|
|
1095
|
+
value: cvc,
|
|
1096
|
+
onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
|
|
1097
|
+
onFocus: () => setFocusedField("cvc"),
|
|
1098
|
+
onBlur: () => setFocusedField(null),
|
|
1099
|
+
placeholder: "CVC",
|
|
1100
|
+
style: { ...embeddedInputStyle(focusedField === "cvc"), padding: "12px" }
|
|
1101
|
+
}
|
|
1102
|
+
),
|
|
1103
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
|
|
1104
|
+
] })
|
|
1105
|
+
] })
|
|
1106
|
+
] })
|
|
1107
|
+
] }),
|
|
1108
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
1109
|
+
/* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Cardholder name" }),
|
|
1110
|
+
/* @__PURE__ */ jsx(
|
|
1111
|
+
"input",
|
|
1112
|
+
{
|
|
1113
|
+
type: "text",
|
|
1114
|
+
value: name,
|
|
1115
|
+
onChange: (e) => setName(e.target.value),
|
|
1116
|
+
onFocus: () => setFocusedField("name"),
|
|
1117
|
+
onBlur: () => setFocusedField(null),
|
|
1118
|
+
placeholder: "Full name on card",
|
|
1119
|
+
style: inputStyle(focusedField === "name")
|
|
1120
|
+
}
|
|
1121
|
+
)
|
|
1122
|
+
] }),
|
|
1123
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "20px" }, children: [
|
|
1124
|
+
/* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Country or region" }),
|
|
1125
|
+
/* @__PURE__ */ jsxs(
|
|
1126
|
+
"select",
|
|
1127
|
+
{
|
|
1128
|
+
value: country,
|
|
1129
|
+
onChange: (e) => setCountry(e.target.value),
|
|
1130
|
+
onFocus: () => setFocusedField("country"),
|
|
1131
|
+
onBlur: () => setFocusedField(null),
|
|
1132
|
+
style: {
|
|
1133
|
+
...inputStyle(focusedField === "country"),
|
|
1134
|
+
appearance: "none",
|
|
1135
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
|
|
1136
|
+
backgroundRepeat: "no-repeat",
|
|
1137
|
+
backgroundPosition: "right 12px center",
|
|
1138
|
+
paddingRight: "32px"
|
|
1139
|
+
},
|
|
1140
|
+
children: [
|
|
1141
|
+
/* @__PURE__ */ jsx("option", { children: "Australia" }),
|
|
1142
|
+
/* @__PURE__ */ jsx("option", { children: "Austria" }),
|
|
1143
|
+
/* @__PURE__ */ jsx("option", { children: "Belgium" }),
|
|
1144
|
+
/* @__PURE__ */ jsx("option", { children: "Brazil" }),
|
|
1145
|
+
/* @__PURE__ */ jsx("option", { children: "Canada" }),
|
|
1146
|
+
/* @__PURE__ */ jsx("option", { children: "Denmark" }),
|
|
1147
|
+
/* @__PURE__ */ jsx("option", { children: "Finland" }),
|
|
1148
|
+
/* @__PURE__ */ jsx("option", { children: "France" }),
|
|
1149
|
+
/* @__PURE__ */ jsx("option", { children: "Germany" }),
|
|
1150
|
+
/* @__PURE__ */ jsx("option", { children: "Ireland" }),
|
|
1151
|
+
/* @__PURE__ */ jsx("option", { children: "Italy" }),
|
|
1152
|
+
/* @__PURE__ */ jsx("option", { children: "Japan" }),
|
|
1153
|
+
/* @__PURE__ */ jsx("option", { children: "Netherlands" }),
|
|
1154
|
+
/* @__PURE__ */ jsx("option", { children: "New Zealand" }),
|
|
1155
|
+
/* @__PURE__ */ jsx("option", { children: "Norway" }),
|
|
1156
|
+
/* @__PURE__ */ jsx("option", { children: "Poland" }),
|
|
1157
|
+
/* @__PURE__ */ jsx("option", { children: "Portugal" }),
|
|
1158
|
+
/* @__PURE__ */ jsx("option", { children: "Spain" }),
|
|
1159
|
+
/* @__PURE__ */ jsx("option", { children: "Sweden" }),
|
|
1160
|
+
/* @__PURE__ */ jsx("option", { children: "Switzerland" }),
|
|
1161
|
+
/* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
|
|
1162
|
+
/* @__PURE__ */ jsx("option", { children: "United States" })
|
|
1163
|
+
]
|
|
1164
|
+
}
|
|
1165
|
+
)
|
|
1166
|
+
] }),
|
|
1167
|
+
/* @__PURE__ */ jsx(
|
|
1168
|
+
"button",
|
|
1169
|
+
{
|
|
1170
|
+
type: "button",
|
|
1171
|
+
disabled: true,
|
|
1172
|
+
style: {
|
|
1173
|
+
width: "100%",
|
|
1174
|
+
padding: "14px",
|
|
1175
|
+
borderRadius: "6px",
|
|
1176
|
+
border: "none",
|
|
1177
|
+
backgroundColor: "#0570de",
|
|
1178
|
+
color: "#fff",
|
|
1179
|
+
fontSize: "16px",
|
|
1180
|
+
fontWeight: 600,
|
|
1181
|
+
cursor: "default",
|
|
1182
|
+
fontFamily: FONT,
|
|
1183
|
+
opacity: 0.7
|
|
1184
|
+
},
|
|
1185
|
+
children: "Subscribe"
|
|
1186
|
+
}
|
|
1187
|
+
),
|
|
1188
|
+
/* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginTop: "12px", fontSize: "12px", color: "#aaa" }, children: [
|
|
1189
|
+
"Powered by ",
|
|
1190
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 700, letterSpacing: "-0.3px" }, children: "stripe" })
|
|
1191
|
+
] })
|
|
1192
|
+
] })
|
|
1193
|
+
] });
|
|
1194
|
+
}
|
|
1195
|
+
var DemoStripePaymentForm = forwardRef(
|
|
1196
|
+
function DemoStripePaymentForm2({
|
|
1197
|
+
productId,
|
|
1198
|
+
mode = "checkout",
|
|
1199
|
+
variant = "elements",
|
|
1200
|
+
onSuccess,
|
|
1201
|
+
onError,
|
|
1202
|
+
onReady,
|
|
1203
|
+
className
|
|
1204
|
+
}, ref) {
|
|
1205
|
+
const { variableStore, tracker, products } = useFunnelContext();
|
|
1206
|
+
const validateOnly = mode === "validate-only";
|
|
1207
|
+
const product = useMemo(() => {
|
|
1208
|
+
if (productId) return products.find((p) => p.id === productId) || null;
|
|
1209
|
+
const selectedId = variableStore.get("products.selectedProductId");
|
|
1210
|
+
return products.find((p) => p.id === selectedId) || null;
|
|
1211
|
+
}, [productId, products, variableStore]);
|
|
1212
|
+
const hasFiredStart = useRef(false);
|
|
1213
|
+
useEffect(() => {
|
|
1214
|
+
if (hasFiredStart.current) return;
|
|
1215
|
+
hasFiredStart.current = true;
|
|
1216
|
+
variableStore.set("payment.loading", false);
|
|
1217
|
+
tracker.track("checkout.start", {
|
|
1218
|
+
productId: product?.id,
|
|
1219
|
+
priceId: product?.stripePriceId || product?.storePriceId,
|
|
1220
|
+
productName: product?.displayName
|
|
1221
|
+
});
|
|
1222
|
+
}, [product, tracker, variableStore]);
|
|
1223
|
+
const handleSubmit = useCallback(async () => {
|
|
1224
|
+
variableStore.set("payment.loading", true);
|
|
1225
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
1226
|
+
try {
|
|
1227
|
+
tracker.track("checkout.payment_added");
|
|
1228
|
+
if (validateOnly) {
|
|
1229
|
+
variableStore.setMany({
|
|
1230
|
+
"card.last4": "4242",
|
|
1231
|
+
"card.brand": "visa",
|
|
1232
|
+
"card.expMonth": 12,
|
|
1233
|
+
"card.expYear": 2030,
|
|
1234
|
+
"card.funding": "credit",
|
|
1235
|
+
"payment.error": ""
|
|
1236
|
+
});
|
|
1237
|
+
onSuccess?.();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
variableStore.set("payment.error", "");
|
|
1241
|
+
tracker.track("purchase.complete", {
|
|
1242
|
+
amount: product?.rawPrice ? product.rawPrice / 100 : 0,
|
|
1243
|
+
currency: product?.currencyCode || "USD"
|
|
1244
|
+
});
|
|
1245
|
+
onSuccess?.();
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
const msg = err instanceof Error ? err.message : "An error occurred";
|
|
1248
|
+
variableStore.set("payment.error", msg);
|
|
1249
|
+
onError?.(msg);
|
|
1250
|
+
} finally {
|
|
1251
|
+
variableStore.set("payment.loading", false);
|
|
1252
|
+
}
|
|
1253
|
+
}, [validateOnly, variableStore, tracker, product, onSuccess, onError]);
|
|
1254
|
+
useImperativeHandle(ref, () => ({ submit: handleSubmit }), [handleSubmit]);
|
|
1255
|
+
return /* @__PURE__ */ jsx("div", { className, children: variant === "embedded" ? /* @__PURE__ */ jsx(DemoEmbeddedForm, { product, onReady }) : /* @__PURE__ */ jsx(DemoElementsForm, { onReady }) });
|
|
1256
|
+
}
|
|
1257
|
+
);
|
|
1258
|
+
var API_BASE_URL2 = "https://api.appfunnel.net";
|
|
1259
|
+
var isDevMode = () => typeof globalThis !== "undefined" && !!globalThis.__APPFUNNEL_DEV__;
|
|
471
1260
|
var InnerPaymentForm = forwardRef(
|
|
472
1261
|
function InnerPaymentForm2({ paymentMode, validateOnly, onSuccess, onError, onReady, layout }, ref) {
|
|
473
1262
|
const stripe = useStripe();
|
|
@@ -515,7 +1304,7 @@ var InnerPaymentForm = forwardRef(
|
|
|
515
1304
|
return;
|
|
516
1305
|
}
|
|
517
1306
|
const response2 = await fetch(
|
|
518
|
-
`${
|
|
1307
|
+
`${API_BASE_URL2}/campaign/${campaignId}/stripe/validate-card`,
|
|
519
1308
|
{
|
|
520
1309
|
method: "POST",
|
|
521
1310
|
headers: { "Content-Type": "application/json" },
|
|
@@ -556,7 +1345,7 @@ var InnerPaymentForm = forwardRef(
|
|
|
556
1345
|
return;
|
|
557
1346
|
}
|
|
558
1347
|
const response = await fetch(
|
|
559
|
-
`${
|
|
1348
|
+
`${API_BASE_URL2}/campaign/${campaignId}/stripe/purchase`,
|
|
560
1349
|
{
|
|
561
1350
|
method: "POST",
|
|
562
1351
|
headers: { "Content-Type": "application/json" },
|
|
@@ -601,8 +1390,8 @@ var InnerPaymentForm = forwardRef(
|
|
|
601
1390
|
] });
|
|
602
1391
|
}
|
|
603
1392
|
);
|
|
604
|
-
var
|
|
605
|
-
function
|
|
1393
|
+
var RealStripePaymentForm = forwardRef(
|
|
1394
|
+
function RealStripePaymentForm2({
|
|
606
1395
|
productId,
|
|
607
1396
|
mode = "checkout",
|
|
608
1397
|
variant = "elements",
|
|
@@ -641,7 +1430,7 @@ var StripePaymentForm = forwardRef(
|
|
|
641
1430
|
try {
|
|
642
1431
|
if (variant === "embedded") {
|
|
643
1432
|
const response = await fetch(
|
|
644
|
-
`${
|
|
1433
|
+
`${API_BASE_URL2}/campaign/${campaignId}/stripe/checkout-session`,
|
|
645
1434
|
{
|
|
646
1435
|
method: "POST",
|
|
647
1436
|
headers: { "Content-Type": "application/json" },
|
|
@@ -667,7 +1456,7 @@ var StripePaymentForm = forwardRef(
|
|
|
667
1456
|
priceId: product.storePriceId
|
|
668
1457
|
};
|
|
669
1458
|
if (validateOnly) body.validateOnly = true;
|
|
670
|
-
const response = await fetch(`${
|
|
1459
|
+
const response = await fetch(`${API_BASE_URL2}${endpoint}`, {
|
|
671
1460
|
method: "POST",
|
|
672
1461
|
headers: { "Content-Type": "application/json" },
|
|
673
1462
|
body: JSON.stringify(body)
|
|
@@ -743,6 +1532,14 @@ var StripePaymentForm = forwardRef(
|
|
|
743
1532
|
) }) });
|
|
744
1533
|
}
|
|
745
1534
|
);
|
|
1535
|
+
var StripePaymentForm = forwardRef(
|
|
1536
|
+
function StripePaymentForm2(props, ref) {
|
|
1537
|
+
if (isDevMode()) {
|
|
1538
|
+
return /* @__PURE__ */ jsx(DemoStripePaymentForm, { ref, ...props });
|
|
1539
|
+
}
|
|
1540
|
+
return /* @__PURE__ */ jsx(RealStripePaymentForm, { ref, ...props });
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
746
1543
|
function PaddleCheckout({
|
|
747
1544
|
productId,
|
|
748
1545
|
mode = "overlay",
|
|
@@ -807,6 +1604,6 @@ function PaddleCheckout({
|
|
|
807
1604
|
return null;
|
|
808
1605
|
}
|
|
809
1606
|
|
|
810
|
-
export { PaddleCheckout, StripePaymentForm, defineConfig, definePage, useData, useDeviceInfo, useFunnel,
|
|
1607
|
+
export { PaddleCheckout, StripePaymentForm, defineConfig, definePage, useData, useDateOfBirth, useDeviceInfo, useFunnel, useKeyboard, useLocale, usePageData, usePayment, useProducts, useQueryParam, useQueryParams, useSafeArea, useTracking, useTranslation, useUser, useUserProperty, useVariable, useVariables };
|
|
811
1608
|
//# sourceMappingURL=index.js.map
|
|
812
1609
|
//# sourceMappingURL=index.js.map
|