@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
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,9 @@ __export(index_exports, {
|
|
|
40
40
|
CopyContext: () => CopyContext,
|
|
41
41
|
CopyProvider: () => CopyProvider,
|
|
42
42
|
CreditGate: () => CreditGate2,
|
|
43
|
+
CurrentPlanCard: () => CurrentPlanCard,
|
|
44
|
+
DEFAULT_ROUTES: () => DEFAULT_ROUTES,
|
|
45
|
+
LaunchCustomerPortalButton: () => LaunchCustomerPortalButton,
|
|
43
46
|
MandateText: () => MandateText,
|
|
44
47
|
PaymentForm: () => PaymentForm2,
|
|
45
48
|
PaymentFormContext: () => PaymentFormContext,
|
|
@@ -52,7 +55,10 @@ __export(index_exports, {
|
|
|
52
55
|
Spinner: () => Spinner,
|
|
53
56
|
StripePaymentFormWrapper: () => StripePaymentFormWrapper,
|
|
54
57
|
TopupForm: () => TopupForm2,
|
|
58
|
+
UnsupportedTransportMethodError: () => UnsupportedTransportMethodError,
|
|
59
|
+
UpdatePaymentMethodButton: () => UpdatePaymentMethodButton,
|
|
55
60
|
confirmPayment: () => confirmPayment,
|
|
61
|
+
createHttpTransport: () => createHttpTransport,
|
|
56
62
|
defaultAuthAdapter: () => defaultAuthAdapter,
|
|
57
63
|
deriveVariant: () => deriveVariant,
|
|
58
64
|
enCopy: () => enCopy,
|
|
@@ -60,12 +66,16 @@ __export(index_exports, {
|
|
|
60
66
|
formatPrice: () => formatPrice,
|
|
61
67
|
getActivePurchases: () => getActivePurchases,
|
|
62
68
|
getCancelledPurchasesWithEndDate: () => getCancelledPurchasesWithEndDate,
|
|
69
|
+
getMinorUnitsPerMajor: () => getMinorUnitsPerMajor,
|
|
63
70
|
getMostRecentPurchase: () => getMostRecentPurchase,
|
|
64
71
|
getPrimaryPurchase: () => getPrimaryPurchase,
|
|
65
72
|
interpolate: () => interpolate,
|
|
66
73
|
isPaidPurchase: () => isPaidPurchase,
|
|
74
|
+
isPlanPurchase: () => isPlanPurchase,
|
|
75
|
+
isTopupPurchase: () => isTopupPurchase,
|
|
67
76
|
mergeCopy: () => mergeCopy,
|
|
68
77
|
resolveCta: () => resolveCta,
|
|
78
|
+
toMajorUnits: () => toMajorUnits,
|
|
69
79
|
useActivation: () => useActivation,
|
|
70
80
|
useBalance: () => useBalance,
|
|
71
81
|
useCheckout: () => useCheckout,
|
|
@@ -74,6 +84,8 @@ __export(index_exports, {
|
|
|
74
84
|
useLocale: () => useLocale,
|
|
75
85
|
useMerchant: () => useMerchant,
|
|
76
86
|
usePaymentForm: () => usePaymentForm,
|
|
87
|
+
usePaymentMethod: () => usePaymentMethod,
|
|
88
|
+
usePaywallResolver: () => usePaywallResolver,
|
|
77
89
|
usePlan: () => usePlan,
|
|
78
90
|
usePlans: () => usePlans,
|
|
79
91
|
useProduct: () => useProduct,
|
|
@@ -82,7 +94,9 @@ __export(index_exports, {
|
|
|
82
94
|
usePurchaseStatus: () => usePurchaseStatus,
|
|
83
95
|
useSolvaPay: () => useSolvaPay,
|
|
84
96
|
useTopup: () => useTopup,
|
|
85
|
-
useTopupAmountSelector: () => useTopupAmountSelector
|
|
97
|
+
useTopupAmountSelector: () => useTopupAmountSelector,
|
|
98
|
+
useTransport: () => useTransport,
|
|
99
|
+
useUsage: () => useUsage
|
|
86
100
|
});
|
|
87
101
|
module.exports = __toCommonJS(index_exports);
|
|
88
102
|
|
|
@@ -118,6 +132,12 @@ function getPrimaryPurchase(purchases) {
|
|
|
118
132
|
function isPaidPurchase(purchase) {
|
|
119
133
|
return (purchase.amount ?? 0) > 0;
|
|
120
134
|
}
|
|
135
|
+
function isPlanPurchase(purchase) {
|
|
136
|
+
return !!purchase.planSnapshot && purchase.metadata?.purpose !== "credit_topup";
|
|
137
|
+
}
|
|
138
|
+
function isTopupPurchase(purchase) {
|
|
139
|
+
return !isPlanPurchase(purchase);
|
|
140
|
+
}
|
|
121
141
|
|
|
122
142
|
// src/adapters/auth.ts
|
|
123
143
|
var defaultAuthAdapter = {
|
|
@@ -149,16 +169,6 @@ function getAuthAdapter(config) {
|
|
|
149
169
|
if (config?.auth?.adapter) {
|
|
150
170
|
return config.auth.adapter;
|
|
151
171
|
}
|
|
152
|
-
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
153
|
-
return {
|
|
154
|
-
async getToken() {
|
|
155
|
-
return config?.auth?.getToken?.() || null;
|
|
156
|
-
},
|
|
157
|
-
async getUserId() {
|
|
158
|
-
return config?.auth?.getUserId?.() || null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
172
|
return defaultAuthAdapter;
|
|
163
173
|
}
|
|
164
174
|
function getCachedCustomerRef(userId) {
|
|
@@ -218,6 +228,167 @@ async function buildRequestHeaders(config) {
|
|
|
218
228
|
return { headers, userId };
|
|
219
229
|
}
|
|
220
230
|
|
|
231
|
+
// src/transport/http.ts
|
|
232
|
+
async function request(config, url, opts) {
|
|
233
|
+
const { headers } = await buildRequestHeaders(config);
|
|
234
|
+
const fetchFn = config?.fetch || fetch;
|
|
235
|
+
const init = { method: opts.method, headers };
|
|
236
|
+
if (opts.body !== void 0) {
|
|
237
|
+
init.body = JSON.stringify(opts.body);
|
|
238
|
+
}
|
|
239
|
+
const res = await fetchFn(url, init);
|
|
240
|
+
if (!res.ok) {
|
|
241
|
+
let serverMessage;
|
|
242
|
+
try {
|
|
243
|
+
const data = await res.clone().json();
|
|
244
|
+
serverMessage = data?.error;
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
|
|
248
|
+
config?.onError?.(error, opts.onErrorContext);
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
return await res.json();
|
|
252
|
+
}
|
|
253
|
+
var DEFAULT_ROUTES = {
|
|
254
|
+
checkPurchase: "/api/check-purchase",
|
|
255
|
+
createPayment: "/api/create-payment-intent",
|
|
256
|
+
processPayment: "/api/process-payment",
|
|
257
|
+
createTopupPayment: "/api/create-topup-payment-intent",
|
|
258
|
+
customerBalance: "/api/customer-balance",
|
|
259
|
+
cancelRenewal: "/api/cancel-renewal",
|
|
260
|
+
reactivateRenewal: "/api/reactivate-renewal",
|
|
261
|
+
activatePlan: "/api/activate-plan",
|
|
262
|
+
createCheckoutSession: "/api/create-checkout-session",
|
|
263
|
+
createCustomerSession: "/api/create-customer-session",
|
|
264
|
+
getMerchant: "/api/merchant",
|
|
265
|
+
getProduct: "/api/get-product",
|
|
266
|
+
listPlans: "/api/list-plans",
|
|
267
|
+
getPaymentMethod: "/api/payment-method",
|
|
268
|
+
getUsage: "/api/usage"
|
|
269
|
+
};
|
|
270
|
+
function routeFor(config, key) {
|
|
271
|
+
const configured = config?.api?.[key];
|
|
272
|
+
return configured || DEFAULT_ROUTES[key];
|
|
273
|
+
}
|
|
274
|
+
function createHttpTransport(config) {
|
|
275
|
+
return {
|
|
276
|
+
checkPurchase: () => request(config, routeFor(config, "checkPurchase"), {
|
|
277
|
+
method: "GET",
|
|
278
|
+
onErrorContext: "checkPurchase",
|
|
279
|
+
errorPrefix: "Failed to check purchase"
|
|
280
|
+
}),
|
|
281
|
+
createPayment: (params) => {
|
|
282
|
+
const body = {};
|
|
283
|
+
if (params.planRef) body.planRef = params.planRef;
|
|
284
|
+
if (params.productRef) body.productRef = params.productRef;
|
|
285
|
+
if (params.customer && (params.customer.name || params.customer.email)) {
|
|
286
|
+
body.customer = params.customer;
|
|
287
|
+
}
|
|
288
|
+
return request(config, routeFor(config, "createPayment"), {
|
|
289
|
+
method: "POST",
|
|
290
|
+
body,
|
|
291
|
+
onErrorContext: "createPayment",
|
|
292
|
+
errorPrefix: "Failed to create payment"
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
processPayment: (params) => request(config, routeFor(config, "processPayment"), {
|
|
296
|
+
method: "POST",
|
|
297
|
+
body: params,
|
|
298
|
+
onErrorContext: "processPayment",
|
|
299
|
+
errorPrefix: "Failed to process payment"
|
|
300
|
+
}),
|
|
301
|
+
createTopupPayment: (params) => request(config, routeFor(config, "createTopupPayment"), {
|
|
302
|
+
method: "POST",
|
|
303
|
+
body: { amount: params.amount, currency: params.currency },
|
|
304
|
+
onErrorContext: "createTopupPayment",
|
|
305
|
+
errorPrefix: "Failed to create topup payment"
|
|
306
|
+
}),
|
|
307
|
+
getBalance: () => request(config, routeFor(config, "customerBalance"), {
|
|
308
|
+
method: "GET",
|
|
309
|
+
onErrorContext: "getBalance",
|
|
310
|
+
errorPrefix: "Failed to fetch balance"
|
|
311
|
+
}),
|
|
312
|
+
cancelRenewal: (params) => request(config, routeFor(config, "cancelRenewal"), {
|
|
313
|
+
method: "POST",
|
|
314
|
+
body: params,
|
|
315
|
+
onErrorContext: "cancelRenewal",
|
|
316
|
+
errorPrefix: "Failed to cancel renewal"
|
|
317
|
+
}),
|
|
318
|
+
reactivateRenewal: (params) => request(config, routeFor(config, "reactivateRenewal"), {
|
|
319
|
+
method: "POST",
|
|
320
|
+
body: params,
|
|
321
|
+
onErrorContext: "reactivateRenewal",
|
|
322
|
+
errorPrefix: "Failed to reactivate renewal"
|
|
323
|
+
}),
|
|
324
|
+
activatePlan: (params) => request(config, routeFor(config, "activatePlan"), {
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: params,
|
|
327
|
+
onErrorContext: "activatePlan",
|
|
328
|
+
errorPrefix: "Failed to activate plan"
|
|
329
|
+
}),
|
|
330
|
+
createCheckoutSession: (params) => {
|
|
331
|
+
const body = {};
|
|
332
|
+
if (params?.planRef) body.planRef = params.planRef;
|
|
333
|
+
if (params?.productRef) body.productRef = params.productRef;
|
|
334
|
+
if (params?.returnUrl) body.returnUrl = params.returnUrl;
|
|
335
|
+
return request(
|
|
336
|
+
config,
|
|
337
|
+
routeFor(config, "createCheckoutSession"),
|
|
338
|
+
{
|
|
339
|
+
method: "POST",
|
|
340
|
+
body,
|
|
341
|
+
onErrorContext: "createCheckoutSession",
|
|
342
|
+
errorPrefix: "Failed to create checkout session"
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
},
|
|
346
|
+
createCustomerSession: () => request(
|
|
347
|
+
config,
|
|
348
|
+
routeFor(config, "createCustomerSession"),
|
|
349
|
+
{
|
|
350
|
+
method: "POST",
|
|
351
|
+
body: {},
|
|
352
|
+
onErrorContext: "createCustomerSession",
|
|
353
|
+
errorPrefix: "Failed to create customer session"
|
|
354
|
+
}
|
|
355
|
+
),
|
|
356
|
+
getMerchant: () => request(config, routeFor(config, "getMerchant"), {
|
|
357
|
+
method: "GET",
|
|
358
|
+
onErrorContext: "getMerchant",
|
|
359
|
+
errorPrefix: "Failed to fetch merchant"
|
|
360
|
+
}),
|
|
361
|
+
getProduct: (productRef) => {
|
|
362
|
+
const base = routeFor(config, "getProduct");
|
|
363
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
364
|
+
return request(config, url, {
|
|
365
|
+
method: "GET",
|
|
366
|
+
onErrorContext: "getProduct",
|
|
367
|
+
errorPrefix: "Failed to fetch product"
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
listPlans: (productRef) => {
|
|
371
|
+
const base = routeFor(config, "listPlans");
|
|
372
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
373
|
+
return request(config, url, {
|
|
374
|
+
method: "GET",
|
|
375
|
+
onErrorContext: "listPlans",
|
|
376
|
+
errorPrefix: "Failed to list plans"
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
getPaymentMethod: () => request(config, routeFor(config, "getPaymentMethod"), {
|
|
380
|
+
method: "GET",
|
|
381
|
+
onErrorContext: "getPaymentMethod",
|
|
382
|
+
errorPrefix: "Failed to load payment method"
|
|
383
|
+
}),
|
|
384
|
+
getUsage: () => request(config, routeFor(config, "getUsage"), {
|
|
385
|
+
method: "GET",
|
|
386
|
+
onErrorContext: "getUsage",
|
|
387
|
+
errorPrefix: "Failed to load usage"
|
|
388
|
+
})
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
221
392
|
// src/i18n/context.tsx
|
|
222
393
|
var import_react = require("react");
|
|
223
394
|
|
|
@@ -367,6 +538,26 @@ var enCopy = {
|
|
|
367
538
|
lowBalanceSubheading: "Top up to continue using {product}",
|
|
368
539
|
topUpCta: "Top up now"
|
|
369
540
|
},
|
|
541
|
+
currentPlan: {
|
|
542
|
+
heading: "Your plan",
|
|
543
|
+
nextBilling: "Next billing: {date}",
|
|
544
|
+
expiresOn: "Expires {date}",
|
|
545
|
+
validIndefinitely: "Valid indefinitely",
|
|
546
|
+
paymentMethod: "{brand} \u2022\u2022\u2022\u2022 {last4}",
|
|
547
|
+
paymentMethodExpires: "expires {month}/{year}",
|
|
548
|
+
noPaymentMethod: "No payment method on file",
|
|
549
|
+
updatePaymentButton: "Update card",
|
|
550
|
+
cycleUnit: {
|
|
551
|
+
weekly: "week",
|
|
552
|
+
monthly: "month",
|
|
553
|
+
quarterly: "3 months",
|
|
554
|
+
yearly: "year"
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
customerPortal: {
|
|
558
|
+
launchButton: "Manage billing",
|
|
559
|
+
loadingLabel: "Loading portal\u2026"
|
|
560
|
+
},
|
|
370
561
|
errors: {
|
|
371
562
|
paymentInitFailed: "Payment initialization failed",
|
|
372
563
|
topupInitFailed: "Top-up initialization failed",
|
|
@@ -380,7 +571,40 @@ var enCopy = {
|
|
|
380
571
|
paymentProcessingFailed: "Payment processing failed. Please try again or contact support.",
|
|
381
572
|
paymentRequires3ds: "Payment requires additional authentication. Please complete the verification.",
|
|
382
573
|
paymentProcessingTimeout: "Payment processing timed out \u2014 webhooks may not be configured",
|
|
383
|
-
|
|
574
|
+
paymentConfirmationDelayed: "Payment succeeded but confirmation is taking longer than usual. Refresh in a moment to see your purchase.",
|
|
575
|
+
paymentStatusPrefix: "Payment status: {status}",
|
|
576
|
+
paywallInvalidContent: "Paywall content is missing or malformed.",
|
|
577
|
+
usageLoadFailed: "Failed to load usage"
|
|
578
|
+
},
|
|
579
|
+
paywall: {
|
|
580
|
+
header: "Unlock access",
|
|
581
|
+
paymentRequiredHeading: "Upgrade to continue",
|
|
582
|
+
activationRequiredHeading: "Add credits to continue",
|
|
583
|
+
resolvedHeading: "You can continue",
|
|
584
|
+
productContext: "For {product}",
|
|
585
|
+
balanceLine: "You have {available} credits, need {required}.",
|
|
586
|
+
paymentRequiredMessage: "You've used all your included calls{forProduct}. Choose a plan below to keep going.",
|
|
587
|
+
paymentRequiredMessageRemaining: "Only {remaining} call{pluralSuffix} left{forProduct}. Choose a plan below to keep going.",
|
|
588
|
+
paymentRequiredProductSuffix: " for {product}",
|
|
589
|
+
retryButton: "Continue",
|
|
590
|
+
hostedCheckoutButton: "Open checkout",
|
|
591
|
+
hostedCheckoutLoading: "Loading checkout\u2026"
|
|
592
|
+
},
|
|
593
|
+
usage: {
|
|
594
|
+
header: "Usage",
|
|
595
|
+
percentUsedLabel: "{percent}% used",
|
|
596
|
+
usedLabel: "{used} / {total} {unit}",
|
|
597
|
+
remainingLabel: "{remaining} {unit} remaining",
|
|
598
|
+
unlimitedLabel: "Unlimited",
|
|
599
|
+
resetsInLabel: "Resets in {days} days",
|
|
600
|
+
resetsOnLabel: "Resets {date}",
|
|
601
|
+
loadingLabel: "Loading usage\u2026",
|
|
602
|
+
emptyLabel: "No usage-based plan is active.",
|
|
603
|
+
approachingLimit: "You're approaching your usage limit.",
|
|
604
|
+
atLimit: "You've reached your usage limit.",
|
|
605
|
+
topUpCta: "Add credits",
|
|
606
|
+
upgradeCta: "Upgrade plan",
|
|
607
|
+
refreshCta: "Refresh"
|
|
384
608
|
}
|
|
385
609
|
};
|
|
386
610
|
|
|
@@ -425,146 +649,66 @@ function useCopyContext() {
|
|
|
425
649
|
// src/SolvaPayProvider.tsx
|
|
426
650
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
427
651
|
var SolvaPayContext = (0, import_react2.createContext)(null);
|
|
428
|
-
|
|
429
|
-
config
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
652
|
+
function resolveTransport(config) {
|
|
653
|
+
return config?.transport ?? createHttpTransport(config);
|
|
654
|
+
}
|
|
655
|
+
var SolvaPayProvider = ({ config, children }) => {
|
|
656
|
+
const initial = config?.initial;
|
|
657
|
+
const [purchaseData, setPurchaseData] = (0, import_react2.useState)(() => {
|
|
658
|
+
if (!initial) return { purchases: [] };
|
|
659
|
+
return {
|
|
660
|
+
// The server-side `PurchaseCheckResult.purchases` shape has looser
|
|
661
|
+
// optional fields than `PurchaseInfo`, but the runtime data is
|
|
662
|
+
// identical — the same rows flow through `transport.checkPurchase()`
|
|
663
|
+
// today. Cast once here to unify the two entry points, and run
|
|
664
|
+
// the same `filterPurchases` the HTTP path applies in
|
|
665
|
+
// `fetchPurchase` so the bootstrap-hydrated data never contains
|
|
666
|
+
// cancelled / expired / suspended rows the active-only derivations
|
|
667
|
+
// (`activePurchase`, `hasPaidPurchase`, …) wouldn't expect.
|
|
668
|
+
purchases: filterPurchases(
|
|
669
|
+
initial.purchase?.purchases ?? []
|
|
670
|
+
),
|
|
671
|
+
customerRef: initial.customerRef ?? initial.purchase?.customerRef,
|
|
672
|
+
email: initial.purchase?.email,
|
|
673
|
+
name: initial.purchase?.name
|
|
674
|
+
};
|
|
438
675
|
});
|
|
439
676
|
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
440
677
|
const [isRefetching, setIsRefetching] = (0, import_react2.useState)(false);
|
|
441
678
|
const [purchaseError, setPurchaseError] = (0, import_react2.useState)(null);
|
|
442
|
-
const [internalCustomerRef, setInternalCustomerRef] = (0, import_react2.useState)(
|
|
679
|
+
const [internalCustomerRef, setInternalCustomerRef] = (0, import_react2.useState)(
|
|
680
|
+
initial?.customerRef ?? void 0
|
|
681
|
+
);
|
|
443
682
|
const [userId, setUserId] = (0, import_react2.useState)(null);
|
|
444
|
-
const [isAuthenticated, setIsAuthenticated] = (0, import_react2.useState)(
|
|
445
|
-
const [creditsValue, setCreditsValue] = (0, import_react2.useState)(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const [
|
|
683
|
+
const [isAuthenticated, setIsAuthenticated] = (0, import_react2.useState)(!!initial?.customerRef);
|
|
684
|
+
const [creditsValue, setCreditsValue] = (0, import_react2.useState)(
|
|
685
|
+
initial?.balance?.credits ?? null
|
|
686
|
+
);
|
|
687
|
+
const [displayCurrencyValue, setDisplayCurrencyValue] = (0, import_react2.useState)(
|
|
688
|
+
initial?.balance?.displayCurrency ?? null
|
|
689
|
+
);
|
|
690
|
+
const [creditsPerMinorUnitValue, setCreditsPerMinorUnitValue] = (0, import_react2.useState)(
|
|
691
|
+
initial?.balance?.creditsPerMinorUnit ?? null
|
|
692
|
+
);
|
|
693
|
+
const [displayExchangeRateValue, setDisplayExchangeRateValue] = (0, import_react2.useState)(
|
|
694
|
+
initial?.balance?.displayExchangeRate ?? null
|
|
695
|
+
);
|
|
449
696
|
const [balanceLoading, setBalanceLoading] = (0, import_react2.useState)(false);
|
|
450
697
|
const balanceInFlightRef = (0, import_react2.useRef)(false);
|
|
451
|
-
const balanceLoadedRef = (0, import_react2.useRef)(
|
|
698
|
+
const balanceLoadedRef = (0, import_react2.useRef)(!!initial?.balance);
|
|
452
699
|
const optimisticUntilRef = (0, import_react2.useRef)(0);
|
|
453
700
|
const optimisticTimerRef = (0, import_react2.useRef)(null);
|
|
454
701
|
const fetchBalanceRef = (0, import_react2.useRef)(null);
|
|
455
702
|
const inFlightRef = (0, import_react2.useRef)(null);
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
const processPaymentRef = (0, import_react2.useRef)(null);
|
|
459
|
-
const createTopupPaymentRef = (0, import_react2.useRef)(null);
|
|
460
|
-
const configRef = (0, import_react2.useRef)(config);
|
|
461
|
-
const buildDefaultCheckPurchaseRef = (0, import_react2.useRef)(
|
|
462
|
-
null
|
|
703
|
+
const loadedCacheKeysRef = (0, import_react2.useRef)(
|
|
704
|
+
new Set(initial?.customerRef ? [initial.customerRef] : [])
|
|
463
705
|
);
|
|
706
|
+
const configRef = (0, import_react2.useRef)(config);
|
|
707
|
+
const transportRef = (0, import_react2.useRef)(resolveTransport(config));
|
|
464
708
|
(0, import_react2.useEffect)(() => {
|
|
465
709
|
configRef.current = config;
|
|
710
|
+
transportRef.current = resolveTransport(config);
|
|
466
711
|
}, [config]);
|
|
467
|
-
(0, import_react2.useEffect)(() => {
|
|
468
|
-
checkPurchaseRef.current = customCheckPurchase || null;
|
|
469
|
-
}, [customCheckPurchase]);
|
|
470
|
-
(0, import_react2.useEffect)(() => {
|
|
471
|
-
createPaymentRef.current = customCreatePayment || null;
|
|
472
|
-
}, [customCreatePayment]);
|
|
473
|
-
(0, import_react2.useEffect)(() => {
|
|
474
|
-
processPaymentRef.current = customProcessPayment || null;
|
|
475
|
-
}, [customProcessPayment]);
|
|
476
|
-
(0, import_react2.useEffect)(() => {
|
|
477
|
-
createTopupPaymentRef.current = customCreateTopupPayment || null;
|
|
478
|
-
}, [customCreateTopupPayment]);
|
|
479
|
-
const buildDefaultCheckPurchase = (0, import_react2.useCallback)(async () => {
|
|
480
|
-
const currentConfig = configRef.current;
|
|
481
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
482
|
-
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
483
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
484
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
485
|
-
if (!res.ok) {
|
|
486
|
-
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
487
|
-
currentConfig?.onError?.(error, "checkPurchase");
|
|
488
|
-
throw error;
|
|
489
|
-
}
|
|
490
|
-
return res.json();
|
|
491
|
-
}, []);
|
|
492
|
-
(0, import_react2.useEffect)(() => {
|
|
493
|
-
buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
|
|
494
|
-
}, [buildDefaultCheckPurchase]);
|
|
495
|
-
const buildDefaultCreatePayment = (0, import_react2.useCallback)(
|
|
496
|
-
async (params) => {
|
|
497
|
-
const currentConfig = configRef.current;
|
|
498
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
499
|
-
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
500
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
501
|
-
const body = {};
|
|
502
|
-
if (params.planRef) {
|
|
503
|
-
body.planRef = params.planRef;
|
|
504
|
-
}
|
|
505
|
-
if (params.productRef) {
|
|
506
|
-
body.productRef = params.productRef;
|
|
507
|
-
}
|
|
508
|
-
if (params.customer && (params.customer.name || params.customer.email)) {
|
|
509
|
-
body.customer = params.customer;
|
|
510
|
-
}
|
|
511
|
-
const res = await fetchFn(route, {
|
|
512
|
-
method: "POST",
|
|
513
|
-
headers,
|
|
514
|
-
body: JSON.stringify(body)
|
|
515
|
-
});
|
|
516
|
-
if (!res.ok) {
|
|
517
|
-
const error = new Error(`Failed to create payment: ${res.statusText}`);
|
|
518
|
-
currentConfig?.onError?.(error, "createPayment");
|
|
519
|
-
throw error;
|
|
520
|
-
}
|
|
521
|
-
return res.json();
|
|
522
|
-
},
|
|
523
|
-
[]
|
|
524
|
-
);
|
|
525
|
-
const buildDefaultProcessPayment = (0, import_react2.useCallback)(
|
|
526
|
-
async (params) => {
|
|
527
|
-
const currentConfig = configRef.current;
|
|
528
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
529
|
-
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
530
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
531
|
-
const res = await fetchFn(route, {
|
|
532
|
-
method: "POST",
|
|
533
|
-
headers,
|
|
534
|
-
body: JSON.stringify(params)
|
|
535
|
-
});
|
|
536
|
-
if (!res.ok) {
|
|
537
|
-
const error = new Error(`Failed to process payment: ${res.statusText}`);
|
|
538
|
-
currentConfig?.onError?.(error, "processPayment");
|
|
539
|
-
throw error;
|
|
540
|
-
}
|
|
541
|
-
return res.json();
|
|
542
|
-
},
|
|
543
|
-
[]
|
|
544
|
-
);
|
|
545
|
-
const buildDefaultCreateTopupPayment = (0, import_react2.useCallback)(
|
|
546
|
-
async (params) => {
|
|
547
|
-
const currentConfig = configRef.current;
|
|
548
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
549
|
-
const route = currentConfig?.api?.createTopupPayment || "/api/create-topup-payment-intent";
|
|
550
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
551
|
-
const res = await fetchFn(route, {
|
|
552
|
-
method: "POST",
|
|
553
|
-
headers,
|
|
554
|
-
body: JSON.stringify({
|
|
555
|
-
amount: params.amount,
|
|
556
|
-
currency: params.currency
|
|
557
|
-
})
|
|
558
|
-
});
|
|
559
|
-
if (!res.ok) {
|
|
560
|
-
const error = new Error(`Failed to create topup payment: ${res.statusText}`);
|
|
561
|
-
currentConfig?.onError?.(error, "createTopupPayment");
|
|
562
|
-
throw error;
|
|
563
|
-
}
|
|
564
|
-
return res.json();
|
|
565
|
-
},
|
|
566
|
-
[]
|
|
567
|
-
);
|
|
568
712
|
const fetchBalanceImpl = (0, import_react2.useCallback)(async () => {
|
|
569
713
|
if (optimisticUntilRef.current > Date.now()) return;
|
|
570
714
|
if (!isAuthenticated && !internalCustomerRef) {
|
|
@@ -582,16 +726,12 @@ var SolvaPayProvider = ({
|
|
|
582
726
|
setBalanceLoading(true);
|
|
583
727
|
}
|
|
584
728
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
589
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
590
|
-
if (!res.ok) {
|
|
591
|
-
console.error("[SolvaPayProvider] Failed to fetch balance:", res.statusText);
|
|
729
|
+
if (!transportRef.current.getBalance) {
|
|
730
|
+
setBalanceLoading(false);
|
|
731
|
+
balanceInFlightRef.current = false;
|
|
592
732
|
return;
|
|
593
733
|
}
|
|
594
|
-
const data = await
|
|
734
|
+
const data = await transportRef.current.getBalance();
|
|
595
735
|
setCreditsValue(data.credits ?? null);
|
|
596
736
|
setDisplayCurrencyValue(data.displayCurrency ?? null);
|
|
597
737
|
setCreditsPerMinorUnitValue(data.creditsPerMinorUnit ?? null);
|
|
@@ -622,54 +762,35 @@ var SolvaPayProvider = ({
|
|
|
622
762
|
fetchBalanceRef.current?.();
|
|
623
763
|
}, OPTIMISTIC_GRACE_MS);
|
|
624
764
|
}, []);
|
|
625
|
-
const _checkPurchase = (0, import_react2.useCallback)(async () => {
|
|
626
|
-
if (checkPurchaseRef.current) {
|
|
627
|
-
return checkPurchaseRef.current();
|
|
628
|
-
}
|
|
629
|
-
if (buildDefaultCheckPurchaseRef.current) {
|
|
630
|
-
return buildDefaultCheckPurchaseRef.current();
|
|
631
|
-
}
|
|
632
|
-
return buildDefaultCheckPurchase();
|
|
633
|
-
}, [buildDefaultCheckPurchase]);
|
|
634
765
|
const createPayment = (0, import_react2.useCallback)(
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return createPaymentRef.current(params);
|
|
638
|
-
}
|
|
639
|
-
return buildDefaultCreatePayment(params);
|
|
640
|
-
},
|
|
641
|
-
[buildDefaultCreatePayment]
|
|
766
|
+
(params) => transportRef.current.createPayment(params),
|
|
767
|
+
[]
|
|
642
768
|
);
|
|
643
769
|
const processPayment = (0, import_react2.useCallback)(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
return processPaymentRef.current(params);
|
|
647
|
-
}
|
|
648
|
-
return buildDefaultProcessPayment(params);
|
|
649
|
-
},
|
|
650
|
-
[buildDefaultProcessPayment]
|
|
770
|
+
(params) => transportRef.current.processPayment(params),
|
|
771
|
+
[]
|
|
651
772
|
);
|
|
652
773
|
const createTopupPayment = (0, import_react2.useCallback)(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
return createTopupPaymentRef.current(params);
|
|
656
|
-
}
|
|
657
|
-
return buildDefaultCreateTopupPayment(params);
|
|
658
|
-
},
|
|
659
|
-
[buildDefaultCreateTopupPayment]
|
|
774
|
+
(params) => transportRef.current.createTopupPayment(params),
|
|
775
|
+
[]
|
|
660
776
|
);
|
|
661
777
|
(0, import_react2.useEffect)(() => {
|
|
778
|
+
if (configRef.current?.initial) {
|
|
779
|
+
setIsAuthenticated(configRef.current.initial.customerRef !== null);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
662
782
|
const detectAuth = async () => {
|
|
663
783
|
const currentConfig = configRef.current;
|
|
664
|
-
const
|
|
665
|
-
const token = await
|
|
666
|
-
const detectedUserId = await
|
|
784
|
+
const adapter2 = getAuthAdapter(currentConfig);
|
|
785
|
+
const token = await adapter2.getToken();
|
|
786
|
+
const detectedUserId = await adapter2.getUserId();
|
|
667
787
|
const prevUserId = userId;
|
|
668
788
|
setIsAuthenticated(!!token);
|
|
669
789
|
setUserId(detectedUserId);
|
|
670
790
|
if (prevUserId !== null && detectedUserId !== prevUserId) {
|
|
671
791
|
clearCachedCustomerRef();
|
|
672
792
|
setInternalCustomerRef(void 0);
|
|
793
|
+
loadedCacheKeysRef.current.clear();
|
|
673
794
|
return;
|
|
674
795
|
}
|
|
675
796
|
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
@@ -678,11 +799,19 @@ var SolvaPayProvider = ({
|
|
|
678
799
|
} else if (!token) {
|
|
679
800
|
clearCachedCustomerRef();
|
|
680
801
|
setInternalCustomerRef(void 0);
|
|
802
|
+
loadedCacheKeysRef.current.clear();
|
|
681
803
|
} else if (token && !cachedRef) {
|
|
682
804
|
setInternalCustomerRef(void 0);
|
|
683
805
|
}
|
|
684
806
|
};
|
|
685
807
|
detectAuth();
|
|
808
|
+
const adapter = getAuthAdapter(configRef.current);
|
|
809
|
+
if (typeof adapter.subscribe === "function") {
|
|
810
|
+
const unsubscribe = adapter.subscribe(() => {
|
|
811
|
+
detectAuth();
|
|
812
|
+
});
|
|
813
|
+
return unsubscribe;
|
|
814
|
+
}
|
|
686
815
|
const interval = setInterval(detectAuth, 3e4);
|
|
687
816
|
return () => clearInterval(interval);
|
|
688
817
|
}, [userId]);
|
|
@@ -701,20 +830,24 @@ var SolvaPayProvider = ({
|
|
|
701
830
|
return;
|
|
702
831
|
}
|
|
703
832
|
inFlightRef.current = cacheKey;
|
|
704
|
-
const
|
|
705
|
-
if (
|
|
833
|
+
const hasLoadedOnce = loadedCacheKeysRef.current.has(cacheKey);
|
|
834
|
+
if (hasLoadedOnce) {
|
|
706
835
|
setIsRefetching(true);
|
|
707
836
|
} else {
|
|
708
837
|
setLoading(true);
|
|
709
838
|
}
|
|
710
839
|
try {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
840
|
+
if (!transportRef.current.checkPurchase) {
|
|
841
|
+
setLoading(false);
|
|
842
|
+
setIsRefetching(false);
|
|
843
|
+
loadedCacheKeysRef.current.add(cacheKey);
|
|
844
|
+
inFlightRef.current = null;
|
|
845
|
+
return;
|
|
714
846
|
}
|
|
715
|
-
const data = await
|
|
847
|
+
const data = await transportRef.current.checkPurchase();
|
|
716
848
|
if (data.customerRef) {
|
|
717
849
|
setInternalCustomerRef(data.customerRef);
|
|
850
|
+
loadedCacheKeysRef.current.add(data.customerRef);
|
|
718
851
|
const currentAdapter = getAuthAdapter(configRef.current);
|
|
719
852
|
const currentUserId = await currentAdapter.getUserId();
|
|
720
853
|
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
@@ -734,40 +867,82 @@ var SolvaPayProvider = ({
|
|
|
734
867
|
}
|
|
735
868
|
} finally {
|
|
736
869
|
if (inFlightRef.current === cacheKey) {
|
|
870
|
+
loadedCacheKeysRef.current.add(cacheKey);
|
|
737
871
|
setLoading(false);
|
|
738
872
|
setIsRefetching(false);
|
|
739
873
|
inFlightRef.current = null;
|
|
740
874
|
}
|
|
741
875
|
}
|
|
742
876
|
},
|
|
743
|
-
[isAuthenticated, internalCustomerRef, userId
|
|
877
|
+
[isAuthenticated, internalCustomerRef, userId]
|
|
744
878
|
);
|
|
745
879
|
const fetchPurchaseRef = (0, import_react2.useRef)(fetchPurchase);
|
|
746
880
|
(0, import_react2.useEffect)(() => {
|
|
747
881
|
fetchPurchaseRef.current = fetchPurchase;
|
|
748
882
|
}, [fetchPurchase]);
|
|
883
|
+
const applyInitialRef = (0, import_react2.useRef)(null);
|
|
749
884
|
const refetchPurchase = (0, import_react2.useCallback)(async () => {
|
|
750
885
|
inFlightRef.current = null;
|
|
886
|
+
const refresh = configRef.current?.refreshInitial;
|
|
887
|
+
const hasCheckPurchase = !!transportRef.current.checkPurchase;
|
|
888
|
+
if (refresh && !hasCheckPurchase) {
|
|
889
|
+
setIsRefetching(true);
|
|
890
|
+
try {
|
|
891
|
+
const next = await refresh();
|
|
892
|
+
if (next) applyInitialRef.current?.(next);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
console.error("[SolvaPayProvider] refetchPurchase (MCP) failed:", err);
|
|
895
|
+
} finally {
|
|
896
|
+
setIsRefetching(false);
|
|
897
|
+
}
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
751
900
|
await fetchPurchaseRef.current(true);
|
|
752
901
|
}, []);
|
|
902
|
+
const applyInitial = (0, import_react2.useCallback)((next) => {
|
|
903
|
+
setPurchaseData({
|
|
904
|
+
// Mirror the HTTP path: strip non-active rows before any
|
|
905
|
+
// derivation (`activePurchase` etc.) reads the array.
|
|
906
|
+
purchases: filterPurchases(
|
|
907
|
+
next.purchase?.purchases ?? []
|
|
908
|
+
),
|
|
909
|
+
customerRef: next.customerRef ?? next.purchase?.customerRef,
|
|
910
|
+
email: next.purchase?.email,
|
|
911
|
+
name: next.purchase?.name
|
|
912
|
+
});
|
|
913
|
+
if (next.customerRef) {
|
|
914
|
+
setInternalCustomerRef(next.customerRef);
|
|
915
|
+
loadedCacheKeysRef.current.add(next.customerRef);
|
|
916
|
+
} else {
|
|
917
|
+
setInternalCustomerRef(void 0);
|
|
918
|
+
loadedCacheKeysRef.current.clear();
|
|
919
|
+
}
|
|
920
|
+
setIsAuthenticated(next.customerRef !== null);
|
|
921
|
+
setCreditsValue(next.balance?.credits ?? null);
|
|
922
|
+
setDisplayCurrencyValue(next.balance?.displayCurrency ?? null);
|
|
923
|
+
setCreditsPerMinorUnitValue(next.balance?.creditsPerMinorUnit ?? null);
|
|
924
|
+
setDisplayExchangeRateValue(next.balance?.displayExchangeRate ?? null);
|
|
925
|
+
balanceLoadedRef.current = !!next.balance;
|
|
926
|
+
}, []);
|
|
927
|
+
(0, import_react2.useEffect)(() => {
|
|
928
|
+
applyInitialRef.current = applyInitial;
|
|
929
|
+
}, [applyInitial]);
|
|
930
|
+
const refreshBootstrap = (0, import_react2.useCallback)(async () => {
|
|
931
|
+
const refresh = configRef.current?.refreshInitial;
|
|
932
|
+
if (refresh) {
|
|
933
|
+
try {
|
|
934
|
+
const next = await refresh();
|
|
935
|
+
if (next) applyInitial(next);
|
|
936
|
+
} catch (err) {
|
|
937
|
+
console.error("[SolvaPayProvider] refreshBootstrap failed:", err);
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
await Promise.all([refetchPurchase(), fetchBalanceRef.current?.() ?? Promise.resolve()]);
|
|
942
|
+
}, [refetchPurchase, applyInitial]);
|
|
753
943
|
const cancelRenewal = (0, import_react2.useCallback)(
|
|
754
944
|
async (params) => {
|
|
755
|
-
const
|
|
756
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
757
|
-
const route = currentConfig?.api?.cancelRenewal || "/api/cancel-renewal";
|
|
758
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
759
|
-
const res = await fetchFn(route, {
|
|
760
|
-
method: "POST",
|
|
761
|
-
headers,
|
|
762
|
-
body: JSON.stringify(params)
|
|
763
|
-
});
|
|
764
|
-
if (!res.ok) {
|
|
765
|
-
const data = await res.json().catch(() => ({}));
|
|
766
|
-
const error = new Error(data.error || `Failed to cancel renewal: ${res.statusText}`);
|
|
767
|
-
currentConfig?.onError?.(error, "cancelRenewal");
|
|
768
|
-
throw error;
|
|
769
|
-
}
|
|
770
|
-
const result = await res.json();
|
|
945
|
+
const result = await transportRef.current.cancelRenewal(params);
|
|
771
946
|
await refetchPurchase();
|
|
772
947
|
return result;
|
|
773
948
|
},
|
|
@@ -775,22 +950,7 @@ var SolvaPayProvider = ({
|
|
|
775
950
|
);
|
|
776
951
|
const reactivateRenewal = (0, import_react2.useCallback)(
|
|
777
952
|
async (params) => {
|
|
778
|
-
const
|
|
779
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
780
|
-
const route = currentConfig?.api?.reactivateRenewal || "/api/reactivate-renewal";
|
|
781
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
782
|
-
const res = await fetchFn(route, {
|
|
783
|
-
method: "POST",
|
|
784
|
-
headers,
|
|
785
|
-
body: JSON.stringify(params)
|
|
786
|
-
});
|
|
787
|
-
if (!res.ok) {
|
|
788
|
-
const data = await res.json().catch(() => ({}));
|
|
789
|
-
const error = new Error(data.error || `Failed to reactivate renewal: ${res.statusText}`);
|
|
790
|
-
currentConfig?.onError?.(error, "reactivateRenewal");
|
|
791
|
-
throw error;
|
|
792
|
-
}
|
|
793
|
-
const result = await res.json();
|
|
953
|
+
const result = await transportRef.current.reactivateRenewal(params);
|
|
794
954
|
await refetchPurchase();
|
|
795
955
|
return result;
|
|
796
956
|
},
|
|
@@ -798,22 +958,7 @@ var SolvaPayProvider = ({
|
|
|
798
958
|
);
|
|
799
959
|
const activatePlan = (0, import_react2.useCallback)(
|
|
800
960
|
async (params) => {
|
|
801
|
-
const
|
|
802
|
-
const { headers } = await buildRequestHeaders(currentConfig);
|
|
803
|
-
const route = currentConfig?.api?.activatePlan || "/api/activate-plan";
|
|
804
|
-
const fetchFn = currentConfig?.fetch || fetch;
|
|
805
|
-
const res = await fetchFn(route, {
|
|
806
|
-
method: "POST",
|
|
807
|
-
headers,
|
|
808
|
-
body: JSON.stringify(params)
|
|
809
|
-
});
|
|
810
|
-
if (!res.ok) {
|
|
811
|
-
const data = await res.json().catch(() => ({}));
|
|
812
|
-
const error = new Error(data.error || `Failed to activate plan: ${res.statusText}`);
|
|
813
|
-
currentConfig?.onError?.(error, "activatePlan");
|
|
814
|
-
throw error;
|
|
815
|
-
}
|
|
816
|
-
const result = await res.json();
|
|
961
|
+
const result = await transportRef.current.activatePlan(params);
|
|
817
962
|
if (result.status === "activated" || result.status === "already_active") {
|
|
818
963
|
await refetchPurchase();
|
|
819
964
|
}
|
|
@@ -821,8 +966,13 @@ var SolvaPayProvider = ({
|
|
|
821
966
|
},
|
|
822
967
|
[refetchPurchase]
|
|
823
968
|
);
|
|
969
|
+
const hydratedFromInitialRef = (0, import_react2.useRef)(!!initial);
|
|
824
970
|
(0, import_react2.useEffect)(() => {
|
|
825
971
|
inFlightRef.current = null;
|
|
972
|
+
if (hydratedFromInitialRef.current) {
|
|
973
|
+
hydratedFromInitialRef.current = false;
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
826
976
|
if (isAuthenticated || internalCustomerRef) {
|
|
827
977
|
fetchPurchase();
|
|
828
978
|
} else {
|
|
@@ -841,8 +991,10 @@ var SolvaPayProvider = ({
|
|
|
841
991
|
[fetchPurchase, userId]
|
|
842
992
|
);
|
|
843
993
|
const purchase = (0, import_react2.useMemo)(() => {
|
|
844
|
-
const
|
|
845
|
-
const
|
|
994
|
+
const planPurchases = purchaseData.purchases.filter(isPlanPurchase);
|
|
995
|
+
const balanceTransactions = purchaseData.purchases.filter((p) => !isPlanPurchase(p));
|
|
996
|
+
const activePurchase = getPrimaryPurchase(planPurchases);
|
|
997
|
+
const activePaidPurchases = planPurchases.filter(
|
|
846
998
|
(p) => p.status === "active" && isPaidPurchase(p)
|
|
847
999
|
);
|
|
848
1000
|
const activePaidPurchase = activePaidPurchases.sort(
|
|
@@ -857,18 +1009,14 @@ var SolvaPayProvider = ({
|
|
|
857
1009
|
name: purchaseData.name,
|
|
858
1010
|
purchases: purchaseData.purchases,
|
|
859
1011
|
hasProduct: (productName) => {
|
|
860
|
-
return
|
|
861
|
-
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
862
|
-
);
|
|
863
|
-
},
|
|
864
|
-
hasPlan: (productName) => {
|
|
865
|
-
return purchaseData.purchases.some(
|
|
1012
|
+
return planPurchases.some(
|
|
866
1013
|
(p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
|
|
867
1014
|
);
|
|
868
1015
|
},
|
|
869
1016
|
activePurchase,
|
|
870
1017
|
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
871
|
-
activePaidPurchase
|
|
1018
|
+
activePaidPurchase,
|
|
1019
|
+
balanceTransactions
|
|
872
1020
|
};
|
|
873
1021
|
}, [loading, isRefetching, purchaseError, purchaseData, internalCustomerRef]);
|
|
874
1022
|
const balance = (0, import_react2.useMemo)(
|
|
@@ -881,7 +1029,15 @@ var SolvaPayProvider = ({
|
|
|
881
1029
|
refetch: fetchBalanceImpl,
|
|
882
1030
|
adjustBalance: adjustBalanceImpl
|
|
883
1031
|
}),
|
|
884
|
-
[
|
|
1032
|
+
[
|
|
1033
|
+
balanceLoading,
|
|
1034
|
+
creditsValue,
|
|
1035
|
+
displayCurrencyValue,
|
|
1036
|
+
creditsPerMinorUnitValue,
|
|
1037
|
+
displayExchangeRateValue,
|
|
1038
|
+
fetchBalanceImpl,
|
|
1039
|
+
adjustBalanceImpl
|
|
1040
|
+
]
|
|
885
1041
|
);
|
|
886
1042
|
const contextValue = (0, import_react2.useMemo)(
|
|
887
1043
|
() => ({
|
|
@@ -896,6 +1052,7 @@ var SolvaPayProvider = ({
|
|
|
896
1052
|
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
897
1053
|
updateCustomerRef,
|
|
898
1054
|
balance,
|
|
1055
|
+
refreshBootstrap,
|
|
899
1056
|
_config: configRef.current
|
|
900
1057
|
}),
|
|
901
1058
|
[
|
|
@@ -910,7 +1067,8 @@ var SolvaPayProvider = ({
|
|
|
910
1067
|
purchaseData.customerRef,
|
|
911
1068
|
internalCustomerRef,
|
|
912
1069
|
updateCustomerRef,
|
|
913
|
-
balance
|
|
1070
|
+
balance,
|
|
1071
|
+
refreshBootstrap
|
|
914
1072
|
]
|
|
915
1073
|
);
|
|
916
1074
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SolvaPayContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CopyProvider, { locale: config?.locale, copy: config?.copy, children }) });
|
|
@@ -1090,8 +1248,11 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
|
1090
1248
|
function getStripeCacheKey(publishableKey, accountId) {
|
|
1091
1249
|
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
1092
1250
|
}
|
|
1093
|
-
async function resolvePlanRef(productRef,
|
|
1251
|
+
async function resolvePlanRef(productRef, config) {
|
|
1252
|
+
const listPlansRoute = config?.api?.listPlans || "/api/list-plans";
|
|
1094
1253
|
const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
|
|
1254
|
+
const fetchFn = config?.fetch || fetch;
|
|
1255
|
+
const { headers } = await buildRequestHeaders(config);
|
|
1095
1256
|
const res = await fetchFn(url, { method: "GET", headers });
|
|
1096
1257
|
if (!res.ok) {
|
|
1097
1258
|
throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
|
|
@@ -1142,8 +1303,7 @@ function useCheckout(options) {
|
|
|
1142
1303
|
try {
|
|
1143
1304
|
let effectivePlanRef = planRef;
|
|
1144
1305
|
if (!effectivePlanRef && productRef) {
|
|
1145
|
-
|
|
1146
|
-
effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
|
|
1306
|
+
effectivePlanRef = await resolvePlanRef(productRef, _config);
|
|
1147
1307
|
setResolvedPlanRef(effectivePlanRef);
|
|
1148
1308
|
}
|
|
1149
1309
|
if (!effectivePlanRef) {
|
|
@@ -1534,27 +1694,41 @@ function usePlan(options) {
|
|
|
1534
1694
|
|
|
1535
1695
|
// src/hooks/useProduct.ts
|
|
1536
1696
|
var import_react8 = require("react");
|
|
1697
|
+
|
|
1698
|
+
// src/transport/cache-key.ts
|
|
1699
|
+
var transportIds = /* @__PURE__ */ new WeakMap();
|
|
1700
|
+
var nextTransportId = 0;
|
|
1701
|
+
function transportIdFor(transport) {
|
|
1702
|
+
let id = transportIds.get(transport);
|
|
1703
|
+
if (id === void 0) {
|
|
1704
|
+
id = ++nextTransportId;
|
|
1705
|
+
transportIds.set(transport, id);
|
|
1706
|
+
}
|
|
1707
|
+
return id;
|
|
1708
|
+
}
|
|
1709
|
+
function createTransportCacheKey(config, fallbackRoute, suffix) {
|
|
1710
|
+
const transport = config?.transport;
|
|
1711
|
+
if (transport) {
|
|
1712
|
+
const id = transportIdFor(transport);
|
|
1713
|
+
return suffix ? `transport:${id}:${suffix}` : `transport:${id}`;
|
|
1714
|
+
}
|
|
1715
|
+
return fallbackRoute;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/hooks/useProduct.ts
|
|
1537
1719
|
var productCache = /* @__PURE__ */ new Map();
|
|
1538
1720
|
var CACHE_DURATION3 = 5 * 60 * 1e3;
|
|
1539
|
-
function
|
|
1540
|
-
return config
|
|
1721
|
+
function cacheKeyFor(config, productRef) {
|
|
1722
|
+
return createTransportCacheKey(config, productRef, productRef);
|
|
1541
1723
|
}
|
|
1542
1724
|
async function fetchProduct(productRef, config) {
|
|
1543
|
-
const
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
const { headers } = await buildRequestHeaders(config);
|
|
1547
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
1548
|
-
if (!res.ok) {
|
|
1549
|
-
const error = new Error(`Failed to fetch product: ${res.statusText || res.status}`);
|
|
1550
|
-
config?.onError?.(error, "getProduct");
|
|
1551
|
-
throw error;
|
|
1552
|
-
}
|
|
1553
|
-
return await res.json();
|
|
1725
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
1726
|
+
if (!transport.getProduct) return null;
|
|
1727
|
+
return transport.getProduct(productRef);
|
|
1554
1728
|
}
|
|
1555
1729
|
function useProduct(productRef) {
|
|
1556
1730
|
const { _config } = useSolvaPay();
|
|
1557
|
-
const cacheKey = productRef
|
|
1731
|
+
const cacheKey = productRef ? cacheKeyFor(_config, productRef) : "";
|
|
1558
1732
|
const [product, setProduct] = (0, import_react8.useState)(
|
|
1559
1733
|
() => productRef ? productCache.get(cacheKey)?.product ?? null : null
|
|
1560
1734
|
);
|
|
@@ -1572,7 +1746,8 @@ function useProduct(productRef) {
|
|
|
1572
1746
|
setError(null);
|
|
1573
1747
|
return;
|
|
1574
1748
|
}
|
|
1575
|
-
const
|
|
1749
|
+
const key = cacheKeyFor(_config, productRef);
|
|
1750
|
+
const cached = productCache.get(key);
|
|
1576
1751
|
const now = Date.now();
|
|
1577
1752
|
if (!force && cached?.product && now - cached.timestamp < CACHE_DURATION3) {
|
|
1578
1753
|
setProduct(cached.product);
|
|
@@ -1597,12 +1772,26 @@ function useProduct(productRef) {
|
|
|
1597
1772
|
setLoading(true);
|
|
1598
1773
|
setError(null);
|
|
1599
1774
|
const promise = fetchProduct(productRef, _config);
|
|
1600
|
-
productCache.set(
|
|
1775
|
+
productCache.set(key, {
|
|
1776
|
+
product: cached?.product ?? null,
|
|
1777
|
+
promise,
|
|
1778
|
+
timestamp: now
|
|
1779
|
+
});
|
|
1601
1780
|
const p = await promise;
|
|
1602
|
-
|
|
1781
|
+
if (p === null) {
|
|
1782
|
+
productCache.set(key, {
|
|
1783
|
+
product: cached?.product ?? null,
|
|
1784
|
+
promise: null,
|
|
1785
|
+
timestamp: cached?.timestamp ?? now
|
|
1786
|
+
});
|
|
1787
|
+
setProduct(cached?.product ?? null);
|
|
1788
|
+
setLoading(false);
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
productCache.set(key, { product: p, promise: null, timestamp: now });
|
|
1603
1792
|
setProduct(p);
|
|
1604
1793
|
} catch (err) {
|
|
1605
|
-
productCache.delete(
|
|
1794
|
+
productCache.delete(key);
|
|
1606
1795
|
setError(err instanceof Error ? err : new Error("Failed to load product"));
|
|
1607
1796
|
} finally {
|
|
1608
1797
|
setLoading(false);
|
|
@@ -1724,6 +1913,9 @@ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
|
|
|
1724
1913
|
function getFractionDigits(currency) {
|
|
1725
1914
|
return ZERO_DECIMAL_CURRENCIES.has(currency.toLowerCase()) ? 0 : 2;
|
|
1726
1915
|
}
|
|
1916
|
+
function getMinorUnitsPerMajor(currency) {
|
|
1917
|
+
return getFractionDigits(currency) === 0 ? 1 : 100;
|
|
1918
|
+
}
|
|
1727
1919
|
function toMajorUnits(amountMinor, currency) {
|
|
1728
1920
|
const fractionDigits = getFractionDigits(currency);
|
|
1729
1921
|
return fractionDigits === 0 ? amountMinor : amountMinor / 100;
|
|
@@ -1733,7 +1925,10 @@ function formatPrice(amountMinor, currency, opts = {}) {
|
|
|
1733
1925
|
if (amountMinor === 0 && free !== "") {
|
|
1734
1926
|
return free;
|
|
1735
1927
|
}
|
|
1736
|
-
const
|
|
1928
|
+
const naturalFractionDigits = getFractionDigits(currency);
|
|
1929
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
1930
|
+
const isWhole = amountMinor % minorPerMajor === 0;
|
|
1931
|
+
const fractionDigits = isWhole ? 0 : naturalFractionDigits;
|
|
1737
1932
|
const major = toMajorUnits(amountMinor, currency);
|
|
1738
1933
|
const formatter = new Intl.NumberFormat(locale, {
|
|
1739
1934
|
style: "currency",
|
|
@@ -1871,24 +2066,17 @@ var import_react14 = require("react");
|
|
|
1871
2066
|
var import_react13 = require("react");
|
|
1872
2067
|
var merchantCache = /* @__PURE__ */ new Map();
|
|
1873
2068
|
var CACHE_DURATION4 = 5 * 60 * 1e3;
|
|
1874
|
-
function
|
|
1875
|
-
return config?.api?.getMerchant || "/api/merchant";
|
|
2069
|
+
function cacheKeyFor2(config) {
|
|
2070
|
+
return createTransportCacheKey(config, config?.api?.getMerchant || "/api/merchant");
|
|
1876
2071
|
}
|
|
1877
2072
|
async function fetchMerchant(config) {
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
1882
|
-
if (!res.ok) {
|
|
1883
|
-
const error = new Error(`Failed to fetch merchant: ${res.statusText || res.status}`);
|
|
1884
|
-
config?.onError?.(error, "getMerchant");
|
|
1885
|
-
throw error;
|
|
1886
|
-
}
|
|
1887
|
-
return await res.json();
|
|
2073
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
2074
|
+
if (!transport.getMerchant) return null;
|
|
2075
|
+
return transport.getMerchant();
|
|
1888
2076
|
}
|
|
1889
2077
|
function useMerchant() {
|
|
1890
2078
|
const { _config } = useSolvaPay();
|
|
1891
|
-
const key =
|
|
2079
|
+
const key = cacheKeyFor2(_config);
|
|
1892
2080
|
const [merchant, setMerchant] = (0, import_react13.useState)(
|
|
1893
2081
|
() => merchantCache.get(key)?.merchant ?? null
|
|
1894
2082
|
);
|
|
@@ -1924,8 +2112,22 @@ function useMerchant() {
|
|
|
1924
2112
|
setLoading(true);
|
|
1925
2113
|
setError(null);
|
|
1926
2114
|
const promise = fetchMerchant(_config);
|
|
1927
|
-
merchantCache.set(key, {
|
|
2115
|
+
merchantCache.set(key, {
|
|
2116
|
+
merchant: cached?.merchant ?? null,
|
|
2117
|
+
promise,
|
|
2118
|
+
timestamp: now
|
|
2119
|
+
});
|
|
1928
2120
|
const m = await promise;
|
|
2121
|
+
if (m === null) {
|
|
2122
|
+
merchantCache.set(key, {
|
|
2123
|
+
merchant: cached?.merchant ?? null,
|
|
2124
|
+
promise: null,
|
|
2125
|
+
timestamp: cached?.timestamp ?? now
|
|
2126
|
+
});
|
|
2127
|
+
setMerchant(cached?.merchant ?? null);
|
|
2128
|
+
setLoading(false);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
1929
2131
|
merchantCache.set(key, { merchant: m, promise: null, timestamp: now });
|
|
1930
2132
|
setMerchant(m);
|
|
1931
2133
|
} catch (err) {
|
|
@@ -2038,31 +2240,36 @@ var MandateText = (0, import_react14.forwardRef)(
|
|
|
2038
2240
|
|
|
2039
2241
|
// src/components/Spinner.tsx
|
|
2040
2242
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2041
|
-
var
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
}) => {
|
|
2045
|
-
const sizeClasses = {
|
|
2046
|
-
sm: "h-4 w-4",
|
|
2047
|
-
md: "h-5 w-5",
|
|
2048
|
-
lg: "h-6 w-6"
|
|
2049
|
-
};
|
|
2243
|
+
var SIZE_PX = { sm: 16, md: 20, lg: 24 };
|
|
2244
|
+
var Spinner = ({ className, size = "md" }) => {
|
|
2245
|
+
const px = SIZE_PX[size];
|
|
2050
2246
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2051
2247
|
"svg",
|
|
2052
2248
|
{
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2249
|
+
"data-solvapay-spinner": "",
|
|
2250
|
+
className,
|
|
2251
|
+
width: px,
|
|
2252
|
+
height: px,
|
|
2056
2253
|
viewBox: "0 0 24 24",
|
|
2057
|
-
|
|
2058
|
-
"aria-
|
|
2254
|
+
fill: "none",
|
|
2255
|
+
"aria-hidden": "true",
|
|
2059
2256
|
children: [
|
|
2060
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2257
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2258
|
+
"circle",
|
|
2259
|
+
{
|
|
2260
|
+
cx: "12",
|
|
2261
|
+
cy: "12",
|
|
2262
|
+
r: "10",
|
|
2263
|
+
stroke: "currentColor",
|
|
2264
|
+
strokeWidth: "4",
|
|
2265
|
+
strokeOpacity: "0.25"
|
|
2266
|
+
}
|
|
2267
|
+
),
|
|
2061
2268
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2062
2269
|
"path",
|
|
2063
2270
|
{
|
|
2064
|
-
className: "opacity-75",
|
|
2065
2271
|
fill: "currentColor",
|
|
2272
|
+
fillOpacity: "0.75",
|
|
2066
2273
|
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"
|
|
2067
2274
|
}
|
|
2068
2275
|
)
|
|
@@ -2080,6 +2287,13 @@ async function confirmPayment(input) {
|
|
|
2080
2287
|
if (!paymentElement) {
|
|
2081
2288
|
return { status: "error", message: copy.errors.cardElementMissing };
|
|
2082
2289
|
}
|
|
2290
|
+
const { error: submitError } = await elements.submit();
|
|
2291
|
+
if (submitError) {
|
|
2292
|
+
return {
|
|
2293
|
+
status: "error",
|
|
2294
|
+
message: submitError.message || copy.errors.paymentUnexpected
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2083
2297
|
const { error: error2, paymentIntent: paymentIntent2 } = await stripe.confirmPayment({
|
|
2084
2298
|
elements,
|
|
2085
2299
|
clientSecret,
|
|
@@ -2394,7 +2608,11 @@ var PaidInner = ({
|
|
|
2394
2608
|
const copy = useCopy();
|
|
2395
2609
|
const customer = useCustomer();
|
|
2396
2610
|
const { processPayment } = useSolvaPay();
|
|
2397
|
-
const { refetch } = usePurchase();
|
|
2611
|
+
const { refetch, hasPaidPurchase } = usePurchase();
|
|
2612
|
+
const hasPaidPurchaseRef = (0, import_react15.useRef)(hasPaidPurchase);
|
|
2613
|
+
(0, import_react15.useEffect)(() => {
|
|
2614
|
+
hasPaidPurchaseRef.current = hasPaidPurchase;
|
|
2615
|
+
}, [hasPaidPurchase]);
|
|
2398
2616
|
const [elementKind, setElementKind] = (0, import_react15.useState)(
|
|
2399
2617
|
children ? null : "payment-element"
|
|
2400
2618
|
);
|
|
@@ -2445,14 +2663,34 @@ var PaidInner = ({
|
|
|
2445
2663
|
refetchPurchase: refetch,
|
|
2446
2664
|
copy
|
|
2447
2665
|
});
|
|
2448
|
-
setIsProcessing(false);
|
|
2449
2666
|
if (reconcileResult.status === "success") {
|
|
2450
2667
|
const pi = paymentIntent;
|
|
2451
2668
|
onSuccess?.(pi);
|
|
2452
2669
|
const paid = { kind: "paid", paymentIntent: pi };
|
|
2453
2670
|
onResult?.(paid);
|
|
2671
|
+
const CONFIRMATION_TIMEOUT_MS = 1e4;
|
|
2672
|
+
const startedAt = Date.now();
|
|
2673
|
+
let attempt = 0;
|
|
2674
|
+
while (!hasPaidPurchaseRef.current && Date.now() - startedAt < CONFIRMATION_TIMEOUT_MS) {
|
|
2675
|
+
attempt += 1;
|
|
2676
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 1500)));
|
|
2677
|
+
if (hasPaidPurchaseRef.current) break;
|
|
2678
|
+
try {
|
|
2679
|
+
await refetch();
|
|
2680
|
+
} catch {
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
if (!hasPaidPurchaseRef.current) {
|
|
2684
|
+
const confirmationMsg = copy.errors.paymentConfirmationDelayed;
|
|
2685
|
+
setError(confirmationMsg);
|
|
2686
|
+
setIsProcessing(false);
|
|
2687
|
+
onError?.(new Error(confirmationMsg));
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
setIsProcessing(false);
|
|
2454
2691
|
return;
|
|
2455
2692
|
}
|
|
2693
|
+
setIsProcessing(false);
|
|
2456
2694
|
const msg = reconcileResult.status === "timeout" ? reconcileResult.error.message : copy.errors.paymentProcessingFailed;
|
|
2457
2695
|
setError(msg);
|
|
2458
2696
|
onError?.(reconcileResult.error);
|
|
@@ -2953,7 +3191,9 @@ function useTopupAmountSelector(options) {
|
|
|
2953
3191
|
if (resolvedAmount < minAmount) {
|
|
2954
3192
|
setError(
|
|
2955
3193
|
interpolate(copy.topup.minAmount, {
|
|
2956
|
-
amount:
|
|
3194
|
+
amount: formatPrice(minAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3195
|
+
free: ""
|
|
3196
|
+
})
|
|
2957
3197
|
})
|
|
2958
3198
|
);
|
|
2959
3199
|
return false;
|
|
@@ -2961,14 +3201,16 @@ function useTopupAmountSelector(options) {
|
|
|
2961
3201
|
if (resolvedAmount > maxAmount) {
|
|
2962
3202
|
setError(
|
|
2963
3203
|
interpolate(copy.topup.maxAmount, {
|
|
2964
|
-
amount:
|
|
3204
|
+
amount: formatPrice(maxAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3205
|
+
free: ""
|
|
3206
|
+
})
|
|
2965
3207
|
})
|
|
2966
3208
|
);
|
|
2967
3209
|
return false;
|
|
2968
3210
|
}
|
|
2969
3211
|
setError(null);
|
|
2970
3212
|
return true;
|
|
2971
|
-
}, [resolvedAmount, minAmount, maxAmount,
|
|
3213
|
+
}, [resolvedAmount, minAmount, maxAmount, currency, copy]);
|
|
2972
3214
|
const reset = (0, import_react16.useCallback)(() => {
|
|
2973
3215
|
setSelectedAmount(null);
|
|
2974
3216
|
setCustomAmountRaw("");
|
|
@@ -3008,15 +3250,31 @@ function usePickerCtx(part) {
|
|
|
3008
3250
|
}
|
|
3009
3251
|
return ctx;
|
|
3010
3252
|
}
|
|
3011
|
-
var Root3 = (0, import_react18.forwardRef)(function AmountPickerRoot({
|
|
3253
|
+
var Root3 = (0, import_react18.forwardRef)(function AmountPickerRoot({
|
|
3254
|
+
currency,
|
|
3255
|
+
minAmount,
|
|
3256
|
+
maxAmount,
|
|
3257
|
+
emit = "major",
|
|
3258
|
+
selector: externalSelector,
|
|
3259
|
+
onChange,
|
|
3260
|
+
asChild,
|
|
3261
|
+
children,
|
|
3262
|
+
...rest
|
|
3263
|
+
}, forwardedRef) {
|
|
3012
3264
|
const solva = (0, import_react18.useContext)(SolvaPayContext);
|
|
3013
3265
|
if (!solva) throw new MissingProviderError("AmountPicker");
|
|
3014
|
-
const
|
|
3266
|
+
const internalSelector = useTopupAmountSelector({ currency, minAmount, maxAmount });
|
|
3267
|
+
const selector = externalSelector ?? internalSelector;
|
|
3015
3268
|
const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
|
|
3269
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
3270
|
+
const resolvedAmountMinor = selector.resolvedAmount != null && selector.resolvedAmount > 0 ? Math.round(selector.resolvedAmount * minorPerMajor) : null;
|
|
3016
3271
|
(0, import_react18.useEffect)(() => {
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3272
|
+
if (emit === "minor") {
|
|
3273
|
+
onChange?.(resolvedAmountMinor);
|
|
3274
|
+
} else {
|
|
3275
|
+
onChange?.(selector.resolvedAmount);
|
|
3276
|
+
}
|
|
3277
|
+
}, [emit, selector.resolvedAmount, resolvedAmountMinor, onChange]);
|
|
3020
3278
|
const rate = displayExchangeRate ?? 1;
|
|
3021
3279
|
const estimatedCredits = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 && resolvedAmountMinor != null ? Math.floor(resolvedAmountMinor / rate * creditsPerMinorUnit) : null;
|
|
3022
3280
|
const isApproximate = rate !== 1;
|
|
@@ -3024,12 +3282,23 @@ var Root3 = (0, import_react18.forwardRef)(function AmountPickerRoot({ currency,
|
|
|
3024
3282
|
() => ({
|
|
3025
3283
|
...selector,
|
|
3026
3284
|
currency,
|
|
3285
|
+
emit,
|
|
3027
3286
|
creditsPerMinorUnit,
|
|
3028
3287
|
displayExchangeRate,
|
|
3029
3288
|
estimatedCredits,
|
|
3030
|
-
isApproximate
|
|
3289
|
+
isApproximate,
|
|
3290
|
+
resolvedAmountMinor
|
|
3031
3291
|
}),
|
|
3032
|
-
[
|
|
3292
|
+
[
|
|
3293
|
+
selector,
|
|
3294
|
+
currency,
|
|
3295
|
+
emit,
|
|
3296
|
+
creditsPerMinorUnit,
|
|
3297
|
+
displayExchangeRate,
|
|
3298
|
+
estimatedCredits,
|
|
3299
|
+
isApproximate,
|
|
3300
|
+
resolvedAmountMinor
|
|
3301
|
+
]
|
|
3033
3302
|
);
|
|
3034
3303
|
const Comp = asChild ? Slot : "div";
|
|
3035
3304
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AmountPickerContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Comp, { ref: forwardedRef, "data-solvapay-amount-picker": "", ...rest, children }) });
|
|
@@ -3048,19 +3317,18 @@ var Option = (0, import_react18.forwardRef)(function AmountPickerOption({ asChil
|
|
|
3048
3317
|
}),
|
|
3049
3318
|
...rest
|
|
3050
3319
|
};
|
|
3320
|
+
const defaultLabel = formatPrice(
|
|
3321
|
+
amount * getMinorUnitsPerMajor(ctx.currency),
|
|
3322
|
+
ctx.currency,
|
|
3323
|
+
{ free: "" }
|
|
3324
|
+
);
|
|
3051
3325
|
if (asChild) {
|
|
3052
3326
|
return (
|
|
3053
3327
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3054
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ??
|
|
3055
|
-
ctx.currencySymbol,
|
|
3056
|
-
amount.toLocaleString()
|
|
3057
|
-
] }) })
|
|
3328
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? defaultLabel })
|
|
3058
3329
|
);
|
|
3059
3330
|
}
|
|
3060
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ??
|
|
3061
|
-
ctx.currencySymbol,
|
|
3062
|
-
amount.toLocaleString()
|
|
3063
|
-
] }) });
|
|
3331
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? defaultLabel });
|
|
3064
3332
|
});
|
|
3065
3333
|
var Custom = (0, import_react18.forwardRef)(function AmountPickerCustom({ asChild, onChange, onFocus, ...rest }, forwardedRef) {
|
|
3066
3334
|
const ctx = usePickerCtx("Custom");
|
|
@@ -3092,9 +3360,12 @@ var Confirm = (0, import_react18.forwardRef)(function AmountPickerConfirm({ asCh
|
|
|
3092
3360
|
const ctx = usePickerCtx("Confirm");
|
|
3093
3361
|
const isDisabled = disabled || !ctx.resolvedAmount;
|
|
3094
3362
|
const handleConfirm = (0, import_react18.useCallback)(() => {
|
|
3095
|
-
if (ctx.validate()
|
|
3096
|
-
|
|
3363
|
+
if (!ctx.validate()) return;
|
|
3364
|
+
if (ctx.emit === "minor") {
|
|
3365
|
+
if (ctx.resolvedAmountMinor != null) onConfirm?.(ctx.resolvedAmountMinor);
|
|
3366
|
+
return;
|
|
3097
3367
|
}
|
|
3368
|
+
if (ctx.resolvedAmount != null) onConfirm?.(ctx.resolvedAmount);
|
|
3098
3369
|
}, [ctx, onConfirm]);
|
|
3099
3370
|
const commonProps = {
|
|
3100
3371
|
"data-solvapay-amount-picker-confirm": "",
|
|
@@ -3314,6 +3585,14 @@ var Inner = ({
|
|
|
3314
3585
|
}
|
|
3315
3586
|
setError(null);
|
|
3316
3587
|
setIsProcessing(true);
|
|
3588
|
+
const { error: submitError } = await elements.submit();
|
|
3589
|
+
if (submitError) {
|
|
3590
|
+
const msg = submitError.message || copy.errors.paymentUnexpected;
|
|
3591
|
+
setError(msg);
|
|
3592
|
+
setIsProcessing(false);
|
|
3593
|
+
onError?.(new Error(msg));
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3317
3596
|
const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
|
|
3318
3597
|
elements,
|
|
3319
3598
|
clientSecret,
|
|
@@ -3817,24 +4096,13 @@ var import_react27 = require("react");
|
|
|
3817
4096
|
|
|
3818
4097
|
// src/primitives/PlanSelector.tsx
|
|
3819
4098
|
var import_react25 = require("react");
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
function
|
|
3823
|
-
const
|
|
3824
|
-
if (
|
|
3825
|
-
|
|
4099
|
+
|
|
4100
|
+
// src/transport/list-plans.ts
|
|
4101
|
+
async function defaultListPlans(productRef, config) {
|
|
4102
|
+
const transport = config?.transport;
|
|
4103
|
+
if (transport) {
|
|
4104
|
+
return transport.listPlans ? transport.listPlans(productRef) : plansCache.get(productRef)?.plans ?? [];
|
|
3826
4105
|
}
|
|
3827
|
-
return ctx;
|
|
3828
|
-
}
|
|
3829
|
-
var CardContext = (0, import_react25.createContext)(null);
|
|
3830
|
-
function useCardContext(part) {
|
|
3831
|
-
const ctx = (0, import_react25.useContext)(CardContext);
|
|
3832
|
-
if (!ctx) {
|
|
3833
|
-
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Card>.`);
|
|
3834
|
-
}
|
|
3835
|
-
return ctx;
|
|
3836
|
-
}
|
|
3837
|
-
async function defaultListPlans(productRef, config) {
|
|
3838
4106
|
const base = config?.api?.listPlans || "/api/list-plans";
|
|
3839
4107
|
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
3840
4108
|
const fetchFn = config?.fetch || fetch;
|
|
@@ -3848,6 +4116,25 @@ async function defaultListPlans(productRef, config) {
|
|
|
3848
4116
|
const data = await res.json();
|
|
3849
4117
|
return data.plans ?? [];
|
|
3850
4118
|
}
|
|
4119
|
+
|
|
4120
|
+
// src/primitives/PlanSelector.tsx
|
|
4121
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
4122
|
+
var PlanSelectorContext = (0, import_react25.createContext)(null);
|
|
4123
|
+
function usePlanSelectorContext(part) {
|
|
4124
|
+
const ctx = (0, import_react25.useContext)(PlanSelectorContext);
|
|
4125
|
+
if (!ctx) {
|
|
4126
|
+
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Root>.`);
|
|
4127
|
+
}
|
|
4128
|
+
return ctx;
|
|
4129
|
+
}
|
|
4130
|
+
var CardContext = (0, import_react25.createContext)(null);
|
|
4131
|
+
function useCardContext(part) {
|
|
4132
|
+
const ctx = (0, import_react25.useContext)(CardContext);
|
|
4133
|
+
if (!ctx) {
|
|
4134
|
+
throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Card>.`);
|
|
4135
|
+
}
|
|
4136
|
+
return ctx;
|
|
4137
|
+
}
|
|
3851
4138
|
var Root6 = (0, import_react25.forwardRef)(function PlanSelectorRoot(props, forwardedRef) {
|
|
3852
4139
|
const {
|
|
3853
4140
|
productRef,
|
|
@@ -3871,7 +4158,7 @@ var Root6 = (0, import_react25.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
3871
4158
|
() => fetcher ?? ((ref) => defaultListPlans(ref, _config)),
|
|
3872
4159
|
[fetcher, _config]
|
|
3873
4160
|
);
|
|
3874
|
-
const { plans, selectedPlan, selectPlan, loading, error } = usePlans({
|
|
4161
|
+
const { plans, selectedPlan, selectPlan, setSelectedPlanIndex, loading, error } = usePlans({
|
|
3875
4162
|
productRef,
|
|
3876
4163
|
fetcher: effectiveFetcher,
|
|
3877
4164
|
filter,
|
|
@@ -3906,6 +4193,9 @@ var Root6 = (0, import_react25.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
3906
4193
|
},
|
|
3907
4194
|
[plans, selectPlan, onSelect]
|
|
3908
4195
|
);
|
|
4196
|
+
const clearSelection = (0, import_react25.useCallback)(() => {
|
|
4197
|
+
setSelectedPlanIndex(-1);
|
|
4198
|
+
}, [setSelectedPlanIndex]);
|
|
3909
4199
|
const selectedPlanRef = selectedPlan?.reference ?? null;
|
|
3910
4200
|
const ctx = (0, import_react25.useMemo)(
|
|
3911
4201
|
() => ({
|
|
@@ -3919,7 +4209,8 @@ var Root6 = (0, import_react25.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
3919
4209
|
isCurrent,
|
|
3920
4210
|
isFree,
|
|
3921
4211
|
isPopular,
|
|
3922
|
-
select
|
|
4212
|
+
select,
|
|
4213
|
+
clearSelection
|
|
3923
4214
|
}),
|
|
3924
4215
|
[
|
|
3925
4216
|
plans,
|
|
@@ -3932,7 +4223,8 @@ var Root6 = (0, import_react25.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
3932
4223
|
isCurrent,
|
|
3933
4224
|
isFree,
|
|
3934
4225
|
isPopular,
|
|
3935
|
-
select
|
|
4226
|
+
select,
|
|
4227
|
+
clearSelection
|
|
3936
4228
|
]
|
|
3937
4229
|
);
|
|
3938
4230
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -4013,9 +4305,11 @@ var Card = (0, import_react25.forwardRef)(function PlanSelectorCard({ asChild, o
|
|
|
4013
4305
|
});
|
|
4014
4306
|
var CardName = (0, import_react25.forwardRef)(function PlanSelectorCardName({ asChild, children, ...rest }, forwardedRef) {
|
|
4015
4307
|
const card = useCardContext("CardName");
|
|
4016
|
-
|
|
4308
|
+
const fallback = card.plan.requiresPayment === false ? "Free" : card.plan.type === "usage-based" ? "Pay as you go" : card.plan.type === "recurring" ? "Plan" : null;
|
|
4309
|
+
const label = card.plan.name ?? fallback;
|
|
4310
|
+
if (!label && children == null) return null;
|
|
4017
4311
|
const Comp = asChild ? Slot : "span";
|
|
4018
|
-
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ??
|
|
4312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ?? label });
|
|
4019
4313
|
});
|
|
4020
4314
|
var CardPrice = (0, import_react25.forwardRef)(function PlanSelectorCardPrice({ asChild, children, ...rest }, forwardedRef) {
|
|
4021
4315
|
const card = useCardContext("CardPrice");
|
|
@@ -4023,14 +4317,25 @@ var CardPrice = (0, import_react25.forwardRef)(function PlanSelectorCardPrice({
|
|
|
4023
4317
|
const copy = useCopy();
|
|
4024
4318
|
const formatted = (0, import_react25.useMemo)(() => {
|
|
4025
4319
|
if (card.isFree) return copy.planSelector.freeBadge;
|
|
4320
|
+
if (card.plan.type === "usage-based") {
|
|
4321
|
+
const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
|
|
4322
|
+
const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
|
|
4323
|
+
const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
|
|
4324
|
+
locale,
|
|
4325
|
+
free: ""
|
|
4326
|
+
});
|
|
4327
|
+
return `${rate} / call`;
|
|
4328
|
+
}
|
|
4026
4329
|
return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
|
|
4027
4330
|
locale,
|
|
4028
4331
|
free: copy.interval.free
|
|
4029
4332
|
});
|
|
4030
4333
|
}, [
|
|
4031
4334
|
card.isFree,
|
|
4335
|
+
card.plan.type,
|
|
4032
4336
|
card.plan.price,
|
|
4033
4337
|
card.plan.currency,
|
|
4338
|
+
card.plan.creditsPerUnit,
|
|
4034
4339
|
locale,
|
|
4035
4340
|
copy.planSelector.freeBadge,
|
|
4036
4341
|
copy.interval.free
|
|
@@ -4041,11 +4346,22 @@ var CardPrice = (0, import_react25.forwardRef)(function PlanSelectorCardPrice({
|
|
|
4041
4346
|
var CardInterval = (0, import_react25.forwardRef)(function PlanSelectorCardInterval({ asChild, children, ...rest }, forwardedRef) {
|
|
4042
4347
|
const card = useCardContext("CardInterval");
|
|
4043
4348
|
const copy = useCopy();
|
|
4044
|
-
if (card.isFree ||
|
|
4045
|
-
const
|
|
4349
|
+
if (card.isFree || card.plan.type === "usage-based") return null;
|
|
4350
|
+
const rawInterval = card.plan.interval ?? normalizeBillingCycle(card.plan.billingCycle);
|
|
4351
|
+
if (!rawInterval) return null;
|
|
4352
|
+
const text = interpolate(copy.planSelector.perIntervalShort, { interval: rawInterval });
|
|
4046
4353
|
const Comp = asChild ? Slot : "span";
|
|
4047
4354
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-interval": "", ...rest, children: children ?? text });
|
|
4048
4355
|
});
|
|
4356
|
+
function normalizeBillingCycle(cycle) {
|
|
4357
|
+
if (!cycle) return null;
|
|
4358
|
+
const lc = cycle.toLowerCase();
|
|
4359
|
+
if (lc === "monthly" || lc === "month") return "month";
|
|
4360
|
+
if (lc === "yearly" || lc === "annually" || lc === "annual" || lc === "year") return "year";
|
|
4361
|
+
if (lc === "weekly" || lc === "week") return "week";
|
|
4362
|
+
if (lc === "daily" || lc === "day") return "day";
|
|
4363
|
+
return cycle;
|
|
4364
|
+
}
|
|
4049
4365
|
var CardBadge = (0, import_react25.forwardRef)(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
|
|
4050
4366
|
const card = useCardContext("CardBadge");
|
|
4051
4367
|
const copy = useCopy();
|
|
@@ -4185,16 +4501,19 @@ var Root7 = (0, import_react26.forwardRef)(function ActivationFlowRoot({
|
|
|
4185
4501
|
const retryActivation = (0, import_react26.useCallback)(async () => {
|
|
4186
4502
|
if (!resolvedPlanRef) return;
|
|
4187
4503
|
setStep("retrying");
|
|
4188
|
-
const
|
|
4504
|
+
const amountMinor2 = Math.round(
|
|
4505
|
+
(amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
|
|
4506
|
+
);
|
|
4189
4507
|
const rate = displayExchangeRate ?? 1;
|
|
4190
4508
|
if (creditsPerMinorUnit) {
|
|
4191
|
-
adjustBalance(Math.floor(
|
|
4509
|
+
adjustBalance(Math.floor(amountMinor2 / rate * creditsPerMinorUnit));
|
|
4192
4510
|
}
|
|
4193
4511
|
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
4194
4512
|
await activate({ productRef, planRef: resolvedPlanRef });
|
|
4195
4513
|
}, [
|
|
4196
4514
|
resolvedPlanRef,
|
|
4197
4515
|
amountSelector.resolvedAmount,
|
|
4516
|
+
currency,
|
|
4198
4517
|
displayExchangeRate,
|
|
4199
4518
|
creditsPerMinorUnit,
|
|
4200
4519
|
adjustBalance,
|
|
@@ -4221,7 +4540,9 @@ var Root7 = (0, import_react26.forwardRef)(function ActivationFlowRoot({
|
|
|
4221
4540
|
calledSuccessRef.current = false;
|
|
4222
4541
|
setStep("summary");
|
|
4223
4542
|
}, [reset]);
|
|
4224
|
-
const
|
|
4543
|
+
const amountMinor = Math.round(
|
|
4544
|
+
(amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
|
|
4545
|
+
);
|
|
4225
4546
|
const ctx = (0, import_react26.useMemo)(
|
|
4226
4547
|
() => ({
|
|
4227
4548
|
step,
|
|
@@ -4229,7 +4550,11 @@ var Root7 = (0, import_react26.forwardRef)(function ActivationFlowRoot({
|
|
|
4229
4550
|
productRef,
|
|
4230
4551
|
planRef: resolvedPlanRef ?? "",
|
|
4231
4552
|
amountSelector,
|
|
4232
|
-
|
|
4553
|
+
amountMinor,
|
|
4554
|
+
// Deprecated alias — same value as amountMinor so legacy consumers
|
|
4555
|
+
// keep working after the zero-decimal fix (previously this was
|
|
4556
|
+
// `resolvedAmount * 100`, which over-billed JPY/KRW by 100x).
|
|
4557
|
+
amountCents: amountMinor,
|
|
4233
4558
|
currency,
|
|
4234
4559
|
activate: handleActivate,
|
|
4235
4560
|
reset: resetFlow,
|
|
@@ -4245,7 +4570,7 @@ var Root7 = (0, import_react26.forwardRef)(function ActivationFlowRoot({
|
|
|
4245
4570
|
productRef,
|
|
4246
4571
|
resolvedPlanRef,
|
|
4247
4572
|
amountSelector,
|
|
4248
|
-
|
|
4573
|
+
amountMinor,
|
|
4249
4574
|
currency,
|
|
4250
4575
|
handleActivate,
|
|
4251
4576
|
resetFlow,
|
|
@@ -4307,7 +4632,7 @@ var ActivateButton = (0, import_react26.forwardRef)(
|
|
|
4307
4632
|
var AmountPickerMount = ({ children }) => {
|
|
4308
4633
|
const ctx = useFlowCtx("AmountPicker");
|
|
4309
4634
|
if (ctx.step !== "selectAmount") return null;
|
|
4310
|
-
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AmountPicker.Root, { currency: ctx.currency, children }) });
|
|
4635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AmountPicker.Root, { currency: ctx.currency, selector: ctx.amountSelector, children }) });
|
|
4311
4636
|
};
|
|
4312
4637
|
var ContinueButton = (0, import_react26.forwardRef)(
|
|
4313
4638
|
function ActivationFlowContinueButton({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
@@ -4450,7 +4775,7 @@ var TopupPaymentStep = () => {
|
|
|
4450
4775
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4451
4776
|
TopupForm2,
|
|
4452
4777
|
{
|
|
4453
|
-
amount: ctx.
|
|
4778
|
+
amount: ctx.amountMinor,
|
|
4454
4779
|
currency: ctx.currency,
|
|
4455
4780
|
onSuccess: ctx.onTopupSuccess,
|
|
4456
4781
|
onError: (err) => ctx.onError?.(err instanceof Error ? err : new Error(String(err)))
|
|
@@ -4631,26 +4956,12 @@ var CheckoutLayout = ({
|
|
|
4631
4956
|
}
|
|
4632
4957
|
);
|
|
4633
4958
|
};
|
|
4634
|
-
async function defaultListPlans2(productRef, config) {
|
|
4635
|
-
const base = config?.api?.listPlans || "/api/list-plans";
|
|
4636
|
-
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
4637
|
-
const fetchFn = config?.fetch || fetch;
|
|
4638
|
-
const { headers } = await buildRequestHeaders(config);
|
|
4639
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
4640
|
-
if (!res.ok) {
|
|
4641
|
-
const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
|
|
4642
|
-
config?.onError?.(error, "listPlans");
|
|
4643
|
-
throw error;
|
|
4644
|
-
}
|
|
4645
|
-
const data = await res.json();
|
|
4646
|
-
return data.plans ?? [];
|
|
4647
|
-
}
|
|
4648
4959
|
var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
|
|
4649
4960
|
const copy = useCopy();
|
|
4650
4961
|
const solva = (0, import_react27.useContext)(SolvaPayContext);
|
|
4651
4962
|
const config = solva?._config;
|
|
4652
4963
|
const fetcher = (0, import_react27.useCallback)(
|
|
4653
|
-
(ref) =>
|
|
4964
|
+
(ref) => defaultListPlans(ref, config),
|
|
4654
4965
|
[config]
|
|
4655
4966
|
);
|
|
4656
4967
|
const { plans, selectedPlan } = usePlans({
|
|
@@ -4888,7 +5199,7 @@ function usePurchaseStatus() {
|
|
|
4888
5199
|
}, []);
|
|
4889
5200
|
const purchaseData = (0, import_react30.useMemo)(() => {
|
|
4890
5201
|
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
4891
|
-
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
5202
|
+
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p) && isPlanPurchase(p);
|
|
4892
5203
|
});
|
|
4893
5204
|
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
4894
5205
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
@@ -5259,6 +5570,737 @@ var CreditGate2 = ({
|
|
|
5259
5570
|
}
|
|
5260
5571
|
);
|
|
5261
5572
|
};
|
|
5573
|
+
|
|
5574
|
+
// src/hooks/usePaymentMethod.ts
|
|
5575
|
+
var import_react33 = require("react");
|
|
5576
|
+
var paymentMethodCache = /* @__PURE__ */ new Map();
|
|
5577
|
+
var CACHE_DURATION5 = 5 * 60 * 1e3;
|
|
5578
|
+
function cacheKeyFor3(config) {
|
|
5579
|
+
return createTransportCacheKey(
|
|
5580
|
+
config,
|
|
5581
|
+
config?.api?.getPaymentMethod || "/api/payment-method"
|
|
5582
|
+
);
|
|
5583
|
+
}
|
|
5584
|
+
async function fetchPaymentMethod(config) {
|
|
5585
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
5586
|
+
if (!transport.getPaymentMethod) return null;
|
|
5587
|
+
return transport.getPaymentMethod();
|
|
5588
|
+
}
|
|
5589
|
+
function usePaymentMethod() {
|
|
5590
|
+
const { _config } = useSolvaPay();
|
|
5591
|
+
const key = cacheKeyFor3(_config);
|
|
5592
|
+
const [paymentMethod, setPaymentMethod] = (0, import_react33.useState)(
|
|
5593
|
+
() => paymentMethodCache.get(key)?.paymentMethod ?? null
|
|
5594
|
+
);
|
|
5595
|
+
const [loading, setLoading] = (0, import_react33.useState)(() => {
|
|
5596
|
+
const cached = paymentMethodCache.get(key);
|
|
5597
|
+
return !cached || !cached.paymentMethod && !cached.promise;
|
|
5598
|
+
});
|
|
5599
|
+
const [error, setError] = (0, import_react33.useState)(null);
|
|
5600
|
+
const load = (0, import_react33.useCallback)(
|
|
5601
|
+
async (force = false) => {
|
|
5602
|
+
const cached = paymentMethodCache.get(key);
|
|
5603
|
+
const now = Date.now();
|
|
5604
|
+
if (!force && cached?.paymentMethod && now - cached.timestamp < CACHE_DURATION5) {
|
|
5605
|
+
setPaymentMethod(cached.paymentMethod);
|
|
5606
|
+
setLoading(false);
|
|
5607
|
+
setError(null);
|
|
5608
|
+
return;
|
|
5609
|
+
}
|
|
5610
|
+
if (!force && cached?.promise) {
|
|
5611
|
+
try {
|
|
5612
|
+
setLoading(true);
|
|
5613
|
+
const pm = await cached.promise;
|
|
5614
|
+
setPaymentMethod(pm);
|
|
5615
|
+
setError(null);
|
|
5616
|
+
} catch (err) {
|
|
5617
|
+
setError(err instanceof Error ? err : new Error("Failed to load payment method"));
|
|
5618
|
+
} finally {
|
|
5619
|
+
setLoading(false);
|
|
5620
|
+
}
|
|
5621
|
+
return;
|
|
5622
|
+
}
|
|
5623
|
+
try {
|
|
5624
|
+
setLoading(true);
|
|
5625
|
+
setError(null);
|
|
5626
|
+
const promise = fetchPaymentMethod(_config);
|
|
5627
|
+
paymentMethodCache.set(key, {
|
|
5628
|
+
paymentMethod: cached?.paymentMethod ?? null,
|
|
5629
|
+
promise,
|
|
5630
|
+
timestamp: now
|
|
5631
|
+
});
|
|
5632
|
+
const pm = await promise;
|
|
5633
|
+
if (pm === null) {
|
|
5634
|
+
paymentMethodCache.set(key, {
|
|
5635
|
+
paymentMethod: cached?.paymentMethod ?? null,
|
|
5636
|
+
promise: null,
|
|
5637
|
+
timestamp: cached?.timestamp ?? now
|
|
5638
|
+
});
|
|
5639
|
+
setPaymentMethod(cached?.paymentMethod ?? null);
|
|
5640
|
+
setLoading(false);
|
|
5641
|
+
return;
|
|
5642
|
+
}
|
|
5643
|
+
paymentMethodCache.set(key, { paymentMethod: pm, promise: null, timestamp: now });
|
|
5644
|
+
setPaymentMethod(pm);
|
|
5645
|
+
} catch (err) {
|
|
5646
|
+
paymentMethodCache.delete(key);
|
|
5647
|
+
setError(err instanceof Error ? err : new Error("Failed to load payment method"));
|
|
5648
|
+
setPaymentMethod(null);
|
|
5649
|
+
} finally {
|
|
5650
|
+
setLoading(false);
|
|
5651
|
+
}
|
|
5652
|
+
},
|
|
5653
|
+
[_config, key]
|
|
5654
|
+
);
|
|
5655
|
+
(0, import_react33.useEffect)(() => {
|
|
5656
|
+
load();
|
|
5657
|
+
}, [load]);
|
|
5658
|
+
return {
|
|
5659
|
+
paymentMethod,
|
|
5660
|
+
loading,
|
|
5661
|
+
error,
|
|
5662
|
+
refetch: () => load(true)
|
|
5663
|
+
};
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
// src/components/UpdatePaymentMethodButton.tsx
|
|
5667
|
+
var import_react36 = require("react");
|
|
5668
|
+
|
|
5669
|
+
// src/components/LaunchCustomerPortalButton.tsx
|
|
5670
|
+
var import_react35 = require("react");
|
|
5671
|
+
|
|
5672
|
+
// src/hooks/useTransport.ts
|
|
5673
|
+
var import_react34 = require("react");
|
|
5674
|
+
function useTransport() {
|
|
5675
|
+
const { _config } = useSolvaPay();
|
|
5676
|
+
return (0, import_react34.useMemo)(() => _config?.transport ?? createHttpTransport(_config), [_config]);
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
// src/components/ExternalLinkGlyph.tsx
|
|
5680
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
5681
|
+
var ExternalLinkGlyph = ({ className }) => {
|
|
5682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
|
|
5683
|
+
"svg",
|
|
5684
|
+
{
|
|
5685
|
+
className: `solvapay-mcp-external-glyph${className ? ` ${className}` : ""}`,
|
|
5686
|
+
width: "0.9em",
|
|
5687
|
+
height: "0.9em",
|
|
5688
|
+
viewBox: "0 0 24 24",
|
|
5689
|
+
fill: "none",
|
|
5690
|
+
stroke: "currentColor",
|
|
5691
|
+
strokeWidth: "2",
|
|
5692
|
+
strokeLinecap: "round",
|
|
5693
|
+
strokeLinejoin: "round",
|
|
5694
|
+
"aria-hidden": "true",
|
|
5695
|
+
focusable: "false",
|
|
5696
|
+
children: [
|
|
5697
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }),
|
|
5698
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M15 3h6v6" }),
|
|
5699
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "m10 14 11-11" })
|
|
5700
|
+
]
|
|
5701
|
+
}
|
|
5702
|
+
);
|
|
5703
|
+
};
|
|
5704
|
+
|
|
5705
|
+
// src/components/LaunchCustomerPortalButton.tsx
|
|
5706
|
+
var import_jsx_runtime31 = (
|
|
5707
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5708
|
+
require("react/jsx-runtime")
|
|
5709
|
+
);
|
|
5710
|
+
var LaunchCustomerPortalButton = (0, import_react35.forwardRef)(function LaunchCustomerPortalButton2({
|
|
5711
|
+
children,
|
|
5712
|
+
onLaunch,
|
|
5713
|
+
onError,
|
|
5714
|
+
onClick,
|
|
5715
|
+
loadingClassName,
|
|
5716
|
+
errorClassName,
|
|
5717
|
+
asChild,
|
|
5718
|
+
...rest
|
|
5719
|
+
}, forwardedRef) {
|
|
5720
|
+
const transport = useTransport();
|
|
5721
|
+
const copy = useCopy();
|
|
5722
|
+
const [state, setState] = (0, import_react35.useState)({ status: "loading" });
|
|
5723
|
+
const onErrorRef = (0, import_react35.useRef)(onError);
|
|
5724
|
+
(0, import_react35.useEffect)(() => {
|
|
5725
|
+
onErrorRef.current = onError;
|
|
5726
|
+
}, [onError]);
|
|
5727
|
+
(0, import_react35.useEffect)(() => {
|
|
5728
|
+
let cancelled = false;
|
|
5729
|
+
transport.createCustomerSession().then(({ customerUrl }) => {
|
|
5730
|
+
if (cancelled) return;
|
|
5731
|
+
setState({ status: "ready", href: customerUrl });
|
|
5732
|
+
}).catch((err) => {
|
|
5733
|
+
if (cancelled) return;
|
|
5734
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5735
|
+
setState({ status: "error", message: error.message });
|
|
5736
|
+
onErrorRef.current?.(error);
|
|
5737
|
+
});
|
|
5738
|
+
return () => {
|
|
5739
|
+
cancelled = true;
|
|
5740
|
+
};
|
|
5741
|
+
}, [transport]);
|
|
5742
|
+
if (state.status === "ready") {
|
|
5743
|
+
const label = children ?? copy.customerPortal.launchButton;
|
|
5744
|
+
const labelText = typeof label === "string" ? label : copy.customerPortal.launchButton;
|
|
5745
|
+
const readyProps = {
|
|
5746
|
+
href: state.href,
|
|
5747
|
+
target: "_blank",
|
|
5748
|
+
rel: "noopener noreferrer",
|
|
5749
|
+
"data-solvapay-launch-customer-portal": "",
|
|
5750
|
+
"data-state": "ready",
|
|
5751
|
+
"aria-label": `${labelText} (opens in a new tab)`,
|
|
5752
|
+
onClick: composeEventHandlers(onClick, () => {
|
|
5753
|
+
onLaunch?.(state.href);
|
|
5754
|
+
}),
|
|
5755
|
+
...rest
|
|
5756
|
+
};
|
|
5757
|
+
if (asChild) {
|
|
5758
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Slot, { ref: forwardedRef, ...readyProps, children: label });
|
|
5759
|
+
}
|
|
5760
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("a", { ref: forwardedRef, ...readyProps, children: [
|
|
5761
|
+
label,
|
|
5762
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLinkGlyph, {})
|
|
5763
|
+
] });
|
|
5764
|
+
}
|
|
5765
|
+
if (state.status === "error") {
|
|
5766
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
5767
|
+
"button",
|
|
5768
|
+
{
|
|
5769
|
+
type: "button",
|
|
5770
|
+
className: errorClassName,
|
|
5771
|
+
"data-solvapay-launch-customer-portal": "",
|
|
5772
|
+
"data-state": "error",
|
|
5773
|
+
"aria-disabled": true,
|
|
5774
|
+
disabled: true,
|
|
5775
|
+
children: children ?? copy.customerPortal.launchButton
|
|
5776
|
+
}
|
|
5777
|
+
);
|
|
5778
|
+
}
|
|
5779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
5780
|
+
"button",
|
|
5781
|
+
{
|
|
5782
|
+
type: "button",
|
|
5783
|
+
className: loadingClassName,
|
|
5784
|
+
"data-solvapay-launch-customer-portal": "",
|
|
5785
|
+
"data-state": "loading",
|
|
5786
|
+
"aria-busy": true,
|
|
5787
|
+
"aria-disabled": true,
|
|
5788
|
+
disabled: true,
|
|
5789
|
+
children: copy.customerPortal.loadingLabel
|
|
5790
|
+
}
|
|
5791
|
+
);
|
|
5792
|
+
});
|
|
5793
|
+
|
|
5794
|
+
// src/components/UpdatePaymentMethodButton.tsx
|
|
5795
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
5796
|
+
var UpdatePaymentMethodButton = (0, import_react36.forwardRef)(function UpdatePaymentMethodButton2({ mode = "portal", children, ...rest }, forwardedRef) {
|
|
5797
|
+
const copy = useCopy();
|
|
5798
|
+
if (mode !== "portal") {
|
|
5799
|
+
throw new Error(
|
|
5800
|
+
`<UpdatePaymentMethodButton mode="${mode}"> is not implemented yet \u2014 use mode="portal" or omit the prop.`
|
|
5801
|
+
);
|
|
5802
|
+
}
|
|
5803
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
5804
|
+
LaunchCustomerPortalButton,
|
|
5805
|
+
{
|
|
5806
|
+
ref: forwardedRef,
|
|
5807
|
+
"data-solvapay-update-payment-method": "",
|
|
5808
|
+
...rest,
|
|
5809
|
+
children: children ?? copy.currentPlan.updatePaymentButton
|
|
5810
|
+
}
|
|
5811
|
+
);
|
|
5812
|
+
});
|
|
5813
|
+
|
|
5814
|
+
// src/primitives/UsageMeter.tsx
|
|
5815
|
+
var import_react38 = require("react");
|
|
5816
|
+
|
|
5817
|
+
// src/hooks/useUsage.ts
|
|
5818
|
+
var import_react37 = require("react");
|
|
5819
|
+
function deriveUsage(purchase) {
|
|
5820
|
+
if (!purchase) return null;
|
|
5821
|
+
const snap = purchase.planSnapshot;
|
|
5822
|
+
const usage = purchase.usage;
|
|
5823
|
+
const meterRef = snap?.meterRef ?? null;
|
|
5824
|
+
const total = typeof snap?.limit === "number" ? snap.limit : null;
|
|
5825
|
+
if (meterRef === null && !usage) return null;
|
|
5826
|
+
const used = typeof usage?.used === "number" ? usage.used : 0;
|
|
5827
|
+
const remaining = total !== null ? Math.max(0, total - used) : null;
|
|
5828
|
+
const percentUsed = total !== null && total > 0 ? Math.min(100, Math.round(used / total * 1e4) / 100) : null;
|
|
5829
|
+
return {
|
|
5830
|
+
meterRef,
|
|
5831
|
+
total,
|
|
5832
|
+
used,
|
|
5833
|
+
remaining,
|
|
5834
|
+
percentUsed,
|
|
5835
|
+
...usage?.periodStart ? { periodStart: usage.periodStart } : {},
|
|
5836
|
+
...usage?.periodEnd ? { periodEnd: usage.periodEnd } : {},
|
|
5837
|
+
...purchase.reference ? { purchaseRef: purchase.reference } : {}
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
function useUsage() {
|
|
5841
|
+
const { activePurchase, refetch: refetchPurchase, loading: purchaseLoading } = usePurchase();
|
|
5842
|
+
const transport = useTransport();
|
|
5843
|
+
const [override, setOverride] = (0, import_react37.useState)(null);
|
|
5844
|
+
const [error, setError] = (0, import_react37.useState)(null);
|
|
5845
|
+
const [transportLoading, setTransportLoading] = (0, import_react37.useState)(false);
|
|
5846
|
+
const derived = (0, import_react37.useMemo)(() => deriveUsage(activePurchase ?? null), [activePurchase]);
|
|
5847
|
+
const activePurchaseRef = activePurchase?.reference ?? null;
|
|
5848
|
+
(0, import_react37.useEffect)(() => {
|
|
5849
|
+
setOverride(null);
|
|
5850
|
+
}, [activePurchaseRef]);
|
|
5851
|
+
const usage = override ?? derived;
|
|
5852
|
+
const refetch = (0, import_react37.useCallback)(async () => {
|
|
5853
|
+
setError(null);
|
|
5854
|
+
if (typeof transport.getUsage === "function") {
|
|
5855
|
+
setTransportLoading(true);
|
|
5856
|
+
try {
|
|
5857
|
+
const next = await transport.getUsage();
|
|
5858
|
+
setOverride(next);
|
|
5859
|
+
} catch (err) {
|
|
5860
|
+
setError(err instanceof Error ? err : new Error("Failed to load usage"));
|
|
5861
|
+
} finally {
|
|
5862
|
+
setTransportLoading(false);
|
|
5863
|
+
}
|
|
5864
|
+
return;
|
|
5865
|
+
}
|
|
5866
|
+
try {
|
|
5867
|
+
await refetchPurchase();
|
|
5868
|
+
} catch (err) {
|
|
5869
|
+
setError(err instanceof Error ? err : new Error("Failed to refetch purchase"));
|
|
5870
|
+
}
|
|
5871
|
+
}, [transport, refetchPurchase]);
|
|
5872
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
5873
|
+
const isApproachingLimit = percentUsed !== null && percentUsed >= 80 && percentUsed < 100;
|
|
5874
|
+
const isAtLimit = percentUsed !== null && percentUsed >= 100;
|
|
5875
|
+
const isUnlimited = usage !== null && usage.total === null;
|
|
5876
|
+
return {
|
|
5877
|
+
usage,
|
|
5878
|
+
loading: purchaseLoading || transportLoading,
|
|
5879
|
+
error,
|
|
5880
|
+
refetch,
|
|
5881
|
+
percentUsed,
|
|
5882
|
+
isApproachingLimit,
|
|
5883
|
+
isAtLimit,
|
|
5884
|
+
isUnlimited,
|
|
5885
|
+
meterRef: usage?.meterRef ?? null
|
|
5886
|
+
};
|
|
5887
|
+
}
|
|
5888
|
+
|
|
5889
|
+
// src/primitives/UsageMeter.tsx
|
|
5890
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
5891
|
+
var UsageMeterContext = (0, import_react38.createContext)(null);
|
|
5892
|
+
function useUsageMeterCtx(part) {
|
|
5893
|
+
const ctx = (0, import_react38.useContext)(UsageMeterContext);
|
|
5894
|
+
if (!ctx) {
|
|
5895
|
+
throw new Error(`UsageMeter.${part} must be rendered inside <UsageMeter.Root>.`);
|
|
5896
|
+
}
|
|
5897
|
+
return ctx;
|
|
5898
|
+
}
|
|
5899
|
+
var Root10 = (0, import_react38.forwardRef)(function UsageMeterRoot({
|
|
5900
|
+
warningAt = 75,
|
|
5901
|
+
criticalAt = 90,
|
|
5902
|
+
classNames,
|
|
5903
|
+
asChild,
|
|
5904
|
+
usageOverride,
|
|
5905
|
+
children,
|
|
5906
|
+
className,
|
|
5907
|
+
...rest
|
|
5908
|
+
}, forwardedRef) {
|
|
5909
|
+
const hookResult = useUsage();
|
|
5910
|
+
const usage = usageOverride !== void 0 ? usageOverride : hookResult.usage;
|
|
5911
|
+
const loading = hookResult.loading && !usage;
|
|
5912
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
5913
|
+
const state = loading ? "loading" : percentUsed === null ? "safe" : percentUsed >= criticalAt ? "critical" : percentUsed >= warningAt ? "warning" : "safe";
|
|
5914
|
+
const ctx = (0, import_react38.useMemo)(
|
|
5915
|
+
() => ({
|
|
5916
|
+
usage,
|
|
5917
|
+
loading,
|
|
5918
|
+
error: hookResult.error,
|
|
5919
|
+
percentUsed,
|
|
5920
|
+
isApproachingLimit: hookResult.isApproachingLimit,
|
|
5921
|
+
isAtLimit: hookResult.isAtLimit,
|
|
5922
|
+
isUnlimited: hookResult.isUnlimited,
|
|
5923
|
+
state,
|
|
5924
|
+
warningAt,
|
|
5925
|
+
criticalAt
|
|
5926
|
+
}),
|
|
5927
|
+
[
|
|
5928
|
+
usage,
|
|
5929
|
+
loading,
|
|
5930
|
+
hookResult.error,
|
|
5931
|
+
percentUsed,
|
|
5932
|
+
hookResult.isApproachingLimit,
|
|
5933
|
+
hookResult.isAtLimit,
|
|
5934
|
+
hookResult.isUnlimited,
|
|
5935
|
+
state,
|
|
5936
|
+
warningAt,
|
|
5937
|
+
criticalAt
|
|
5938
|
+
]
|
|
5939
|
+
);
|
|
5940
|
+
const rootClass = [classNames?.root ?? "solvapay-usage-meter", className].filter(Boolean).join(" ");
|
|
5941
|
+
const Comp = asChild ? Slot : "div";
|
|
5942
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(UsageMeterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
5943
|
+
Comp,
|
|
5944
|
+
{
|
|
5945
|
+
ref: forwardedRef,
|
|
5946
|
+
"data-solvapay-usage-meter": "",
|
|
5947
|
+
"data-state": state,
|
|
5948
|
+
className: rootClass,
|
|
5949
|
+
...rest,
|
|
5950
|
+
children
|
|
5951
|
+
}
|
|
5952
|
+
) });
|
|
5953
|
+
});
|
|
5954
|
+
var Bar = (0, import_react38.forwardRef)(function UsageMeterBar({ className, style, ...rest }, forwardedRef) {
|
|
5955
|
+
const ctx = useUsageMeterCtx("Bar");
|
|
5956
|
+
const width = ctx.percentUsed === null ? 0 : Math.max(0, Math.min(100, ctx.percentUsed));
|
|
5957
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
5958
|
+
"div",
|
|
5959
|
+
{
|
|
5960
|
+
ref: forwardedRef,
|
|
5961
|
+
"data-solvapay-usage-meter-bar": "",
|
|
5962
|
+
"data-state": ctx.state,
|
|
5963
|
+
role: "progressbar",
|
|
5964
|
+
"aria-valuenow": Math.round(width),
|
|
5965
|
+
"aria-valuemin": 0,
|
|
5966
|
+
"aria-valuemax": 100,
|
|
5967
|
+
className: className ?? "solvapay-usage-meter-bar",
|
|
5968
|
+
style: {
|
|
5969
|
+
// Expose as a CSS custom property so default styles can drive
|
|
5970
|
+
// `width: var(--solvapay-usage-meter-fill)` without inline styling.
|
|
5971
|
+
["--solvapay-usage-meter-fill"]: `${width}%`,
|
|
5972
|
+
...style
|
|
5973
|
+
},
|
|
5974
|
+
...rest
|
|
5975
|
+
}
|
|
5976
|
+
);
|
|
5977
|
+
});
|
|
5978
|
+
var Label = (0, import_react38.forwardRef)(function UsageMeterLabel({ className, children, ...rest }, forwardedRef) {
|
|
5979
|
+
const ctx = useUsageMeterCtx("Label");
|
|
5980
|
+
const copy = useCopy();
|
|
5981
|
+
if (!ctx.usage) return null;
|
|
5982
|
+
if (ctx.isUnlimited) {
|
|
5983
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
5984
|
+
"span",
|
|
5985
|
+
{
|
|
5986
|
+
ref: forwardedRef,
|
|
5987
|
+
"data-solvapay-usage-meter-label": "",
|
|
5988
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
5989
|
+
...rest,
|
|
5990
|
+
children: children ?? copy.usage.unlimitedLabel
|
|
5991
|
+
}
|
|
5992
|
+
);
|
|
5993
|
+
}
|
|
5994
|
+
const unit = ctx.usage.meterRef ?? "units";
|
|
5995
|
+
const label = ctx.usage.total !== null ? interpolate(copy.usage.usedLabel, {
|
|
5996
|
+
used: String(ctx.usage.used),
|
|
5997
|
+
total: String(ctx.usage.total),
|
|
5998
|
+
unit
|
|
5999
|
+
}) : interpolate(copy.usage.remainingLabel, {
|
|
6000
|
+
remaining: String(ctx.usage.remaining ?? 0),
|
|
6001
|
+
unit
|
|
6002
|
+
});
|
|
6003
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
6004
|
+
"span",
|
|
6005
|
+
{
|
|
6006
|
+
ref: forwardedRef,
|
|
6007
|
+
"data-solvapay-usage-meter-label": "",
|
|
6008
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
6009
|
+
...rest,
|
|
6010
|
+
children: children ?? label
|
|
6011
|
+
}
|
|
6012
|
+
);
|
|
6013
|
+
});
|
|
6014
|
+
var Percentage = (0, import_react38.forwardRef)(function UsageMeterPercentage({ className, children, ...rest }, forwardedRef) {
|
|
6015
|
+
const ctx = useUsageMeterCtx("Percentage");
|
|
6016
|
+
const copy = useCopy();
|
|
6017
|
+
if (ctx.percentUsed === null) return null;
|
|
6018
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
6019
|
+
"span",
|
|
6020
|
+
{
|
|
6021
|
+
ref: forwardedRef,
|
|
6022
|
+
"data-solvapay-usage-meter-percentage": "",
|
|
6023
|
+
className: className ?? "solvapay-usage-meter-percentage",
|
|
6024
|
+
...rest,
|
|
6025
|
+
children: children ?? interpolate(copy.usage.percentUsedLabel, { percent: String(Math.round(ctx.percentUsed)) })
|
|
6026
|
+
}
|
|
6027
|
+
);
|
|
6028
|
+
});
|
|
6029
|
+
function formatResetsIn(copy, periodEnd, nowMs = typeof performance !== "undefined" ? performance.timeOrigin + performance.now() : 0) {
|
|
6030
|
+
const end = new Date(periodEnd).getTime();
|
|
6031
|
+
if (Number.isNaN(end)) return null;
|
|
6032
|
+
const daysLeft = Math.max(0, Math.ceil((end - nowMs) / (24 * 60 * 60 * 1e3)));
|
|
6033
|
+
return daysLeft > 0 ? interpolate(copy.usage.resetsInLabel, { days: String(daysLeft) }) : interpolate(copy.usage.resetsOnLabel, { date: new Date(periodEnd).toLocaleDateString() });
|
|
6034
|
+
}
|
|
6035
|
+
var ResetsIn = (0, import_react38.forwardRef)(function UsageMeterResetsIn({ className, children, ...rest }, forwardedRef) {
|
|
6036
|
+
const ctx = useUsageMeterCtx("ResetsIn");
|
|
6037
|
+
const copy = useCopy();
|
|
6038
|
+
const periodEnd = ctx.usage?.periodEnd;
|
|
6039
|
+
if (!periodEnd) return null;
|
|
6040
|
+
const label = formatResetsIn(copy, periodEnd);
|
|
6041
|
+
if (!label) return null;
|
|
6042
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
6043
|
+
"span",
|
|
6044
|
+
{
|
|
6045
|
+
ref: forwardedRef,
|
|
6046
|
+
"data-solvapay-usage-meter-resets-in": "",
|
|
6047
|
+
className: className ?? "solvapay-usage-meter-resets-in",
|
|
6048
|
+
...rest,
|
|
6049
|
+
children: children ?? label
|
|
6050
|
+
}
|
|
6051
|
+
);
|
|
6052
|
+
});
|
|
6053
|
+
var Loading7 = (0, import_react38.forwardRef)(function UsageMeterLoading({ className, children, ...rest }, forwardedRef) {
|
|
6054
|
+
const ctx = useUsageMeterCtx("Loading");
|
|
6055
|
+
const copy = useCopy();
|
|
6056
|
+
if (ctx.state !== "loading") return null;
|
|
6057
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
6058
|
+
"div",
|
|
6059
|
+
{
|
|
6060
|
+
ref: forwardedRef,
|
|
6061
|
+
"data-solvapay-usage-meter-loading": "",
|
|
6062
|
+
className: className ?? "solvapay-usage-meter-loading",
|
|
6063
|
+
...rest,
|
|
6064
|
+
children: children ?? copy.usage.loadingLabel
|
|
6065
|
+
}
|
|
6066
|
+
);
|
|
6067
|
+
});
|
|
6068
|
+
var Empty = (0, import_react38.forwardRef)(function UsageMeterEmpty({ className, children, ...rest }, forwardedRef) {
|
|
6069
|
+
const ctx = useUsageMeterCtx("Empty");
|
|
6070
|
+
const copy = useCopy();
|
|
6071
|
+
if (ctx.loading || ctx.usage !== null) return null;
|
|
6072
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
6073
|
+
"div",
|
|
6074
|
+
{
|
|
6075
|
+
ref: forwardedRef,
|
|
6076
|
+
"data-solvapay-usage-meter-empty": "",
|
|
6077
|
+
className: className ?? "solvapay-usage-meter-empty",
|
|
6078
|
+
...rest,
|
|
6079
|
+
children: children ?? copy.usage.emptyLabel
|
|
6080
|
+
}
|
|
6081
|
+
);
|
|
6082
|
+
});
|
|
6083
|
+
var UsageMeter = Object.assign(Root10, {
|
|
6084
|
+
Root: Root10,
|
|
6085
|
+
Bar,
|
|
6086
|
+
Label,
|
|
6087
|
+
Percentage,
|
|
6088
|
+
ResetsIn,
|
|
6089
|
+
Loading: Loading7,
|
|
6090
|
+
Empty
|
|
6091
|
+
});
|
|
6092
|
+
|
|
6093
|
+
// src/components/CurrentPlanCard.tsx
|
|
6094
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
6095
|
+
function PlanTypeLine({
|
|
6096
|
+
purchase,
|
|
6097
|
+
formatDate,
|
|
6098
|
+
className
|
|
6099
|
+
}) {
|
|
6100
|
+
const copy = useCopy();
|
|
6101
|
+
const planType = purchase.planSnapshot?.planType ?? "one-time";
|
|
6102
|
+
if (planType === "recurring" || purchase.isRecurring) {
|
|
6103
|
+
const date2 = formatDate(purchase.nextBillingDate);
|
|
6104
|
+
if (!date2) return null;
|
|
6105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6106
|
+
"span",
|
|
6107
|
+
{
|
|
6108
|
+
className,
|
|
6109
|
+
"data-solvapay-current-plan-next-billing": "",
|
|
6110
|
+
children: interpolate(copy.currentPlan.nextBilling, { date: date2 })
|
|
6111
|
+
}
|
|
6112
|
+
);
|
|
6113
|
+
}
|
|
6114
|
+
if (planType === "usage-based") {
|
|
6115
|
+
return null;
|
|
6116
|
+
}
|
|
6117
|
+
const date = formatDate(purchase.endDate);
|
|
6118
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className, "data-solvapay-current-plan-expires": "", children: date ? interpolate(copy.currentPlan.expiresOn, { date }) : copy.currentPlan.validIndefinitely });
|
|
6119
|
+
}
|
|
6120
|
+
function PaymentMethodLine({
|
|
6121
|
+
paymentMethod,
|
|
6122
|
+
className
|
|
6123
|
+
}) {
|
|
6124
|
+
const copy = useCopy();
|
|
6125
|
+
if (paymentMethod.kind === "none") {
|
|
6126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className, "data-solvapay-current-plan-payment-method": "none", children: copy.currentPlan.noPaymentMethod });
|
|
6127
|
+
}
|
|
6128
|
+
const brandDisplay = paymentMethod.brand.charAt(0).toUpperCase() + paymentMethod.brand.slice(1);
|
|
6129
|
+
const label = interpolate(copy.currentPlan.paymentMethod, {
|
|
6130
|
+
brand: brandDisplay,
|
|
6131
|
+
last4: paymentMethod.last4
|
|
6132
|
+
});
|
|
6133
|
+
const expires = interpolate(copy.currentPlan.paymentMethodExpires, {
|
|
6134
|
+
month: String(paymentMethod.expMonth).padStart(2, "0"),
|
|
6135
|
+
year: paymentMethod.expYear
|
|
6136
|
+
});
|
|
6137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className, "data-solvapay-current-plan-payment-method": "card", children: [
|
|
6138
|
+
label,
|
|
6139
|
+
", ",
|
|
6140
|
+
expires
|
|
6141
|
+
] });
|
|
6142
|
+
}
|
|
6143
|
+
var CurrentPlanCard = ({
|
|
6144
|
+
hidePaymentMethod,
|
|
6145
|
+
hideCancelButton,
|
|
6146
|
+
hideUpdatePaymentButton,
|
|
6147
|
+
hideUsageMeter,
|
|
6148
|
+
classNames: overrides,
|
|
6149
|
+
className
|
|
6150
|
+
}) => {
|
|
6151
|
+
const copy = useCopy();
|
|
6152
|
+
const { activePurchase } = usePurchase();
|
|
6153
|
+
const { formatDate } = usePurchaseStatus();
|
|
6154
|
+
const { paymentMethod } = usePaymentMethod();
|
|
6155
|
+
if (!activePurchase) return null;
|
|
6156
|
+
const planType = activePurchase.planSnapshot?.planType ?? "one-time";
|
|
6157
|
+
const isUsageBased = planType === "usage-based";
|
|
6158
|
+
const amount = activePurchase.originalAmount ?? activePurchase.amount ?? 0;
|
|
6159
|
+
const currency = activePurchase.currency ?? "usd";
|
|
6160
|
+
const cycleKey = activePurchase.billingCycle;
|
|
6161
|
+
const intervalLabel = cycleKey ? copy.currentPlan.cycleUnit[cycleKey] ?? cycleKey : void 0;
|
|
6162
|
+
const priceLabel = formatPrice(amount, currency, {
|
|
6163
|
+
interval: intervalLabel
|
|
6164
|
+
});
|
|
6165
|
+
const planName = activePurchase.planSnapshot?.name ?? activePurchase.productName;
|
|
6166
|
+
const productContext = activePurchase.productName && activePurchase.productName !== planName ? activePurchase.productName : null;
|
|
6167
|
+
const rootClass = [
|
|
6168
|
+
"solvapay-current-plan-card",
|
|
6169
|
+
overrides?.root,
|
|
6170
|
+
className
|
|
6171
|
+
].filter(Boolean).join(" ");
|
|
6172
|
+
const shouldShowPaymentMethod = !hidePaymentMethod && paymentMethod !== null;
|
|
6173
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
6174
|
+
"div",
|
|
6175
|
+
{
|
|
6176
|
+
className: rootClass,
|
|
6177
|
+
"data-solvapay-current-plan-card": "",
|
|
6178
|
+
"data-plan-type": planType,
|
|
6179
|
+
"data-solvapay-current-plan-ref": activePurchase.planRef ?? void 0,
|
|
6180
|
+
children: [
|
|
6181
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6182
|
+
"h2",
|
|
6183
|
+
{
|
|
6184
|
+
className: overrides?.heading ?? "solvapay-current-plan-heading",
|
|
6185
|
+
"data-solvapay-current-plan-heading": "",
|
|
6186
|
+
children: copy.currentPlan.heading
|
|
6187
|
+
}
|
|
6188
|
+
),
|
|
6189
|
+
productContext && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6190
|
+
"div",
|
|
6191
|
+
{
|
|
6192
|
+
className: overrides?.productContext ?? "solvapay-current-plan-product-context",
|
|
6193
|
+
"data-solvapay-current-plan-product-context": "",
|
|
6194
|
+
children: productContext
|
|
6195
|
+
}
|
|
6196
|
+
),
|
|
6197
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6198
|
+
"div",
|
|
6199
|
+
{
|
|
6200
|
+
className: overrides?.planName ?? "solvapay-current-plan-name",
|
|
6201
|
+
"data-solvapay-current-plan-name": "",
|
|
6202
|
+
children: planName
|
|
6203
|
+
}
|
|
6204
|
+
),
|
|
6205
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6206
|
+
"div",
|
|
6207
|
+
{
|
|
6208
|
+
className: overrides?.price ?? "solvapay-current-plan-price",
|
|
6209
|
+
"data-solvapay-current-plan-price": "",
|
|
6210
|
+
children: priceLabel
|
|
6211
|
+
}
|
|
6212
|
+
),
|
|
6213
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6214
|
+
PlanTypeLine,
|
|
6215
|
+
{
|
|
6216
|
+
purchase: activePurchase,
|
|
6217
|
+
formatDate,
|
|
6218
|
+
className: overrides?.dateLine ?? "solvapay-current-plan-date-line"
|
|
6219
|
+
}
|
|
6220
|
+
),
|
|
6221
|
+
isUsageBased && !hideUsageMeter && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6222
|
+
"div",
|
|
6223
|
+
{
|
|
6224
|
+
className: overrides?.usageMeter ?? "solvapay-current-plan-usage-meter",
|
|
6225
|
+
"data-solvapay-current-plan-usage-meter": "",
|
|
6226
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(UsageMeter.Root, { children: [
|
|
6227
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UsageMeter.Label, {}),
|
|
6228
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UsageMeter.Bar, {}),
|
|
6229
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UsageMeter.Percentage, {}),
|
|
6230
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UsageMeter.ResetsIn, {}),
|
|
6231
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UsageMeter.Loading, {})
|
|
6232
|
+
] })
|
|
6233
|
+
}
|
|
6234
|
+
),
|
|
6235
|
+
isUsageBased && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6236
|
+
"div",
|
|
6237
|
+
{
|
|
6238
|
+
className: overrides?.balanceLine ?? "solvapay-current-plan-balance-line",
|
|
6239
|
+
"data-solvapay-current-plan-balance-line": "",
|
|
6240
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(BalanceBadge, {})
|
|
6241
|
+
}
|
|
6242
|
+
),
|
|
6243
|
+
shouldShowPaymentMethod && paymentMethod && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
6244
|
+
PaymentMethodLine,
|
|
6245
|
+
{
|
|
6246
|
+
paymentMethod,
|
|
6247
|
+
className: overrides?.paymentMethod ?? "solvapay-current-plan-payment-method"
|
|
6248
|
+
}
|
|
6249
|
+
),
|
|
6250
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
6251
|
+
"div",
|
|
6252
|
+
{
|
|
6253
|
+
className: overrides?.actions ?? "solvapay-current-plan-actions",
|
|
6254
|
+
"data-solvapay-current-plan-actions": "",
|
|
6255
|
+
children: [
|
|
6256
|
+
!hideUpdatePaymentButton && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(UpdatePaymentMethodButton, {}),
|
|
6257
|
+
!hideCancelButton && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(CancelPlanButton, {})
|
|
6258
|
+
]
|
|
6259
|
+
}
|
|
6260
|
+
)
|
|
6261
|
+
]
|
|
6262
|
+
}
|
|
6263
|
+
);
|
|
6264
|
+
};
|
|
6265
|
+
|
|
6266
|
+
// src/hooks/usePaywallResolver.ts
|
|
6267
|
+
var import_react39 = require("react");
|
|
6268
|
+
function usePaywallResolver(content) {
|
|
6269
|
+
const { activePurchase, hasPaidPurchase, refetch: refetchPurchase } = usePurchase();
|
|
6270
|
+
const { credits, refetch: refetchBalance } = useBalance();
|
|
6271
|
+
const resolved = (0, import_react39.useMemo)(() => {
|
|
6272
|
+
if (!content) return false;
|
|
6273
|
+
const productMatches = !content.product || activePurchase?.productRef === content.product || activePurchase?.productName === content.product;
|
|
6274
|
+
if (content.kind === "payment_required") {
|
|
6275
|
+
return Boolean(hasPaidPurchase && productMatches);
|
|
6276
|
+
}
|
|
6277
|
+
const balance = content.balance;
|
|
6278
|
+
if (balance && typeof balance.remainingUnits === "number" && balance.remainingUnits > 0) {
|
|
6279
|
+
return true;
|
|
6280
|
+
}
|
|
6281
|
+
if (balance && credits != null && balance.creditsPerUnit && credits >= balance.creditsPerUnit) {
|
|
6282
|
+
return true;
|
|
6283
|
+
}
|
|
6284
|
+
return Boolean(productMatches && activePurchase?.status === "active");
|
|
6285
|
+
}, [content, hasPaidPurchase, activePurchase, credits]);
|
|
6286
|
+
const refetch = (0, import_react39.useCallback)(async () => {
|
|
6287
|
+
await Promise.all([
|
|
6288
|
+
refetchPurchase().catch(() => void 0),
|
|
6289
|
+
refetchBalance().catch(() => void 0)
|
|
6290
|
+
]);
|
|
6291
|
+
}, [refetchPurchase, refetchBalance]);
|
|
6292
|
+
return { resolved, refetch };
|
|
6293
|
+
}
|
|
6294
|
+
|
|
6295
|
+
// src/transport/types.ts
|
|
6296
|
+
var UnsupportedTransportMethodError = class extends Error {
|
|
6297
|
+
method;
|
|
6298
|
+
constructor(method) {
|
|
6299
|
+
super(`SolvaPay transport does not implement "${method}"`);
|
|
6300
|
+
this.name = "UnsupportedTransportMethodError";
|
|
6301
|
+
this.method = method;
|
|
6302
|
+
}
|
|
6303
|
+
};
|
|
5262
6304
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5263
6305
|
0 && (module.exports = {
|
|
5264
6306
|
ActivationFlow,
|
|
@@ -5271,6 +6313,9 @@ var CreditGate2 = ({
|
|
|
5271
6313
|
CopyContext,
|
|
5272
6314
|
CopyProvider,
|
|
5273
6315
|
CreditGate,
|
|
6316
|
+
CurrentPlanCard,
|
|
6317
|
+
DEFAULT_ROUTES,
|
|
6318
|
+
LaunchCustomerPortalButton,
|
|
5274
6319
|
MandateText,
|
|
5275
6320
|
PaymentForm,
|
|
5276
6321
|
PaymentFormContext,
|
|
@@ -5283,7 +6328,10 @@ var CreditGate2 = ({
|
|
|
5283
6328
|
Spinner,
|
|
5284
6329
|
StripePaymentFormWrapper,
|
|
5285
6330
|
TopupForm,
|
|
6331
|
+
UnsupportedTransportMethodError,
|
|
6332
|
+
UpdatePaymentMethodButton,
|
|
5286
6333
|
confirmPayment,
|
|
6334
|
+
createHttpTransport,
|
|
5287
6335
|
defaultAuthAdapter,
|
|
5288
6336
|
deriveVariant,
|
|
5289
6337
|
enCopy,
|
|
@@ -5291,12 +6339,16 @@ var CreditGate2 = ({
|
|
|
5291
6339
|
formatPrice,
|
|
5292
6340
|
getActivePurchases,
|
|
5293
6341
|
getCancelledPurchasesWithEndDate,
|
|
6342
|
+
getMinorUnitsPerMajor,
|
|
5294
6343
|
getMostRecentPurchase,
|
|
5295
6344
|
getPrimaryPurchase,
|
|
5296
6345
|
interpolate,
|
|
5297
6346
|
isPaidPurchase,
|
|
6347
|
+
isPlanPurchase,
|
|
6348
|
+
isTopupPurchase,
|
|
5298
6349
|
mergeCopy,
|
|
5299
6350
|
resolveCta,
|
|
6351
|
+
toMajorUnits,
|
|
5300
6352
|
useActivation,
|
|
5301
6353
|
useBalance,
|
|
5302
6354
|
useCheckout,
|
|
@@ -5305,6 +6357,8 @@ var CreditGate2 = ({
|
|
|
5305
6357
|
useLocale,
|
|
5306
6358
|
useMerchant,
|
|
5307
6359
|
usePaymentForm,
|
|
6360
|
+
usePaymentMethod,
|
|
6361
|
+
usePaywallResolver,
|
|
5308
6362
|
usePlan,
|
|
5309
6363
|
usePlans,
|
|
5310
6364
|
useProduct,
|
|
@@ -5313,5 +6367,7 @@ var CreditGate2 = ({
|
|
|
5313
6367
|
usePurchaseStatus,
|
|
5314
6368
|
useSolvaPay,
|
|
5315
6369
|
useTopup,
|
|
5316
|
-
useTopupAmountSelector
|
|
6370
|
+
useTopupAmountSelector,
|
|
6371
|
+
useTransport,
|
|
6372
|
+
useUsage
|
|
5317
6373
|
});
|