@solvapay/react 1.0.7 → 1.0.8-preview.10
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 +4393 -1036
- package/dist/index.d.cts +375 -652
- package/dist/index.d.ts +375 -652
- package/dist/index.js +570 -1665
- 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,820 +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/utils/headers.ts
|
|
39
|
-
var CUSTOMER_REF_KEY = "solvapay_customerRef";
|
|
40
|
-
var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
|
|
41
|
-
var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
|
|
42
|
-
function getAuthAdapter(config) {
|
|
43
|
-
if (config?.auth?.adapter) {
|
|
44
|
-
return config.auth.adapter;
|
|
45
|
-
}
|
|
46
|
-
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
47
|
-
return {
|
|
48
|
-
async getToken() {
|
|
49
|
-
return config?.auth?.getToken?.() || null;
|
|
50
|
-
},
|
|
51
|
-
async getUserId() {
|
|
52
|
-
return config?.auth?.getUserId?.() || null;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
return defaultAuthAdapter;
|
|
57
|
-
}
|
|
58
|
-
function getCachedCustomerRef(userId) {
|
|
59
|
-
if (typeof window === "undefined") return null;
|
|
60
|
-
const cached = localStorage.getItem(CUSTOMER_REF_KEY);
|
|
61
|
-
const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
|
|
62
|
-
const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
|
|
63
|
-
if (!cached || !expiry) return null;
|
|
64
|
-
if (Date.now() > parseInt(expiry)) {
|
|
65
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
66
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
67
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (userId !== void 0 && userId !== null) {
|
|
71
|
-
if (cachedUserId !== userId) {
|
|
72
|
-
clearCachedCustomerRef();
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return cached;
|
|
77
|
-
}
|
|
78
|
-
var CACHE_DURATION = 24 * 60 * 60 * 1e3;
|
|
79
|
-
function setCachedCustomerRef(customerRef, userId) {
|
|
80
|
-
if (typeof window === "undefined") return;
|
|
81
|
-
if (userId === void 0 || userId === null) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
|
|
85
|
-
localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
|
|
86
|
-
localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
|
|
87
|
-
}
|
|
88
|
-
function clearCachedCustomerRef() {
|
|
89
|
-
if (typeof window === "undefined") return;
|
|
90
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
91
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
92
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
93
|
-
}
|
|
94
|
-
async function buildRequestHeaders(config) {
|
|
95
|
-
const adapter = getAuthAdapter(config);
|
|
96
|
-
const token = await adapter.getToken();
|
|
97
|
-
const userId = await adapter.getUserId();
|
|
98
|
-
const cachedRef = getCachedCustomerRef(userId);
|
|
99
|
-
const headers = {
|
|
100
|
-
"Content-Type": "application/json"
|
|
101
|
-
};
|
|
102
|
-
if (token) {
|
|
103
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
104
|
-
}
|
|
105
|
-
if (cachedRef) {
|
|
106
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
107
|
-
}
|
|
108
|
-
if (config?.headers) {
|
|
109
|
-
const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
|
|
110
|
-
Object.assign(headers, custom);
|
|
111
|
-
}
|
|
112
|
-
return { headers, userId };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/SolvaPayProvider.tsx
|
|
116
|
-
import { jsx } from "react/jsx-runtime";
|
|
117
|
-
var SolvaPayContext = createContext(null);
|
|
118
|
-
var SolvaPayProvider = ({
|
|
119
|
-
config,
|
|
120
|
-
createPayment: customCreatePayment,
|
|
121
|
-
checkPurchase: customCheckPurchase,
|
|
122
|
-
processPayment: customProcessPayment,
|
|
123
|
-
createTopupPayment: customCreateTopupPayment,
|
|
124
|
-
children
|
|
125
|
-
}) => {
|
|
126
|
-
const [purchaseData, setPurchaseData] = useState({
|
|
127
|
-
purchases: []
|
|
128
|
-
});
|
|
129
|
-
const [loading, setLoading] = useState(false);
|
|
130
|
-
const [isRefetching, setIsRefetching] = useState(false);
|
|
131
|
-
const [purchaseError, setPurchaseError] = useState(null);
|
|
132
|
-
const [internalCustomerRef, setInternalCustomerRef] = useState(void 0);
|
|
133
|
-
const [userId, setUserId] = useState(null);
|
|
134
|
-
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
135
|
-
const [creditsValue, setCreditsValue] = useState(null);
|
|
136
|
-
const [displayCurrencyValue, setDisplayCurrencyValue] = useState(null);
|
|
137
|
-
const [creditsPerMinorUnitValue, setCreditsPerMinorUnitValue] = useState(null);
|
|
138
|
-
const [displayExchangeRateValue, setDisplayExchangeRateValue] = useState(null);
|
|
139
|
-
const [balanceLoading, setBalanceLoading] = useState(false);
|
|
140
|
-
const balanceInFlightRef = useRef(false);
|
|
141
|
-
const balanceLoadedRef = useRef(false);
|
|
142
|
-
const optimisticUntilRef = useRef(0);
|
|
143
|
-
const optimisticTimerRef = useRef(null);
|
|
144
|
-
const fetchBalanceRef = useRef(null);
|
|
145
|
-
const inFlightRef = useRef(null);
|
|
146
|
-
const checkPurchaseRef = useRef(null);
|
|
147
|
-
const createPaymentRef = useRef(null);
|
|
148
|
-
const processPaymentRef = useRef(null);
|
|
149
|
-
const createTopupPaymentRef = useRef(null);
|
|
150
|
-
const configRef = useRef(config);
|
|
151
|
-
const buildDefaultCheckPurchaseRef = useRef(
|
|
152
|
-
null
|
|
153
|
-
);
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
configRef.current = config;
|
|
156
|
-
}, [config]);
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
checkPurchaseRef.current = customCheckPurchase || null;
|
|
159
|
-
}, [customCheckPurchase]);
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
createPaymentRef.current = customCreatePayment || null;
|
|
162
|
-
}, [customCreatePayment]);
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
processPaymentRef.current = customProcessPayment || null;
|
|
165
|
-
}, [customProcessPayment]);
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
createTopupPaymentRef.current = customCreateTopupPayment || null;
|
|
168
|
-
}, [customCreateTopupPayment]);
|
|
169
|
-
const buildDefaultCheckPurchase = useCallback(async () => {
|
|
170
|
-
const currentConfig = configRef.current;
|
|
171
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
172
|
-
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
173
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
174
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
175
|
-
if (!res.ok) {
|
|
176
|
-
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
177
|
-
currentConfig?.onError?.(error, "checkPurchase");
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
return res.json();
|
|
181
|
-
}, []);
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
|
|
184
|
-
}, [buildDefaultCheckPurchase]);
|
|
185
|
-
const buildDefaultCreatePayment = useCallback(
|
|
186
|
-
async (params) => {
|
|
187
|
-
const currentConfig = configRef.current;
|
|
188
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
189
|
-
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
190
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
191
|
-
const body = {};
|
|
192
|
-
if (params.planRef) {
|
|
193
|
-
body.planRef = params.planRef;
|
|
194
|
-
}
|
|
195
|
-
if (params.productRef) {
|
|
196
|
-
body.productRef = params.productRef;
|
|
197
|
-
}
|
|
198
|
-
const res = await fetchFn(route, {
|
|
199
|
-
method: "POST",
|
|
200
|
-
headers,
|
|
201
|
-
body: JSON.stringify(body)
|
|
202
|
-
});
|
|
203
|
-
if (!res.ok) {
|
|
204
|
-
const error = new Error(`Failed to create payment: ${res.statusText}`);
|
|
205
|
-
currentConfig?.onError?.(error, "createPayment");
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
return res.json();
|
|
209
|
-
},
|
|
210
|
-
[]
|
|
211
|
-
);
|
|
212
|
-
const buildDefaultProcessPayment = useCallback(
|
|
213
|
-
async (params) => {
|
|
214
|
-
const currentConfig = configRef.current;
|
|
215
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
216
|
-
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
217
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
218
|
-
const res = await fetchFn(route, {
|
|
219
|
-
method: "POST",
|
|
220
|
-
headers,
|
|
221
|
-
body: JSON.stringify(params)
|
|
222
|
-
});
|
|
223
|
-
if (!res.ok) {
|
|
224
|
-
const error = new Error(`Failed to process payment: ${res.statusText}`);
|
|
225
|
-
currentConfig?.onError?.(error, "processPayment");
|
|
226
|
-
throw error;
|
|
227
|
-
}
|
|
228
|
-
return res.json();
|
|
229
|
-
},
|
|
230
|
-
[]
|
|
231
|
-
);
|
|
232
|
-
const buildDefaultCreateTopupPayment = useCallback(
|
|
233
|
-
async (params) => {
|
|
234
|
-
const currentConfig = configRef.current;
|
|
235
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
236
|
-
const route = currentConfig?.api?.createTopupPayment || "/api/create-topup-payment-intent";
|
|
237
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
238
|
-
const res = await fetchFn(route, {
|
|
239
|
-
method: "POST",
|
|
240
|
-
headers,
|
|
241
|
-
body: JSON.stringify({
|
|
242
|
-
amount: params.amount,
|
|
243
|
-
currency: params.currency
|
|
244
|
-
})
|
|
245
|
-
});
|
|
246
|
-
if (!res.ok) {
|
|
247
|
-
const error = new Error(`Failed to create topup payment: ${res.statusText}`);
|
|
248
|
-
currentConfig?.onError?.(error, "createTopupPayment");
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
251
|
-
return res.json();
|
|
252
|
-
},
|
|
253
|
-
[]
|
|
254
|
-
);
|
|
255
|
-
const fetchBalanceImpl = useCallback(async () => {
|
|
256
|
-
if (optimisticUntilRef.current > Date.now()) return;
|
|
257
|
-
if (!isAuthenticated && !internalCustomerRef) {
|
|
258
|
-
setCreditsValue(null);
|
|
259
|
-
setDisplayCurrencyValue(null);
|
|
260
|
-
setCreditsPerMinorUnitValue(null);
|
|
261
|
-
setDisplayExchangeRateValue(null);
|
|
262
|
-
setBalanceLoading(false);
|
|
263
|
-
balanceLoadedRef.current = false;
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (balanceInFlightRef.current) return;
|
|
267
|
-
balanceInFlightRef.current = true;
|
|
268
|
-
if (!balanceLoadedRef.current) {
|
|
269
|
-
setBalanceLoading(true);
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
const currentConfig = configRef.current;
|
|
273
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
274
|
-
const route = currentConfig?.api?.customerBalance || "/api/customer-balance";
|
|
275
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
276
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
277
|
-
if (!res.ok) {
|
|
278
|
-
console.error("[SolvaPayProvider] Failed to fetch balance:", res.statusText);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const data = await res.json();
|
|
282
|
-
setCreditsValue(data.credits ?? null);
|
|
283
|
-
setDisplayCurrencyValue(data.displayCurrency ?? null);
|
|
284
|
-
setCreditsPerMinorUnitValue(data.creditsPerMinorUnit ?? null);
|
|
285
|
-
setDisplayExchangeRateValue(data.displayExchangeRate ?? null);
|
|
286
|
-
balanceLoadedRef.current = true;
|
|
287
|
-
} catch (error) {
|
|
288
|
-
console.error("[SolvaPayProvider] Failed to fetch balance:", error);
|
|
289
|
-
} finally {
|
|
290
|
-
setBalanceLoading(false);
|
|
291
|
-
balanceInFlightRef.current = false;
|
|
292
|
-
}
|
|
293
|
-
}, [isAuthenticated, internalCustomerRef]);
|
|
294
|
-
useEffect(() => {
|
|
295
|
-
fetchBalanceRef.current = fetchBalanceImpl;
|
|
296
|
-
}, [fetchBalanceImpl]);
|
|
297
|
-
useEffect(() => {
|
|
298
|
-
return () => {
|
|
299
|
-
if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
|
|
300
|
-
};
|
|
301
|
-
}, []);
|
|
302
|
-
const OPTIMISTIC_GRACE_MS = 8e3;
|
|
303
|
-
const adjustBalanceImpl = useCallback((credits) => {
|
|
304
|
-
setCreditsValue((prev) => (prev ?? 0) + credits);
|
|
305
|
-
optimisticUntilRef.current = Date.now() + OPTIMISTIC_GRACE_MS;
|
|
306
|
-
if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
|
|
307
|
-
optimisticTimerRef.current = setTimeout(() => {
|
|
308
|
-
optimisticUntilRef.current = 0;
|
|
309
|
-
fetchBalanceRef.current?.();
|
|
310
|
-
}, OPTIMISTIC_GRACE_MS);
|
|
311
|
-
}, []);
|
|
312
|
-
const _checkPurchase = useCallback(async () => {
|
|
313
|
-
if (checkPurchaseRef.current) {
|
|
314
|
-
return checkPurchaseRef.current();
|
|
315
|
-
}
|
|
316
|
-
if (buildDefaultCheckPurchaseRef.current) {
|
|
317
|
-
return buildDefaultCheckPurchaseRef.current();
|
|
318
|
-
}
|
|
319
|
-
return buildDefaultCheckPurchase();
|
|
320
|
-
}, [buildDefaultCheckPurchase]);
|
|
321
|
-
const createPayment = useCallback(
|
|
322
|
-
async (params) => {
|
|
323
|
-
if (createPaymentRef.current) {
|
|
324
|
-
return createPaymentRef.current(params);
|
|
325
|
-
}
|
|
326
|
-
return buildDefaultCreatePayment(params);
|
|
327
|
-
},
|
|
328
|
-
[buildDefaultCreatePayment]
|
|
329
|
-
);
|
|
330
|
-
const processPayment = useCallback(
|
|
331
|
-
async (params) => {
|
|
332
|
-
if (processPaymentRef.current) {
|
|
333
|
-
return processPaymentRef.current(params);
|
|
334
|
-
}
|
|
335
|
-
return buildDefaultProcessPayment(params);
|
|
336
|
-
},
|
|
337
|
-
[buildDefaultProcessPayment]
|
|
338
|
-
);
|
|
339
|
-
const createTopupPayment = useCallback(
|
|
340
|
-
async (params) => {
|
|
341
|
-
if (createTopupPaymentRef.current) {
|
|
342
|
-
return createTopupPaymentRef.current(params);
|
|
343
|
-
}
|
|
344
|
-
return buildDefaultCreateTopupPayment(params);
|
|
345
|
-
},
|
|
346
|
-
[buildDefaultCreateTopupPayment]
|
|
347
|
-
);
|
|
348
|
-
useEffect(() => {
|
|
349
|
-
const detectAuth = async () => {
|
|
350
|
-
const currentConfig = configRef.current;
|
|
351
|
-
const adapter = getAuthAdapter(currentConfig);
|
|
352
|
-
const token = await adapter.getToken();
|
|
353
|
-
const detectedUserId = await adapter.getUserId();
|
|
354
|
-
const prevUserId = userId;
|
|
355
|
-
setIsAuthenticated(!!token);
|
|
356
|
-
setUserId(detectedUserId);
|
|
357
|
-
if (prevUserId !== null && detectedUserId !== prevUserId) {
|
|
358
|
-
clearCachedCustomerRef();
|
|
359
|
-
setInternalCustomerRef(void 0);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
363
|
-
if (cachedRef && token) {
|
|
364
|
-
setInternalCustomerRef(cachedRef);
|
|
365
|
-
} else if (!token) {
|
|
366
|
-
clearCachedCustomerRef();
|
|
367
|
-
setInternalCustomerRef(void 0);
|
|
368
|
-
} else if (token && !cachedRef) {
|
|
369
|
-
setInternalCustomerRef(void 0);
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
detectAuth();
|
|
373
|
-
const interval = setInterval(detectAuth, 3e4);
|
|
374
|
-
return () => clearInterval(interval);
|
|
375
|
-
}, [userId]);
|
|
376
|
-
const fetchPurchase = useCallback(
|
|
377
|
-
async (force = false) => {
|
|
378
|
-
if (!isAuthenticated && !internalCustomerRef) {
|
|
379
|
-
setPurchaseData({ purchases: [] });
|
|
380
|
-
setPurchaseError(null);
|
|
381
|
-
setLoading(false);
|
|
382
|
-
setIsRefetching(false);
|
|
383
|
-
inFlightRef.current = null;
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
387
|
-
if (inFlightRef.current === cacheKey && !force) {
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
inFlightRef.current = cacheKey;
|
|
391
|
-
const hasExistingData = purchaseData.purchases.length > 0;
|
|
392
|
-
if (hasExistingData) {
|
|
393
|
-
setIsRefetching(true);
|
|
394
|
-
} else {
|
|
395
|
-
setLoading(true);
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
|
|
399
|
-
if (!checkFn) {
|
|
400
|
-
throw new Error("checkPurchase function not available");
|
|
401
|
-
}
|
|
402
|
-
const data = await checkFn();
|
|
403
|
-
if (data.customerRef) {
|
|
404
|
-
setInternalCustomerRef(data.customerRef);
|
|
405
|
-
const currentAdapter = getAuthAdapter(configRef.current);
|
|
406
|
-
const currentUserId = await currentAdapter.getUserId();
|
|
407
|
-
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
408
|
-
}
|
|
409
|
-
if (inFlightRef.current === cacheKey) {
|
|
410
|
-
const filteredData = {
|
|
411
|
-
...data,
|
|
412
|
-
purchases: filterPurchases(data.purchases || [])
|
|
413
|
-
};
|
|
414
|
-
setPurchaseData(filteredData);
|
|
415
|
-
setPurchaseError(null);
|
|
416
|
-
}
|
|
417
|
-
} catch (err) {
|
|
418
|
-
console.error("[SolvaPayProvider] Failed to fetch purchase:", err);
|
|
419
|
-
if (inFlightRef.current === cacheKey) {
|
|
420
|
-
setPurchaseError(err instanceof Error ? err : new Error(String(err)));
|
|
421
|
-
}
|
|
422
|
-
} finally {
|
|
423
|
-
if (inFlightRef.current === cacheKey) {
|
|
424
|
-
setLoading(false);
|
|
425
|
-
setIsRefetching(false);
|
|
426
|
-
inFlightRef.current = null;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
[isAuthenticated, internalCustomerRef, userId, purchaseData.purchases.length]
|
|
431
|
-
);
|
|
432
|
-
const fetchPurchaseRef = useRef(fetchPurchase);
|
|
433
|
-
useEffect(() => {
|
|
434
|
-
fetchPurchaseRef.current = fetchPurchase;
|
|
435
|
-
}, [fetchPurchase]);
|
|
436
|
-
const refetchPurchase = useCallback(async () => {
|
|
437
|
-
inFlightRef.current = null;
|
|
438
|
-
await fetchPurchaseRef.current(true);
|
|
439
|
-
}, []);
|
|
440
|
-
const cancelRenewal = useCallback(
|
|
441
|
-
async (params) => {
|
|
442
|
-
const currentConfig = configRef.current;
|
|
443
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
444
|
-
const route = currentConfig?.api?.cancelRenewal || "/api/cancel-renewal";
|
|
445
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
446
|
-
const res = await fetchFn(route, {
|
|
447
|
-
method: "POST",
|
|
448
|
-
headers,
|
|
449
|
-
body: JSON.stringify(params)
|
|
450
|
-
});
|
|
451
|
-
if (!res.ok) {
|
|
452
|
-
const data = await res.json().catch(() => ({}));
|
|
453
|
-
const error = new Error(data.error || `Failed to cancel renewal: ${res.statusText}`);
|
|
454
|
-
currentConfig?.onError?.(error, "cancelRenewal");
|
|
455
|
-
throw error;
|
|
456
|
-
}
|
|
457
|
-
const result = await res.json();
|
|
458
|
-
await refetchPurchase();
|
|
459
|
-
return result;
|
|
460
|
-
},
|
|
461
|
-
[refetchPurchase]
|
|
462
|
-
);
|
|
463
|
-
const reactivateRenewal = useCallback(
|
|
464
|
-
async (params) => {
|
|
465
|
-
const currentConfig = configRef.current;
|
|
466
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
467
|
-
const route = currentConfig?.api?.reactivateRenewal || "/api/reactivate-renewal";
|
|
468
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
469
|
-
const res = await fetchFn(route, {
|
|
470
|
-
method: "POST",
|
|
471
|
-
headers,
|
|
472
|
-
body: JSON.stringify(params)
|
|
473
|
-
});
|
|
474
|
-
if (!res.ok) {
|
|
475
|
-
const data = await res.json().catch(() => ({}));
|
|
476
|
-
const error = new Error(data.error || `Failed to reactivate renewal: ${res.statusText}`);
|
|
477
|
-
currentConfig?.onError?.(error, "reactivateRenewal");
|
|
478
|
-
throw error;
|
|
479
|
-
}
|
|
480
|
-
const result = await res.json();
|
|
481
|
-
await refetchPurchase();
|
|
482
|
-
return result;
|
|
483
|
-
},
|
|
484
|
-
[refetchPurchase]
|
|
485
|
-
);
|
|
486
|
-
const activatePlan = useCallback(
|
|
487
|
-
async (params) => {
|
|
488
|
-
const currentConfig = configRef.current;
|
|
489
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
490
|
-
const route = currentConfig?.api?.activatePlan || "/api/activate-plan";
|
|
491
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
492
|
-
const res = await fetchFn(route, {
|
|
493
|
-
method: "POST",
|
|
494
|
-
headers,
|
|
495
|
-
body: JSON.stringify(params)
|
|
496
|
-
});
|
|
497
|
-
if (!res.ok) {
|
|
498
|
-
const data = await res.json().catch(() => ({}));
|
|
499
|
-
const error = new Error(data.error || `Failed to activate plan: ${res.statusText}`);
|
|
500
|
-
currentConfig?.onError?.(error, "activatePlan");
|
|
501
|
-
throw error;
|
|
502
|
-
}
|
|
503
|
-
const result = await res.json();
|
|
504
|
-
if (result.status === "activated" || result.status === "already_active") {
|
|
505
|
-
await refetchPurchase();
|
|
506
|
-
}
|
|
507
|
-
return result;
|
|
508
|
-
},
|
|
509
|
-
[refetchPurchase]
|
|
510
|
-
);
|
|
511
|
-
useEffect(() => {
|
|
512
|
-
inFlightRef.current = null;
|
|
513
|
-
if (isAuthenticated || internalCustomerRef) {
|
|
514
|
-
fetchPurchase();
|
|
515
|
-
} else {
|
|
516
|
-
setPurchaseData({ purchases: [] });
|
|
517
|
-
setPurchaseError(null);
|
|
518
|
-
setLoading(false);
|
|
519
|
-
setIsRefetching(false);
|
|
520
|
-
}
|
|
521
|
-
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
522
|
-
const updateCustomerRef = useCallback(
|
|
523
|
-
(newCustomerRef) => {
|
|
524
|
-
setInternalCustomerRef(newCustomerRef);
|
|
525
|
-
setCachedCustomerRef(newCustomerRef, userId);
|
|
526
|
-
fetchPurchase(true);
|
|
527
|
-
},
|
|
528
|
-
[fetchPurchase, userId]
|
|
529
|
-
);
|
|
530
|
-
const purchase = useMemo(() => {
|
|
531
|
-
const activePurchase = getPrimaryPurchase(purchaseData.purchases);
|
|
532
|
-
const activePaidPurchases = purchaseData.purchases.filter(
|
|
533
|
-
(p) => p.status === "active" && isPaidPurchase(p)
|
|
534
|
-
);
|
|
535
|
-
const activePaidPurchase = activePaidPurchases.sort(
|
|
536
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
537
|
-
)[0] || null;
|
|
538
|
-
return {
|
|
539
|
-
loading,
|
|
540
|
-
isRefetching,
|
|
541
|
-
error: purchaseError,
|
|
542
|
-
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
543
|
-
email: purchaseData.email,
|
|
544
|
-
name: purchaseData.name,
|
|
545
|
-
purchases: purchaseData.purchases,
|
|
546
|
-
hasProduct: (productName) => {
|
|
547
|
-
return purchaseData.purchases.some(
|
|
548
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
549
|
-
);
|
|
550
|
-
},
|
|
551
|
-
hasPlan: (productName) => {
|
|
552
|
-
return purchaseData.purchases.some(
|
|
553
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
554
|
-
);
|
|
555
|
-
},
|
|
556
|
-
activePurchase,
|
|
557
|
-
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
558
|
-
activePaidPurchase
|
|
559
|
-
};
|
|
560
|
-
}, [loading, isRefetching, purchaseError, purchaseData, internalCustomerRef]);
|
|
561
|
-
const balance = useMemo(
|
|
562
|
-
() => ({
|
|
563
|
-
loading: balanceLoading,
|
|
564
|
-
credits: creditsValue,
|
|
565
|
-
displayCurrency: displayCurrencyValue,
|
|
566
|
-
creditsPerMinorUnit: creditsPerMinorUnitValue,
|
|
567
|
-
displayExchangeRate: displayExchangeRateValue,
|
|
568
|
-
refetch: fetchBalanceImpl,
|
|
569
|
-
adjustBalance: adjustBalanceImpl
|
|
570
|
-
}),
|
|
571
|
-
[balanceLoading, creditsValue, displayCurrencyValue, creditsPerMinorUnitValue, displayExchangeRateValue, fetchBalanceImpl, adjustBalanceImpl]
|
|
572
|
-
);
|
|
573
|
-
const contextValue = useMemo(
|
|
574
|
-
() => ({
|
|
575
|
-
purchase,
|
|
576
|
-
refetchPurchase,
|
|
577
|
-
createPayment,
|
|
578
|
-
processPayment,
|
|
579
|
-
createTopupPayment,
|
|
580
|
-
cancelRenewal,
|
|
581
|
-
reactivateRenewal,
|
|
582
|
-
activatePlan,
|
|
583
|
-
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
584
|
-
updateCustomerRef,
|
|
585
|
-
balance,
|
|
586
|
-
_config: configRef.current
|
|
587
|
-
}),
|
|
588
|
-
[
|
|
589
|
-
purchase,
|
|
590
|
-
refetchPurchase,
|
|
591
|
-
createPayment,
|
|
592
|
-
processPayment,
|
|
593
|
-
createTopupPayment,
|
|
594
|
-
cancelRenewal,
|
|
595
|
-
reactivateRenewal,
|
|
596
|
-
activatePlan,
|
|
597
|
-
purchaseData.customerRef,
|
|
598
|
-
internalCustomerRef,
|
|
599
|
-
updateCustomerRef,
|
|
600
|
-
balance
|
|
601
|
-
]
|
|
602
|
-
);
|
|
603
|
-
return /* @__PURE__ */ jsx(SolvaPayContext.Provider, { value: contextValue, children });
|
|
604
|
-
};
|
|
605
|
-
|
|
606
72
|
// src/PaymentForm.tsx
|
|
607
|
-
import {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
return context;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// src/hooks/useCheckout.ts
|
|
627
|
-
var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
628
|
-
function getStripeCacheKey(publishableKey, accountId) {
|
|
629
|
-
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
630
|
-
}
|
|
631
|
-
async function resolvePlanRef(productRef, fetchFn, headers, listPlansRoute) {
|
|
632
|
-
const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
|
|
633
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
634
|
-
if (!res.ok) {
|
|
635
|
-
throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
|
|
636
|
-
}
|
|
637
|
-
const data = await res.json();
|
|
638
|
-
const allPlans = data.plans ?? [];
|
|
639
|
-
const activePlans = allPlans.filter((p) => p.isActive !== false && p.status !== "inactive");
|
|
640
|
-
if (activePlans.length === 0) {
|
|
641
|
-
throw new Error(
|
|
642
|
-
`No active plans found for product "${productRef}". Configure at least one plan in the SolvaPay Console.`
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
if (activePlans.length === 1) {
|
|
646
|
-
return activePlans[0].reference;
|
|
647
|
-
}
|
|
648
|
-
const defaultPlan = activePlans.find((p) => p.default === true);
|
|
649
|
-
if (defaultPlan) {
|
|
650
|
-
return defaultPlan.reference;
|
|
651
|
-
}
|
|
652
|
-
throw new Error(
|
|
653
|
-
`Product "${productRef}" has ${activePlans.length} active plans but none is marked as default. Either pass planRef explicitly, use <PricingSelector> for user selection, or mark one plan as default in the SolvaPay Console.`
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
function useCheckout(options) {
|
|
657
|
-
const { planRef, productRef } = options;
|
|
658
|
-
const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
|
|
659
|
-
const [loading, setLoading] = useState2(false);
|
|
660
|
-
const [error, setError] = useState2(null);
|
|
661
|
-
const [stripePromise, setStripePromise] = useState2(null);
|
|
662
|
-
const [clientSecret, setClientSecret] = useState2(null);
|
|
663
|
-
const [resolvedPlanRef, setResolvedPlanRef] = useState2(planRef || null);
|
|
664
|
-
const isStartingRef = useRef2(false);
|
|
665
|
-
const startCheckout = useCallback2(async () => {
|
|
666
|
-
if (isStartingRef.current || loading) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
if (!planRef && !productRef) {
|
|
670
|
-
setError(
|
|
671
|
-
new Error(
|
|
672
|
-
"useCheckout: either planRef or productRef is required. Pass planRef directly, or pass productRef to auto-resolve the plan."
|
|
673
|
-
)
|
|
674
|
-
);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
isStartingRef.current = true;
|
|
678
|
-
setLoading(true);
|
|
679
|
-
setError(null);
|
|
680
|
-
try {
|
|
681
|
-
let effectivePlanRef = planRef;
|
|
682
|
-
if (!effectivePlanRef && productRef) {
|
|
683
|
-
const listPlansRoute = _config?.api?.listPlans || "/api/list-plans";
|
|
684
|
-
effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
|
|
685
|
-
setResolvedPlanRef(effectivePlanRef);
|
|
686
|
-
}
|
|
687
|
-
if (!effectivePlanRef) {
|
|
688
|
-
throw new Error("Could not determine plan reference for checkout");
|
|
689
|
-
}
|
|
690
|
-
const result = await createPayment({ planRef: effectivePlanRef, productRef });
|
|
691
|
-
if (!result || typeof result !== "object") {
|
|
692
|
-
throw new Error("Invalid payment intent response from server");
|
|
693
|
-
}
|
|
694
|
-
if (!result.clientSecret || typeof result.clientSecret !== "string") {
|
|
695
|
-
throw new Error("Invalid client secret in payment intent response");
|
|
696
|
-
}
|
|
697
|
-
if (!result.publishableKey || typeof result.publishableKey !== "string") {
|
|
698
|
-
throw new Error("Invalid publishable key in payment intent response");
|
|
699
|
-
}
|
|
700
|
-
if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
|
|
701
|
-
updateCustomerRef(result.customerRef);
|
|
702
|
-
}
|
|
703
|
-
const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
|
|
704
|
-
const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
|
|
705
|
-
let stripe = stripePromiseCache.get(cacheKey);
|
|
706
|
-
if (!stripe) {
|
|
707
|
-
stripe = loadStripe(result.publishableKey, stripeOptions);
|
|
708
|
-
stripePromiseCache.set(cacheKey, stripe);
|
|
709
|
-
}
|
|
710
|
-
setStripePromise(stripe);
|
|
711
|
-
setClientSecret(result.clientSecret);
|
|
712
|
-
} catch (err) {
|
|
713
|
-
const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
|
|
714
|
-
setError(error2);
|
|
715
|
-
} finally {
|
|
716
|
-
setLoading(false);
|
|
717
|
-
isStartingRef.current = false;
|
|
718
|
-
}
|
|
719
|
-
}, [planRef, productRef, createPayment, updateCustomerRef, loading, _config]);
|
|
720
|
-
const reset = useCallback2(() => {
|
|
721
|
-
isStartingRef.current = false;
|
|
722
|
-
setLoading(false);
|
|
723
|
-
setError(null);
|
|
724
|
-
setStripePromise(null);
|
|
725
|
-
setClientSecret(null);
|
|
726
|
-
setResolvedPlanRef(planRef || null);
|
|
727
|
-
}, [planRef]);
|
|
728
|
-
return {
|
|
729
|
-
loading,
|
|
730
|
-
error,
|
|
731
|
-
stripePromise,
|
|
732
|
-
clientSecret,
|
|
733
|
-
resolvedPlanRef,
|
|
734
|
-
startCheckout,
|
|
735
|
-
reset
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/hooks/usePurchase.ts
|
|
740
|
-
function usePurchase() {
|
|
741
|
-
const { purchase, refetchPurchase } = useSolvaPay();
|
|
742
|
-
return {
|
|
743
|
-
...purchase,
|
|
744
|
-
refetch: refetchPurchase
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// src/components/Spinner.tsx
|
|
749
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
750
|
-
var Spinner = ({
|
|
751
|
-
className = "",
|
|
752
|
-
size = "md"
|
|
753
|
-
}) => {
|
|
754
|
-
const sizeClasses = {
|
|
755
|
-
sm: "h-4 w-4",
|
|
756
|
-
md: "h-5 w-5",
|
|
757
|
-
lg: "h-6 w-6"
|
|
758
|
-
};
|
|
759
|
-
return /* @__PURE__ */ jsxs(
|
|
760
|
-
"svg",
|
|
761
|
-
{
|
|
762
|
-
className: `animate-spin ${sizeClasses[size]} ${className}`,
|
|
763
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
764
|
-
fill: "none",
|
|
765
|
-
viewBox: "0 0 24 24",
|
|
766
|
-
role: "status",
|
|
767
|
-
"aria-busy": "true",
|
|
768
|
-
children: [
|
|
769
|
-
/* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
770
|
-
/* @__PURE__ */ jsx2(
|
|
771
|
-
"path",
|
|
772
|
-
{
|
|
773
|
-
className: "opacity-75",
|
|
774
|
-
fill: "currentColor",
|
|
775
|
-
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"
|
|
776
|
-
}
|
|
777
|
-
)
|
|
778
|
-
]
|
|
779
|
-
}
|
|
780
|
-
);
|
|
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 }) });
|
|
781
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
|
+
});
|
|
782
100
|
|
|
783
101
|
// src/components/StripePaymentFormWrapper.tsx
|
|
784
|
-
import { useState
|
|
102
|
+
import { useState } from "react";
|
|
785
103
|
import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
|
|
786
|
-
|
|
787
|
-
// src/hooks/useCustomer.ts
|
|
788
|
-
function useCustomer() {
|
|
789
|
-
const { purchase, customerRef } = useSolvaPay();
|
|
790
|
-
return {
|
|
791
|
-
customerRef: purchase.customerRef || customerRef,
|
|
792
|
-
email: purchase.email,
|
|
793
|
-
name: purchase.name,
|
|
794
|
-
loading: purchase.loading
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// src/components/StripePaymentFormWrapper.tsx
|
|
799
|
-
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";
|
|
800
105
|
var StripePaymentFormWrapper = ({
|
|
801
106
|
onSuccess,
|
|
802
107
|
onError,
|
|
803
108
|
returnUrl: _returnUrl,
|
|
804
|
-
submitButtonText
|
|
109
|
+
submitButtonText,
|
|
805
110
|
buttonClassName,
|
|
806
111
|
clientSecret
|
|
807
112
|
}) => {
|
|
808
113
|
const stripe = useStripe();
|
|
809
114
|
const elements = useElements();
|
|
810
115
|
const customer = useCustomer();
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
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);
|
|
814
121
|
const handleSubmit = async (event) => {
|
|
815
122
|
event.preventDefault();
|
|
816
123
|
if (!stripe || !elements) {
|
|
817
|
-
const errorMessage =
|
|
124
|
+
const errorMessage = copy.errors.stripeUnavailable;
|
|
818
125
|
setMessage(errorMessage);
|
|
819
126
|
if (onError) {
|
|
820
127
|
onError(new Error(errorMessage));
|
|
@@ -822,7 +129,7 @@ var StripePaymentFormWrapper = ({
|
|
|
822
129
|
return;
|
|
823
130
|
}
|
|
824
131
|
if (!clientSecret) {
|
|
825
|
-
const errorMessage =
|
|
132
|
+
const errorMessage = copy.errors.paymentIntentUnavailable;
|
|
826
133
|
setMessage(errorMessage);
|
|
827
134
|
if (onError) {
|
|
828
135
|
onError(new Error(errorMessage));
|
|
@@ -834,7 +141,7 @@ var StripePaymentFormWrapper = ({
|
|
|
834
141
|
try {
|
|
835
142
|
const cardElement = elements.getElement(CardElement);
|
|
836
143
|
if (!cardElement) {
|
|
837
|
-
const errorMessage =
|
|
144
|
+
const errorMessage = copy.errors.cardElementMissing;
|
|
838
145
|
setMessage(errorMessage);
|
|
839
146
|
setIsProcessing(false);
|
|
840
147
|
if (onError) {
|
|
@@ -852,7 +159,7 @@ var StripePaymentFormWrapper = ({
|
|
|
852
159
|
}
|
|
853
160
|
});
|
|
854
161
|
if (error) {
|
|
855
|
-
const errorMessage = error.message ||
|
|
162
|
+
const errorMessage = error.message || copy.errors.paymentUnexpected;
|
|
856
163
|
setMessage(errorMessage);
|
|
857
164
|
setIsProcessing(false);
|
|
858
165
|
if (onError) {
|
|
@@ -865,8 +172,8 @@ var StripePaymentFormWrapper = ({
|
|
|
865
172
|
await onSuccess(paymentIntent);
|
|
866
173
|
setMessage("Payment successful!");
|
|
867
174
|
} catch (err) {
|
|
868
|
-
const error2 = err instanceof Error ? err : new Error(
|
|
869
|
-
setMessage(
|
|
175
|
+
const error2 = err instanceof Error ? err : new Error(copy.errors.paymentProcessingFailed);
|
|
176
|
+
setMessage(copy.errors.paymentProcessingFailed);
|
|
870
177
|
setIsProcessing(false);
|
|
871
178
|
if (onError) {
|
|
872
179
|
onError(error2);
|
|
@@ -877,14 +184,18 @@ var StripePaymentFormWrapper = ({
|
|
|
877
184
|
setMessage("Payment successful!");
|
|
878
185
|
}
|
|
879
186
|
} else if (paymentIntent && paymentIntent.status === "requires_action") {
|
|
880
|
-
setMessage(
|
|
187
|
+
setMessage(copy.errors.paymentRequires3ds);
|
|
881
188
|
setIsProcessing(false);
|
|
882
189
|
} else if (paymentIntent) {
|
|
883
|
-
setMessage(
|
|
190
|
+
setMessage(
|
|
191
|
+
interpolate(copy.errors.paymentStatusPrefix, {
|
|
192
|
+
status: paymentIntent.status || "processing"
|
|
193
|
+
})
|
|
194
|
+
);
|
|
884
195
|
setIsProcessing(false);
|
|
885
196
|
}
|
|
886
197
|
} catch (err) {
|
|
887
|
-
const error = err instanceof Error ? err : new Error(
|
|
198
|
+
const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
|
|
888
199
|
setMessage(error.message);
|
|
889
200
|
if (onError) {
|
|
890
201
|
onError(error);
|
|
@@ -912,8 +223,8 @@ var StripePaymentFormWrapper = ({
|
|
|
912
223
|
},
|
|
913
224
|
hidePostalCode: true
|
|
914
225
|
};
|
|
915
|
-
return /* @__PURE__ */ jsxs2(
|
|
916
|
-
isReady ? /* @__PURE__ */
|
|
226
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
227
|
+
isReady ? /* @__PURE__ */ jsx2(
|
|
917
228
|
CardElement,
|
|
918
229
|
{
|
|
919
230
|
options: cardElementOptions,
|
|
@@ -927,9 +238,9 @@ var StripePaymentFormWrapper = ({
|
|
|
927
238
|
}
|
|
928
239
|
}
|
|
929
240
|
}
|
|
930
|
-
) : /* @__PURE__ */
|
|
931
|
-
message && !isSuccess && /* @__PURE__ */
|
|
932
|
-
/* @__PURE__ */
|
|
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(
|
|
933
244
|
"button",
|
|
934
245
|
{
|
|
935
246
|
type: "submit",
|
|
@@ -938,936 +249,530 @@ var StripePaymentFormWrapper = ({
|
|
|
938
249
|
"aria-busy": isProcessing,
|
|
939
250
|
"aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
|
|
940
251
|
onClick: handleSubmit,
|
|
941
|
-
children: isProcessing ? /* @__PURE__ */ jsxs2(
|
|
942
|
-
/* @__PURE__ */
|
|
943
|
-
/* @__PURE__ */
|
|
944
|
-
] }) :
|
|
252
|
+
children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
253
|
+
/* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
|
|
254
|
+
/* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
|
|
255
|
+
] }) : effectiveSubmitText
|
|
945
256
|
}
|
|
946
257
|
)
|
|
947
258
|
] });
|
|
948
259
|
};
|
|
949
260
|
|
|
950
|
-
// src/
|
|
951
|
-
import {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
},
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
);
|
|
1027
|
-
|
|
1028
|
-
const hasError = !!checkoutError;
|
|
1029
|
-
const hasStripeData = !!(stripePromise && clientSecret);
|
|
1030
|
-
const elementsOptions = useMemo2(() => {
|
|
1031
|
-
if (!clientSecret) return void 0;
|
|
1032
|
-
return { clientSecret };
|
|
1033
|
-
}, [clientSecret]);
|
|
1034
|
-
const [hasMountedElements, setHasMountedElements] = useState4(false);
|
|
1035
|
-
useEffect2(() => {
|
|
1036
|
-
if (hasStripeData) {
|
|
1037
|
-
setHasMountedElements(true);
|
|
1038
|
-
}
|
|
1039
|
-
}, [hasStripeData]);
|
|
1040
|
-
const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
|
|
1041
|
-
return /* @__PURE__ */ jsx4("div", { className, children: !hasPlanOrProduct ? /* @__PURE__ */ jsx4("div", { children: "PaymentForm: either planRef or productRef is required" }) : hasError ? /* @__PURE__ */ jsxs3("div", { children: [
|
|
1042
|
-
/* @__PURE__ */ jsx4("div", { children: "Payment initialization failed" }),
|
|
1043
|
-
/* @__PURE__ */ jsx4("div", { children: checkoutError?.message || "Unknown error" })
|
|
1044
|
-
] }) : shouldRenderElements && elementsOptions ? /* @__PURE__ */ jsx4(
|
|
1045
|
-
Elements,
|
|
1046
|
-
{
|
|
1047
|
-
stripe: stripePromise,
|
|
1048
|
-
options: elementsOptions,
|
|
1049
|
-
children: /* @__PURE__ */ jsx4(
|
|
1050
|
-
StripePaymentFormWrapper,
|
|
1051
|
-
{
|
|
1052
|
-
onSuccess: handleSuccess,
|
|
1053
|
-
onError: handleError,
|
|
1054
|
-
returnUrl: finalReturnUrl,
|
|
1055
|
-
submitButtonText,
|
|
1056
|
-
buttonClassName,
|
|
1057
|
-
clientSecret
|
|
1058
|
-
}
|
|
1059
|
-
)
|
|
1060
|
-
},
|
|
1061
|
-
clientSecret
|
|
1062
|
-
) : /* @__PURE__ */ jsxs3("div", { children: [
|
|
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: [
|
|
1063
339
|
/* @__PURE__ */ jsx4(
|
|
1064
|
-
"
|
|
340
|
+
"button",
|
|
1065
341
|
{
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
justifyContent: "center"
|
|
1071
|
-
},
|
|
1072
|
-
children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
|
|
342
|
+
type: "button",
|
|
343
|
+
onClick: ctx.backToSelectAmount,
|
|
344
|
+
className: "solvapay-activation-flow-change-amount",
|
|
345
|
+
children: copy.activationFlow.changeAmountButton
|
|
1073
346
|
}
|
|
1074
347
|
),
|
|
1075
348
|
/* @__PURE__ */ jsx4(
|
|
1076
|
-
|
|
349
|
+
TopupForm,
|
|
1077
350
|
{
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
"aria-disabled": "true",
|
|
1083
|
-
children: submitButtonText
|
|
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)))
|
|
1084
355
|
}
|
|
1085
356
|
)
|
|
1086
|
-
] })
|
|
357
|
+
] });
|
|
1087
358
|
};
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1116
|
-
isStartingRef.current = true;
|
|
1117
|
-
setLoading(true);
|
|
1118
|
-
setError(null);
|
|
1119
|
-
try {
|
|
1120
|
-
const result = await createTopupPayment({ amount, currency });
|
|
1121
|
-
if (!result || typeof result !== "object") {
|
|
1122
|
-
throw new Error("Invalid topup payment intent response from server");
|
|
1123
|
-
}
|
|
1124
|
-
if (!result.clientSecret || typeof result.clientSecret !== "string") {
|
|
1125
|
-
throw new Error("Invalid client secret in topup payment intent response");
|
|
1126
|
-
}
|
|
1127
|
-
if (!result.publishableKey || typeof result.publishableKey !== "string") {
|
|
1128
|
-
throw new Error("Invalid publishable key in topup payment intent response");
|
|
1129
|
-
}
|
|
1130
|
-
if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
|
|
1131
|
-
updateCustomerRef(result.customerRef);
|
|
1132
|
-
}
|
|
1133
|
-
const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
|
|
1134
|
-
const cacheKey = getStripeCacheKey2(result.publishableKey, result.accountId);
|
|
1135
|
-
let stripe = stripePromiseCache2.get(cacheKey);
|
|
1136
|
-
if (!stripe) {
|
|
1137
|
-
stripe = loadStripe2(result.publishableKey, stripeOptions);
|
|
1138
|
-
stripePromiseCache2.set(cacheKey, stripe);
|
|
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
|
|
1139
386
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
|
1148
401
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
isStartingRef.current = false;
|
|
1152
|
-
setLoading(false);
|
|
1153
|
-
setError(null);
|
|
1154
|
-
setStripePromise(null);
|
|
1155
|
-
setClientSecret(null);
|
|
1156
|
-
}, []);
|
|
1157
|
-
return {
|
|
1158
|
-
loading,
|
|
1159
|
-
error,
|
|
1160
|
-
stripePromise,
|
|
1161
|
-
clientSecret,
|
|
1162
|
-
startTopup,
|
|
1163
|
-
reset
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
402
|
+
);
|
|
403
|
+
};
|
|
1166
404
|
|
|
1167
|
-
// src/
|
|
1168
|
-
import { jsx as jsx5, jsxs as
|
|
1169
|
-
var
|
|
1170
|
-
|
|
1171
|
-
|
|
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 = ({
|
|
417
|
+
planRef,
|
|
418
|
+
productRef,
|
|
419
|
+
prefillCustomer,
|
|
420
|
+
size = "auto",
|
|
421
|
+
requireTermsAcceptance,
|
|
1172
422
|
onSuccess,
|
|
423
|
+
onResult,
|
|
424
|
+
onFreePlan,
|
|
1173
425
|
onError,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
426
|
+
initialPlanRef,
|
|
427
|
+
onPlanSelect,
|
|
428
|
+
planSelector,
|
|
429
|
+
showBackButton = true,
|
|
430
|
+
submitButtonText,
|
|
431
|
+
returnUrl
|
|
1178
432
|
}) => {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (!hasInitializedRef.current && hasAmount && !topupLoading && !topupError && !clientSecret) {
|
|
1190
|
-
hasInitializedRef.current = true;
|
|
1191
|
-
startTopup().catch(() => {
|
|
1192
|
-
hasInitializedRef.current = false;
|
|
1193
|
-
});
|
|
1194
|
-
}
|
|
1195
|
-
if (hasAmount && clientSecret) {
|
|
1196
|
-
hasInitializedRef.current = true;
|
|
1197
|
-
}
|
|
1198
|
-
}, [hasAmount, topupLoading, topupError, clientSecret, startTopup]);
|
|
1199
|
-
const handleSuccess = useCallback5(
|
|
1200
|
-
async (paymentIntent) => {
|
|
1201
|
-
if (onSuccess) {
|
|
1202
|
-
await onSuccess(paymentIntent);
|
|
1203
|
-
}
|
|
1204
|
-
},
|
|
1205
|
-
[onSuccess]
|
|
1206
|
-
);
|
|
1207
|
-
const handleError = useCallback5(
|
|
1208
|
-
(err) => {
|
|
1209
|
-
if (onError) {
|
|
1210
|
-
onError(err);
|
|
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));
|
|
1211
443
|
}
|
|
444
|
+
});
|
|
445
|
+
ro.observe(el);
|
|
446
|
+
return () => ro.disconnect();
|
|
447
|
+
}, [size]);
|
|
448
|
+
const resolvedSize = size === "auto" ? autoSize : size;
|
|
449
|
+
const [selectedPlanRef, setSelectedPlanRef] = useState2(planRef ?? null);
|
|
450
|
+
const [userStep, setUserStep] = useState2(planRef ? "pay" : "select");
|
|
451
|
+
const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
|
|
452
|
+
const { plan: resolvedPlan } = usePlan({
|
|
453
|
+
planRef: effectivePlanRef,
|
|
454
|
+
productRef
|
|
455
|
+
});
|
|
456
|
+
const isUsageBased = resolvedPlan?.type === "usage-based";
|
|
457
|
+
const step = useMemo(() => {
|
|
458
|
+
if (userStep === "pay" && isUsageBased) return "activate";
|
|
459
|
+
if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
|
|
460
|
+
return userStep;
|
|
461
|
+
}, [userStep, isUsageBased, resolvedPlan]);
|
|
462
|
+
const handlePlanSelect = useCallback(
|
|
463
|
+
(ref, plan) => {
|
|
464
|
+
setSelectedPlanRef(ref);
|
|
465
|
+
onPlanSelect?.(ref, plan);
|
|
466
|
+
setUserStep(plan.type === "usage-based" ? "activate" : "pay");
|
|
1212
467
|
},
|
|
1213
|
-
[
|
|
468
|
+
[onPlanSelect]
|
|
1214
469
|
);
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
}, [clientSecret]);
|
|
1222
|
-
return /* @__PURE__ */ jsx5("div", { className, children: !hasAmount ? /* @__PURE__ */ jsx5("div", { children: "TopupForm: amount must be a positive number" }) : hasError ? /* @__PURE__ */ jsxs4("div", { children: [
|
|
1223
|
-
/* @__PURE__ */ jsx5("div", { children: "Top-up initialization failed" }),
|
|
1224
|
-
/* @__PURE__ */ jsx5("div", { children: topupError?.message || "Unknown error" })
|
|
1225
|
-
] }) : hasStripeData && elementsOptions ? /* @__PURE__ */ jsx5(
|
|
1226
|
-
Elements2,
|
|
470
|
+
const handleBack = useCallback(() => {
|
|
471
|
+
if (planRef) return;
|
|
472
|
+
setUserStep("select");
|
|
473
|
+
}, [planRef]);
|
|
474
|
+
return /* @__PURE__ */ jsxs5(
|
|
475
|
+
"div",
|
|
1227
476
|
{
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
477
|
+
ref: rootRef,
|
|
478
|
+
"data-solvapay-checkout-layout": resolvedSize,
|
|
479
|
+
"data-solvapay-step": step,
|
|
480
|
+
className: "solvapay-checkout-layout",
|
|
481
|
+
children: [
|
|
482
|
+
!planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
|
|
483
|
+
"button",
|
|
484
|
+
{
|
|
485
|
+
type: "button",
|
|
486
|
+
onClick: handleBack,
|
|
487
|
+
"data-solvapay-checkout-back": "",
|
|
488
|
+
className: "solvapay-back",
|
|
489
|
+
children: copy.planSelector.backButton
|
|
490
|
+
}
|
|
491
|
+
),
|
|
492
|
+
step === "select" && /* @__PURE__ */ jsx5(
|
|
493
|
+
SelectStep,
|
|
494
|
+
{
|
|
495
|
+
productRef: productRef ?? "",
|
|
496
|
+
initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
|
|
497
|
+
filter: planSelector?.filter,
|
|
498
|
+
sortBy: planSelector?.sortBy,
|
|
499
|
+
popularPlanRef: planSelector?.popularPlanRef,
|
|
500
|
+
onContinue: handlePlanSelect
|
|
501
|
+
}
|
|
502
|
+
),
|
|
503
|
+
step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
|
|
504
|
+
ActivationFlow2,
|
|
505
|
+
{
|
|
506
|
+
productRef,
|
|
507
|
+
planRef: effectivePlanRef,
|
|
508
|
+
onBack: planRef ? void 0 : handleBack,
|
|
509
|
+
onSuccess: (result) => onResult?.(result),
|
|
510
|
+
onError
|
|
511
|
+
}
|
|
512
|
+
),
|
|
513
|
+
step === "pay" && /* @__PURE__ */ jsx5(
|
|
514
|
+
PaymentForm2,
|
|
515
|
+
{
|
|
516
|
+
planRef: effectivePlanRef,
|
|
517
|
+
productRef,
|
|
518
|
+
prefillCustomer,
|
|
519
|
+
requireTermsAcceptance,
|
|
520
|
+
onSuccess,
|
|
521
|
+
onResult,
|
|
522
|
+
onFreePlan,
|
|
523
|
+
onError,
|
|
524
|
+
submitButtonText,
|
|
525
|
+
returnUrl
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
};
|
|
532
|
+
async function defaultListPlans(productRef, config) {
|
|
533
|
+
const base = config?.api?.listPlans || "/api/list-plans";
|
|
534
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
535
|
+
const fetchFn = config?.fetch || fetch;
|
|
536
|
+
const { headers } = await buildRequestHeaders(config);
|
|
537
|
+
const res = await fetchFn(url, { method: "GET", headers });
|
|
538
|
+
if (!res.ok) {
|
|
539
|
+
const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
|
|
540
|
+
config?.onError?.(error, "listPlans");
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
const data = await res.json();
|
|
544
|
+
return data.plans ?? [];
|
|
545
|
+
}
|
|
546
|
+
var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
|
|
547
|
+
const copy = useCopy();
|
|
548
|
+
const solva = useContext(SolvaPayContext);
|
|
549
|
+
const config = solva?._config;
|
|
550
|
+
const fetcher = useCallback(
|
|
551
|
+
(ref) => defaultListPlans(ref, config),
|
|
552
|
+
[config]
|
|
553
|
+
);
|
|
554
|
+
const { plans, selectedPlan } = usePlans({
|
|
555
|
+
productRef,
|
|
556
|
+
fetcher,
|
|
557
|
+
filter,
|
|
558
|
+
sortBy,
|
|
559
|
+
autoSelectFirstPaid: true,
|
|
560
|
+
initialPlanRef
|
|
561
|
+
});
|
|
562
|
+
const [userSelected, setUserSelected] = useState2(null);
|
|
563
|
+
const selected = userSelected ?? (selectedPlan && selectedPlan.requiresPayment !== false ? { ref: selectedPlan.reference, plan: selectedPlan } : null);
|
|
564
|
+
const autoSkipRef = useRef(false);
|
|
565
|
+
useEffect(() => {
|
|
566
|
+
if (autoSkipRef.current) return;
|
|
567
|
+
const selectable = plans.filter((p) => p.requiresPayment !== false);
|
|
568
|
+
if (plans.length === 1 && selectable.length === 1) {
|
|
569
|
+
autoSkipRef.current = true;
|
|
570
|
+
onContinue(selectable[0].reference, selectable[0]);
|
|
571
|
+
}
|
|
572
|
+
}, [plans, onContinue]);
|
|
573
|
+
const handleSelect = useCallback((ref, plan) => {
|
|
574
|
+
setUserSelected({ ref, plan });
|
|
575
|
+
}, []);
|
|
576
|
+
const continueLabel = useMemo(() => copy.planSelector.continueButton, [copy]);
|
|
577
|
+
return /* @__PURE__ */ jsxs5(Fragment5, { children: [
|
|
1244
578
|
/* @__PURE__ */ jsx5(
|
|
1245
|
-
|
|
579
|
+
PlanSelector2,
|
|
1246
580
|
{
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
581
|
+
productRef,
|
|
582
|
+
initialPlanRef,
|
|
583
|
+
filter,
|
|
584
|
+
sortBy,
|
|
585
|
+
popularPlanRef,
|
|
586
|
+
autoSelectFirstPaid: true,
|
|
587
|
+
onSelect: handleSelect
|
|
1254
588
|
}
|
|
1255
589
|
),
|
|
1256
590
|
/* @__PURE__ */ jsx5(
|
|
1257
591
|
"button",
|
|
1258
592
|
{
|
|
1259
|
-
type: "
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
"
|
|
1263
|
-
|
|
1264
|
-
|
|
593
|
+
type: "button",
|
|
594
|
+
className: "solvapay-continue",
|
|
595
|
+
"data-solvapay-checkout-continue": "",
|
|
596
|
+
"data-state": selected ? "idle" : "disabled",
|
|
597
|
+
disabled: !selected,
|
|
598
|
+
onClick: () => {
|
|
599
|
+
if (selected) onContinue(selected.ref, selected.plan);
|
|
600
|
+
},
|
|
601
|
+
children: continueLabel
|
|
1265
602
|
}
|
|
1266
603
|
)
|
|
1267
|
-
] })
|
|
604
|
+
] });
|
|
1268
605
|
};
|
|
1269
606
|
|
|
1270
|
-
// src/components/
|
|
1271
|
-
import
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
607
|
+
// src/components/AmountPicker.tsx
|
|
608
|
+
import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
609
|
+
var AmountPicker2 = ({
|
|
610
|
+
currency,
|
|
611
|
+
minAmount,
|
|
612
|
+
maxAmount,
|
|
613
|
+
showCreditEstimate = true,
|
|
614
|
+
onChange,
|
|
1276
615
|
className
|
|
1277
616
|
}) => {
|
|
1278
|
-
const
|
|
1279
|
-
const [hasLoadedOnce, setHasLoadedOnce] = React5.useState(false);
|
|
1280
|
-
React5.useEffect(() => {
|
|
1281
|
-
if (!loading) {
|
|
1282
|
-
setHasLoadedOnce(true);
|
|
1283
|
-
}
|
|
1284
|
-
}, [loading]);
|
|
1285
|
-
const planToDisplay = activePurchase?.productName || null;
|
|
1286
|
-
const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
|
|
1287
|
-
if (children) {
|
|
1288
|
-
return /* @__PURE__ */ jsx6(Fragment2, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
1289
|
-
}
|
|
1290
|
-
if (!shouldShow) {
|
|
1291
|
-
return null;
|
|
1292
|
-
}
|
|
1293
|
-
const computedClassName = typeof className === "function" ? className({ purchases }) : className;
|
|
617
|
+
const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
|
|
1294
618
|
return /* @__PURE__ */ jsx6(
|
|
1295
|
-
|
|
619
|
+
AmountPicker.Root,
|
|
1296
620
|
{
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
"aria-busy": loading,
|
|
1304
|
-
"aria-label": `Current product: ${planToDisplay}`,
|
|
1305
|
-
children: planToDisplay
|
|
621
|
+
currency,
|
|
622
|
+
minAmount,
|
|
623
|
+
maxAmount,
|
|
624
|
+
onChange,
|
|
625
|
+
className: rootClass,
|
|
626
|
+
children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
|
|
1306
627
|
}
|
|
1307
628
|
);
|
|
1308
629
|
};
|
|
1309
|
-
var
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
630
|
+
var DefaultTree3 = ({ showCreditEstimate }) => {
|
|
631
|
+
const ctx = useAmountPicker();
|
|
632
|
+
const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
|
|
633
|
+
return /* @__PURE__ */ jsxs6(Fragment6, { children: [
|
|
634
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
|
|
635
|
+
/* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
|
|
636
|
+
AmountPicker.Option,
|
|
637
|
+
{
|
|
638
|
+
amount,
|
|
639
|
+
className: "solvapay-amount-picker-pill"
|
|
640
|
+
},
|
|
641
|
+
amount
|
|
642
|
+
)) }),
|
|
643
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
|
|
644
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
|
|
645
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-row", children: [
|
|
646
|
+
/* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
|
|
647
|
+
/* @__PURE__ */ jsx6(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
|
|
648
|
+
] })
|
|
649
|
+
] }),
|
|
650
|
+
showCreditEstimate && ctx.estimatedCredits != null && /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-credit-estimate", children: creditEstimate(ctx.estimatedCredits) }),
|
|
651
|
+
ctx.error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "solvapay-amount-picker-error", children: ctx.error })
|
|
652
|
+
] });
|
|
1322
653
|
};
|
|
1323
654
|
|
|
1324
|
-
// src/components/
|
|
1325
|
-
import {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
productRef,
|
|
1352
|
-
filter,
|
|
1353
|
-
sortBy,
|
|
1354
|
-
autoSelectFirstPaid = false,
|
|
1355
|
-
initialPlanRef,
|
|
1356
|
-
selectionReady = true
|
|
1357
|
-
} = options;
|
|
1358
|
-
const fetcherRef = useRef6(fetcher);
|
|
1359
|
-
const filterRef = useRef6(filter);
|
|
1360
|
-
const sortByRef = useRef6(sortBy);
|
|
1361
|
-
const autoSelectFirstPaidRef = useRef6(autoSelectFirstPaid);
|
|
1362
|
-
const initialPlanRefRef = useRef6(initialPlanRef);
|
|
1363
|
-
const selectionReadyRef = useRef6(selectionReady);
|
|
1364
|
-
const hasAppliedInitialRef = useRef6(false);
|
|
1365
|
-
const userHasSelectedRef = useRef6(false);
|
|
1366
|
-
const [selectedPlanIndex, setSelectedPlanIndexState] = useState6(() => {
|
|
1367
|
-
if (!selectionReady || !productRef) return 0;
|
|
1368
|
-
const cached = plansCache.get(productRef);
|
|
1369
|
-
if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
|
|
1370
|
-
return 0;
|
|
1371
|
-
}
|
|
1372
|
-
const processed = processPlans(cached.plans, filter, sortBy);
|
|
1373
|
-
if (processed.length === 0) return 0;
|
|
1374
|
-
const idx = computeInitialIndex(processed, initialPlanRef, autoSelectFirstPaid);
|
|
1375
|
-
hasAppliedInitialRef.current = true;
|
|
1376
|
-
return idx;
|
|
1377
|
-
});
|
|
1378
|
-
const [plans, setPlans] = useState6(() => {
|
|
1379
|
-
if (!productRef) return [];
|
|
1380
|
-
const cached = plansCache.get(productRef);
|
|
1381
|
-
if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
|
|
1382
|
-
return [];
|
|
655
|
+
// src/components/CancelledPlanNotice.tsx
|
|
656
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
657
|
+
var CancelledPlanNotice2 = ({
|
|
658
|
+
onReactivated,
|
|
659
|
+
onError,
|
|
660
|
+
className
|
|
661
|
+
}) => {
|
|
662
|
+
const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
|
|
663
|
+
return /* @__PURE__ */ jsxs7(
|
|
664
|
+
CancelledPlanNotice.Root,
|
|
665
|
+
{
|
|
666
|
+
onReactivated,
|
|
667
|
+
onError,
|
|
668
|
+
className: rootClass,
|
|
669
|
+
children: [
|
|
670
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
|
|
671
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-details", children: [
|
|
672
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
|
|
673
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
|
|
674
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
|
|
675
|
+
] }),
|
|
676
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-footer", children: [
|
|
677
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
|
|
678
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
|
|
681
|
+
]
|
|
1383
682
|
}
|
|
1384
|
-
return processPlans(cached.plans, filter, sortBy);
|
|
1385
|
-
});
|
|
1386
|
-
const [loading, setLoading] = useState6(() => plans.length === 0);
|
|
1387
|
-
const [error, setError] = useState6(null);
|
|
1388
|
-
useEffect4(() => {
|
|
1389
|
-
fetcherRef.current = fetcher;
|
|
1390
|
-
}, [fetcher]);
|
|
1391
|
-
useEffect4(() => {
|
|
1392
|
-
filterRef.current = filter;
|
|
1393
|
-
}, [filter]);
|
|
1394
|
-
useEffect4(() => {
|
|
1395
|
-
sortByRef.current = sortBy;
|
|
1396
|
-
}, [sortBy]);
|
|
1397
|
-
useEffect4(() => {
|
|
1398
|
-
autoSelectFirstPaidRef.current = autoSelectFirstPaid;
|
|
1399
|
-
}, [autoSelectFirstPaid]);
|
|
1400
|
-
useEffect4(() => {
|
|
1401
|
-
initialPlanRefRef.current = initialPlanRef;
|
|
1402
|
-
}, [initialPlanRef]);
|
|
1403
|
-
useEffect4(() => {
|
|
1404
|
-
selectionReadyRef.current = selectionReady;
|
|
1405
|
-
}, [selectionReady]);
|
|
1406
|
-
const setSelectedPlanIndex = useCallback6((index) => {
|
|
1407
|
-
userHasSelectedRef.current = true;
|
|
1408
|
-
setSelectedPlanIndexState(index);
|
|
1409
|
-
}, []);
|
|
1410
|
-
const applyInitialSelection = useCallback6((processedPlans) => {
|
|
1411
|
-
if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
|
|
1412
|
-
if (!selectionReadyRef.current || processedPlans.length === 0) return;
|
|
1413
|
-
hasAppliedInitialRef.current = true;
|
|
1414
|
-
const idx = computeInitialIndex(
|
|
1415
|
-
processedPlans,
|
|
1416
|
-
initialPlanRefRef.current,
|
|
1417
|
-
autoSelectFirstPaidRef.current
|
|
1418
|
-
);
|
|
1419
|
-
setSelectedPlanIndexState(idx);
|
|
1420
|
-
}, []);
|
|
1421
|
-
const fetchPlans = useCallback6(
|
|
1422
|
-
async (force = false) => {
|
|
1423
|
-
if (!productRef) {
|
|
1424
|
-
setError(new Error("Product reference not configured"));
|
|
1425
|
-
setLoading(false);
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
const cached = plansCache.get(productRef);
|
|
1429
|
-
const now = Date.now();
|
|
1430
|
-
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1431
|
-
const processedPlans = processPlans(
|
|
1432
|
-
cached.plans,
|
|
1433
|
-
filterRef.current,
|
|
1434
|
-
sortByRef.current
|
|
1435
|
-
);
|
|
1436
|
-
setPlans(processedPlans);
|
|
1437
|
-
setLoading(false);
|
|
1438
|
-
setError(null);
|
|
1439
|
-
applyInitialSelection(processedPlans);
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
if (cached?.promise) {
|
|
1443
|
-
try {
|
|
1444
|
-
setLoading(true);
|
|
1445
|
-
const fetchedPlans = await cached.promise;
|
|
1446
|
-
const processedPlans = processPlans(
|
|
1447
|
-
fetchedPlans,
|
|
1448
|
-
filterRef.current,
|
|
1449
|
-
sortByRef.current
|
|
1450
|
-
);
|
|
1451
|
-
setPlans(processedPlans);
|
|
1452
|
-
setError(null);
|
|
1453
|
-
applyInitialSelection(processedPlans);
|
|
1454
|
-
} catch (err) {
|
|
1455
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1456
|
-
} finally {
|
|
1457
|
-
setLoading(false);
|
|
1458
|
-
}
|
|
1459
|
-
return;
|
|
1460
|
-
}
|
|
1461
|
-
try {
|
|
1462
|
-
setLoading(true);
|
|
1463
|
-
setError(null);
|
|
1464
|
-
const fetchPromise = fetcherRef.current(productRef);
|
|
1465
|
-
plansCache.set(productRef, { plans: [], timestamp: now, promise: fetchPromise });
|
|
1466
|
-
const fetchedPlans = await fetchPromise;
|
|
1467
|
-
plansCache.set(productRef, { plans: fetchedPlans, timestamp: now, promise: null });
|
|
1468
|
-
const processedPlans = processPlans(
|
|
1469
|
-
fetchedPlans,
|
|
1470
|
-
filterRef.current,
|
|
1471
|
-
sortByRef.current
|
|
1472
|
-
);
|
|
1473
|
-
setPlans(processedPlans);
|
|
1474
|
-
applyInitialSelection(processedPlans);
|
|
1475
|
-
} catch (err) {
|
|
1476
|
-
plansCache.delete(productRef);
|
|
1477
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1478
|
-
} finally {
|
|
1479
|
-
setLoading(false);
|
|
1480
|
-
}
|
|
1481
|
-
},
|
|
1482
|
-
[productRef, applyInitialSelection]
|
|
1483
|
-
);
|
|
1484
|
-
useEffect4(() => {
|
|
1485
|
-
fetchPlans();
|
|
1486
|
-
}, [fetchPlans]);
|
|
1487
|
-
useEffect4(() => {
|
|
1488
|
-
if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
|
|
1489
|
-
if (!selectionReady || plans.length === 0) return;
|
|
1490
|
-
applyInitialSelection(plans);
|
|
1491
|
-
}, [selectionReady, plans, applyInitialSelection]);
|
|
1492
|
-
const selectedPlan = useMemo4(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
|
|
1493
|
-
const selectPlan = useCallback6(
|
|
1494
|
-
(planRef) => {
|
|
1495
|
-
const index = plans.findIndex((p) => p.reference === planRef);
|
|
1496
|
-
if (index >= 0) {
|
|
1497
|
-
setSelectedPlanIndex(index);
|
|
1498
|
-
}
|
|
1499
|
-
},
|
|
1500
|
-
[plans, setSelectedPlanIndex]
|
|
1501
683
|
);
|
|
1502
|
-
|
|
1503
|
-
plans,
|
|
1504
|
-
loading,
|
|
1505
|
-
error,
|
|
1506
|
-
selectedPlanIndex,
|
|
1507
|
-
selectedPlan,
|
|
1508
|
-
setSelectedPlanIndex,
|
|
1509
|
-
selectPlan,
|
|
1510
|
-
refetch: () => fetchPlans(true),
|
|
1511
|
-
isSelectionReady: hasAppliedInitialRef.current
|
|
1512
|
-
};
|
|
1513
|
-
}
|
|
684
|
+
};
|
|
1514
685
|
|
|
1515
|
-
// src/components/
|
|
1516
|
-
import { Fragment as
|
|
1517
|
-
var
|
|
686
|
+
// src/components/CreditGate.tsx
|
|
687
|
+
import { Fragment as Fragment7, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
688
|
+
var AllowedContent = ({ children }) => {
|
|
689
|
+
const ctx = useCreditGate();
|
|
690
|
+
if (ctx.state !== "allowed") return null;
|
|
691
|
+
return /* @__PURE__ */ jsx8(Fragment7, { children });
|
|
692
|
+
};
|
|
693
|
+
var DefaultBlockedTree = ({ fallback }) => {
|
|
694
|
+
const ctx = useCreditGate();
|
|
695
|
+
if (ctx.state !== "blocked") return null;
|
|
696
|
+
if (fallback) return /* @__PURE__ */ jsx8(Fragment7, { children: fallback });
|
|
697
|
+
return /* @__PURE__ */ jsxs8(Fragment7, { children: [
|
|
698
|
+
/* @__PURE__ */ jsx8(CreditGate.Heading, { className: "solvapay-credit-gate-heading" }),
|
|
699
|
+
/* @__PURE__ */ jsx8(CreditGate.Subheading, { className: "solvapay-credit-gate-subheading" }),
|
|
700
|
+
/* @__PURE__ */ jsx8(CreditGate.Topup, {})
|
|
701
|
+
] });
|
|
702
|
+
};
|
|
703
|
+
var CreditGate2 = ({
|
|
704
|
+
minCredits,
|
|
1518
705
|
productRef,
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
706
|
+
topupAmount,
|
|
707
|
+
topupCurrency,
|
|
708
|
+
fallback,
|
|
709
|
+
className,
|
|
1523
710
|
children
|
|
1524
711
|
}) => {
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
},
|
|
1539
|
-
[plans]
|
|
1540
|
-
);
|
|
1541
|
-
const activePurchase = useMemo5(() => {
|
|
1542
|
-
const activePurchases = purchases.filter((p) => p.status === "active");
|
|
1543
|
-
return activePurchases.sort(
|
|
1544
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1545
|
-
)[0] || null;
|
|
1546
|
-
}, [purchases]);
|
|
1547
|
-
const isCurrentPlan = useCallback7(
|
|
1548
|
-
(planRef) => {
|
|
1549
|
-
return activePurchase?.planSnapshot?.reference === planRef;
|
|
1550
|
-
},
|
|
1551
|
-
[activePurchase]
|
|
1552
|
-
);
|
|
1553
|
-
return /* @__PURE__ */ jsx8(Fragment4, { children: children({
|
|
1554
|
-
...plansHook,
|
|
1555
|
-
purchases,
|
|
1556
|
-
isPaidPlan,
|
|
1557
|
-
isCurrentPlan
|
|
1558
|
-
}) });
|
|
1559
|
-
};
|
|
1560
|
-
var PlanSelector = PricingSelector;
|
|
1561
|
-
|
|
1562
|
-
// src/hooks/useBalance.ts
|
|
1563
|
-
import { useEffect as useEffect5 } from "react";
|
|
1564
|
-
function useBalance() {
|
|
1565
|
-
const { balance } = useSolvaPay();
|
|
1566
|
-
useEffect5(() => {
|
|
1567
|
-
balance.refetch();
|
|
1568
|
-
}, [balance.refetch]);
|
|
1569
|
-
return balance;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
// src/components/BalanceBadge.tsx
|
|
1573
|
-
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1574
|
-
function BalanceBadge({ className, numberOnly, children }) {
|
|
1575
|
-
const { credits, displayCurrency, creditsPerMinorUnit, displayExchangeRate, loading } = useBalance();
|
|
1576
|
-
if (children) {
|
|
1577
|
-
return /* @__PURE__ */ jsx9(Fragment5, { children: children({ credits, loading, displayCurrency, creditsPerMinorUnit }) });
|
|
1578
|
-
}
|
|
1579
|
-
if (loading) {
|
|
1580
|
-
return /* @__PURE__ */ jsx9("span", { className, "aria-busy": "true" });
|
|
1581
|
-
}
|
|
1582
|
-
if (credits == null) {
|
|
1583
|
-
return null;
|
|
1584
|
-
}
|
|
1585
|
-
const formattedCredits = new Intl.NumberFormat().format(credits);
|
|
1586
|
-
if (numberOnly) {
|
|
1587
|
-
return /* @__PURE__ */ jsx9("span", { className, children: formattedCredits });
|
|
1588
|
-
}
|
|
1589
|
-
let currencyEquivalent = "";
|
|
1590
|
-
if (displayCurrency && creditsPerMinorUnit) {
|
|
1591
|
-
const usdCents = credits / creditsPerMinorUnit;
|
|
1592
|
-
const displayMajorUnits = usdCents * (displayExchangeRate ?? 1) / 100;
|
|
1593
|
-
currencyEquivalent = ` (~${new Intl.NumberFormat(void 0, {
|
|
1594
|
-
style: "currency",
|
|
1595
|
-
currency: displayCurrency,
|
|
1596
|
-
minimumFractionDigits: 2
|
|
1597
|
-
}).format(displayMajorUnits)})`;
|
|
1598
|
-
}
|
|
1599
|
-
return /* @__PURE__ */ jsxs5("span", { className, children: [
|
|
1600
|
-
formattedCredits,
|
|
1601
|
-
" credits",
|
|
1602
|
-
currencyEquivalent
|
|
1603
|
-
] });
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
// src/hooks/usePurchaseStatus.ts
|
|
1607
|
-
import { useMemo as useMemo6, useCallback as useCallback8 } from "react";
|
|
1608
|
-
function usePurchaseStatus() {
|
|
1609
|
-
const { purchases } = usePurchase();
|
|
1610
|
-
const isPaidPurchase2 = useCallback8((p) => {
|
|
1611
|
-
return (p.amount ?? 0) > 0;
|
|
1612
|
-
}, []);
|
|
1613
|
-
const purchaseData = useMemo6(() => {
|
|
1614
|
-
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
1615
|
-
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
1616
|
-
});
|
|
1617
|
-
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
1618
|
-
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1619
|
-
)[0] || null;
|
|
1620
|
-
const shouldShowCancelledNotice = !!cancelledPurchase;
|
|
1621
|
-
return {
|
|
1622
|
-
cancelledPurchase,
|
|
1623
|
-
shouldShowCancelledNotice
|
|
1624
|
-
};
|
|
1625
|
-
}, [purchases, isPaidPurchase2]);
|
|
1626
|
-
const formatDate = useCallback8((dateString) => {
|
|
1627
|
-
if (!dateString) return null;
|
|
1628
|
-
return new Date(dateString).toLocaleDateString("en-US", {
|
|
1629
|
-
year: "numeric",
|
|
1630
|
-
month: "long",
|
|
1631
|
-
day: "numeric"
|
|
1632
|
-
});
|
|
1633
|
-
}, []);
|
|
1634
|
-
const getDaysUntilExpiration = useCallback8((endDate) => {
|
|
1635
|
-
if (!endDate) return null;
|
|
1636
|
-
const now = /* @__PURE__ */ new Date();
|
|
1637
|
-
const expiration = new Date(endDate);
|
|
1638
|
-
const diffTime = expiration.getTime() - now.getTime();
|
|
1639
|
-
const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
|
|
1640
|
-
return diffDays > 0 ? diffDays : 0;
|
|
1641
|
-
}, []);
|
|
1642
|
-
return {
|
|
1643
|
-
cancelledPurchase: purchaseData.cancelledPurchase,
|
|
1644
|
-
shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
|
|
1645
|
-
formatDate,
|
|
1646
|
-
getDaysUntilExpiration
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
// src/hooks/usePurchaseActions.ts
|
|
1651
|
-
import { useState as useState7, useCallback as useCallback9 } from "react";
|
|
1652
|
-
function usePurchaseActions() {
|
|
1653
|
-
const ctx = useSolvaPay();
|
|
1654
|
-
const [isCancelling, setIsCancelling] = useState7(false);
|
|
1655
|
-
const [isReactivating, setIsReactivating] = useState7(false);
|
|
1656
|
-
const [isActivating, setIsActivating] = useState7(false);
|
|
1657
|
-
const cancelRenewal = useCallback9(
|
|
1658
|
-
async (params) => {
|
|
1659
|
-
setIsCancelling(true);
|
|
1660
|
-
try {
|
|
1661
|
-
return await ctx.cancelRenewal(params);
|
|
1662
|
-
} finally {
|
|
1663
|
-
setIsCancelling(false);
|
|
1664
|
-
}
|
|
1665
|
-
},
|
|
1666
|
-
[ctx.cancelRenewal]
|
|
1667
|
-
);
|
|
1668
|
-
const reactivateRenewal = useCallback9(
|
|
1669
|
-
async (params) => {
|
|
1670
|
-
setIsReactivating(true);
|
|
1671
|
-
try {
|
|
1672
|
-
return await ctx.reactivateRenewal(params);
|
|
1673
|
-
} finally {
|
|
1674
|
-
setIsReactivating(false);
|
|
1675
|
-
}
|
|
1676
|
-
},
|
|
1677
|
-
[ctx.reactivateRenewal]
|
|
1678
|
-
);
|
|
1679
|
-
const activatePlan = useCallback9(
|
|
1680
|
-
async (params) => {
|
|
1681
|
-
setIsActivating(true);
|
|
1682
|
-
try {
|
|
1683
|
-
return await ctx.activatePlan(params);
|
|
1684
|
-
} finally {
|
|
1685
|
-
setIsActivating(false);
|
|
1686
|
-
}
|
|
1687
|
-
},
|
|
1688
|
-
[ctx.activatePlan]
|
|
1689
|
-
);
|
|
1690
|
-
return {
|
|
1691
|
-
cancelRenewal,
|
|
1692
|
-
reactivateRenewal,
|
|
1693
|
-
activatePlan,
|
|
1694
|
-
isCancelling,
|
|
1695
|
-
isReactivating,
|
|
1696
|
-
isActivating
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
// src/hooks/useActivation.ts
|
|
1701
|
-
import { useState as useState8, useCallback as useCallback10 } from "react";
|
|
1702
|
-
function useActivation() {
|
|
1703
|
-
const { activatePlan } = useSolvaPay();
|
|
1704
|
-
const [state, setState] = useState8("idle");
|
|
1705
|
-
const [error, setError] = useState8(null);
|
|
1706
|
-
const [result, setResult] = useState8(null);
|
|
1707
|
-
const activate = useCallback10(
|
|
1708
|
-
async (params) => {
|
|
1709
|
-
setState("activating");
|
|
1710
|
-
setError(null);
|
|
1711
|
-
setResult(null);
|
|
1712
|
-
try {
|
|
1713
|
-
const data = await activatePlan(params);
|
|
1714
|
-
setResult(data);
|
|
1715
|
-
switch (data.status) {
|
|
1716
|
-
case "activated":
|
|
1717
|
-
case "already_active":
|
|
1718
|
-
setState("activated");
|
|
1719
|
-
break;
|
|
1720
|
-
case "topup_required":
|
|
1721
|
-
setState("topup_required");
|
|
1722
|
-
break;
|
|
1723
|
-
case "payment_required":
|
|
1724
|
-
setError("This plan requires payment. Please select a different plan.");
|
|
1725
|
-
setState("payment_required");
|
|
1726
|
-
break;
|
|
1727
|
-
case "invalid":
|
|
1728
|
-
setError(data.message || "Invalid plan configuration.");
|
|
1729
|
-
setState("error");
|
|
1730
|
-
break;
|
|
1731
|
-
default:
|
|
1732
|
-
setError("Unexpected response from server.");
|
|
1733
|
-
setState("error");
|
|
1734
|
-
}
|
|
1735
|
-
} catch (err) {
|
|
1736
|
-
setError(err instanceof Error ? err.message : "Activation failed");
|
|
1737
|
-
setState("error");
|
|
1738
|
-
}
|
|
1739
|
-
},
|
|
1740
|
-
[activatePlan]
|
|
1741
|
-
);
|
|
1742
|
-
const reset = useCallback10(() => {
|
|
1743
|
-
setState("idle");
|
|
1744
|
-
setError(null);
|
|
1745
|
-
setResult(null);
|
|
1746
|
-
}, []);
|
|
1747
|
-
return { activate, state, error, result, reset };
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
// src/hooks/useTopupAmountSelector.ts
|
|
1751
|
-
import { useState as useState9, useCallback as useCallback11, useMemo as useMemo7 } from "react";
|
|
1752
|
-
function getQuickAmounts(currency) {
|
|
1753
|
-
switch (currency.toUpperCase()) {
|
|
1754
|
-
case "SEK":
|
|
1755
|
-
case "NOK":
|
|
1756
|
-
case "DKK":
|
|
1757
|
-
return [100, 500, 1e3, 5e3];
|
|
1758
|
-
case "JPY":
|
|
1759
|
-
return [1e3, 5e3, 1e4, 5e4];
|
|
1760
|
-
case "KRW":
|
|
1761
|
-
return [1e4, 5e4, 1e5, 5e5];
|
|
1762
|
-
case "ISK":
|
|
1763
|
-
case "HUF":
|
|
1764
|
-
return [1e3, 5e3, 1e4, 5e4];
|
|
1765
|
-
default:
|
|
1766
|
-
return [10, 50, 100, 500];
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
function getCurrencySymbol(currency) {
|
|
1770
|
-
try {
|
|
1771
|
-
const parts = new Intl.NumberFormat("en", { style: "currency", currency }).formatToParts(0);
|
|
1772
|
-
const sym = parts.find((p) => p.type === "currency");
|
|
1773
|
-
return sym?.value || currency;
|
|
1774
|
-
} catch {
|
|
1775
|
-
return currency;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
var DEFAULT_MIN = 1;
|
|
1779
|
-
var DEFAULT_MAX = 1e5;
|
|
1780
|
-
function useTopupAmountSelector(options) {
|
|
1781
|
-
const { currency, minAmount = DEFAULT_MIN, maxAmount = DEFAULT_MAX } = options;
|
|
1782
|
-
const [selectedAmount, setSelectedAmount] = useState9(null);
|
|
1783
|
-
const [customAmount, setCustomAmountRaw] = useState9("");
|
|
1784
|
-
const [error, setError] = useState9(null);
|
|
1785
|
-
const quickAmounts = useMemo7(() => getQuickAmounts(currency), [currency]);
|
|
1786
|
-
const currencySymbol = useMemo7(() => getCurrencySymbol(currency), [currency]);
|
|
1787
|
-
const resolvedAmount = useMemo7(() => {
|
|
1788
|
-
if (customAmount) {
|
|
1789
|
-
const parsed = parseFloat(customAmount);
|
|
1790
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
712
|
+
const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
|
|
713
|
+
return /* @__PURE__ */ jsxs8(
|
|
714
|
+
CreditGate.Root,
|
|
715
|
+
{
|
|
716
|
+
minCredits,
|
|
717
|
+
productRef,
|
|
718
|
+
topupAmount,
|
|
719
|
+
topupCurrency,
|
|
720
|
+
className: rootClass,
|
|
721
|
+
children: [
|
|
722
|
+
/* @__PURE__ */ jsx8(AllowedContent, { children }),
|
|
723
|
+
/* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
|
|
724
|
+
]
|
|
1791
725
|
}
|
|
1792
|
-
return selectedAmount;
|
|
1793
|
-
}, [selectedAmount, customAmount]);
|
|
1794
|
-
const selectQuickAmount = useCallback11(
|
|
1795
|
-
(amount) => {
|
|
1796
|
-
setSelectedAmount(amount);
|
|
1797
|
-
setCustomAmountRaw("");
|
|
1798
|
-
setError(null);
|
|
1799
|
-
},
|
|
1800
|
-
[]
|
|
1801
|
-
);
|
|
1802
|
-
const setCustomAmount = useCallback11(
|
|
1803
|
-
(value) => {
|
|
1804
|
-
const sanitized = value.replace(/[^0-9.]/g, "");
|
|
1805
|
-
const dotIndex = sanitized.indexOf(".");
|
|
1806
|
-
const cleaned = dotIndex === -1 ? sanitized : sanitized.slice(0, dotIndex + 1) + sanitized.slice(dotIndex + 1).replace(/\./g, "");
|
|
1807
|
-
setCustomAmountRaw(cleaned);
|
|
1808
|
-
setSelectedAmount(null);
|
|
1809
|
-
setError(null);
|
|
1810
|
-
},
|
|
1811
|
-
[]
|
|
1812
726
|
);
|
|
1813
|
-
|
|
1814
|
-
if (resolvedAmount == null) {
|
|
1815
|
-
setError("Please select or enter an amount");
|
|
1816
|
-
return false;
|
|
1817
|
-
}
|
|
1818
|
-
if (resolvedAmount < minAmount) {
|
|
1819
|
-
setError(`Minimum amount is ${currencySymbol}${minAmount}`);
|
|
1820
|
-
return false;
|
|
1821
|
-
}
|
|
1822
|
-
if (resolvedAmount > maxAmount) {
|
|
1823
|
-
setError(`Maximum amount is ${currencySymbol}${maxAmount.toLocaleString()}`);
|
|
1824
|
-
return false;
|
|
1825
|
-
}
|
|
1826
|
-
setError(null);
|
|
1827
|
-
return true;
|
|
1828
|
-
}, [resolvedAmount, minAmount, maxAmount, currencySymbol]);
|
|
1829
|
-
const reset = useCallback11(() => {
|
|
1830
|
-
setSelectedAmount(null);
|
|
1831
|
-
setCustomAmountRaw("");
|
|
1832
|
-
setError(null);
|
|
1833
|
-
}, []);
|
|
1834
|
-
return {
|
|
1835
|
-
quickAmounts,
|
|
1836
|
-
selectedAmount,
|
|
1837
|
-
customAmount,
|
|
1838
|
-
resolvedAmount,
|
|
1839
|
-
selectQuickAmount,
|
|
1840
|
-
setCustomAmount,
|
|
1841
|
-
error,
|
|
1842
|
-
validate,
|
|
1843
|
-
reset,
|
|
1844
|
-
currencySymbol
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
727
|
+
};
|
|
1847
728
|
export {
|
|
729
|
+
ActivationFlow2 as ActivationFlow,
|
|
730
|
+
AmountPicker2 as AmountPicker,
|
|
1848
731
|
BalanceBadge,
|
|
1849
|
-
|
|
732
|
+
CancelPlanButton,
|
|
733
|
+
CancelledPlanNotice2 as CancelledPlanNotice,
|
|
734
|
+
CheckoutLayout,
|
|
735
|
+
CheckoutSummary,
|
|
736
|
+
CopyContext,
|
|
737
|
+
CopyProvider,
|
|
738
|
+
CreditGate2 as CreditGate,
|
|
739
|
+
MandateText,
|
|
740
|
+
PaymentForm2 as PaymentForm,
|
|
741
|
+
PaymentFormContext,
|
|
742
|
+
PaymentFormProvider,
|
|
1850
743
|
PlanBadge,
|
|
1851
|
-
PlanSelector,
|
|
1852
|
-
PricingSelector,
|
|
744
|
+
PlanSelector2 as PlanSelector,
|
|
1853
745
|
ProductBadge,
|
|
1854
746
|
PurchaseGate,
|
|
1855
747
|
SolvaPayProvider,
|
|
1856
748
|
Spinner,
|
|
1857
749
|
StripePaymentFormWrapper,
|
|
1858
750
|
TopupForm,
|
|
751
|
+
confirmPayment,
|
|
1859
752
|
defaultAuthAdapter,
|
|
753
|
+
deriveVariant,
|
|
754
|
+
enCopy,
|
|
1860
755
|
filterPurchases,
|
|
756
|
+
formatPrice,
|
|
1861
757
|
getActivePurchases,
|
|
1862
758
|
getCancelledPurchasesWithEndDate,
|
|
1863
759
|
getMostRecentPurchase,
|
|
1864
760
|
getPrimaryPurchase,
|
|
761
|
+
interpolate,
|
|
1865
762
|
isPaidPurchase,
|
|
763
|
+
mergeCopy,
|
|
764
|
+
resolveCta,
|
|
1866
765
|
useActivation,
|
|
1867
766
|
useBalance,
|
|
1868
767
|
useCheckout,
|
|
768
|
+
useCopy,
|
|
1869
769
|
useCustomer,
|
|
770
|
+
useLocale,
|
|
771
|
+
useMerchant,
|
|
772
|
+
usePaymentForm,
|
|
773
|
+
usePlan,
|
|
1870
774
|
usePlans,
|
|
775
|
+
useProduct,
|
|
1871
776
|
usePurchase,
|
|
1872
777
|
usePurchaseActions,
|
|
1873
778
|
usePurchaseStatus,
|