@appfunnel-dev/sdk 0.5.0 → 0.7.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/dist/chunk-EVUYCLVY.cjs +1326 -0
- package/dist/chunk-EVUYCLVY.cjs.map +1 -0
- package/dist/chunk-H3KHXZSI.js +1321 -0
- 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 +12172 -0
- package/dist/elements/index.cjs.map +1 -0
- package/dist/elements/index.d.cts +602 -0
- package/dist/elements/index.d.ts +602 -0
- package/dist/elements/index.js +12137 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/index.cjs +590 -371
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -14
- package/dist/index.d.ts +132 -14
- package/dist/index.js +555 -355
- package/dist/index.js.map +1 -1
- package/dist/internal-C7seLJBr.d.cts +516 -0
- package/dist/internal-C7seLJBr.d.ts +516 -0
- package/dist/internal.cjs +5 -1196
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +2 -204
- package/dist/internal.d.ts +2 -204
- package/dist/internal.js +1 -1199
- package/dist/internal.js.map +1 -1
- package/package.json +17 -2
- package/dist/types-ChorYUCl.d.cts +0 -255
- package/dist/types-ChorYUCl.d.ts +0 -255
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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, useState, useRef, useEffect, useCallback, useImperativeHandle, useMemo, useSyncExternalStore } from 'react';
|
|
3
6
|
import { loadStripe } from '@stripe/stripe-js';
|
|
4
|
-
import {
|
|
7
|
+
import { useStripe, useElements, PaymentElement, EmbeddedCheckoutProvider, EmbeddedCheckout, Elements } from '@stripe/react-stripe-js';
|
|
8
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
9
|
|
|
6
10
|
// src/config.ts
|
|
7
11
|
function defineConfig(config) {
|
|
@@ -10,23 +14,11 @@ function defineConfig(config) {
|
|
|
10
14
|
function definePage(definition) {
|
|
11
15
|
return definition;
|
|
12
16
|
}
|
|
13
|
-
function registerIntegration(id, loader) {
|
|
14
|
-
}
|
|
15
|
-
var FunnelContext = createContext(null);
|
|
16
|
-
function useFunnelContext() {
|
|
17
|
-
const ctx = useContext(FunnelContext);
|
|
18
|
-
if (!ctx) {
|
|
19
|
-
throw new Error("useFunnelContext must be used within a <FunnelProvider>");
|
|
20
|
-
}
|
|
21
|
-
return ctx;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// src/hooks/useVariable.ts
|
|
25
17
|
function useVariable(id) {
|
|
26
18
|
const { variableStore } = useFunnelContext();
|
|
27
19
|
const subscribe = useCallback(
|
|
28
|
-
(callback) => variableStore.subscribe(callback),
|
|
29
|
-
[variableStore]
|
|
20
|
+
(callback) => variableStore.subscribe(callback, { keys: [id] }),
|
|
21
|
+
[variableStore, id]
|
|
30
22
|
);
|
|
31
23
|
const getSnapshot = useCallback(
|
|
32
24
|
() => variableStore.get(id),
|
|
@@ -51,10 +43,106 @@ function useVariables() {
|
|
|
51
43
|
);
|
|
52
44
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
53
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
|
|
54
142
|
function useUser() {
|
|
55
|
-
const { variableStore
|
|
143
|
+
const { variableStore } = useFunnelContext();
|
|
56
144
|
const subscribe = useCallback(
|
|
57
|
-
(cb) => variableStore.subscribe(cb),
|
|
145
|
+
(cb) => variableStore.subscribe(cb, { prefix: "user." }),
|
|
58
146
|
[variableStore]
|
|
59
147
|
);
|
|
60
148
|
const getSnapshot = useCallback(
|
|
@@ -71,24 +159,23 @@ function useUser() {
|
|
|
71
159
|
dateOfBirth: variables["user.dateOfBirth"] || "",
|
|
72
160
|
setEmail(email) {
|
|
73
161
|
variableStore.set("user.email", email);
|
|
74
|
-
tracker.identify(email);
|
|
75
162
|
},
|
|
76
163
|
setName(name) {
|
|
77
164
|
variableStore.set("user.name", name);
|
|
78
165
|
},
|
|
79
166
|
setDateOfBirth(dateOfBirth) {
|
|
80
|
-
variableStore.set("user.dateOfBirth", dateOfBirth);
|
|
167
|
+
variableStore.set("user.dateOfBirth", toISODate(dateOfBirth));
|
|
81
168
|
}
|
|
82
169
|
}),
|
|
83
|
-
[variables, variableStore
|
|
170
|
+
[variables, variableStore]
|
|
84
171
|
);
|
|
85
172
|
}
|
|
86
173
|
function useUserProperty(field) {
|
|
87
174
|
const { variableStore } = useFunnelContext();
|
|
88
175
|
const key = `user.${field}`;
|
|
89
176
|
const subscribe = useCallback(
|
|
90
|
-
(cb) => variableStore.subscribe(cb),
|
|
91
|
-
[variableStore]
|
|
177
|
+
(cb) => variableStore.subscribe(cb, { keys: [key] }),
|
|
178
|
+
[variableStore, key]
|
|
92
179
|
);
|
|
93
180
|
const getSnapshot = useCallback(
|
|
94
181
|
() => variableStore.get(key) || "",
|
|
@@ -101,49 +188,28 @@ function useUserProperty(field) {
|
|
|
101
188
|
);
|
|
102
189
|
return [value, setValue];
|
|
103
190
|
}
|
|
104
|
-
function
|
|
191
|
+
function useDateOfBirth(format = "MM/DD/YYYY") {
|
|
105
192
|
const { variableStore } = useFunnelContext();
|
|
106
|
-
const
|
|
193
|
+
const key = "user.dateOfBirth";
|
|
107
194
|
const subscribe = useCallback(
|
|
108
|
-
(cb) => variableStore.subscribe(cb),
|
|
195
|
+
(cb) => variableStore.subscribe(cb, { keys: [key] }),
|
|
109
196
|
[variableStore]
|
|
110
197
|
);
|
|
111
198
|
const getSnapshot = useCallback(
|
|
112
|
-
() => variableStore.get(
|
|
113
|
-
[variableStore
|
|
199
|
+
() => variableStore.get(key) || "",
|
|
200
|
+
[variableStore]
|
|
114
201
|
);
|
|
115
202
|
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
116
203
|
const setValue = useCallback(
|
|
117
|
-
(v) => variableStore.set(
|
|
118
|
-
[variableStore,
|
|
204
|
+
(v) => variableStore.set(key, toISODateWithFormat(v, format)),
|
|
205
|
+
[variableStore, format]
|
|
119
206
|
);
|
|
120
207
|
return [value, setValue];
|
|
121
208
|
}
|
|
122
|
-
function useResponses() {
|
|
123
|
-
const { variableStore } = useFunnelContext();
|
|
124
|
-
const subscribe = useCallback(
|
|
125
|
-
(cb) => variableStore.subscribe(cb),
|
|
126
|
-
[variableStore]
|
|
127
|
-
);
|
|
128
|
-
const getSnapshot = useCallback(
|
|
129
|
-
() => variableStore.getState(),
|
|
130
|
-
[variableStore]
|
|
131
|
-
);
|
|
132
|
-
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
133
|
-
return useMemo(() => {
|
|
134
|
-
const result = {};
|
|
135
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
136
|
-
if (key.startsWith("answers.")) {
|
|
137
|
-
result[key.slice(8)] = value;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return result;
|
|
141
|
-
}, [variables]);
|
|
142
|
-
}
|
|
143
209
|
function useQueryParams() {
|
|
144
210
|
const { variableStore } = useFunnelContext();
|
|
145
211
|
const subscribe = useCallback(
|
|
146
|
-
(cb) => variableStore.subscribe(cb),
|
|
212
|
+
(cb) => variableStore.subscribe(cb, { prefix: "query." }),
|
|
147
213
|
[variableStore]
|
|
148
214
|
);
|
|
149
215
|
const getSnapshot = useCallback(
|
|
@@ -165,8 +231,8 @@ function useQueryParam(key) {
|
|
|
165
231
|
const { variableStore } = useFunnelContext();
|
|
166
232
|
const prefixedKey = `query.${key}`;
|
|
167
233
|
const subscribe = useCallback(
|
|
168
|
-
(cb) => variableStore.subscribe(cb),
|
|
169
|
-
[variableStore]
|
|
234
|
+
(cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
|
|
235
|
+
[variableStore, prefixedKey]
|
|
170
236
|
);
|
|
171
237
|
const getSnapshot = useCallback(
|
|
172
238
|
() => variableStore.get(prefixedKey) || "",
|
|
@@ -178,8 +244,8 @@ function useData(key) {
|
|
|
178
244
|
const { variableStore } = useFunnelContext();
|
|
179
245
|
const prefixedKey = `data.${key}`;
|
|
180
246
|
const subscribe = useCallback(
|
|
181
|
-
(cb) => variableStore.subscribe(cb),
|
|
182
|
-
[variableStore]
|
|
247
|
+
(cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
|
|
248
|
+
[variableStore, prefixedKey]
|
|
183
249
|
);
|
|
184
250
|
const getSnapshot = useCallback(
|
|
185
251
|
() => variableStore.get(prefixedKey),
|
|
@@ -250,99 +316,10 @@ function useTranslation() {
|
|
|
250
316
|
const availableLocales = i18n.getAvailableLocales();
|
|
251
317
|
return { t, locale, setLocale, availableLocales };
|
|
252
318
|
}
|
|
253
|
-
function useNavigation() {
|
|
254
|
-
const { router, variableStore, tracker } = useFunnelContext();
|
|
255
|
-
const subscribe = useCallback(
|
|
256
|
-
(cb) => variableStore.subscribe(cb),
|
|
257
|
-
[variableStore]
|
|
258
|
-
);
|
|
259
|
-
const getSnapshot = useCallback(
|
|
260
|
-
() => variableStore.getState(),
|
|
261
|
-
[variableStore]
|
|
262
|
-
);
|
|
263
|
-
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
264
|
-
const goToNextPage = useCallback(() => {
|
|
265
|
-
const previousPage = router.getCurrentPage();
|
|
266
|
-
if (previousPage) {
|
|
267
|
-
tracker.stopPageTracking();
|
|
268
|
-
}
|
|
269
|
-
const nextKey = router.goToNextPage(variables);
|
|
270
|
-
if (nextKey) {
|
|
271
|
-
const nextPage = router.getCurrentPage();
|
|
272
|
-
if (nextPage) {
|
|
273
|
-
tracker.track("page.view", {
|
|
274
|
-
pageId: nextPage.key,
|
|
275
|
-
pageKey: nextPage.key,
|
|
276
|
-
pageName: nextPage.name
|
|
277
|
-
});
|
|
278
|
-
tracker.startPageTracking(nextPage.key);
|
|
279
|
-
}
|
|
280
|
-
variableStore.setMany({
|
|
281
|
-
"page.currentId": nextKey,
|
|
282
|
-
"page.currentIndex": router.getPageHistory().length,
|
|
283
|
-
"page.current": router.getPageHistory().length + 1,
|
|
284
|
-
"page.startedAt": Date.now(),
|
|
285
|
-
"page.timeOnCurrent": 0
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}, [router, variables, tracker, variableStore]);
|
|
289
|
-
const goBack = useCallback(() => {
|
|
290
|
-
tracker.stopPageTracking();
|
|
291
|
-
const prevKey = router.goBack();
|
|
292
|
-
if (prevKey) {
|
|
293
|
-
const page = router.getCurrentPage();
|
|
294
|
-
if (page) {
|
|
295
|
-
tracker.track("page.view", {
|
|
296
|
-
pageId: page.key,
|
|
297
|
-
pageKey: page.key,
|
|
298
|
-
pageName: page.name
|
|
299
|
-
});
|
|
300
|
-
tracker.startPageTracking(page.key);
|
|
301
|
-
}
|
|
302
|
-
variableStore.setMany({
|
|
303
|
-
"page.currentId": prevKey,
|
|
304
|
-
"page.currentIndex": router.getPageHistory().length,
|
|
305
|
-
"page.current": router.getPageHistory().length + 1,
|
|
306
|
-
"page.startedAt": Date.now(),
|
|
307
|
-
"page.timeOnCurrent": 0
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}, [router, tracker, variableStore]);
|
|
311
|
-
const goToPage = useCallback((pageKey) => {
|
|
312
|
-
tracker.stopPageTracking();
|
|
313
|
-
const key = router.goToPage(pageKey);
|
|
314
|
-
if (key) {
|
|
315
|
-
const page = router.getCurrentPage();
|
|
316
|
-
if (page) {
|
|
317
|
-
tracker.track("page.view", {
|
|
318
|
-
pageId: page.key,
|
|
319
|
-
pageKey: page.key,
|
|
320
|
-
pageName: page.name
|
|
321
|
-
});
|
|
322
|
-
tracker.startPageTracking(page.key);
|
|
323
|
-
}
|
|
324
|
-
variableStore.setMany({
|
|
325
|
-
"page.currentId": key,
|
|
326
|
-
"page.currentIndex": router.getPageHistory().length,
|
|
327
|
-
"page.current": router.getPageHistory().length + 1,
|
|
328
|
-
"page.startedAt": Date.now(),
|
|
329
|
-
"page.timeOnCurrent": 0
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}, [router, tracker, variableStore]);
|
|
333
|
-
return {
|
|
334
|
-
goToNextPage,
|
|
335
|
-
goBack,
|
|
336
|
-
goToPage,
|
|
337
|
-
currentPage: router.getCurrentPage(),
|
|
338
|
-
pageHistory: router.getPageHistory(),
|
|
339
|
-
progress: router.getProgress()
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
319
|
function useProducts() {
|
|
343
320
|
const { products, variableStore, selectProduct: ctxSelect } = useFunnelContext();
|
|
344
321
|
const subscribe = useCallback(
|
|
345
|
-
(cb) => variableStore.subscribe(cb),
|
|
322
|
+
(cb) => variableStore.subscribe(cb, { keys: ["products.selectedProductId"] }),
|
|
346
323
|
[variableStore]
|
|
347
324
|
);
|
|
348
325
|
const getSnapshot = useCallback(
|
|
@@ -372,10 +349,19 @@ function useTracking() {
|
|
|
372
349
|
);
|
|
373
350
|
return { track, identify };
|
|
374
351
|
}
|
|
352
|
+
var PAYMENT_KEYS = [
|
|
353
|
+
"card.last4",
|
|
354
|
+
"card.brand",
|
|
355
|
+
"card.expMonth",
|
|
356
|
+
"card.expYear",
|
|
357
|
+
"payment.loading",
|
|
358
|
+
"payment.error",
|
|
359
|
+
"payment.customerId"
|
|
360
|
+
];
|
|
375
361
|
function usePayment() {
|
|
376
|
-
const { variableStore
|
|
362
|
+
const { variableStore } = useFunnelContext();
|
|
377
363
|
const subscribe = useCallback(
|
|
378
|
-
(cb) => variableStore.subscribe(cb),
|
|
364
|
+
(cb) => variableStore.subscribe(cb, { keys: PAYMENT_KEYS }),
|
|
379
365
|
[variableStore]
|
|
380
366
|
);
|
|
381
367
|
const getSnapshot = useCallback(
|
|
@@ -388,14 +374,188 @@ function usePayment() {
|
|
|
388
374
|
const brand = variables["card.brand"] || "";
|
|
389
375
|
const expMonth = variables["card.expMonth"] || 0;
|
|
390
376
|
const expYear = variables["card.expYear"] || 0;
|
|
377
|
+
const customerId = variables["payment.customerId"] || null;
|
|
391
378
|
return {
|
|
392
|
-
customerId
|
|
379
|
+
customerId,
|
|
393
380
|
isAuthorized: !!last4,
|
|
394
381
|
loading: !!variables["payment.loading"],
|
|
395
382
|
error: variables["payment.error"] || null,
|
|
396
383
|
cardDetails: last4 ? { last4, brand, expMonth, expYear } : null
|
|
397
384
|
};
|
|
398
|
-
}, [variables
|
|
385
|
+
}, [variables]);
|
|
386
|
+
}
|
|
387
|
+
var DEVICE_KEYS = [
|
|
388
|
+
"os.name",
|
|
389
|
+
"os.timezone",
|
|
390
|
+
"device.type",
|
|
391
|
+
"device.isMobile",
|
|
392
|
+
"device.isTablet",
|
|
393
|
+
"device.screenWidth",
|
|
394
|
+
"device.screenHeight",
|
|
395
|
+
"device.viewportWidth",
|
|
396
|
+
"device.viewportHeight",
|
|
397
|
+
"device.colorDepth",
|
|
398
|
+
"device.pixelRatio",
|
|
399
|
+
"browser.name",
|
|
400
|
+
"browser.version",
|
|
401
|
+
"browser.userAgent",
|
|
402
|
+
"browser.cookieEnabled",
|
|
403
|
+
"browser.online",
|
|
404
|
+
"browser.language"
|
|
405
|
+
];
|
|
406
|
+
function useDeviceInfo() {
|
|
407
|
+
const { variableStore } = useFunnelContext();
|
|
408
|
+
const subscribe = useCallback(
|
|
409
|
+
(cb) => variableStore.subscribe(cb, { keys: DEVICE_KEYS }),
|
|
410
|
+
[variableStore]
|
|
411
|
+
);
|
|
412
|
+
const getSnapshot = useCallback(
|
|
413
|
+
() => variableStore.getState(),
|
|
414
|
+
[variableStore]
|
|
415
|
+
);
|
|
416
|
+
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
417
|
+
return useMemo(() => ({
|
|
418
|
+
os: {
|
|
419
|
+
name: variables["os.name"] || "",
|
|
420
|
+
timezone: variables["os.timezone"] || ""
|
|
421
|
+
},
|
|
422
|
+
device: {
|
|
423
|
+
type: variables["device.type"] || "desktop",
|
|
424
|
+
isMobile: !!variables["device.isMobile"],
|
|
425
|
+
isTablet: !!variables["device.isTablet"],
|
|
426
|
+
screenWidth: variables["device.screenWidth"] || 0,
|
|
427
|
+
screenHeight: variables["device.screenHeight"] || 0,
|
|
428
|
+
viewportWidth: variables["device.viewportWidth"] || 0,
|
|
429
|
+
viewportHeight: variables["device.viewportHeight"] || 0,
|
|
430
|
+
colorDepth: variables["device.colorDepth"] || 24,
|
|
431
|
+
pixelRatio: variables["device.pixelRatio"] || 1
|
|
432
|
+
},
|
|
433
|
+
browser: {
|
|
434
|
+
name: variables["browser.name"] || "",
|
|
435
|
+
version: variables["browser.version"] || "",
|
|
436
|
+
userAgent: variables["browser.userAgent"] || "",
|
|
437
|
+
cookieEnabled: variables["browser.cookieEnabled"] !== false,
|
|
438
|
+
online: variables["browser.online"] !== false,
|
|
439
|
+
language: variables["browser.language"] || "en"
|
|
440
|
+
}
|
|
441
|
+
}), [variables]);
|
|
442
|
+
}
|
|
443
|
+
function useSafeArea() {
|
|
444
|
+
const [insets, setInsets] = useState({ top: 0, right: 0, bottom: 0, left: 0 });
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (typeof window === "undefined") return;
|
|
447
|
+
const el = document.createElement("div");
|
|
448
|
+
el.style.cssText = [
|
|
449
|
+
"position:fixed",
|
|
450
|
+
"top:env(safe-area-inset-top,0px)",
|
|
451
|
+
"right:env(safe-area-inset-right,0px)",
|
|
452
|
+
"bottom:env(safe-area-inset-bottom,0px)",
|
|
453
|
+
"left:env(safe-area-inset-left,0px)",
|
|
454
|
+
"pointer-events:none",
|
|
455
|
+
"visibility:hidden",
|
|
456
|
+
"z-index:-1"
|
|
457
|
+
].join(";");
|
|
458
|
+
document.body.appendChild(el);
|
|
459
|
+
function read() {
|
|
460
|
+
const style = getComputedStyle(el);
|
|
461
|
+
setInsets({
|
|
462
|
+
top: parseFloat(style.top) || 0,
|
|
463
|
+
right: parseFloat(style.right) || 0,
|
|
464
|
+
bottom: parseFloat(style.bottom) || 0,
|
|
465
|
+
left: parseFloat(style.left) || 0
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
read();
|
|
469
|
+
const observer = new ResizeObserver(read);
|
|
470
|
+
observer.observe(el);
|
|
471
|
+
window.addEventListener("resize", read);
|
|
472
|
+
return () => {
|
|
473
|
+
observer.disconnect();
|
|
474
|
+
window.removeEventListener("resize", read);
|
|
475
|
+
el.remove();
|
|
476
|
+
};
|
|
477
|
+
}, []);
|
|
478
|
+
return insets;
|
|
479
|
+
}
|
|
480
|
+
function useKeyboard() {
|
|
481
|
+
const [state, setState] = useState({ isOpen: false, height: 0 });
|
|
482
|
+
const timeoutRef = useRef();
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (typeof window === "undefined") return;
|
|
485
|
+
if ("virtualKeyboard" in navigator) {
|
|
486
|
+
const vk = navigator.virtualKeyboard;
|
|
487
|
+
vk.overlaysContent = true;
|
|
488
|
+
const handler = () => {
|
|
489
|
+
const h = vk.boundingRect.height;
|
|
490
|
+
setState((prev) => {
|
|
491
|
+
if (prev.height === h && prev.isOpen === h > 0) return prev;
|
|
492
|
+
return { isOpen: h > 0, height: h };
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
vk.addEventListener("geometrychange", handler);
|
|
496
|
+
handler();
|
|
497
|
+
return () => vk.removeEventListener("geometrychange", handler);
|
|
498
|
+
}
|
|
499
|
+
const vv = window.visualViewport;
|
|
500
|
+
if (!vv) return;
|
|
501
|
+
let layoutHeight = window.innerHeight;
|
|
502
|
+
function compute() {
|
|
503
|
+
const kbHeight = Math.max(0, layoutHeight - vv.height);
|
|
504
|
+
const h = kbHeight > 40 ? Math.round(kbHeight) : 0;
|
|
505
|
+
setState((prev) => {
|
|
506
|
+
if (prev.height === h && prev.isOpen === h > 0) return prev;
|
|
507
|
+
return { isOpen: h > 0, height: h };
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function onViewportResize() {
|
|
511
|
+
compute();
|
|
512
|
+
clearTimeout(timeoutRef.current);
|
|
513
|
+
timeoutRef.current = setTimeout(compute, 1e3);
|
|
514
|
+
}
|
|
515
|
+
function onWindowResize() {
|
|
516
|
+
layoutHeight = window.innerHeight;
|
|
517
|
+
compute();
|
|
518
|
+
}
|
|
519
|
+
vv.addEventListener("resize", onViewportResize);
|
|
520
|
+
window.addEventListener("resize", onWindowResize);
|
|
521
|
+
window.addEventListener("orientationchange", onWindowResize);
|
|
522
|
+
compute();
|
|
523
|
+
return () => {
|
|
524
|
+
clearTimeout(timeoutRef.current);
|
|
525
|
+
vv.removeEventListener("resize", onViewportResize);
|
|
526
|
+
window.removeEventListener("resize", onWindowResize);
|
|
527
|
+
window.removeEventListener("orientationchange", onWindowResize);
|
|
528
|
+
};
|
|
529
|
+
}, []);
|
|
530
|
+
return state;
|
|
531
|
+
}
|
|
532
|
+
var PAGE_KEYS = [
|
|
533
|
+
"page.currentId",
|
|
534
|
+
"page.currentIndex",
|
|
535
|
+
"page.current",
|
|
536
|
+
"page.total",
|
|
537
|
+
"page.progressPercentage",
|
|
538
|
+
"page.startedAt"
|
|
539
|
+
];
|
|
540
|
+
function usePageData() {
|
|
541
|
+
const { variableStore } = useFunnelContext();
|
|
542
|
+
const subscribe = useCallback(
|
|
543
|
+
(cb) => variableStore.subscribe(cb, { keys: PAGE_KEYS }),
|
|
544
|
+
[variableStore]
|
|
545
|
+
);
|
|
546
|
+
const getSnapshot = useCallback(
|
|
547
|
+
() => variableStore.getState(),
|
|
548
|
+
[variableStore]
|
|
549
|
+
);
|
|
550
|
+
const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
551
|
+
return useMemo(() => ({
|
|
552
|
+
currentId: variables["page.currentId"] || "",
|
|
553
|
+
currentIndex: variables["page.currentIndex"] || 0,
|
|
554
|
+
current: variables["page.current"] || 1,
|
|
555
|
+
total: variables["page.total"] || 0,
|
|
556
|
+
progressPercentage: variables["page.progressPercentage"] || 0,
|
|
557
|
+
startedAt: variables["page.startedAt"] || 0
|
|
558
|
+
}), [variables]);
|
|
399
559
|
}
|
|
400
560
|
|
|
401
561
|
// src/hooks/useFunnel.ts
|
|
@@ -415,242 +575,282 @@ function useFunnel() {
|
|
|
415
575
|
payment: usePayment()
|
|
416
576
|
};
|
|
417
577
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
validateOnly,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
setError(null);
|
|
436
|
-
variableStore.set("payment.loading", true);
|
|
437
|
-
try {
|
|
438
|
-
const confirmFn = paymentMode === "setup" ? stripe.confirmSetup : stripe.confirmPayment;
|
|
439
|
-
const confirmResult = await confirmFn({
|
|
440
|
-
elements,
|
|
441
|
-
redirect: "if_required",
|
|
442
|
-
confirmParams: { return_url: window.location.href }
|
|
443
|
-
});
|
|
444
|
-
if (confirmResult.error) {
|
|
445
|
-
const msg = confirmResult.error.message || "Payment failed";
|
|
578
|
+
var API_BASE_URL = "https://api.appfunnel.net";
|
|
579
|
+
var InnerPaymentForm = forwardRef(
|
|
580
|
+
function InnerPaymentForm2({ paymentMode, validateOnly, onSuccess, onError, onReady, layout }, ref) {
|
|
581
|
+
const stripe = useStripe();
|
|
582
|
+
const elements = useElements();
|
|
583
|
+
const [error, setError] = useState(null);
|
|
584
|
+
const { variableStore, campaignId, tracker, products } = useFunnelContext();
|
|
585
|
+
const readyFired = useRef(false);
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
if (stripe && elements && !readyFired.current) {
|
|
588
|
+
readyFired.current = true;
|
|
589
|
+
onReady?.();
|
|
590
|
+
}
|
|
591
|
+
}, [stripe, elements, onReady]);
|
|
592
|
+
const handleSubmit = useCallback(async () => {
|
|
593
|
+
if (!stripe || !elements) {
|
|
594
|
+
const msg = "Stripe not loaded";
|
|
446
595
|
setError(msg);
|
|
447
|
-
variableStore.set("payment.error", msg);
|
|
448
596
|
onError?.(msg);
|
|
449
597
|
return;
|
|
450
598
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
599
|
+
setError(null);
|
|
600
|
+
variableStore.set("payment.loading", true);
|
|
601
|
+
try {
|
|
602
|
+
const confirmFn = paymentMode === "setup" ? stripe.confirmSetup : stripe.confirmPayment;
|
|
603
|
+
const confirmResult = await confirmFn({
|
|
604
|
+
elements,
|
|
605
|
+
redirect: "if_required",
|
|
606
|
+
confirmParams: { return_url: window.location.href }
|
|
607
|
+
});
|
|
608
|
+
if (confirmResult.error) {
|
|
609
|
+
const msg = confirmResult.error.message || "Payment failed";
|
|
456
610
|
setError(msg);
|
|
457
611
|
variableStore.set("payment.error", msg);
|
|
458
612
|
onError?.(msg);
|
|
459
613
|
return;
|
|
460
614
|
}
|
|
461
|
-
|
|
462
|
-
|
|
615
|
+
tracker.track("checkout.payment_added");
|
|
616
|
+
if (validateOnly) {
|
|
617
|
+
const piId = "paymentIntent" in confirmResult ? confirmResult.paymentIntent : void 0;
|
|
618
|
+
if (!piId?.id) {
|
|
619
|
+
const msg = "PaymentIntent not found after confirmation";
|
|
620
|
+
setError(msg);
|
|
621
|
+
variableStore.set("payment.error", msg);
|
|
622
|
+
onError?.(msg);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const response2 = await fetch(
|
|
626
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/validate-card`,
|
|
627
|
+
{
|
|
628
|
+
method: "POST",
|
|
629
|
+
headers: { "Content-Type": "application/json" },
|
|
630
|
+
body: JSON.stringify({
|
|
631
|
+
campaignId,
|
|
632
|
+
sessionId: tracker.getSessionId(),
|
|
633
|
+
paymentIntentId: piId.id
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
const result2 = await response2.json();
|
|
638
|
+
if (!result2.success) {
|
|
639
|
+
const msg = result2.error || "Card validation failed";
|
|
640
|
+
setError(msg);
|
|
641
|
+
variableStore.set("payment.error", msg);
|
|
642
|
+
onError?.(msg);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
variableStore.setMany({
|
|
646
|
+
"card.last4": result2.card.last4,
|
|
647
|
+
"card.brand": result2.card.brand,
|
|
648
|
+
"card.expMonth": result2.card.expMonth,
|
|
649
|
+
"card.expYear": result2.card.expYear,
|
|
650
|
+
"card.funding": result2.card.funding,
|
|
651
|
+
"payment.error": ""
|
|
652
|
+
});
|
|
653
|
+
onSuccess?.();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const paymentIntentId = "paymentIntent" in confirmResult ? confirmResult.paymentIntent?.id : void 0;
|
|
657
|
+
const selectedId = variableStore.get("products.selectedProductId");
|
|
658
|
+
const product = products.find((p) => p.id === selectedId);
|
|
659
|
+
if (!product?.stripePriceId) {
|
|
660
|
+
const msg = "No product selected or missing Stripe price";
|
|
661
|
+
setError(msg);
|
|
662
|
+
variableStore.set("payment.error", msg);
|
|
663
|
+
onError?.(msg);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const response = await fetch(
|
|
667
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
|
|
463
668
|
{
|
|
464
669
|
method: "POST",
|
|
465
670
|
headers: { "Content-Type": "application/json" },
|
|
466
671
|
body: JSON.stringify({
|
|
467
672
|
campaignId,
|
|
468
673
|
sessionId: tracker.getSessionId(),
|
|
469
|
-
|
|
674
|
+
stripePriceId: product.stripePriceId,
|
|
675
|
+
trialPeriodDays: product.hasTrial ? product.trialDays : void 0,
|
|
676
|
+
onSessionPiId: paymentMode === "payment" ? paymentIntentId : void 0
|
|
470
677
|
})
|
|
471
678
|
}
|
|
472
679
|
);
|
|
473
|
-
const
|
|
474
|
-
if (
|
|
475
|
-
|
|
680
|
+
const result = await response.json();
|
|
681
|
+
if (result.success) {
|
|
682
|
+
variableStore.set("payment.error", "");
|
|
683
|
+
if (result.eventId || result.eventIds?.firstPeriod) {
|
|
684
|
+
tracker.track("purchase.complete", {
|
|
685
|
+
amount: product.rawPrice ? product.rawPrice / 100 : 0,
|
|
686
|
+
currency: product.currencyCode || "USD"
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
onSuccess?.();
|
|
690
|
+
} else {
|
|
691
|
+
const msg = result.error || "Failed to process payment";
|
|
476
692
|
setError(msg);
|
|
477
693
|
variableStore.set("payment.error", msg);
|
|
478
694
|
onError?.(msg);
|
|
479
|
-
return;
|
|
480
695
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"card.brand": result2.card.brand,
|
|
484
|
-
"card.expMonth": result2.card.expMonth,
|
|
485
|
-
"card.expYear": result2.card.expYear,
|
|
486
|
-
"card.funding": result2.card.funding,
|
|
487
|
-
"payment.error": ""
|
|
488
|
-
});
|
|
489
|
-
onSuccess?.();
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
const paymentIntentId = "paymentIntent" in confirmResult ? confirmResult.paymentIntent?.id : void 0;
|
|
493
|
-
const selectedId = variableStore.get("products.selectedProductId");
|
|
494
|
-
const product = products.find((p) => p.id === selectedId);
|
|
495
|
-
if (!product?.stripePriceId) {
|
|
496
|
-
const msg = "No product selected or missing Stripe price";
|
|
696
|
+
} catch (err) {
|
|
697
|
+
const msg = err instanceof Error ? err.message : "An error occurred";
|
|
497
698
|
setError(msg);
|
|
498
699
|
variableStore.set("payment.error", msg);
|
|
499
700
|
onError?.(msg);
|
|
500
|
-
|
|
701
|
+
} finally {
|
|
702
|
+
variableStore.set("payment.loading", false);
|
|
501
703
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
704
|
+
}, [stripe, elements, paymentMode, validateOnly, variableStore, campaignId, tracker, products, onSuccess, onError]);
|
|
705
|
+
useImperativeHandle(ref, () => ({ submit: handleSubmit }), [handleSubmit]);
|
|
706
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
707
|
+
/* @__PURE__ */ jsx(PaymentElement, { options: { layout: layout || "tabs" } }),
|
|
708
|
+
error && /* @__PURE__ */ jsx("div", { style: { color: "#ef4444", fontSize: "14px", marginTop: "12px" }, children: error })
|
|
709
|
+
] });
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
var StripePaymentForm = forwardRef(
|
|
713
|
+
function StripePaymentForm2({
|
|
714
|
+
productId,
|
|
715
|
+
mode = "checkout",
|
|
716
|
+
variant = "elements",
|
|
717
|
+
onSuccess,
|
|
718
|
+
onError,
|
|
719
|
+
onReady,
|
|
720
|
+
className,
|
|
721
|
+
appearance,
|
|
722
|
+
layout
|
|
723
|
+
}, ref) {
|
|
724
|
+
const { campaignId, tracker, variableStore, products } = useFunnelContext();
|
|
725
|
+
const [email] = useVariable("user.email");
|
|
726
|
+
const validateOnly = mode === "validate-only";
|
|
727
|
+
const product = useMemo(() => {
|
|
728
|
+
if (productId) return products.find((p) => p.id === productId) || null;
|
|
729
|
+
const selectedId = variableStore.get("products.selectedProductId");
|
|
730
|
+
return products.find((p) => p.id === selectedId) || null;
|
|
731
|
+
}, [productId, products, variableStore]);
|
|
732
|
+
const paymentMode = useMemo(() => {
|
|
733
|
+
if (validateOnly) return "payment";
|
|
734
|
+
if (product?.hasTrial && !product.paidTrial) return "setup";
|
|
735
|
+
return "payment";
|
|
736
|
+
}, [product, validateOnly]);
|
|
737
|
+
const [stripePromise, setStripePromise] = useState(null);
|
|
738
|
+
const [clientSecret, setClientSecret] = useState(null);
|
|
739
|
+
const [error, setError] = useState(null);
|
|
740
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
741
|
+
const hasInitialized = useRef(false);
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
if (!email || !campaignId || hasInitialized.current) return;
|
|
744
|
+
if (!product?.storePriceId) return;
|
|
745
|
+
hasInitialized.current = true;
|
|
746
|
+
setIsLoading(true);
|
|
747
|
+
variableStore.set("payment.loading", true);
|
|
748
|
+
const createIntent = async () => {
|
|
749
|
+
try {
|
|
750
|
+
if (variant === "embedded") {
|
|
751
|
+
const response = await fetch(
|
|
752
|
+
`${API_BASE_URL}/campaign/${campaignId}/stripe/checkout-session`,
|
|
753
|
+
{
|
|
754
|
+
method: "POST",
|
|
755
|
+
headers: { "Content-Type": "application/json" },
|
|
756
|
+
body: JSON.stringify({
|
|
757
|
+
campaignId,
|
|
758
|
+
sessionId: tracker.getSessionId(),
|
|
759
|
+
customerEmail: email,
|
|
760
|
+
priceId: product.storePriceId,
|
|
761
|
+
trialPeriodDays: product.hasTrial ? product.trialDays : void 0
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
const result = await response.json();
|
|
766
|
+
if (!result.success) throw new Error(result.error || "Failed to create checkout session");
|
|
767
|
+
setStripePromise(loadStripe(result.publishableKey));
|
|
768
|
+
setClientSecret(result.clientSecret);
|
|
769
|
+
} else {
|
|
770
|
+
const endpoint = paymentMode === "setup" ? `/campaign/${campaignId}/stripe/setup-intent` : `/campaign/${campaignId}/stripe/payment-intent`;
|
|
771
|
+
const body = {
|
|
772
|
+
campaignId,
|
|
773
|
+
sessionId: tracker.getSessionId(),
|
|
774
|
+
customerEmail: email,
|
|
775
|
+
priceId: product.storePriceId
|
|
776
|
+
};
|
|
777
|
+
if (validateOnly) body.validateOnly = true;
|
|
778
|
+
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: { "Content-Type": "application/json" },
|
|
781
|
+
body: JSON.stringify(body)
|
|
782
|
+
});
|
|
783
|
+
const result = await response.json();
|
|
784
|
+
if (!result.success) throw new Error(result.error || "Failed to create intent");
|
|
785
|
+
setStripePromise(loadStripe(result.publishableKey));
|
|
786
|
+
setClientSecret(result.clientSecret);
|
|
787
|
+
variableStore.set("user.stripeCustomerId", result.customerId);
|
|
788
|
+
}
|
|
789
|
+
tracker.track("checkout.start", {
|
|
790
|
+
productId: product.id,
|
|
791
|
+
priceId: product.stripePriceId || product.storePriceId,
|
|
792
|
+
productName: product.displayName
|
|
525
793
|
});
|
|
794
|
+
} catch (err) {
|
|
795
|
+
const msg = err instanceof Error ? err.message : "Failed to initialize payment";
|
|
796
|
+
setError(msg);
|
|
797
|
+
variableStore.set("payment.error", msg);
|
|
798
|
+
} finally {
|
|
799
|
+
setIsLoading(false);
|
|
800
|
+
variableStore.set("payment.loading", false);
|
|
526
801
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
onError?.(msg);
|
|
533
|
-
}
|
|
534
|
-
} catch (err) {
|
|
535
|
-
const msg = err instanceof Error ? err.message : "An error occurred";
|
|
536
|
-
setError(msg);
|
|
537
|
-
variableStore.set("payment.error", msg);
|
|
538
|
-
onError?.(msg);
|
|
539
|
-
} finally {
|
|
540
|
-
variableStore.set("payment.loading", false);
|
|
802
|
+
};
|
|
803
|
+
createIntent();
|
|
804
|
+
}, [email, campaignId, product, paymentMode, validateOnly, variant, tracker, variableStore]);
|
|
805
|
+
if (isLoading) {
|
|
806
|
+
return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "Loading payment form..." });
|
|
541
807
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (typeof window !== "undefined") {
|
|
545
|
-
window.__paymentElementSubmit = handleSubmit;
|
|
808
|
+
if (!email) {
|
|
809
|
+
return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#ef4444", fontSize: "14px" }, children: "Email is required to initialize payment" });
|
|
546
810
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const product = useMemo(() => {
|
|
570
|
-
if (productId) return products.find((p) => p.id === productId) || null;
|
|
571
|
-
const selectedId = variableStore.get("products.selectedProductId");
|
|
572
|
-
return products.find((p) => p.id === selectedId) || null;
|
|
573
|
-
}, [productId, products, variableStore]);
|
|
574
|
-
const paymentMode = useMemo(() => {
|
|
575
|
-
if (validateOnly) return "payment";
|
|
576
|
-
if (product?.hasTrial && !product.paidTrial) return "setup";
|
|
577
|
-
return "payment";
|
|
578
|
-
}, [product, validateOnly]);
|
|
579
|
-
const [stripePromise, setStripePromise] = useState(null);
|
|
580
|
-
const [clientSecret, setClientSecret] = useState(null);
|
|
581
|
-
const [error, setError] = useState(null);
|
|
582
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
583
|
-
const hasInitialized = useRef(false);
|
|
584
|
-
useEffect(() => {
|
|
585
|
-
if (!email || !campaignId || hasInitialized.current) return;
|
|
586
|
-
if (!product?.storePriceId) return;
|
|
587
|
-
hasInitialized.current = true;
|
|
588
|
-
setIsLoading(true);
|
|
589
|
-
variableStore.set("payment.loading", true);
|
|
590
|
-
const createIntent = async () => {
|
|
591
|
-
try {
|
|
592
|
-
const endpoint = paymentMode === "setup" ? `/campaign/${campaignId}/stripe/setup-intent` : `/campaign/${campaignId}/stripe/payment-intent`;
|
|
593
|
-
const body = {
|
|
594
|
-
campaignId,
|
|
595
|
-
sessionId: tracker.getSessionId(),
|
|
596
|
-
customerEmail: email,
|
|
597
|
-
priceId: product.storePriceId
|
|
598
|
-
};
|
|
599
|
-
if (validateOnly) body.validateOnly = true;
|
|
600
|
-
const response = await fetch(`${apiBaseUrl}${endpoint}`, {
|
|
601
|
-
method: "POST",
|
|
602
|
-
headers: { "Content-Type": "application/json" },
|
|
603
|
-
body: JSON.stringify(body)
|
|
604
|
-
});
|
|
605
|
-
const result = await response.json();
|
|
606
|
-
if (!result.success) {
|
|
607
|
-
throw new Error(result.error || "Failed to create intent");
|
|
811
|
+
if (error) {
|
|
812
|
+
return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#ef4444", fontSize: "14px" }, children: error });
|
|
813
|
+
}
|
|
814
|
+
if (!stripePromise || !clientSecret) {
|
|
815
|
+
return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "Initializing payment..." });
|
|
816
|
+
}
|
|
817
|
+
if (variant === "embedded") {
|
|
818
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
|
|
819
|
+
EmbeddedCheckoutProvider,
|
|
820
|
+
{
|
|
821
|
+
stripe: stripePromise,
|
|
822
|
+
options: {
|
|
823
|
+
clientSecret,
|
|
824
|
+
onComplete: () => {
|
|
825
|
+
tracker.track("purchase.complete", {
|
|
826
|
+
amount: product?.rawPrice ? product.rawPrice / 100 : 0,
|
|
827
|
+
currency: product?.currencyCode || "USD"
|
|
828
|
+
});
|
|
829
|
+
onSuccess?.();
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
children: /* @__PURE__ */ jsx(EmbeddedCheckout, {})
|
|
608
833
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
priceId: product.stripePriceId || product.storePriceId,
|
|
615
|
-
productName: product.displayName
|
|
616
|
-
});
|
|
617
|
-
} catch (err) {
|
|
618
|
-
const msg = err instanceof Error ? err.message : "Failed to initialize payment";
|
|
619
|
-
setError(msg);
|
|
620
|
-
variableStore.set("payment.error", msg);
|
|
621
|
-
} finally {
|
|
622
|
-
setIsLoading(false);
|
|
623
|
-
variableStore.set("payment.loading", false);
|
|
624
|
-
}
|
|
834
|
+
) });
|
|
835
|
+
}
|
|
836
|
+
const defaultAppearance = {
|
|
837
|
+
theme: "stripe",
|
|
838
|
+
variables: { colorPrimary: "#3b82f6", borderRadius: "8px" }
|
|
625
839
|
};
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "Initializing payment..." });
|
|
840
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(Elements, { stripe: stripePromise, options: { clientSecret, appearance: appearance || defaultAppearance }, children: /* @__PURE__ */ jsx(
|
|
841
|
+
InnerPaymentForm,
|
|
842
|
+
{
|
|
843
|
+
ref,
|
|
844
|
+
paymentMode,
|
|
845
|
+
validateOnly,
|
|
846
|
+
onSuccess,
|
|
847
|
+
onError,
|
|
848
|
+
onReady,
|
|
849
|
+
layout
|
|
850
|
+
}
|
|
851
|
+
) }) });
|
|
639
852
|
}
|
|
640
|
-
|
|
641
|
-
theme: "stripe",
|
|
642
|
-
variables: { colorPrimary: "#3b82f6", borderRadius: "8px" }
|
|
643
|
-
};
|
|
644
|
-
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(Elements, { stripe: stripePromise, options: { clientSecret, appearance: appearance || defaultAppearance }, children: /* @__PURE__ */ jsx(
|
|
645
|
-
InnerPaymentForm,
|
|
646
|
-
{
|
|
647
|
-
paymentMode,
|
|
648
|
-
validateOnly,
|
|
649
|
-
onSuccess,
|
|
650
|
-
onError
|
|
651
|
-
}
|
|
652
|
-
) }) });
|
|
653
|
-
}
|
|
853
|
+
);
|
|
654
854
|
function PaddleCheckout({
|
|
655
855
|
productId,
|
|
656
856
|
mode = "overlay",
|
|
@@ -715,6 +915,6 @@ function PaddleCheckout({
|
|
|
715
915
|
return null;
|
|
716
916
|
}
|
|
717
917
|
|
|
718
|
-
export { PaddleCheckout,
|
|
918
|
+
export { PaddleCheckout, StripePaymentForm, defineConfig, definePage, useData, useDateOfBirth, useDeviceInfo, useFunnel, useKeyboard, useLocale, usePageData, usePayment, useProducts, useQueryParam, useQueryParams, useSafeArea, useTracking, useTranslation, useUser, useUserProperty, useVariable, useVariables };
|
|
719
919
|
//# sourceMappingURL=index.js.map
|
|
720
920
|
//# sourceMappingURL=index.js.map
|