@solvapay/react 1.0.8-preview.9 → 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/README.md +194 -25
- 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-MOP3ZBGC.js → chunk-37R5NZGF.js} +1116 -1041
- 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 +1422 -366
- package/dist/index.d.cts +251 -64
- package/dist/index.d.ts +251 -64
- package/dist/index.js +54 -26
- 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 +1177 -115
- package/dist/primitives/index.d.cts +208 -20
- package/dist/primitives/index.d.ts +208 -20
- package/dist/primitives/index.js +66 -21
- package/dist/styles.css +246 -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 +20 -4
- package/dist/CancelPlanButton-CieT9swn.d.cts +0 -1102
- package/dist/CancelPlanButton-f56UlQN-.d.ts +0 -1102
|
@@ -31,6 +31,240 @@ function getPrimaryPurchase(purchases) {
|
|
|
31
31
|
function isPaidPurchase(purchase) {
|
|
32
32
|
return (purchase.amount ?? 0) > 0;
|
|
33
33
|
}
|
|
34
|
+
function isPlanPurchase(purchase) {
|
|
35
|
+
return !!purchase.planSnapshot && purchase.metadata?.purpose !== "credit_topup";
|
|
36
|
+
}
|
|
37
|
+
function isTopupPurchase(purchase) {
|
|
38
|
+
return !isPlanPurchase(purchase);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/headers.ts
|
|
42
|
+
var CUSTOMER_REF_KEY = "solvapay_customerRef";
|
|
43
|
+
var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
|
|
44
|
+
var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
|
|
45
|
+
function getAuthAdapter(config) {
|
|
46
|
+
if (config?.auth?.adapter) {
|
|
47
|
+
return config.auth.adapter;
|
|
48
|
+
}
|
|
49
|
+
return defaultAuthAdapter;
|
|
50
|
+
}
|
|
51
|
+
function getCachedCustomerRef(userId) {
|
|
52
|
+
if (typeof window === "undefined") return null;
|
|
53
|
+
const cached = localStorage.getItem(CUSTOMER_REF_KEY);
|
|
54
|
+
const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
|
|
55
|
+
const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
|
|
56
|
+
if (!cached || !expiry) return null;
|
|
57
|
+
if (Date.now() > parseInt(expiry)) {
|
|
58
|
+
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
59
|
+
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
60
|
+
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (userId !== void 0 && userId !== null) {
|
|
64
|
+
if (cachedUserId !== userId) {
|
|
65
|
+
clearCachedCustomerRef();
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
71
|
+
var CACHE_DURATION = 24 * 60 * 60 * 1e3;
|
|
72
|
+
function setCachedCustomerRef(customerRef, userId) {
|
|
73
|
+
if (typeof window === "undefined") return;
|
|
74
|
+
if (userId === void 0 || userId === null) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
|
|
78
|
+
localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
|
|
79
|
+
localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
|
|
80
|
+
}
|
|
81
|
+
function clearCachedCustomerRef() {
|
|
82
|
+
if (typeof window === "undefined") return;
|
|
83
|
+
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
84
|
+
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
85
|
+
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
86
|
+
}
|
|
87
|
+
async function buildRequestHeaders(config) {
|
|
88
|
+
const adapter = getAuthAdapter(config);
|
|
89
|
+
const token = await adapter.getToken();
|
|
90
|
+
const userId = await adapter.getUserId();
|
|
91
|
+
const cachedRef = getCachedCustomerRef(userId);
|
|
92
|
+
const headers = {
|
|
93
|
+
"Content-Type": "application/json"
|
|
94
|
+
};
|
|
95
|
+
if (token) {
|
|
96
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
97
|
+
}
|
|
98
|
+
if (cachedRef) {
|
|
99
|
+
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
100
|
+
}
|
|
101
|
+
if (config?.headers) {
|
|
102
|
+
const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
|
|
103
|
+
Object.assign(headers, custom);
|
|
104
|
+
}
|
|
105
|
+
return { headers, userId };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/transport/http.ts
|
|
109
|
+
async function request(config, url, opts) {
|
|
110
|
+
const { headers } = await buildRequestHeaders(config);
|
|
111
|
+
const fetchFn = config?.fetch || fetch;
|
|
112
|
+
const init = { method: opts.method, headers };
|
|
113
|
+
if (opts.body !== void 0) {
|
|
114
|
+
init.body = JSON.stringify(opts.body);
|
|
115
|
+
}
|
|
116
|
+
const res = await fetchFn(url, init);
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
let serverMessage;
|
|
119
|
+
try {
|
|
120
|
+
const data = await res.clone().json();
|
|
121
|
+
serverMessage = data?.error;
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
|
|
125
|
+
config?.onError?.(error, opts.onErrorContext);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
return await res.json();
|
|
129
|
+
}
|
|
130
|
+
var DEFAULT_ROUTES = {
|
|
131
|
+
checkPurchase: "/api/check-purchase",
|
|
132
|
+
createPayment: "/api/create-payment-intent",
|
|
133
|
+
processPayment: "/api/process-payment",
|
|
134
|
+
createTopupPayment: "/api/create-topup-payment-intent",
|
|
135
|
+
customerBalance: "/api/customer-balance",
|
|
136
|
+
cancelRenewal: "/api/cancel-renewal",
|
|
137
|
+
reactivateRenewal: "/api/reactivate-renewal",
|
|
138
|
+
activatePlan: "/api/activate-plan",
|
|
139
|
+
createCheckoutSession: "/api/create-checkout-session",
|
|
140
|
+
createCustomerSession: "/api/create-customer-session",
|
|
141
|
+
getMerchant: "/api/merchant",
|
|
142
|
+
getProduct: "/api/get-product",
|
|
143
|
+
listPlans: "/api/list-plans",
|
|
144
|
+
getPaymentMethod: "/api/payment-method",
|
|
145
|
+
getUsage: "/api/usage"
|
|
146
|
+
};
|
|
147
|
+
function routeFor(config, key) {
|
|
148
|
+
const configured = config?.api?.[key];
|
|
149
|
+
return configured || DEFAULT_ROUTES[key];
|
|
150
|
+
}
|
|
151
|
+
function createHttpTransport(config) {
|
|
152
|
+
return {
|
|
153
|
+
checkPurchase: () => request(config, routeFor(config, "checkPurchase"), {
|
|
154
|
+
method: "GET",
|
|
155
|
+
onErrorContext: "checkPurchase",
|
|
156
|
+
errorPrefix: "Failed to check purchase"
|
|
157
|
+
}),
|
|
158
|
+
createPayment: (params) => {
|
|
159
|
+
const body = {};
|
|
160
|
+
if (params.planRef) body.planRef = params.planRef;
|
|
161
|
+
if (params.productRef) body.productRef = params.productRef;
|
|
162
|
+
if (params.customer && (params.customer.name || params.customer.email)) {
|
|
163
|
+
body.customer = params.customer;
|
|
164
|
+
}
|
|
165
|
+
return request(config, routeFor(config, "createPayment"), {
|
|
166
|
+
method: "POST",
|
|
167
|
+
body,
|
|
168
|
+
onErrorContext: "createPayment",
|
|
169
|
+
errorPrefix: "Failed to create payment"
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
processPayment: (params) => request(config, routeFor(config, "processPayment"), {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: params,
|
|
175
|
+
onErrorContext: "processPayment",
|
|
176
|
+
errorPrefix: "Failed to process payment"
|
|
177
|
+
}),
|
|
178
|
+
createTopupPayment: (params) => request(config, routeFor(config, "createTopupPayment"), {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: { amount: params.amount, currency: params.currency },
|
|
181
|
+
onErrorContext: "createTopupPayment",
|
|
182
|
+
errorPrefix: "Failed to create topup payment"
|
|
183
|
+
}),
|
|
184
|
+
getBalance: () => request(config, routeFor(config, "customerBalance"), {
|
|
185
|
+
method: "GET",
|
|
186
|
+
onErrorContext: "getBalance",
|
|
187
|
+
errorPrefix: "Failed to fetch balance"
|
|
188
|
+
}),
|
|
189
|
+
cancelRenewal: (params) => request(config, routeFor(config, "cancelRenewal"), {
|
|
190
|
+
method: "POST",
|
|
191
|
+
body: params,
|
|
192
|
+
onErrorContext: "cancelRenewal",
|
|
193
|
+
errorPrefix: "Failed to cancel renewal"
|
|
194
|
+
}),
|
|
195
|
+
reactivateRenewal: (params) => request(config, routeFor(config, "reactivateRenewal"), {
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: params,
|
|
198
|
+
onErrorContext: "reactivateRenewal",
|
|
199
|
+
errorPrefix: "Failed to reactivate renewal"
|
|
200
|
+
}),
|
|
201
|
+
activatePlan: (params) => request(config, routeFor(config, "activatePlan"), {
|
|
202
|
+
method: "POST",
|
|
203
|
+
body: params,
|
|
204
|
+
onErrorContext: "activatePlan",
|
|
205
|
+
errorPrefix: "Failed to activate plan"
|
|
206
|
+
}),
|
|
207
|
+
createCheckoutSession: (params) => {
|
|
208
|
+
const body = {};
|
|
209
|
+
if (params?.planRef) body.planRef = params.planRef;
|
|
210
|
+
if (params?.productRef) body.productRef = params.productRef;
|
|
211
|
+
if (params?.returnUrl) body.returnUrl = params.returnUrl;
|
|
212
|
+
return request(
|
|
213
|
+
config,
|
|
214
|
+
routeFor(config, "createCheckoutSession"),
|
|
215
|
+
{
|
|
216
|
+
method: "POST",
|
|
217
|
+
body,
|
|
218
|
+
onErrorContext: "createCheckoutSession",
|
|
219
|
+
errorPrefix: "Failed to create checkout session"
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
createCustomerSession: () => request(
|
|
224
|
+
config,
|
|
225
|
+
routeFor(config, "createCustomerSession"),
|
|
226
|
+
{
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: {},
|
|
229
|
+
onErrorContext: "createCustomerSession",
|
|
230
|
+
errorPrefix: "Failed to create customer session"
|
|
231
|
+
}
|
|
232
|
+
),
|
|
233
|
+
getMerchant: () => request(config, routeFor(config, "getMerchant"), {
|
|
234
|
+
method: "GET",
|
|
235
|
+
onErrorContext: "getMerchant",
|
|
236
|
+
errorPrefix: "Failed to fetch merchant"
|
|
237
|
+
}),
|
|
238
|
+
getProduct: (productRef) => {
|
|
239
|
+
const base = routeFor(config, "getProduct");
|
|
240
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
241
|
+
return request(config, url, {
|
|
242
|
+
method: "GET",
|
|
243
|
+
onErrorContext: "getProduct",
|
|
244
|
+
errorPrefix: "Failed to fetch product"
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
listPlans: (productRef) => {
|
|
248
|
+
const base = routeFor(config, "listPlans");
|
|
249
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
250
|
+
return request(config, url, {
|
|
251
|
+
method: "GET",
|
|
252
|
+
onErrorContext: "listPlans",
|
|
253
|
+
errorPrefix: "Failed to list plans"
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
getPaymentMethod: () => request(config, routeFor(config, "getPaymentMethod"), {
|
|
257
|
+
method: "GET",
|
|
258
|
+
onErrorContext: "getPaymentMethod",
|
|
259
|
+
errorPrefix: "Failed to load payment method"
|
|
260
|
+
}),
|
|
261
|
+
getUsage: () => request(config, routeFor(config, "getUsage"), {
|
|
262
|
+
method: "GET",
|
|
263
|
+
onErrorContext: "getUsage",
|
|
264
|
+
errorPrefix: "Failed to load usage"
|
|
265
|
+
})
|
|
266
|
+
};
|
|
267
|
+
}
|
|
34
268
|
|
|
35
269
|
// src/i18n/en.ts
|
|
36
270
|
function intervalPhrase(ctx) {
|
|
@@ -178,6 +412,26 @@ var enCopy = {
|
|
|
178
412
|
lowBalanceSubheading: "Top up to continue using {product}",
|
|
179
413
|
topUpCta: "Top up now"
|
|
180
414
|
},
|
|
415
|
+
currentPlan: {
|
|
416
|
+
heading: "Your plan",
|
|
417
|
+
nextBilling: "Next billing: {date}",
|
|
418
|
+
expiresOn: "Expires {date}",
|
|
419
|
+
validIndefinitely: "Valid indefinitely",
|
|
420
|
+
paymentMethod: "{brand} \u2022\u2022\u2022\u2022 {last4}",
|
|
421
|
+
paymentMethodExpires: "expires {month}/{year}",
|
|
422
|
+
noPaymentMethod: "No payment method on file",
|
|
423
|
+
updatePaymentButton: "Update card",
|
|
424
|
+
cycleUnit: {
|
|
425
|
+
weekly: "week",
|
|
426
|
+
monthly: "month",
|
|
427
|
+
quarterly: "3 months",
|
|
428
|
+
yearly: "year"
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
customerPortal: {
|
|
432
|
+
launchButton: "Manage billing",
|
|
433
|
+
loadingLabel: "Loading portal\u2026"
|
|
434
|
+
},
|
|
181
435
|
errors: {
|
|
182
436
|
paymentInitFailed: "Payment initialization failed",
|
|
183
437
|
topupInitFailed: "Top-up initialization failed",
|
|
@@ -191,7 +445,40 @@ var enCopy = {
|
|
|
191
445
|
paymentProcessingFailed: "Payment processing failed. Please try again or contact support.",
|
|
192
446
|
paymentRequires3ds: "Payment requires additional authentication. Please complete the verification.",
|
|
193
447
|
paymentProcessingTimeout: "Payment processing timed out \u2014 webhooks may not be configured",
|
|
194
|
-
|
|
448
|
+
paymentConfirmationDelayed: "Payment succeeded but confirmation is taking longer than usual. Refresh in a moment to see your purchase.",
|
|
449
|
+
paymentStatusPrefix: "Payment status: {status}",
|
|
450
|
+
paywallInvalidContent: "Paywall content is missing or malformed.",
|
|
451
|
+
usageLoadFailed: "Failed to load usage"
|
|
452
|
+
},
|
|
453
|
+
paywall: {
|
|
454
|
+
header: "Unlock access",
|
|
455
|
+
paymentRequiredHeading: "Upgrade to continue",
|
|
456
|
+
activationRequiredHeading: "Add credits to continue",
|
|
457
|
+
resolvedHeading: "You can continue",
|
|
458
|
+
productContext: "For {product}",
|
|
459
|
+
balanceLine: "You have {available} credits, need {required}.",
|
|
460
|
+
paymentRequiredMessage: "You've used all your included calls{forProduct}. Choose a plan below to keep going.",
|
|
461
|
+
paymentRequiredMessageRemaining: "Only {remaining} call{pluralSuffix} left{forProduct}. Choose a plan below to keep going.",
|
|
462
|
+
paymentRequiredProductSuffix: " for {product}",
|
|
463
|
+
retryButton: "Continue",
|
|
464
|
+
hostedCheckoutButton: "Open checkout",
|
|
465
|
+
hostedCheckoutLoading: "Loading checkout\u2026"
|
|
466
|
+
},
|
|
467
|
+
usage: {
|
|
468
|
+
header: "Usage",
|
|
469
|
+
percentUsedLabel: "{percent}% used",
|
|
470
|
+
usedLabel: "{used} / {total} {unit}",
|
|
471
|
+
remainingLabel: "{remaining} {unit} remaining",
|
|
472
|
+
unlimitedLabel: "Unlimited",
|
|
473
|
+
resetsInLabel: "Resets in {days} days",
|
|
474
|
+
resetsOnLabel: "Resets {date}",
|
|
475
|
+
loadingLabel: "Loading usage\u2026",
|
|
476
|
+
emptyLabel: "No usage-based plan is active.",
|
|
477
|
+
approachingLimit: "You're approaching your usage limit.",
|
|
478
|
+
atLimit: "You've reached your usage limit.",
|
|
479
|
+
topUpCta: "Add credits",
|
|
480
|
+
upgradeCta: "Upgrade plan",
|
|
481
|
+
refreshCta: "Refresh"
|
|
195
482
|
}
|
|
196
483
|
};
|
|
197
484
|
|
|
@@ -236,227 +523,68 @@ function useCopyContext() {
|
|
|
236
523
|
|
|
237
524
|
// src/SolvaPayProvider.tsx
|
|
238
525
|
import { createContext as createContext2, useState, useEffect, useCallback, useMemo as useMemo2, useRef } from "react";
|
|
239
|
-
|
|
240
|
-
// src/utils/headers.ts
|
|
241
|
-
var CUSTOMER_REF_KEY = "solvapay_customerRef";
|
|
242
|
-
var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
|
|
243
|
-
var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
|
|
244
|
-
function getAuthAdapter(config) {
|
|
245
|
-
if (config?.auth?.adapter) {
|
|
246
|
-
return config.auth.adapter;
|
|
247
|
-
}
|
|
248
|
-
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
249
|
-
return {
|
|
250
|
-
async getToken() {
|
|
251
|
-
return config?.auth?.getToken?.() || null;
|
|
252
|
-
},
|
|
253
|
-
async getUserId() {
|
|
254
|
-
return config?.auth?.getUserId?.() || null;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
return defaultAuthAdapter;
|
|
259
|
-
}
|
|
260
|
-
function getCachedCustomerRef(userId) {
|
|
261
|
-
if (typeof window === "undefined") return null;
|
|
262
|
-
const cached = localStorage.getItem(CUSTOMER_REF_KEY);
|
|
263
|
-
const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
|
|
264
|
-
const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
|
|
265
|
-
if (!cached || !expiry) return null;
|
|
266
|
-
if (Date.now() > parseInt(expiry)) {
|
|
267
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
268
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
269
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
if (userId !== void 0 && userId !== null) {
|
|
273
|
-
if (cachedUserId !== userId) {
|
|
274
|
-
clearCachedCustomerRef();
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return cached;
|
|
279
|
-
}
|
|
280
|
-
var CACHE_DURATION = 24 * 60 * 60 * 1e3;
|
|
281
|
-
function setCachedCustomerRef(customerRef, userId) {
|
|
282
|
-
if (typeof window === "undefined") return;
|
|
283
|
-
if (userId === void 0 || userId === null) {
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
|
|
287
|
-
localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
|
|
288
|
-
localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
|
|
289
|
-
}
|
|
290
|
-
function clearCachedCustomerRef() {
|
|
291
|
-
if (typeof window === "undefined") return;
|
|
292
|
-
localStorage.removeItem(CUSTOMER_REF_KEY);
|
|
293
|
-
localStorage.removeItem(CUSTOMER_REF_EXPIRY);
|
|
294
|
-
localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
|
|
295
|
-
}
|
|
296
|
-
async function buildRequestHeaders(config) {
|
|
297
|
-
const adapter = getAuthAdapter(config);
|
|
298
|
-
const token = await adapter.getToken();
|
|
299
|
-
const userId = await adapter.getUserId();
|
|
300
|
-
const cachedRef = getCachedCustomerRef(userId);
|
|
301
|
-
const headers = {
|
|
302
|
-
"Content-Type": "application/json"
|
|
303
|
-
};
|
|
304
|
-
if (token) {
|
|
305
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
306
|
-
}
|
|
307
|
-
if (cachedRef) {
|
|
308
|
-
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
309
|
-
}
|
|
310
|
-
if (config?.headers) {
|
|
311
|
-
const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
|
|
312
|
-
Object.assign(headers, custom);
|
|
313
|
-
}
|
|
314
|
-
return { headers, userId };
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/SolvaPayProvider.tsx
|
|
318
526
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
319
527
|
var SolvaPayContext = createContext2(null);
|
|
320
|
-
|
|
321
|
-
config
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
528
|
+
function resolveTransport(config) {
|
|
529
|
+
return config?.transport ?? createHttpTransport(config);
|
|
530
|
+
}
|
|
531
|
+
var SolvaPayProvider = ({ config, children }) => {
|
|
532
|
+
const initial = config?.initial;
|
|
533
|
+
const [purchaseData, setPurchaseData] = useState(() => {
|
|
534
|
+
if (!initial) return { purchases: [] };
|
|
535
|
+
return {
|
|
536
|
+
// The server-side `PurchaseCheckResult.purchases` shape has looser
|
|
537
|
+
// optional fields than `PurchaseInfo`, but the runtime data is
|
|
538
|
+
// identical — the same rows flow through `transport.checkPurchase()`
|
|
539
|
+
// today. Cast once here to unify the two entry points, and run
|
|
540
|
+
// the same `filterPurchases` the HTTP path applies in
|
|
541
|
+
// `fetchPurchase` so the bootstrap-hydrated data never contains
|
|
542
|
+
// cancelled / expired / suspended rows the active-only derivations
|
|
543
|
+
// (`activePurchase`, `hasPaidPurchase`, …) wouldn't expect.
|
|
544
|
+
purchases: filterPurchases(
|
|
545
|
+
initial.purchase?.purchases ?? []
|
|
546
|
+
),
|
|
547
|
+
customerRef: initial.customerRef ?? initial.purchase?.customerRef,
|
|
548
|
+
email: initial.purchase?.email,
|
|
549
|
+
name: initial.purchase?.name
|
|
550
|
+
};
|
|
330
551
|
});
|
|
331
552
|
const [loading, setLoading] = useState(false);
|
|
332
553
|
const [isRefetching, setIsRefetching] = useState(false);
|
|
333
554
|
const [purchaseError, setPurchaseError] = useState(null);
|
|
334
|
-
const [internalCustomerRef, setInternalCustomerRef] = useState(
|
|
555
|
+
const [internalCustomerRef, setInternalCustomerRef] = useState(
|
|
556
|
+
initial?.customerRef ?? void 0
|
|
557
|
+
);
|
|
335
558
|
const [userId, setUserId] = useState(null);
|
|
336
|
-
const [isAuthenticated, setIsAuthenticated] = useState(
|
|
337
|
-
const [creditsValue, setCreditsValue] = useState(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const [
|
|
559
|
+
const [isAuthenticated, setIsAuthenticated] = useState(!!initial?.customerRef);
|
|
560
|
+
const [creditsValue, setCreditsValue] = useState(
|
|
561
|
+
initial?.balance?.credits ?? null
|
|
562
|
+
);
|
|
563
|
+
const [displayCurrencyValue, setDisplayCurrencyValue] = useState(
|
|
564
|
+
initial?.balance?.displayCurrency ?? null
|
|
565
|
+
);
|
|
566
|
+
const [creditsPerMinorUnitValue, setCreditsPerMinorUnitValue] = useState(
|
|
567
|
+
initial?.balance?.creditsPerMinorUnit ?? null
|
|
568
|
+
);
|
|
569
|
+
const [displayExchangeRateValue, setDisplayExchangeRateValue] = useState(
|
|
570
|
+
initial?.balance?.displayExchangeRate ?? null
|
|
571
|
+
);
|
|
341
572
|
const [balanceLoading, setBalanceLoading] = useState(false);
|
|
342
573
|
const balanceInFlightRef = useRef(false);
|
|
343
|
-
const balanceLoadedRef = useRef(
|
|
574
|
+
const balanceLoadedRef = useRef(!!initial?.balance);
|
|
344
575
|
const optimisticUntilRef = useRef(0);
|
|
345
576
|
const optimisticTimerRef = useRef(null);
|
|
346
577
|
const fetchBalanceRef = useRef(null);
|
|
347
578
|
const inFlightRef = useRef(null);
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
const processPaymentRef = useRef(null);
|
|
351
|
-
const createTopupPaymentRef = useRef(null);
|
|
352
|
-
const configRef = useRef(config);
|
|
353
|
-
const buildDefaultCheckPurchaseRef = useRef(
|
|
354
|
-
null
|
|
579
|
+
const loadedCacheKeysRef = useRef(
|
|
580
|
+
new Set(initial?.customerRef ? [initial.customerRef] : [])
|
|
355
581
|
);
|
|
582
|
+
const configRef = useRef(config);
|
|
583
|
+
const transportRef = useRef(resolveTransport(config));
|
|
356
584
|
useEffect(() => {
|
|
357
585
|
configRef.current = config;
|
|
586
|
+
transportRef.current = resolveTransport(config);
|
|
358
587
|
}, [config]);
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
checkPurchaseRef.current = customCheckPurchase || null;
|
|
361
|
-
}, [customCheckPurchase]);
|
|
362
|
-
useEffect(() => {
|
|
363
|
-
createPaymentRef.current = customCreatePayment || null;
|
|
364
|
-
}, [customCreatePayment]);
|
|
365
|
-
useEffect(() => {
|
|
366
|
-
processPaymentRef.current = customProcessPayment || null;
|
|
367
|
-
}, [customProcessPayment]);
|
|
368
|
-
useEffect(() => {
|
|
369
|
-
createTopupPaymentRef.current = customCreateTopupPayment || null;
|
|
370
|
-
}, [customCreateTopupPayment]);
|
|
371
|
-
const buildDefaultCheckPurchase = useCallback(async () => {
|
|
372
|
-
const currentConfig = configRef.current;
|
|
373
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
374
|
-
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
375
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
376
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
377
|
-
if (!res.ok) {
|
|
378
|
-
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
379
|
-
currentConfig?.onError?.(error, "checkPurchase");
|
|
380
|
-
throw error;
|
|
381
|
-
}
|
|
382
|
-
return res.json();
|
|
383
|
-
}, []);
|
|
384
|
-
useEffect(() => {
|
|
385
|
-
buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
|
|
386
|
-
}, [buildDefaultCheckPurchase]);
|
|
387
|
-
const buildDefaultCreatePayment = useCallback(
|
|
388
|
-
async (params) => {
|
|
389
|
-
const currentConfig = configRef.current;
|
|
390
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
391
|
-
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
392
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
393
|
-
const body = {};
|
|
394
|
-
if (params.planRef) {
|
|
395
|
-
body.planRef = params.planRef;
|
|
396
|
-
}
|
|
397
|
-
if (params.productRef) {
|
|
398
|
-
body.productRef = params.productRef;
|
|
399
|
-
}
|
|
400
|
-
if (params.customer && (params.customer.name || params.customer.email)) {
|
|
401
|
-
body.customer = params.customer;
|
|
402
|
-
}
|
|
403
|
-
const res = await fetchFn(route, {
|
|
404
|
-
method: "POST",
|
|
405
|
-
headers,
|
|
406
|
-
body: JSON.stringify(body)
|
|
407
|
-
});
|
|
408
|
-
if (!res.ok) {
|
|
409
|
-
const error = new Error(`Failed to create payment: ${res.statusText}`);
|
|
410
|
-
currentConfig?.onError?.(error, "createPayment");
|
|
411
|
-
throw error;
|
|
412
|
-
}
|
|
413
|
-
return res.json();
|
|
414
|
-
},
|
|
415
|
-
[]
|
|
416
|
-
);
|
|
417
|
-
const buildDefaultProcessPayment = useCallback(
|
|
418
|
-
async (params) => {
|
|
419
|
-
const currentConfig = configRef.current;
|
|
420
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
421
|
-
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
422
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
423
|
-
const res = await fetchFn(route, {
|
|
424
|
-
method: "POST",
|
|
425
|
-
headers,
|
|
426
|
-
body: JSON.stringify(params)
|
|
427
|
-
});
|
|
428
|
-
if (!res.ok) {
|
|
429
|
-
const error = new Error(`Failed to process payment: ${res.statusText}`);
|
|
430
|
-
currentConfig?.onError?.(error, "processPayment");
|
|
431
|
-
throw error;
|
|
432
|
-
}
|
|
433
|
-
return res.json();
|
|
434
|
-
},
|
|
435
|
-
[]
|
|
436
|
-
);
|
|
437
|
-
const buildDefaultCreateTopupPayment = useCallback(
|
|
438
|
-
async (params) => {
|
|
439
|
-
const currentConfig = configRef.current;
|
|
440
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
441
|
-
const route = currentConfig?.api?.createTopupPayment || "/api/create-topup-payment-intent";
|
|
442
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
443
|
-
const res = await fetchFn(route, {
|
|
444
|
-
method: "POST",
|
|
445
|
-
headers,
|
|
446
|
-
body: JSON.stringify({
|
|
447
|
-
amount: params.amount,
|
|
448
|
-
currency: params.currency
|
|
449
|
-
})
|
|
450
|
-
});
|
|
451
|
-
if (!res.ok) {
|
|
452
|
-
const error = new Error(`Failed to create topup payment: ${res.statusText}`);
|
|
453
|
-
currentConfig?.onError?.(error, "createTopupPayment");
|
|
454
|
-
throw error;
|
|
455
|
-
}
|
|
456
|
-
return res.json();
|
|
457
|
-
},
|
|
458
|
-
[]
|
|
459
|
-
);
|
|
460
588
|
const fetchBalanceImpl = useCallback(async () => {
|
|
461
589
|
if (optimisticUntilRef.current > Date.now()) return;
|
|
462
590
|
if (!isAuthenticated && !internalCustomerRef) {
|
|
@@ -474,16 +602,12 @@ var SolvaPayProvider = ({
|
|
|
474
602
|
setBalanceLoading(true);
|
|
475
603
|
}
|
|
476
604
|
try {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
481
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
482
|
-
if (!res.ok) {
|
|
483
|
-
console.error("[SolvaPayProvider] Failed to fetch balance:", res.statusText);
|
|
605
|
+
if (!transportRef.current.getBalance) {
|
|
606
|
+
setBalanceLoading(false);
|
|
607
|
+
balanceInFlightRef.current = false;
|
|
484
608
|
return;
|
|
485
609
|
}
|
|
486
|
-
const data = await
|
|
610
|
+
const data = await transportRef.current.getBalance();
|
|
487
611
|
setCreditsValue(data.credits ?? null);
|
|
488
612
|
setDisplayCurrencyValue(data.displayCurrency ?? null);
|
|
489
613
|
setCreditsPerMinorUnitValue(data.creditsPerMinorUnit ?? null);
|
|
@@ -514,54 +638,35 @@ var SolvaPayProvider = ({
|
|
|
514
638
|
fetchBalanceRef.current?.();
|
|
515
639
|
}, OPTIMISTIC_GRACE_MS);
|
|
516
640
|
}, []);
|
|
517
|
-
const _checkPurchase = useCallback(async () => {
|
|
518
|
-
if (checkPurchaseRef.current) {
|
|
519
|
-
return checkPurchaseRef.current();
|
|
520
|
-
}
|
|
521
|
-
if (buildDefaultCheckPurchaseRef.current) {
|
|
522
|
-
return buildDefaultCheckPurchaseRef.current();
|
|
523
|
-
}
|
|
524
|
-
return buildDefaultCheckPurchase();
|
|
525
|
-
}, [buildDefaultCheckPurchase]);
|
|
526
641
|
const createPayment = useCallback(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return createPaymentRef.current(params);
|
|
530
|
-
}
|
|
531
|
-
return buildDefaultCreatePayment(params);
|
|
532
|
-
},
|
|
533
|
-
[buildDefaultCreatePayment]
|
|
642
|
+
(params) => transportRef.current.createPayment(params),
|
|
643
|
+
[]
|
|
534
644
|
);
|
|
535
645
|
const processPayment = useCallback(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return processPaymentRef.current(params);
|
|
539
|
-
}
|
|
540
|
-
return buildDefaultProcessPayment(params);
|
|
541
|
-
},
|
|
542
|
-
[buildDefaultProcessPayment]
|
|
646
|
+
(params) => transportRef.current.processPayment(params),
|
|
647
|
+
[]
|
|
543
648
|
);
|
|
544
649
|
const createTopupPayment = useCallback(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
return createTopupPaymentRef.current(params);
|
|
548
|
-
}
|
|
549
|
-
return buildDefaultCreateTopupPayment(params);
|
|
550
|
-
},
|
|
551
|
-
[buildDefaultCreateTopupPayment]
|
|
650
|
+
(params) => transportRef.current.createTopupPayment(params),
|
|
651
|
+
[]
|
|
552
652
|
);
|
|
553
653
|
useEffect(() => {
|
|
654
|
+
if (configRef.current?.initial) {
|
|
655
|
+
setIsAuthenticated(configRef.current.initial.customerRef !== null);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
554
658
|
const detectAuth = async () => {
|
|
555
659
|
const currentConfig = configRef.current;
|
|
556
|
-
const
|
|
557
|
-
const token = await
|
|
558
|
-
const detectedUserId = await
|
|
660
|
+
const adapter2 = getAuthAdapter(currentConfig);
|
|
661
|
+
const token = await adapter2.getToken();
|
|
662
|
+
const detectedUserId = await adapter2.getUserId();
|
|
559
663
|
const prevUserId = userId;
|
|
560
664
|
setIsAuthenticated(!!token);
|
|
561
665
|
setUserId(detectedUserId);
|
|
562
666
|
if (prevUserId !== null && detectedUserId !== prevUserId) {
|
|
563
667
|
clearCachedCustomerRef();
|
|
564
668
|
setInternalCustomerRef(void 0);
|
|
669
|
+
loadedCacheKeysRef.current.clear();
|
|
565
670
|
return;
|
|
566
671
|
}
|
|
567
672
|
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
@@ -570,11 +675,19 @@ var SolvaPayProvider = ({
|
|
|
570
675
|
} else if (!token) {
|
|
571
676
|
clearCachedCustomerRef();
|
|
572
677
|
setInternalCustomerRef(void 0);
|
|
678
|
+
loadedCacheKeysRef.current.clear();
|
|
573
679
|
} else if (token && !cachedRef) {
|
|
574
680
|
setInternalCustomerRef(void 0);
|
|
575
681
|
}
|
|
576
682
|
};
|
|
577
683
|
detectAuth();
|
|
684
|
+
const adapter = getAuthAdapter(configRef.current);
|
|
685
|
+
if (typeof adapter.subscribe === "function") {
|
|
686
|
+
const unsubscribe = adapter.subscribe(() => {
|
|
687
|
+
detectAuth();
|
|
688
|
+
});
|
|
689
|
+
return unsubscribe;
|
|
690
|
+
}
|
|
578
691
|
const interval = setInterval(detectAuth, 3e4);
|
|
579
692
|
return () => clearInterval(interval);
|
|
580
693
|
}, [userId]);
|
|
@@ -593,20 +706,24 @@ var SolvaPayProvider = ({
|
|
|
593
706
|
return;
|
|
594
707
|
}
|
|
595
708
|
inFlightRef.current = cacheKey;
|
|
596
|
-
const
|
|
597
|
-
if (
|
|
709
|
+
const hasLoadedOnce = loadedCacheKeysRef.current.has(cacheKey);
|
|
710
|
+
if (hasLoadedOnce) {
|
|
598
711
|
setIsRefetching(true);
|
|
599
712
|
} else {
|
|
600
713
|
setLoading(true);
|
|
601
714
|
}
|
|
602
715
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
716
|
+
if (!transportRef.current.checkPurchase) {
|
|
717
|
+
setLoading(false);
|
|
718
|
+
setIsRefetching(false);
|
|
719
|
+
loadedCacheKeysRef.current.add(cacheKey);
|
|
720
|
+
inFlightRef.current = null;
|
|
721
|
+
return;
|
|
606
722
|
}
|
|
607
|
-
const data = await
|
|
723
|
+
const data = await transportRef.current.checkPurchase();
|
|
608
724
|
if (data.customerRef) {
|
|
609
725
|
setInternalCustomerRef(data.customerRef);
|
|
726
|
+
loadedCacheKeysRef.current.add(data.customerRef);
|
|
610
727
|
const currentAdapter = getAuthAdapter(configRef.current);
|
|
611
728
|
const currentUserId = await currentAdapter.getUserId();
|
|
612
729
|
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
@@ -626,40 +743,82 @@ var SolvaPayProvider = ({
|
|
|
626
743
|
}
|
|
627
744
|
} finally {
|
|
628
745
|
if (inFlightRef.current === cacheKey) {
|
|
746
|
+
loadedCacheKeysRef.current.add(cacheKey);
|
|
629
747
|
setLoading(false);
|
|
630
748
|
setIsRefetching(false);
|
|
631
749
|
inFlightRef.current = null;
|
|
632
750
|
}
|
|
633
751
|
}
|
|
634
752
|
},
|
|
635
|
-
[isAuthenticated, internalCustomerRef, userId
|
|
753
|
+
[isAuthenticated, internalCustomerRef, userId]
|
|
636
754
|
);
|
|
637
755
|
const fetchPurchaseRef = useRef(fetchPurchase);
|
|
638
756
|
useEffect(() => {
|
|
639
757
|
fetchPurchaseRef.current = fetchPurchase;
|
|
640
758
|
}, [fetchPurchase]);
|
|
759
|
+
const applyInitialRef = useRef(null);
|
|
641
760
|
const refetchPurchase = useCallback(async () => {
|
|
642
761
|
inFlightRef.current = null;
|
|
762
|
+
const refresh = configRef.current?.refreshInitial;
|
|
763
|
+
const hasCheckPurchase = !!transportRef.current.checkPurchase;
|
|
764
|
+
if (refresh && !hasCheckPurchase) {
|
|
765
|
+
setIsRefetching(true);
|
|
766
|
+
try {
|
|
767
|
+
const next = await refresh();
|
|
768
|
+
if (next) applyInitialRef.current?.(next);
|
|
769
|
+
} catch (err) {
|
|
770
|
+
console.error("[SolvaPayProvider] refetchPurchase (MCP) failed:", err);
|
|
771
|
+
} finally {
|
|
772
|
+
setIsRefetching(false);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
643
776
|
await fetchPurchaseRef.current(true);
|
|
644
777
|
}, []);
|
|
778
|
+
const applyInitial = useCallback((next) => {
|
|
779
|
+
setPurchaseData({
|
|
780
|
+
// Mirror the HTTP path: strip non-active rows before any
|
|
781
|
+
// derivation (`activePurchase` etc.) reads the array.
|
|
782
|
+
purchases: filterPurchases(
|
|
783
|
+
next.purchase?.purchases ?? []
|
|
784
|
+
),
|
|
785
|
+
customerRef: next.customerRef ?? next.purchase?.customerRef,
|
|
786
|
+
email: next.purchase?.email,
|
|
787
|
+
name: next.purchase?.name
|
|
788
|
+
});
|
|
789
|
+
if (next.customerRef) {
|
|
790
|
+
setInternalCustomerRef(next.customerRef);
|
|
791
|
+
loadedCacheKeysRef.current.add(next.customerRef);
|
|
792
|
+
} else {
|
|
793
|
+
setInternalCustomerRef(void 0);
|
|
794
|
+
loadedCacheKeysRef.current.clear();
|
|
795
|
+
}
|
|
796
|
+
setIsAuthenticated(next.customerRef !== null);
|
|
797
|
+
setCreditsValue(next.balance?.credits ?? null);
|
|
798
|
+
setDisplayCurrencyValue(next.balance?.displayCurrency ?? null);
|
|
799
|
+
setCreditsPerMinorUnitValue(next.balance?.creditsPerMinorUnit ?? null);
|
|
800
|
+
setDisplayExchangeRateValue(next.balance?.displayExchangeRate ?? null);
|
|
801
|
+
balanceLoadedRef.current = !!next.balance;
|
|
802
|
+
}, []);
|
|
803
|
+
useEffect(() => {
|
|
804
|
+
applyInitialRef.current = applyInitial;
|
|
805
|
+
}, [applyInitial]);
|
|
806
|
+
const refreshBootstrap = useCallback(async () => {
|
|
807
|
+
const refresh = configRef.current?.refreshInitial;
|
|
808
|
+
if (refresh) {
|
|
809
|
+
try {
|
|
810
|
+
const next = await refresh();
|
|
811
|
+
if (next) applyInitial(next);
|
|
812
|
+
} catch (err) {
|
|
813
|
+
console.error("[SolvaPayProvider] refreshBootstrap failed:", err);
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
await Promise.all([refetchPurchase(), fetchBalanceRef.current?.() ?? Promise.resolve()]);
|
|
818
|
+
}, [refetchPurchase, applyInitial]);
|
|
645
819
|
const cancelRenewal = useCallback(
|
|
646
820
|
async (params) => {
|
|
647
|
-
const
|
|
648
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
649
|
-
const route = currentConfig?.api?.cancelRenewal || "/api/cancel-renewal";
|
|
650
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
651
|
-
const res = await fetchFn(route, {
|
|
652
|
-
method: "POST",
|
|
653
|
-
headers,
|
|
654
|
-
body: JSON.stringify(params)
|
|
655
|
-
});
|
|
656
|
-
if (!res.ok) {
|
|
657
|
-
const data = await res.json().catch(() => ({}));
|
|
658
|
-
const error = new Error(data.error || `Failed to cancel renewal: ${res.statusText}`);
|
|
659
|
-
currentConfig?.onError?.(error, "cancelRenewal");
|
|
660
|
-
throw error;
|
|
661
|
-
}
|
|
662
|
-
const result = await res.json();
|
|
821
|
+
const result = await transportRef.current.cancelRenewal(params);
|
|
663
822
|
await refetchPurchase();
|
|
664
823
|
return result;
|
|
665
824
|
},
|
|
@@ -667,22 +826,7 @@ var SolvaPayProvider = ({
|
|
|
667
826
|
);
|
|
668
827
|
const reactivateRenewal = useCallback(
|
|
669
828
|
async (params) => {
|
|
670
|
-
const
|
|
671
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
672
|
-
const route = currentConfig?.api?.reactivateRenewal || "/api/reactivate-renewal";
|
|
673
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
674
|
-
const res = await fetchFn(route, {
|
|
675
|
-
method: "POST",
|
|
676
|
-
headers,
|
|
677
|
-
body: JSON.stringify(params)
|
|
678
|
-
});
|
|
679
|
-
if (!res.ok) {
|
|
680
|
-
const data = await res.json().catch(() => ({}));
|
|
681
|
-
const error = new Error(data.error || `Failed to reactivate renewal: ${res.statusText}`);
|
|
682
|
-
currentConfig?.onError?.(error, "reactivateRenewal");
|
|
683
|
-
throw error;
|
|
684
|
-
}
|
|
685
|
-
const result = await res.json();
|
|
829
|
+
const result = await transportRef.current.reactivateRenewal(params);
|
|
686
830
|
await refetchPurchase();
|
|
687
831
|
return result;
|
|
688
832
|
},
|
|
@@ -690,22 +834,7 @@ var SolvaPayProvider = ({
|
|
|
690
834
|
);
|
|
691
835
|
const activatePlan = useCallback(
|
|
692
836
|
async (params) => {
|
|
693
|
-
const
|
|
694
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
695
|
-
const route = currentConfig?.api?.activatePlan || "/api/activate-plan";
|
|
696
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
697
|
-
const res = await fetchFn(route, {
|
|
698
|
-
method: "POST",
|
|
699
|
-
headers,
|
|
700
|
-
body: JSON.stringify(params)
|
|
701
|
-
});
|
|
702
|
-
if (!res.ok) {
|
|
703
|
-
const data = await res.json().catch(() => ({}));
|
|
704
|
-
const error = new Error(data.error || `Failed to activate plan: ${res.statusText}`);
|
|
705
|
-
currentConfig?.onError?.(error, "activatePlan");
|
|
706
|
-
throw error;
|
|
707
|
-
}
|
|
708
|
-
const result = await res.json();
|
|
837
|
+
const result = await transportRef.current.activatePlan(params);
|
|
709
838
|
if (result.status === "activated" || result.status === "already_active") {
|
|
710
839
|
await refetchPurchase();
|
|
711
840
|
}
|
|
@@ -713,8 +842,13 @@ var SolvaPayProvider = ({
|
|
|
713
842
|
},
|
|
714
843
|
[refetchPurchase]
|
|
715
844
|
);
|
|
845
|
+
const hydratedFromInitialRef = useRef(!!initial);
|
|
716
846
|
useEffect(() => {
|
|
717
847
|
inFlightRef.current = null;
|
|
848
|
+
if (hydratedFromInitialRef.current) {
|
|
849
|
+
hydratedFromInitialRef.current = false;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
718
852
|
if (isAuthenticated || internalCustomerRef) {
|
|
719
853
|
fetchPurchase();
|
|
720
854
|
} else {
|
|
@@ -733,8 +867,10 @@ var SolvaPayProvider = ({
|
|
|
733
867
|
[fetchPurchase, userId]
|
|
734
868
|
);
|
|
735
869
|
const purchase = useMemo2(() => {
|
|
736
|
-
const
|
|
737
|
-
const
|
|
870
|
+
const planPurchases = purchaseData.purchases.filter(isPlanPurchase);
|
|
871
|
+
const balanceTransactions = purchaseData.purchases.filter((p) => !isPlanPurchase(p));
|
|
872
|
+
const activePurchase = getPrimaryPurchase(planPurchases);
|
|
873
|
+
const activePaidPurchases = planPurchases.filter(
|
|
738
874
|
(p) => p.status === "active" && isPaidPurchase(p)
|
|
739
875
|
);
|
|
740
876
|
const activePaidPurchase = activePaidPurchases.sort(
|
|
@@ -749,18 +885,14 @@ var SolvaPayProvider = ({
|
|
|
749
885
|
name: purchaseData.name,
|
|
750
886
|
purchases: purchaseData.purchases,
|
|
751
887
|
hasProduct: (productName) => {
|
|
752
|
-
return
|
|
753
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
754
|
-
);
|
|
755
|
-
},
|
|
756
|
-
hasPlan: (productName) => {
|
|
757
|
-
return purchaseData.purchases.some(
|
|
888
|
+
return planPurchases.some(
|
|
758
889
|
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
759
890
|
);
|
|
760
891
|
},
|
|
761
892
|
activePurchase,
|
|
762
893
|
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
763
|
-
activePaidPurchase
|
|
894
|
+
activePaidPurchase,
|
|
895
|
+
balanceTransactions
|
|
764
896
|
};
|
|
765
897
|
}, [loading, isRefetching, purchaseError, purchaseData, internalCustomerRef]);
|
|
766
898
|
const balance = useMemo2(
|
|
@@ -773,7 +905,15 @@ var SolvaPayProvider = ({
|
|
|
773
905
|
refetch: fetchBalanceImpl,
|
|
774
906
|
adjustBalance: adjustBalanceImpl
|
|
775
907
|
}),
|
|
776
|
-
[
|
|
908
|
+
[
|
|
909
|
+
balanceLoading,
|
|
910
|
+
creditsValue,
|
|
911
|
+
displayCurrencyValue,
|
|
912
|
+
creditsPerMinorUnitValue,
|
|
913
|
+
displayExchangeRateValue,
|
|
914
|
+
fetchBalanceImpl,
|
|
915
|
+
adjustBalanceImpl
|
|
916
|
+
]
|
|
777
917
|
);
|
|
778
918
|
const contextValue = useMemo2(
|
|
779
919
|
() => ({
|
|
@@ -788,6 +928,7 @@ var SolvaPayProvider = ({
|
|
|
788
928
|
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
789
929
|
updateCustomerRef,
|
|
790
930
|
balance,
|
|
931
|
+
refreshBootstrap,
|
|
791
932
|
_config: configRef.current
|
|
792
933
|
}),
|
|
793
934
|
[
|
|
@@ -802,7 +943,8 @@ var SolvaPayProvider = ({
|
|
|
802
943
|
purchaseData.customerRef,
|
|
803
944
|
internalCustomerRef,
|
|
804
945
|
updateCustomerRef,
|
|
805
|
-
balance
|
|
946
|
+
balance,
|
|
947
|
+
refreshBootstrap
|
|
806
948
|
]
|
|
807
949
|
);
|
|
808
950
|
return /* @__PURE__ */ jsx2(SolvaPayContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(CopyProvider, { locale: config?.locale, copy: config?.copy, children }) });
|
|
@@ -939,8 +1081,11 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
|
939
1081
|
function getStripeCacheKey(publishableKey, accountId) {
|
|
940
1082
|
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
941
1083
|
}
|
|
942
|
-
async function resolvePlanRef(productRef,
|
|
1084
|
+
async function resolvePlanRef(productRef, config) {
|
|
1085
|
+
const listPlansRoute = config?.api?.listPlans || "/api/list-plans";
|
|
943
1086
|
const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
|
|
1087
|
+
const fetchFn = config?.fetch || fetch;
|
|
1088
|
+
const { headers } = await buildRequestHeaders(config);
|
|
944
1089
|
const res = await fetchFn(url, { method: "GET", headers });
|
|
945
1090
|
if (!res.ok) {
|
|
946
1091
|
throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
|
|
@@ -991,8 +1136,7 @@ function useCheckout(options) {
|
|
|
991
1136
|
try {
|
|
992
1137
|
let effectivePlanRef = planRef;
|
|
993
1138
|
if (!effectivePlanRef && productRef) {
|
|
994
|
-
|
|
995
|
-
effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
|
|
1139
|
+
effectivePlanRef = await resolvePlanRef(productRef, _config);
|
|
996
1140
|
setResolvedPlanRef(effectivePlanRef);
|
|
997
1141
|
}
|
|
998
1142
|
if (!effectivePlanRef) {
|
|
@@ -1381,27 +1525,41 @@ function usePlan(options) {
|
|
|
1381
1525
|
|
|
1382
1526
|
// src/hooks/useProduct.ts
|
|
1383
1527
|
import { useCallback as useCallback5, useEffect as useEffect4, useState as useState5 } from "react";
|
|
1528
|
+
|
|
1529
|
+
// src/transport/cache-key.ts
|
|
1530
|
+
var transportIds = /* @__PURE__ */ new WeakMap();
|
|
1531
|
+
var nextTransportId = 0;
|
|
1532
|
+
function transportIdFor(transport) {
|
|
1533
|
+
let id = transportIds.get(transport);
|
|
1534
|
+
if (id === void 0) {
|
|
1535
|
+
id = ++nextTransportId;
|
|
1536
|
+
transportIds.set(transport, id);
|
|
1537
|
+
}
|
|
1538
|
+
return id;
|
|
1539
|
+
}
|
|
1540
|
+
function createTransportCacheKey(config, fallbackRoute, suffix) {
|
|
1541
|
+
const transport = config?.transport;
|
|
1542
|
+
if (transport) {
|
|
1543
|
+
const id = transportIdFor(transport);
|
|
1544
|
+
return suffix ? `transport:${id}:${suffix}` : `transport:${id}`;
|
|
1545
|
+
}
|
|
1546
|
+
return fallbackRoute;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/hooks/useProduct.ts
|
|
1384
1550
|
var productCache = /* @__PURE__ */ new Map();
|
|
1385
1551
|
var CACHE_DURATION3 = 5 * 60 * 1e3;
|
|
1386
|
-
function
|
|
1387
|
-
return config
|
|
1552
|
+
function cacheKeyFor(config, productRef) {
|
|
1553
|
+
return createTransportCacheKey(config, productRef, productRef);
|
|
1388
1554
|
}
|
|
1389
1555
|
async function fetchProduct(productRef, config) {
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
const { headers } = await buildRequestHeaders(config);
|
|
1394
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
1395
|
-
if (!res.ok) {
|
|
1396
|
-
const error = new Error(`Failed to fetch product: ${res.statusText || res.status}`);
|
|
1397
|
-
config?.onError?.(error, "getProduct");
|
|
1398
|
-
throw error;
|
|
1399
|
-
}
|
|
1400
|
-
return await res.json();
|
|
1556
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
1557
|
+
if (!transport.getProduct) return null;
|
|
1558
|
+
return transport.getProduct(productRef);
|
|
1401
1559
|
}
|
|
1402
1560
|
function useProduct(productRef) {
|
|
1403
1561
|
const { _config } = useSolvaPay();
|
|
1404
|
-
const cacheKey = productRef
|
|
1562
|
+
const cacheKey = productRef ? cacheKeyFor(_config, productRef) : "";
|
|
1405
1563
|
const [product, setProduct] = useState5(
|
|
1406
1564
|
() => productRef ? productCache.get(cacheKey)?.product ?? null : null
|
|
1407
1565
|
);
|
|
@@ -1419,7 +1577,8 @@ function useProduct(productRef) {
|
|
|
1419
1577
|
setError(null);
|
|
1420
1578
|
return;
|
|
1421
1579
|
}
|
|
1422
|
-
const
|
|
1580
|
+
const key = cacheKeyFor(_config, productRef);
|
|
1581
|
+
const cached = productCache.get(key);
|
|
1423
1582
|
const now = Date.now();
|
|
1424
1583
|
if (!force && cached?.product && now - cached.timestamp < CACHE_DURATION3) {
|
|
1425
1584
|
setProduct(cached.product);
|
|
@@ -1444,12 +1603,26 @@ function useProduct(productRef) {
|
|
|
1444
1603
|
setLoading(true);
|
|
1445
1604
|
setError(null);
|
|
1446
1605
|
const promise = fetchProduct(productRef, _config);
|
|
1447
|
-
productCache.set(
|
|
1606
|
+
productCache.set(key, {
|
|
1607
|
+
product: cached?.product ?? null,
|
|
1608
|
+
promise,
|
|
1609
|
+
timestamp: now
|
|
1610
|
+
});
|
|
1448
1611
|
const p = await promise;
|
|
1449
|
-
|
|
1612
|
+
if (p === null) {
|
|
1613
|
+
productCache.set(key, {
|
|
1614
|
+
product: cached?.product ?? null,
|
|
1615
|
+
promise: null,
|
|
1616
|
+
timestamp: cached?.timestamp ?? now
|
|
1617
|
+
});
|
|
1618
|
+
setProduct(cached?.product ?? null);
|
|
1619
|
+
setLoading(false);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
productCache.set(key, { product: p, promise: null, timestamp: now });
|
|
1450
1623
|
setProduct(p);
|
|
1451
1624
|
} catch (err) {
|
|
1452
|
-
productCache.delete(
|
|
1625
|
+
productCache.delete(key);
|
|
1453
1626
|
setError(err instanceof Error ? err : new Error("Failed to load product"));
|
|
1454
1627
|
} finally {
|
|
1455
1628
|
setLoading(false);
|
|
@@ -1556,6 +1729,9 @@ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
|
|
|
1556
1729
|
function getFractionDigits(currency) {
|
|
1557
1730
|
return ZERO_DECIMAL_CURRENCIES.has(currency.toLowerCase()) ? 0 : 2;
|
|
1558
1731
|
}
|
|
1732
|
+
function getMinorUnitsPerMajor(currency) {
|
|
1733
|
+
return getFractionDigits(currency) === 0 ? 1 : 100;
|
|
1734
|
+
}
|
|
1559
1735
|
function toMajorUnits(amountMinor, currency) {
|
|
1560
1736
|
const fractionDigits = getFractionDigits(currency);
|
|
1561
1737
|
return fractionDigits === 0 ? amountMinor : amountMinor / 100;
|
|
@@ -1565,7 +1741,10 @@ function formatPrice(amountMinor, currency, opts = {}) {
|
|
|
1565
1741
|
if (amountMinor === 0 && free !== "") {
|
|
1566
1742
|
return free;
|
|
1567
1743
|
}
|
|
1568
|
-
const
|
|
1744
|
+
const naturalFractionDigits = getFractionDigits(currency);
|
|
1745
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
1746
|
+
const isWhole = amountMinor % minorPerMajor === 0;
|
|
1747
|
+
const fractionDigits = isWhole ? 0 : naturalFractionDigits;
|
|
1569
1748
|
const major = toMajorUnits(amountMinor, currency);
|
|
1570
1749
|
const formatter = new Intl.NumberFormat(locale, {
|
|
1571
1750
|
style: "currency",
|
|
@@ -1759,24 +1938,17 @@ var CheckoutSummary2 = ({
|
|
|
1759
1938
|
import { useCallback as useCallback7, useEffect as useEffect5, useState as useState7 } from "react";
|
|
1760
1939
|
var merchantCache = /* @__PURE__ */ new Map();
|
|
1761
1940
|
var CACHE_DURATION4 = 5 * 60 * 1e3;
|
|
1762
|
-
function
|
|
1763
|
-
return config?.api?.getMerchant || "/api/merchant";
|
|
1941
|
+
function cacheKeyFor2(config) {
|
|
1942
|
+
return createTransportCacheKey(config, config?.api?.getMerchant || "/api/merchant");
|
|
1764
1943
|
}
|
|
1765
1944
|
async function fetchMerchant(config) {
|
|
1766
|
-
const
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
1770
|
-
if (!res.ok) {
|
|
1771
|
-
const error = new Error(`Failed to fetch merchant: ${res.statusText || res.status}`);
|
|
1772
|
-
config?.onError?.(error, "getMerchant");
|
|
1773
|
-
throw error;
|
|
1774
|
-
}
|
|
1775
|
-
return await res.json();
|
|
1945
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
1946
|
+
if (!transport.getMerchant) return null;
|
|
1947
|
+
return transport.getMerchant();
|
|
1776
1948
|
}
|
|
1777
1949
|
function useMerchant() {
|
|
1778
1950
|
const { _config } = useSolvaPay();
|
|
1779
|
-
const key =
|
|
1951
|
+
const key = cacheKeyFor2(_config);
|
|
1780
1952
|
const [merchant, setMerchant] = useState7(
|
|
1781
1953
|
() => merchantCache.get(key)?.merchant ?? null
|
|
1782
1954
|
);
|
|
@@ -1812,8 +1984,22 @@ function useMerchant() {
|
|
|
1812
1984
|
setLoading(true);
|
|
1813
1985
|
setError(null);
|
|
1814
1986
|
const promise = fetchMerchant(_config);
|
|
1815
|
-
merchantCache.set(key, {
|
|
1987
|
+
merchantCache.set(key, {
|
|
1988
|
+
merchant: cached?.merchant ?? null,
|
|
1989
|
+
promise,
|
|
1990
|
+
timestamp: now
|
|
1991
|
+
});
|
|
1816
1992
|
const m = await promise;
|
|
1993
|
+
if (m === null) {
|
|
1994
|
+
merchantCache.set(key, {
|
|
1995
|
+
merchant: cached?.merchant ?? null,
|
|
1996
|
+
promise: null,
|
|
1997
|
+
timestamp: cached?.timestamp ?? now
|
|
1998
|
+
});
|
|
1999
|
+
setMerchant(cached?.merchant ?? null);
|
|
2000
|
+
setLoading(false);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
1817
2003
|
merchantCache.set(key, { merchant: m, promise: null, timestamp: now });
|
|
1818
2004
|
setMerchant(m);
|
|
1819
2005
|
} catch (err) {
|
|
@@ -1927,31 +2113,36 @@ var MandateText = forwardRef2(
|
|
|
1927
2113
|
|
|
1928
2114
|
// src/components/Spinner.tsx
|
|
1929
2115
|
import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1930
|
-
var
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
}) => {
|
|
1934
|
-
const sizeClasses = {
|
|
1935
|
-
sm: "h-4 w-4",
|
|
1936
|
-
md: "h-5 w-5",
|
|
1937
|
-
lg: "h-6 w-6"
|
|
1938
|
-
};
|
|
2116
|
+
var SIZE_PX = { sm: 16, md: 20, lg: 24 };
|
|
2117
|
+
var Spinner = ({ className, size = "md" }) => {
|
|
2118
|
+
const px = SIZE_PX[size];
|
|
1939
2119
|
return /* @__PURE__ */ jsxs2(
|
|
1940
2120
|
"svg",
|
|
1941
2121
|
{
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2122
|
+
"data-solvapay-spinner": "",
|
|
2123
|
+
className,
|
|
2124
|
+
width: px,
|
|
2125
|
+
height: px,
|
|
1945
2126
|
viewBox: "0 0 24 24",
|
|
1946
|
-
|
|
1947
|
-
"aria-
|
|
2127
|
+
fill: "none",
|
|
2128
|
+
"aria-hidden": "true",
|
|
1948
2129
|
children: [
|
|
1949
|
-
/* @__PURE__ */ jsx9(
|
|
2130
|
+
/* @__PURE__ */ jsx9(
|
|
2131
|
+
"circle",
|
|
2132
|
+
{
|
|
2133
|
+
cx: "12",
|
|
2134
|
+
cy: "12",
|
|
2135
|
+
r: "10",
|
|
2136
|
+
stroke: "currentColor",
|
|
2137
|
+
strokeWidth: "4",
|
|
2138
|
+
strokeOpacity: "0.25"
|
|
2139
|
+
}
|
|
2140
|
+
),
|
|
1950
2141
|
/* @__PURE__ */ jsx9(
|
|
1951
2142
|
"path",
|
|
1952
2143
|
{
|
|
1953
|
-
className: "opacity-75",
|
|
1954
2144
|
fill: "currentColor",
|
|
2145
|
+
fillOpacity: "0.75",
|
|
1955
2146
|
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"
|
|
1956
2147
|
}
|
|
1957
2148
|
)
|
|
@@ -1969,6 +2160,13 @@ async function confirmPayment(input) {
|
|
|
1969
2160
|
if (!paymentElement) {
|
|
1970
2161
|
return { status: "error", message: copy.errors.cardElementMissing };
|
|
1971
2162
|
}
|
|
2163
|
+
const { error: submitError } = await elements.submit();
|
|
2164
|
+
if (submitError) {
|
|
2165
|
+
return {
|
|
2166
|
+
status: "error",
|
|
2167
|
+
message: submitError.message || copy.errors.paymentUnexpected
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
1972
2170
|
const { error: error2, paymentIntent: paymentIntent2 } = await stripe.confirmPayment({
|
|
1973
2171
|
elements,
|
|
1974
2172
|
clientSecret,
|
|
@@ -2301,7 +2499,11 @@ var PaidInner = ({
|
|
|
2301
2499
|
const copy = useCopy();
|
|
2302
2500
|
const customer = useCustomer();
|
|
2303
2501
|
const { processPayment } = useSolvaPay();
|
|
2304
|
-
const { refetch } = usePurchase();
|
|
2502
|
+
const { refetch, hasPaidPurchase } = usePurchase();
|
|
2503
|
+
const hasPaidPurchaseRef = useRef4(hasPaidPurchase);
|
|
2504
|
+
useEffect6(() => {
|
|
2505
|
+
hasPaidPurchaseRef.current = hasPaidPurchase;
|
|
2506
|
+
}, [hasPaidPurchase]);
|
|
2305
2507
|
const [elementKind, setElementKind] = useState8(
|
|
2306
2508
|
children ? null : "payment-element"
|
|
2307
2509
|
);
|
|
@@ -2352,14 +2554,34 @@ var PaidInner = ({
|
|
|
2352
2554
|
refetchPurchase: refetch,
|
|
2353
2555
|
copy
|
|
2354
2556
|
});
|
|
2355
|
-
setIsProcessing(false);
|
|
2356
2557
|
if (reconcileResult.status === "success") {
|
|
2357
2558
|
const pi = paymentIntent;
|
|
2358
2559
|
onSuccess?.(pi);
|
|
2359
2560
|
const paid = { kind: "paid", paymentIntent: pi };
|
|
2360
2561
|
onResult?.(paid);
|
|
2562
|
+
const CONFIRMATION_TIMEOUT_MS = 1e4;
|
|
2563
|
+
const startedAt = Date.now();
|
|
2564
|
+
let attempt = 0;
|
|
2565
|
+
while (!hasPaidPurchaseRef.current && Date.now() - startedAt < CONFIRMATION_TIMEOUT_MS) {
|
|
2566
|
+
attempt += 1;
|
|
2567
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 1500)));
|
|
2568
|
+
if (hasPaidPurchaseRef.current) break;
|
|
2569
|
+
try {
|
|
2570
|
+
await refetch();
|
|
2571
|
+
} catch {
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (!hasPaidPurchaseRef.current) {
|
|
2575
|
+
const confirmationMsg = copy.errors.paymentConfirmationDelayed;
|
|
2576
|
+
setError(confirmationMsg);
|
|
2577
|
+
setIsProcessing(false);
|
|
2578
|
+
onError?.(new Error(confirmationMsg));
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
setIsProcessing(false);
|
|
2361
2582
|
return;
|
|
2362
2583
|
}
|
|
2584
|
+
setIsProcessing(false);
|
|
2363
2585
|
const msg = reconcileResult.status === "timeout" ? reconcileResult.error.message : copy.errors.paymentProcessingFailed;
|
|
2364
2586
|
setError(msg);
|
|
2365
2587
|
onError?.(reconcileResult.error);
|
|
@@ -2825,7 +3047,9 @@ function useTopupAmountSelector(options) {
|
|
|
2825
3047
|
if (resolvedAmount < minAmount) {
|
|
2826
3048
|
setError(
|
|
2827
3049
|
interpolate(copy.topup.minAmount, {
|
|
2828
|
-
amount:
|
|
3050
|
+
amount: formatPrice(minAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3051
|
+
free: ""
|
|
3052
|
+
})
|
|
2829
3053
|
})
|
|
2830
3054
|
);
|
|
2831
3055
|
return false;
|
|
@@ -2833,14 +3057,16 @@ function useTopupAmountSelector(options) {
|
|
|
2833
3057
|
if (resolvedAmount > maxAmount) {
|
|
2834
3058
|
setError(
|
|
2835
3059
|
interpolate(copy.topup.maxAmount, {
|
|
2836
|
-
amount:
|
|
3060
|
+
amount: formatPrice(maxAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3061
|
+
free: ""
|
|
3062
|
+
})
|
|
2837
3063
|
})
|
|
2838
3064
|
);
|
|
2839
3065
|
return false;
|
|
2840
3066
|
}
|
|
2841
3067
|
setError(null);
|
|
2842
3068
|
return true;
|
|
2843
|
-
}, [resolvedAmount, minAmount, maxAmount,
|
|
3069
|
+
}, [resolvedAmount, minAmount, maxAmount, currency, copy]);
|
|
2844
3070
|
const reset = useCallback9(() => {
|
|
2845
3071
|
setSelectedAmount(null);
|
|
2846
3072
|
setCustomAmountRaw("");
|
|
@@ -2879,7 +3105,7 @@ import {
|
|
|
2879
3105
|
useEffect as useEffect8,
|
|
2880
3106
|
useMemo as useMemo8
|
|
2881
3107
|
} from "react";
|
|
2882
|
-
import {
|
|
3108
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2883
3109
|
var AmountPickerContext = createContext6(null);
|
|
2884
3110
|
function usePickerCtx(part) {
|
|
2885
3111
|
const ctx = useContext8(AmountPickerContext);
|
|
@@ -2888,15 +3114,31 @@ function usePickerCtx(part) {
|
|
|
2888
3114
|
}
|
|
2889
3115
|
return ctx;
|
|
2890
3116
|
}
|
|
2891
|
-
var Root3 = forwardRef4(function AmountPickerRoot({
|
|
3117
|
+
var Root3 = forwardRef4(function AmountPickerRoot({
|
|
3118
|
+
currency,
|
|
3119
|
+
minAmount,
|
|
3120
|
+
maxAmount,
|
|
3121
|
+
emit = "major",
|
|
3122
|
+
selector: externalSelector,
|
|
3123
|
+
onChange,
|
|
3124
|
+
asChild,
|
|
3125
|
+
children,
|
|
3126
|
+
...rest
|
|
3127
|
+
}, forwardedRef) {
|
|
2892
3128
|
const solva = useContext8(SolvaPayContext);
|
|
2893
3129
|
if (!solva) throw new MissingProviderError("AmountPicker");
|
|
2894
|
-
const
|
|
3130
|
+
const internalSelector = useTopupAmountSelector({ currency, minAmount, maxAmount });
|
|
3131
|
+
const selector = externalSelector ?? internalSelector;
|
|
2895
3132
|
const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
|
|
3133
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
3134
|
+
const resolvedAmountMinor = selector.resolvedAmount != null && selector.resolvedAmount > 0 ? Math.round(selector.resolvedAmount * minorPerMajor) : null;
|
|
2896
3135
|
useEffect8(() => {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3136
|
+
if (emit === "minor") {
|
|
3137
|
+
onChange?.(resolvedAmountMinor);
|
|
3138
|
+
} else {
|
|
3139
|
+
onChange?.(selector.resolvedAmount);
|
|
3140
|
+
}
|
|
3141
|
+
}, [emit, selector.resolvedAmount, resolvedAmountMinor, onChange]);
|
|
2900
3142
|
const rate = displayExchangeRate ?? 1;
|
|
2901
3143
|
const estimatedCredits = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 && resolvedAmountMinor != null ? Math.floor(resolvedAmountMinor / rate * creditsPerMinorUnit) : null;
|
|
2902
3144
|
const isApproximate = rate !== 1;
|
|
@@ -2904,12 +3146,23 @@ var Root3 = forwardRef4(function AmountPickerRoot({ currency, minAmount, maxAmou
|
|
|
2904
3146
|
() => ({
|
|
2905
3147
|
...selector,
|
|
2906
3148
|
currency,
|
|
3149
|
+
emit,
|
|
2907
3150
|
creditsPerMinorUnit,
|
|
2908
3151
|
displayExchangeRate,
|
|
2909
3152
|
estimatedCredits,
|
|
2910
|
-
isApproximate
|
|
3153
|
+
isApproximate,
|
|
3154
|
+
resolvedAmountMinor
|
|
2911
3155
|
}),
|
|
2912
|
-
[
|
|
3156
|
+
[
|
|
3157
|
+
selector,
|
|
3158
|
+
currency,
|
|
3159
|
+
emit,
|
|
3160
|
+
creditsPerMinorUnit,
|
|
3161
|
+
displayExchangeRate,
|
|
3162
|
+
estimatedCredits,
|
|
3163
|
+
isApproximate,
|
|
3164
|
+
resolvedAmountMinor
|
|
3165
|
+
]
|
|
2913
3166
|
);
|
|
2914
3167
|
const Comp = asChild ? Slot : "div";
|
|
2915
3168
|
return /* @__PURE__ */ jsx11(AmountPickerContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx11(Comp, { ref: forwardedRef, "data-solvapay-amount-picker": "", ...rest, children }) });
|
|
@@ -2928,19 +3181,18 @@ var Option = forwardRef4(function AmountPickerOption({ asChild, amount, onClick,
|
|
|
2928
3181
|
}),
|
|
2929
3182
|
...rest
|
|
2930
3183
|
};
|
|
3184
|
+
const defaultLabel = formatPrice(
|
|
3185
|
+
amount * getMinorUnitsPerMajor(ctx.currency),
|
|
3186
|
+
ctx.currency,
|
|
3187
|
+
{ free: "" }
|
|
3188
|
+
);
|
|
2931
3189
|
if (asChild) {
|
|
2932
3190
|
return (
|
|
2933
3191
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2934
|
-
/* @__PURE__ */ jsx11(Slot, { ref: forwardedRef, ...commonProps, children: children ??
|
|
2935
|
-
ctx.currencySymbol,
|
|
2936
|
-
amount.toLocaleString()
|
|
2937
|
-
] }) })
|
|
3192
|
+
/* @__PURE__ */ jsx11(Slot, { ref: forwardedRef, ...commonProps, children: children ?? defaultLabel })
|
|
2938
3193
|
);
|
|
2939
3194
|
}
|
|
2940
|
-
return /* @__PURE__ */ jsx11("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ??
|
|
2941
|
-
ctx.currencySymbol,
|
|
2942
|
-
amount.toLocaleString()
|
|
2943
|
-
] }) });
|
|
3195
|
+
return /* @__PURE__ */ jsx11("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? defaultLabel });
|
|
2944
3196
|
});
|
|
2945
3197
|
var Custom = forwardRef4(function AmountPickerCustom({ asChild, onChange, onFocus, ...rest }, forwardedRef) {
|
|
2946
3198
|
const ctx = usePickerCtx("Custom");
|
|
@@ -2972,9 +3224,12 @@ var Confirm = forwardRef4(function AmountPickerConfirm({ asChild, onClick, onCon
|
|
|
2972
3224
|
const ctx = usePickerCtx("Confirm");
|
|
2973
3225
|
const isDisabled = disabled || !ctx.resolvedAmount;
|
|
2974
3226
|
const handleConfirm = useCallback10(() => {
|
|
2975
|
-
if (ctx.validate()
|
|
2976
|
-
|
|
3227
|
+
if (!ctx.validate()) return;
|
|
3228
|
+
if (ctx.emit === "minor") {
|
|
3229
|
+
if (ctx.resolvedAmountMinor != null) onConfirm?.(ctx.resolvedAmountMinor);
|
|
3230
|
+
return;
|
|
2977
3231
|
}
|
|
3232
|
+
if (ctx.resolvedAmount != null) onConfirm?.(ctx.resolvedAmount);
|
|
2978
3233
|
}, [ctx, onConfirm]);
|
|
2979
3234
|
const commonProps = {
|
|
2980
3235
|
"data-solvapay-amount-picker-confirm": "",
|
|
@@ -3109,7 +3364,7 @@ import {
|
|
|
3109
3364
|
useElements as useElements2,
|
|
3110
3365
|
PaymentElement as StripePaymentElement2
|
|
3111
3366
|
} from "@stripe/react-stripe-js";
|
|
3112
|
-
import { Fragment as
|
|
3367
|
+
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3113
3368
|
var TopupFormContext = createContext7(null);
|
|
3114
3369
|
function useTopupCtx(part) {
|
|
3115
3370
|
const ctx = useContext9(TopupFormContext);
|
|
@@ -3214,6 +3469,14 @@ var Inner = ({
|
|
|
3214
3469
|
}
|
|
3215
3470
|
setError(null);
|
|
3216
3471
|
setIsProcessing(true);
|
|
3472
|
+
const { error: submitError } = await elements.submit();
|
|
3473
|
+
if (submitError) {
|
|
3474
|
+
const msg = submitError.message || copy.errors.paymentUnexpected;
|
|
3475
|
+
setError(msg);
|
|
3476
|
+
setIsProcessing(false);
|
|
3477
|
+
onError?.(new Error(msg));
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3217
3480
|
const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
|
|
3218
3481
|
elements,
|
|
3219
3482
|
clientSecret,
|
|
@@ -3322,7 +3585,7 @@ var SubmitButton2 = forwardRef5(function TopupFormSubmitButton({ asChild, onClic
|
|
|
3322
3585
|
const ctx = useTopupCtx("SubmitButton");
|
|
3323
3586
|
const copy = useCopy();
|
|
3324
3587
|
const dataState = ctx.isProcessing ? "processing" : !ctx.canSubmit ? "disabled" : "idle";
|
|
3325
|
-
const content = ctx.isProcessing ? /* @__PURE__ */
|
|
3588
|
+
const content = ctx.isProcessing ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
3326
3589
|
/* @__PURE__ */ jsx12(Spinner, { size: "sm" }),
|
|
3327
3590
|
/* @__PURE__ */ jsx12("span", { children: copy.cta.processing })
|
|
3328
3591
|
] }) : children ? children : copy.cta.topUp;
|
|
@@ -3386,145 +3649,11 @@ function useTopupForm() {
|
|
|
3386
3649
|
return useTopupCtx("useTopupForm");
|
|
3387
3650
|
}
|
|
3388
3651
|
|
|
3389
|
-
// src/TopupForm.tsx
|
|
3390
|
-
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3391
|
-
var TopupForm2 = (props) => {
|
|
3392
|
-
const { className, buttonClassName, submitButtonText, ...rootProps } = props;
|
|
3393
|
-
const rootClass = ["solvapay-topup-form", className].filter(Boolean).join(" ");
|
|
3394
|
-
const buttonClass = ["solvapay-topup-form-submit", buttonClassName].filter(Boolean).join(" ");
|
|
3395
|
-
return /* @__PURE__ */ jsxs6(TopupForm.Root, { ...rootProps, className: rootClass, children: [
|
|
3396
|
-
/* @__PURE__ */ jsx13(TopupForm.PaymentElement, {}),
|
|
3397
|
-
/* @__PURE__ */ jsx13(TopupForm.Error, { className: "solvapay-topup-form-error" }),
|
|
3398
|
-
/* @__PURE__ */ jsx13(TopupForm.Loading, { className: "solvapay-topup-form-loading" }),
|
|
3399
|
-
/* @__PURE__ */ jsx13(TopupForm.SubmitButton, { className: buttonClass, children: submitButtonText })
|
|
3400
|
-
] });
|
|
3401
|
-
};
|
|
3402
|
-
|
|
3403
|
-
// src/primitives/ProductBadge.tsx
|
|
3404
|
-
import { forwardRef as forwardRef6, useContext as useContext10, useEffect as useEffect10, useState as useState12 } from "react";
|
|
3405
|
-
import { Fragment as Fragment5, jsx as jsx14 } from "react/jsx-runtime";
|
|
3406
|
-
var ProductBadgeImpl = forwardRef6(function ProductBadge({ asChild, children, ...rest }, forwardedRef) {
|
|
3407
|
-
const solva = useContext10(SolvaPayContext);
|
|
3408
|
-
if (!solva) throw new MissingProviderError("ProductBadge");
|
|
3409
|
-
const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
|
|
3410
|
-
const copy = useCopy();
|
|
3411
|
-
const [hasLoadedOnce, setHasLoadedOnce] = useState12(false);
|
|
3412
|
-
useEffect10(() => {
|
|
3413
|
-
if (!loading) setHasLoadedOnce(true);
|
|
3414
|
-
}, [loading]);
|
|
3415
|
-
const planToDisplay = activePurchase?.productName ?? null;
|
|
3416
|
-
const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
|
|
3417
|
-
if (!shouldShow) return null;
|
|
3418
|
-
const commonProps = {
|
|
3419
|
-
"data-solvapay-product-badge": "",
|
|
3420
|
-
"data-loading": loading ? "" : void 0,
|
|
3421
|
-
"data-has-purchase": activePurchase ? "" : void 0,
|
|
3422
|
-
"data-has-paid-purchase": hasPaidPurchase ? "" : void 0,
|
|
3423
|
-
role: "status",
|
|
3424
|
-
"aria-live": "polite",
|
|
3425
|
-
"aria-busy": loading,
|
|
3426
|
-
"aria-label": interpolate(copy.product.currentProductLabel, { name: planToDisplay }),
|
|
3427
|
-
...rest
|
|
3428
|
-
};
|
|
3429
|
-
if (asChild) {
|
|
3430
|
-
return (
|
|
3431
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3432
|
-
/* @__PURE__ */ jsx14(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx14(Fragment5, { children: planToDisplay }) })
|
|
3433
|
-
);
|
|
3434
|
-
}
|
|
3435
|
-
void purchases;
|
|
3436
|
-
return (
|
|
3437
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3438
|
-
/* @__PURE__ */ jsx14("div", { ref: forwardedRef, ...commonProps, children: children ?? planToDisplay })
|
|
3439
|
-
);
|
|
3440
|
-
});
|
|
3441
|
-
var ProductBadge2 = ProductBadgeImpl;
|
|
3442
|
-
var PlanBadge = ProductBadgeImpl;
|
|
3443
|
-
|
|
3444
|
-
// src/primitives/PurchaseGate.tsx
|
|
3445
|
-
import {
|
|
3446
|
-
createContext as createContext8,
|
|
3447
|
-
forwardRef as forwardRef7,
|
|
3448
|
-
useContext as useContext11,
|
|
3449
|
-
useMemo as useMemo10
|
|
3450
|
-
} from "react";
|
|
3451
|
-
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
3452
|
-
var PurchaseGateContext = createContext8(null);
|
|
3453
|
-
function useGateCtx(part) {
|
|
3454
|
-
const ctx = useContext11(PurchaseGateContext);
|
|
3455
|
-
if (!ctx) {
|
|
3456
|
-
throw new Error(`PurchaseGate.${part} must be rendered inside <PurchaseGate.Root>.`);
|
|
3457
|
-
}
|
|
3458
|
-
return ctx;
|
|
3459
|
-
}
|
|
3460
|
-
var Root5 = forwardRef7(function PurchaseGateRoot({ requirePlan, requireProduct, asChild, children, ...rest }, forwardedRef) {
|
|
3461
|
-
const solva = useContext11(SolvaPayContext);
|
|
3462
|
-
if (!solva) throw new MissingProviderError("PurchaseGate");
|
|
3463
|
-
const { purchases, loading, hasProduct, error } = usePurchase();
|
|
3464
|
-
const productToCheck = requireProduct || requirePlan;
|
|
3465
|
-
const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
|
|
3466
|
-
const state = loading ? "loading" : hasAccess ? "allowed" : "blocked";
|
|
3467
|
-
const ctx = useMemo10(
|
|
3468
|
-
() => ({ state, loading, hasAccess, error }),
|
|
3469
|
-
[state, loading, hasAccess, error]
|
|
3470
|
-
);
|
|
3471
|
-
const Comp = asChild ? Slot : "div";
|
|
3472
|
-
return /* @__PURE__ */ jsx15(PurchaseGateContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx15(
|
|
3473
|
-
Comp,
|
|
3474
|
-
{
|
|
3475
|
-
ref: forwardedRef,
|
|
3476
|
-
"data-solvapay-purchase-gate": "",
|
|
3477
|
-
"data-state": state,
|
|
3478
|
-
...rest,
|
|
3479
|
-
children
|
|
3480
|
-
}
|
|
3481
|
-
) });
|
|
3482
|
-
});
|
|
3483
|
-
var Allowed = forwardRef7(function PurchaseGateAllowed({ asChild, children, ...rest }, forwardedRef) {
|
|
3484
|
-
const ctx = useGateCtx("Allowed");
|
|
3485
|
-
if (ctx.state !== "allowed") return null;
|
|
3486
|
-
const Comp = asChild ? Slot : "div";
|
|
3487
|
-
return /* @__PURE__ */ jsx15(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-allowed": "", ...rest, children });
|
|
3488
|
-
});
|
|
3489
|
-
var Blocked = forwardRef7(function PurchaseGateBlocked({ asChild, children, ...rest }, forwardedRef) {
|
|
3490
|
-
const ctx = useGateCtx("Blocked");
|
|
3491
|
-
if (ctx.state !== "blocked") return null;
|
|
3492
|
-
const Comp = asChild ? Slot : "div";
|
|
3493
|
-
return /* @__PURE__ */ jsx15(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-blocked": "", ...rest, children });
|
|
3494
|
-
});
|
|
3495
|
-
var Loading3 = forwardRef7(function PurchaseGateLoading({ asChild, children, ...rest }, forwardedRef) {
|
|
3496
|
-
const ctx = useGateCtx("Loading");
|
|
3497
|
-
if (ctx.state !== "loading") return null;
|
|
3498
|
-
const Comp = asChild ? Slot : "div";
|
|
3499
|
-
return /* @__PURE__ */ jsx15(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-loading": "", ...rest, children });
|
|
3500
|
-
});
|
|
3501
|
-
var ErrorSlot3 = forwardRef7(function PurchaseGateError({ asChild, children, ...rest }, forwardedRef) {
|
|
3502
|
-
const ctx = useGateCtx("Error");
|
|
3503
|
-
if (!ctx.error) return null;
|
|
3504
|
-
const Comp = asChild ? Slot : "div";
|
|
3505
|
-
return /* @__PURE__ */ jsx15(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-error": "", role: "alert", ...rest, children: children ?? ctx.error.message });
|
|
3506
|
-
});
|
|
3507
|
-
var PurchaseGateRoot2 = Root5;
|
|
3508
|
-
var PurchaseGateAllowed2 = Allowed;
|
|
3509
|
-
var PurchaseGateBlocked2 = Blocked;
|
|
3510
|
-
var PurchaseGateLoading2 = Loading3;
|
|
3511
|
-
var PurchaseGateError2 = ErrorSlot3;
|
|
3512
|
-
var PurchaseGate = {
|
|
3513
|
-
Root: Root5,
|
|
3514
|
-
Allowed,
|
|
3515
|
-
Blocked,
|
|
3516
|
-
Loading: Loading3,
|
|
3517
|
-
Error: ErrorSlot3
|
|
3518
|
-
};
|
|
3519
|
-
function usePurchaseGate() {
|
|
3520
|
-
return useGateCtx("usePurchaseGate");
|
|
3521
|
-
}
|
|
3522
|
-
|
|
3523
3652
|
// src/primitives/BalanceBadge.tsx
|
|
3524
|
-
import { forwardRef as
|
|
3525
|
-
import { Fragment as
|
|
3526
|
-
var BalanceBadge =
|
|
3527
|
-
const solva =
|
|
3653
|
+
import { forwardRef as forwardRef6, useContext as useContext10 } from "react";
|
|
3654
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3655
|
+
var BalanceBadge = forwardRef6(function BalanceBadge2({ asChild, numberOnly, lowThreshold = 10, children, ...rest }, forwardedRef) {
|
|
3656
|
+
const solva = useContext10(SolvaPayContext);
|
|
3528
3657
|
if (!solva) throw new MissingProviderError("BalanceBadge");
|
|
3529
3658
|
const { credits, displayCurrency, creditsPerMinorUnit, displayExchangeRate, loading } = useBalance();
|
|
3530
3659
|
const copy = useCopy();
|
|
@@ -3541,10 +3670,10 @@ var BalanceBadge = forwardRef8(function BalanceBadge2({ asChild, numberOnly, low
|
|
|
3541
3670
|
if (asChild) {
|
|
3542
3671
|
return (
|
|
3543
3672
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3544
|
-
/* @__PURE__ */
|
|
3673
|
+
/* @__PURE__ */ jsx13(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx13(Fragment4, {}) })
|
|
3545
3674
|
);
|
|
3546
3675
|
}
|
|
3547
|
-
return /* @__PURE__ */
|
|
3676
|
+
return /* @__PURE__ */ jsx13("span", { ref: forwardedRef, ...commonProps });
|
|
3548
3677
|
}
|
|
3549
3678
|
if (credits == null) return null;
|
|
3550
3679
|
const formattedCredits = new Intl.NumberFormat(locale).format(credits);
|
|
@@ -3559,7 +3688,7 @@ var BalanceBadge = forwardRef8(function BalanceBadge2({ asChild, numberOnly, low
|
|
|
3559
3688
|
}).format(displayMajorUnits);
|
|
3560
3689
|
currencyEquivalent = interpolate(copy.balance.currencyEquivalent, { amount: formatted });
|
|
3561
3690
|
}
|
|
3562
|
-
const content = children ?? /* @__PURE__ */
|
|
3691
|
+
const content = children ?? /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
3563
3692
|
formattedCredits,
|
|
3564
3693
|
!numberOnly && copy.balance.credits,
|
|
3565
3694
|
!numberOnly && currencyEquivalent
|
|
@@ -3567,38 +3696,27 @@ var BalanceBadge = forwardRef8(function BalanceBadge2({ asChild, numberOnly, low
|
|
|
3567
3696
|
if (asChild) {
|
|
3568
3697
|
return (
|
|
3569
3698
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3570
|
-
/* @__PURE__ */
|
|
3699
|
+
/* @__PURE__ */ jsx13(Slot, { ref: forwardedRef, ...commonProps, children: content })
|
|
3571
3700
|
);
|
|
3572
3701
|
}
|
|
3573
|
-
return /* @__PURE__ */
|
|
3702
|
+
return /* @__PURE__ */ jsx13("span", { ref: forwardedRef, ...commonProps, children: content });
|
|
3574
3703
|
});
|
|
3575
3704
|
|
|
3576
3705
|
// src/primitives/PlanSelector.tsx
|
|
3577
3706
|
import {
|
|
3578
|
-
createContext as
|
|
3579
|
-
forwardRef as
|
|
3707
|
+
createContext as createContext8,
|
|
3708
|
+
forwardRef as forwardRef7,
|
|
3580
3709
|
useCallback as useCallback13,
|
|
3581
|
-
useContext as
|
|
3582
|
-
useMemo as
|
|
3710
|
+
useContext as useContext11,
|
|
3711
|
+
useMemo as useMemo10
|
|
3583
3712
|
} from "react";
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
function usePlanSelectorContext(part) {
|
|
3587
|
-
const ctx = useContext13(PlanSelectorContext);
|
|
3588
|
-
if (!ctx) {
|
|
3589
|
-
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Root>.`);
|
|
3590
|
-
}
|
|
3591
|
-
return ctx;
|
|
3592
|
-
}
|
|
3593
|
-
var CardContext = createContext9(null);
|
|
3594
|
-
function useCardContext(part) {
|
|
3595
|
-
const ctx = useContext13(CardContext);
|
|
3596
|
-
if (!ctx) {
|
|
3597
|
-
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Card>.`);
|
|
3598
|
-
}
|
|
3599
|
-
return ctx;
|
|
3600
|
-
}
|
|
3713
|
+
|
|
3714
|
+
// src/transport/list-plans.ts
|
|
3601
3715
|
async function defaultListPlans(productRef, config) {
|
|
3716
|
+
const transport = config?.transport;
|
|
3717
|
+
if (transport) {
|
|
3718
|
+
return transport.listPlans ? transport.listPlans(productRef) : plansCache.get(productRef)?.plans ?? [];
|
|
3719
|
+
}
|
|
3602
3720
|
const base = config?.api?.listPlans || "/api/list-plans";
|
|
3603
3721
|
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
3604
3722
|
const fetchFn = config?.fetch || fetch;
|
|
@@ -3612,7 +3730,26 @@ async function defaultListPlans(productRef, config) {
|
|
|
3612
3730
|
const data = await res.json();
|
|
3613
3731
|
return data.plans ?? [];
|
|
3614
3732
|
}
|
|
3615
|
-
|
|
3733
|
+
|
|
3734
|
+
// src/primitives/PlanSelector.tsx
|
|
3735
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
3736
|
+
var PlanSelectorContext = createContext8(null);
|
|
3737
|
+
function usePlanSelectorContext(part) {
|
|
3738
|
+
const ctx = useContext11(PlanSelectorContext);
|
|
3739
|
+
if (!ctx) {
|
|
3740
|
+
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Root>.`);
|
|
3741
|
+
}
|
|
3742
|
+
return ctx;
|
|
3743
|
+
}
|
|
3744
|
+
var CardContext = createContext8(null);
|
|
3745
|
+
function useCardContext(part) {
|
|
3746
|
+
const ctx = useContext11(CardContext);
|
|
3747
|
+
if (!ctx) {
|
|
3748
|
+
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Card>.`);
|
|
3749
|
+
}
|
|
3750
|
+
return ctx;
|
|
3751
|
+
}
|
|
3752
|
+
var Root5 = forwardRef7(function PlanSelectorRoot(props, forwardedRef) {
|
|
3616
3753
|
const {
|
|
3617
3754
|
productRef,
|
|
3618
3755
|
fetcher,
|
|
@@ -3626,16 +3763,16 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3626
3763
|
children,
|
|
3627
3764
|
...rest
|
|
3628
3765
|
} = props;
|
|
3629
|
-
const solva =
|
|
3766
|
+
const solva = useContext11(SolvaPayContext);
|
|
3630
3767
|
if (!solva) throw new MissingProviderError("PlanSelector");
|
|
3631
3768
|
if (!productRef) throw new MissingProductRefError("PlanSelector");
|
|
3632
3769
|
const { _config } = solva;
|
|
3633
3770
|
const { purchases } = usePurchase();
|
|
3634
|
-
const effectiveFetcher =
|
|
3771
|
+
const effectiveFetcher = useMemo10(
|
|
3635
3772
|
() => fetcher ?? ((ref) => defaultListPlans(ref, _config)),
|
|
3636
3773
|
[fetcher, _config]
|
|
3637
3774
|
);
|
|
3638
|
-
const { plans, selectedPlan, selectPlan, loading, error } = usePlans({
|
|
3775
|
+
const { plans, selectedPlan, selectPlan, setSelectedPlanIndex, loading, error } = usePlans({
|
|
3639
3776
|
productRef,
|
|
3640
3777
|
fetcher: effectiveFetcher,
|
|
3641
3778
|
filter,
|
|
@@ -3643,7 +3780,7 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3643
3780
|
autoSelectFirstPaid,
|
|
3644
3781
|
initialPlanRef
|
|
3645
3782
|
});
|
|
3646
|
-
const autoCurrentPlanRef =
|
|
3783
|
+
const autoCurrentPlanRef = useMemo10(() => {
|
|
3647
3784
|
const active = purchases.filter((p) => p.status === "active").sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0];
|
|
3648
3785
|
return active?.planSnapshot?.reference ?? null;
|
|
3649
3786
|
}, [purchases]);
|
|
@@ -3670,8 +3807,11 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3670
3807
|
},
|
|
3671
3808
|
[plans, selectPlan, onSelect]
|
|
3672
3809
|
);
|
|
3810
|
+
const clearSelection = useCallback13(() => {
|
|
3811
|
+
setSelectedPlanIndex(-1);
|
|
3812
|
+
}, [setSelectedPlanIndex]);
|
|
3673
3813
|
const selectedPlanRef = selectedPlan?.reference ?? null;
|
|
3674
|
-
const ctx =
|
|
3814
|
+
const ctx = useMemo10(
|
|
3675
3815
|
() => ({
|
|
3676
3816
|
plans,
|
|
3677
3817
|
loading,
|
|
@@ -3683,7 +3823,8 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3683
3823
|
isCurrent,
|
|
3684
3824
|
isFree,
|
|
3685
3825
|
isPopular,
|
|
3686
|
-
select
|
|
3826
|
+
select,
|
|
3827
|
+
clearSelection
|
|
3687
3828
|
}),
|
|
3688
3829
|
[
|
|
3689
3830
|
plans,
|
|
@@ -3696,10 +3837,11 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3696
3837
|
isCurrent,
|
|
3697
3838
|
isFree,
|
|
3698
3839
|
isPopular,
|
|
3699
|
-
select
|
|
3840
|
+
select,
|
|
3841
|
+
clearSelection
|
|
3700
3842
|
]
|
|
3701
3843
|
);
|
|
3702
|
-
return /* @__PURE__ */
|
|
3844
|
+
return /* @__PURE__ */ jsx14(
|
|
3703
3845
|
PlanSelectionProvider,
|
|
3704
3846
|
{
|
|
3705
3847
|
value: {
|
|
@@ -3712,19 +3854,19 @@ var Root6 = forwardRef9(function PlanSelectorRoot(props, forwardedRef) {
|
|
|
3712
3854
|
loading,
|
|
3713
3855
|
error
|
|
3714
3856
|
},
|
|
3715
|
-
children: /* @__PURE__ */
|
|
3857
|
+
children: /* @__PURE__ */ jsx14("div", { ref: forwardedRef, "data-solvapay-plan-selector": "", ...rest, children: /* @__PURE__ */ jsx14(PlanSelectorContext.Provider, { value: ctx, children }) })
|
|
3716
3858
|
}
|
|
3717
3859
|
);
|
|
3718
3860
|
});
|
|
3719
|
-
var Heading =
|
|
3861
|
+
var Heading = forwardRef7(function PlanSelectorHeading({ asChild, children, ...rest }, forwardedRef) {
|
|
3720
3862
|
usePlanSelectorContext("Heading");
|
|
3721
3863
|
const copy = useCopy();
|
|
3722
3864
|
const Comp = asChild ? Slot : "h3";
|
|
3723
|
-
return /* @__PURE__ */
|
|
3865
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-heading": "", ...rest, children: children ?? copy.planSelector.heading });
|
|
3724
3866
|
});
|
|
3725
|
-
var Grid =
|
|
3867
|
+
var Grid = forwardRef7(function PlanSelectorGrid({ children, ...rest }, forwardedRef) {
|
|
3726
3868
|
const ctx = usePlanSelectorContext("Grid");
|
|
3727
|
-
return /* @__PURE__ */
|
|
3869
|
+
return /* @__PURE__ */ jsx14("div", { ref: forwardedRef, "data-solvapay-plan-selector-grid": "", ...rest, children: ctx.plans.map((plan) => {
|
|
3728
3870
|
const isCurrent = ctx.isCurrent(plan.reference);
|
|
3729
3871
|
const isFree = ctx.isFree(plan.reference);
|
|
3730
3872
|
const isPopular = ctx.isPopular(plan.reference);
|
|
@@ -3740,10 +3882,10 @@ var Grid = forwardRef9(function PlanSelectorGrid({ children, ...rest }, forwarde
|
|
|
3740
3882
|
disabled,
|
|
3741
3883
|
select: () => ctx.select(plan.reference)
|
|
3742
3884
|
};
|
|
3743
|
-
return /* @__PURE__ */
|
|
3885
|
+
return /* @__PURE__ */ jsx14(CardContext.Provider, { value: cardCtx, children }, plan.reference);
|
|
3744
3886
|
}) });
|
|
3745
3887
|
});
|
|
3746
|
-
var Card =
|
|
3888
|
+
var Card = forwardRef7(function PlanSelectorCard({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
3747
3889
|
const card = useCardContext("Card");
|
|
3748
3890
|
const dataTrial = !!(card.plan.trialDays && card.plan.trialDays > 0);
|
|
3749
3891
|
const commonProps = {
|
|
@@ -3761,10 +3903,10 @@ var Card = forwardRef9(function PlanSelectorCard({ asChild, onClick, children, .
|
|
|
3761
3903
|
if (asChild) {
|
|
3762
3904
|
return (
|
|
3763
3905
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3764
|
-
/* @__PURE__ */
|
|
3906
|
+
/* @__PURE__ */ jsx14(Slot, { ref: forwardedRef, ...commonProps, children })
|
|
3765
3907
|
);
|
|
3766
3908
|
}
|
|
3767
|
-
return /* @__PURE__ */
|
|
3909
|
+
return /* @__PURE__ */ jsx14(
|
|
3768
3910
|
"button",
|
|
3769
3911
|
{
|
|
3770
3912
|
ref: forwardedRef,
|
|
@@ -3775,42 +3917,66 @@ var Card = forwardRef9(function PlanSelectorCard({ asChild, onClick, children, .
|
|
|
3775
3917
|
}
|
|
3776
3918
|
);
|
|
3777
3919
|
});
|
|
3778
|
-
var CardName =
|
|
3920
|
+
var CardName = forwardRef7(function PlanSelectorCardName({ asChild, children, ...rest }, forwardedRef) {
|
|
3779
3921
|
const card = useCardContext("CardName");
|
|
3780
|
-
|
|
3922
|
+
const fallback = card.plan.requiresPayment === false ? "Free" : card.plan.type === "usage-based" ? "Pay as you go" : card.plan.type === "recurring" ? "Plan" : null;
|
|
3923
|
+
const label = card.plan.name ?? fallback;
|
|
3924
|
+
if (!label && children == null) return null;
|
|
3781
3925
|
const Comp = asChild ? Slot : "span";
|
|
3782
|
-
return /* @__PURE__ */
|
|
3926
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ?? label });
|
|
3783
3927
|
});
|
|
3784
|
-
var CardPrice =
|
|
3928
|
+
var CardPrice = forwardRef7(function PlanSelectorCardPrice({ asChild, children, ...rest }, forwardedRef) {
|
|
3785
3929
|
const card = useCardContext("CardPrice");
|
|
3786
3930
|
const locale = useLocale();
|
|
3787
3931
|
const copy = useCopy();
|
|
3788
|
-
const formatted =
|
|
3932
|
+
const formatted = useMemo10(() => {
|
|
3789
3933
|
if (card.isFree) return copy.planSelector.freeBadge;
|
|
3934
|
+
if (card.plan.type === "usage-based") {
|
|
3935
|
+
const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
|
|
3936
|
+
const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
|
|
3937
|
+
const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
|
|
3938
|
+
locale,
|
|
3939
|
+
free: ""
|
|
3940
|
+
});
|
|
3941
|
+
return `${rate} / call`;
|
|
3942
|
+
}
|
|
3790
3943
|
return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
|
|
3791
3944
|
locale,
|
|
3792
3945
|
free: copy.interval.free
|
|
3793
3946
|
});
|
|
3794
3947
|
}, [
|
|
3795
3948
|
card.isFree,
|
|
3949
|
+
card.plan.type,
|
|
3796
3950
|
card.plan.price,
|
|
3797
3951
|
card.plan.currency,
|
|
3952
|
+
card.plan.creditsPerUnit,
|
|
3798
3953
|
locale,
|
|
3799
3954
|
copy.planSelector.freeBadge,
|
|
3800
3955
|
copy.interval.free
|
|
3801
3956
|
]);
|
|
3802
3957
|
const Comp = asChild ? Slot : "span";
|
|
3803
|
-
return /* @__PURE__ */
|
|
3958
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-price": "", ...rest, children: children ?? formatted });
|
|
3804
3959
|
});
|
|
3805
|
-
var CardInterval =
|
|
3960
|
+
var CardInterval = forwardRef7(function PlanSelectorCardInterval({ asChild, children, ...rest }, forwardedRef) {
|
|
3806
3961
|
const card = useCardContext("CardInterval");
|
|
3807
3962
|
const copy = useCopy();
|
|
3808
|
-
if (card.isFree ||
|
|
3809
|
-
const
|
|
3963
|
+
if (card.isFree || card.plan.type === "usage-based") return null;
|
|
3964
|
+
const rawInterval = card.plan.interval ?? normalizeBillingCycle(card.plan.billingCycle);
|
|
3965
|
+
if (!rawInterval) return null;
|
|
3966
|
+
const text = interpolate(copy.planSelector.perIntervalShort, { interval: rawInterval });
|
|
3810
3967
|
const Comp = asChild ? Slot : "span";
|
|
3811
|
-
return /* @__PURE__ */
|
|
3968
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-interval": "", ...rest, children: children ?? text });
|
|
3812
3969
|
});
|
|
3813
|
-
|
|
3970
|
+
function normalizeBillingCycle(cycle) {
|
|
3971
|
+
if (!cycle) return null;
|
|
3972
|
+
const lc = cycle.toLowerCase();
|
|
3973
|
+
if (lc === "monthly" || lc === "month") return "month";
|
|
3974
|
+
if (lc === "yearly" || lc === "annually" || lc === "annual" || lc === "year") return "year";
|
|
3975
|
+
if (lc === "weekly" || lc === "week") return "week";
|
|
3976
|
+
if (lc === "daily" || lc === "day") return "day";
|
|
3977
|
+
return cycle;
|
|
3978
|
+
}
|
|
3979
|
+
var CardBadge = forwardRef7(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
|
|
3814
3980
|
const card = useCardContext("CardBadge");
|
|
3815
3981
|
const copy = useCopy();
|
|
3816
3982
|
let variant = null;
|
|
@@ -3824,7 +3990,7 @@ var CardBadge = forwardRef9(function PlanSelectorCardBadge({ asChild, children,
|
|
|
3824
3990
|
}
|
|
3825
3991
|
if (!variant) return null;
|
|
3826
3992
|
const Comp = asChild ? Slot : "span";
|
|
3827
|
-
return /* @__PURE__ */
|
|
3993
|
+
return /* @__PURE__ */ jsx14(
|
|
3828
3994
|
Comp,
|
|
3829
3995
|
{
|
|
3830
3996
|
ref: forwardedRef,
|
|
@@ -3835,19 +4001,19 @@ var CardBadge = forwardRef9(function PlanSelectorCardBadge({ asChild, children,
|
|
|
3835
4001
|
}
|
|
3836
4002
|
);
|
|
3837
4003
|
});
|
|
3838
|
-
var
|
|
4004
|
+
var Loading3 = forwardRef7(function PlanSelectorLoading({ asChild, children, ...rest }, forwardedRef) {
|
|
3839
4005
|
const ctx = usePlanSelectorContext("Loading");
|
|
3840
4006
|
if (!ctx.loading || ctx.plans.length > 0) return null;
|
|
3841
4007
|
const Comp = asChild ? Slot : "div";
|
|
3842
|
-
return /* @__PURE__ */
|
|
4008
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-loading": "", ...rest, children });
|
|
3843
4009
|
});
|
|
3844
|
-
var
|
|
4010
|
+
var ErrorSlot3 = forwardRef7(function PlanSelectorError({ asChild, children, ...rest }, forwardedRef) {
|
|
3845
4011
|
const ctx = usePlanSelectorContext("Error");
|
|
3846
4012
|
if (!ctx.error) return null;
|
|
3847
4013
|
const Comp = asChild ? Slot : "div";
|
|
3848
|
-
return /* @__PURE__ */
|
|
4014
|
+
return /* @__PURE__ */ jsx14(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-error": "", role: "alert", ...rest, children: children ?? ctx.error.message });
|
|
3849
4015
|
});
|
|
3850
|
-
var PlanSelectorRoot2 =
|
|
4016
|
+
var PlanSelectorRoot2 = Root5;
|
|
3851
4017
|
var PlanSelectorHeading2 = Heading;
|
|
3852
4018
|
var PlanSelectorGrid2 = Grid;
|
|
3853
4019
|
var PlanSelectorCard2 = Card;
|
|
@@ -3855,10 +4021,10 @@ var PlanSelectorCardName2 = CardName;
|
|
|
3855
4021
|
var PlanSelectorCardPrice2 = CardPrice;
|
|
3856
4022
|
var PlanSelectorCardInterval2 = CardInterval;
|
|
3857
4023
|
var PlanSelectorCardBadge2 = CardBadge;
|
|
3858
|
-
var PlanSelectorLoading2 =
|
|
3859
|
-
var PlanSelectorError2 =
|
|
4024
|
+
var PlanSelectorLoading2 = Loading3;
|
|
4025
|
+
var PlanSelectorError2 = ErrorSlot3;
|
|
3860
4026
|
var PlanSelector = {
|
|
3861
|
-
Root:
|
|
4027
|
+
Root: Root5,
|
|
3862
4028
|
Heading,
|
|
3863
4029
|
Grid,
|
|
3864
4030
|
Card,
|
|
@@ -3866,306 +4032,21 @@ var PlanSelector = {
|
|
|
3866
4032
|
CardPrice,
|
|
3867
4033
|
CardInterval,
|
|
3868
4034
|
CardBadge,
|
|
3869
|
-
Loading:
|
|
3870
|
-
Error:
|
|
4035
|
+
Loading: Loading3,
|
|
4036
|
+
Error: ErrorSlot3
|
|
3871
4037
|
};
|
|
3872
4038
|
function usePlanSelector() {
|
|
3873
4039
|
return usePlanSelectorContext("usePlanSelector");
|
|
3874
4040
|
}
|
|
3875
4041
|
|
|
3876
|
-
// src/primitives/ActivationFlow.tsx
|
|
3877
|
-
import {
|
|
3878
|
-
createContext as createContext10,
|
|
3879
|
-
forwardRef as forwardRef10,
|
|
3880
|
-
useCallback as useCallback14,
|
|
3881
|
-
useContext as useContext14,
|
|
3882
|
-
useEffect as useEffect11,
|
|
3883
|
-
useMemo as useMemo12,
|
|
3884
|
-
useRef as useRef7,
|
|
3885
|
-
useState as useState13
|
|
3886
|
-
} from "react";
|
|
3887
|
-
import { Fragment as Fragment7, jsx as jsx18 } from "react/jsx-runtime";
|
|
3888
|
-
var ActivationFlowContext = createContext10(null);
|
|
3889
|
-
function useFlowCtx(part) {
|
|
3890
|
-
const ctx = useContext14(ActivationFlowContext);
|
|
3891
|
-
if (!ctx) {
|
|
3892
|
-
throw new Error(`ActivationFlow.${part} must be rendered inside <ActivationFlow.Root>.`);
|
|
3893
|
-
}
|
|
3894
|
-
return ctx;
|
|
3895
|
-
}
|
|
3896
|
-
var Root7 = forwardRef10(function ActivationFlowRoot({
|
|
3897
|
-
productRef,
|
|
3898
|
-
planRef: planRefProp,
|
|
3899
|
-
onSuccess,
|
|
3900
|
-
onError,
|
|
3901
|
-
retryDelayMs = 1500,
|
|
3902
|
-
retryBackoffMs = 2e3,
|
|
3903
|
-
asChild,
|
|
3904
|
-
children,
|
|
3905
|
-
...rest
|
|
3906
|
-
}, forwardedRef) {
|
|
3907
|
-
const solva = useContext14(SolvaPayContext);
|
|
3908
|
-
if (!solva) throw new MissingProviderError("ActivationFlow");
|
|
3909
|
-
if (!productRef) throw new MissingProductRefError("ActivationFlow");
|
|
3910
|
-
const planSelection = usePlanSelection();
|
|
3911
|
-
const resolvedPlanRef = planRefProp ?? planSelection?.selectedPlanRef ?? void 0;
|
|
3912
|
-
const { plan } = usePlan({ planRef: resolvedPlanRef, productRef });
|
|
3913
|
-
const { activate, state, error, result, reset } = useActivation();
|
|
3914
|
-
const currency = plan?.currency ?? "USD";
|
|
3915
|
-
const amountSelector = useTopupAmountSelector({ currency });
|
|
3916
|
-
const { adjustBalance, creditsPerMinorUnit, displayExchangeRate } = useBalance();
|
|
3917
|
-
const [step, setStep] = useState13("summary");
|
|
3918
|
-
const calledSuccessRef = useRef7(false);
|
|
3919
|
-
const retryTimeoutRef = useRef7(null);
|
|
3920
|
-
useEffect11(() => {
|
|
3921
|
-
if (state === "activated" && !calledSuccessRef.current) {
|
|
3922
|
-
calledSuccessRef.current = true;
|
|
3923
|
-
setStep("activated");
|
|
3924
|
-
if (result) {
|
|
3925
|
-
const activationResult = { kind: "activated", result };
|
|
3926
|
-
onSuccess?.(activationResult);
|
|
3927
|
-
}
|
|
3928
|
-
}
|
|
3929
|
-
}, [state, result, onSuccess]);
|
|
3930
|
-
useEffect11(() => {
|
|
3931
|
-
if (state === "topup_required" && (step === "summary" || step === "activating")) {
|
|
3932
|
-
setStep("selectAmount");
|
|
3933
|
-
}
|
|
3934
|
-
}, [state, step]);
|
|
3935
|
-
useEffect11(() => {
|
|
3936
|
-
if ((state === "error" || state === "payment_required") && error) {
|
|
3937
|
-
setStep("error");
|
|
3938
|
-
if (state === "error") onError?.(new Error(error));
|
|
3939
|
-
}
|
|
3940
|
-
}, [state, error, onError]);
|
|
3941
|
-
const handleActivate = useCallback14(async () => {
|
|
3942
|
-
if (!resolvedPlanRef) return;
|
|
3943
|
-
setStep("activating");
|
|
3944
|
-
await activate({ productRef, planRef: resolvedPlanRef });
|
|
3945
|
-
}, [activate, productRef, resolvedPlanRef]);
|
|
3946
|
-
const goToTopupPayment = useCallback14(() => {
|
|
3947
|
-
if (amountSelector.validate()) setStep("topupPayment");
|
|
3948
|
-
}, [amountSelector]);
|
|
3949
|
-
const backToSelectAmount = useCallback14(() => {
|
|
3950
|
-
setStep("selectAmount");
|
|
3951
|
-
}, []);
|
|
3952
|
-
const retryActivation = useCallback14(async () => {
|
|
3953
|
-
if (!resolvedPlanRef) return;
|
|
3954
|
-
setStep("retrying");
|
|
3955
|
-
const amountMinor = Math.round((amountSelector.resolvedAmount || 0) * 100);
|
|
3956
|
-
const rate = displayExchangeRate ?? 1;
|
|
3957
|
-
if (creditsPerMinorUnit) {
|
|
3958
|
-
adjustBalance(Math.floor(amountMinor / rate * creditsPerMinorUnit));
|
|
3959
|
-
}
|
|
3960
|
-
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
3961
|
-
await activate({ productRef, planRef: resolvedPlanRef });
|
|
3962
|
-
}, [
|
|
3963
|
-
resolvedPlanRef,
|
|
3964
|
-
amountSelector.resolvedAmount,
|
|
3965
|
-
displayExchangeRate,
|
|
3966
|
-
creditsPerMinorUnit,
|
|
3967
|
-
adjustBalance,
|
|
3968
|
-
retryDelayMs,
|
|
3969
|
-
activate,
|
|
3970
|
-
productRef
|
|
3971
|
-
]);
|
|
3972
|
-
const handleTopupSuccess = useCallback14(async () => {
|
|
3973
|
-
await retryActivation();
|
|
3974
|
-
}, [retryActivation]);
|
|
3975
|
-
useEffect11(() => {
|
|
3976
|
-
if (step !== "retrying") return;
|
|
3977
|
-
if (state !== "topup_required") return;
|
|
3978
|
-
if (!resolvedPlanRef) return;
|
|
3979
|
-
retryTimeoutRef.current = setTimeout(async () => {
|
|
3980
|
-
await activate({ productRef, planRef: resolvedPlanRef });
|
|
3981
|
-
}, retryBackoffMs);
|
|
3982
|
-
return () => {
|
|
3983
|
-
if (retryTimeoutRef.current) clearTimeout(retryTimeoutRef.current);
|
|
3984
|
-
};
|
|
3985
|
-
}, [step, state, resolvedPlanRef, retryBackoffMs, activate, productRef]);
|
|
3986
|
-
const resetFlow = useCallback14(() => {
|
|
3987
|
-
reset();
|
|
3988
|
-
calledSuccessRef.current = false;
|
|
3989
|
-
setStep("summary");
|
|
3990
|
-
}, [reset]);
|
|
3991
|
-
const amountCents = Math.round((amountSelector.resolvedAmount || 0) * 100);
|
|
3992
|
-
const ctx = useMemo12(
|
|
3993
|
-
() => ({
|
|
3994
|
-
step,
|
|
3995
|
-
plan,
|
|
3996
|
-
productRef,
|
|
3997
|
-
planRef: resolvedPlanRef ?? "",
|
|
3998
|
-
amountSelector,
|
|
3999
|
-
amountCents,
|
|
4000
|
-
currency,
|
|
4001
|
-
activate: handleActivate,
|
|
4002
|
-
reset: resetFlow,
|
|
4003
|
-
goToTopupPayment,
|
|
4004
|
-
backToSelectAmount,
|
|
4005
|
-
onTopupSuccess: handleTopupSuccess,
|
|
4006
|
-
error,
|
|
4007
|
-
onError
|
|
4008
|
-
}),
|
|
4009
|
-
[
|
|
4010
|
-
step,
|
|
4011
|
-
plan,
|
|
4012
|
-
productRef,
|
|
4013
|
-
resolvedPlanRef,
|
|
4014
|
-
amountSelector,
|
|
4015
|
-
amountCents,
|
|
4016
|
-
currency,
|
|
4017
|
-
handleActivate,
|
|
4018
|
-
resetFlow,
|
|
4019
|
-
goToTopupPayment,
|
|
4020
|
-
backToSelectAmount,
|
|
4021
|
-
handleTopupSuccess,
|
|
4022
|
-
error,
|
|
4023
|
-
onError
|
|
4024
|
-
]
|
|
4025
|
-
);
|
|
4026
|
-
const Comp = asChild ? Slot : "div";
|
|
4027
|
-
return /* @__PURE__ */ jsx18(ActivationFlowContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx18(
|
|
4028
|
-
Comp,
|
|
4029
|
-
{
|
|
4030
|
-
ref: forwardedRef,
|
|
4031
|
-
"data-solvapay-activation-flow": "",
|
|
4032
|
-
"data-state": step,
|
|
4033
|
-
...rest,
|
|
4034
|
-
children
|
|
4035
|
-
}
|
|
4036
|
-
) });
|
|
4037
|
-
});
|
|
4038
|
-
function matchStep(step, allowed) {
|
|
4039
|
-
return allowed.includes(step);
|
|
4040
|
-
}
|
|
4041
|
-
var Summary2 = forwardRef10(function ActivationFlowSummary({ asChild, children, ...rest }, forwardedRef) {
|
|
4042
|
-
const ctx = useFlowCtx("Summary");
|
|
4043
|
-
if (!matchStep(ctx.step, ["summary", "activating"])) return null;
|
|
4044
|
-
const Comp = asChild ? Slot : "div";
|
|
4045
|
-
return /* @__PURE__ */ jsx18(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-summary": "", ...rest, children });
|
|
4046
|
-
});
|
|
4047
|
-
var ActivateButton = forwardRef10(
|
|
4048
|
-
function ActivationFlowActivateButton({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
4049
|
-
const ctx = useFlowCtx("ActivateButton");
|
|
4050
|
-
const copy = useCopy();
|
|
4051
|
-
if (!matchStep(ctx.step, ["summary", "activating"])) return null;
|
|
4052
|
-
const isActivating = ctx.step === "activating";
|
|
4053
|
-
const disabled = isActivating;
|
|
4054
|
-
const commonProps = {
|
|
4055
|
-
"data-solvapay-activation-flow-activate": "",
|
|
4056
|
-
"data-state": isActivating ? "activating" : "idle",
|
|
4057
|
-
"aria-busy": isActivating,
|
|
4058
|
-
disabled,
|
|
4059
|
-
onClick: composeEventHandlers(onClick, () => {
|
|
4060
|
-
void ctx.activate();
|
|
4061
|
-
}),
|
|
4062
|
-
...rest
|
|
4063
|
-
};
|
|
4064
|
-
const label = isActivating ? copy.activationFlow.activatingLabel : copy.activationFlow.activateButton;
|
|
4065
|
-
if (asChild) {
|
|
4066
|
-
return (
|
|
4067
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4068
|
-
/* @__PURE__ */ jsx18(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx18(Fragment7, { children: label }) })
|
|
4069
|
-
);
|
|
4070
|
-
}
|
|
4071
|
-
return /* @__PURE__ */ jsx18("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
|
|
4072
|
-
}
|
|
4073
|
-
);
|
|
4074
|
-
var AmountPickerMount = ({ children }) => {
|
|
4075
|
-
const ctx = useFlowCtx("AmountPicker");
|
|
4076
|
-
if (ctx.step !== "selectAmount") return null;
|
|
4077
|
-
return /* @__PURE__ */ jsx18("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ jsx18(AmountPicker.Root, { currency: ctx.currency, children }) });
|
|
4078
|
-
};
|
|
4079
|
-
var ContinueButton = forwardRef10(
|
|
4080
|
-
function ActivationFlowContinueButton({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
4081
|
-
const ctx = useFlowCtx("ContinueButton");
|
|
4082
|
-
const copy = useCopy();
|
|
4083
|
-
if (ctx.step !== "selectAmount") return null;
|
|
4084
|
-
const disabled = !ctx.amountSelector.resolvedAmount;
|
|
4085
|
-
const commonProps = {
|
|
4086
|
-
"data-solvapay-activation-flow-continue": "",
|
|
4087
|
-
"data-state": disabled ? "disabled" : "idle",
|
|
4088
|
-
disabled,
|
|
4089
|
-
"aria-disabled": disabled || void 0,
|
|
4090
|
-
onClick: composeEventHandlers(onClick, () => {
|
|
4091
|
-
ctx.goToTopupPayment();
|
|
4092
|
-
}),
|
|
4093
|
-
...rest
|
|
4094
|
-
};
|
|
4095
|
-
if (asChild) {
|
|
4096
|
-
return (
|
|
4097
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4098
|
-
/* @__PURE__ */ jsx18(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx18(Fragment7, { children: copy.activationFlow.continueToPayment }) })
|
|
4099
|
-
);
|
|
4100
|
-
}
|
|
4101
|
-
return /* @__PURE__ */ jsx18("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? copy.activationFlow.continueToPayment });
|
|
4102
|
-
}
|
|
4103
|
-
);
|
|
4104
|
-
var Retrying = forwardRef10(function ActivationFlowRetrying({ asChild, children, ...rest }, forwardedRef) {
|
|
4105
|
-
const ctx = useFlowCtx("Retrying");
|
|
4106
|
-
if (ctx.step !== "retrying") return null;
|
|
4107
|
-
const Comp = asChild ? Slot : "div";
|
|
4108
|
-
return /* @__PURE__ */ jsx18(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-retrying": "", ...rest, children });
|
|
4109
|
-
});
|
|
4110
|
-
var Activated = forwardRef10(function ActivationFlowActivated({ asChild, children, ...rest }, forwardedRef) {
|
|
4111
|
-
const ctx = useFlowCtx("Activated");
|
|
4112
|
-
if (ctx.step !== "activated") return null;
|
|
4113
|
-
const Comp = asChild ? Slot : "div";
|
|
4114
|
-
return /* @__PURE__ */ jsx18(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-activated": "", ...rest, children });
|
|
4115
|
-
});
|
|
4116
|
-
var Loading5 = forwardRef10(function ActivationFlowLoading({ asChild, children, ...rest }, forwardedRef) {
|
|
4117
|
-
const ctx = useFlowCtx("Loading");
|
|
4118
|
-
if (ctx.plan) return null;
|
|
4119
|
-
const Comp = asChild ? Slot : "div";
|
|
4120
|
-
return /* @__PURE__ */ jsx18(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-loading": "", ...rest, children });
|
|
4121
|
-
});
|
|
4122
|
-
var ErrorSlot5 = forwardRef10(function ActivationFlowError({ asChild, children, ...rest }, forwardedRef) {
|
|
4123
|
-
const ctx = useFlowCtx("Error");
|
|
4124
|
-
if (ctx.step !== "error") return null;
|
|
4125
|
-
const Comp = asChild ? Slot : "div";
|
|
4126
|
-
return /* @__PURE__ */ jsx18(
|
|
4127
|
-
Comp,
|
|
4128
|
-
{
|
|
4129
|
-
ref: forwardedRef,
|
|
4130
|
-
role: "alert",
|
|
4131
|
-
"data-solvapay-activation-flow-error": "",
|
|
4132
|
-
...rest,
|
|
4133
|
-
children: children ?? ctx.error
|
|
4134
|
-
}
|
|
4135
|
-
);
|
|
4136
|
-
});
|
|
4137
|
-
var ActivationFlowRoot2 = Root7;
|
|
4138
|
-
var ActivationFlowSummary2 = Summary2;
|
|
4139
|
-
var ActivationFlowActivateButton2 = ActivateButton;
|
|
4140
|
-
var ActivationFlowAmountPicker = AmountPickerMount;
|
|
4141
|
-
var ActivationFlowContinueButton2 = ContinueButton;
|
|
4142
|
-
var ActivationFlowRetrying2 = Retrying;
|
|
4143
|
-
var ActivationFlowActivated2 = Activated;
|
|
4144
|
-
var ActivationFlowLoading2 = Loading5;
|
|
4145
|
-
var ActivationFlowError2 = ErrorSlot5;
|
|
4146
|
-
var ActivationFlow = {
|
|
4147
|
-
Root: Root7,
|
|
4148
|
-
Summary: Summary2,
|
|
4149
|
-
ActivateButton,
|
|
4150
|
-
AmountPicker: AmountPickerMount,
|
|
4151
|
-
ContinueButton,
|
|
4152
|
-
Retrying,
|
|
4153
|
-
Activated,
|
|
4154
|
-
Loading: Loading5,
|
|
4155
|
-
Error: ErrorSlot5
|
|
4156
|
-
};
|
|
4157
|
-
function useActivationFlow() {
|
|
4158
|
-
return useFlowCtx("useActivationFlow");
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
4042
|
// src/hooks/usePurchaseActions.ts
|
|
4162
|
-
import { useState as
|
|
4043
|
+
import { useState as useState12, useCallback as useCallback14 } from "react";
|
|
4163
4044
|
function usePurchaseActions() {
|
|
4164
4045
|
const ctx = useSolvaPay();
|
|
4165
|
-
const [isCancelling, setIsCancelling] =
|
|
4166
|
-
const [isReactivating, setIsReactivating] =
|
|
4167
|
-
const [isActivating, setIsActivating] =
|
|
4168
|
-
const cancelRenewal =
|
|
4046
|
+
const [isCancelling, setIsCancelling] = useState12(false);
|
|
4047
|
+
const [isReactivating, setIsReactivating] = useState12(false);
|
|
4048
|
+
const [isActivating, setIsActivating] = useState12(false);
|
|
4049
|
+
const cancelRenewal = useCallback14(
|
|
4169
4050
|
async (params) => {
|
|
4170
4051
|
setIsCancelling(true);
|
|
4171
4052
|
try {
|
|
@@ -4176,7 +4057,7 @@ function usePurchaseActions() {
|
|
|
4176
4057
|
},
|
|
4177
4058
|
[ctx.cancelRenewal]
|
|
4178
4059
|
);
|
|
4179
|
-
const reactivateRenewal =
|
|
4060
|
+
const reactivateRenewal = useCallback14(
|
|
4180
4061
|
async (params) => {
|
|
4181
4062
|
setIsReactivating(true);
|
|
4182
4063
|
try {
|
|
@@ -4187,7 +4068,7 @@ function usePurchaseActions() {
|
|
|
4187
4068
|
},
|
|
4188
4069
|
[ctx.reactivateRenewal]
|
|
4189
4070
|
);
|
|
4190
|
-
const activatePlan =
|
|
4071
|
+
const activatePlan = useCallback14(
|
|
4191
4072
|
async (params) => {
|
|
4192
4073
|
setIsActivating(true);
|
|
4193
4074
|
try {
|
|
@@ -4209,8 +4090,8 @@ function usePurchaseActions() {
|
|
|
4209
4090
|
}
|
|
4210
4091
|
|
|
4211
4092
|
// src/primitives/CancelPlanButton.tsx
|
|
4212
|
-
import { forwardRef as
|
|
4213
|
-
import { Fragment as
|
|
4093
|
+
import { forwardRef as forwardRef8, useCallback as useCallback15, useContext as useContext12 } from "react";
|
|
4094
|
+
import { Fragment as Fragment5, jsx as jsx15 } from "react/jsx-runtime";
|
|
4214
4095
|
function resolveConfirmText(confirm, purchase, defaults) {
|
|
4215
4096
|
if (confirm === false) return null;
|
|
4216
4097
|
if (typeof confirm === "string") return confirm;
|
|
@@ -4218,7 +4099,7 @@ function resolveConfirmText(confirm, purchase, defaults) {
|
|
|
4218
4099
|
if (planType === "usage-based") return defaults.usageBased;
|
|
4219
4100
|
return defaults.recurring;
|
|
4220
4101
|
}
|
|
4221
|
-
var CancelPlanButton =
|
|
4102
|
+
var CancelPlanButton = forwardRef8(
|
|
4222
4103
|
function CancelPlanButton2({
|
|
4223
4104
|
asChild,
|
|
4224
4105
|
purchaseRef,
|
|
@@ -4230,14 +4111,14 @@ var CancelPlanButton = forwardRef11(
|
|
|
4230
4111
|
children,
|
|
4231
4112
|
...rest
|
|
4232
4113
|
}, forwardedRef) {
|
|
4233
|
-
const solva =
|
|
4114
|
+
const solva = useContext12(SolvaPayContext);
|
|
4234
4115
|
if (!solva) throw new MissingProviderError("CancelPlanButton");
|
|
4235
4116
|
const copy = useCopy();
|
|
4236
4117
|
const { activePurchase } = usePurchase();
|
|
4237
4118
|
const { cancelRenewal, isCancelling } = usePurchaseActions();
|
|
4238
4119
|
const effectivePurchase = purchaseRef ? activePurchase && activePurchase.reference === purchaseRef ? activePurchase : { reference: purchaseRef } : activePurchase;
|
|
4239
4120
|
const effectiveRef = purchaseRef ?? effectivePurchase?.reference;
|
|
4240
|
-
const cancel =
|
|
4121
|
+
const cancel = useCallback15(async () => {
|
|
4241
4122
|
if (!effectiveRef) return;
|
|
4242
4123
|
const confirmText = resolveConfirmText(confirm, effectivePurchase, {
|
|
4243
4124
|
recurring: copy.cancelPlan.confirmRecurring,
|
|
@@ -4270,24 +4151,24 @@ var CancelPlanButton = forwardRef11(
|
|
|
4270
4151
|
if (asChild) {
|
|
4271
4152
|
return (
|
|
4272
4153
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4273
|
-
/* @__PURE__ */
|
|
4154
|
+
/* @__PURE__ */ jsx15(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx15(Fragment5, { children: label }) })
|
|
4274
4155
|
);
|
|
4275
4156
|
}
|
|
4276
|
-
return /* @__PURE__ */
|
|
4157
|
+
return /* @__PURE__ */ jsx15("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
|
|
4277
4158
|
}
|
|
4278
4159
|
);
|
|
4279
4160
|
|
|
4280
4161
|
// src/hooks/usePurchaseStatus.ts
|
|
4281
|
-
import { useMemo as
|
|
4162
|
+
import { useMemo as useMemo11, useCallback as useCallback16 } from "react";
|
|
4282
4163
|
function usePurchaseStatus() {
|
|
4283
4164
|
const { purchases } = usePurchase();
|
|
4284
4165
|
const locale = useLocale();
|
|
4285
|
-
const isPaidPurchase2 =
|
|
4166
|
+
const isPaidPurchase2 = useCallback16((p) => {
|
|
4286
4167
|
return (p.amount ?? 0) > 0;
|
|
4287
4168
|
}, []);
|
|
4288
|
-
const purchaseData =
|
|
4169
|
+
const purchaseData = useMemo11(() => {
|
|
4289
4170
|
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
4290
|
-
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
4171
|
+
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p) && isPlanPurchase(p);
|
|
4291
4172
|
});
|
|
4292
4173
|
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
4293
4174
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
@@ -4298,7 +4179,7 @@ function usePurchaseStatus() {
|
|
|
4298
4179
|
shouldShowCancelledNotice
|
|
4299
4180
|
};
|
|
4300
4181
|
}, [purchases, isPaidPurchase2]);
|
|
4301
|
-
const formatDate =
|
|
4182
|
+
const formatDate = useCallback16(
|
|
4302
4183
|
(dateString) => {
|
|
4303
4184
|
if (!dateString) return null;
|
|
4304
4185
|
return new Date(dateString).toLocaleDateString(locale || "en-US", {
|
|
@@ -4309,7 +4190,7 @@ function usePurchaseStatus() {
|
|
|
4309
4190
|
},
|
|
4310
4191
|
[locale]
|
|
4311
4192
|
);
|
|
4312
|
-
const getDaysUntilExpiration =
|
|
4193
|
+
const getDaysUntilExpiration = useCallback16((endDate) => {
|
|
4313
4194
|
if (!endDate) return null;
|
|
4314
4195
|
const now = /* @__PURE__ */ new Date();
|
|
4315
4196
|
const expiration = new Date(endDate);
|
|
@@ -4327,16 +4208,16 @@ function usePurchaseStatus() {
|
|
|
4327
4208
|
|
|
4328
4209
|
// src/primitives/CancelledPlanNotice.tsx
|
|
4329
4210
|
import {
|
|
4330
|
-
createContext as
|
|
4331
|
-
forwardRef as
|
|
4332
|
-
useCallback as
|
|
4333
|
-
useContext as
|
|
4334
|
-
useMemo as
|
|
4211
|
+
createContext as createContext9,
|
|
4212
|
+
forwardRef as forwardRef9,
|
|
4213
|
+
useCallback as useCallback17,
|
|
4214
|
+
useContext as useContext13,
|
|
4215
|
+
useMemo as useMemo12
|
|
4335
4216
|
} from "react";
|
|
4336
|
-
import { Fragment as
|
|
4337
|
-
var CancelledPlanNoticeContext =
|
|
4217
|
+
import { Fragment as Fragment6, jsx as jsx16 } from "react/jsx-runtime";
|
|
4218
|
+
var CancelledPlanNoticeContext = createContext9(null);
|
|
4338
4219
|
function useNoticeCtx(part) {
|
|
4339
|
-
const ctx =
|
|
4220
|
+
const ctx = useContext13(CancelledPlanNoticeContext);
|
|
4340
4221
|
if (!ctx) {
|
|
4341
4222
|
throw new Error(
|
|
4342
4223
|
`CancelledPlanNotice.${part} must be rendered inside <CancelledPlanNotice.Root>.`
|
|
@@ -4344,8 +4225,8 @@ function useNoticeCtx(part) {
|
|
|
4344
4225
|
}
|
|
4345
4226
|
return ctx;
|
|
4346
4227
|
}
|
|
4347
|
-
var
|
|
4348
|
-
const solva =
|
|
4228
|
+
var Root6 = forwardRef9(function CancelledPlanNoticeRoot({ onReactivated, onError, asChild, children, ...rest }, forwardedRef) {
|
|
4229
|
+
const solva = useContext13(SolvaPayContext);
|
|
4349
4230
|
if (!solva) throw new MissingProviderError("CancelledPlanNotice");
|
|
4350
4231
|
const {
|
|
4351
4232
|
cancelledPurchase,
|
|
@@ -4354,7 +4235,7 @@ var Root8 = forwardRef12(function CancelledPlanNoticeRoot({ onReactivated, onErr
|
|
|
4354
4235
|
getDaysUntilExpiration
|
|
4355
4236
|
} = usePurchaseStatus();
|
|
4356
4237
|
const { reactivateRenewal, isReactivating } = usePurchaseActions();
|
|
4357
|
-
const reactivate =
|
|
4238
|
+
const reactivate = useCallback17(async () => {
|
|
4358
4239
|
if (!cancelledPurchase) return;
|
|
4359
4240
|
try {
|
|
4360
4241
|
await reactivateRenewal({ purchaseRef: cancelledPurchase.reference });
|
|
@@ -4363,11 +4244,11 @@ var Root8 = forwardRef12(function CancelledPlanNoticeRoot({ onReactivated, onErr
|
|
|
4363
4244
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
4364
4245
|
}
|
|
4365
4246
|
}, [cancelledPurchase, reactivateRenewal, onReactivated, onError]);
|
|
4366
|
-
const daysRemaining =
|
|
4247
|
+
const daysRemaining = useMemo12(
|
|
4367
4248
|
() => cancelledPurchase ? getDaysUntilExpiration(cancelledPurchase.endDate) : null,
|
|
4368
4249
|
[cancelledPurchase, getDaysUntilExpiration]
|
|
4369
4250
|
);
|
|
4370
|
-
const ctx =
|
|
4251
|
+
const ctx = useMemo12(() => {
|
|
4371
4252
|
if (!cancelledPurchase) return null;
|
|
4372
4253
|
const state = daysRemaining != null && daysRemaining > 0 ? "active" : "expired";
|
|
4373
4254
|
return {
|
|
@@ -4382,7 +4263,7 @@ var Root8 = forwardRef12(function CancelledPlanNoticeRoot({ onReactivated, onErr
|
|
|
4382
4263
|
}, [cancelledPurchase, daysRemaining, formatDate, reactivate, isReactivating]);
|
|
4383
4264
|
if (!shouldShowCancelledNotice || !ctx) return null;
|
|
4384
4265
|
const Comp = asChild ? Slot : "div";
|
|
4385
|
-
return /* @__PURE__ */
|
|
4266
|
+
return /* @__PURE__ */ jsx16(CancelledPlanNoticeContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx16(
|
|
4386
4267
|
Comp,
|
|
4387
4268
|
{
|
|
4388
4269
|
ref: forwardedRef,
|
|
@@ -4394,61 +4275,61 @@ var Root8 = forwardRef12(function CancelledPlanNoticeRoot({ onReactivated, onErr
|
|
|
4394
4275
|
}
|
|
4395
4276
|
) });
|
|
4396
4277
|
});
|
|
4397
|
-
var Heading2 =
|
|
4278
|
+
var Heading2 = forwardRef9(function CancelledPlanNoticeHeading({ asChild, children, ...rest }, forwardedRef) {
|
|
4398
4279
|
useNoticeCtx("Heading");
|
|
4399
4280
|
const copy = useCopy();
|
|
4400
4281
|
const Comp = asChild ? Slot : "p";
|
|
4401
|
-
return /* @__PURE__ */
|
|
4282
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-heading": "", ...rest, children: children ?? copy.cancelledNotice.heading });
|
|
4402
4283
|
});
|
|
4403
|
-
var Expires =
|
|
4284
|
+
var Expires = forwardRef9(function CancelledPlanNoticeExpires({ asChild, children, ...rest }, forwardedRef) {
|
|
4404
4285
|
const ctx = useNoticeCtx("Expires");
|
|
4405
4286
|
const copy = useCopy();
|
|
4406
4287
|
const date = ctx.formatDate(ctx.purchase.endDate);
|
|
4407
4288
|
if (!ctx.purchase.endDate) {
|
|
4408
4289
|
const Comp2 = asChild ? Slot : "p";
|
|
4409
|
-
return /* @__PURE__ */
|
|
4290
|
+
return /* @__PURE__ */ jsx16(Comp2, { ref: forwardedRef, "data-solvapay-cancelled-notice-expires": "", ...rest, children: children ?? copy.cancelledNotice.accessEnded });
|
|
4410
4291
|
}
|
|
4411
4292
|
const Comp = asChild ? Slot : "p";
|
|
4412
|
-
return /* @__PURE__ */
|
|
4293
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-expires": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.expiresLabel, { date: date ?? "" }) });
|
|
4413
4294
|
});
|
|
4414
|
-
var DaysRemaining =
|
|
4295
|
+
var DaysRemaining = forwardRef9(
|
|
4415
4296
|
function CancelledPlanNoticeDaysRemaining({ asChild, children, ...rest }, forwardedRef) {
|
|
4416
4297
|
const ctx = useNoticeCtx("DaysRemaining");
|
|
4417
4298
|
const copy = useCopy();
|
|
4418
4299
|
if (ctx.daysRemaining == null || ctx.daysRemaining <= 0) return null;
|
|
4419
4300
|
const template = ctx.daysRemaining === 1 ? copy.cancelledNotice.dayRemaining : copy.cancelledNotice.daysRemaining;
|
|
4420
4301
|
const Comp = asChild ? Slot : "p";
|
|
4421
|
-
return /* @__PURE__ */
|
|
4302
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-days-remaining": "", ...rest, children: children ?? interpolate(template, { days: ctx.daysRemaining }) });
|
|
4422
4303
|
}
|
|
4423
4304
|
);
|
|
4424
|
-
var AccessUntil =
|
|
4305
|
+
var AccessUntil = forwardRef9(
|
|
4425
4306
|
function CancelledPlanNoticeAccessUntil({ asChild, children, ...rest }, forwardedRef) {
|
|
4426
4307
|
const ctx = useNoticeCtx("AccessUntil");
|
|
4427
4308
|
const copy = useCopy();
|
|
4428
4309
|
if (!ctx.purchase.endDate) return null;
|
|
4429
4310
|
const Comp = asChild ? Slot : "p";
|
|
4430
|
-
return /* @__PURE__ */
|
|
4311
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-access-until": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.accessUntil, {
|
|
4431
4312
|
product: ctx.purchase.productName
|
|
4432
4313
|
}) });
|
|
4433
4314
|
}
|
|
4434
4315
|
);
|
|
4435
|
-
var CancelledOn =
|
|
4316
|
+
var CancelledOn = forwardRef9(
|
|
4436
4317
|
function CancelledPlanNoticeCancelledOn({ asChild, children, ...rest }, forwardedRef) {
|
|
4437
4318
|
const ctx = useNoticeCtx("CancelledOn");
|
|
4438
4319
|
const copy = useCopy();
|
|
4439
4320
|
if (!ctx.purchase.cancelledAt) return null;
|
|
4440
4321
|
const date = ctx.formatDate(ctx.purchase.cancelledAt);
|
|
4441
4322
|
const Comp = asChild ? Slot : "p";
|
|
4442
|
-
return /* @__PURE__ */
|
|
4323
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-cancelled-on": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.cancelledOn, { date: date ?? "" }) });
|
|
4443
4324
|
}
|
|
4444
4325
|
);
|
|
4445
|
-
var Reason =
|
|
4326
|
+
var Reason = forwardRef9(function CancelledPlanNoticeReason({ asChild, children, ...rest }, forwardedRef) {
|
|
4446
4327
|
const ctx = useNoticeCtx("Reason");
|
|
4447
4328
|
if (!ctx.hasReason) return null;
|
|
4448
4329
|
const Comp = asChild ? Slot : "span";
|
|
4449
|
-
return /* @__PURE__ */
|
|
4330
|
+
return /* @__PURE__ */ jsx16(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-reason": "", ...rest, children: children ?? ctx.purchase.cancellationReason });
|
|
4450
4331
|
});
|
|
4451
|
-
var ReactivateButton =
|
|
4332
|
+
var ReactivateButton = forwardRef9(
|
|
4452
4333
|
function CancelledPlanNoticeReactivateButton({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
4453
4334
|
const ctx = useNoticeCtx("ReactivateButton");
|
|
4454
4335
|
const copy = useCopy();
|
|
@@ -4468,13 +4349,13 @@ var ReactivateButton = forwardRef12(
|
|
|
4468
4349
|
if (asChild) {
|
|
4469
4350
|
return (
|
|
4470
4351
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4471
|
-
/* @__PURE__ */
|
|
4352
|
+
/* @__PURE__ */ jsx16(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx16(Fragment6, { children: label }) })
|
|
4472
4353
|
);
|
|
4473
4354
|
}
|
|
4474
|
-
return /* @__PURE__ */
|
|
4355
|
+
return /* @__PURE__ */ jsx16("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
|
|
4475
4356
|
}
|
|
4476
4357
|
);
|
|
4477
|
-
var CancelledPlanNoticeRoot2 =
|
|
4358
|
+
var CancelledPlanNoticeRoot2 = Root6;
|
|
4478
4359
|
var CancelledPlanNoticeHeading2 = Heading2;
|
|
4479
4360
|
var CancelledPlanNoticeExpires2 = Expires;
|
|
4480
4361
|
var CancelledPlanNoticeDaysRemaining2 = DaysRemaining;
|
|
@@ -4483,7 +4364,7 @@ var CancelledPlanNoticeCancelledOn2 = CancelledOn;
|
|
|
4483
4364
|
var CancelledPlanNoticeReason2 = Reason;
|
|
4484
4365
|
var CancelledPlanNoticeReactivateButton2 = ReactivateButton;
|
|
4485
4366
|
var CancelledPlanNotice = {
|
|
4486
|
-
Root:
|
|
4367
|
+
Root: Root6,
|
|
4487
4368
|
Heading: Heading2,
|
|
4488
4369
|
Expires,
|
|
4489
4370
|
DaysRemaining,
|
|
@@ -4496,122 +4377,320 @@ function useCancelledPlanNotice() {
|
|
|
4496
4377
|
return useNoticeCtx("useCancelledPlanNotice");
|
|
4497
4378
|
}
|
|
4498
4379
|
|
|
4499
|
-
// src/
|
|
4500
|
-
import {
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
function
|
|
4509
|
-
|
|
4380
|
+
// src/hooks/useTransport.ts
|
|
4381
|
+
import { useMemo as useMemo13 } from "react";
|
|
4382
|
+
function useTransport() {
|
|
4383
|
+
const { _config } = useSolvaPay();
|
|
4384
|
+
return useMemo13(() => _config?.transport ?? createHttpTransport(_config), [_config]);
|
|
4385
|
+
}
|
|
4386
|
+
|
|
4387
|
+
// src/hooks/useUsage.ts
|
|
4388
|
+
import { useCallback as useCallback18, useEffect as useEffect10, useMemo as useMemo14, useState as useState13 } from "react";
|
|
4389
|
+
function deriveUsage(purchase) {
|
|
4390
|
+
if (!purchase) return null;
|
|
4391
|
+
const snap = purchase.planSnapshot;
|
|
4392
|
+
const usage = purchase.usage;
|
|
4393
|
+
const meterRef = snap?.meterRef ?? null;
|
|
4394
|
+
const total = typeof snap?.limit === "number" ? snap.limit : null;
|
|
4395
|
+
if (meterRef === null && !usage) return null;
|
|
4396
|
+
const used = typeof usage?.used === "number" ? usage.used : 0;
|
|
4397
|
+
const remaining = total !== null ? Math.max(0, total - used) : null;
|
|
4398
|
+
const percentUsed = total !== null && total > 0 ? Math.min(100, Math.round(used / total * 1e4) / 100) : null;
|
|
4399
|
+
return {
|
|
4400
|
+
meterRef,
|
|
4401
|
+
total,
|
|
4402
|
+
used,
|
|
4403
|
+
remaining,
|
|
4404
|
+
percentUsed,
|
|
4405
|
+
...usage?.periodStart ? { periodStart: usage.periodStart } : {},
|
|
4406
|
+
...usage?.periodEnd ? { periodEnd: usage.periodEnd } : {},
|
|
4407
|
+
...purchase.reference ? { purchaseRef: purchase.reference } : {}
|
|
4408
|
+
};
|
|
4409
|
+
}
|
|
4410
|
+
function useUsage() {
|
|
4411
|
+
const { activePurchase, refetch: refetchPurchase, loading: purchaseLoading } = usePurchase();
|
|
4412
|
+
const transport = useTransport();
|
|
4413
|
+
const [override, setOverride] = useState13(null);
|
|
4414
|
+
const [error, setError] = useState13(null);
|
|
4415
|
+
const [transportLoading, setTransportLoading] = useState13(false);
|
|
4416
|
+
const derived = useMemo14(() => deriveUsage(activePurchase ?? null), [activePurchase]);
|
|
4417
|
+
const activePurchaseRef = activePurchase?.reference ?? null;
|
|
4418
|
+
useEffect10(() => {
|
|
4419
|
+
setOverride(null);
|
|
4420
|
+
}, [activePurchaseRef]);
|
|
4421
|
+
const usage = override ?? derived;
|
|
4422
|
+
const refetch = useCallback18(async () => {
|
|
4423
|
+
setError(null);
|
|
4424
|
+
if (typeof transport.getUsage === "function") {
|
|
4425
|
+
setTransportLoading(true);
|
|
4426
|
+
try {
|
|
4427
|
+
const next = await transport.getUsage();
|
|
4428
|
+
setOverride(next);
|
|
4429
|
+
} catch (err) {
|
|
4430
|
+
setError(err instanceof Error ? err : new Error("Failed to load usage"));
|
|
4431
|
+
} finally {
|
|
4432
|
+
setTransportLoading(false);
|
|
4433
|
+
}
|
|
4434
|
+
return;
|
|
4435
|
+
}
|
|
4436
|
+
try {
|
|
4437
|
+
await refetchPurchase();
|
|
4438
|
+
} catch (err) {
|
|
4439
|
+
setError(err instanceof Error ? err : new Error("Failed to refetch purchase"));
|
|
4440
|
+
}
|
|
4441
|
+
}, [transport, refetchPurchase]);
|
|
4442
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
4443
|
+
const isApproachingLimit = percentUsed !== null && percentUsed >= 80 && percentUsed < 100;
|
|
4444
|
+
const isAtLimit = percentUsed !== null && percentUsed >= 100;
|
|
4445
|
+
const isUnlimited = usage !== null && usage.total === null;
|
|
4446
|
+
return {
|
|
4447
|
+
usage,
|
|
4448
|
+
loading: purchaseLoading || transportLoading,
|
|
4449
|
+
error,
|
|
4450
|
+
refetch,
|
|
4451
|
+
percentUsed,
|
|
4452
|
+
isApproachingLimit,
|
|
4453
|
+
isAtLimit,
|
|
4454
|
+
isUnlimited,
|
|
4455
|
+
meterRef: usage?.meterRef ?? null
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
// src/primitives/UsageMeter.tsx
|
|
4460
|
+
import { createContext as createContext10, forwardRef as forwardRef10, useContext as useContext14, useMemo as useMemo15 } from "react";
|
|
4461
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
4462
|
+
var UsageMeterContext = createContext10(null);
|
|
4463
|
+
function useUsageMeterCtx(part) {
|
|
4464
|
+
const ctx = useContext14(UsageMeterContext);
|
|
4510
4465
|
if (!ctx) {
|
|
4511
|
-
throw new Error(`
|
|
4466
|
+
throw new Error(`UsageMeter.${part} must be rendered inside <UsageMeter.Root>.`);
|
|
4512
4467
|
}
|
|
4513
4468
|
return ctx;
|
|
4514
4469
|
}
|
|
4515
|
-
var
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
topupCurrency = "usd",
|
|
4470
|
+
var Root7 = forwardRef10(function UsageMeterRoot({
|
|
4471
|
+
warningAt = 75,
|
|
4472
|
+
criticalAt = 90,
|
|
4473
|
+
classNames,
|
|
4520
4474
|
asChild,
|
|
4475
|
+
usageOverride,
|
|
4521
4476
|
children,
|
|
4477
|
+
className,
|
|
4522
4478
|
...rest
|
|
4523
4479
|
}, forwardedRef) {
|
|
4524
|
-
const
|
|
4525
|
-
|
|
4526
|
-
const
|
|
4527
|
-
const
|
|
4528
|
-
const
|
|
4529
|
-
const state = loading && credits == null ? "loading" : hasCredits ? "allowed" : "blocked";
|
|
4480
|
+
const hookResult = useUsage();
|
|
4481
|
+
const usage = usageOverride !== void 0 ? usageOverride : hookResult.usage;
|
|
4482
|
+
const loading = hookResult.loading && !usage;
|
|
4483
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
4484
|
+
const state = loading ? "loading" : percentUsed === null ? "safe" : percentUsed >= criticalAt ? "critical" : percentUsed >= warningAt ? "warning" : "safe";
|
|
4530
4485
|
const ctx = useMemo15(
|
|
4531
4486
|
() => ({
|
|
4532
|
-
|
|
4487
|
+
usage,
|
|
4533
4488
|
loading,
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4489
|
+
error: hookResult.error,
|
|
4490
|
+
percentUsed,
|
|
4491
|
+
isApproachingLimit: hookResult.isApproachingLimit,
|
|
4492
|
+
isAtLimit: hookResult.isAtLimit,
|
|
4493
|
+
isUnlimited: hookResult.isUnlimited,
|
|
4494
|
+
state,
|
|
4495
|
+
warningAt,
|
|
4496
|
+
criticalAt
|
|
4539
4497
|
}),
|
|
4540
|
-
[
|
|
4498
|
+
[
|
|
4499
|
+
usage,
|
|
4500
|
+
loading,
|
|
4501
|
+
hookResult.error,
|
|
4502
|
+
percentUsed,
|
|
4503
|
+
hookResult.isApproachingLimit,
|
|
4504
|
+
hookResult.isAtLimit,
|
|
4505
|
+
hookResult.isUnlimited,
|
|
4506
|
+
state,
|
|
4507
|
+
warningAt,
|
|
4508
|
+
criticalAt
|
|
4509
|
+
]
|
|
4541
4510
|
);
|
|
4511
|
+
const rootClass = [classNames?.root ?? "solvapay-usage-meter", className].filter(Boolean).join(" ");
|
|
4542
4512
|
const Comp = asChild ? Slot : "div";
|
|
4543
|
-
return /* @__PURE__ */
|
|
4513
|
+
return /* @__PURE__ */ jsx17(UsageMeterContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx17(
|
|
4544
4514
|
Comp,
|
|
4545
4515
|
{
|
|
4546
4516
|
ref: forwardedRef,
|
|
4547
|
-
"data-solvapay-
|
|
4517
|
+
"data-solvapay-usage-meter": "",
|
|
4548
4518
|
"data-state": state,
|
|
4519
|
+
className: rootClass,
|
|
4549
4520
|
...rest,
|
|
4550
4521
|
children
|
|
4551
4522
|
}
|
|
4552
4523
|
) });
|
|
4553
4524
|
});
|
|
4554
|
-
var
|
|
4555
|
-
const ctx =
|
|
4556
|
-
const
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4525
|
+
var Bar = forwardRef10(function UsageMeterBar({ className, style, ...rest }, forwardedRef) {
|
|
4526
|
+
const ctx = useUsageMeterCtx("Bar");
|
|
4527
|
+
const width = ctx.percentUsed === null ? 0 : Math.max(0, Math.min(100, ctx.percentUsed));
|
|
4528
|
+
return /* @__PURE__ */ jsx17(
|
|
4529
|
+
"div",
|
|
4530
|
+
{
|
|
4531
|
+
ref: forwardedRef,
|
|
4532
|
+
"data-solvapay-usage-meter-bar": "",
|
|
4533
|
+
"data-state": ctx.state,
|
|
4534
|
+
role: "progressbar",
|
|
4535
|
+
"aria-valuenow": Math.round(width),
|
|
4536
|
+
"aria-valuemin": 0,
|
|
4537
|
+
"aria-valuemax": 100,
|
|
4538
|
+
className: className ?? "solvapay-usage-meter-bar",
|
|
4539
|
+
style: {
|
|
4540
|
+
// Expose as a CSS custom property so default styles can drive
|
|
4541
|
+
// `width: var(--solvapay-usage-meter-fill)` without inline styling.
|
|
4542
|
+
["--solvapay-usage-meter-fill"]: `${width}%`,
|
|
4543
|
+
...style
|
|
4544
|
+
},
|
|
4545
|
+
...rest
|
|
4546
|
+
}
|
|
4547
|
+
);
|
|
4560
4548
|
});
|
|
4561
|
-
var
|
|
4562
|
-
const ctx =
|
|
4549
|
+
var Label = forwardRef10(function UsageMeterLabel({ className, children, ...rest }, forwardedRef) {
|
|
4550
|
+
const ctx = useUsageMeterCtx("Label");
|
|
4563
4551
|
const copy = useCopy();
|
|
4564
|
-
if (ctx.
|
|
4565
|
-
|
|
4566
|
-
|
|
4552
|
+
if (!ctx.usage) return null;
|
|
4553
|
+
if (ctx.isUnlimited) {
|
|
4554
|
+
return /* @__PURE__ */ jsx17(
|
|
4555
|
+
"span",
|
|
4556
|
+
{
|
|
4557
|
+
ref: forwardedRef,
|
|
4558
|
+
"data-solvapay-usage-meter-label": "",
|
|
4559
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
4560
|
+
...rest,
|
|
4561
|
+
children: children ?? copy.usage.unlimitedLabel
|
|
4562
|
+
}
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
4565
|
+
const unit = ctx.usage.meterRef ?? "units";
|
|
4566
|
+
const label = ctx.usage.total !== null ? interpolate(copy.usage.usedLabel, {
|
|
4567
|
+
used: String(ctx.usage.used),
|
|
4568
|
+
total: String(ctx.usage.total),
|
|
4569
|
+
unit
|
|
4570
|
+
}) : interpolate(copy.usage.remainingLabel, {
|
|
4571
|
+
remaining: String(ctx.usage.remaining ?? 0),
|
|
4572
|
+
unit
|
|
4567
4573
|
});
|
|
4568
|
-
|
|
4569
|
-
|
|
4574
|
+
return /* @__PURE__ */ jsx17(
|
|
4575
|
+
"span",
|
|
4576
|
+
{
|
|
4577
|
+
ref: forwardedRef,
|
|
4578
|
+
"data-solvapay-usage-meter-label": "",
|
|
4579
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
4580
|
+
...rest,
|
|
4581
|
+
children: children ?? label
|
|
4582
|
+
}
|
|
4583
|
+
);
|
|
4570
4584
|
});
|
|
4571
|
-
var
|
|
4572
|
-
const ctx =
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4585
|
+
var Percentage = forwardRef10(function UsageMeterPercentage({ className, children, ...rest }, forwardedRef) {
|
|
4586
|
+
const ctx = useUsageMeterCtx("Percentage");
|
|
4587
|
+
const copy = useCopy();
|
|
4588
|
+
if (ctx.percentUsed === null) return null;
|
|
4589
|
+
return /* @__PURE__ */ jsx17(
|
|
4590
|
+
"span",
|
|
4576
4591
|
{
|
|
4577
|
-
|
|
4578
|
-
|
|
4592
|
+
ref: forwardedRef,
|
|
4593
|
+
"data-solvapay-usage-meter-percentage": "",
|
|
4594
|
+
className: className ?? "solvapay-usage-meter-percentage",
|
|
4595
|
+
...rest,
|
|
4596
|
+
children: children ?? interpolate(copy.usage.percentUsedLabel, { percent: String(Math.round(ctx.percentUsed)) })
|
|
4579
4597
|
}
|
|
4580
4598
|
);
|
|
4581
|
-
};
|
|
4582
|
-
|
|
4583
|
-
const
|
|
4599
|
+
});
|
|
4600
|
+
function formatResetsIn(copy, periodEnd, nowMs = typeof performance !== "undefined" ? performance.timeOrigin + performance.now() : 0) {
|
|
4601
|
+
const end = new Date(periodEnd).getTime();
|
|
4602
|
+
if (Number.isNaN(end)) return null;
|
|
4603
|
+
const daysLeft = Math.max(0, Math.ceil((end - nowMs) / (24 * 60 * 60 * 1e3)));
|
|
4604
|
+
return daysLeft > 0 ? interpolate(copy.usage.resetsInLabel, { days: String(daysLeft) }) : interpolate(copy.usage.resetsOnLabel, { date: new Date(periodEnd).toLocaleDateString() });
|
|
4605
|
+
}
|
|
4606
|
+
var ResetsIn = forwardRef10(function UsageMeterResetsIn({ className, children, ...rest }, forwardedRef) {
|
|
4607
|
+
const ctx = useUsageMeterCtx("ResetsIn");
|
|
4608
|
+
const copy = useCopy();
|
|
4609
|
+
const periodEnd = ctx.usage?.periodEnd;
|
|
4610
|
+
if (!periodEnd) return null;
|
|
4611
|
+
const label = formatResetsIn(copy, periodEnd);
|
|
4612
|
+
if (!label) return null;
|
|
4613
|
+
return /* @__PURE__ */ jsx17(
|
|
4614
|
+
"span",
|
|
4615
|
+
{
|
|
4616
|
+
ref: forwardedRef,
|
|
4617
|
+
"data-solvapay-usage-meter-resets-in": "",
|
|
4618
|
+
className: className ?? "solvapay-usage-meter-resets-in",
|
|
4619
|
+
...rest,
|
|
4620
|
+
children: children ?? label
|
|
4621
|
+
}
|
|
4622
|
+
);
|
|
4623
|
+
});
|
|
4624
|
+
var Loading4 = forwardRef10(function UsageMeterLoading({ className, children, ...rest }, forwardedRef) {
|
|
4625
|
+
const ctx = useUsageMeterCtx("Loading");
|
|
4626
|
+
const copy = useCopy();
|
|
4584
4627
|
if (ctx.state !== "loading") return null;
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4628
|
+
return /* @__PURE__ */ jsx17(
|
|
4629
|
+
"div",
|
|
4630
|
+
{
|
|
4631
|
+
ref: forwardedRef,
|
|
4632
|
+
"data-solvapay-usage-meter-loading": "",
|
|
4633
|
+
className: className ?? "solvapay-usage-meter-loading",
|
|
4634
|
+
...rest,
|
|
4635
|
+
children: children ?? copy.usage.loadingLabel
|
|
4636
|
+
}
|
|
4589
4637
|
);
|
|
4590
4638
|
});
|
|
4591
|
-
var
|
|
4592
|
-
|
|
4593
|
-
const
|
|
4594
|
-
return
|
|
4595
|
-
|
|
4596
|
-
|
|
4639
|
+
var Empty = forwardRef10(function UsageMeterEmpty({ className, children, ...rest }, forwardedRef) {
|
|
4640
|
+
const ctx = useUsageMeterCtx("Empty");
|
|
4641
|
+
const copy = useCopy();
|
|
4642
|
+
if (ctx.loading || ctx.usage !== null) return null;
|
|
4643
|
+
return /* @__PURE__ */ jsx17(
|
|
4644
|
+
"div",
|
|
4645
|
+
{
|
|
4646
|
+
ref: forwardedRef,
|
|
4647
|
+
"data-solvapay-usage-meter-empty": "",
|
|
4648
|
+
className: className ?? "solvapay-usage-meter-empty",
|
|
4649
|
+
...rest,
|
|
4650
|
+
children: children ?? copy.usage.emptyLabel
|
|
4651
|
+
}
|
|
4597
4652
|
);
|
|
4598
4653
|
});
|
|
4599
|
-
var
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4654
|
+
var UsageMeter = Object.assign(Root7, {
|
|
4655
|
+
Root: Root7,
|
|
4656
|
+
Bar,
|
|
4657
|
+
Label,
|
|
4658
|
+
Percentage,
|
|
4659
|
+
ResetsIn,
|
|
4660
|
+
Loading: Loading4,
|
|
4661
|
+
Empty
|
|
4662
|
+
});
|
|
4663
|
+
function useUsageMeter() {
|
|
4664
|
+
return useUsageMeterCtx("useUsageMeter");
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
// src/hooks/usePaywallResolver.ts
|
|
4668
|
+
import { useCallback as useCallback19, useMemo as useMemo16 } from "react";
|
|
4669
|
+
function usePaywallResolver(content) {
|
|
4670
|
+
const { activePurchase, hasPaidPurchase, refetch: refetchPurchase } = usePurchase();
|
|
4671
|
+
const { credits, refetch: refetchBalance } = useBalance();
|
|
4672
|
+
const resolved = useMemo16(() => {
|
|
4673
|
+
if (!content) return false;
|
|
4674
|
+
const productMatches = !content.product || activePurchase?.productRef === content.product || activePurchase?.productName === content.product;
|
|
4675
|
+
if (content.kind === "payment_required") {
|
|
4676
|
+
return Boolean(hasPaidPurchase && productMatches);
|
|
4677
|
+
}
|
|
4678
|
+
const balance = content.balance;
|
|
4679
|
+
if (balance && typeof balance.remainingUnits === "number" && balance.remainingUnits > 0) {
|
|
4680
|
+
return true;
|
|
4681
|
+
}
|
|
4682
|
+
if (balance && credits != null && balance.creditsPerUnit && credits >= balance.creditsPerUnit) {
|
|
4683
|
+
return true;
|
|
4684
|
+
}
|
|
4685
|
+
return Boolean(productMatches && activePurchase?.status === "active");
|
|
4686
|
+
}, [content, hasPaidPurchase, activePurchase, credits]);
|
|
4687
|
+
const refetch = useCallback19(async () => {
|
|
4688
|
+
await Promise.all([
|
|
4689
|
+
refetchPurchase().catch(() => void 0),
|
|
4690
|
+
refetchBalance().catch(() => void 0)
|
|
4691
|
+
]);
|
|
4692
|
+
}, [refetchPurchase, refetchBalance]);
|
|
4693
|
+
return { resolved, refetch };
|
|
4615
4694
|
}
|
|
4616
4695
|
|
|
4617
4696
|
export {
|
|
@@ -4621,7 +4700,10 @@ export {
|
|
|
4621
4700
|
getMostRecentPurchase,
|
|
4622
4701
|
getPrimaryPurchase,
|
|
4623
4702
|
isPaidPurchase,
|
|
4624
|
-
|
|
4703
|
+
isPlanPurchase,
|
|
4704
|
+
isTopupPurchase,
|
|
4705
|
+
DEFAULT_ROUTES,
|
|
4706
|
+
createHttpTransport,
|
|
4625
4707
|
enCopy,
|
|
4626
4708
|
mergeCopy,
|
|
4627
4709
|
CopyContext,
|
|
@@ -4633,19 +4715,27 @@ export {
|
|
|
4633
4715
|
Slot,
|
|
4634
4716
|
Slottable,
|
|
4635
4717
|
composeEventHandlers,
|
|
4718
|
+
MissingProviderError,
|
|
4719
|
+
MissingProductRefError,
|
|
4636
4720
|
useSolvaPay,
|
|
4637
4721
|
useCheckout,
|
|
4638
4722
|
usePurchase,
|
|
4639
4723
|
useCustomer,
|
|
4640
4724
|
useCopy,
|
|
4641
4725
|
useLocale,
|
|
4726
|
+
plansCache,
|
|
4642
4727
|
usePlans,
|
|
4643
4728
|
usePlan,
|
|
4729
|
+
createTransportCacheKey,
|
|
4730
|
+
productCache,
|
|
4644
4731
|
useProduct,
|
|
4645
4732
|
useActivation,
|
|
4733
|
+
usePlanSelection,
|
|
4646
4734
|
PaymentFormContext,
|
|
4647
4735
|
usePaymentForm,
|
|
4648
4736
|
PaymentFormProvider,
|
|
4737
|
+
getMinorUnitsPerMajor,
|
|
4738
|
+
toMajorUnits,
|
|
4649
4739
|
formatPrice,
|
|
4650
4740
|
interpolate,
|
|
4651
4741
|
CheckoutSummaryRoot2 as CheckoutSummaryRoot,
|
|
@@ -4657,6 +4747,7 @@ export {
|
|
|
4657
4747
|
CheckoutSummary,
|
|
4658
4748
|
useCheckoutSummary,
|
|
4659
4749
|
CheckoutSummary2,
|
|
4750
|
+
merchantCache,
|
|
4660
4751
|
useMerchant,
|
|
4661
4752
|
deriveVariant,
|
|
4662
4753
|
MandateText,
|
|
@@ -4691,17 +4782,8 @@ export {
|
|
|
4691
4782
|
TopupFormError2 as TopupFormError,
|
|
4692
4783
|
TopupForm,
|
|
4693
4784
|
useTopupForm,
|
|
4694
|
-
TopupForm2,
|
|
4695
|
-
ProductBadge2 as ProductBadge,
|
|
4696
|
-
PlanBadge,
|
|
4697
|
-
PurchaseGateRoot2 as PurchaseGateRoot,
|
|
4698
|
-
PurchaseGateAllowed2 as PurchaseGateAllowed,
|
|
4699
|
-
PurchaseGateBlocked2 as PurchaseGateBlocked,
|
|
4700
|
-
PurchaseGateLoading2 as PurchaseGateLoading,
|
|
4701
|
-
PurchaseGateError2 as PurchaseGateError,
|
|
4702
|
-
PurchaseGate,
|
|
4703
|
-
usePurchaseGate,
|
|
4704
4785
|
BalanceBadge,
|
|
4786
|
+
defaultListPlans,
|
|
4705
4787
|
PlanSelectorRoot2 as PlanSelectorRoot,
|
|
4706
4788
|
PlanSelectorHeading2 as PlanSelectorHeading,
|
|
4707
4789
|
PlanSelectorGrid2 as PlanSelectorGrid,
|
|
@@ -4714,17 +4796,6 @@ export {
|
|
|
4714
4796
|
PlanSelectorError2 as PlanSelectorError,
|
|
4715
4797
|
PlanSelector,
|
|
4716
4798
|
usePlanSelector,
|
|
4717
|
-
ActivationFlowRoot2 as ActivationFlowRoot,
|
|
4718
|
-
ActivationFlowSummary2 as ActivationFlowSummary,
|
|
4719
|
-
ActivationFlowActivateButton2 as ActivationFlowActivateButton,
|
|
4720
|
-
ActivationFlowAmountPicker,
|
|
4721
|
-
ActivationFlowContinueButton2 as ActivationFlowContinueButton,
|
|
4722
|
-
ActivationFlowRetrying2 as ActivationFlowRetrying,
|
|
4723
|
-
ActivationFlowActivated2 as ActivationFlowActivated,
|
|
4724
|
-
ActivationFlowLoading2 as ActivationFlowLoading,
|
|
4725
|
-
ActivationFlowError2 as ActivationFlowError,
|
|
4726
|
-
ActivationFlow,
|
|
4727
|
-
useActivationFlow,
|
|
4728
4799
|
usePurchaseActions,
|
|
4729
4800
|
CancelPlanButton,
|
|
4730
4801
|
usePurchaseStatus,
|
|
@@ -4738,12 +4809,16 @@ export {
|
|
|
4738
4809
|
CancelledPlanNoticeReactivateButton2 as CancelledPlanNoticeReactivateButton,
|
|
4739
4810
|
CancelledPlanNotice,
|
|
4740
4811
|
useCancelledPlanNotice,
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4812
|
+
useTransport,
|
|
4813
|
+
useUsage,
|
|
4814
|
+
Root7 as Root,
|
|
4815
|
+
Bar,
|
|
4816
|
+
Label,
|
|
4817
|
+
Percentage,
|
|
4818
|
+
ResetsIn,
|
|
4819
|
+
Loading4 as Loading,
|
|
4820
|
+
Empty,
|
|
4821
|
+
UsageMeter,
|
|
4822
|
+
useUsageMeter,
|
|
4823
|
+
usePaywallResolver
|
|
4749
4824
|
};
|