@solvapay/react 1.0.6 → 1.0.8-preview.1
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/CONTRIBUTING.md +117 -0
- package/README.md +234 -1
- package/dist/CancelPlanButton-CieT9swn.d.cts +1102 -0
- package/dist/CancelPlanButton-f56UlQN-.d.ts +1102 -0
- package/dist/chunk-MOP3ZBGC.js +4749 -0
- package/dist/index.cjs +4645 -624
- package/dist/index.d.cts +503 -577
- package/dist/index.d.ts +503 -577
- package/dist/index.js +604 -1042
- package/dist/primitives/index.cjs +4254 -0
- package/dist/primitives/index.d.cts +533 -0
- package/dist/primitives/index.d.ts +533 -0
- package/dist/primitives/index.js +187 -0
- package/dist/styles.css +869 -0
- package/package.json +20 -5
package/dist/index.js
CHANGED
|
@@ -1,606 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActivationFlow,
|
|
3
|
+
AmountPicker,
|
|
4
|
+
BalanceBadge,
|
|
5
|
+
CancelPlanButton,
|
|
6
|
+
CancelledPlanNotice,
|
|
7
|
+
CheckoutSummary2 as CheckoutSummary,
|
|
8
|
+
CopyContext,
|
|
9
|
+
CopyProvider,
|
|
10
|
+
CreditGate,
|
|
11
|
+
MandateText,
|
|
12
|
+
PaymentForm,
|
|
13
|
+
PaymentFormCardElement,
|
|
14
|
+
PaymentFormContext,
|
|
15
|
+
PaymentFormCustomerFields,
|
|
16
|
+
PaymentFormError,
|
|
17
|
+
PaymentFormLoading,
|
|
18
|
+
PaymentFormMandateText,
|
|
19
|
+
PaymentFormPaymentElement,
|
|
20
|
+
PaymentFormProvider,
|
|
21
|
+
PaymentFormSubmitButton,
|
|
22
|
+
PaymentFormSummary,
|
|
23
|
+
PaymentFormTermsCheckbox,
|
|
24
|
+
PlanBadge,
|
|
25
|
+
PlanSelector,
|
|
26
|
+
ProductBadge,
|
|
27
|
+
PurchaseGate,
|
|
28
|
+
SolvaPayContext,
|
|
29
|
+
SolvaPayProvider,
|
|
30
|
+
Spinner,
|
|
31
|
+
TopupForm2 as TopupForm,
|
|
32
|
+
buildRequestHeaders,
|
|
33
|
+
confirmPayment,
|
|
34
|
+
deriveVariant,
|
|
35
|
+
enCopy,
|
|
36
|
+
filterPurchases,
|
|
37
|
+
formatPrice,
|
|
38
|
+
getActivePurchases,
|
|
39
|
+
getCancelledPurchasesWithEndDate,
|
|
40
|
+
getMostRecentPurchase,
|
|
41
|
+
getPrimaryPurchase,
|
|
42
|
+
interpolate,
|
|
43
|
+
isPaidPurchase,
|
|
44
|
+
mergeCopy,
|
|
45
|
+
resolveCta,
|
|
46
|
+
useActivation,
|
|
47
|
+
useActivationFlow,
|
|
48
|
+
useAmountPicker,
|
|
49
|
+
useAmountPickerCopy,
|
|
50
|
+
useBalance,
|
|
51
|
+
useCheckout,
|
|
52
|
+
useCopy,
|
|
53
|
+
useCreditGate,
|
|
54
|
+
useCustomer,
|
|
55
|
+
useLocale,
|
|
56
|
+
useMerchant,
|
|
57
|
+
usePaymentForm,
|
|
58
|
+
usePlan,
|
|
59
|
+
usePlans,
|
|
60
|
+
useProduct,
|
|
61
|
+
usePurchase,
|
|
62
|
+
usePurchaseActions,
|
|
63
|
+
usePurchaseStatus,
|
|
64
|
+
useSolvaPay,
|
|
65
|
+
useTopup,
|
|
66
|
+
useTopupAmountSelector
|
|
67
|
+
} from "./chunk-MOP3ZBGC.js";
|
|
1
68
|
import {
|
|
2
69
|
defaultAuthAdapter
|
|
3
70
|
} from "./chunk-OUSEQRCT.js";
|
|
4
71
|
|
|
5
|
-
// src/SolvaPayProvider.tsx
|
|
6
|
-
import { createContext, useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
7
|
-
|
|
8
|
-
// src/utils/purchases.ts
|
|
9
|
-
function filterPurchases(purchases) {
|
|
10
|
-
return purchases.filter((purchase) => purchase.status === "active");
|
|
11
|
-
}
|
|
12
|
-
function getActivePurchases(purchases) {
|
|
13
|
-
return purchases.filter((purchase) => purchase.status === "active");
|
|
14
|
-
}
|
|
15
|
-
function getCancelledPurchasesWithEndDate(purchases) {
|
|
16
|
-
const now = /* @__PURE__ */ new Date();
|
|
17
|
-
return purchases.filter((purchase) => {
|
|
18
|
-
return purchase.status === "active" && purchase.cancelledAt && purchase.endDate && new Date(purchase.endDate) > now;
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function getMostRecentPurchase(purchases) {
|
|
22
|
-
if (purchases.length === 0) return null;
|
|
23
|
-
return purchases.reduce((latest, current) => {
|
|
24
|
-
return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
function getPrimaryPurchase(purchases) {
|
|
28
|
-
const filtered = filterPurchases(purchases);
|
|
29
|
-
if (filtered.length > 0) {
|
|
30
|
-
return getMostRecentPurchase(filtered);
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
function isPaidPurchase(purchase) {
|
|
35
|
-
return (purchase.amount ?? 0) > 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// src/SolvaPayProvider.tsx
|
|
39
|
-
import { jsx } from "react/jsx-runtime";
|
|
40
|
-
var SolvaPayContext = createContext(null);
|
|
41
|
-
var CUSTOMER_REF_KEY = "solvapay_customerRef";
|
|
42
|
-
var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
|
|
43
|
-
var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
|
|
44
|
-
var CACHE_DURATION = 24 * 60 * 60 * 1e3;
|
|
45
|
-
function getCachedCustomerRef(userId) {
|
|
46
|
-
if (typeof window === "undefined") return null;
|
|
47
|
-
const cached = localStorage.getItem(CUSTOMER_REF_KEY);
|
|
48
|
-
const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
|
|
49
|
-
const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
|
|
50
|
-
if (!cached || !expiry) return null;
|
|
51
|
-
if (Date.now() > parseInt(expiry)) {
|
|
52
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
53
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
54
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
if (userId !== void 0 && userId !== null) {
|
|
58
|
-
if (cachedUserId !== userId) {
|
|
59
|
-
clearCachedCustomerRef();
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return cached;
|
|
64
|
-
}
|
|
65
|
-
function setCachedCustomerRef(customerRef, userId) {
|
|
66
|
-
if (typeof window === "undefined") return;
|
|
67
|
-
if (userId === void 0 || userId === null) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
|
|
71
|
-
localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
|
|
72
|
-
localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
|
|
73
|
-
}
|
|
74
|
-
function clearCachedCustomerRef() {
|
|
75
|
-
if (typeof window === "undefined") return;
|
|
76
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
77
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
78
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
79
|
-
}
|
|
80
|
-
function getAuthAdapter(config) {
|
|
81
|
-
if (config?.auth?.adapter) {
|
|
82
|
-
return config.auth.adapter;
|
|
83
|
-
}
|
|
84
|
-
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
85
|
-
return {
|
|
86
|
-
async getToken() {
|
|
87
|
-
return config?.auth?.getToken?.() || null;
|
|
88
|
-
},
|
|
89
|
-
async getUserId() {
|
|
90
|
-
return config?.auth?.getUserId?.() || null;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return defaultAuthAdapter;
|
|
95
|
-
}
|
|
96
|
-
var SolvaPayProvider = ({
|
|
97
|
-
config,
|
|
98
|
-
createPayment: customCreatePayment,
|
|
99
|
-
checkPurchase: customCheckPurchase,
|
|
100
|
-
processPayment: customProcessPayment,
|
|
101
|
-
children
|
|
102
|
-
}) => {
|
|
103
|
-
const [purchaseData, setPurchaseData] = useState({
|
|
104
|
-
purchases: []
|
|
105
|
-
});
|
|
106
|
-
const [loading, setLoading] = useState(false);
|
|
107
|
-
const [internalCustomerRef, setInternalCustomerRef] = useState(void 0);
|
|
108
|
-
const [userId, setUserId] = useState(null);
|
|
109
|
-
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
110
|
-
const inFlightRef = useRef(null);
|
|
111
|
-
const lastFetchedRef = useRef(null);
|
|
112
|
-
const checkPurchaseRef = useRef(null);
|
|
113
|
-
const createPaymentRef = useRef(null);
|
|
114
|
-
const processPaymentRef = useRef(null);
|
|
115
|
-
const configRef = useRef(config);
|
|
116
|
-
const buildDefaultCheckPurchaseRef = useRef(
|
|
117
|
-
null
|
|
118
|
-
);
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
configRef.current = config;
|
|
121
|
-
}, [config]);
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
checkPurchaseRef.current = customCheckPurchase || null;
|
|
124
|
-
}, [customCheckPurchase]);
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
createPaymentRef.current = customCreatePayment || null;
|
|
127
|
-
}, [customCreatePayment]);
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
processPaymentRef.current = customProcessPayment || null;
|
|
130
|
-
}, [customProcessPayment]);
|
|
131
|
-
const buildDefaultCheckPurchase = useCallback(async () => {
|
|
132
|
-
const currentConfig = configRef.current;
|
|
133
|
-
const adapter = getAuthAdapter(currentConfig);
|
|
134
|
-
const token = await adapter.getToken();
|
|
135
|
-
const detectedUserId = await adapter.getUserId();
|
|
136
|
-
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
137
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
138
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
139
|
-
const headers = {
|
|
140
|
-
"Content-Type": "application/json"
|
|
141
|
-
};
|
|
142
|
-
if (token) {
|
|
143
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
144
|
-
}
|
|
145
|
-
if (cachedRef) {
|
|
146
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
147
|
-
}
|
|
148
|
-
if (currentConfig?.headers) {
|
|
149
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
150
|
-
Object.assign(headers, customHeaders);
|
|
151
|
-
}
|
|
152
|
-
const res = await fetchFn(route, {
|
|
153
|
-
method: "GET",
|
|
154
|
-
headers
|
|
155
|
-
});
|
|
156
|
-
if (!res.ok) {
|
|
157
|
-
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
158
|
-
currentConfig?.onError?.(error, "checkPurchase");
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
return res.json();
|
|
162
|
-
}, []);
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
|
|
165
|
-
}, [buildDefaultCheckPurchase]);
|
|
166
|
-
const buildDefaultCreatePayment = useCallback(
|
|
167
|
-
async (params) => {
|
|
168
|
-
const currentConfig = configRef.current;
|
|
169
|
-
const adapter = getAuthAdapter(currentConfig);
|
|
170
|
-
const token = await adapter.getToken();
|
|
171
|
-
const detectedUserId = await adapter.getUserId();
|
|
172
|
-
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
173
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
174
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
175
|
-
const headers = {
|
|
176
|
-
"Content-Type": "application/json"
|
|
177
|
-
};
|
|
178
|
-
if (token) {
|
|
179
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
180
|
-
}
|
|
181
|
-
if (cachedRef) {
|
|
182
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
183
|
-
}
|
|
184
|
-
if (currentConfig?.headers) {
|
|
185
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
186
|
-
Object.assign(headers, customHeaders);
|
|
187
|
-
}
|
|
188
|
-
const body = { planRef: params.planRef };
|
|
189
|
-
if (params.productRef) {
|
|
190
|
-
body.productRef = params.productRef;
|
|
191
|
-
}
|
|
192
|
-
const res = await fetchFn(route, {
|
|
193
|
-
method: "POST",
|
|
194
|
-
headers,
|
|
195
|
-
body: JSON.stringify(body)
|
|
196
|
-
});
|
|
197
|
-
if (!res.ok) {
|
|
198
|
-
const error = new Error(`Failed to create payment: ${res.statusText}`);
|
|
199
|
-
currentConfig?.onError?.(error, "createPayment");
|
|
200
|
-
throw error;
|
|
201
|
-
}
|
|
202
|
-
return res.json();
|
|
203
|
-
},
|
|
204
|
-
[]
|
|
205
|
-
);
|
|
206
|
-
const buildDefaultProcessPayment = useCallback(
|
|
207
|
-
async (params) => {
|
|
208
|
-
const currentConfig = configRef.current;
|
|
209
|
-
const adapter = getAuthAdapter(currentConfig);
|
|
210
|
-
const token = await adapter.getToken();
|
|
211
|
-
const detectedUserId = await adapter.getUserId();
|
|
212
|
-
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
213
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
214
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
215
|
-
const headers = {
|
|
216
|
-
"Content-Type": "application/json"
|
|
217
|
-
};
|
|
218
|
-
if (token) {
|
|
219
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
220
|
-
}
|
|
221
|
-
if (cachedRef) {
|
|
222
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
223
|
-
}
|
|
224
|
-
if (currentConfig?.headers) {
|
|
225
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
226
|
-
Object.assign(headers, customHeaders);
|
|
227
|
-
}
|
|
228
|
-
const res = await fetchFn(route, {
|
|
229
|
-
method: "POST",
|
|
230
|
-
headers,
|
|
231
|
-
body: JSON.stringify(params)
|
|
232
|
-
});
|
|
233
|
-
if (!res.ok) {
|
|
234
|
-
const error = new Error(`Failed to process payment: ${res.statusText}`);
|
|
235
|
-
currentConfig?.onError?.(error, "processPayment");
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
return res.json();
|
|
239
|
-
},
|
|
240
|
-
[]
|
|
241
|
-
);
|
|
242
|
-
const _checkPurchase = useCallback(async () => {
|
|
243
|
-
if (checkPurchaseRef.current) {
|
|
244
|
-
return checkPurchaseRef.current();
|
|
245
|
-
}
|
|
246
|
-
if (buildDefaultCheckPurchaseRef.current) {
|
|
247
|
-
return buildDefaultCheckPurchaseRef.current();
|
|
248
|
-
}
|
|
249
|
-
return buildDefaultCheckPurchase();
|
|
250
|
-
}, [buildDefaultCheckPurchase]);
|
|
251
|
-
const createPayment = useCallback(
|
|
252
|
-
async (params) => {
|
|
253
|
-
if (createPaymentRef.current) {
|
|
254
|
-
return createPaymentRef.current(params);
|
|
255
|
-
}
|
|
256
|
-
return buildDefaultCreatePayment(params);
|
|
257
|
-
},
|
|
258
|
-
[buildDefaultCreatePayment]
|
|
259
|
-
);
|
|
260
|
-
const processPayment = useCallback(
|
|
261
|
-
async (params) => {
|
|
262
|
-
if (processPaymentRef.current) {
|
|
263
|
-
return processPaymentRef.current(params);
|
|
264
|
-
}
|
|
265
|
-
return buildDefaultProcessPayment(params);
|
|
266
|
-
},
|
|
267
|
-
[buildDefaultProcessPayment]
|
|
268
|
-
);
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
const detectAuth = async () => {
|
|
271
|
-
const currentConfig = configRef.current;
|
|
272
|
-
const adapter = getAuthAdapter(currentConfig);
|
|
273
|
-
const token = await adapter.getToken();
|
|
274
|
-
const detectedUserId = await adapter.getUserId();
|
|
275
|
-
const prevUserId = userId;
|
|
276
|
-
setIsAuthenticated(!!token);
|
|
277
|
-
setUserId(detectedUserId);
|
|
278
|
-
if (prevUserId !== null && detectedUserId !== prevUserId) {
|
|
279
|
-
clearCachedCustomerRef();
|
|
280
|
-
setInternalCustomerRef(void 0);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
284
|
-
if (cachedRef && token) {
|
|
285
|
-
setInternalCustomerRef(cachedRef);
|
|
286
|
-
} else if (!token) {
|
|
287
|
-
clearCachedCustomerRef();
|
|
288
|
-
setInternalCustomerRef(void 0);
|
|
289
|
-
} else if (token && !cachedRef) {
|
|
290
|
-
setInternalCustomerRef(void 0);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
detectAuth();
|
|
294
|
-
const interval = setInterval(detectAuth, 5e3);
|
|
295
|
-
return () => clearInterval(interval);
|
|
296
|
-
}, [userId]);
|
|
297
|
-
const fetchPurchase = useCallback(
|
|
298
|
-
async (force = false) => {
|
|
299
|
-
if (!isAuthenticated && !internalCustomerRef) {
|
|
300
|
-
setPurchaseData({ purchases: [] });
|
|
301
|
-
setLoading(false);
|
|
302
|
-
inFlightRef.current = null;
|
|
303
|
-
lastFetchedRef.current = null;
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
307
|
-
if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (inFlightRef.current === cacheKey) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
inFlightRef.current = cacheKey;
|
|
314
|
-
setLoading(true);
|
|
315
|
-
try {
|
|
316
|
-
const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
|
|
317
|
-
if (!checkFn) {
|
|
318
|
-
throw new Error("checkPurchase function not available");
|
|
319
|
-
}
|
|
320
|
-
const data = await checkFn();
|
|
321
|
-
if (data.customerRef) {
|
|
322
|
-
setInternalCustomerRef(data.customerRef);
|
|
323
|
-
const currentAdapter = getAuthAdapter(configRef.current);
|
|
324
|
-
const currentUserId = await currentAdapter.getUserId();
|
|
325
|
-
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
326
|
-
}
|
|
327
|
-
if (inFlightRef.current === cacheKey) {
|
|
328
|
-
const filteredData = {
|
|
329
|
-
...data,
|
|
330
|
-
purchases: filterPurchases(data.purchases || [])
|
|
331
|
-
};
|
|
332
|
-
setPurchaseData(filteredData);
|
|
333
|
-
lastFetchedRef.current = cacheKey;
|
|
334
|
-
}
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.error("[SolvaPayProvider] Failed to fetch purchase:", error);
|
|
337
|
-
if (inFlightRef.current === cacheKey) {
|
|
338
|
-
setPurchaseData({
|
|
339
|
-
purchases: []
|
|
340
|
-
});
|
|
341
|
-
lastFetchedRef.current = cacheKey;
|
|
342
|
-
}
|
|
343
|
-
} finally {
|
|
344
|
-
if (inFlightRef.current === cacheKey) {
|
|
345
|
-
setLoading(false);
|
|
346
|
-
inFlightRef.current = null;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
[isAuthenticated, internalCustomerRef, userId]
|
|
351
|
-
);
|
|
352
|
-
const fetchPurchaseRef = useRef(fetchPurchase);
|
|
353
|
-
useEffect(() => {
|
|
354
|
-
fetchPurchaseRef.current = fetchPurchase;
|
|
355
|
-
}, [fetchPurchase]);
|
|
356
|
-
const refetchPurchase = useCallback(async () => {
|
|
357
|
-
lastFetchedRef.current = null;
|
|
358
|
-
inFlightRef.current = null;
|
|
359
|
-
setPurchaseData({ purchases: [] });
|
|
360
|
-
await fetchPurchaseRef.current(true);
|
|
361
|
-
}, []);
|
|
362
|
-
useEffect(() => {
|
|
363
|
-
lastFetchedRef.current = null;
|
|
364
|
-
inFlightRef.current = null;
|
|
365
|
-
if (isAuthenticated || internalCustomerRef) {
|
|
366
|
-
fetchPurchase();
|
|
367
|
-
} else {
|
|
368
|
-
setPurchaseData({ purchases: [] });
|
|
369
|
-
setLoading(false);
|
|
370
|
-
}
|
|
371
|
-
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
372
|
-
const updateCustomerRef = useCallback(
|
|
373
|
-
(newCustomerRef) => {
|
|
374
|
-
setInternalCustomerRef(newCustomerRef);
|
|
375
|
-
setCachedCustomerRef(newCustomerRef, userId);
|
|
376
|
-
fetchPurchase(true);
|
|
377
|
-
},
|
|
378
|
-
[fetchPurchase, userId]
|
|
379
|
-
);
|
|
380
|
-
const purchase = useMemo(() => {
|
|
381
|
-
const activePurchase = getPrimaryPurchase(purchaseData.purchases);
|
|
382
|
-
const activePaidPurchases = purchaseData.purchases.filter(
|
|
383
|
-
(p) => p.status === "active" && isPaidPurchase(p)
|
|
384
|
-
);
|
|
385
|
-
const activePaidPurchase = activePaidPurchases.sort(
|
|
386
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
387
|
-
)[0] || null;
|
|
388
|
-
return {
|
|
389
|
-
loading,
|
|
390
|
-
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
391
|
-
email: purchaseData.email,
|
|
392
|
-
name: purchaseData.name,
|
|
393
|
-
purchases: purchaseData.purchases,
|
|
394
|
-
hasProduct: (productName) => {
|
|
395
|
-
return purchaseData.purchases.some(
|
|
396
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
397
|
-
);
|
|
398
|
-
},
|
|
399
|
-
hasPlan: (productName) => {
|
|
400
|
-
return purchaseData.purchases.some(
|
|
401
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
402
|
-
);
|
|
403
|
-
},
|
|
404
|
-
activePurchase,
|
|
405
|
-
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
406
|
-
activePaidPurchase
|
|
407
|
-
};
|
|
408
|
-
}, [loading, purchaseData, internalCustomerRef]);
|
|
409
|
-
const contextValue = useMemo(
|
|
410
|
-
() => ({
|
|
411
|
-
purchase,
|
|
412
|
-
refetchPurchase,
|
|
413
|
-
createPayment,
|
|
414
|
-
processPayment,
|
|
415
|
-
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
416
|
-
updateCustomerRef
|
|
417
|
-
}),
|
|
418
|
-
[
|
|
419
|
-
purchase,
|
|
420
|
-
refetchPurchase,
|
|
421
|
-
createPayment,
|
|
422
|
-
processPayment,
|
|
423
|
-
purchaseData.customerRef,
|
|
424
|
-
internalCustomerRef,
|
|
425
|
-
updateCustomerRef
|
|
426
|
-
]
|
|
427
|
-
);
|
|
428
|
-
return /* @__PURE__ */ jsx(SolvaPayContext.Provider, { value: contextValue, children });
|
|
429
|
-
};
|
|
430
|
-
|
|
431
72
|
// src/PaymentForm.tsx
|
|
432
|
-
import {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
return context;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// src/hooks/useCheckout.ts
|
|
452
|
-
var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
453
|
-
function getStripeCacheKey(publishableKey, accountId) {
|
|
454
|
-
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
455
|
-
}
|
|
456
|
-
function useCheckout(options) {
|
|
457
|
-
const { planRef, productRef } = options;
|
|
458
|
-
const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
|
|
459
|
-
const [loading, setLoading] = useState2(false);
|
|
460
|
-
const [error, setError] = useState2(
|
|
461
|
-
!planRef || typeof planRef !== "string" ? new Error("useCheckout: planRef parameter is required and must be a string") : null
|
|
462
|
-
);
|
|
463
|
-
const [stripePromise, setStripePromise] = useState2(null);
|
|
464
|
-
const [clientSecret, setClientSecret] = useState2(null);
|
|
465
|
-
const isStartingRef = useRef2(false);
|
|
466
|
-
const startCheckout = useCallback2(async () => {
|
|
467
|
-
if (isStartingRef.current || loading) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (!planRef || typeof planRef !== "string") {
|
|
471
|
-
setError(new Error("useCheckout: planRef parameter is required and must be a string"));
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
isStartingRef.current = true;
|
|
475
|
-
setLoading(true);
|
|
476
|
-
setError(null);
|
|
477
|
-
try {
|
|
478
|
-
const result = await createPayment({ planRef, productRef });
|
|
479
|
-
if (!result || typeof result !== "object") {
|
|
480
|
-
throw new Error("Invalid payment intent response from server");
|
|
481
|
-
}
|
|
482
|
-
if (!result.clientSecret || typeof result.clientSecret !== "string") {
|
|
483
|
-
throw new Error("Invalid client secret in payment intent response");
|
|
484
|
-
}
|
|
485
|
-
if (!result.publishableKey || typeof result.publishableKey !== "string") {
|
|
486
|
-
throw new Error("Invalid publishable key in payment intent response");
|
|
487
|
-
}
|
|
488
|
-
if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
|
|
489
|
-
updateCustomerRef(result.customerRef);
|
|
490
|
-
}
|
|
491
|
-
const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
|
|
492
|
-
const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
|
|
493
|
-
let stripe = stripePromiseCache.get(cacheKey);
|
|
494
|
-
if (!stripe) {
|
|
495
|
-
stripe = loadStripe(result.publishableKey, stripeOptions);
|
|
496
|
-
stripePromiseCache.set(cacheKey, stripe);
|
|
497
|
-
}
|
|
498
|
-
setStripePromise(stripe);
|
|
499
|
-
setClientSecret(result.clientSecret);
|
|
500
|
-
} catch (err) {
|
|
501
|
-
const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
|
|
502
|
-
setError(error2);
|
|
503
|
-
} finally {
|
|
504
|
-
setLoading(false);
|
|
505
|
-
isStartingRef.current = false;
|
|
506
|
-
}
|
|
507
|
-
}, [planRef, productRef, createPayment, updateCustomerRef, loading]);
|
|
508
|
-
const reset = useCallback2(() => {
|
|
509
|
-
isStartingRef.current = false;
|
|
510
|
-
setLoading(false);
|
|
511
|
-
setError(null);
|
|
512
|
-
setStripePromise(null);
|
|
513
|
-
setClientSecret(null);
|
|
514
|
-
}, []);
|
|
515
|
-
return {
|
|
516
|
-
loading,
|
|
517
|
-
error,
|
|
518
|
-
stripePromise,
|
|
519
|
-
clientSecret,
|
|
520
|
-
startCheckout,
|
|
521
|
-
reset
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// src/hooks/usePurchase.ts
|
|
526
|
-
function usePurchase() {
|
|
527
|
-
const { purchase, refetchPurchase } = useSolvaPay();
|
|
528
|
-
return {
|
|
529
|
-
...purchase,
|
|
530
|
-
refetch: refetchPurchase
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// src/components/Spinner.tsx
|
|
535
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
536
|
-
var Spinner = ({
|
|
537
|
-
className = "",
|
|
538
|
-
size = "md"
|
|
539
|
-
}) => {
|
|
540
|
-
const sizeClasses = {
|
|
541
|
-
sm: "h-4 w-4",
|
|
542
|
-
md: "h-5 w-5",
|
|
543
|
-
lg: "h-6 w-6"
|
|
544
|
-
};
|
|
545
|
-
return /* @__PURE__ */ jsxs(
|
|
546
|
-
"svg",
|
|
547
|
-
{
|
|
548
|
-
className: `animate-spin ${sizeClasses[size]} ${className}`,
|
|
549
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
550
|
-
fill: "none",
|
|
551
|
-
viewBox: "0 0 24 24",
|
|
552
|
-
role: "status",
|
|
553
|
-
"aria-busy": "true",
|
|
554
|
-
children: [
|
|
555
|
-
/* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
556
|
-
/* @__PURE__ */ jsx2(
|
|
557
|
-
"path",
|
|
558
|
-
{
|
|
559
|
-
className: "opacity-75",
|
|
560
|
-
fill: "currentColor",
|
|
561
|
-
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
562
|
-
}
|
|
563
|
-
)
|
|
564
|
-
]
|
|
565
|
-
}
|
|
566
|
-
);
|
|
73
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
74
|
+
var DefaultTree = ({
|
|
75
|
+
requireTermsAcceptance
|
|
76
|
+
}) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
77
|
+
/* @__PURE__ */ jsx(PaymentForm.Summary, {}),
|
|
78
|
+
/* @__PURE__ */ jsx(PaymentForm.CustomerFields, {}),
|
|
79
|
+
/* @__PURE__ */ jsx(PaymentForm.PaymentElement, {}),
|
|
80
|
+
/* @__PURE__ */ jsx(PaymentForm.Error, {}),
|
|
81
|
+
/* @__PURE__ */ jsx(PaymentForm.MandateText, {}),
|
|
82
|
+
requireTermsAcceptance && /* @__PURE__ */ jsx(PaymentForm.TermsCheckbox, {}),
|
|
83
|
+
/* @__PURE__ */ jsx(PaymentForm.SubmitButton, {})
|
|
84
|
+
] });
|
|
85
|
+
var PaymentFormBase = (props) => {
|
|
86
|
+
const { children, requireTermsAcceptance = false } = props;
|
|
87
|
+
return /* @__PURE__ */ jsx(PaymentForm.Root, { ...props, children: children ?? /* @__PURE__ */ jsx(DefaultTree, { requireTermsAcceptance }) });
|
|
567
88
|
};
|
|
89
|
+
var PaymentForm2 = Object.assign(PaymentFormBase, {
|
|
90
|
+
Summary: PaymentFormSummary,
|
|
91
|
+
CustomerFields: PaymentFormCustomerFields,
|
|
92
|
+
PaymentElement: PaymentFormPaymentElement,
|
|
93
|
+
CardElement: PaymentFormCardElement,
|
|
94
|
+
MandateText: PaymentFormMandateText,
|
|
95
|
+
TermsCheckbox: PaymentFormTermsCheckbox,
|
|
96
|
+
SubmitButton: PaymentFormSubmitButton,
|
|
97
|
+
Loading: PaymentFormLoading,
|
|
98
|
+
Error: PaymentFormError
|
|
99
|
+
});
|
|
568
100
|
|
|
569
101
|
// src/components/StripePaymentFormWrapper.tsx
|
|
570
|
-
import { useState
|
|
102
|
+
import { useState } from "react";
|
|
571
103
|
import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
|
|
572
|
-
|
|
573
|
-
// src/hooks/useCustomer.ts
|
|
574
|
-
function useCustomer() {
|
|
575
|
-
const { purchase, customerRef } = useSolvaPay();
|
|
576
|
-
return {
|
|
577
|
-
customerRef: purchase.customerRef || customerRef,
|
|
578
|
-
email: purchase.email,
|
|
579
|
-
name: purchase.name,
|
|
580
|
-
loading: purchase.loading
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/components/StripePaymentFormWrapper.tsx
|
|
585
|
-
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
104
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
586
105
|
var StripePaymentFormWrapper = ({
|
|
587
106
|
onSuccess,
|
|
588
107
|
onError,
|
|
589
108
|
returnUrl: _returnUrl,
|
|
590
|
-
submitButtonText
|
|
109
|
+
submitButtonText,
|
|
591
110
|
buttonClassName,
|
|
592
111
|
clientSecret
|
|
593
112
|
}) => {
|
|
594
113
|
const stripe = useStripe();
|
|
595
114
|
const elements = useElements();
|
|
596
115
|
const customer = useCustomer();
|
|
597
|
-
const
|
|
598
|
-
const
|
|
599
|
-
const [
|
|
116
|
+
const copy = useCopy();
|
|
117
|
+
const effectiveSubmitText = submitButtonText ?? copy.cta.payNow;
|
|
118
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
119
|
+
const [message, setMessage] = useState(null);
|
|
120
|
+
const [cardComplete, setCardComplete] = useState(false);
|
|
600
121
|
const handleSubmit = async (event) => {
|
|
601
122
|
event.preventDefault();
|
|
602
123
|
if (!stripe || !elements) {
|
|
603
|
-
const errorMessage =
|
|
124
|
+
const errorMessage = copy.errors.stripeUnavailable;
|
|
604
125
|
setMessage(errorMessage);
|
|
605
126
|
if (onError) {
|
|
606
127
|
onError(new Error(errorMessage));
|
|
@@ -608,7 +129,7 @@ var StripePaymentFormWrapper = ({
|
|
|
608
129
|
return;
|
|
609
130
|
}
|
|
610
131
|
if (!clientSecret) {
|
|
611
|
-
const errorMessage =
|
|
132
|
+
const errorMessage = copy.errors.paymentIntentUnavailable;
|
|
612
133
|
setMessage(errorMessage);
|
|
613
134
|
if (onError) {
|
|
614
135
|
onError(new Error(errorMessage));
|
|
@@ -620,7 +141,7 @@ var StripePaymentFormWrapper = ({
|
|
|
620
141
|
try {
|
|
621
142
|
const cardElement = elements.getElement(CardElement);
|
|
622
143
|
if (!cardElement) {
|
|
623
|
-
const errorMessage =
|
|
144
|
+
const errorMessage = copy.errors.cardElementMissing;
|
|
624
145
|
setMessage(errorMessage);
|
|
625
146
|
setIsProcessing(false);
|
|
626
147
|
if (onError) {
|
|
@@ -638,7 +159,7 @@ var StripePaymentFormWrapper = ({
|
|
|
638
159
|
}
|
|
639
160
|
});
|
|
640
161
|
if (error) {
|
|
641
|
-
const errorMessage = error.message ||
|
|
162
|
+
const errorMessage = error.message || copy.errors.paymentUnexpected;
|
|
642
163
|
setMessage(errorMessage);
|
|
643
164
|
setIsProcessing(false);
|
|
644
165
|
if (onError) {
|
|
@@ -651,8 +172,8 @@ var StripePaymentFormWrapper = ({
|
|
|
651
172
|
await onSuccess(paymentIntent);
|
|
652
173
|
setMessage("Payment successful!");
|
|
653
174
|
} catch (err) {
|
|
654
|
-
const error2 = err instanceof Error ? err : new Error(
|
|
655
|
-
setMessage(
|
|
175
|
+
const error2 = err instanceof Error ? err : new Error(copy.errors.paymentProcessingFailed);
|
|
176
|
+
setMessage(copy.errors.paymentProcessingFailed);
|
|
656
177
|
setIsProcessing(false);
|
|
657
178
|
if (onError) {
|
|
658
179
|
onError(error2);
|
|
@@ -663,14 +184,18 @@ var StripePaymentFormWrapper = ({
|
|
|
663
184
|
setMessage("Payment successful!");
|
|
664
185
|
}
|
|
665
186
|
} else if (paymentIntent && paymentIntent.status === "requires_action") {
|
|
666
|
-
setMessage(
|
|
187
|
+
setMessage(copy.errors.paymentRequires3ds);
|
|
667
188
|
setIsProcessing(false);
|
|
668
189
|
} else if (paymentIntent) {
|
|
669
|
-
setMessage(
|
|
190
|
+
setMessage(
|
|
191
|
+
interpolate(copy.errors.paymentStatusPrefix, {
|
|
192
|
+
status: paymentIntent.status || "processing"
|
|
193
|
+
})
|
|
194
|
+
);
|
|
670
195
|
setIsProcessing(false);
|
|
671
196
|
}
|
|
672
197
|
} catch (err) {
|
|
673
|
-
const error = err instanceof Error ? err : new Error(
|
|
198
|
+
const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
|
|
674
199
|
setMessage(error.message);
|
|
675
200
|
if (onError) {
|
|
676
201
|
onError(error);
|
|
@@ -684,20 +209,22 @@ var StripePaymentFormWrapper = ({
|
|
|
684
209
|
const cardElementOptions = {
|
|
685
210
|
style: {
|
|
686
211
|
base: {
|
|
687
|
-
fontSize: "
|
|
688
|
-
|
|
212
|
+
fontSize: "17px",
|
|
213
|
+
lineHeight: "24px",
|
|
214
|
+
color: "#1e293b",
|
|
215
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
689
216
|
"::placeholder": {
|
|
690
|
-
color: "#
|
|
217
|
+
color: "#94a3b8"
|
|
691
218
|
}
|
|
692
219
|
},
|
|
693
220
|
invalid: {
|
|
694
|
-
color: "#
|
|
221
|
+
color: "#dc2626"
|
|
695
222
|
}
|
|
696
223
|
},
|
|
697
224
|
hidePostalCode: true
|
|
698
225
|
};
|
|
699
|
-
return /* @__PURE__ */ jsxs2(
|
|
700
|
-
isReady ? /* @__PURE__ */
|
|
226
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
227
|
+
isReady ? /* @__PURE__ */ jsx2(
|
|
701
228
|
CardElement,
|
|
702
229
|
{
|
|
703
230
|
options: cardElementOptions,
|
|
@@ -711,24 +238,9 @@ var StripePaymentFormWrapper = ({
|
|
|
711
238
|
}
|
|
712
239
|
}
|
|
713
240
|
}
|
|
714
|
-
) : /* @__PURE__ */
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
style: {
|
|
718
|
-
padding: "12px",
|
|
719
|
-
border: "1px solid #cbd5e1",
|
|
720
|
-
borderRadius: "0.375rem",
|
|
721
|
-
backgroundColor: "white",
|
|
722
|
-
display: "flex",
|
|
723
|
-
alignItems: "center",
|
|
724
|
-
justifyContent: "center",
|
|
725
|
-
minHeight: "40px"
|
|
726
|
-
},
|
|
727
|
-
children: /* @__PURE__ */ jsx3(Spinner, { size: "sm" })
|
|
728
|
-
}
|
|
729
|
-
),
|
|
730
|
-
message && !isSuccess && /* @__PURE__ */ jsx3("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
731
|
-
/* @__PURE__ */ jsx3(
|
|
241
|
+
) : /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ jsx2(Spinner, { size: "sm" }) }),
|
|
242
|
+
message && !isSuccess && /* @__PURE__ */ jsx2("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
243
|
+
/* @__PURE__ */ jsx2(
|
|
732
244
|
"button",
|
|
733
245
|
{
|
|
734
246
|
type: "submit",
|
|
@@ -737,488 +249,538 @@ var StripePaymentFormWrapper = ({
|
|
|
737
249
|
"aria-busy": isProcessing,
|
|
738
250
|
"aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
|
|
739
251
|
onClick: handleSubmit,
|
|
740
|
-
children: isProcessing ? /* @__PURE__ */ jsxs2(
|
|
741
|
-
/* @__PURE__ */
|
|
742
|
-
/* @__PURE__ */
|
|
743
|
-
] }) :
|
|
252
|
+
children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
253
|
+
/* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
|
|
254
|
+
/* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
|
|
255
|
+
] }) : effectiveSubmitText
|
|
744
256
|
}
|
|
745
257
|
)
|
|
746
258
|
] });
|
|
747
259
|
};
|
|
748
260
|
|
|
749
|
-
// src/
|
|
750
|
-
import {
|
|
751
|
-
|
|
261
|
+
// src/components/CheckoutLayout.tsx
|
|
262
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState as useState2 } from "react";
|
|
263
|
+
|
|
264
|
+
// src/components/PlanSelector.tsx
|
|
265
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
266
|
+
var DefaultTree2 = () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
267
|
+
/* @__PURE__ */ jsx3(PlanSelector.Heading, { className: "solvapay-plan-selector-heading" }),
|
|
268
|
+
/* @__PURE__ */ jsx3(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ jsxs3(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
|
|
269
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
|
|
270
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
|
|
271
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardPrice, { className: "solvapay-plan-selector-card-price" }),
|
|
272
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardInterval, { className: "solvapay-plan-selector-card-interval" })
|
|
273
|
+
] }) }),
|
|
274
|
+
/* @__PURE__ */ jsx3(PlanSelector.Loading, { className: "solvapay-plan-selector-loading" }),
|
|
275
|
+
/* @__PURE__ */ jsx3(PlanSelector.Error, { className: "solvapay-plan-selector-error" })
|
|
276
|
+
] });
|
|
277
|
+
var PlanSelector2 = (props) => {
|
|
278
|
+
const { children, className, ...rootProps } = props;
|
|
279
|
+
const rootClass = ["solvapay-plan-selector", className].filter(Boolean).join(" ");
|
|
280
|
+
return /* @__PURE__ */ jsx3(PlanSelector.Root, { ...rootProps, className: rootClass, children: children ?? /* @__PURE__ */ jsx3(DefaultTree2, {}) });
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/components/ActivationFlow.tsx
|
|
284
|
+
import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
285
|
+
var ActivationFlow2 = (props) => {
|
|
286
|
+
const { className, onBack, ...rootProps } = props;
|
|
287
|
+
const rootClass = ["solvapay-activation-flow", className].filter(Boolean).join(" ");
|
|
288
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Root, { ...rootProps, className: rootClass, children: [
|
|
289
|
+
/* @__PURE__ */ jsx4(SummaryStep, {}),
|
|
290
|
+
/* @__PURE__ */ jsx4(AmountStep, {}),
|
|
291
|
+
/* @__PURE__ */ jsx4(TopupPaymentStep, {}),
|
|
292
|
+
/* @__PURE__ */ jsx4(RetryingStep, {}),
|
|
293
|
+
/* @__PURE__ */ jsx4(ActivatedStep, {}),
|
|
294
|
+
/* @__PURE__ */ jsx4(ErrorStep, {}),
|
|
295
|
+
onBack && /* @__PURE__ */ jsx4(BackButton, { onBack })
|
|
296
|
+
] });
|
|
297
|
+
};
|
|
298
|
+
var SummaryStep = () => {
|
|
299
|
+
const copy = useCopy();
|
|
300
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Summary, { className: "solvapay-activation-flow-summary", children: [
|
|
301
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.heading }),
|
|
302
|
+
/* @__PURE__ */ jsx4(CheckoutSummary, {}),
|
|
303
|
+
/* @__PURE__ */ jsx4(ActivationFlow.ActivateButton, { className: "solvapay-activation-flow-activate" })
|
|
304
|
+
] });
|
|
305
|
+
};
|
|
306
|
+
var AmountStep = () => {
|
|
307
|
+
const copy = useCopy();
|
|
308
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.AmountPicker, { children: [
|
|
309
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-topup-heading", children: copy.activationFlow.topupHeading }),
|
|
310
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-activation-flow-topup-subheading", children: copy.activationFlow.topupSubheading }),
|
|
311
|
+
/* @__PURE__ */ jsx4(AmountPickerBody, {}),
|
|
312
|
+
/* @__PURE__ */ jsx4(ActivationFlow.ContinueButton, { className: "solvapay-activation-flow-continue" })
|
|
313
|
+
] });
|
|
314
|
+
};
|
|
315
|
+
var AmountPickerBody = () => {
|
|
316
|
+
const ctx = useAmountPicker();
|
|
317
|
+
const { selectAmountLabel, customAmountLabel } = useAmountPickerCopy();
|
|
318
|
+
return /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
319
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
|
|
320
|
+
/* @__PURE__ */ jsx4("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx4(
|
|
321
|
+
AmountPicker.Option,
|
|
322
|
+
{
|
|
323
|
+
amount,
|
|
324
|
+
className: "solvapay-amount-picker-pill"
|
|
325
|
+
},
|
|
326
|
+
amount
|
|
327
|
+
)) }),
|
|
328
|
+
/* @__PURE__ */ jsxs4("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
|
|
329
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
|
|
330
|
+
/* @__PURE__ */ jsx4(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
|
|
331
|
+
] })
|
|
332
|
+
] });
|
|
333
|
+
};
|
|
334
|
+
var TopupPaymentStep = () => {
|
|
335
|
+
const ctx = useActivationFlow();
|
|
336
|
+
const copy = useCopy();
|
|
337
|
+
if (ctx.step !== "topupPayment") return null;
|
|
338
|
+
return /* @__PURE__ */ jsxs4("div", { "data-solvapay-activation-flow-topup-payment": "", children: [
|
|
339
|
+
/* @__PURE__ */ jsx4(
|
|
340
|
+
"button",
|
|
341
|
+
{
|
|
342
|
+
type: "button",
|
|
343
|
+
onClick: ctx.backToSelectAmount,
|
|
344
|
+
className: "solvapay-activation-flow-change-amount",
|
|
345
|
+
children: copy.activationFlow.changeAmountButton
|
|
346
|
+
}
|
|
347
|
+
),
|
|
348
|
+
/* @__PURE__ */ jsx4(
|
|
349
|
+
TopupForm,
|
|
350
|
+
{
|
|
351
|
+
amount: ctx.amountCents,
|
|
352
|
+
currency: ctx.currency,
|
|
353
|
+
onSuccess: ctx.onTopupSuccess,
|
|
354
|
+
onError: (err) => ctx.onError?.(err instanceof Error ? err : new Error(String(err)))
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
] });
|
|
358
|
+
};
|
|
359
|
+
var RetryingStep = () => {
|
|
360
|
+
const copy = useCopy();
|
|
361
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Retrying, { className: "solvapay-activation-flow-retrying", children: [
|
|
362
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.retryingHeading }),
|
|
363
|
+
/* @__PURE__ */ jsx4("p", { children: copy.activationFlow.retryingSubheading })
|
|
364
|
+
] });
|
|
365
|
+
};
|
|
366
|
+
var ActivatedStep = () => {
|
|
367
|
+
const copy = useCopy();
|
|
368
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Activated, { className: "solvapay-activation-flow-activated", children: [
|
|
369
|
+
/* @__PURE__ */ jsx4("h3", { children: copy.activationFlow.activatedHeading }),
|
|
370
|
+
/* @__PURE__ */ jsx4("p", { children: copy.activationFlow.activatedSubheading })
|
|
371
|
+
] });
|
|
372
|
+
};
|
|
373
|
+
var ErrorStep = () => {
|
|
374
|
+
const ctx = useActivationFlow();
|
|
375
|
+
const copy = useCopy();
|
|
376
|
+
if (ctx.step !== "error") return null;
|
|
377
|
+
return /* @__PURE__ */ jsxs4("div", { className: "solvapay-activation-flow-error", role: "alert", children: [
|
|
378
|
+
/* @__PURE__ */ jsx4("p", { children: ctx.error }),
|
|
379
|
+
/* @__PURE__ */ jsx4(
|
|
380
|
+
"button",
|
|
381
|
+
{
|
|
382
|
+
type: "button",
|
|
383
|
+
onClick: ctx.reset,
|
|
384
|
+
className: "solvapay-activation-flow-try-again",
|
|
385
|
+
children: copy.activationFlow.tryAgainButton
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
] });
|
|
389
|
+
};
|
|
390
|
+
var BackButton = ({ onBack }) => {
|
|
391
|
+
const ctx = useActivationFlow();
|
|
392
|
+
const copy = useCopy();
|
|
393
|
+
if (ctx.step !== "summary") return null;
|
|
394
|
+
return /* @__PURE__ */ jsx4(
|
|
395
|
+
"button",
|
|
396
|
+
{
|
|
397
|
+
type: "button",
|
|
398
|
+
onClick: onBack,
|
|
399
|
+
className: "solvapay-activation-flow-back",
|
|
400
|
+
children: copy.activationFlow.backButton
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/components/CheckoutLayout.tsx
|
|
406
|
+
import { Fragment as Fragment5, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
407
|
+
var BREAKPOINTS = {
|
|
408
|
+
chat: 480,
|
|
409
|
+
desktop: 768
|
|
410
|
+
};
|
|
411
|
+
function widthToSize(width) {
|
|
412
|
+
if (width < BREAKPOINTS.chat) return "chat";
|
|
413
|
+
if (width < BREAKPOINTS.desktop) return "mobile";
|
|
414
|
+
return "desktop";
|
|
415
|
+
}
|
|
416
|
+
var CheckoutLayout = ({
|
|
752
417
|
planRef,
|
|
753
418
|
productRef,
|
|
419
|
+
prefillCustomer,
|
|
420
|
+
size = "auto",
|
|
421
|
+
requireTermsAcceptance,
|
|
754
422
|
onSuccess,
|
|
423
|
+
onResult,
|
|
424
|
+
onFreePlan,
|
|
755
425
|
onError,
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
426
|
+
initialPlanRef,
|
|
427
|
+
onPlanSelect,
|
|
428
|
+
planSelector,
|
|
429
|
+
showBackButton = true,
|
|
430
|
+
submitButtonText,
|
|
431
|
+
returnUrl
|
|
760
432
|
}) => {
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const hasInitializedRef = useRef3(false);
|
|
772
|
-
useEffect2(() => {
|
|
773
|
-
if (!hasInitializedRef.current && validPlanRef && !checkoutLoading && !checkoutError && !clientSecret) {
|
|
774
|
-
hasInitializedRef.current = true;
|
|
775
|
-
startCheckout().catch(() => {
|
|
776
|
-
hasInitializedRef.current = false;
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
if (validPlanRef && clientSecret) {
|
|
780
|
-
hasInitializedRef.current = true;
|
|
781
|
-
}
|
|
782
|
-
}, [validPlanRef, checkoutLoading, checkoutError, clientSecret, startCheckout]);
|
|
783
|
-
const handleSuccess = useCallback3(
|
|
784
|
-
async (paymentIntent) => {
|
|
785
|
-
let processingTimeout = false;
|
|
786
|
-
let processingResult = null;
|
|
787
|
-
const paymentIntentAny = paymentIntent;
|
|
788
|
-
if (processPayment && productRef) {
|
|
789
|
-
try {
|
|
790
|
-
const result = await processPayment({
|
|
791
|
-
paymentIntentId: paymentIntentAny.id,
|
|
792
|
-
productRef,
|
|
793
|
-
planRef
|
|
794
|
-
});
|
|
795
|
-
processingResult = result;
|
|
796
|
-
const isTimeout = result?.status === "timeout";
|
|
797
|
-
processingTimeout = isTimeout;
|
|
798
|
-
if (isTimeout) {
|
|
799
|
-
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
800
|
-
const delay = attempt * 1e3;
|
|
801
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
802
|
-
await refetch();
|
|
803
|
-
}
|
|
804
|
-
if (onSuccess) {
|
|
805
|
-
await onSuccess({
|
|
806
|
-
...paymentIntentAny,
|
|
807
|
-
_processingTimeout: processingTimeout,
|
|
808
|
-
_processingResult: processingResult
|
|
809
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
throw new Error("Payment processing timed out");
|
|
813
|
-
} else {
|
|
814
|
-
await refetch();
|
|
815
|
-
}
|
|
816
|
-
} catch (error) {
|
|
817
|
-
console.error("[PaymentForm] Failed to process payment:", error);
|
|
818
|
-
if (onSuccess) {
|
|
819
|
-
try {
|
|
820
|
-
await onSuccess({
|
|
821
|
-
...paymentIntentAny,
|
|
822
|
-
_processingError: error
|
|
823
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
824
|
-
});
|
|
825
|
-
} catch {
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
throw error;
|
|
829
|
-
}
|
|
830
|
-
} else {
|
|
831
|
-
await refetch();
|
|
832
|
-
}
|
|
833
|
-
if (onSuccess && !processingTimeout) {
|
|
834
|
-
await onSuccess({
|
|
835
|
-
...paymentIntentAny,
|
|
836
|
-
_processingTimeout: processingTimeout,
|
|
837
|
-
_processingResult: processingResult
|
|
838
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
839
|
-
});
|
|
433
|
+
const rootRef = useRef(null);
|
|
434
|
+
const [autoSize, setAutoSize] = useState2("desktop");
|
|
435
|
+
const copy = useCopy();
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (size !== "auto") return;
|
|
438
|
+
const el = rootRef.current;
|
|
439
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
440
|
+
const ro = new ResizeObserver((entries) => {
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
setAutoSize(widthToSize(entry.contentRect.width));
|
|
840
443
|
}
|
|
841
|
-
}
|
|
842
|
-
|
|
444
|
+
});
|
|
445
|
+
ro.observe(el);
|
|
446
|
+
return () => ro.disconnect();
|
|
447
|
+
}, [size]);
|
|
448
|
+
const resolvedSize = size === "auto" ? autoSize : size;
|
|
449
|
+
const [selectedPlanRef, setSelectedPlanRef] = useState2(
|
|
450
|
+
planRef ?? initialPlanRef ?? null
|
|
843
451
|
);
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
452
|
+
const [userStep, setUserStep] = useState2(
|
|
453
|
+
planRef ? "pay" : selectedPlanRef ? "pay" : "select"
|
|
454
|
+
);
|
|
455
|
+
const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
|
|
456
|
+
const { plan: resolvedPlan } = usePlan({
|
|
457
|
+
planRef: effectivePlanRef,
|
|
458
|
+
productRef
|
|
459
|
+
});
|
|
460
|
+
const isUsageBased = resolvedPlan?.type === "usage-based";
|
|
461
|
+
const step = useMemo(() => {
|
|
462
|
+
if (userStep === "pay" && isUsageBased) return "activate";
|
|
463
|
+
if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
|
|
464
|
+
return userStep;
|
|
465
|
+
}, [userStep, isUsageBased, resolvedPlan]);
|
|
466
|
+
const handlePlanSelect = useCallback(
|
|
467
|
+
(ref, plan) => {
|
|
468
|
+
setSelectedPlanRef(ref);
|
|
469
|
+
onPlanSelect?.(ref, plan);
|
|
470
|
+
setUserStep(plan.type === "usage-based" ? "activate" : "pay");
|
|
849
471
|
},
|
|
850
|
-
[
|
|
472
|
+
[onPlanSelect]
|
|
851
473
|
);
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
474
|
+
const handleBack = useCallback(() => {
|
|
475
|
+
if (planRef) return;
|
|
476
|
+
setUserStep("select");
|
|
477
|
+
}, [planRef]);
|
|
478
|
+
return /* @__PURE__ */ jsxs5(
|
|
479
|
+
"div",
|
|
480
|
+
{
|
|
481
|
+
ref: rootRef,
|
|
482
|
+
"data-solvapay-checkout-layout": resolvedSize,
|
|
483
|
+
"data-solvapay-step": step,
|
|
484
|
+
className: "solvapay-checkout-layout",
|
|
485
|
+
children: [
|
|
486
|
+
!planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
|
|
487
|
+
"button",
|
|
488
|
+
{
|
|
489
|
+
type: "button",
|
|
490
|
+
onClick: handleBack,
|
|
491
|
+
"data-solvapay-checkout-back": "",
|
|
492
|
+
className: "solvapay-back",
|
|
493
|
+
children: copy.planSelector.backButton
|
|
494
|
+
}
|
|
495
|
+
),
|
|
496
|
+
step === "select" && /* @__PURE__ */ jsx5(
|
|
497
|
+
SelectStep,
|
|
498
|
+
{
|
|
499
|
+
productRef: productRef ?? "",
|
|
500
|
+
initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
|
|
501
|
+
filter: planSelector?.filter,
|
|
502
|
+
sortBy: planSelector?.sortBy,
|
|
503
|
+
popularPlanRef: planSelector?.popularPlanRef,
|
|
504
|
+
onContinue: handlePlanSelect
|
|
505
|
+
}
|
|
506
|
+
),
|
|
507
|
+
step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
|
|
508
|
+
ActivationFlow2,
|
|
509
|
+
{
|
|
510
|
+
productRef,
|
|
511
|
+
planRef: effectivePlanRef,
|
|
512
|
+
onBack: planRef ? void 0 : handleBack,
|
|
513
|
+
onSuccess: (result) => onResult?.(result),
|
|
514
|
+
onError
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
step === "pay" && /* @__PURE__ */ jsx5(
|
|
518
|
+
PaymentForm2,
|
|
880
519
|
{
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
520
|
+
planRef: effectivePlanRef,
|
|
521
|
+
productRef,
|
|
522
|
+
prefillCustomer,
|
|
523
|
+
requireTermsAcceptance,
|
|
524
|
+
onSuccess,
|
|
525
|
+
onResult,
|
|
526
|
+
onFreePlan,
|
|
527
|
+
onError,
|
|
884
528
|
submitButtonText,
|
|
885
|
-
|
|
886
|
-
clientSecret
|
|
529
|
+
returnUrl
|
|
887
530
|
}
|
|
888
531
|
)
|
|
889
|
-
|
|
890
|
-
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
async function defaultListPlans(productRef, config) {
|
|
537
|
+
const base = config?.api?.listPlans || "/api/list-plans";
|
|
538
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
539
|
+
const fetchFn = config?.fetch || fetch;
|
|
540
|
+
const { headers } = await buildRequestHeaders(config);
|
|
541
|
+
const res = await fetchFn(url, { method: "GET", headers });
|
|
542
|
+
if (!res.ok) {
|
|
543
|
+
const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
|
|
544
|
+
config?.onError?.(error, "listPlans");
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
const data = await res.json();
|
|
548
|
+
return data.plans ?? [];
|
|
549
|
+
}
|
|
550
|
+
var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
|
|
551
|
+
const copy = useCopy();
|
|
552
|
+
const solva = useContext(SolvaPayContext);
|
|
553
|
+
const config = solva?._config;
|
|
554
|
+
const fetcher = useCallback(
|
|
555
|
+
(ref) => defaultListPlans(ref, config),
|
|
556
|
+
[config]
|
|
557
|
+
);
|
|
558
|
+
const { plans, selectedPlan } = usePlans({
|
|
559
|
+
productRef,
|
|
560
|
+
fetcher,
|
|
561
|
+
filter,
|
|
562
|
+
sortBy,
|
|
563
|
+
autoSelectFirstPaid: true,
|
|
564
|
+
initialPlanRef
|
|
565
|
+
});
|
|
566
|
+
const [userSelected, setUserSelected] = useState2(null);
|
|
567
|
+
const selected = userSelected ?? (selectedPlan && selectedPlan.requiresPayment !== false ? { ref: selectedPlan.reference, plan: selectedPlan } : null);
|
|
568
|
+
const autoSkipRef = useRef(false);
|
|
569
|
+
useEffect(() => {
|
|
570
|
+
if (autoSkipRef.current) return;
|
|
571
|
+
const selectable = plans.filter((p) => p.requiresPayment !== false);
|
|
572
|
+
if (plans.length === 1 && selectable.length === 1) {
|
|
573
|
+
autoSkipRef.current = true;
|
|
574
|
+
onContinue(selectable[0].reference, selectable[0]);
|
|
575
|
+
}
|
|
576
|
+
}, [plans, onContinue]);
|
|
577
|
+
const handleSelect = useCallback((ref, plan) => {
|
|
578
|
+
setUserSelected({ ref, plan });
|
|
579
|
+
}, []);
|
|
580
|
+
const continueLabel = useMemo(() => copy.planSelector.continueButton, [copy]);
|
|
581
|
+
return /* @__PURE__ */ jsxs5(Fragment5, { children: [
|
|
582
|
+
/* @__PURE__ */ jsx5(
|
|
583
|
+
PlanSelector2,
|
|
584
|
+
{
|
|
585
|
+
productRef,
|
|
586
|
+
initialPlanRef,
|
|
587
|
+
filter,
|
|
588
|
+
sortBy,
|
|
589
|
+
popularPlanRef,
|
|
590
|
+
autoSelectFirstPaid: true,
|
|
591
|
+
onSelect: handleSelect
|
|
592
|
+
}
|
|
593
|
+
),
|
|
594
|
+
/* @__PURE__ */ jsx5(
|
|
595
|
+
"button",
|
|
596
|
+
{
|
|
597
|
+
type: "button",
|
|
598
|
+
className: "solvapay-continue",
|
|
599
|
+
"data-solvapay-checkout-continue": "",
|
|
600
|
+
"data-state": selected ? "idle" : "disabled",
|
|
601
|
+
disabled: !selected,
|
|
602
|
+
onClick: () => {
|
|
603
|
+
if (selected) onContinue(selected.ref, selected.plan);
|
|
604
|
+
},
|
|
605
|
+
children: continueLabel
|
|
606
|
+
}
|
|
891
607
|
)
|
|
892
|
-
)
|
|
893
|
-
// Loading state before Stripe data is available
|
|
894
|
-
/* @__PURE__ */ jsxs3("div", { children: [
|
|
895
|
-
/* @__PURE__ */ jsx4(
|
|
896
|
-
"div",
|
|
897
|
-
{
|
|
898
|
-
style: {
|
|
899
|
-
minHeight: "40px",
|
|
900
|
-
display: "flex",
|
|
901
|
-
alignItems: "center",
|
|
902
|
-
justifyContent: "center"
|
|
903
|
-
},
|
|
904
|
-
children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
|
|
905
|
-
}
|
|
906
|
-
),
|
|
907
|
-
/* @__PURE__ */ jsx4(
|
|
908
|
-
"button",
|
|
909
|
-
{
|
|
910
|
-
type: "submit",
|
|
911
|
-
disabled: true,
|
|
912
|
-
className: buttonClassName,
|
|
913
|
-
"aria-busy": "false",
|
|
914
|
-
"aria-disabled": "true",
|
|
915
|
-
children: submitButtonText
|
|
916
|
-
}
|
|
917
|
-
)
|
|
918
|
-
] })
|
|
919
|
-
) });
|
|
608
|
+
] });
|
|
920
609
|
};
|
|
921
610
|
|
|
922
|
-
// src/components/
|
|
923
|
-
import
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
611
|
+
// src/components/AmountPicker.tsx
|
|
612
|
+
import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
613
|
+
var AmountPicker2 = ({
|
|
614
|
+
currency,
|
|
615
|
+
minAmount,
|
|
616
|
+
maxAmount,
|
|
617
|
+
showCreditEstimate = true,
|
|
618
|
+
onChange,
|
|
928
619
|
className
|
|
929
620
|
}) => {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
if (!loading) {
|
|
934
|
-
setHasLoadedOnce(true);
|
|
935
|
-
}
|
|
936
|
-
}, [loading]);
|
|
937
|
-
const planToDisplay = activePurchase?.productName || null;
|
|
938
|
-
const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
|
|
939
|
-
if (children) {
|
|
940
|
-
return /* @__PURE__ */ jsx5(Fragment2, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
941
|
-
}
|
|
942
|
-
if (!shouldShow) {
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
const computedClassName = typeof className === "function" ? className({ purchases }) : className;
|
|
946
|
-
return /* @__PURE__ */ jsx5(
|
|
947
|
-
Component,
|
|
621
|
+
const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
|
|
622
|
+
return /* @__PURE__ */ jsx6(
|
|
623
|
+
AmountPicker.Root,
|
|
948
624
|
{
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
"aria-busy": loading,
|
|
956
|
-
"aria-label": `Current product: ${planToDisplay}`,
|
|
957
|
-
children: planToDisplay
|
|
625
|
+
currency,
|
|
626
|
+
minAmount,
|
|
627
|
+
maxAmount,
|
|
628
|
+
onChange,
|
|
629
|
+
className: rootClass,
|
|
630
|
+
children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
|
|
958
631
|
}
|
|
959
632
|
);
|
|
960
633
|
};
|
|
961
|
-
var
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
634
|
+
var DefaultTree3 = ({ showCreditEstimate }) => {
|
|
635
|
+
const ctx = useAmountPicker();
|
|
636
|
+
const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
|
|
637
|
+
return /* @__PURE__ */ jsxs6(Fragment6, { children: [
|
|
638
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
|
|
639
|
+
/* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
|
|
640
|
+
AmountPicker.Option,
|
|
641
|
+
{
|
|
642
|
+
amount,
|
|
643
|
+
className: "solvapay-amount-picker-pill"
|
|
644
|
+
},
|
|
645
|
+
amount
|
|
646
|
+
)) }),
|
|
647
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
|
|
648
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
|
|
649
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-row", children: [
|
|
650
|
+
/* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
|
|
651
|
+
/* @__PURE__ */ jsx6(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
|
|
652
|
+
] })
|
|
653
|
+
] }),
|
|
654
|
+
showCreditEstimate && ctx.estimatedCredits != null && /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-credit-estimate", children: creditEstimate(ctx.estimatedCredits) }),
|
|
655
|
+
ctx.error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "solvapay-amount-picker-error", children: ctx.error })
|
|
656
|
+
] });
|
|
974
657
|
};
|
|
975
658
|
|
|
976
|
-
// src/components/
|
|
977
|
-
import {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
}, [autoSelectFirstPaid]);
|
|
1005
|
-
const fetchPlans = useCallback4(
|
|
1006
|
-
async (force = false) => {
|
|
1007
|
-
if (!productRef) {
|
|
1008
|
-
setError(new Error("Product reference not configured"));
|
|
1009
|
-
setLoading(false);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
const cached = plansCache.get(productRef);
|
|
1013
|
-
const now = Date.now();
|
|
1014
|
-
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1015
|
-
const cachedPlans = cached.plans;
|
|
1016
|
-
let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
|
|
1017
|
-
if (sortByRef.current) {
|
|
1018
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1019
|
-
}
|
|
1020
|
-
setPlans(processedPlans);
|
|
1021
|
-
setLoading(false);
|
|
1022
|
-
setError(null);
|
|
1023
|
-
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1024
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1025
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1026
|
-
}
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
if (cached?.promise) {
|
|
1030
|
-
try {
|
|
1031
|
-
setLoading(true);
|
|
1032
|
-
const fetchedPlans = await cached.promise;
|
|
1033
|
-
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1034
|
-
if (sortByRef.current) {
|
|
1035
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1036
|
-
}
|
|
1037
|
-
setPlans(processedPlans);
|
|
1038
|
-
setError(null);
|
|
1039
|
-
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1040
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1041
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1042
|
-
}
|
|
1043
|
-
} catch (err) {
|
|
1044
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1045
|
-
} finally {
|
|
1046
|
-
setLoading(false);
|
|
1047
|
-
}
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
try {
|
|
1051
|
-
setLoading(true);
|
|
1052
|
-
setError(null);
|
|
1053
|
-
const fetchPromise = fetcherRef.current(productRef);
|
|
1054
|
-
plansCache.set(productRef, {
|
|
1055
|
-
plans: [],
|
|
1056
|
-
timestamp: now,
|
|
1057
|
-
promise: fetchPromise
|
|
1058
|
-
});
|
|
1059
|
-
const fetchedPlans = await fetchPromise;
|
|
1060
|
-
plansCache.set(productRef, {
|
|
1061
|
-
plans: fetchedPlans,
|
|
1062
|
-
timestamp: now,
|
|
1063
|
-
promise: null
|
|
1064
|
-
});
|
|
1065
|
-
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1066
|
-
if (sortByRef.current) {
|
|
1067
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1068
|
-
}
|
|
1069
|
-
setPlans(processedPlans);
|
|
1070
|
-
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1071
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1072
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1073
|
-
}
|
|
1074
|
-
} catch (err) {
|
|
1075
|
-
plansCache.delete(productRef);
|
|
1076
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1077
|
-
} finally {
|
|
1078
|
-
setLoading(false);
|
|
1079
|
-
}
|
|
1080
|
-
},
|
|
1081
|
-
[productRef]
|
|
1082
|
-
);
|
|
1083
|
-
useEffect3(() => {
|
|
1084
|
-
fetchPlans();
|
|
1085
|
-
}, [fetchPlans]);
|
|
1086
|
-
const selectedPlan = useMemo3(() => {
|
|
1087
|
-
return plans[selectedPlanIndex] || null;
|
|
1088
|
-
}, [plans, selectedPlanIndex]);
|
|
1089
|
-
const selectPlan = useCallback4(
|
|
1090
|
-
(planRef) => {
|
|
1091
|
-
const index = plans.findIndex((p) => p.reference === planRef);
|
|
1092
|
-
if (index >= 0) {
|
|
1093
|
-
setSelectedPlanIndex(index);
|
|
1094
|
-
}
|
|
1095
|
-
},
|
|
1096
|
-
[plans]
|
|
659
|
+
// src/components/CancelledPlanNotice.tsx
|
|
660
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
661
|
+
var CancelledPlanNotice2 = ({
|
|
662
|
+
onReactivated,
|
|
663
|
+
onError,
|
|
664
|
+
className
|
|
665
|
+
}) => {
|
|
666
|
+
const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
|
|
667
|
+
return /* @__PURE__ */ jsxs7(
|
|
668
|
+
CancelledPlanNotice.Root,
|
|
669
|
+
{
|
|
670
|
+
onReactivated,
|
|
671
|
+
onError,
|
|
672
|
+
className: rootClass,
|
|
673
|
+
children: [
|
|
674
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
|
|
675
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-details", children: [
|
|
676
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
|
|
677
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
|
|
678
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-footer", children: [
|
|
681
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
|
|
682
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
|
|
683
|
+
] }),
|
|
684
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
|
|
685
|
+
]
|
|
686
|
+
}
|
|
1097
687
|
);
|
|
1098
|
-
|
|
1099
|
-
plans,
|
|
1100
|
-
loading,
|
|
1101
|
-
error,
|
|
1102
|
-
selectedPlanIndex,
|
|
1103
|
-
selectedPlan,
|
|
1104
|
-
setSelectedPlanIndex,
|
|
1105
|
-
selectPlan,
|
|
1106
|
-
refetch: () => fetchPlans(true)
|
|
1107
|
-
// Force refetch
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
688
|
+
};
|
|
1110
689
|
|
|
1111
|
-
// src/components/
|
|
1112
|
-
import { Fragment as
|
|
1113
|
-
var
|
|
690
|
+
// src/components/CreditGate.tsx
|
|
691
|
+
import { Fragment as Fragment7, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
692
|
+
var AllowedContent = ({ children }) => {
|
|
693
|
+
const ctx = useCreditGate();
|
|
694
|
+
if (ctx.state !== "allowed") return null;
|
|
695
|
+
return /* @__PURE__ */ jsx8(Fragment7, { children });
|
|
696
|
+
};
|
|
697
|
+
var DefaultBlockedTree = ({ fallback }) => {
|
|
698
|
+
const ctx = useCreditGate();
|
|
699
|
+
if (ctx.state !== "blocked") return null;
|
|
700
|
+
if (fallback) return /* @__PURE__ */ jsx8(Fragment7, { children: fallback });
|
|
701
|
+
return /* @__PURE__ */ jsxs8(Fragment7, { children: [
|
|
702
|
+
/* @__PURE__ */ jsx8(CreditGate.Heading, { className: "solvapay-credit-gate-heading" }),
|
|
703
|
+
/* @__PURE__ */ jsx8(CreditGate.Subheading, { className: "solvapay-credit-gate-subheading" }),
|
|
704
|
+
/* @__PURE__ */ jsx8(CreditGate.Topup, {})
|
|
705
|
+
] });
|
|
706
|
+
};
|
|
707
|
+
var CreditGate2 = ({
|
|
708
|
+
minCredits,
|
|
1114
709
|
productRef,
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
710
|
+
topupAmount,
|
|
711
|
+
topupCurrency,
|
|
712
|
+
fallback,
|
|
713
|
+
className,
|
|
1119
714
|
children
|
|
1120
715
|
}) => {
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
[plans]
|
|
1136
|
-
);
|
|
1137
|
-
const activePurchase = useMemo4(() => {
|
|
1138
|
-
const activePurchases = purchases.filter((p) => p.status === "active");
|
|
1139
|
-
return activePurchases.sort(
|
|
1140
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1141
|
-
)[0] || null;
|
|
1142
|
-
}, [purchases]);
|
|
1143
|
-
const isCurrentPlan = useCallback5(
|
|
1144
|
-
(planRef) => {
|
|
1145
|
-
return activePurchase?.planSnapshot?.reference === planRef;
|
|
1146
|
-
},
|
|
1147
|
-
[activePurchase]
|
|
716
|
+
const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
|
|
717
|
+
return /* @__PURE__ */ jsxs8(
|
|
718
|
+
CreditGate.Root,
|
|
719
|
+
{
|
|
720
|
+
minCredits,
|
|
721
|
+
productRef,
|
|
722
|
+
topupAmount,
|
|
723
|
+
topupCurrency,
|
|
724
|
+
className: rootClass,
|
|
725
|
+
children: [
|
|
726
|
+
/* @__PURE__ */ jsx8(AllowedContent, { children }),
|
|
727
|
+
/* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
|
|
728
|
+
]
|
|
729
|
+
}
|
|
1148
730
|
);
|
|
1149
|
-
return /* @__PURE__ */ jsx7(Fragment4, { children: children({
|
|
1150
|
-
...plansHook,
|
|
1151
|
-
purchases,
|
|
1152
|
-
isPaidPlan,
|
|
1153
|
-
isCurrentPlan
|
|
1154
|
-
}) });
|
|
1155
731
|
};
|
|
1156
|
-
var PlanSelector = PricingSelector;
|
|
1157
|
-
|
|
1158
|
-
// src/hooks/usePurchaseStatus.ts
|
|
1159
|
-
import { useMemo as useMemo5, useCallback as useCallback6 } from "react";
|
|
1160
|
-
function usePurchaseStatus() {
|
|
1161
|
-
const { purchases } = usePurchase();
|
|
1162
|
-
const isPaidPurchase2 = useCallback6((p) => {
|
|
1163
|
-
return (p.amount ?? 0) > 0;
|
|
1164
|
-
}, []);
|
|
1165
|
-
const purchaseData = useMemo5(() => {
|
|
1166
|
-
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
1167
|
-
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
1168
|
-
});
|
|
1169
|
-
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
1170
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1171
|
-
)[0] || null;
|
|
1172
|
-
const shouldShowCancelledNotice = !!cancelledPurchase;
|
|
1173
|
-
return {
|
|
1174
|
-
cancelledPurchase,
|
|
1175
|
-
shouldShowCancelledNotice
|
|
1176
|
-
};
|
|
1177
|
-
}, [purchases, isPaidPurchase2]);
|
|
1178
|
-
const formatDate = useCallback6((dateString) => {
|
|
1179
|
-
if (!dateString) return null;
|
|
1180
|
-
return new Date(dateString).toLocaleDateString("en-US", {
|
|
1181
|
-
year: "numeric",
|
|
1182
|
-
month: "long",
|
|
1183
|
-
day: "numeric"
|
|
1184
|
-
});
|
|
1185
|
-
}, []);
|
|
1186
|
-
const getDaysUntilExpiration = useCallback6((endDate) => {
|
|
1187
|
-
if (!endDate) return null;
|
|
1188
|
-
const now = /* @__PURE__ */ new Date();
|
|
1189
|
-
const expiration = new Date(endDate);
|
|
1190
|
-
const diffTime = expiration.getTime() - now.getTime();
|
|
1191
|
-
const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
|
|
1192
|
-
return diffDays > 0 ? diffDays : 0;
|
|
1193
|
-
}, []);
|
|
1194
|
-
return {
|
|
1195
|
-
cancelledPurchase: purchaseData.cancelledPurchase,
|
|
1196
|
-
shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
|
|
1197
|
-
formatDate,
|
|
1198
|
-
getDaysUntilExpiration
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
732
|
export {
|
|
1202
|
-
|
|
733
|
+
ActivationFlow2 as ActivationFlow,
|
|
734
|
+
AmountPicker2 as AmountPicker,
|
|
735
|
+
BalanceBadge,
|
|
736
|
+
CancelPlanButton,
|
|
737
|
+
CancelledPlanNotice2 as CancelledPlanNotice,
|
|
738
|
+
CheckoutLayout,
|
|
739
|
+
CheckoutSummary,
|
|
740
|
+
CopyContext,
|
|
741
|
+
CopyProvider,
|
|
742
|
+
CreditGate2 as CreditGate,
|
|
743
|
+
MandateText,
|
|
744
|
+
PaymentForm2 as PaymentForm,
|
|
745
|
+
PaymentFormContext,
|
|
746
|
+
PaymentFormProvider,
|
|
1203
747
|
PlanBadge,
|
|
1204
|
-
PlanSelector,
|
|
1205
|
-
PricingSelector,
|
|
748
|
+
PlanSelector2 as PlanSelector,
|
|
1206
749
|
ProductBadge,
|
|
1207
750
|
PurchaseGate,
|
|
1208
751
|
SolvaPayProvider,
|
|
1209
752
|
Spinner,
|
|
1210
753
|
StripePaymentFormWrapper,
|
|
754
|
+
TopupForm,
|
|
755
|
+
confirmPayment,
|
|
1211
756
|
defaultAuthAdapter,
|
|
757
|
+
deriveVariant,
|
|
758
|
+
enCopy,
|
|
1212
759
|
filterPurchases,
|
|
760
|
+
formatPrice,
|
|
1213
761
|
getActivePurchases,
|
|
1214
762
|
getCancelledPurchasesWithEndDate,
|
|
1215
763
|
getMostRecentPurchase,
|
|
1216
764
|
getPrimaryPurchase,
|
|
765
|
+
interpolate,
|
|
1217
766
|
isPaidPurchase,
|
|
767
|
+
mergeCopy,
|
|
768
|
+
resolveCta,
|
|
769
|
+
useActivation,
|
|
770
|
+
useBalance,
|
|
1218
771
|
useCheckout,
|
|
772
|
+
useCopy,
|
|
1219
773
|
useCustomer,
|
|
774
|
+
useLocale,
|
|
775
|
+
useMerchant,
|
|
776
|
+
usePaymentForm,
|
|
777
|
+
usePlan,
|
|
1220
778
|
usePlans,
|
|
779
|
+
useProduct,
|
|
1221
780
|
usePurchase,
|
|
781
|
+
usePurchaseActions,
|
|
1222
782
|
usePurchaseStatus,
|
|
1223
|
-
useSolvaPay
|
|
783
|
+
useSolvaPay,
|
|
784
|
+
useTopup,
|
|
785
|
+
useTopupAmountSelector
|
|
1224
786
|
};
|