@solvapay/react 1.0.9-preview.1 → 1.0.9
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/CHANGELOG.md +209 -0
- package/CONTRIBUTING.md +117 -0
- package/README.md +426 -24
- package/dist/adapters/auth.d.cts +13 -0
- package/dist/adapters/auth.d.ts +13 -0
- package/dist/adapters/auth.js +1 -0
- package/dist/chunk-37R5NZGF.js +4824 -0
- package/dist/chunk-HWVJL5X6.js +429 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-R2ZPZ7VM.js +597 -0
- package/dist/chunk-ZAV7CQ4G.js +361 -0
- package/dist/index-WBjulQHf.d.cts +3656 -0
- package/dist/index-onWNU7iT.d.ts +3656 -0
- package/dist/index.cjs +5556 -1143
- package/dist/index.d.cts +618 -708
- package/dist/index.d.ts +618 -708
- package/dist/index.js +597 -1664
- package/dist/mcp/index.cjs +8096 -0
- package/dist/mcp/index.d.cts +1013 -0
- package/dist/mcp/index.d.ts +1013 -0
- package/dist/mcp/index.js +2724 -0
- package/dist/mcp/styles.css +1020 -0
- package/dist/primitives/index.cjs +5316 -0
- package/dist/primitives/index.d.cts +721 -0
- package/dist/primitives/index.d.ts +721 -0
- package/dist/primitives/index.js +232 -0
- package/dist/styles.css +1115 -0
- package/dist/useUsage-BmOYXxgG.d.cts +413 -0
- package/dist/useUsage-nD7zwSbG.d.ts +413 -0
- package/dist/webapi-K5XBCEO6.js +3775 -0
- package/package.json +37 -6
package/dist/index.js
CHANGED
|
@@ -1,820 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CurrentPlanCard,
|
|
3
|
+
LaunchCustomerPortalButton,
|
|
4
|
+
UpdatePaymentMethodButton,
|
|
5
|
+
usePaymentMethod
|
|
6
|
+
} from "./chunk-HWVJL5X6.js";
|
|
7
|
+
import {
|
|
8
|
+
ActivationFlow,
|
|
9
|
+
CreditGate,
|
|
10
|
+
PlanBadge,
|
|
11
|
+
ProductBadge,
|
|
12
|
+
PurchaseGate,
|
|
13
|
+
TopupForm,
|
|
14
|
+
useActivationFlow,
|
|
15
|
+
useCreditGate
|
|
16
|
+
} from "./chunk-R2ZPZ7VM.js";
|
|
17
|
+
import {
|
|
18
|
+
AmountPicker,
|
|
19
|
+
BalanceBadge,
|
|
20
|
+
CancelPlanButton,
|
|
21
|
+
CancelledPlanNotice,
|
|
22
|
+
CheckoutSummary2 as CheckoutSummary,
|
|
23
|
+
CopyContext,
|
|
24
|
+
CopyProvider,
|
|
25
|
+
DEFAULT_ROUTES,
|
|
26
|
+
MandateText,
|
|
27
|
+
PaymentForm,
|
|
28
|
+
PaymentFormCardElement,
|
|
29
|
+
PaymentFormContext,
|
|
30
|
+
PaymentFormCustomerFields,
|
|
31
|
+
PaymentFormError,
|
|
32
|
+
PaymentFormLoading,
|
|
33
|
+
PaymentFormMandateText,
|
|
34
|
+
PaymentFormPaymentElement,
|
|
35
|
+
PaymentFormProvider,
|
|
36
|
+
PaymentFormSubmitButton,
|
|
37
|
+
PaymentFormSummary,
|
|
38
|
+
PaymentFormTermsCheckbox,
|
|
39
|
+
PlanSelector,
|
|
40
|
+
SolvaPayContext,
|
|
41
|
+
SolvaPayProvider,
|
|
42
|
+
Spinner,
|
|
43
|
+
confirmPayment,
|
|
44
|
+
createHttpTransport,
|
|
45
|
+
defaultListPlans,
|
|
46
|
+
deriveVariant,
|
|
47
|
+
enCopy,
|
|
48
|
+
filterPurchases,
|
|
49
|
+
formatPrice,
|
|
50
|
+
getActivePurchases,
|
|
51
|
+
getCancelledPurchasesWithEndDate,
|
|
52
|
+
getMinorUnitsPerMajor,
|
|
53
|
+
getMostRecentPurchase,
|
|
54
|
+
getPrimaryPurchase,
|
|
55
|
+
interpolate,
|
|
56
|
+
isPaidPurchase,
|
|
57
|
+
isPlanPurchase,
|
|
58
|
+
isTopupPurchase,
|
|
59
|
+
mergeCopy,
|
|
60
|
+
resolveCta,
|
|
61
|
+
toMajorUnits,
|
|
62
|
+
useActivation,
|
|
63
|
+
useAmountPicker,
|
|
64
|
+
useAmountPickerCopy,
|
|
65
|
+
useBalance,
|
|
66
|
+
useCheckout,
|
|
67
|
+
useCopy,
|
|
68
|
+
useCustomer,
|
|
69
|
+
useLocale,
|
|
70
|
+
useMerchant,
|
|
71
|
+
usePaymentForm,
|
|
72
|
+
usePaywallResolver,
|
|
73
|
+
usePlan,
|
|
74
|
+
usePlans,
|
|
75
|
+
useProduct,
|
|
76
|
+
usePurchase,
|
|
77
|
+
usePurchaseActions,
|
|
78
|
+
usePurchaseStatus,
|
|
79
|
+
useSolvaPay,
|
|
80
|
+
useTopup,
|
|
81
|
+
useTopupAmountSelector,
|
|
82
|
+
useTransport,
|
|
83
|
+
useUsage
|
|
84
|
+
} from "./chunk-37R5NZGF.js";
|
|
1
85
|
import {
|
|
2
86
|
defaultAuthAdapter
|
|
3
87
|
} from "./chunk-OUSEQRCT.js";
|
|
4
|
-
|
|
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
|
-
};
|
|
88
|
+
import "./chunk-MLKGABMK.js";
|
|
605
89
|
|
|
606
90
|
// 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
|
-
);
|
|
91
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
92
|
+
var DefaultTree = ({
|
|
93
|
+
requireTermsAcceptance
|
|
94
|
+
}) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
95
|
+
/* @__PURE__ */ jsx(PaymentForm.Summary, {}),
|
|
96
|
+
/* @__PURE__ */ jsx(PaymentForm.CustomerFields, {}),
|
|
97
|
+
/* @__PURE__ */ jsx(PaymentForm.PaymentElement, {}),
|
|
98
|
+
/* @__PURE__ */ jsx(PaymentForm.Error, {}),
|
|
99
|
+
/* @__PURE__ */ jsx(PaymentForm.MandateText, {}),
|
|
100
|
+
requireTermsAcceptance && /* @__PURE__ */ jsx(PaymentForm.TermsCheckbox, {}),
|
|
101
|
+
/* @__PURE__ */ jsx(PaymentForm.SubmitButton, {})
|
|
102
|
+
] });
|
|
103
|
+
var PaymentFormBase = (props) => {
|
|
104
|
+
const { children, requireTermsAcceptance = false } = props;
|
|
105
|
+
return /* @__PURE__ */ jsx(PaymentForm.Root, { ...props, children: children ?? /* @__PURE__ */ jsx(DefaultTree, { requireTermsAcceptance }) });
|
|
781
106
|
};
|
|
107
|
+
var PaymentForm2 = Object.assign(PaymentFormBase, {
|
|
108
|
+
Summary: PaymentFormSummary,
|
|
109
|
+
CustomerFields: PaymentFormCustomerFields,
|
|
110
|
+
PaymentElement: PaymentFormPaymentElement,
|
|
111
|
+
CardElement: PaymentFormCardElement,
|
|
112
|
+
MandateText: PaymentFormMandateText,
|
|
113
|
+
TermsCheckbox: PaymentFormTermsCheckbox,
|
|
114
|
+
SubmitButton: PaymentFormSubmitButton,
|
|
115
|
+
Loading: PaymentFormLoading,
|
|
116
|
+
Error: PaymentFormError
|
|
117
|
+
});
|
|
782
118
|
|
|
783
119
|
// src/components/StripePaymentFormWrapper.tsx
|
|
784
|
-
import { useState
|
|
120
|
+
import { useState } from "react";
|
|
785
121
|
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";
|
|
122
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
800
123
|
var StripePaymentFormWrapper = ({
|
|
801
124
|
onSuccess,
|
|
802
125
|
onError,
|
|
803
126
|
returnUrl: _returnUrl,
|
|
804
|
-
submitButtonText
|
|
127
|
+
submitButtonText,
|
|
805
128
|
buttonClassName,
|
|
806
129
|
clientSecret
|
|
807
130
|
}) => {
|
|
808
131
|
const stripe = useStripe();
|
|
809
132
|
const elements = useElements();
|
|
810
133
|
const customer = useCustomer();
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const [
|
|
134
|
+
const copy = useCopy();
|
|
135
|
+
const effectiveSubmitText = submitButtonText ?? copy.cta.payNow;
|
|
136
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
137
|
+
const [message, setMessage] = useState(null);
|
|
138
|
+
const [cardComplete, setCardComplete] = useState(false);
|
|
814
139
|
const handleSubmit = async (event) => {
|
|
815
140
|
event.preventDefault();
|
|
816
141
|
if (!stripe || !elements) {
|
|
817
|
-
const errorMessage =
|
|
142
|
+
const errorMessage = copy.errors.stripeUnavailable;
|
|
818
143
|
setMessage(errorMessage);
|
|
819
144
|
if (onError) {
|
|
820
145
|
onError(new Error(errorMessage));
|
|
@@ -822,7 +147,7 @@ var StripePaymentFormWrapper = ({
|
|
|
822
147
|
return;
|
|
823
148
|
}
|
|
824
149
|
if (!clientSecret) {
|
|
825
|
-
const errorMessage =
|
|
150
|
+
const errorMessage = copy.errors.paymentIntentUnavailable;
|
|
826
151
|
setMessage(errorMessage);
|
|
827
152
|
if (onError) {
|
|
828
153
|
onError(new Error(errorMessage));
|
|
@@ -834,7 +159,7 @@ var StripePaymentFormWrapper = ({
|
|
|
834
159
|
try {
|
|
835
160
|
const cardElement = elements.getElement(CardElement);
|
|
836
161
|
if (!cardElement) {
|
|
837
|
-
const errorMessage =
|
|
162
|
+
const errorMessage = copy.errors.cardElementMissing;
|
|
838
163
|
setMessage(errorMessage);
|
|
839
164
|
setIsProcessing(false);
|
|
840
165
|
if (onError) {
|
|
@@ -852,7 +177,7 @@ var StripePaymentFormWrapper = ({
|
|
|
852
177
|
}
|
|
853
178
|
});
|
|
854
179
|
if (error) {
|
|
855
|
-
const errorMessage = error.message ||
|
|
180
|
+
const errorMessage = error.message || copy.errors.paymentUnexpected;
|
|
856
181
|
setMessage(errorMessage);
|
|
857
182
|
setIsProcessing(false);
|
|
858
183
|
if (onError) {
|
|
@@ -865,8 +190,8 @@ var StripePaymentFormWrapper = ({
|
|
|
865
190
|
await onSuccess(paymentIntent);
|
|
866
191
|
setMessage("Payment successful!");
|
|
867
192
|
} catch (err) {
|
|
868
|
-
const error2 = err instanceof Error ? err : new Error(
|
|
869
|
-
setMessage(
|
|
193
|
+
const error2 = err instanceof Error ? err : new Error(copy.errors.paymentProcessingFailed);
|
|
194
|
+
setMessage(copy.errors.paymentProcessingFailed);
|
|
870
195
|
setIsProcessing(false);
|
|
871
196
|
if (onError) {
|
|
872
197
|
onError(error2);
|
|
@@ -877,14 +202,18 @@ var StripePaymentFormWrapper = ({
|
|
|
877
202
|
setMessage("Payment successful!");
|
|
878
203
|
}
|
|
879
204
|
} else if (paymentIntent && paymentIntent.status === "requires_action") {
|
|
880
|
-
setMessage(
|
|
205
|
+
setMessage(copy.errors.paymentRequires3ds);
|
|
881
206
|
setIsProcessing(false);
|
|
882
207
|
} else if (paymentIntent) {
|
|
883
|
-
setMessage(
|
|
208
|
+
setMessage(
|
|
209
|
+
interpolate(copy.errors.paymentStatusPrefix, {
|
|
210
|
+
status: paymentIntent.status || "processing"
|
|
211
|
+
})
|
|
212
|
+
);
|
|
884
213
|
setIsProcessing(false);
|
|
885
214
|
}
|
|
886
215
|
} catch (err) {
|
|
887
|
-
const error = err instanceof Error ? err : new Error(
|
|
216
|
+
const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
|
|
888
217
|
setMessage(error.message);
|
|
889
218
|
if (onError) {
|
|
890
219
|
onError(error);
|
|
@@ -912,8 +241,8 @@ var StripePaymentFormWrapper = ({
|
|
|
912
241
|
},
|
|
913
242
|
hidePostalCode: true
|
|
914
243
|
};
|
|
915
|
-
return /* @__PURE__ */ jsxs2(
|
|
916
|
-
isReady ? /* @__PURE__ */
|
|
244
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
245
|
+
isReady ? /* @__PURE__ */ jsx2(
|
|
917
246
|
CardElement,
|
|
918
247
|
{
|
|
919
248
|
options: cardElementOptions,
|
|
@@ -927,9 +256,9 @@ var StripePaymentFormWrapper = ({
|
|
|
927
256
|
}
|
|
928
257
|
}
|
|
929
258
|
}
|
|
930
|
-
) : /* @__PURE__ */
|
|
931
|
-
message && !isSuccess && /* @__PURE__ */
|
|
932
|
-
/* @__PURE__ */
|
|
259
|
+
) : /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ jsx2(Spinner, { size: "sm" }) }),
|
|
260
|
+
message && !isSuccess && /* @__PURE__ */ jsx2("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
261
|
+
/* @__PURE__ */ jsx2(
|
|
933
262
|
"button",
|
|
934
263
|
{
|
|
935
264
|
type: "submit",
|
|
@@ -938,940 +267,544 @@ var StripePaymentFormWrapper = ({
|
|
|
938
267
|
"aria-busy": isProcessing,
|
|
939
268
|
"aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
|
|
940
269
|
onClick: handleSubmit,
|
|
941
|
-
children: isProcessing ? /* @__PURE__ */ jsxs2(
|
|
942
|
-
/* @__PURE__ */
|
|
943
|
-
/* @__PURE__ */
|
|
944
|
-
] }) :
|
|
270
|
+
children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
271
|
+
/* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
|
|
272
|
+
/* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
|
|
273
|
+
] }) : effectiveSubmitText
|
|
945
274
|
}
|
|
946
275
|
)
|
|
947
276
|
] });
|
|
948
277
|
};
|
|
949
278
|
|
|
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: [
|
|
279
|
+
// src/components/CheckoutLayout.tsx
|
|
280
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState as useState2 } from "react";
|
|
281
|
+
|
|
282
|
+
// src/components/PlanSelector.tsx
|
|
283
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
284
|
+
var DefaultTree2 = () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
285
|
+
/* @__PURE__ */ jsx3(PlanSelector.Heading, { className: "solvapay-plan-selector-heading" }),
|
|
286
|
+
/* @__PURE__ */ jsx3(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ jsxs3(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
|
|
287
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
|
|
288
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
|
|
289
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardPrice, { className: "solvapay-plan-selector-card-price" }),
|
|
290
|
+
/* @__PURE__ */ jsx3(PlanSelector.CardInterval, { className: "solvapay-plan-selector-card-interval" })
|
|
291
|
+
] }) }),
|
|
292
|
+
/* @__PURE__ */ jsx3(PlanSelector.Loading, { className: "solvapay-plan-selector-loading" }),
|
|
293
|
+
/* @__PURE__ */ jsx3(PlanSelector.Error, { className: "solvapay-plan-selector-error" })
|
|
294
|
+
] });
|
|
295
|
+
var PlanSelector2 = (props) => {
|
|
296
|
+
const { children, className, ...rootProps } = props;
|
|
297
|
+
const rootClass = ["solvapay-plan-selector", className].filter(Boolean).join(" ");
|
|
298
|
+
return /* @__PURE__ */ jsx3(PlanSelector.Root, { ...rootProps, className: rootClass, children: children ?? /* @__PURE__ */ jsx3(DefaultTree2, {}) });
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// src/components/ActivationFlow.tsx
|
|
302
|
+
import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
303
|
+
var ActivationFlow2 = (props) => {
|
|
304
|
+
const { className, onBack, ...rootProps } = props;
|
|
305
|
+
const rootClass = ["solvapay-activation-flow", className].filter(Boolean).join(" ");
|
|
306
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Root, { ...rootProps, className: rootClass, children: [
|
|
307
|
+
/* @__PURE__ */ jsx4(SummaryStep, {}),
|
|
308
|
+
/* @__PURE__ */ jsx4(AmountStep, {}),
|
|
309
|
+
/* @__PURE__ */ jsx4(TopupPaymentStep, {}),
|
|
310
|
+
/* @__PURE__ */ jsx4(RetryingStep, {}),
|
|
311
|
+
/* @__PURE__ */ jsx4(ActivatedStep, {}),
|
|
312
|
+
/* @__PURE__ */ jsx4(ErrorStep, {}),
|
|
313
|
+
onBack && /* @__PURE__ */ jsx4(BackButton, { onBack })
|
|
314
|
+
] });
|
|
315
|
+
};
|
|
316
|
+
var SummaryStep = () => {
|
|
317
|
+
const copy = useCopy();
|
|
318
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Summary, { className: "solvapay-activation-flow-summary", children: [
|
|
319
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.heading }),
|
|
320
|
+
/* @__PURE__ */ jsx4(CheckoutSummary, {}),
|
|
321
|
+
/* @__PURE__ */ jsx4(ActivationFlow.ActivateButton, { className: "solvapay-activation-flow-activate" })
|
|
322
|
+
] });
|
|
323
|
+
};
|
|
324
|
+
var AmountStep = () => {
|
|
325
|
+
const copy = useCopy();
|
|
326
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.AmountPicker, { children: [
|
|
327
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-topup-heading", children: copy.activationFlow.topupHeading }),
|
|
328
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-activation-flow-topup-subheading", children: copy.activationFlow.topupSubheading }),
|
|
329
|
+
/* @__PURE__ */ jsx4(AmountPickerBody, {}),
|
|
330
|
+
/* @__PURE__ */ jsx4(ActivationFlow.ContinueButton, { className: "solvapay-activation-flow-continue" })
|
|
331
|
+
] });
|
|
332
|
+
};
|
|
333
|
+
var AmountPickerBody = () => {
|
|
334
|
+
const ctx = useAmountPicker();
|
|
335
|
+
const { selectAmountLabel, customAmountLabel } = useAmountPickerCopy();
|
|
336
|
+
return /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
337
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
|
|
338
|
+
/* @__PURE__ */ jsx4("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx4(
|
|
339
|
+
AmountPicker.Option,
|
|
340
|
+
{
|
|
341
|
+
amount,
|
|
342
|
+
className: "solvapay-amount-picker-pill"
|
|
343
|
+
},
|
|
344
|
+
amount
|
|
345
|
+
)) }),
|
|
346
|
+
/* @__PURE__ */ jsxs4("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
|
|
347
|
+
/* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
|
|
348
|
+
/* @__PURE__ */ jsx4(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
|
|
349
|
+
] })
|
|
350
|
+
] });
|
|
351
|
+
};
|
|
352
|
+
var TopupPaymentStep = () => {
|
|
353
|
+
const ctx = useActivationFlow();
|
|
354
|
+
const copy = useCopy();
|
|
355
|
+
if (ctx.step !== "topupPayment") return null;
|
|
356
|
+
return /* @__PURE__ */ jsxs4("div", { "data-solvapay-activation-flow-topup-payment": "", children: [
|
|
1063
357
|
/* @__PURE__ */ jsx4(
|
|
1064
|
-
"
|
|
358
|
+
"button",
|
|
1065
359
|
{
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
justifyContent: "center"
|
|
1071
|
-
},
|
|
1072
|
-
children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
|
|
360
|
+
type: "button",
|
|
361
|
+
onClick: ctx.backToSelectAmount,
|
|
362
|
+
className: "solvapay-activation-flow-change-amount",
|
|
363
|
+
children: copy.activationFlow.changeAmountButton
|
|
1073
364
|
}
|
|
1074
365
|
),
|
|
1075
366
|
/* @__PURE__ */ jsx4(
|
|
1076
|
-
|
|
367
|
+
TopupForm,
|
|
1077
368
|
{
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
"aria-disabled": "true",
|
|
1083
|
-
children: submitButtonText
|
|
369
|
+
amount: ctx.amountMinor,
|
|
370
|
+
currency: ctx.currency,
|
|
371
|
+
onSuccess: ctx.onTopupSuccess,
|
|
372
|
+
onError: (err) => ctx.onError?.(err instanceof Error ? err : new Error(String(err)))
|
|
1084
373
|
}
|
|
1085
374
|
)
|
|
1086
|
-
] })
|
|
375
|
+
] });
|
|
1087
376
|
};
|
|
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);
|
|
377
|
+
var RetryingStep = () => {
|
|
378
|
+
const copy = useCopy();
|
|
379
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Retrying, { className: "solvapay-activation-flow-retrying", children: [
|
|
380
|
+
/* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.retryingHeading }),
|
|
381
|
+
/* @__PURE__ */ jsx4("p", { children: copy.activationFlow.retryingSubheading })
|
|
382
|
+
] });
|
|
383
|
+
};
|
|
384
|
+
var ActivatedStep = () => {
|
|
385
|
+
const copy = useCopy();
|
|
386
|
+
return /* @__PURE__ */ jsxs4(ActivationFlow.Activated, { className: "solvapay-activation-flow-activated", children: [
|
|
387
|
+
/* @__PURE__ */ jsx4("h3", { children: copy.activationFlow.activatedHeading }),
|
|
388
|
+
/* @__PURE__ */ jsx4("p", { children: copy.activationFlow.activatedSubheading })
|
|
389
|
+
] });
|
|
390
|
+
};
|
|
391
|
+
var ErrorStep = () => {
|
|
392
|
+
const ctx = useActivationFlow();
|
|
393
|
+
const copy = useCopy();
|
|
394
|
+
if (ctx.step !== "error") return null;
|
|
395
|
+
return /* @__PURE__ */ jsxs4("div", { className: "solvapay-activation-flow-error", role: "alert", children: [
|
|
396
|
+
/* @__PURE__ */ jsx4("p", { children: ctx.error }),
|
|
397
|
+
/* @__PURE__ */ jsx4(
|
|
398
|
+
"button",
|
|
399
|
+
{
|
|
400
|
+
type: "button",
|
|
401
|
+
onClick: ctx.reset,
|
|
402
|
+
className: "solvapay-activation-flow-try-again",
|
|
403
|
+
children: copy.activationFlow.tryAgainButton
|
|
1139
404
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
405
|
+
)
|
|
406
|
+
] });
|
|
407
|
+
};
|
|
408
|
+
var BackButton = ({ onBack }) => {
|
|
409
|
+
const ctx = useActivationFlow();
|
|
410
|
+
const copy = useCopy();
|
|
411
|
+
if (ctx.step !== "summary") return null;
|
|
412
|
+
return /* @__PURE__ */ jsx4(
|
|
413
|
+
"button",
|
|
414
|
+
{
|
|
415
|
+
type: "button",
|
|
416
|
+
onClick: onBack,
|
|
417
|
+
className: "solvapay-activation-flow-back",
|
|
418
|
+
children: copy.activationFlow.backButton
|
|
1148
419
|
}
|
|
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
|
-
}
|
|
420
|
+
);
|
|
421
|
+
};
|
|
1166
422
|
|
|
1167
|
-
// src/
|
|
1168
|
-
import { jsx as jsx5, jsxs as
|
|
1169
|
-
var
|
|
1170
|
-
|
|
1171
|
-
|
|
423
|
+
// src/components/CheckoutLayout.tsx
|
|
424
|
+
import { Fragment as Fragment5, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
425
|
+
var BREAKPOINTS = {
|
|
426
|
+
chat: 480,
|
|
427
|
+
desktop: 768
|
|
428
|
+
};
|
|
429
|
+
function widthToSize(width) {
|
|
430
|
+
if (width < BREAKPOINTS.chat) return "chat";
|
|
431
|
+
if (width < BREAKPOINTS.desktop) return "mobile";
|
|
432
|
+
return "desktop";
|
|
433
|
+
}
|
|
434
|
+
var CheckoutLayout = ({
|
|
435
|
+
planRef,
|
|
436
|
+
productRef,
|
|
437
|
+
prefillCustomer,
|
|
438
|
+
size = "auto",
|
|
439
|
+
requireTermsAcceptance,
|
|
1172
440
|
onSuccess,
|
|
441
|
+
onResult,
|
|
442
|
+
onFreePlan,
|
|
1173
443
|
onError,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
444
|
+
initialPlanRef,
|
|
445
|
+
onPlanSelect,
|
|
446
|
+
planSelector,
|
|
447
|
+
showBackButton = true,
|
|
448
|
+
submitButtonText,
|
|
449
|
+
returnUrl
|
|
1178
450
|
}) => {
|
|
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);
|
|
451
|
+
const rootRef = useRef(null);
|
|
452
|
+
const [autoSize, setAutoSize] = useState2("desktop");
|
|
453
|
+
const copy = useCopy();
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
if (size !== "auto") return;
|
|
456
|
+
const el = rootRef.current;
|
|
457
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
458
|
+
const ro = new ResizeObserver((entries) => {
|
|
459
|
+
for (const entry of entries) {
|
|
460
|
+
setAutoSize(widthToSize(entry.contentRect.width));
|
|
1211
461
|
}
|
|
462
|
+
});
|
|
463
|
+
ro.observe(el);
|
|
464
|
+
return () => ro.disconnect();
|
|
465
|
+
}, [size]);
|
|
466
|
+
const resolvedSize = size === "auto" ? autoSize : size;
|
|
467
|
+
const [selectedPlanRef, setSelectedPlanRef] = useState2(planRef ?? null);
|
|
468
|
+
const [userStep, setUserStep] = useState2(planRef ? "pay" : "select");
|
|
469
|
+
const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
|
|
470
|
+
const { plan: resolvedPlan } = usePlan({
|
|
471
|
+
planRef: effectivePlanRef,
|
|
472
|
+
productRef
|
|
473
|
+
});
|
|
474
|
+
const isUsageBased = resolvedPlan?.type === "usage-based";
|
|
475
|
+
const step = useMemo(() => {
|
|
476
|
+
if (userStep === "pay" && isUsageBased) return "activate";
|
|
477
|
+
if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
|
|
478
|
+
return userStep;
|
|
479
|
+
}, [userStep, isUsageBased, resolvedPlan]);
|
|
480
|
+
const handlePlanSelect = useCallback(
|
|
481
|
+
(ref, plan) => {
|
|
482
|
+
setSelectedPlanRef(ref);
|
|
483
|
+
onPlanSelect?.(ref, plan);
|
|
484
|
+
setUserStep(plan.type === "usage-based" ? "activate" : "pay");
|
|
1212
485
|
},
|
|
1213
|
-
[
|
|
486
|
+
[onPlanSelect]
|
|
1214
487
|
);
|
|
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,
|
|
488
|
+
const handleBack = useCallback(() => {
|
|
489
|
+
if (planRef) return;
|
|
490
|
+
setUserStep("select");
|
|
491
|
+
}, [planRef]);
|
|
492
|
+
return /* @__PURE__ */ jsxs5(
|
|
493
|
+
"div",
|
|
1227
494
|
{
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
495
|
+
ref: rootRef,
|
|
496
|
+
"data-solvapay-checkout-layout": resolvedSize,
|
|
497
|
+
"data-solvapay-step": step,
|
|
498
|
+
className: "solvapay-checkout-layout",
|
|
499
|
+
children: [
|
|
500
|
+
!planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
|
|
501
|
+
"button",
|
|
502
|
+
{
|
|
503
|
+
type: "button",
|
|
504
|
+
onClick: handleBack,
|
|
505
|
+
"data-solvapay-checkout-back": "",
|
|
506
|
+
className: "solvapay-back",
|
|
507
|
+
children: copy.planSelector.backButton
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
step === "select" && /* @__PURE__ */ jsx5(
|
|
511
|
+
SelectStep,
|
|
512
|
+
{
|
|
513
|
+
productRef: productRef ?? "",
|
|
514
|
+
initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
|
|
515
|
+
filter: planSelector?.filter,
|
|
516
|
+
sortBy: planSelector?.sortBy,
|
|
517
|
+
popularPlanRef: planSelector?.popularPlanRef,
|
|
518
|
+
onContinue: handlePlanSelect
|
|
519
|
+
}
|
|
520
|
+
),
|
|
521
|
+
step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
|
|
522
|
+
ActivationFlow2,
|
|
523
|
+
{
|
|
524
|
+
productRef,
|
|
525
|
+
planRef: effectivePlanRef,
|
|
526
|
+
onBack: planRef ? void 0 : handleBack,
|
|
527
|
+
onSuccess: (result) => onResult?.(result),
|
|
528
|
+
onError
|
|
529
|
+
}
|
|
530
|
+
),
|
|
531
|
+
step === "pay" && /* @__PURE__ */ jsx5(
|
|
532
|
+
PaymentForm2,
|
|
533
|
+
{
|
|
534
|
+
planRef: effectivePlanRef,
|
|
535
|
+
productRef,
|
|
536
|
+
prefillCustomer,
|
|
537
|
+
requireTermsAcceptance,
|
|
538
|
+
onSuccess,
|
|
539
|
+
onResult,
|
|
540
|
+
onFreePlan,
|
|
541
|
+
onError,
|
|
542
|
+
submitButtonText,
|
|
543
|
+
returnUrl
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
};
|
|
550
|
+
var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
|
|
551
|
+
const copy = useCopy();
|
|
552
|
+
const solva = useContext(SolvaPayContext);
|
|
553
|
+
const config = solva?._config;
|
|
554
|
+
const fetcher = useCallback(
|
|
555
|
+
(ref) => defaultListPlans(ref, config),
|
|
556
|
+
[config]
|
|
557
|
+
);
|
|
558
|
+
const { plans, selectedPlan } = usePlans({
|
|
559
|
+
productRef,
|
|
560
|
+
fetcher,
|
|
561
|
+
filter,
|
|
562
|
+
sortBy,
|
|
563
|
+
autoSelectFirstPaid: true,
|
|
564
|
+
initialPlanRef
|
|
565
|
+
});
|
|
566
|
+
const [userSelected, setUserSelected] = useState2(null);
|
|
567
|
+
const selected = userSelected ?? (selectedPlan && selectedPlan.requiresPayment !== false ? { ref: selectedPlan.reference, plan: selectedPlan } : null);
|
|
568
|
+
const autoSkipRef = useRef(false);
|
|
569
|
+
useEffect(() => {
|
|
570
|
+
if (autoSkipRef.current) return;
|
|
571
|
+
const selectable = plans.filter((p) => p.requiresPayment !== false);
|
|
572
|
+
if (plans.length === 1 && selectable.length === 1) {
|
|
573
|
+
autoSkipRef.current = true;
|
|
574
|
+
onContinue(selectable[0].reference, selectable[0]);
|
|
575
|
+
}
|
|
576
|
+
}, [plans, onContinue]);
|
|
577
|
+
const handleSelect = useCallback((ref, plan) => {
|
|
578
|
+
setUserSelected({ ref, plan });
|
|
579
|
+
}, []);
|
|
580
|
+
const continueLabel = useMemo(() => copy.planSelector.continueButton, [copy]);
|
|
581
|
+
return /* @__PURE__ */ jsxs5(Fragment5, { children: [
|
|
1244
582
|
/* @__PURE__ */ jsx5(
|
|
1245
|
-
|
|
583
|
+
PlanSelector2,
|
|
1246
584
|
{
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
585
|
+
productRef,
|
|
586
|
+
initialPlanRef,
|
|
587
|
+
filter,
|
|
588
|
+
sortBy,
|
|
589
|
+
popularPlanRef,
|
|
590
|
+
autoSelectFirstPaid: true,
|
|
591
|
+
onSelect: handleSelect
|
|
1254
592
|
}
|
|
1255
593
|
),
|
|
1256
594
|
/* @__PURE__ */ jsx5(
|
|
1257
595
|
"button",
|
|
1258
596
|
{
|
|
1259
|
-
type: "
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
"
|
|
1263
|
-
|
|
1264
|
-
|
|
597
|
+
type: "button",
|
|
598
|
+
className: "solvapay-continue",
|
|
599
|
+
"data-solvapay-checkout-continue": "",
|
|
600
|
+
"data-state": selected ? "idle" : "disabled",
|
|
601
|
+
disabled: !selected,
|
|
602
|
+
onClick: () => {
|
|
603
|
+
if (selected) onContinue(selected.ref, selected.plan);
|
|
604
|
+
},
|
|
605
|
+
children: continueLabel
|
|
1265
606
|
}
|
|
1266
607
|
)
|
|
1267
|
-
] })
|
|
608
|
+
] });
|
|
1268
609
|
};
|
|
1269
610
|
|
|
1270
|
-
// src/components/
|
|
1271
|
-
import
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
611
|
+
// src/components/AmountPicker.tsx
|
|
612
|
+
import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
613
|
+
var AmountPicker2 = ({
|
|
614
|
+
currency,
|
|
615
|
+
minAmount,
|
|
616
|
+
maxAmount,
|
|
617
|
+
showCreditEstimate = true,
|
|
618
|
+
onChange,
|
|
1276
619
|
className
|
|
1277
620
|
}) => {
|
|
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;
|
|
621
|
+
const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
|
|
1294
622
|
return /* @__PURE__ */ jsx6(
|
|
1295
|
-
|
|
623
|
+
AmountPicker.Root,
|
|
1296
624
|
{
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
"aria-busy": loading,
|
|
1304
|
-
"aria-label": `Current product: ${planToDisplay}`,
|
|
1305
|
-
children: planToDisplay
|
|
625
|
+
currency,
|
|
626
|
+
minAmount,
|
|
627
|
+
maxAmount,
|
|
628
|
+
onChange,
|
|
629
|
+
className: rootClass,
|
|
630
|
+
children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
|
|
1306
631
|
}
|
|
1307
632
|
);
|
|
1308
633
|
};
|
|
1309
|
-
var
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
634
|
+
var DefaultTree3 = ({ showCreditEstimate }) => {
|
|
635
|
+
const ctx = useAmountPicker();
|
|
636
|
+
const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
|
|
637
|
+
return /* @__PURE__ */ jsxs6(Fragment6, { children: [
|
|
638
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
|
|
639
|
+
/* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
|
|
640
|
+
AmountPicker.Option,
|
|
641
|
+
{
|
|
642
|
+
amount,
|
|
643
|
+
className: "solvapay-amount-picker-pill"
|
|
644
|
+
},
|
|
645
|
+
amount
|
|
646
|
+
)) }),
|
|
647
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
|
|
648
|
+
/* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
|
|
649
|
+
/* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-row", children: [
|
|
650
|
+
/* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
|
|
651
|
+
/* @__PURE__ */ jsx6(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
|
|
652
|
+
] })
|
|
653
|
+
] }),
|
|
654
|
+
showCreditEstimate && ctx.estimatedCredits != null && /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-credit-estimate", children: creditEstimate(ctx.estimatedCredits) }),
|
|
655
|
+
ctx.error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "solvapay-amount-picker-error", children: ctx.error })
|
|
656
|
+
] });
|
|
1322
657
|
};
|
|
1323
658
|
|
|
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 [];
|
|
659
|
+
// src/components/CancelledPlanNotice.tsx
|
|
660
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
661
|
+
var CancelledPlanNotice2 = ({
|
|
662
|
+
onReactivated,
|
|
663
|
+
onError,
|
|
664
|
+
className
|
|
665
|
+
}) => {
|
|
666
|
+
const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
|
|
667
|
+
return /* @__PURE__ */ jsxs7(
|
|
668
|
+
CancelledPlanNotice.Root,
|
|
669
|
+
{
|
|
670
|
+
onReactivated,
|
|
671
|
+
onError,
|
|
672
|
+
className: rootClass,
|
|
673
|
+
children: [
|
|
674
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
|
|
675
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-details", children: [
|
|
676
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
|
|
677
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
|
|
678
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-footer", children: [
|
|
681
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
|
|
682
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
|
|
683
|
+
] }),
|
|
684
|
+
/* @__PURE__ */ jsx7(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
|
|
685
|
+
]
|
|
1383
686
|
}
|
|
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
687
|
);
|
|
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
|
-
}
|
|
688
|
+
};
|
|
1514
689
|
|
|
1515
|
-
// src/components/
|
|
1516
|
-
import { Fragment as
|
|
1517
|
-
var
|
|
690
|
+
// src/components/CreditGate.tsx
|
|
691
|
+
import { Fragment as Fragment7, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
692
|
+
var AllowedContent = ({ children }) => {
|
|
693
|
+
const ctx = useCreditGate();
|
|
694
|
+
if (ctx.state !== "allowed") return null;
|
|
695
|
+
return /* @__PURE__ */ jsx8(Fragment7, { children });
|
|
696
|
+
};
|
|
697
|
+
var DefaultBlockedTree = ({ fallback }) => {
|
|
698
|
+
const ctx = useCreditGate();
|
|
699
|
+
if (ctx.state !== "blocked") return null;
|
|
700
|
+
if (fallback) return /* @__PURE__ */ jsx8(Fragment7, { children: fallback });
|
|
701
|
+
return /* @__PURE__ */ jsxs8(Fragment7, { children: [
|
|
702
|
+
/* @__PURE__ */ jsx8(CreditGate.Heading, { className: "solvapay-credit-gate-heading" }),
|
|
703
|
+
/* @__PURE__ */ jsx8(CreditGate.Subheading, { className: "solvapay-credit-gate-subheading" }),
|
|
704
|
+
/* @__PURE__ */ jsx8(CreditGate.Topup, {})
|
|
705
|
+
] });
|
|
706
|
+
};
|
|
707
|
+
var CreditGate2 = ({
|
|
708
|
+
minCredits,
|
|
1518
709
|
productRef,
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
710
|
+
topupAmount,
|
|
711
|
+
topupCurrency,
|
|
712
|
+
fallback,
|
|
713
|
+
className,
|
|
1523
714
|
children
|
|
1524
715
|
}) => {
|
|
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]
|
|
716
|
+
const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
|
|
717
|
+
return /* @__PURE__ */ jsxs8(
|
|
718
|
+
CreditGate.Root,
|
|
719
|
+
{
|
|
720
|
+
minCredits,
|
|
721
|
+
productRef,
|
|
722
|
+
topupAmount,
|
|
723
|
+
topupCurrency,
|
|
724
|
+
className: rootClass,
|
|
725
|
+
children: [
|
|
726
|
+
/* @__PURE__ */ jsx8(AllowedContent, { children }),
|
|
727
|
+
/* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
|
|
728
|
+
]
|
|
729
|
+
}
|
|
1552
730
|
);
|
|
1553
|
-
return /* @__PURE__ */ jsx8(Fragment4, { children: children({
|
|
1554
|
-
...plansHook,
|
|
1555
|
-
purchases,
|
|
1556
|
-
isPaidPlan,
|
|
1557
|
-
isCurrentPlan
|
|
1558
|
-
}) });
|
|
1559
731
|
};
|
|
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
732
|
|
|
1572
|
-
// src/
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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)})`;
|
|
733
|
+
// src/transport/types.ts
|
|
734
|
+
var UnsupportedTransportMethodError = class extends Error {
|
|
735
|
+
method;
|
|
736
|
+
constructor(method) {
|
|
737
|
+
super(`SolvaPay transport does not implement "${method}"`);
|
|
738
|
+
this.name = "UnsupportedTransportMethodError";
|
|
739
|
+
this.method = method;
|
|
1598
740
|
}
|
|
1599
|
-
|
|
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;
|
|
1791
|
-
}
|
|
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
|
-
);
|
|
1813
|
-
const validate = useCallback11(() => {
|
|
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
|
-
}
|
|
741
|
+
};
|
|
1847
742
|
export {
|
|
743
|
+
ActivationFlow2 as ActivationFlow,
|
|
744
|
+
AmountPicker2 as AmountPicker,
|
|
1848
745
|
BalanceBadge,
|
|
1849
|
-
|
|
746
|
+
CancelPlanButton,
|
|
747
|
+
CancelledPlanNotice2 as CancelledPlanNotice,
|
|
748
|
+
CheckoutLayout,
|
|
749
|
+
CheckoutSummary,
|
|
750
|
+
CopyContext,
|
|
751
|
+
CopyProvider,
|
|
752
|
+
CreditGate2 as CreditGate,
|
|
753
|
+
CurrentPlanCard,
|
|
754
|
+
DEFAULT_ROUTES,
|
|
755
|
+
LaunchCustomerPortalButton,
|
|
756
|
+
MandateText,
|
|
757
|
+
PaymentForm2 as PaymentForm,
|
|
758
|
+
PaymentFormContext,
|
|
759
|
+
PaymentFormProvider,
|
|
1850
760
|
PlanBadge,
|
|
1851
|
-
PlanSelector,
|
|
1852
|
-
PricingSelector,
|
|
761
|
+
PlanSelector2 as PlanSelector,
|
|
1853
762
|
ProductBadge,
|
|
1854
763
|
PurchaseGate,
|
|
1855
764
|
SolvaPayProvider,
|
|
1856
765
|
Spinner,
|
|
1857
766
|
StripePaymentFormWrapper,
|
|
1858
767
|
TopupForm,
|
|
768
|
+
UnsupportedTransportMethodError,
|
|
769
|
+
UpdatePaymentMethodButton,
|
|
770
|
+
confirmPayment,
|
|
771
|
+
createHttpTransport,
|
|
1859
772
|
defaultAuthAdapter,
|
|
773
|
+
deriveVariant,
|
|
774
|
+
enCopy,
|
|
1860
775
|
filterPurchases,
|
|
776
|
+
formatPrice,
|
|
1861
777
|
getActivePurchases,
|
|
1862
778
|
getCancelledPurchasesWithEndDate,
|
|
779
|
+
getMinorUnitsPerMajor,
|
|
1863
780
|
getMostRecentPurchase,
|
|
1864
781
|
getPrimaryPurchase,
|
|
782
|
+
interpolate,
|
|
1865
783
|
isPaidPurchase,
|
|
784
|
+
isPlanPurchase,
|
|
785
|
+
isTopupPurchase,
|
|
786
|
+
mergeCopy,
|
|
787
|
+
resolveCta,
|
|
788
|
+
toMajorUnits,
|
|
1866
789
|
useActivation,
|
|
1867
790
|
useBalance,
|
|
1868
791
|
useCheckout,
|
|
792
|
+
useCopy,
|
|
1869
793
|
useCustomer,
|
|
794
|
+
useLocale,
|
|
795
|
+
useMerchant,
|
|
796
|
+
usePaymentForm,
|
|
797
|
+
usePaymentMethod,
|
|
798
|
+
usePaywallResolver,
|
|
799
|
+
usePlan,
|
|
1870
800
|
usePlans,
|
|
801
|
+
useProduct,
|
|
1871
802
|
usePurchase,
|
|
1872
803
|
usePurchaseActions,
|
|
1873
804
|
usePurchaseStatus,
|
|
1874
805
|
useSolvaPay,
|
|
1875
806
|
useTopup,
|
|
1876
|
-
useTopupAmountSelector
|
|
807
|
+
useTopupAmountSelector,
|
|
808
|
+
useTransport,
|
|
809
|
+
useUsage
|
|
1877
810
|
};
|