@solvapay/react 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.cjs +954 -294
- package/dist/index.d.cts +316 -113
- package/dist/index.d.ts +316 -113
- package/dist/index.js +946 -293
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.tsx
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BalanceBadge: () => BalanceBadge,
|
|
33
34
|
PaymentForm: () => PaymentForm,
|
|
34
35
|
PlanBadge: () => PlanBadge,
|
|
35
36
|
PlanSelector: () => PlanSelector,
|
|
@@ -39,6 +40,7 @@ __export(index_exports, {
|
|
|
39
40
|
SolvaPayProvider: () => SolvaPayProvider,
|
|
40
41
|
Spinner: () => Spinner,
|
|
41
42
|
StripePaymentFormWrapper: () => StripePaymentFormWrapper,
|
|
43
|
+
TopupForm: () => TopupForm,
|
|
42
44
|
defaultAuthAdapter: () => defaultAuthAdapter,
|
|
43
45
|
filterPurchases: () => filterPurchases,
|
|
44
46
|
getActivePurchases: () => getActivePurchases,
|
|
@@ -46,12 +48,17 @@ __export(index_exports, {
|
|
|
46
48
|
getMostRecentPurchase: () => getMostRecentPurchase,
|
|
47
49
|
getPrimaryPurchase: () => getPrimaryPurchase,
|
|
48
50
|
isPaidPurchase: () => isPaidPurchase,
|
|
51
|
+
useActivation: () => useActivation,
|
|
52
|
+
useBalance: () => useBalance,
|
|
49
53
|
useCheckout: () => useCheckout,
|
|
50
54
|
useCustomer: () => useCustomer,
|
|
51
55
|
usePlans: () => usePlans,
|
|
52
56
|
usePurchase: () => usePurchase,
|
|
57
|
+
usePurchaseActions: () => usePurchaseActions,
|
|
53
58
|
usePurchaseStatus: () => usePurchaseStatus,
|
|
54
|
-
useSolvaPay: () => useSolvaPay
|
|
59
|
+
useSolvaPay: () => useSolvaPay,
|
|
60
|
+
useTopup: () => useTopup,
|
|
61
|
+
useTopupAmountSelector: () => useTopupAmountSelector
|
|
55
62
|
});
|
|
56
63
|
module.exports = __toCommonJS(index_exports);
|
|
57
64
|
|
|
@@ -110,13 +117,26 @@ var defaultAuthAdapter = {
|
|
|
110
117
|
}
|
|
111
118
|
};
|
|
112
119
|
|
|
113
|
-
// src/
|
|
114
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
115
|
-
var SolvaPayContext = (0, import_react.createContext)(null);
|
|
120
|
+
// src/utils/headers.ts
|
|
116
121
|
var CUSTOMER_REF_KEY = "solvapay_customerRef";
|
|
117
122
|
var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
|
|
118
123
|
var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
|
|
119
|
-
|
|
124
|
+
function getAuthAdapter(config) {
|
|
125
|
+
if (config?.auth?.adapter) {
|
|
126
|
+
return config.auth.adapter;
|
|
127
|
+
}
|
|
128
|
+
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
129
|
+
return {
|
|
130
|
+
async getToken() {
|
|
131
|
+
return config?.auth?.getToken?.() || null;
|
|
132
|
+
},
|
|
133
|
+
async getUserId() {
|
|
134
|
+
return config?.auth?.getUserId?.() || null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return defaultAuthAdapter;
|
|
139
|
+
}
|
|
120
140
|
function getCachedCustomerRef(userId) {
|
|
121
141
|
if (typeof window === "undefined") return null;
|
|
122
142
|
const cached = localStorage.getItem(CUSTOMER_REF_KEY);
|
|
@@ -137,6 +157,7 @@ function getCachedCustomerRef(userId) {
|
|
|
137
157
|
}
|
|
138
158
|
return cached;
|
|
139
159
|
}
|
|
160
|
+
var CACHE_DURATION = 24 * 60 * 60 * 1e3;
|
|
140
161
|
function setCachedCustomerRef(customerRef, userId) {
|
|
141
162
|
if (typeof window === "undefined") return;
|
|
142
163
|
if (userId === void 0 || userId === null) {
|
|
@@ -152,41 +173,62 @@ function clearCachedCustomerRef() {
|
|
|
152
173
|
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
153
174
|
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
154
175
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
async function buildRequestHeaders(config) {
|
|
177
|
+
const adapter = getAuthAdapter(config);
|
|
178
|
+
const token = await adapter.getToken();
|
|
179
|
+
const userId = await adapter.getUserId();
|
|
180
|
+
const cachedRef = getCachedCustomerRef(userId);
|
|
181
|
+
const headers = {
|
|
182
|
+
"Content-Type": "application/json"
|
|
183
|
+
};
|
|
184
|
+
if (token) {
|
|
185
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
158
186
|
}
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
async getToken() {
|
|
162
|
-
return config?.auth?.getToken?.() || null;
|
|
163
|
-
},
|
|
164
|
-
async getUserId() {
|
|
165
|
-
return config?.auth?.getUserId?.() || null;
|
|
166
|
-
}
|
|
167
|
-
};
|
|
187
|
+
if (cachedRef) {
|
|
188
|
+
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
168
189
|
}
|
|
169
|
-
|
|
190
|
+
if (config?.headers) {
|
|
191
|
+
const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
|
|
192
|
+
Object.assign(headers, custom);
|
|
193
|
+
}
|
|
194
|
+
return { headers, userId };
|
|
170
195
|
}
|
|
196
|
+
|
|
197
|
+
// src/SolvaPayProvider.tsx
|
|
198
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
199
|
+
var SolvaPayContext = (0, import_react.createContext)(null);
|
|
171
200
|
var SolvaPayProvider = ({
|
|
172
201
|
config,
|
|
173
202
|
createPayment: customCreatePayment,
|
|
174
203
|
checkPurchase: customCheckPurchase,
|
|
175
204
|
processPayment: customProcessPayment,
|
|
205
|
+
createTopupPayment: customCreateTopupPayment,
|
|
176
206
|
children
|
|
177
207
|
}) => {
|
|
178
208
|
const [purchaseData, setPurchaseData] = (0, import_react.useState)({
|
|
179
209
|
purchases: []
|
|
180
210
|
});
|
|
181
211
|
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
212
|
+
const [isRefetching, setIsRefetching] = (0, import_react.useState)(false);
|
|
213
|
+
const [purchaseError, setPurchaseError] = (0, import_react.useState)(null);
|
|
182
214
|
const [internalCustomerRef, setInternalCustomerRef] = (0, import_react.useState)(void 0);
|
|
183
215
|
const [userId, setUserId] = (0, import_react.useState)(null);
|
|
184
216
|
const [isAuthenticated, setIsAuthenticated] = (0, import_react.useState)(false);
|
|
217
|
+
const [creditsValue, setCreditsValue] = (0, import_react.useState)(null);
|
|
218
|
+
const [displayCurrencyValue, setDisplayCurrencyValue] = (0, import_react.useState)(null);
|
|
219
|
+
const [creditsPerMinorUnitValue, setCreditsPerMinorUnitValue] = (0, import_react.useState)(null);
|
|
220
|
+
const [displayExchangeRateValue, setDisplayExchangeRateValue] = (0, import_react.useState)(null);
|
|
221
|
+
const [balanceLoading, setBalanceLoading] = (0, import_react.useState)(false);
|
|
222
|
+
const balanceInFlightRef = (0, import_react.useRef)(false);
|
|
223
|
+
const balanceLoadedRef = (0, import_react.useRef)(false);
|
|
224
|
+
const optimisticUntilRef = (0, import_react.useRef)(0);
|
|
225
|
+
const optimisticTimerRef = (0, import_react.useRef)(null);
|
|
226
|
+
const fetchBalanceRef = (0, import_react.useRef)(null);
|
|
185
227
|
const inFlightRef = (0, import_react.useRef)(null);
|
|
186
|
-
const lastFetchedRef = (0, import_react.useRef)(null);
|
|
187
228
|
const checkPurchaseRef = (0, import_react.useRef)(null);
|
|
188
229
|
const createPaymentRef = (0, import_react.useRef)(null);
|
|
189
230
|
const processPaymentRef = (0, import_react.useRef)(null);
|
|
231
|
+
const createTopupPaymentRef = (0, import_react.useRef)(null);
|
|
190
232
|
const configRef = (0, import_react.useRef)(config);
|
|
191
233
|
const buildDefaultCheckPurchaseRef = (0, import_react.useRef)(
|
|
192
234
|
null
|
|
@@ -203,31 +245,15 @@ var SolvaPayProvider = ({
|
|
|
203
245
|
(0, import_react.useEffect)(() => {
|
|
204
246
|
processPaymentRef.current = customProcessPayment || null;
|
|
205
247
|
}, [customProcessPayment]);
|
|
248
|
+
(0, import_react.useEffect)(() => {
|
|
249
|
+
createTopupPaymentRef.current = customCreateTopupPayment || null;
|
|
250
|
+
}, [customCreateTopupPayment]);
|
|
206
251
|
const buildDefaultCheckPurchase = (0, import_react.useCallback)(async () => {
|
|
207
252
|
const currentConfig = configRef.current;
|
|
208
|
-
const
|
|
209
|
-
const token = await adapter.getToken();
|
|
210
|
-
const detectedUserId = await adapter.getUserId();
|
|
253
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
211
254
|
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
212
255
|
const fetchFn = currentConfig?.fetch || fetch;
|
|
213
|
-
const
|
|
214
|
-
const headers = {
|
|
215
|
-
"Content-Type": "application/json"
|
|
216
|
-
};
|
|
217
|
-
if (token) {
|
|
218
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
219
|
-
}
|
|
220
|
-
if (cachedRef) {
|
|
221
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
222
|
-
}
|
|
223
|
-
if (currentConfig?.headers) {
|
|
224
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
225
|
-
Object.assign(headers, customHeaders);
|
|
226
|
-
}
|
|
227
|
-
const res = await fetchFn(route, {
|
|
228
|
-
method: "GET",
|
|
229
|
-
headers
|
|
230
|
-
});
|
|
256
|
+
const res = await fetchFn(route, { method: "GET", headers });
|
|
231
257
|
if (!res.ok) {
|
|
232
258
|
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
233
259
|
currentConfig?.onError?.(error, "checkPurchase");
|
|
@@ -241,26 +267,13 @@ var SolvaPayProvider = ({
|
|
|
241
267
|
const buildDefaultCreatePayment = (0, import_react.useCallback)(
|
|
242
268
|
async (params) => {
|
|
243
269
|
const currentConfig = configRef.current;
|
|
244
|
-
const
|
|
245
|
-
const token = await adapter.getToken();
|
|
246
|
-
const detectedUserId = await adapter.getUserId();
|
|
270
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
247
271
|
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
248
272
|
const fetchFn = currentConfig?.fetch || fetch;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
};
|
|
253
|
-
if (token) {
|
|
254
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
255
|
-
}
|
|
256
|
-
if (cachedRef) {
|
|
257
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
273
|
+
const body = {};
|
|
274
|
+
if (params.planRef) {
|
|
275
|
+
body.planRef = params.planRef;
|
|
258
276
|
}
|
|
259
|
-
if (currentConfig?.headers) {
|
|
260
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
261
|
-
Object.assign(headers, customHeaders);
|
|
262
|
-
}
|
|
263
|
-
const body = { planRef: params.planRef };
|
|
264
277
|
if (params.productRef) {
|
|
265
278
|
body.productRef = params.productRef;
|
|
266
279
|
}
|
|
@@ -281,25 +294,9 @@ var SolvaPayProvider = ({
|
|
|
281
294
|
const buildDefaultProcessPayment = (0, import_react.useCallback)(
|
|
282
295
|
async (params) => {
|
|
283
296
|
const currentConfig = configRef.current;
|
|
284
|
-
const
|
|
285
|
-
const token = await adapter.getToken();
|
|
286
|
-
const detectedUserId = await adapter.getUserId();
|
|
297
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
287
298
|
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
288
299
|
const fetchFn = currentConfig?.fetch || fetch;
|
|
289
|
-
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
290
|
-
const headers = {
|
|
291
|
-
"Content-Type": "application/json"
|
|
292
|
-
};
|
|
293
|
-
if (token) {
|
|
294
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
295
|
-
}
|
|
296
|
-
if (cachedRef) {
|
|
297
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
298
|
-
}
|
|
299
|
-
if (currentConfig?.headers) {
|
|
300
|
-
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
301
|
-
Object.assign(headers, customHeaders);
|
|
302
|
-
}
|
|
303
300
|
const res = await fetchFn(route, {
|
|
304
301
|
method: "POST",
|
|
305
302
|
headers,
|
|
@@ -314,6 +311,86 @@ var SolvaPayProvider = ({
|
|
|
314
311
|
},
|
|
315
312
|
[]
|
|
316
313
|
);
|
|
314
|
+
const buildDefaultCreateTopupPayment = (0, import_react.useCallback)(
|
|
315
|
+
async (params) => {
|
|
316
|
+
const currentConfig = configRef.current;
|
|
317
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
318
|
+
const route = currentConfig?.api?.createTopupPayment || "/api/create-topup-payment-intent";
|
|
319
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
320
|
+
const res = await fetchFn(route, {
|
|
321
|
+
method: "POST",
|
|
322
|
+
headers,
|
|
323
|
+
body: JSON.stringify({
|
|
324
|
+
amount: params.amount,
|
|
325
|
+
currency: params.currency
|
|
326
|
+
})
|
|
327
|
+
});
|
|
328
|
+
if (!res.ok) {
|
|
329
|
+
const error = new Error(`Failed to create topup payment: ${res.statusText}`);
|
|
330
|
+
currentConfig?.onError?.(error, "createTopupPayment");
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
return res.json();
|
|
334
|
+
},
|
|
335
|
+
[]
|
|
336
|
+
);
|
|
337
|
+
const fetchBalanceImpl = (0, import_react.useCallback)(async () => {
|
|
338
|
+
if (optimisticUntilRef.current > Date.now()) return;
|
|
339
|
+
if (!isAuthenticated && !internalCustomerRef) {
|
|
340
|
+
setCreditsValue(null);
|
|
341
|
+
setDisplayCurrencyValue(null);
|
|
342
|
+
setCreditsPerMinorUnitValue(null);
|
|
343
|
+
setDisplayExchangeRateValue(null);
|
|
344
|
+
setBalanceLoading(false);
|
|
345
|
+
balanceLoadedRef.current = false;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (balanceInFlightRef.current) return;
|
|
349
|
+
balanceInFlightRef.current = true;
|
|
350
|
+
if (!balanceLoadedRef.current) {
|
|
351
|
+
setBalanceLoading(true);
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const currentConfig = configRef.current;
|
|
355
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
356
|
+
const route = currentConfig?.api?.customerBalance || "/api/customer-balance";
|
|
357
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
358
|
+
const res = await fetchFn(route, { method: "GET", headers });
|
|
359
|
+
if (!res.ok) {
|
|
360
|
+
console.error("[SolvaPayProvider] Failed to fetch balance:", res.statusText);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const data = await res.json();
|
|
364
|
+
setCreditsValue(data.credits ?? null);
|
|
365
|
+
setDisplayCurrencyValue(data.displayCurrency ?? null);
|
|
366
|
+
setCreditsPerMinorUnitValue(data.creditsPerMinorUnit ?? null);
|
|
367
|
+
setDisplayExchangeRateValue(data.displayExchangeRate ?? null);
|
|
368
|
+
balanceLoadedRef.current = true;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error("[SolvaPayProvider] Failed to fetch balance:", error);
|
|
371
|
+
} finally {
|
|
372
|
+
setBalanceLoading(false);
|
|
373
|
+
balanceInFlightRef.current = false;
|
|
374
|
+
}
|
|
375
|
+
}, [isAuthenticated, internalCustomerRef]);
|
|
376
|
+
(0, import_react.useEffect)(() => {
|
|
377
|
+
fetchBalanceRef.current = fetchBalanceImpl;
|
|
378
|
+
}, [fetchBalanceImpl]);
|
|
379
|
+
(0, import_react.useEffect)(() => {
|
|
380
|
+
return () => {
|
|
381
|
+
if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
|
|
382
|
+
};
|
|
383
|
+
}, []);
|
|
384
|
+
const OPTIMISTIC_GRACE_MS = 8e3;
|
|
385
|
+
const adjustBalanceImpl = (0, import_react.useCallback)((credits) => {
|
|
386
|
+
setCreditsValue((prev) => (prev ?? 0) + credits);
|
|
387
|
+
optimisticUntilRef.current = Date.now() + OPTIMISTIC_GRACE_MS;
|
|
388
|
+
if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
|
|
389
|
+
optimisticTimerRef.current = setTimeout(() => {
|
|
390
|
+
optimisticUntilRef.current = 0;
|
|
391
|
+
fetchBalanceRef.current?.();
|
|
392
|
+
}, OPTIMISTIC_GRACE_MS);
|
|
393
|
+
}, []);
|
|
317
394
|
const _checkPurchase = (0, import_react.useCallback)(async () => {
|
|
318
395
|
if (checkPurchaseRef.current) {
|
|
319
396
|
return checkPurchaseRef.current();
|
|
@@ -341,6 +418,15 @@ var SolvaPayProvider = ({
|
|
|
341
418
|
},
|
|
342
419
|
[buildDefaultProcessPayment]
|
|
343
420
|
);
|
|
421
|
+
const createTopupPayment = (0, import_react.useCallback)(
|
|
422
|
+
async (params) => {
|
|
423
|
+
if (createTopupPaymentRef.current) {
|
|
424
|
+
return createTopupPaymentRef.current(params);
|
|
425
|
+
}
|
|
426
|
+
return buildDefaultCreateTopupPayment(params);
|
|
427
|
+
},
|
|
428
|
+
[buildDefaultCreateTopupPayment]
|
|
429
|
+
);
|
|
344
430
|
(0, import_react.useEffect)(() => {
|
|
345
431
|
const detectAuth = async () => {
|
|
346
432
|
const currentConfig = configRef.current;
|
|
@@ -366,27 +452,30 @@ var SolvaPayProvider = ({
|
|
|
366
452
|
}
|
|
367
453
|
};
|
|
368
454
|
detectAuth();
|
|
369
|
-
const interval = setInterval(detectAuth,
|
|
455
|
+
const interval = setInterval(detectAuth, 3e4);
|
|
370
456
|
return () => clearInterval(interval);
|
|
371
457
|
}, [userId]);
|
|
372
458
|
const fetchPurchase = (0, import_react.useCallback)(
|
|
373
459
|
async (force = false) => {
|
|
374
460
|
if (!isAuthenticated && !internalCustomerRef) {
|
|
375
461
|
setPurchaseData({ purchases: [] });
|
|
462
|
+
setPurchaseError(null);
|
|
376
463
|
setLoading(false);
|
|
464
|
+
setIsRefetching(false);
|
|
377
465
|
inFlightRef.current = null;
|
|
378
|
-
lastFetchedRef.current = null;
|
|
379
466
|
return;
|
|
380
467
|
}
|
|
381
468
|
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
382
|
-
if (
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
if (inFlightRef.current === cacheKey) {
|
|
469
|
+
if (inFlightRef.current === cacheKey && !force) {
|
|
386
470
|
return;
|
|
387
471
|
}
|
|
388
472
|
inFlightRef.current = cacheKey;
|
|
389
|
-
|
|
473
|
+
const hasExistingData = purchaseData.purchases.length > 0;
|
|
474
|
+
if (hasExistingData) {
|
|
475
|
+
setIsRefetching(true);
|
|
476
|
+
} else {
|
|
477
|
+
setLoading(true);
|
|
478
|
+
}
|
|
390
479
|
try {
|
|
391
480
|
const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
|
|
392
481
|
if (!checkFn) {
|
|
@@ -405,43 +494,111 @@ var SolvaPayProvider = ({
|
|
|
405
494
|
purchases: filterPurchases(data.purchases || [])
|
|
406
495
|
};
|
|
407
496
|
setPurchaseData(filteredData);
|
|
408
|
-
|
|
497
|
+
setPurchaseError(null);
|
|
409
498
|
}
|
|
410
|
-
} catch (
|
|
411
|
-
console.error("[SolvaPayProvider] Failed to fetch purchase:",
|
|
499
|
+
} catch (err) {
|
|
500
|
+
console.error("[SolvaPayProvider] Failed to fetch purchase:", err);
|
|
412
501
|
if (inFlightRef.current === cacheKey) {
|
|
413
|
-
|
|
414
|
-
purchases: []
|
|
415
|
-
});
|
|
416
|
-
lastFetchedRef.current = cacheKey;
|
|
502
|
+
setPurchaseError(err instanceof Error ? err : new Error(String(err)));
|
|
417
503
|
}
|
|
418
504
|
} finally {
|
|
419
505
|
if (inFlightRef.current === cacheKey) {
|
|
420
506
|
setLoading(false);
|
|
507
|
+
setIsRefetching(false);
|
|
421
508
|
inFlightRef.current = null;
|
|
422
509
|
}
|
|
423
510
|
}
|
|
424
511
|
},
|
|
425
|
-
[isAuthenticated, internalCustomerRef, userId]
|
|
512
|
+
[isAuthenticated, internalCustomerRef, userId, purchaseData.purchases.length]
|
|
426
513
|
);
|
|
427
514
|
const fetchPurchaseRef = (0, import_react.useRef)(fetchPurchase);
|
|
428
515
|
(0, import_react.useEffect)(() => {
|
|
429
516
|
fetchPurchaseRef.current = fetchPurchase;
|
|
430
517
|
}, [fetchPurchase]);
|
|
431
518
|
const refetchPurchase = (0, import_react.useCallback)(async () => {
|
|
432
|
-
lastFetchedRef.current = null;
|
|
433
519
|
inFlightRef.current = null;
|
|
434
|
-
setPurchaseData({ purchases: [] });
|
|
435
520
|
await fetchPurchaseRef.current(true);
|
|
436
521
|
}, []);
|
|
522
|
+
const cancelRenewal = (0, import_react.useCallback)(
|
|
523
|
+
async (params) => {
|
|
524
|
+
const currentConfig = configRef.current;
|
|
525
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
526
|
+
const route = currentConfig?.api?.cancelRenewal || "/api/cancel-renewal";
|
|
527
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
528
|
+
const res = await fetchFn(route, {
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers,
|
|
531
|
+
body: JSON.stringify(params)
|
|
532
|
+
});
|
|
533
|
+
if (!res.ok) {
|
|
534
|
+
const data = await res.json().catch(() => ({}));
|
|
535
|
+
const error = new Error(data.error || `Failed to cancel renewal: ${res.statusText}`);
|
|
536
|
+
currentConfig?.onError?.(error, "cancelRenewal");
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
const result = await res.json();
|
|
540
|
+
await refetchPurchase();
|
|
541
|
+
return result;
|
|
542
|
+
},
|
|
543
|
+
[refetchPurchase]
|
|
544
|
+
);
|
|
545
|
+
const reactivateRenewal = (0, import_react.useCallback)(
|
|
546
|
+
async (params) => {
|
|
547
|
+
const currentConfig = configRef.current;
|
|
548
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
549
|
+
const route = currentConfig?.api?.reactivateRenewal || "/api/reactivate-renewal";
|
|
550
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
551
|
+
const res = await fetchFn(route, {
|
|
552
|
+
method: "POST",
|
|
553
|
+
headers,
|
|
554
|
+
body: JSON.stringify(params)
|
|
555
|
+
});
|
|
556
|
+
if (!res.ok) {
|
|
557
|
+
const data = await res.json().catch(() => ({}));
|
|
558
|
+
const error = new Error(data.error || `Failed to reactivate renewal: ${res.statusText}`);
|
|
559
|
+
currentConfig?.onError?.(error, "reactivateRenewal");
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
const result = await res.json();
|
|
563
|
+
await refetchPurchase();
|
|
564
|
+
return result;
|
|
565
|
+
},
|
|
566
|
+
[refetchPurchase]
|
|
567
|
+
);
|
|
568
|
+
const activatePlan = (0, import_react.useCallback)(
|
|
569
|
+
async (params) => {
|
|
570
|
+
const currentConfig = configRef.current;
|
|
571
|
+
const { headers } = await buildRequestHeaders(currentConfig);
|
|
572
|
+
const route = currentConfig?.api?.activatePlan || "/api/activate-plan";
|
|
573
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
574
|
+
const res = await fetchFn(route, {
|
|
575
|
+
method: "POST",
|
|
576
|
+
headers,
|
|
577
|
+
body: JSON.stringify(params)
|
|
578
|
+
});
|
|
579
|
+
if (!res.ok) {
|
|
580
|
+
const data = await res.json().catch(() => ({}));
|
|
581
|
+
const error = new Error(data.error || `Failed to activate plan: ${res.statusText}`);
|
|
582
|
+
currentConfig?.onError?.(error, "activatePlan");
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
const result = await res.json();
|
|
586
|
+
if (result.status === "activated" || result.status === "already_active") {
|
|
587
|
+
await refetchPurchase();
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
},
|
|
591
|
+
[refetchPurchase]
|
|
592
|
+
);
|
|
437
593
|
(0, import_react.useEffect)(() => {
|
|
438
|
-
lastFetchedRef.current = null;
|
|
439
594
|
inFlightRef.current = null;
|
|
440
595
|
if (isAuthenticated || internalCustomerRef) {
|
|
441
596
|
fetchPurchase();
|
|
442
597
|
} else {
|
|
443
598
|
setPurchaseData({ purchases: [] });
|
|
599
|
+
setPurchaseError(null);
|
|
444
600
|
setLoading(false);
|
|
601
|
+
setIsRefetching(false);
|
|
445
602
|
}
|
|
446
603
|
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
447
604
|
const updateCustomerRef = (0, import_react.useCallback)(
|
|
@@ -462,6 +619,8 @@ var SolvaPayProvider = ({
|
|
|
462
619
|
)[0] || null;
|
|
463
620
|
return {
|
|
464
621
|
loading,
|
|
622
|
+
isRefetching,
|
|
623
|
+
error: purchaseError,
|
|
465
624
|
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
466
625
|
email: purchaseData.email,
|
|
467
626
|
name: purchaseData.name,
|
|
@@ -480,24 +639,47 @@ var SolvaPayProvider = ({
|
|
|
480
639
|
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
481
640
|
activePaidPurchase
|
|
482
641
|
};
|
|
483
|
-
}, [loading, purchaseData, internalCustomerRef]);
|
|
642
|
+
}, [loading, isRefetching, purchaseError, purchaseData, internalCustomerRef]);
|
|
643
|
+
const balance = (0, import_react.useMemo)(
|
|
644
|
+
() => ({
|
|
645
|
+
loading: balanceLoading,
|
|
646
|
+
credits: creditsValue,
|
|
647
|
+
displayCurrency: displayCurrencyValue,
|
|
648
|
+
creditsPerMinorUnit: creditsPerMinorUnitValue,
|
|
649
|
+
displayExchangeRate: displayExchangeRateValue,
|
|
650
|
+
refetch: fetchBalanceImpl,
|
|
651
|
+
adjustBalance: adjustBalanceImpl
|
|
652
|
+
}),
|
|
653
|
+
[balanceLoading, creditsValue, displayCurrencyValue, creditsPerMinorUnitValue, displayExchangeRateValue, fetchBalanceImpl, adjustBalanceImpl]
|
|
654
|
+
);
|
|
484
655
|
const contextValue = (0, import_react.useMemo)(
|
|
485
656
|
() => ({
|
|
486
657
|
purchase,
|
|
487
658
|
refetchPurchase,
|
|
488
659
|
createPayment,
|
|
489
660
|
processPayment,
|
|
661
|
+
createTopupPayment,
|
|
662
|
+
cancelRenewal,
|
|
663
|
+
reactivateRenewal,
|
|
664
|
+
activatePlan,
|
|
490
665
|
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
491
|
-
updateCustomerRef
|
|
666
|
+
updateCustomerRef,
|
|
667
|
+
balance,
|
|
668
|
+
_config: configRef.current
|
|
492
669
|
}),
|
|
493
670
|
[
|
|
494
671
|
purchase,
|
|
495
672
|
refetchPurchase,
|
|
496
673
|
createPayment,
|
|
497
674
|
processPayment,
|
|
675
|
+
createTopupPayment,
|
|
676
|
+
cancelRenewal,
|
|
677
|
+
reactivateRenewal,
|
|
678
|
+
activatePlan,
|
|
498
679
|
purchaseData.customerRef,
|
|
499
680
|
internalCustomerRef,
|
|
500
|
-
updateCustomerRef
|
|
681
|
+
updateCustomerRef,
|
|
682
|
+
balance
|
|
501
683
|
]
|
|
502
684
|
);
|
|
503
685
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SolvaPayContext.Provider, { value: contextValue, children });
|
|
@@ -528,29 +710,66 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
|
528
710
|
function getStripeCacheKey(publishableKey, accountId) {
|
|
529
711
|
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
530
712
|
}
|
|
713
|
+
async function resolvePlanRef(productRef, fetchFn, headers, listPlansRoute) {
|
|
714
|
+
const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
|
|
715
|
+
const res = await fetchFn(url, { method: "GET", headers });
|
|
716
|
+
if (!res.ok) {
|
|
717
|
+
throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
|
|
718
|
+
}
|
|
719
|
+
const data = await res.json();
|
|
720
|
+
const allPlans = data.plans ?? [];
|
|
721
|
+
const activePlans = allPlans.filter((p) => p.isActive !== false && p.status !== "inactive");
|
|
722
|
+
if (activePlans.length === 0) {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`No active plans found for product "${productRef}". Configure at least one plan in the SolvaPay Console.`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
if (activePlans.length === 1) {
|
|
728
|
+
return activePlans[0].reference;
|
|
729
|
+
}
|
|
730
|
+
const defaultPlan = activePlans.find((p) => p.default === true);
|
|
731
|
+
if (defaultPlan) {
|
|
732
|
+
return defaultPlan.reference;
|
|
733
|
+
}
|
|
734
|
+
throw new Error(
|
|
735
|
+
`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.`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
531
738
|
function useCheckout(options) {
|
|
532
739
|
const { planRef, productRef } = options;
|
|
533
|
-
const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
|
|
740
|
+
const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
|
|
534
741
|
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
535
|
-
const [error, setError] = (0, import_react3.useState)(
|
|
536
|
-
!planRef || typeof planRef !== "string" ? new Error("useCheckout: planRef parameter is required and must be a string") : null
|
|
537
|
-
);
|
|
742
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
538
743
|
const [stripePromise, setStripePromise] = (0, import_react3.useState)(null);
|
|
539
744
|
const [clientSecret, setClientSecret] = (0, import_react3.useState)(null);
|
|
745
|
+
const [resolvedPlanRef, setResolvedPlanRef] = (0, import_react3.useState)(planRef || null);
|
|
540
746
|
const isStartingRef = (0, import_react3.useRef)(false);
|
|
541
747
|
const startCheckout = (0, import_react3.useCallback)(async () => {
|
|
542
748
|
if (isStartingRef.current || loading) {
|
|
543
749
|
return;
|
|
544
750
|
}
|
|
545
|
-
if (!planRef
|
|
546
|
-
setError(
|
|
751
|
+
if (!planRef && !productRef) {
|
|
752
|
+
setError(
|
|
753
|
+
new Error(
|
|
754
|
+
"useCheckout: either planRef or productRef is required. Pass planRef directly, or pass productRef to auto-resolve the plan."
|
|
755
|
+
)
|
|
756
|
+
);
|
|
547
757
|
return;
|
|
548
758
|
}
|
|
549
759
|
isStartingRef.current = true;
|
|
550
760
|
setLoading(true);
|
|
551
761
|
setError(null);
|
|
552
762
|
try {
|
|
553
|
-
|
|
763
|
+
let effectivePlanRef = planRef;
|
|
764
|
+
if (!effectivePlanRef && productRef) {
|
|
765
|
+
const listPlansRoute = _config?.api?.listPlans || "/api/list-plans";
|
|
766
|
+
effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
|
|
767
|
+
setResolvedPlanRef(effectivePlanRef);
|
|
768
|
+
}
|
|
769
|
+
if (!effectivePlanRef) {
|
|
770
|
+
throw new Error("Could not determine plan reference for checkout");
|
|
771
|
+
}
|
|
772
|
+
const result = await createPayment({ planRef: effectivePlanRef, productRef });
|
|
554
773
|
if (!result || typeof result !== "object") {
|
|
555
774
|
throw new Error("Invalid payment intent response from server");
|
|
556
775
|
}
|
|
@@ -579,19 +798,21 @@ function useCheckout(options) {
|
|
|
579
798
|
setLoading(false);
|
|
580
799
|
isStartingRef.current = false;
|
|
581
800
|
}
|
|
582
|
-
}, [planRef, productRef, createPayment, updateCustomerRef, loading]);
|
|
801
|
+
}, [planRef, productRef, createPayment, updateCustomerRef, loading, _config]);
|
|
583
802
|
const reset = (0, import_react3.useCallback)(() => {
|
|
584
803
|
isStartingRef.current = false;
|
|
585
804
|
setLoading(false);
|
|
586
805
|
setError(null);
|
|
587
806
|
setStripePromise(null);
|
|
588
807
|
setClientSecret(null);
|
|
589
|
-
|
|
808
|
+
setResolvedPlanRef(planRef || null);
|
|
809
|
+
}, [planRef]);
|
|
590
810
|
return {
|
|
591
811
|
loading,
|
|
592
812
|
error,
|
|
593
813
|
stripePromise,
|
|
594
814
|
clientSecret,
|
|
815
|
+
resolvedPlanRef,
|
|
595
816
|
startCheckout,
|
|
596
817
|
reset
|
|
597
818
|
};
|
|
@@ -759,14 +980,16 @@ var StripePaymentFormWrapper = ({
|
|
|
759
980
|
const cardElementOptions = {
|
|
760
981
|
style: {
|
|
761
982
|
base: {
|
|
762
|
-
fontSize: "
|
|
763
|
-
|
|
983
|
+
fontSize: "17px",
|
|
984
|
+
lineHeight: "24px",
|
|
985
|
+
color: "#1e293b",
|
|
986
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
764
987
|
"::placeholder": {
|
|
765
|
-
color: "#
|
|
988
|
+
color: "#94a3b8"
|
|
766
989
|
}
|
|
767
990
|
},
|
|
768
991
|
invalid: {
|
|
769
|
-
color: "#
|
|
992
|
+
color: "#dc2626"
|
|
770
993
|
}
|
|
771
994
|
},
|
|
772
995
|
hidePostalCode: true
|
|
@@ -786,22 +1009,7 @@ var StripePaymentFormWrapper = ({
|
|
|
786
1009
|
}
|
|
787
1010
|
}
|
|
788
1011
|
}
|
|
789
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
790
|
-
"div",
|
|
791
|
-
{
|
|
792
|
-
style: {
|
|
793
|
-
padding: "12px",
|
|
794
|
-
border: "1px solid #cbd5e1",
|
|
795
|
-
borderRadius: "0.375rem",
|
|
796
|
-
backgroundColor: "white",
|
|
797
|
-
display: "flex",
|
|
798
|
-
alignItems: "center",
|
|
799
|
-
justifyContent: "center",
|
|
800
|
-
minHeight: "40px"
|
|
801
|
-
},
|
|
802
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" })
|
|
803
|
-
}
|
|
804
|
-
),
|
|
1012
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }) }),
|
|
805
1013
|
message && !isSuccess && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
806
1014
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
807
1015
|
"button",
|
|
@@ -833,88 +1041,62 @@ var PaymentForm = ({
|
|
|
833
1041
|
className,
|
|
834
1042
|
buttonClassName
|
|
835
1043
|
}) => {
|
|
836
|
-
const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
|
|
837
1044
|
const {
|
|
838
1045
|
loading: checkoutLoading,
|
|
839
1046
|
error: checkoutError,
|
|
840
1047
|
clientSecret,
|
|
841
1048
|
startCheckout,
|
|
842
|
-
stripePromise
|
|
843
|
-
|
|
1049
|
+
stripePromise,
|
|
1050
|
+
resolvedPlanRef
|
|
1051
|
+
} = useCheckout({ planRef, productRef });
|
|
844
1052
|
const { refetch } = usePurchase();
|
|
845
1053
|
const { processPayment } = useSolvaPay();
|
|
846
1054
|
const hasInitializedRef = (0, import_react5.useRef)(false);
|
|
1055
|
+
const hasPlanOrProduct = !!(planRef || productRef);
|
|
847
1056
|
(0, import_react5.useEffect)(() => {
|
|
848
|
-
if (!hasInitializedRef.current &&
|
|
1057
|
+
if (!hasInitializedRef.current && hasPlanOrProduct && !checkoutLoading && !checkoutError && !clientSecret) {
|
|
849
1058
|
hasInitializedRef.current = true;
|
|
850
1059
|
startCheckout().catch(() => {
|
|
851
1060
|
hasInitializedRef.current = false;
|
|
852
1061
|
});
|
|
853
1062
|
}
|
|
854
|
-
if (
|
|
1063
|
+
if (hasPlanOrProduct && clientSecret) {
|
|
855
1064
|
hasInitializedRef.current = true;
|
|
856
1065
|
}
|
|
857
|
-
}, [
|
|
1066
|
+
}, [hasPlanOrProduct, checkoutLoading, checkoutError, clientSecret, startCheckout]);
|
|
1067
|
+
const effectivePlanRef = planRef || resolvedPlanRef;
|
|
858
1068
|
const handleSuccess = (0, import_react5.useCallback)(
|
|
859
1069
|
async (paymentIntent) => {
|
|
860
|
-
let processingTimeout = false;
|
|
861
|
-
let processingResult = null;
|
|
862
1070
|
const paymentIntentAny = paymentIntent;
|
|
863
1071
|
if (processPayment && productRef) {
|
|
864
1072
|
try {
|
|
865
1073
|
const result = await processPayment({
|
|
866
1074
|
paymentIntentId: paymentIntentAny.id,
|
|
867
1075
|
productRef,
|
|
868
|
-
planRef
|
|
1076
|
+
planRef: effectivePlanRef || void 0
|
|
869
1077
|
});
|
|
870
|
-
processingResult = result;
|
|
871
1078
|
const isTimeout = result?.status === "timeout";
|
|
872
|
-
processingTimeout = isTimeout;
|
|
873
1079
|
if (isTimeout) {
|
|
874
1080
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
875
|
-
|
|
876
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1081
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
|
|
877
1082
|
await refetch();
|
|
878
1083
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
_processingTimeout: processingTimeout,
|
|
883
|
-
_processingResult: processingResult
|
|
884
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
throw new Error("Payment processing timed out");
|
|
888
|
-
} else {
|
|
889
|
-
await refetch();
|
|
1084
|
+
const err = new Error("Payment processing timed out \u2014 webhooks may not be configured");
|
|
1085
|
+
onError?.(err);
|
|
1086
|
+
return;
|
|
890
1087
|
}
|
|
1088
|
+
await refetch();
|
|
891
1089
|
} catch (error) {
|
|
892
1090
|
console.error("[PaymentForm] Failed to process payment:", error);
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
await onSuccess({
|
|
896
|
-
...paymentIntentAny,
|
|
897
|
-
_processingError: error
|
|
898
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
899
|
-
});
|
|
900
|
-
} catch {
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
throw error;
|
|
1091
|
+
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
1092
|
+
return;
|
|
904
1093
|
}
|
|
905
1094
|
} else {
|
|
906
1095
|
await refetch();
|
|
907
1096
|
}
|
|
908
|
-
|
|
909
|
-
await onSuccess({
|
|
910
|
-
...paymentIntentAny,
|
|
911
|
-
_processingTimeout: processingTimeout,
|
|
912
|
-
_processingResult: processingResult
|
|
913
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
914
|
-
});
|
|
915
|
-
}
|
|
1097
|
+
onSuccess?.(paymentIntentAny);
|
|
916
1098
|
},
|
|
917
|
-
[processPayment, productRef,
|
|
1099
|
+
[processPayment, productRef, effectivePlanRef, refetch, onSuccess, onError]
|
|
918
1100
|
);
|
|
919
1101
|
const handleError = (0, import_react5.useCallback)(
|
|
920
1102
|
(err) => {
|
|
@@ -925,7 +1107,6 @@ var PaymentForm = ({
|
|
|
925
1107
|
[onError]
|
|
926
1108
|
);
|
|
927
1109
|
const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
|
|
928
|
-
const isValidPlanRef = planRef && typeof planRef === "string";
|
|
929
1110
|
const hasError = !!checkoutError;
|
|
930
1111
|
const hasStripeData = !!(stripePromise && clientSecret);
|
|
931
1112
|
const elementsOptions = (0, import_react5.useMemo)(() => {
|
|
@@ -939,72 +1120,246 @@ var PaymentForm = ({
|
|
|
939
1120
|
}
|
|
940
1121
|
}, [hasStripeData]);
|
|
941
1122
|
const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
|
|
942
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !
|
|
1123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !hasPlanOrProduct ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "PaymentForm: either planRef or productRef is required" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
943
1124
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "Payment initialization failed" }),
|
|
944
1125
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: checkoutError?.message || "Unknown error" })
|
|
945
|
-
] }) : shouldRenderElements && elementsOptions ? (
|
|
946
|
-
|
|
947
|
-
|
|
1126
|
+
] }) : shouldRenderElements && elementsOptions ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1127
|
+
import_react_stripe_js2.Elements,
|
|
1128
|
+
{
|
|
1129
|
+
stripe: stripePromise,
|
|
1130
|
+
options: elementsOptions,
|
|
1131
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1132
|
+
StripePaymentFormWrapper,
|
|
1133
|
+
{
|
|
1134
|
+
onSuccess: handleSuccess,
|
|
1135
|
+
onError: handleError,
|
|
1136
|
+
returnUrl: finalReturnUrl,
|
|
1137
|
+
submitButtonText,
|
|
1138
|
+
buttonClassName,
|
|
1139
|
+
clientSecret
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
},
|
|
1143
|
+
clientSecret
|
|
1144
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
948
1145
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
949
|
-
|
|
1146
|
+
"div",
|
|
950
1147
|
{
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1148
|
+
style: {
|
|
1149
|
+
minHeight: "40px",
|
|
1150
|
+
display: "flex",
|
|
1151
|
+
alignItems: "center",
|
|
1152
|
+
justifyContent: "center"
|
|
1153
|
+
},
|
|
1154
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "md" })
|
|
1155
|
+
}
|
|
1156
|
+
),
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1158
|
+
"button",
|
|
1159
|
+
{
|
|
1160
|
+
type: "submit",
|
|
1161
|
+
disabled: true,
|
|
1162
|
+
className: buttonClassName,
|
|
1163
|
+
"aria-busy": "false",
|
|
1164
|
+
"aria-disabled": "true",
|
|
1165
|
+
children: submitButtonText
|
|
1166
|
+
}
|
|
966
1167
|
)
|
|
967
|
-
)
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1168
|
+
] }) });
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
// src/TopupForm.tsx
|
|
1172
|
+
var import_react7 = require("react");
|
|
1173
|
+
var import_react_stripe_js3 = require("@stripe/react-stripe-js");
|
|
1174
|
+
|
|
1175
|
+
// src/hooks/useTopup.ts
|
|
1176
|
+
var import_react6 = require("react");
|
|
1177
|
+
var import_stripe_js2 = require("@stripe/stripe-js");
|
|
1178
|
+
var stripePromiseCache2 = /* @__PURE__ */ new Map();
|
|
1179
|
+
function getStripeCacheKey2(publishableKey, accountId) {
|
|
1180
|
+
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
1181
|
+
}
|
|
1182
|
+
function useTopup(options) {
|
|
1183
|
+
const { amount, currency } = options;
|
|
1184
|
+
const { createTopupPayment, customerRef, updateCustomerRef } = useSolvaPay();
|
|
1185
|
+
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
1186
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1187
|
+
const [stripePromise, setStripePromise] = (0, import_react6.useState)(null);
|
|
1188
|
+
const [clientSecret, setClientSecret] = (0, import_react6.useState)(null);
|
|
1189
|
+
const isStartingRef = (0, import_react6.useRef)(false);
|
|
1190
|
+
const startTopup = (0, import_react6.useCallback)(async () => {
|
|
1191
|
+
if (isStartingRef.current || loading) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (!amount || amount <= 0) {
|
|
1195
|
+
setError(new Error("useTopup: amount must be a positive number"));
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
isStartingRef.current = true;
|
|
1199
|
+
setLoading(true);
|
|
1200
|
+
setError(null);
|
|
1201
|
+
try {
|
|
1202
|
+
const result = await createTopupPayment({ amount, currency });
|
|
1203
|
+
if (!result || typeof result !== "object") {
|
|
1204
|
+
throw new Error("Invalid topup payment intent response from server");
|
|
1205
|
+
}
|
|
1206
|
+
if (!result.clientSecret || typeof result.clientSecret !== "string") {
|
|
1207
|
+
throw new Error("Invalid client secret in topup payment intent response");
|
|
1208
|
+
}
|
|
1209
|
+
if (!result.publishableKey || typeof result.publishableKey !== "string") {
|
|
1210
|
+
throw new Error("Invalid publishable key in topup payment intent response");
|
|
1211
|
+
}
|
|
1212
|
+
if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
|
|
1213
|
+
updateCustomerRef(result.customerRef);
|
|
1214
|
+
}
|
|
1215
|
+
const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
|
|
1216
|
+
const cacheKey = getStripeCacheKey2(result.publishableKey, result.accountId);
|
|
1217
|
+
let stripe = stripePromiseCache2.get(cacheKey);
|
|
1218
|
+
if (!stripe) {
|
|
1219
|
+
stripe = (0, import_stripe_js2.loadStripe)(result.publishableKey, stripeOptions);
|
|
1220
|
+
stripePromiseCache2.set(cacheKey, stripe);
|
|
1221
|
+
}
|
|
1222
|
+
setStripePromise(stripe);
|
|
1223
|
+
setClientSecret(result.clientSecret);
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
const error2 = err instanceof Error ? err : new Error("Failed to start topup");
|
|
1226
|
+
setError(error2);
|
|
1227
|
+
} finally {
|
|
1228
|
+
setLoading(false);
|
|
1229
|
+
isStartingRef.current = false;
|
|
1230
|
+
}
|
|
1231
|
+
}, [amount, currency, createTopupPayment, customerRef, updateCustomerRef, loading]);
|
|
1232
|
+
const reset = (0, import_react6.useCallback)(() => {
|
|
1233
|
+
isStartingRef.current = false;
|
|
1234
|
+
setLoading(false);
|
|
1235
|
+
setError(null);
|
|
1236
|
+
setStripePromise(null);
|
|
1237
|
+
setClientSecret(null);
|
|
1238
|
+
}, []);
|
|
1239
|
+
return {
|
|
1240
|
+
loading,
|
|
1241
|
+
error,
|
|
1242
|
+
stripePromise,
|
|
1243
|
+
clientSecret,
|
|
1244
|
+
startTopup,
|
|
1245
|
+
reset
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/TopupForm.tsx
|
|
1250
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1251
|
+
var TopupForm = ({
|
|
1252
|
+
amount,
|
|
1253
|
+
currency,
|
|
1254
|
+
onSuccess,
|
|
1255
|
+
onError,
|
|
1256
|
+
returnUrl,
|
|
1257
|
+
submitButtonText = "Top Up",
|
|
1258
|
+
className,
|
|
1259
|
+
buttonClassName
|
|
1260
|
+
}) => {
|
|
1261
|
+
const {
|
|
1262
|
+
loading: topupLoading,
|
|
1263
|
+
error: topupError,
|
|
1264
|
+
clientSecret,
|
|
1265
|
+
startTopup,
|
|
1266
|
+
stripePromise
|
|
1267
|
+
} = useTopup({ amount, currency });
|
|
1268
|
+
const hasInitializedRef = (0, import_react7.useRef)(false);
|
|
1269
|
+
const hasAmount = amount > 0;
|
|
1270
|
+
(0, import_react7.useEffect)(() => {
|
|
1271
|
+
if (!hasInitializedRef.current && hasAmount && !topupLoading && !topupError && !clientSecret) {
|
|
1272
|
+
hasInitializedRef.current = true;
|
|
1273
|
+
startTopup().catch(() => {
|
|
1274
|
+
hasInitializedRef.current = false;
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
if (hasAmount && clientSecret) {
|
|
1278
|
+
hasInitializedRef.current = true;
|
|
1279
|
+
}
|
|
1280
|
+
}, [hasAmount, topupLoading, topupError, clientSecret, startTopup]);
|
|
1281
|
+
const handleSuccess = (0, import_react7.useCallback)(
|
|
1282
|
+
async (paymentIntent) => {
|
|
1283
|
+
if (onSuccess) {
|
|
1284
|
+
await onSuccess(paymentIntent);
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1287
|
+
[onSuccess]
|
|
1288
|
+
);
|
|
1289
|
+
const handleError = (0, import_react7.useCallback)(
|
|
1290
|
+
(err) => {
|
|
1291
|
+
if (onError) {
|
|
1292
|
+
onError(err);
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
[onError]
|
|
1296
|
+
);
|
|
1297
|
+
const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
|
|
1298
|
+
const hasError = !!topupError;
|
|
1299
|
+
const hasStripeData = !!(stripePromise && clientSecret);
|
|
1300
|
+
const elementsOptions = (0, import_react7.useMemo)(() => {
|
|
1301
|
+
if (!clientSecret) return void 0;
|
|
1302
|
+
return { clientSecret };
|
|
1303
|
+
}, [clientSecret]);
|
|
1304
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: !hasAmount ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: "TopupForm: amount must be a positive number" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
1305
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: "Top-up initialization failed" }),
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: topupError?.message || "Unknown error" })
|
|
1307
|
+
] }) : hasStripeData && elementsOptions ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1308
|
+
import_react_stripe_js3.Elements,
|
|
1309
|
+
{
|
|
1310
|
+
stripe: stripePromise,
|
|
1311
|
+
options: elementsOptions,
|
|
1312
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1313
|
+
StripePaymentFormWrapper,
|
|
984
1314
|
{
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1315
|
+
onSuccess: handleSuccess,
|
|
1316
|
+
onError: handleError,
|
|
1317
|
+
returnUrl: finalReturnUrl,
|
|
1318
|
+
submitButtonText,
|
|
1319
|
+
buttonClassName,
|
|
1320
|
+
clientSecret
|
|
991
1321
|
}
|
|
992
1322
|
)
|
|
993
|
-
|
|
994
|
-
|
|
1323
|
+
},
|
|
1324
|
+
clientSecret
|
|
1325
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
1326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1327
|
+
"div",
|
|
1328
|
+
{
|
|
1329
|
+
style: {
|
|
1330
|
+
minHeight: "40px",
|
|
1331
|
+
display: "flex",
|
|
1332
|
+
alignItems: "center",
|
|
1333
|
+
justifyContent: "center"
|
|
1334
|
+
},
|
|
1335
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, { size: "md" })
|
|
1336
|
+
}
|
|
1337
|
+
),
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1339
|
+
"button",
|
|
1340
|
+
{
|
|
1341
|
+
type: "submit",
|
|
1342
|
+
disabled: true,
|
|
1343
|
+
className: buttonClassName,
|
|
1344
|
+
"aria-busy": "false",
|
|
1345
|
+
"aria-disabled": "true",
|
|
1346
|
+
children: submitButtonText
|
|
1347
|
+
}
|
|
1348
|
+
)
|
|
1349
|
+
] }) });
|
|
995
1350
|
};
|
|
996
1351
|
|
|
997
1352
|
// src/components/ProductBadge.tsx
|
|
998
|
-
var
|
|
999
|
-
var
|
|
1353
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
1354
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1000
1355
|
var ProductBadge = ({
|
|
1001
1356
|
children,
|
|
1002
1357
|
as: Component = "div",
|
|
1003
1358
|
className
|
|
1004
1359
|
}) => {
|
|
1005
1360
|
const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
|
|
1006
|
-
const [hasLoadedOnce, setHasLoadedOnce] =
|
|
1007
|
-
|
|
1361
|
+
const [hasLoadedOnce, setHasLoadedOnce] = import_react8.default.useState(false);
|
|
1362
|
+
import_react8.default.useEffect(() => {
|
|
1008
1363
|
if (!loading) {
|
|
1009
1364
|
setHasLoadedOnce(true);
|
|
1010
1365
|
}
|
|
@@ -1012,13 +1367,13 @@ var ProductBadge = ({
|
|
|
1012
1367
|
const planToDisplay = activePurchase?.productName || null;
|
|
1013
1368
|
const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
|
|
1014
1369
|
if (children) {
|
|
1015
|
-
return /* @__PURE__ */ (0,
|
|
1370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
1016
1371
|
}
|
|
1017
1372
|
if (!shouldShow) {
|
|
1018
1373
|
return null;
|
|
1019
1374
|
}
|
|
1020
1375
|
const computedClassName = typeof className === "function" ? className({ purchases }) : className;
|
|
1021
|
-
return /* @__PURE__ */ (0,
|
|
1376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1022
1377
|
Component,
|
|
1023
1378
|
{
|
|
1024
1379
|
className: computedClassName,
|
|
@@ -1036,12 +1391,12 @@ var ProductBadge = ({
|
|
|
1036
1391
|
var PlanBadge = ProductBadge;
|
|
1037
1392
|
|
|
1038
1393
|
// src/components/PurchaseGate.tsx
|
|
1039
|
-
var
|
|
1394
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1040
1395
|
var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
|
|
1041
1396
|
const { purchases, loading, hasProduct } = usePurchase();
|
|
1042
1397
|
const productToCheck = requireProduct || requirePlan;
|
|
1043
1398
|
const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
|
|
1044
|
-
return /* @__PURE__ */ (0,
|
|
1399
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: children({
|
|
1045
1400
|
hasAccess,
|
|
1046
1401
|
purchases,
|
|
1047
1402
|
loading
|
|
@@ -1049,35 +1404,103 @@ var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
|
|
|
1049
1404
|
};
|
|
1050
1405
|
|
|
1051
1406
|
// src/components/PricingSelector.tsx
|
|
1052
|
-
var
|
|
1407
|
+
var import_react10 = require("react");
|
|
1053
1408
|
|
|
1054
1409
|
// src/hooks/usePlans.ts
|
|
1055
|
-
var
|
|
1410
|
+
var import_react9 = require("react");
|
|
1056
1411
|
var plansCache = /* @__PURE__ */ new Map();
|
|
1057
1412
|
var CACHE_DURATION2 = 5 * 60 * 1e3;
|
|
1413
|
+
function processPlans(raw, filter, sortBy) {
|
|
1414
|
+
let result = sortBy ? [...raw].sort(sortBy) : raw;
|
|
1415
|
+
if (filter) result = result.filter(filter);
|
|
1416
|
+
return result;
|
|
1417
|
+
}
|
|
1418
|
+
function computeInitialIndex(plans, initialPlanRef, autoSelectFirstPaid) {
|
|
1419
|
+
if (plans.length === 0) return 0;
|
|
1420
|
+
if (initialPlanRef) {
|
|
1421
|
+
const idx = plans.findIndex((p) => p.reference === initialPlanRef);
|
|
1422
|
+
if (idx >= 0) return idx;
|
|
1423
|
+
}
|
|
1424
|
+
if (autoSelectFirstPaid) {
|
|
1425
|
+
const idx = plans.findIndex((p) => p.requiresPayment !== false);
|
|
1426
|
+
return idx >= 0 ? idx : 0;
|
|
1427
|
+
}
|
|
1428
|
+
return 0;
|
|
1429
|
+
}
|
|
1058
1430
|
function usePlans(options) {
|
|
1059
|
-
const {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
(0,
|
|
1431
|
+
const {
|
|
1432
|
+
fetcher,
|
|
1433
|
+
productRef,
|
|
1434
|
+
filter,
|
|
1435
|
+
sortBy,
|
|
1436
|
+
autoSelectFirstPaid = false,
|
|
1437
|
+
initialPlanRef,
|
|
1438
|
+
selectionReady = true
|
|
1439
|
+
} = options;
|
|
1440
|
+
const fetcherRef = (0, import_react9.useRef)(fetcher);
|
|
1441
|
+
const filterRef = (0, import_react9.useRef)(filter);
|
|
1442
|
+
const sortByRef = (0, import_react9.useRef)(sortBy);
|
|
1443
|
+
const autoSelectFirstPaidRef = (0, import_react9.useRef)(autoSelectFirstPaid);
|
|
1444
|
+
const initialPlanRefRef = (0, import_react9.useRef)(initialPlanRef);
|
|
1445
|
+
const selectionReadyRef = (0, import_react9.useRef)(selectionReady);
|
|
1446
|
+
const hasAppliedInitialRef = (0, import_react9.useRef)(false);
|
|
1447
|
+
const userHasSelectedRef = (0, import_react9.useRef)(false);
|
|
1448
|
+
const [selectedPlanIndex, setSelectedPlanIndexState] = (0, import_react9.useState)(() => {
|
|
1449
|
+
if (!selectionReady || !productRef) return 0;
|
|
1450
|
+
const cached = plansCache.get(productRef);
|
|
1451
|
+
if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
|
|
1452
|
+
return 0;
|
|
1453
|
+
}
|
|
1454
|
+
const processed = processPlans(cached.plans, filter, sortBy);
|
|
1455
|
+
if (processed.length === 0) return 0;
|
|
1456
|
+
const idx = computeInitialIndex(processed, initialPlanRef, autoSelectFirstPaid);
|
|
1457
|
+
hasAppliedInitialRef.current = true;
|
|
1458
|
+
return idx;
|
|
1459
|
+
});
|
|
1460
|
+
const [plans, setPlans] = (0, import_react9.useState)(() => {
|
|
1461
|
+
if (!productRef) return [];
|
|
1462
|
+
const cached = plansCache.get(productRef);
|
|
1463
|
+
if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
|
|
1464
|
+
return [];
|
|
1465
|
+
}
|
|
1466
|
+
return processPlans(cached.plans, filter, sortBy);
|
|
1467
|
+
});
|
|
1468
|
+
const [loading, setLoading] = (0, import_react9.useState)(() => plans.length === 0);
|
|
1469
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1470
|
+
(0, import_react9.useEffect)(() => {
|
|
1069
1471
|
fetcherRef.current = fetcher;
|
|
1070
1472
|
}, [fetcher]);
|
|
1071
|
-
(0,
|
|
1473
|
+
(0, import_react9.useEffect)(() => {
|
|
1072
1474
|
filterRef.current = filter;
|
|
1073
1475
|
}, [filter]);
|
|
1074
|
-
(0,
|
|
1476
|
+
(0, import_react9.useEffect)(() => {
|
|
1075
1477
|
sortByRef.current = sortBy;
|
|
1076
1478
|
}, [sortBy]);
|
|
1077
|
-
(0,
|
|
1479
|
+
(0, import_react9.useEffect)(() => {
|
|
1078
1480
|
autoSelectFirstPaidRef.current = autoSelectFirstPaid;
|
|
1079
1481
|
}, [autoSelectFirstPaid]);
|
|
1080
|
-
|
|
1482
|
+
(0, import_react9.useEffect)(() => {
|
|
1483
|
+
initialPlanRefRef.current = initialPlanRef;
|
|
1484
|
+
}, [initialPlanRef]);
|
|
1485
|
+
(0, import_react9.useEffect)(() => {
|
|
1486
|
+
selectionReadyRef.current = selectionReady;
|
|
1487
|
+
}, [selectionReady]);
|
|
1488
|
+
const setSelectedPlanIndex = (0, import_react9.useCallback)((index) => {
|
|
1489
|
+
userHasSelectedRef.current = true;
|
|
1490
|
+
setSelectedPlanIndexState(index);
|
|
1491
|
+
}, []);
|
|
1492
|
+
const applyInitialSelection = (0, import_react9.useCallback)((processedPlans) => {
|
|
1493
|
+
if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
|
|
1494
|
+
if (!selectionReadyRef.current || processedPlans.length === 0) return;
|
|
1495
|
+
hasAppliedInitialRef.current = true;
|
|
1496
|
+
const idx = computeInitialIndex(
|
|
1497
|
+
processedPlans,
|
|
1498
|
+
initialPlanRefRef.current,
|
|
1499
|
+
autoSelectFirstPaidRef.current
|
|
1500
|
+
);
|
|
1501
|
+
setSelectedPlanIndexState(idx);
|
|
1502
|
+
}, []);
|
|
1503
|
+
const fetchPlans = (0, import_react9.useCallback)(
|
|
1081
1504
|
async (force = false) => {
|
|
1082
1505
|
if (!productRef) {
|
|
1083
1506
|
setError(new Error("Product reference not configured"));
|
|
@@ -1087,34 +1510,29 @@ function usePlans(options) {
|
|
|
1087
1510
|
const cached = plansCache.get(productRef);
|
|
1088
1511
|
const now = Date.now();
|
|
1089
1512
|
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1513
|
+
const processedPlans = processPlans(
|
|
1514
|
+
cached.plans,
|
|
1515
|
+
filterRef.current,
|
|
1516
|
+
sortByRef.current
|
|
1517
|
+
);
|
|
1095
1518
|
setPlans(processedPlans);
|
|
1096
1519
|
setLoading(false);
|
|
1097
1520
|
setError(null);
|
|
1098
|
-
|
|
1099
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1100
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1101
|
-
}
|
|
1521
|
+
applyInitialSelection(processedPlans);
|
|
1102
1522
|
return;
|
|
1103
1523
|
}
|
|
1104
1524
|
if (cached?.promise) {
|
|
1105
1525
|
try {
|
|
1106
1526
|
setLoading(true);
|
|
1107
1527
|
const fetchedPlans = await cached.promise;
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1528
|
+
const processedPlans = processPlans(
|
|
1529
|
+
fetchedPlans,
|
|
1530
|
+
filterRef.current,
|
|
1531
|
+
sortByRef.current
|
|
1532
|
+
);
|
|
1112
1533
|
setPlans(processedPlans);
|
|
1113
1534
|
setError(null);
|
|
1114
|
-
|
|
1115
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1116
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1117
|
-
}
|
|
1535
|
+
applyInitialSelection(processedPlans);
|
|
1118
1536
|
} catch (err) {
|
|
1119
1537
|
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1120
1538
|
} finally {
|
|
@@ -1126,26 +1544,16 @@ function usePlans(options) {
|
|
|
1126
1544
|
setLoading(true);
|
|
1127
1545
|
setError(null);
|
|
1128
1546
|
const fetchPromise = fetcherRef.current(productRef);
|
|
1129
|
-
plansCache.set(productRef, {
|
|
1130
|
-
plans: [],
|
|
1131
|
-
timestamp: now,
|
|
1132
|
-
promise: fetchPromise
|
|
1133
|
-
});
|
|
1547
|
+
plansCache.set(productRef, { plans: [], timestamp: now, promise: fetchPromise });
|
|
1134
1548
|
const fetchedPlans = await fetchPromise;
|
|
1135
|
-
plansCache.set(productRef, {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
if (sortByRef.current) {
|
|
1142
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1143
|
-
}
|
|
1549
|
+
plansCache.set(productRef, { plans: fetchedPlans, timestamp: now, promise: null });
|
|
1550
|
+
const processedPlans = processPlans(
|
|
1551
|
+
fetchedPlans,
|
|
1552
|
+
filterRef.current,
|
|
1553
|
+
sortByRef.current
|
|
1554
|
+
);
|
|
1144
1555
|
setPlans(processedPlans);
|
|
1145
|
-
|
|
1146
|
-
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1147
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1148
|
-
}
|
|
1556
|
+
applyInitialSelection(processedPlans);
|
|
1149
1557
|
} catch (err) {
|
|
1150
1558
|
plansCache.delete(productRef);
|
|
1151
1559
|
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
@@ -1153,22 +1561,25 @@ function usePlans(options) {
|
|
|
1153
1561
|
setLoading(false);
|
|
1154
1562
|
}
|
|
1155
1563
|
},
|
|
1156
|
-
[productRef]
|
|
1564
|
+
[productRef, applyInitialSelection]
|
|
1157
1565
|
);
|
|
1158
|
-
(0,
|
|
1566
|
+
(0, import_react9.useEffect)(() => {
|
|
1159
1567
|
fetchPlans();
|
|
1160
1568
|
}, [fetchPlans]);
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1569
|
+
(0, import_react9.useEffect)(() => {
|
|
1570
|
+
if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
|
|
1571
|
+
if (!selectionReady || plans.length === 0) return;
|
|
1572
|
+
applyInitialSelection(plans);
|
|
1573
|
+
}, [selectionReady, plans, applyInitialSelection]);
|
|
1574
|
+
const selectedPlan = (0, import_react9.useMemo)(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
|
|
1575
|
+
const selectPlan = (0, import_react9.useCallback)(
|
|
1165
1576
|
(planRef) => {
|
|
1166
1577
|
const index = plans.findIndex((p) => p.reference === planRef);
|
|
1167
1578
|
if (index >= 0) {
|
|
1168
1579
|
setSelectedPlanIndex(index);
|
|
1169
1580
|
}
|
|
1170
1581
|
},
|
|
1171
|
-
[plans]
|
|
1582
|
+
[plans, setSelectedPlanIndex]
|
|
1172
1583
|
);
|
|
1173
1584
|
return {
|
|
1174
1585
|
plans,
|
|
@@ -1178,13 +1589,13 @@ function usePlans(options) {
|
|
|
1178
1589
|
selectedPlan,
|
|
1179
1590
|
setSelectedPlanIndex,
|
|
1180
1591
|
selectPlan,
|
|
1181
|
-
refetch: () => fetchPlans(true)
|
|
1182
|
-
|
|
1592
|
+
refetch: () => fetchPlans(true),
|
|
1593
|
+
isSelectionReady: hasAppliedInitialRef.current
|
|
1183
1594
|
};
|
|
1184
1595
|
}
|
|
1185
1596
|
|
|
1186
1597
|
// src/components/PricingSelector.tsx
|
|
1187
|
-
var
|
|
1598
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1188
1599
|
var PricingSelector = ({
|
|
1189
1600
|
productRef,
|
|
1190
1601
|
fetcher,
|
|
@@ -1202,26 +1613,26 @@ var PricingSelector = ({
|
|
|
1202
1613
|
autoSelectFirstPaid
|
|
1203
1614
|
});
|
|
1204
1615
|
const { plans } = plansHook;
|
|
1205
|
-
const isPaidPlan = (0,
|
|
1616
|
+
const isPaidPlan = (0, import_react10.useCallback)(
|
|
1206
1617
|
(planRef) => {
|
|
1207
1618
|
const plan = plans.find((p) => p.reference === planRef);
|
|
1208
|
-
return plan ?
|
|
1619
|
+
return plan ? plan.requiresPayment !== false : true;
|
|
1209
1620
|
},
|
|
1210
1621
|
[plans]
|
|
1211
1622
|
);
|
|
1212
|
-
const activePurchase = (0,
|
|
1623
|
+
const activePurchase = (0, import_react10.useMemo)(() => {
|
|
1213
1624
|
const activePurchases = purchases.filter((p) => p.status === "active");
|
|
1214
1625
|
return activePurchases.sort(
|
|
1215
1626
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1216
1627
|
)[0] || null;
|
|
1217
1628
|
}, [purchases]);
|
|
1218
|
-
const isCurrentPlan = (0,
|
|
1629
|
+
const isCurrentPlan = (0, import_react10.useCallback)(
|
|
1219
1630
|
(planRef) => {
|
|
1220
1631
|
return activePurchase?.planSnapshot?.reference === planRef;
|
|
1221
1632
|
},
|
|
1222
1633
|
[activePurchase]
|
|
1223
1634
|
);
|
|
1224
|
-
return /* @__PURE__ */ (0,
|
|
1635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: children({
|
|
1225
1636
|
...plansHook,
|
|
1226
1637
|
purchases,
|
|
1227
1638
|
isPaidPlan,
|
|
@@ -1230,14 +1641,58 @@ var PricingSelector = ({
|
|
|
1230
1641
|
};
|
|
1231
1642
|
var PlanSelector = PricingSelector;
|
|
1232
1643
|
|
|
1644
|
+
// src/hooks/useBalance.ts
|
|
1645
|
+
var import_react11 = require("react");
|
|
1646
|
+
function useBalance() {
|
|
1647
|
+
const { balance } = useSolvaPay();
|
|
1648
|
+
(0, import_react11.useEffect)(() => {
|
|
1649
|
+
balance.refetch();
|
|
1650
|
+
}, [balance.refetch]);
|
|
1651
|
+
return balance;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/components/BalanceBadge.tsx
|
|
1655
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1656
|
+
function BalanceBadge({ className, numberOnly, children }) {
|
|
1657
|
+
const { credits, displayCurrency, creditsPerMinorUnit, displayExchangeRate, loading } = useBalance();
|
|
1658
|
+
if (children) {
|
|
1659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: children({ credits, loading, displayCurrency, creditsPerMinorUnit }) });
|
|
1660
|
+
}
|
|
1661
|
+
if (loading) {
|
|
1662
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className, "aria-busy": "true" });
|
|
1663
|
+
}
|
|
1664
|
+
if (credits == null) {
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
const formattedCredits = new Intl.NumberFormat().format(credits);
|
|
1668
|
+
if (numberOnly) {
|
|
1669
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className, children: formattedCredits });
|
|
1670
|
+
}
|
|
1671
|
+
let currencyEquivalent = "";
|
|
1672
|
+
if (displayCurrency && creditsPerMinorUnit) {
|
|
1673
|
+
const usdCents = credits / creditsPerMinorUnit;
|
|
1674
|
+
const displayMajorUnits = usdCents * (displayExchangeRate ?? 1) / 100;
|
|
1675
|
+
currencyEquivalent = ` (~${new Intl.NumberFormat(void 0, {
|
|
1676
|
+
style: "currency",
|
|
1677
|
+
currency: displayCurrency,
|
|
1678
|
+
minimumFractionDigits: 2
|
|
1679
|
+
}).format(displayMajorUnits)})`;
|
|
1680
|
+
}
|
|
1681
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className, children: [
|
|
1682
|
+
formattedCredits,
|
|
1683
|
+
" credits",
|
|
1684
|
+
currencyEquivalent
|
|
1685
|
+
] });
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1233
1688
|
// src/hooks/usePurchaseStatus.ts
|
|
1234
|
-
var
|
|
1689
|
+
var import_react12 = require("react");
|
|
1235
1690
|
function usePurchaseStatus() {
|
|
1236
1691
|
const { purchases } = usePurchase();
|
|
1237
|
-
const isPaidPurchase2 = (0,
|
|
1692
|
+
const isPaidPurchase2 = (0, import_react12.useCallback)((p) => {
|
|
1238
1693
|
return (p.amount ?? 0) > 0;
|
|
1239
1694
|
}, []);
|
|
1240
|
-
const purchaseData = (0,
|
|
1695
|
+
const purchaseData = (0, import_react12.useMemo)(() => {
|
|
1241
1696
|
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
1242
1697
|
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
1243
1698
|
});
|
|
@@ -1250,7 +1705,7 @@ function usePurchaseStatus() {
|
|
|
1250
1705
|
shouldShowCancelledNotice
|
|
1251
1706
|
};
|
|
1252
1707
|
}, [purchases, isPaidPurchase2]);
|
|
1253
|
-
const formatDate = (0,
|
|
1708
|
+
const formatDate = (0, import_react12.useCallback)((dateString) => {
|
|
1254
1709
|
if (!dateString) return null;
|
|
1255
1710
|
return new Date(dateString).toLocaleDateString("en-US", {
|
|
1256
1711
|
year: "numeric",
|
|
@@ -1258,7 +1713,7 @@ function usePurchaseStatus() {
|
|
|
1258
1713
|
day: "numeric"
|
|
1259
1714
|
});
|
|
1260
1715
|
}, []);
|
|
1261
|
-
const getDaysUntilExpiration = (0,
|
|
1716
|
+
const getDaysUntilExpiration = (0, import_react12.useCallback)((endDate) => {
|
|
1262
1717
|
if (!endDate) return null;
|
|
1263
1718
|
const now = /* @__PURE__ */ new Date();
|
|
1264
1719
|
const expiration = new Date(endDate);
|
|
@@ -1273,8 +1728,207 @@ function usePurchaseStatus() {
|
|
|
1273
1728
|
getDaysUntilExpiration
|
|
1274
1729
|
};
|
|
1275
1730
|
}
|
|
1731
|
+
|
|
1732
|
+
// src/hooks/usePurchaseActions.ts
|
|
1733
|
+
var import_react13 = require("react");
|
|
1734
|
+
function usePurchaseActions() {
|
|
1735
|
+
const ctx = useSolvaPay();
|
|
1736
|
+
const [isCancelling, setIsCancelling] = (0, import_react13.useState)(false);
|
|
1737
|
+
const [isReactivating, setIsReactivating] = (0, import_react13.useState)(false);
|
|
1738
|
+
const [isActivating, setIsActivating] = (0, import_react13.useState)(false);
|
|
1739
|
+
const cancelRenewal = (0, import_react13.useCallback)(
|
|
1740
|
+
async (params) => {
|
|
1741
|
+
setIsCancelling(true);
|
|
1742
|
+
try {
|
|
1743
|
+
return await ctx.cancelRenewal(params);
|
|
1744
|
+
} finally {
|
|
1745
|
+
setIsCancelling(false);
|
|
1746
|
+
}
|
|
1747
|
+
},
|
|
1748
|
+
[ctx.cancelRenewal]
|
|
1749
|
+
);
|
|
1750
|
+
const reactivateRenewal = (0, import_react13.useCallback)(
|
|
1751
|
+
async (params) => {
|
|
1752
|
+
setIsReactivating(true);
|
|
1753
|
+
try {
|
|
1754
|
+
return await ctx.reactivateRenewal(params);
|
|
1755
|
+
} finally {
|
|
1756
|
+
setIsReactivating(false);
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
[ctx.reactivateRenewal]
|
|
1760
|
+
);
|
|
1761
|
+
const activatePlan = (0, import_react13.useCallback)(
|
|
1762
|
+
async (params) => {
|
|
1763
|
+
setIsActivating(true);
|
|
1764
|
+
try {
|
|
1765
|
+
return await ctx.activatePlan(params);
|
|
1766
|
+
} finally {
|
|
1767
|
+
setIsActivating(false);
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
[ctx.activatePlan]
|
|
1771
|
+
);
|
|
1772
|
+
return {
|
|
1773
|
+
cancelRenewal,
|
|
1774
|
+
reactivateRenewal,
|
|
1775
|
+
activatePlan,
|
|
1776
|
+
isCancelling,
|
|
1777
|
+
isReactivating,
|
|
1778
|
+
isActivating
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// src/hooks/useActivation.ts
|
|
1783
|
+
var import_react14 = require("react");
|
|
1784
|
+
function useActivation() {
|
|
1785
|
+
const { activatePlan } = useSolvaPay();
|
|
1786
|
+
const [state, setState] = (0, import_react14.useState)("idle");
|
|
1787
|
+
const [error, setError] = (0, import_react14.useState)(null);
|
|
1788
|
+
const [result, setResult] = (0, import_react14.useState)(null);
|
|
1789
|
+
const activate = (0, import_react14.useCallback)(
|
|
1790
|
+
async (params) => {
|
|
1791
|
+
setState("activating");
|
|
1792
|
+
setError(null);
|
|
1793
|
+
setResult(null);
|
|
1794
|
+
try {
|
|
1795
|
+
const data = await activatePlan(params);
|
|
1796
|
+
setResult(data);
|
|
1797
|
+
switch (data.status) {
|
|
1798
|
+
case "activated":
|
|
1799
|
+
case "already_active":
|
|
1800
|
+
setState("activated");
|
|
1801
|
+
break;
|
|
1802
|
+
case "topup_required":
|
|
1803
|
+
setState("topup_required");
|
|
1804
|
+
break;
|
|
1805
|
+
case "payment_required":
|
|
1806
|
+
setError("This plan requires payment. Please select a different plan.");
|
|
1807
|
+
setState("payment_required");
|
|
1808
|
+
break;
|
|
1809
|
+
case "invalid":
|
|
1810
|
+
setError(data.message || "Invalid plan configuration.");
|
|
1811
|
+
setState("error");
|
|
1812
|
+
break;
|
|
1813
|
+
default:
|
|
1814
|
+
setError("Unexpected response from server.");
|
|
1815
|
+
setState("error");
|
|
1816
|
+
}
|
|
1817
|
+
} catch (err) {
|
|
1818
|
+
setError(err instanceof Error ? err.message : "Activation failed");
|
|
1819
|
+
setState("error");
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
[activatePlan]
|
|
1823
|
+
);
|
|
1824
|
+
const reset = (0, import_react14.useCallback)(() => {
|
|
1825
|
+
setState("idle");
|
|
1826
|
+
setError(null);
|
|
1827
|
+
setResult(null);
|
|
1828
|
+
}, []);
|
|
1829
|
+
return { activate, state, error, result, reset };
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// src/hooks/useTopupAmountSelector.ts
|
|
1833
|
+
var import_react15 = require("react");
|
|
1834
|
+
function getQuickAmounts(currency) {
|
|
1835
|
+
switch (currency.toUpperCase()) {
|
|
1836
|
+
case "SEK":
|
|
1837
|
+
case "NOK":
|
|
1838
|
+
case "DKK":
|
|
1839
|
+
return [100, 500, 1e3, 5e3];
|
|
1840
|
+
case "JPY":
|
|
1841
|
+
return [1e3, 5e3, 1e4, 5e4];
|
|
1842
|
+
case "KRW":
|
|
1843
|
+
return [1e4, 5e4, 1e5, 5e5];
|
|
1844
|
+
case "ISK":
|
|
1845
|
+
case "HUF":
|
|
1846
|
+
return [1e3, 5e3, 1e4, 5e4];
|
|
1847
|
+
default:
|
|
1848
|
+
return [10, 50, 100, 500];
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
function getCurrencySymbol(currency) {
|
|
1852
|
+
try {
|
|
1853
|
+
const parts = new Intl.NumberFormat("en", { style: "currency", currency }).formatToParts(0);
|
|
1854
|
+
const sym = parts.find((p) => p.type === "currency");
|
|
1855
|
+
return sym?.value || currency;
|
|
1856
|
+
} catch {
|
|
1857
|
+
return currency;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
var DEFAULT_MIN = 1;
|
|
1861
|
+
var DEFAULT_MAX = 1e5;
|
|
1862
|
+
function useTopupAmountSelector(options) {
|
|
1863
|
+
const { currency, minAmount = DEFAULT_MIN, maxAmount = DEFAULT_MAX } = options;
|
|
1864
|
+
const [selectedAmount, setSelectedAmount] = (0, import_react15.useState)(null);
|
|
1865
|
+
const [customAmount, setCustomAmountRaw] = (0, import_react15.useState)("");
|
|
1866
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
1867
|
+
const quickAmounts = (0, import_react15.useMemo)(() => getQuickAmounts(currency), [currency]);
|
|
1868
|
+
const currencySymbol = (0, import_react15.useMemo)(() => getCurrencySymbol(currency), [currency]);
|
|
1869
|
+
const resolvedAmount = (0, import_react15.useMemo)(() => {
|
|
1870
|
+
if (customAmount) {
|
|
1871
|
+
const parsed = parseFloat(customAmount);
|
|
1872
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
1873
|
+
}
|
|
1874
|
+
return selectedAmount;
|
|
1875
|
+
}, [selectedAmount, customAmount]);
|
|
1876
|
+
const selectQuickAmount = (0, import_react15.useCallback)(
|
|
1877
|
+
(amount) => {
|
|
1878
|
+
setSelectedAmount(amount);
|
|
1879
|
+
setCustomAmountRaw("");
|
|
1880
|
+
setError(null);
|
|
1881
|
+
},
|
|
1882
|
+
[]
|
|
1883
|
+
);
|
|
1884
|
+
const setCustomAmount = (0, import_react15.useCallback)(
|
|
1885
|
+
(value) => {
|
|
1886
|
+
const sanitized = value.replace(/[^0-9.]/g, "");
|
|
1887
|
+
const dotIndex = sanitized.indexOf(".");
|
|
1888
|
+
const cleaned = dotIndex === -1 ? sanitized : sanitized.slice(0, dotIndex + 1) + sanitized.slice(dotIndex + 1).replace(/\./g, "");
|
|
1889
|
+
setCustomAmountRaw(cleaned);
|
|
1890
|
+
setSelectedAmount(null);
|
|
1891
|
+
setError(null);
|
|
1892
|
+
},
|
|
1893
|
+
[]
|
|
1894
|
+
);
|
|
1895
|
+
const validate = (0, import_react15.useCallback)(() => {
|
|
1896
|
+
if (resolvedAmount == null) {
|
|
1897
|
+
setError("Please select or enter an amount");
|
|
1898
|
+
return false;
|
|
1899
|
+
}
|
|
1900
|
+
if (resolvedAmount < minAmount) {
|
|
1901
|
+
setError(`Minimum amount is ${currencySymbol}${minAmount}`);
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
if (resolvedAmount > maxAmount) {
|
|
1905
|
+
setError(`Maximum amount is ${currencySymbol}${maxAmount.toLocaleString()}`);
|
|
1906
|
+
return false;
|
|
1907
|
+
}
|
|
1908
|
+
setError(null);
|
|
1909
|
+
return true;
|
|
1910
|
+
}, [resolvedAmount, minAmount, maxAmount, currencySymbol]);
|
|
1911
|
+
const reset = (0, import_react15.useCallback)(() => {
|
|
1912
|
+
setSelectedAmount(null);
|
|
1913
|
+
setCustomAmountRaw("");
|
|
1914
|
+
setError(null);
|
|
1915
|
+
}, []);
|
|
1916
|
+
return {
|
|
1917
|
+
quickAmounts,
|
|
1918
|
+
selectedAmount,
|
|
1919
|
+
customAmount,
|
|
1920
|
+
resolvedAmount,
|
|
1921
|
+
selectQuickAmount,
|
|
1922
|
+
setCustomAmount,
|
|
1923
|
+
error,
|
|
1924
|
+
validate,
|
|
1925
|
+
reset,
|
|
1926
|
+
currencySymbol
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1276
1929
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1277
1930
|
0 && (module.exports = {
|
|
1931
|
+
BalanceBadge,
|
|
1278
1932
|
PaymentForm,
|
|
1279
1933
|
PlanBadge,
|
|
1280
1934
|
PlanSelector,
|
|
@@ -1284,6 +1938,7 @@ function usePurchaseStatus() {
|
|
|
1284
1938
|
SolvaPayProvider,
|
|
1285
1939
|
Spinner,
|
|
1286
1940
|
StripePaymentFormWrapper,
|
|
1941
|
+
TopupForm,
|
|
1287
1942
|
defaultAuthAdapter,
|
|
1288
1943
|
filterPurchases,
|
|
1289
1944
|
getActivePurchases,
|
|
@@ -1291,10 +1946,15 @@ function usePurchaseStatus() {
|
|
|
1291
1946
|
getMostRecentPurchase,
|
|
1292
1947
|
getPrimaryPurchase,
|
|
1293
1948
|
isPaidPurchase,
|
|
1949
|
+
useActivation,
|
|
1950
|
+
useBalance,
|
|
1294
1951
|
useCheckout,
|
|
1295
1952
|
useCustomer,
|
|
1296
1953
|
usePlans,
|
|
1297
1954
|
usePurchase,
|
|
1955
|
+
usePurchaseActions,
|
|
1298
1956
|
usePurchaseStatus,
|
|
1299
|
-
useSolvaPay
|
|
1957
|
+
useSolvaPay,
|
|
1958
|
+
useTopup,
|
|
1959
|
+
useTopupAmountSelector
|
|
1300
1960
|
});
|