@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
|
@@ -82,6 +82,16 @@ __export(primitives_exports, {
|
|
|
82
82
|
PaymentFormSubmitButton: () => PaymentFormSubmitButton2,
|
|
83
83
|
PaymentFormSummary: () => PaymentFormSummary,
|
|
84
84
|
PaymentFormTermsCheckbox: () => PaymentFormTermsCheckbox2,
|
|
85
|
+
PaywallNotice: () => PaywallNotice,
|
|
86
|
+
PaywallNoticeBalance: () => Balance,
|
|
87
|
+
PaywallNoticeEmbeddedCheckout: () => EmbeddedCheckout,
|
|
88
|
+
PaywallNoticeHeading: () => Heading4,
|
|
89
|
+
PaywallNoticeHostedCheckoutLink: () => HostedCheckoutLink,
|
|
90
|
+
PaywallNoticeMessage: () => Message,
|
|
91
|
+
PaywallNoticePlans: () => Plans,
|
|
92
|
+
PaywallNoticeProductContext: () => ProductContext,
|
|
93
|
+
PaywallNoticeRetry: () => Retry,
|
|
94
|
+
PaywallNoticeRoot: () => Root10,
|
|
85
95
|
PlanBadge: () => PlanBadge,
|
|
86
96
|
PlanSelector: () => PlanSelector,
|
|
87
97
|
PlanSelectorCard: () => PlanSelectorCard2,
|
|
@@ -109,6 +119,14 @@ __export(primitives_exports, {
|
|
|
109
119
|
TopupFormPaymentElement: () => TopupFormPaymentElement,
|
|
110
120
|
TopupFormRoot: () => TopupFormRoot2,
|
|
111
121
|
TopupFormSubmitButton: () => TopupFormSubmitButton2,
|
|
122
|
+
UsageMeter: () => UsageMeter,
|
|
123
|
+
UsageMeterBar: () => Bar,
|
|
124
|
+
UsageMeterEmpty: () => Empty,
|
|
125
|
+
UsageMeterLabel: () => Label,
|
|
126
|
+
UsageMeterLoading: () => Loading7,
|
|
127
|
+
UsageMeterPercentage: () => Percentage,
|
|
128
|
+
UsageMeterResetsIn: () => ResetsIn,
|
|
129
|
+
UsageMeterRoot: () => Root11,
|
|
112
130
|
composeEventHandlers: () => composeEventHandlers,
|
|
113
131
|
composeRefs: () => composeRefs,
|
|
114
132
|
setRef: () => setRef,
|
|
@@ -118,9 +136,11 @@ __export(primitives_exports, {
|
|
|
118
136
|
useCancelledPlanNotice: () => useCancelledPlanNotice,
|
|
119
137
|
useCheckoutSummary: () => useCheckoutSummary,
|
|
120
138
|
useCreditGate: () => useCreditGate,
|
|
139
|
+
usePaywallNotice: () => usePaywallNotice,
|
|
121
140
|
usePlanSelector: () => usePlanSelector,
|
|
122
141
|
usePurchaseGate: () => usePurchaseGate,
|
|
123
|
-
useTopupForm: () => useTopupForm
|
|
142
|
+
useTopupForm: () => useTopupForm,
|
|
143
|
+
useUsageMeter: () => useUsageMeter
|
|
124
144
|
});
|
|
125
145
|
module.exports = __toCommonJS(primitives_exports);
|
|
126
146
|
|
|
@@ -250,6 +270,11 @@ var import_react4 = require("react");
|
|
|
250
270
|
// src/SolvaPayProvider.tsx
|
|
251
271
|
var import_react3 = require("react");
|
|
252
272
|
|
|
273
|
+
// src/utils/purchases.ts
|
|
274
|
+
function isPlanPurchase(purchase) {
|
|
275
|
+
return !!purchase.planSnapshot && purchase.metadata?.purpose !== "credit_topup";
|
|
276
|
+
}
|
|
277
|
+
|
|
253
278
|
// src/adapters/auth.ts
|
|
254
279
|
var defaultAuthAdapter = {
|
|
255
280
|
async getToken() {
|
|
@@ -280,16 +305,6 @@ function getAuthAdapter(config) {
|
|
|
280
305
|
if (config?.auth?.adapter) {
|
|
281
306
|
return config.auth.adapter;
|
|
282
307
|
}
|
|
283
|
-
if (config?.auth?.getToken || config?.auth?.getUserId) {
|
|
284
|
-
return {
|
|
285
|
-
async getToken() {
|
|
286
|
-
return config?.auth?.getToken?.() || null;
|
|
287
|
-
},
|
|
288
|
-
async getUserId() {
|
|
289
|
-
return config?.auth?.getUserId?.() || null;
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
308
|
return defaultAuthAdapter;
|
|
294
309
|
}
|
|
295
310
|
function getCachedCustomerRef(userId) {
|
|
@@ -340,6 +355,167 @@ async function buildRequestHeaders(config) {
|
|
|
340
355
|
return { headers, userId };
|
|
341
356
|
}
|
|
342
357
|
|
|
358
|
+
// src/transport/http.ts
|
|
359
|
+
async function request(config, url, opts) {
|
|
360
|
+
const { headers } = await buildRequestHeaders(config);
|
|
361
|
+
const fetchFn = config?.fetch || fetch;
|
|
362
|
+
const init = { method: opts.method, headers };
|
|
363
|
+
if (opts.body !== void 0) {
|
|
364
|
+
init.body = JSON.stringify(opts.body);
|
|
365
|
+
}
|
|
366
|
+
const res = await fetchFn(url, init);
|
|
367
|
+
if (!res.ok) {
|
|
368
|
+
let serverMessage;
|
|
369
|
+
try {
|
|
370
|
+
const data = await res.clone().json();
|
|
371
|
+
serverMessage = data?.error;
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
|
|
375
|
+
config?.onError?.(error, opts.onErrorContext);
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
return await res.json();
|
|
379
|
+
}
|
|
380
|
+
var DEFAULT_ROUTES = {
|
|
381
|
+
checkPurchase: "/api/check-purchase",
|
|
382
|
+
createPayment: "/api/create-payment-intent",
|
|
383
|
+
processPayment: "/api/process-payment",
|
|
384
|
+
createTopupPayment: "/api/create-topup-payment-intent",
|
|
385
|
+
customerBalance: "/api/customer-balance",
|
|
386
|
+
cancelRenewal: "/api/cancel-renewal",
|
|
387
|
+
reactivateRenewal: "/api/reactivate-renewal",
|
|
388
|
+
activatePlan: "/api/activate-plan",
|
|
389
|
+
createCheckoutSession: "/api/create-checkout-session",
|
|
390
|
+
createCustomerSession: "/api/create-customer-session",
|
|
391
|
+
getMerchant: "/api/merchant",
|
|
392
|
+
getProduct: "/api/get-product",
|
|
393
|
+
listPlans: "/api/list-plans",
|
|
394
|
+
getPaymentMethod: "/api/payment-method",
|
|
395
|
+
getUsage: "/api/usage"
|
|
396
|
+
};
|
|
397
|
+
function routeFor(config, key) {
|
|
398
|
+
const configured = config?.api?.[key];
|
|
399
|
+
return configured || DEFAULT_ROUTES[key];
|
|
400
|
+
}
|
|
401
|
+
function createHttpTransport(config) {
|
|
402
|
+
return {
|
|
403
|
+
checkPurchase: () => request(config, routeFor(config, "checkPurchase"), {
|
|
404
|
+
method: "GET",
|
|
405
|
+
onErrorContext: "checkPurchase",
|
|
406
|
+
errorPrefix: "Failed to check purchase"
|
|
407
|
+
}),
|
|
408
|
+
createPayment: (params) => {
|
|
409
|
+
const body = {};
|
|
410
|
+
if (params.planRef) body.planRef = params.planRef;
|
|
411
|
+
if (params.productRef) body.productRef = params.productRef;
|
|
412
|
+
if (params.customer && (params.customer.name || params.customer.email)) {
|
|
413
|
+
body.customer = params.customer;
|
|
414
|
+
}
|
|
415
|
+
return request(config, routeFor(config, "createPayment"), {
|
|
416
|
+
method: "POST",
|
|
417
|
+
body,
|
|
418
|
+
onErrorContext: "createPayment",
|
|
419
|
+
errorPrefix: "Failed to create payment"
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
processPayment: (params) => request(config, routeFor(config, "processPayment"), {
|
|
423
|
+
method: "POST",
|
|
424
|
+
body: params,
|
|
425
|
+
onErrorContext: "processPayment",
|
|
426
|
+
errorPrefix: "Failed to process payment"
|
|
427
|
+
}),
|
|
428
|
+
createTopupPayment: (params) => request(config, routeFor(config, "createTopupPayment"), {
|
|
429
|
+
method: "POST",
|
|
430
|
+
body: { amount: params.amount, currency: params.currency },
|
|
431
|
+
onErrorContext: "createTopupPayment",
|
|
432
|
+
errorPrefix: "Failed to create topup payment"
|
|
433
|
+
}),
|
|
434
|
+
getBalance: () => request(config, routeFor(config, "customerBalance"), {
|
|
435
|
+
method: "GET",
|
|
436
|
+
onErrorContext: "getBalance",
|
|
437
|
+
errorPrefix: "Failed to fetch balance"
|
|
438
|
+
}),
|
|
439
|
+
cancelRenewal: (params) => request(config, routeFor(config, "cancelRenewal"), {
|
|
440
|
+
method: "POST",
|
|
441
|
+
body: params,
|
|
442
|
+
onErrorContext: "cancelRenewal",
|
|
443
|
+
errorPrefix: "Failed to cancel renewal"
|
|
444
|
+
}),
|
|
445
|
+
reactivateRenewal: (params) => request(config, routeFor(config, "reactivateRenewal"), {
|
|
446
|
+
method: "POST",
|
|
447
|
+
body: params,
|
|
448
|
+
onErrorContext: "reactivateRenewal",
|
|
449
|
+
errorPrefix: "Failed to reactivate renewal"
|
|
450
|
+
}),
|
|
451
|
+
activatePlan: (params) => request(config, routeFor(config, "activatePlan"), {
|
|
452
|
+
method: "POST",
|
|
453
|
+
body: params,
|
|
454
|
+
onErrorContext: "activatePlan",
|
|
455
|
+
errorPrefix: "Failed to activate plan"
|
|
456
|
+
}),
|
|
457
|
+
createCheckoutSession: (params) => {
|
|
458
|
+
const body = {};
|
|
459
|
+
if (params?.planRef) body.planRef = params.planRef;
|
|
460
|
+
if (params?.productRef) body.productRef = params.productRef;
|
|
461
|
+
if (params?.returnUrl) body.returnUrl = params.returnUrl;
|
|
462
|
+
return request(
|
|
463
|
+
config,
|
|
464
|
+
routeFor(config, "createCheckoutSession"),
|
|
465
|
+
{
|
|
466
|
+
method: "POST",
|
|
467
|
+
body,
|
|
468
|
+
onErrorContext: "createCheckoutSession",
|
|
469
|
+
errorPrefix: "Failed to create checkout session"
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
},
|
|
473
|
+
createCustomerSession: () => request(
|
|
474
|
+
config,
|
|
475
|
+
routeFor(config, "createCustomerSession"),
|
|
476
|
+
{
|
|
477
|
+
method: "POST",
|
|
478
|
+
body: {},
|
|
479
|
+
onErrorContext: "createCustomerSession",
|
|
480
|
+
errorPrefix: "Failed to create customer session"
|
|
481
|
+
}
|
|
482
|
+
),
|
|
483
|
+
getMerchant: () => request(config, routeFor(config, "getMerchant"), {
|
|
484
|
+
method: "GET",
|
|
485
|
+
onErrorContext: "getMerchant",
|
|
486
|
+
errorPrefix: "Failed to fetch merchant"
|
|
487
|
+
}),
|
|
488
|
+
getProduct: (productRef) => {
|
|
489
|
+
const base = routeFor(config, "getProduct");
|
|
490
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
491
|
+
return request(config, url, {
|
|
492
|
+
method: "GET",
|
|
493
|
+
onErrorContext: "getProduct",
|
|
494
|
+
errorPrefix: "Failed to fetch product"
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
listPlans: (productRef) => {
|
|
498
|
+
const base = routeFor(config, "listPlans");
|
|
499
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
500
|
+
return request(config, url, {
|
|
501
|
+
method: "GET",
|
|
502
|
+
onErrorContext: "listPlans",
|
|
503
|
+
errorPrefix: "Failed to list plans"
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
getPaymentMethod: () => request(config, routeFor(config, "getPaymentMethod"), {
|
|
507
|
+
method: "GET",
|
|
508
|
+
onErrorContext: "getPaymentMethod",
|
|
509
|
+
errorPrefix: "Failed to load payment method"
|
|
510
|
+
}),
|
|
511
|
+
getUsage: () => request(config, routeFor(config, "getUsage"), {
|
|
512
|
+
method: "GET",
|
|
513
|
+
onErrorContext: "getUsage",
|
|
514
|
+
errorPrefix: "Failed to load usage"
|
|
515
|
+
})
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
343
519
|
// src/i18n/context.tsx
|
|
344
520
|
var import_react2 = require("react");
|
|
345
521
|
|
|
@@ -489,6 +665,26 @@ var enCopy = {
|
|
|
489
665
|
lowBalanceSubheading: "Top up to continue using {product}",
|
|
490
666
|
topUpCta: "Top up now"
|
|
491
667
|
},
|
|
668
|
+
currentPlan: {
|
|
669
|
+
heading: "Your plan",
|
|
670
|
+
nextBilling: "Next billing: {date}",
|
|
671
|
+
expiresOn: "Expires {date}",
|
|
672
|
+
validIndefinitely: "Valid indefinitely",
|
|
673
|
+
paymentMethod: "{brand} \u2022\u2022\u2022\u2022 {last4}",
|
|
674
|
+
paymentMethodExpires: "expires {month}/{year}",
|
|
675
|
+
noPaymentMethod: "No payment method on file",
|
|
676
|
+
updatePaymentButton: "Update card",
|
|
677
|
+
cycleUnit: {
|
|
678
|
+
weekly: "week",
|
|
679
|
+
monthly: "month",
|
|
680
|
+
quarterly: "3 months",
|
|
681
|
+
yearly: "year"
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
customerPortal: {
|
|
685
|
+
launchButton: "Manage billing",
|
|
686
|
+
loadingLabel: "Loading portal\u2026"
|
|
687
|
+
},
|
|
492
688
|
errors: {
|
|
493
689
|
paymentInitFailed: "Payment initialization failed",
|
|
494
690
|
topupInitFailed: "Top-up initialization failed",
|
|
@@ -502,7 +698,40 @@ var enCopy = {
|
|
|
502
698
|
paymentProcessingFailed: "Payment processing failed. Please try again or contact support.",
|
|
503
699
|
paymentRequires3ds: "Payment requires additional authentication. Please complete the verification.",
|
|
504
700
|
paymentProcessingTimeout: "Payment processing timed out \u2014 webhooks may not be configured",
|
|
505
|
-
|
|
701
|
+
paymentConfirmationDelayed: "Payment succeeded but confirmation is taking longer than usual. Refresh in a moment to see your purchase.",
|
|
702
|
+
paymentStatusPrefix: "Payment status: {status}",
|
|
703
|
+
paywallInvalidContent: "Paywall content is missing or malformed.",
|
|
704
|
+
usageLoadFailed: "Failed to load usage"
|
|
705
|
+
},
|
|
706
|
+
paywall: {
|
|
707
|
+
header: "Unlock access",
|
|
708
|
+
paymentRequiredHeading: "Upgrade to continue",
|
|
709
|
+
activationRequiredHeading: "Add credits to continue",
|
|
710
|
+
resolvedHeading: "You can continue",
|
|
711
|
+
productContext: "For {product}",
|
|
712
|
+
balanceLine: "You have {available} credits, need {required}.",
|
|
713
|
+
paymentRequiredMessage: "You've used all your included calls{forProduct}. Choose a plan below to keep going.",
|
|
714
|
+
paymentRequiredMessageRemaining: "Only {remaining} call{pluralSuffix} left{forProduct}. Choose a plan below to keep going.",
|
|
715
|
+
paymentRequiredProductSuffix: " for {product}",
|
|
716
|
+
retryButton: "Continue",
|
|
717
|
+
hostedCheckoutButton: "Open checkout",
|
|
718
|
+
hostedCheckoutLoading: "Loading checkout\u2026"
|
|
719
|
+
},
|
|
720
|
+
usage: {
|
|
721
|
+
header: "Usage",
|
|
722
|
+
percentUsedLabel: "{percent}% used",
|
|
723
|
+
usedLabel: "{used} / {total} {unit}",
|
|
724
|
+
remainingLabel: "{remaining} {unit} remaining",
|
|
725
|
+
unlimitedLabel: "Unlimited",
|
|
726
|
+
resetsInLabel: "Resets in {days} days",
|
|
727
|
+
resetsOnLabel: "Resets {date}",
|
|
728
|
+
loadingLabel: "Loading usage\u2026",
|
|
729
|
+
emptyLabel: "No usage-based plan is active.",
|
|
730
|
+
approachingLimit: "You're approaching your usage limit.",
|
|
731
|
+
atLimit: "You've reached your usage limit.",
|
|
732
|
+
topUpCta: "Add credits",
|
|
733
|
+
upgradeCta: "Upgrade plan",
|
|
734
|
+
refreshCta: "Refresh"
|
|
506
735
|
}
|
|
507
736
|
};
|
|
508
737
|
|
|
@@ -832,27 +1061,41 @@ function usePlan(options) {
|
|
|
832
1061
|
|
|
833
1062
|
// src/hooks/useProduct.ts
|
|
834
1063
|
var import_react7 = require("react");
|
|
1064
|
+
|
|
1065
|
+
// src/transport/cache-key.ts
|
|
1066
|
+
var transportIds = /* @__PURE__ */ new WeakMap();
|
|
1067
|
+
var nextTransportId = 0;
|
|
1068
|
+
function transportIdFor(transport) {
|
|
1069
|
+
let id = transportIds.get(transport);
|
|
1070
|
+
if (id === void 0) {
|
|
1071
|
+
id = ++nextTransportId;
|
|
1072
|
+
transportIds.set(transport, id);
|
|
1073
|
+
}
|
|
1074
|
+
return id;
|
|
1075
|
+
}
|
|
1076
|
+
function createTransportCacheKey(config, fallbackRoute, suffix) {
|
|
1077
|
+
const transport = config?.transport;
|
|
1078
|
+
if (transport) {
|
|
1079
|
+
const id = transportIdFor(transport);
|
|
1080
|
+
return suffix ? `transport:${id}:${suffix}` : `transport:${id}`;
|
|
1081
|
+
}
|
|
1082
|
+
return fallbackRoute;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/hooks/useProduct.ts
|
|
835
1086
|
var productCache = /* @__PURE__ */ new Map();
|
|
836
1087
|
var CACHE_DURATION3 = 5 * 60 * 1e3;
|
|
837
|
-
function
|
|
838
|
-
return config
|
|
1088
|
+
function cacheKeyFor(config, productRef) {
|
|
1089
|
+
return createTransportCacheKey(config, productRef, productRef);
|
|
839
1090
|
}
|
|
840
1091
|
async function fetchProduct(productRef, config) {
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const { headers } = await buildRequestHeaders(config);
|
|
845
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
846
|
-
if (!res.ok) {
|
|
847
|
-
const error = new Error(`Failed to fetch product: ${res.statusText || res.status}`);
|
|
848
|
-
config?.onError?.(error, "getProduct");
|
|
849
|
-
throw error;
|
|
850
|
-
}
|
|
851
|
-
return await res.json();
|
|
1092
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
1093
|
+
if (!transport.getProduct) return null;
|
|
1094
|
+
return transport.getProduct(productRef);
|
|
852
1095
|
}
|
|
853
1096
|
function useProduct(productRef) {
|
|
854
1097
|
const { _config } = useSolvaPay();
|
|
855
|
-
const cacheKey = productRef
|
|
1098
|
+
const cacheKey = productRef ? cacheKeyFor(_config, productRef) : "";
|
|
856
1099
|
const [product, setProduct] = (0, import_react7.useState)(
|
|
857
1100
|
() => productRef ? productCache.get(cacheKey)?.product ?? null : null
|
|
858
1101
|
);
|
|
@@ -870,7 +1113,8 @@ function useProduct(productRef) {
|
|
|
870
1113
|
setError(null);
|
|
871
1114
|
return;
|
|
872
1115
|
}
|
|
873
|
-
const
|
|
1116
|
+
const key = cacheKeyFor(_config, productRef);
|
|
1117
|
+
const cached = productCache.get(key);
|
|
874
1118
|
const now = Date.now();
|
|
875
1119
|
if (!force && cached?.product && now - cached.timestamp < CACHE_DURATION3) {
|
|
876
1120
|
setProduct(cached.product);
|
|
@@ -895,12 +1139,26 @@ function useProduct(productRef) {
|
|
|
895
1139
|
setLoading(true);
|
|
896
1140
|
setError(null);
|
|
897
1141
|
const promise = fetchProduct(productRef, _config);
|
|
898
|
-
productCache.set(
|
|
1142
|
+
productCache.set(key, {
|
|
1143
|
+
product: cached?.product ?? null,
|
|
1144
|
+
promise,
|
|
1145
|
+
timestamp: now
|
|
1146
|
+
});
|
|
899
1147
|
const p = await promise;
|
|
900
|
-
|
|
1148
|
+
if (p === null) {
|
|
1149
|
+
productCache.set(key, {
|
|
1150
|
+
product: cached?.product ?? null,
|
|
1151
|
+
promise: null,
|
|
1152
|
+
timestamp: cached?.timestamp ?? now
|
|
1153
|
+
});
|
|
1154
|
+
setProduct(cached?.product ?? null);
|
|
1155
|
+
setLoading(false);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
productCache.set(key, { product: p, promise: null, timestamp: now });
|
|
901
1159
|
setProduct(p);
|
|
902
1160
|
} catch (err) {
|
|
903
|
-
productCache.delete(
|
|
1161
|
+
productCache.delete(key);
|
|
904
1162
|
setError(err instanceof Error ? err : new Error("Failed to load product"));
|
|
905
1163
|
} finally {
|
|
906
1164
|
setLoading(false);
|
|
@@ -949,6 +1207,9 @@ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
|
|
|
949
1207
|
function getFractionDigits(currency) {
|
|
950
1208
|
return ZERO_DECIMAL_CURRENCIES.has(currency.toLowerCase()) ? 0 : 2;
|
|
951
1209
|
}
|
|
1210
|
+
function getMinorUnitsPerMajor(currency) {
|
|
1211
|
+
return getFractionDigits(currency) === 0 ? 1 : 100;
|
|
1212
|
+
}
|
|
952
1213
|
function toMajorUnits(amountMinor, currency) {
|
|
953
1214
|
const fractionDigits = getFractionDigits(currency);
|
|
954
1215
|
return fractionDigits === 0 ? amountMinor : amountMinor / 100;
|
|
@@ -958,7 +1219,10 @@ function formatPrice(amountMinor, currency, opts = {}) {
|
|
|
958
1219
|
if (amountMinor === 0 && free !== "") {
|
|
959
1220
|
return free;
|
|
960
1221
|
}
|
|
961
|
-
const
|
|
1222
|
+
const naturalFractionDigits = getFractionDigits(currency);
|
|
1223
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
1224
|
+
const isWhole = amountMinor % minorPerMajor === 0;
|
|
1225
|
+
const fractionDigits = isWhole ? 0 : naturalFractionDigits;
|
|
962
1226
|
const major = toMajorUnits(amountMinor, currency);
|
|
963
1227
|
const formatter = new Intl.NumberFormat(locale, {
|
|
964
1228
|
style: "currency",
|
|
@@ -1127,6 +1391,26 @@ function useCheckoutSummary() {
|
|
|
1127
1391
|
// src/primitives/PlanSelector.tsx
|
|
1128
1392
|
var import_react10 = require("react");
|
|
1129
1393
|
|
|
1394
|
+
// src/transport/list-plans.ts
|
|
1395
|
+
async function defaultListPlans(productRef, config) {
|
|
1396
|
+
const transport = config?.transport;
|
|
1397
|
+
if (transport) {
|
|
1398
|
+
return transport.listPlans ? transport.listPlans(productRef) : plansCache.get(productRef)?.plans ?? [];
|
|
1399
|
+
}
|
|
1400
|
+
const base = config?.api?.listPlans || "/api/list-plans";
|
|
1401
|
+
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
1402
|
+
const fetchFn = config?.fetch || fetch;
|
|
1403
|
+
const { headers } = await buildRequestHeaders(config);
|
|
1404
|
+
const res = await fetchFn(url, { method: "GET", headers });
|
|
1405
|
+
if (!res.ok) {
|
|
1406
|
+
const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
|
|
1407
|
+
config?.onError?.(error, "listPlans");
|
|
1408
|
+
throw error;
|
|
1409
|
+
}
|
|
1410
|
+
const data = await res.json();
|
|
1411
|
+
return data.plans ?? [];
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1130
1414
|
// src/hooks/usePurchase.ts
|
|
1131
1415
|
function usePurchase() {
|
|
1132
1416
|
const { purchase, refetchPurchase } = useSolvaPay();
|
|
@@ -1154,20 +1438,6 @@ function useCardContext(part) {
|
|
|
1154
1438
|
}
|
|
1155
1439
|
return ctx;
|
|
1156
1440
|
}
|
|
1157
|
-
async function defaultListPlans(productRef, config) {
|
|
1158
|
-
const base = config?.api?.listPlans || "/api/list-plans";
|
|
1159
|
-
const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
|
|
1160
|
-
const fetchFn = config?.fetch || fetch;
|
|
1161
|
-
const { headers } = await buildRequestHeaders(config);
|
|
1162
|
-
const res = await fetchFn(url, { method: "GET", headers });
|
|
1163
|
-
if (!res.ok) {
|
|
1164
|
-
const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
|
|
1165
|
-
config?.onError?.(error, "listPlans");
|
|
1166
|
-
throw error;
|
|
1167
|
-
}
|
|
1168
|
-
const data = await res.json();
|
|
1169
|
-
return data.plans ?? [];
|
|
1170
|
-
}
|
|
1171
1441
|
var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forwardedRef) {
|
|
1172
1442
|
const {
|
|
1173
1443
|
productRef,
|
|
@@ -1191,7 +1461,7 @@ var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
1191
1461
|
() => fetcher ?? ((ref) => defaultListPlans(ref, _config)),
|
|
1192
1462
|
[fetcher, _config]
|
|
1193
1463
|
);
|
|
1194
|
-
const { plans, selectedPlan, selectPlan, loading, error } = usePlans({
|
|
1464
|
+
const { plans, selectedPlan, selectPlan, setSelectedPlanIndex, loading, error } = usePlans({
|
|
1195
1465
|
productRef,
|
|
1196
1466
|
fetcher: effectiveFetcher,
|
|
1197
1467
|
filter,
|
|
@@ -1226,6 +1496,9 @@ var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
1226
1496
|
},
|
|
1227
1497
|
[plans, selectPlan, onSelect]
|
|
1228
1498
|
);
|
|
1499
|
+
const clearSelection = (0, import_react10.useCallback)(() => {
|
|
1500
|
+
setSelectedPlanIndex(-1);
|
|
1501
|
+
}, [setSelectedPlanIndex]);
|
|
1229
1502
|
const selectedPlanRef = selectedPlan?.reference ?? null;
|
|
1230
1503
|
const ctx = (0, import_react10.useMemo)(
|
|
1231
1504
|
() => ({
|
|
@@ -1239,7 +1512,8 @@ var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
1239
1512
|
isCurrent,
|
|
1240
1513
|
isFree,
|
|
1241
1514
|
isPopular,
|
|
1242
|
-
select
|
|
1515
|
+
select,
|
|
1516
|
+
clearSelection
|
|
1243
1517
|
}),
|
|
1244
1518
|
[
|
|
1245
1519
|
plans,
|
|
@@ -1252,7 +1526,8 @@ var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forw
|
|
|
1252
1526
|
isCurrent,
|
|
1253
1527
|
isFree,
|
|
1254
1528
|
isPopular,
|
|
1255
|
-
select
|
|
1529
|
+
select,
|
|
1530
|
+
clearSelection
|
|
1256
1531
|
]
|
|
1257
1532
|
);
|
|
1258
1533
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -1333,9 +1608,11 @@ var Card = (0, import_react10.forwardRef)(function PlanSelectorCard({ asChild, o
|
|
|
1333
1608
|
});
|
|
1334
1609
|
var CardName = (0, import_react10.forwardRef)(function PlanSelectorCardName({ asChild, children, ...rest }, forwardedRef) {
|
|
1335
1610
|
const card = useCardContext("CardName");
|
|
1336
|
-
|
|
1611
|
+
const fallback = card.plan.requiresPayment === false ? "Free" : card.plan.type === "usage-based" ? "Pay as you go" : card.plan.type === "recurring" ? "Plan" : null;
|
|
1612
|
+
const label = card.plan.name ?? fallback;
|
|
1613
|
+
if (!label && children == null) return null;
|
|
1337
1614
|
const Comp = asChild ? Slot : "span";
|
|
1338
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ??
|
|
1615
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ?? label });
|
|
1339
1616
|
});
|
|
1340
1617
|
var CardPrice = (0, import_react10.forwardRef)(function PlanSelectorCardPrice({ asChild, children, ...rest }, forwardedRef) {
|
|
1341
1618
|
const card = useCardContext("CardPrice");
|
|
@@ -1343,14 +1620,25 @@ var CardPrice = (0, import_react10.forwardRef)(function PlanSelectorCardPrice({
|
|
|
1343
1620
|
const copy = useCopy();
|
|
1344
1621
|
const formatted = (0, import_react10.useMemo)(() => {
|
|
1345
1622
|
if (card.isFree) return copy.planSelector.freeBadge;
|
|
1623
|
+
if (card.plan.type === "usage-based") {
|
|
1624
|
+
const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
|
|
1625
|
+
const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
|
|
1626
|
+
const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
|
|
1627
|
+
locale,
|
|
1628
|
+
free: ""
|
|
1629
|
+
});
|
|
1630
|
+
return `${rate} / call`;
|
|
1631
|
+
}
|
|
1346
1632
|
return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
|
|
1347
1633
|
locale,
|
|
1348
1634
|
free: copy.interval.free
|
|
1349
1635
|
});
|
|
1350
1636
|
}, [
|
|
1351
1637
|
card.isFree,
|
|
1638
|
+
card.plan.type,
|
|
1352
1639
|
card.plan.price,
|
|
1353
1640
|
card.plan.currency,
|
|
1641
|
+
card.plan.creditsPerUnit,
|
|
1354
1642
|
locale,
|
|
1355
1643
|
copy.planSelector.freeBadge,
|
|
1356
1644
|
copy.interval.free
|
|
@@ -1361,11 +1649,22 @@ var CardPrice = (0, import_react10.forwardRef)(function PlanSelectorCardPrice({
|
|
|
1361
1649
|
var CardInterval = (0, import_react10.forwardRef)(function PlanSelectorCardInterval({ asChild, children, ...rest }, forwardedRef) {
|
|
1362
1650
|
const card = useCardContext("CardInterval");
|
|
1363
1651
|
const copy = useCopy();
|
|
1364
|
-
if (card.isFree ||
|
|
1365
|
-
const
|
|
1652
|
+
if (card.isFree || card.plan.type === "usage-based") return null;
|
|
1653
|
+
const rawInterval = card.plan.interval ?? normalizeBillingCycle(card.plan.billingCycle);
|
|
1654
|
+
if (!rawInterval) return null;
|
|
1655
|
+
const text = interpolate(copy.planSelector.perIntervalShort, { interval: rawInterval });
|
|
1366
1656
|
const Comp = asChild ? Slot : "span";
|
|
1367
1657
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-interval": "", ...rest, children: children ?? text });
|
|
1368
1658
|
});
|
|
1659
|
+
function normalizeBillingCycle(cycle) {
|
|
1660
|
+
if (!cycle) return null;
|
|
1661
|
+
const lc = cycle.toLowerCase();
|
|
1662
|
+
if (lc === "monthly" || lc === "month") return "month";
|
|
1663
|
+
if (lc === "yearly" || lc === "annually" || lc === "annual" || lc === "year") return "year";
|
|
1664
|
+
if (lc === "weekly" || lc === "week") return "week";
|
|
1665
|
+
if (lc === "daily" || lc === "day") return "day";
|
|
1666
|
+
return cycle;
|
|
1667
|
+
}
|
|
1369
1668
|
var CardBadge = (0, import_react10.forwardRef)(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
|
|
1370
1669
|
const card = useCardContext("CardBadge");
|
|
1371
1670
|
const copy = useCopy();
|
|
@@ -1440,8 +1739,11 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
|
1440
1739
|
function getStripeCacheKey(publishableKey, accountId) {
|
|
1441
1740
|
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
1442
1741
|
}
|
|
1443
|
-
async function resolvePlanRef(productRef,
|
|
1742
|
+
async function resolvePlanRef(productRef, config) {
|
|
1743
|
+
const listPlansRoute = config?.api?.listPlans || "/api/list-plans";
|
|
1444
1744
|
const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
|
|
1745
|
+
const fetchFn = config?.fetch || fetch;
|
|
1746
|
+
const { headers } = await buildRequestHeaders(config);
|
|
1445
1747
|
const res = await fetchFn(url, { method: "GET", headers });
|
|
1446
1748
|
if (!res.ok) {
|
|
1447
1749
|
throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
|
|
@@ -1492,8 +1794,7 @@ function useCheckout(options) {
|
|
|
1492
1794
|
try {
|
|
1493
1795
|
let effectivePlanRef = planRef;
|
|
1494
1796
|
if (!effectivePlanRef && productRef) {
|
|
1495
|
-
|
|
1496
|
-
effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
|
|
1797
|
+
effectivePlanRef = await resolvePlanRef(productRef, _config);
|
|
1497
1798
|
setResolvedPlanRef(effectivePlanRef);
|
|
1498
1799
|
}
|
|
1499
1800
|
if (!effectivePlanRef) {
|
|
@@ -1657,24 +1958,17 @@ var import_react15 = require("react");
|
|
|
1657
1958
|
var import_react14 = require("react");
|
|
1658
1959
|
var merchantCache = /* @__PURE__ */ new Map();
|
|
1659
1960
|
var CACHE_DURATION4 = 5 * 60 * 1e3;
|
|
1660
|
-
function
|
|
1661
|
-
return config?.api?.getMerchant || "/api/merchant";
|
|
1961
|
+
function cacheKeyFor2(config) {
|
|
1962
|
+
return createTransportCacheKey(config, config?.api?.getMerchant || "/api/merchant");
|
|
1662
1963
|
}
|
|
1663
1964
|
async function fetchMerchant(config) {
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
const res = await fetchFn(route, { method: "GET", headers });
|
|
1668
|
-
if (!res.ok) {
|
|
1669
|
-
const error = new Error(`Failed to fetch merchant: ${res.statusText || res.status}`);
|
|
1670
|
-
config?.onError?.(error, "getMerchant");
|
|
1671
|
-
throw error;
|
|
1672
|
-
}
|
|
1673
|
-
return await res.json();
|
|
1965
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
1966
|
+
if (!transport.getMerchant) return null;
|
|
1967
|
+
return transport.getMerchant();
|
|
1674
1968
|
}
|
|
1675
1969
|
function useMerchant() {
|
|
1676
1970
|
const { _config } = useSolvaPay();
|
|
1677
|
-
const key =
|
|
1971
|
+
const key = cacheKeyFor2(_config);
|
|
1678
1972
|
const [merchant, setMerchant] = (0, import_react14.useState)(
|
|
1679
1973
|
() => merchantCache.get(key)?.merchant ?? null
|
|
1680
1974
|
);
|
|
@@ -1710,8 +2004,22 @@ function useMerchant() {
|
|
|
1710
2004
|
setLoading(true);
|
|
1711
2005
|
setError(null);
|
|
1712
2006
|
const promise = fetchMerchant(_config);
|
|
1713
|
-
merchantCache.set(key, {
|
|
2007
|
+
merchantCache.set(key, {
|
|
2008
|
+
merchant: cached?.merchant ?? null,
|
|
2009
|
+
promise,
|
|
2010
|
+
timestamp: now
|
|
2011
|
+
});
|
|
1714
2012
|
const m = await promise;
|
|
2013
|
+
if (m === null) {
|
|
2014
|
+
merchantCache.set(key, {
|
|
2015
|
+
merchant: cached?.merchant ?? null,
|
|
2016
|
+
promise: null,
|
|
2017
|
+
timestamp: cached?.timestamp ?? now
|
|
2018
|
+
});
|
|
2019
|
+
setMerchant(cached?.merchant ?? null);
|
|
2020
|
+
setLoading(false);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
1715
2023
|
merchantCache.set(key, { merchant: m, promise: null, timestamp: now });
|
|
1716
2024
|
setMerchant(m);
|
|
1717
2025
|
} catch (err) {
|
|
@@ -1824,31 +2132,36 @@ var MandateText = (0, import_react15.forwardRef)(
|
|
|
1824
2132
|
|
|
1825
2133
|
// src/components/Spinner.tsx
|
|
1826
2134
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1827
|
-
var
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
}) => {
|
|
1831
|
-
const sizeClasses = {
|
|
1832
|
-
sm: "h-4 w-4",
|
|
1833
|
-
md: "h-5 w-5",
|
|
1834
|
-
lg: "h-6 w-6"
|
|
1835
|
-
};
|
|
2135
|
+
var SIZE_PX = { sm: 16, md: 20, lg: 24 };
|
|
2136
|
+
var Spinner = ({ className, size = "md" }) => {
|
|
2137
|
+
const px = SIZE_PX[size];
|
|
1836
2138
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1837
2139
|
"svg",
|
|
1838
2140
|
{
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2141
|
+
"data-solvapay-spinner": "",
|
|
2142
|
+
className,
|
|
2143
|
+
width: px,
|
|
2144
|
+
height: px,
|
|
1842
2145
|
viewBox: "0 0 24 24",
|
|
1843
|
-
|
|
1844
|
-
"aria-
|
|
2146
|
+
fill: "none",
|
|
2147
|
+
"aria-hidden": "true",
|
|
1845
2148
|
children: [
|
|
1846
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2149
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2150
|
+
"circle",
|
|
2151
|
+
{
|
|
2152
|
+
cx: "12",
|
|
2153
|
+
cy: "12",
|
|
2154
|
+
r: "10",
|
|
2155
|
+
stroke: "currentColor",
|
|
2156
|
+
strokeWidth: "4",
|
|
2157
|
+
strokeOpacity: "0.25"
|
|
2158
|
+
}
|
|
2159
|
+
),
|
|
1847
2160
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1848
2161
|
"path",
|
|
1849
2162
|
{
|
|
1850
|
-
className: "opacity-75",
|
|
1851
2163
|
fill: "currentColor",
|
|
2164
|
+
fillOpacity: "0.75",
|
|
1852
2165
|
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"
|
|
1853
2166
|
}
|
|
1854
2167
|
)
|
|
@@ -1866,6 +2179,13 @@ async function confirmPayment(input) {
|
|
|
1866
2179
|
if (!paymentElement) {
|
|
1867
2180
|
return { status: "error", message: copy.errors.cardElementMissing };
|
|
1868
2181
|
}
|
|
2182
|
+
const { error: submitError } = await elements.submit();
|
|
2183
|
+
if (submitError) {
|
|
2184
|
+
return {
|
|
2185
|
+
status: "error",
|
|
2186
|
+
message: submitError.message || copy.errors.paymentUnexpected
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
1869
2189
|
const { error: error2, paymentIntent: paymentIntent2 } = await stripe.confirmPayment({
|
|
1870
2190
|
elements,
|
|
1871
2191
|
clientSecret,
|
|
@@ -2180,7 +2500,11 @@ var PaidInner = ({
|
|
|
2180
2500
|
const copy = useCopy();
|
|
2181
2501
|
const customer = useCustomer();
|
|
2182
2502
|
const { processPayment } = useSolvaPay();
|
|
2183
|
-
const { refetch } = usePurchase();
|
|
2503
|
+
const { refetch, hasPaidPurchase } = usePurchase();
|
|
2504
|
+
const hasPaidPurchaseRef = (0, import_react16.useRef)(hasPaidPurchase);
|
|
2505
|
+
(0, import_react16.useEffect)(() => {
|
|
2506
|
+
hasPaidPurchaseRef.current = hasPaidPurchase;
|
|
2507
|
+
}, [hasPaidPurchase]);
|
|
2184
2508
|
const [elementKind, setElementKind] = (0, import_react16.useState)(
|
|
2185
2509
|
children ? null : "payment-element"
|
|
2186
2510
|
);
|
|
@@ -2231,14 +2555,34 @@ var PaidInner = ({
|
|
|
2231
2555
|
refetchPurchase: refetch,
|
|
2232
2556
|
copy
|
|
2233
2557
|
});
|
|
2234
|
-
setIsProcessing(false);
|
|
2235
2558
|
if (reconcileResult.status === "success") {
|
|
2236
2559
|
const pi = paymentIntent;
|
|
2237
2560
|
onSuccess?.(pi);
|
|
2238
2561
|
const paid = { kind: "paid", paymentIntent: pi };
|
|
2239
2562
|
onResult?.(paid);
|
|
2563
|
+
const CONFIRMATION_TIMEOUT_MS = 1e4;
|
|
2564
|
+
const startedAt = Date.now();
|
|
2565
|
+
let attempt = 0;
|
|
2566
|
+
while (!hasPaidPurchaseRef.current && Date.now() - startedAt < CONFIRMATION_TIMEOUT_MS) {
|
|
2567
|
+
attempt += 1;
|
|
2568
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 1500)));
|
|
2569
|
+
if (hasPaidPurchaseRef.current) break;
|
|
2570
|
+
try {
|
|
2571
|
+
await refetch();
|
|
2572
|
+
} catch {
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (!hasPaidPurchaseRef.current) {
|
|
2576
|
+
const confirmationMsg = copy.errors.paymentConfirmationDelayed;
|
|
2577
|
+
setError(confirmationMsg);
|
|
2578
|
+
setIsProcessing(false);
|
|
2579
|
+
onError?.(new Error(confirmationMsg));
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
setIsProcessing(false);
|
|
2240
2583
|
return;
|
|
2241
2584
|
}
|
|
2585
|
+
setIsProcessing(false);
|
|
2242
2586
|
const msg = reconcileResult.status === "timeout" ? reconcileResult.error.message : copy.errors.paymentProcessingFailed;
|
|
2243
2587
|
setError(msg);
|
|
2244
2588
|
onError?.(reconcileResult.error);
|
|
@@ -2710,7 +3054,9 @@ function useTopupAmountSelector(options) {
|
|
|
2710
3054
|
if (resolvedAmount < minAmount) {
|
|
2711
3055
|
setError(
|
|
2712
3056
|
interpolate(copy.topup.minAmount, {
|
|
2713
|
-
amount:
|
|
3057
|
+
amount: formatPrice(minAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3058
|
+
free: ""
|
|
3059
|
+
})
|
|
2714
3060
|
})
|
|
2715
3061
|
);
|
|
2716
3062
|
return false;
|
|
@@ -2718,14 +3064,16 @@ function useTopupAmountSelector(options) {
|
|
|
2718
3064
|
if (resolvedAmount > maxAmount) {
|
|
2719
3065
|
setError(
|
|
2720
3066
|
interpolate(copy.topup.maxAmount, {
|
|
2721
|
-
amount:
|
|
3067
|
+
amount: formatPrice(maxAmount * getMinorUnitsPerMajor(currency), currency, {
|
|
3068
|
+
free: ""
|
|
3069
|
+
})
|
|
2722
3070
|
})
|
|
2723
3071
|
);
|
|
2724
3072
|
return false;
|
|
2725
3073
|
}
|
|
2726
3074
|
setError(null);
|
|
2727
3075
|
return true;
|
|
2728
|
-
}, [resolvedAmount, minAmount, maxAmount,
|
|
3076
|
+
}, [resolvedAmount, minAmount, maxAmount, currency, copy]);
|
|
2729
3077
|
const reset = (0, import_react17.useCallback)(() => {
|
|
2730
3078
|
setSelectedAmount(null);
|
|
2731
3079
|
setCustomAmountRaw("");
|
|
@@ -2765,15 +3113,31 @@ function usePickerCtx(part) {
|
|
|
2765
3113
|
}
|
|
2766
3114
|
return ctx;
|
|
2767
3115
|
}
|
|
2768
|
-
var Root4 = (0, import_react19.forwardRef)(function AmountPickerRoot({
|
|
3116
|
+
var Root4 = (0, import_react19.forwardRef)(function AmountPickerRoot({
|
|
3117
|
+
currency,
|
|
3118
|
+
minAmount,
|
|
3119
|
+
maxAmount,
|
|
3120
|
+
emit = "major",
|
|
3121
|
+
selector: externalSelector,
|
|
3122
|
+
onChange,
|
|
3123
|
+
asChild,
|
|
3124
|
+
children,
|
|
3125
|
+
...rest
|
|
3126
|
+
}, forwardedRef) {
|
|
2769
3127
|
const solva = (0, import_react19.useContext)(SolvaPayContext);
|
|
2770
3128
|
if (!solva) throw new MissingProviderError("AmountPicker");
|
|
2771
|
-
const
|
|
3129
|
+
const internalSelector = useTopupAmountSelector({ currency, minAmount, maxAmount });
|
|
3130
|
+
const selector = externalSelector ?? internalSelector;
|
|
2772
3131
|
const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
|
|
3132
|
+
const minorPerMajor = getMinorUnitsPerMajor(currency);
|
|
3133
|
+
const resolvedAmountMinor = selector.resolvedAmount != null && selector.resolvedAmount > 0 ? Math.round(selector.resolvedAmount * minorPerMajor) : null;
|
|
2773
3134
|
(0, import_react19.useEffect)(() => {
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
3135
|
+
if (emit === "minor") {
|
|
3136
|
+
onChange?.(resolvedAmountMinor);
|
|
3137
|
+
} else {
|
|
3138
|
+
onChange?.(selector.resolvedAmount);
|
|
3139
|
+
}
|
|
3140
|
+
}, [emit, selector.resolvedAmount, resolvedAmountMinor, onChange]);
|
|
2777
3141
|
const rate = displayExchangeRate ?? 1;
|
|
2778
3142
|
const estimatedCredits = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 && resolvedAmountMinor != null ? Math.floor(resolvedAmountMinor / rate * creditsPerMinorUnit) : null;
|
|
2779
3143
|
const isApproximate = rate !== 1;
|
|
@@ -2781,12 +3145,23 @@ var Root4 = (0, import_react19.forwardRef)(function AmountPickerRoot({ currency,
|
|
|
2781
3145
|
() => ({
|
|
2782
3146
|
...selector,
|
|
2783
3147
|
currency,
|
|
3148
|
+
emit,
|
|
2784
3149
|
creditsPerMinorUnit,
|
|
2785
3150
|
displayExchangeRate,
|
|
2786
3151
|
estimatedCredits,
|
|
2787
|
-
isApproximate
|
|
3152
|
+
isApproximate,
|
|
3153
|
+
resolvedAmountMinor
|
|
2788
3154
|
}),
|
|
2789
|
-
[
|
|
3155
|
+
[
|
|
3156
|
+
selector,
|
|
3157
|
+
currency,
|
|
3158
|
+
emit,
|
|
3159
|
+
creditsPerMinorUnit,
|
|
3160
|
+
displayExchangeRate,
|
|
3161
|
+
estimatedCredits,
|
|
3162
|
+
isApproximate,
|
|
3163
|
+
resolvedAmountMinor
|
|
3164
|
+
]
|
|
2790
3165
|
);
|
|
2791
3166
|
const Comp = asChild ? Slot : "div";
|
|
2792
3167
|
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 }) });
|
|
@@ -2805,19 +3180,18 @@ var Option = (0, import_react19.forwardRef)(function AmountPickerOption({ asChil
|
|
|
2805
3180
|
}),
|
|
2806
3181
|
...rest
|
|
2807
3182
|
};
|
|
3183
|
+
const defaultLabel = formatPrice(
|
|
3184
|
+
amount * getMinorUnitsPerMajor(ctx.currency),
|
|
3185
|
+
ctx.currency,
|
|
3186
|
+
{ free: "" }
|
|
3187
|
+
);
|
|
2808
3188
|
if (asChild) {
|
|
2809
3189
|
return (
|
|
2810
3190
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2811
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ??
|
|
2812
|
-
ctx.currencySymbol,
|
|
2813
|
-
amount.toLocaleString()
|
|
2814
|
-
] }) })
|
|
3191
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? defaultLabel })
|
|
2815
3192
|
);
|
|
2816
3193
|
}
|
|
2817
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ??
|
|
2818
|
-
ctx.currencySymbol,
|
|
2819
|
-
amount.toLocaleString()
|
|
2820
|
-
] }) });
|
|
3194
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? defaultLabel });
|
|
2821
3195
|
});
|
|
2822
3196
|
var Custom = (0, import_react19.forwardRef)(function AmountPickerCustom({ asChild, onChange, onFocus, ...rest }, forwardedRef) {
|
|
2823
3197
|
const ctx = usePickerCtx("Custom");
|
|
@@ -2849,9 +3223,12 @@ var Confirm = (0, import_react19.forwardRef)(function AmountPickerConfirm({ asCh
|
|
|
2849
3223
|
const ctx = usePickerCtx("Confirm");
|
|
2850
3224
|
const isDisabled = disabled || !ctx.resolvedAmount;
|
|
2851
3225
|
const handleConfirm = (0, import_react19.useCallback)(() => {
|
|
2852
|
-
if (ctx.validate()
|
|
2853
|
-
|
|
3226
|
+
if (!ctx.validate()) return;
|
|
3227
|
+
if (ctx.emit === "minor") {
|
|
3228
|
+
if (ctx.resolvedAmountMinor != null) onConfirm?.(ctx.resolvedAmountMinor);
|
|
3229
|
+
return;
|
|
2854
3230
|
}
|
|
3231
|
+
if (ctx.resolvedAmount != null) onConfirm?.(ctx.resolvedAmount);
|
|
2855
3232
|
}, [ctx, onConfirm]);
|
|
2856
3233
|
const commonProps = {
|
|
2857
3234
|
"data-solvapay-amount-picker-confirm": "",
|
|
@@ -2964,16 +3341,19 @@ var Root5 = (0, import_react20.forwardRef)(function ActivationFlowRoot({
|
|
|
2964
3341
|
const retryActivation = (0, import_react20.useCallback)(async () => {
|
|
2965
3342
|
if (!resolvedPlanRef) return;
|
|
2966
3343
|
setStep("retrying");
|
|
2967
|
-
const
|
|
3344
|
+
const amountMinor2 = Math.round(
|
|
3345
|
+
(amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
|
|
3346
|
+
);
|
|
2968
3347
|
const rate = displayExchangeRate ?? 1;
|
|
2969
3348
|
if (creditsPerMinorUnit) {
|
|
2970
|
-
adjustBalance(Math.floor(
|
|
3349
|
+
adjustBalance(Math.floor(amountMinor2 / rate * creditsPerMinorUnit));
|
|
2971
3350
|
}
|
|
2972
3351
|
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
2973
3352
|
await activate({ productRef, planRef: resolvedPlanRef });
|
|
2974
3353
|
}, [
|
|
2975
3354
|
resolvedPlanRef,
|
|
2976
3355
|
amountSelector.resolvedAmount,
|
|
3356
|
+
currency,
|
|
2977
3357
|
displayExchangeRate,
|
|
2978
3358
|
creditsPerMinorUnit,
|
|
2979
3359
|
adjustBalance,
|
|
@@ -3000,7 +3380,9 @@ var Root5 = (0, import_react20.forwardRef)(function ActivationFlowRoot({
|
|
|
3000
3380
|
calledSuccessRef.current = false;
|
|
3001
3381
|
setStep("summary");
|
|
3002
3382
|
}, [reset]);
|
|
3003
|
-
const
|
|
3383
|
+
const amountMinor = Math.round(
|
|
3384
|
+
(amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
|
|
3385
|
+
);
|
|
3004
3386
|
const ctx = (0, import_react20.useMemo)(
|
|
3005
3387
|
() => ({
|
|
3006
3388
|
step,
|
|
@@ -3008,7 +3390,11 @@ var Root5 = (0, import_react20.forwardRef)(function ActivationFlowRoot({
|
|
|
3008
3390
|
productRef,
|
|
3009
3391
|
planRef: resolvedPlanRef ?? "",
|
|
3010
3392
|
amountSelector,
|
|
3011
|
-
|
|
3393
|
+
amountMinor,
|
|
3394
|
+
// Deprecated alias — same value as amountMinor so legacy consumers
|
|
3395
|
+
// keep working after the zero-decimal fix (previously this was
|
|
3396
|
+
// `resolvedAmount * 100`, which over-billed JPY/KRW by 100x).
|
|
3397
|
+
amountCents: amountMinor,
|
|
3012
3398
|
currency,
|
|
3013
3399
|
activate: handleActivate,
|
|
3014
3400
|
reset: resetFlow,
|
|
@@ -3024,7 +3410,7 @@ var Root5 = (0, import_react20.forwardRef)(function ActivationFlowRoot({
|
|
|
3024
3410
|
productRef,
|
|
3025
3411
|
resolvedPlanRef,
|
|
3026
3412
|
amountSelector,
|
|
3027
|
-
|
|
3413
|
+
amountMinor,
|
|
3028
3414
|
currency,
|
|
3029
3415
|
handleActivate,
|
|
3030
3416
|
resetFlow,
|
|
@@ -3086,7 +3472,7 @@ var ActivateButton = (0, import_react20.forwardRef)(
|
|
|
3086
3472
|
var AmountPickerMount = ({ children }) => {
|
|
3087
3473
|
const ctx = useFlowCtx("AmountPicker");
|
|
3088
3474
|
if (ctx.step !== "selectAmount") return null;
|
|
3089
|
-
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AmountPicker.Root, { currency: ctx.currency, children }) });
|
|
3475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AmountPicker.Root, { currency: ctx.currency, selector: ctx.amountSelector, children }) });
|
|
3090
3476
|
};
|
|
3091
3477
|
var ContinueButton = (0, import_react20.forwardRef)(
|
|
3092
3478
|
function ActivationFlowContinueButton({ asChild, onClick, children, ...rest }, forwardedRef) {
|
|
@@ -3357,7 +3743,7 @@ function usePurchaseStatus() {
|
|
|
3357
3743
|
}, []);
|
|
3358
3744
|
const purchaseData = (0, import_react24.useMemo)(() => {
|
|
3359
3745
|
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
3360
|
-
return p.status === "active" && p.cancelledAt && isPaidPurchase(p);
|
|
3746
|
+
return p.status === "active" && p.cancelledAt && isPaidPurchase(p) && isPlanPurchase(p);
|
|
3361
3747
|
});
|
|
3362
3748
|
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
3363
3749
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
@@ -3746,6 +4132,14 @@ var Inner = ({
|
|
|
3746
4132
|
}
|
|
3747
4133
|
setError(null);
|
|
3748
4134
|
setIsProcessing(true);
|
|
4135
|
+
const { error: submitError } = await elements.submit();
|
|
4136
|
+
if (submitError) {
|
|
4137
|
+
const msg = submitError.message || copy.errors.paymentUnexpected;
|
|
4138
|
+
setError(msg);
|
|
4139
|
+
setIsProcessing(false);
|
|
4140
|
+
onError?.(new Error(msg));
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
3749
4143
|
const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
|
|
3750
4144
|
elements,
|
|
3751
4145
|
clientSecret,
|
|
@@ -4158,6 +4552,654 @@ var ProductBadgeImpl = (0, import_react30.forwardRef)(function ProductBadge({ as
|
|
|
4158
4552
|
});
|
|
4159
4553
|
var ProductBadge2 = ProductBadgeImpl;
|
|
4160
4554
|
var PlanBadge = ProductBadgeImpl;
|
|
4555
|
+
|
|
4556
|
+
// src/primitives/PaywallNotice.tsx
|
|
4557
|
+
var import_react32 = require("react");
|
|
4558
|
+
|
|
4559
|
+
// src/hooks/usePaywallResolver.ts
|
|
4560
|
+
var import_react31 = require("react");
|
|
4561
|
+
function usePaywallResolver(content) {
|
|
4562
|
+
const { activePurchase, hasPaidPurchase, refetch: refetchPurchase } = usePurchase();
|
|
4563
|
+
const { credits, refetch: refetchBalance } = useBalance();
|
|
4564
|
+
const resolved = (0, import_react31.useMemo)(() => {
|
|
4565
|
+
if (!content) return false;
|
|
4566
|
+
const productMatches = !content.product || activePurchase?.productRef === content.product || activePurchase?.productName === content.product;
|
|
4567
|
+
if (content.kind === "payment_required") {
|
|
4568
|
+
return Boolean(hasPaidPurchase && productMatches);
|
|
4569
|
+
}
|
|
4570
|
+
const balance = content.balance;
|
|
4571
|
+
if (balance && typeof balance.remainingUnits === "number" && balance.remainingUnits > 0) {
|
|
4572
|
+
return true;
|
|
4573
|
+
}
|
|
4574
|
+
if (balance && credits != null && balance.creditsPerUnit && credits >= balance.creditsPerUnit) {
|
|
4575
|
+
return true;
|
|
4576
|
+
}
|
|
4577
|
+
return Boolean(productMatches && activePurchase?.status === "active");
|
|
4578
|
+
}, [content, hasPaidPurchase, activePurchase, credits]);
|
|
4579
|
+
const refetch = (0, import_react31.useCallback)(async () => {
|
|
4580
|
+
await Promise.all([
|
|
4581
|
+
refetchPurchase().catch(() => void 0),
|
|
4582
|
+
refetchBalance().catch(() => void 0)
|
|
4583
|
+
]);
|
|
4584
|
+
}, [refetchPurchase, refetchBalance]);
|
|
4585
|
+
return { resolved, refetch };
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
// src/utils/isPayg.ts
|
|
4589
|
+
function isPaygPlan(plan) {
|
|
4590
|
+
if (!plan) return false;
|
|
4591
|
+
const type = plan.planType ?? plan.type;
|
|
4592
|
+
return type === "usage-based" || type === "hybrid";
|
|
4593
|
+
}
|
|
4594
|
+
|
|
4595
|
+
// src/primitives/PaywallNotice.tsx
|
|
4596
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4597
|
+
var PaywallNoticeContext = (0, import_react32.createContext)(null);
|
|
4598
|
+
function usePaywallNoticeCtx(part) {
|
|
4599
|
+
const ctx = (0, import_react32.useContext)(PaywallNoticeContext);
|
|
4600
|
+
if (!ctx) {
|
|
4601
|
+
throw new Error(`PaywallNotice.${part} must be rendered inside <PaywallNotice.Root>.`);
|
|
4602
|
+
}
|
|
4603
|
+
return ctx;
|
|
4604
|
+
}
|
|
4605
|
+
var Root10 = (0, import_react32.forwardRef)(function PaywallNoticeRoot({ content, onResolved, classNames, asChild, children, ...rest }, forwardedRef) {
|
|
4606
|
+
const { resolved, refetch } = usePaywallResolver(content);
|
|
4607
|
+
const classNamesResolved = (0, import_react32.useMemo)(() => classNames ?? {}, [classNames]);
|
|
4608
|
+
(0, import_react32.useEffect)(() => {
|
|
4609
|
+
if (resolved) onResolved?.();
|
|
4610
|
+
}, [resolved, onResolved]);
|
|
4611
|
+
const ctx = (0, import_react32.useMemo)(
|
|
4612
|
+
() => ({
|
|
4613
|
+
content,
|
|
4614
|
+
resolved,
|
|
4615
|
+
refetch,
|
|
4616
|
+
onResolved,
|
|
4617
|
+
classNames: classNamesResolved
|
|
4618
|
+
}),
|
|
4619
|
+
[content, resolved, refetch, onResolved, classNamesResolved]
|
|
4620
|
+
);
|
|
4621
|
+
const Comp = asChild ? Slot : "div";
|
|
4622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaywallNoticeContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4623
|
+
Comp,
|
|
4624
|
+
{
|
|
4625
|
+
ref: forwardedRef,
|
|
4626
|
+
"data-solvapay-paywall-notice": "",
|
|
4627
|
+
"data-kind": content.kind,
|
|
4628
|
+
"data-state": resolved ? "resolved" : "pending",
|
|
4629
|
+
className: classNamesResolved.root,
|
|
4630
|
+
...rest,
|
|
4631
|
+
children
|
|
4632
|
+
}
|
|
4633
|
+
) });
|
|
4634
|
+
});
|
|
4635
|
+
var Heading4 = (0, import_react32.forwardRef)(function PaywallNoticeHeading({ asChild, children, className, ...rest }, forwardedRef) {
|
|
4636
|
+
const ctx = usePaywallNoticeCtx("Heading");
|
|
4637
|
+
const copy = useCopy();
|
|
4638
|
+
const defaultText = ctx.resolved ? copy.paywall.resolvedHeading : ctx.content.kind === "payment_required" ? copy.paywall.paymentRequiredHeading : copy.paywall.activationRequiredHeading;
|
|
4639
|
+
const Comp = asChild ? Slot : "h2";
|
|
4640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4641
|
+
Comp,
|
|
4642
|
+
{
|
|
4643
|
+
ref: forwardedRef,
|
|
4644
|
+
"data-solvapay-paywall-heading": "",
|
|
4645
|
+
className: className ?? ctx.classNames.heading,
|
|
4646
|
+
...rest,
|
|
4647
|
+
children: children ?? defaultText
|
|
4648
|
+
}
|
|
4649
|
+
);
|
|
4650
|
+
});
|
|
4651
|
+
var Message = (0, import_react32.forwardRef)(function PaywallNoticeMessage({ asChild, children, className, ...rest }, forwardedRef) {
|
|
4652
|
+
const ctx = usePaywallNoticeCtx("Message");
|
|
4653
|
+
const copy = useCopy();
|
|
4654
|
+
const Comp = asChild ? Slot : "p";
|
|
4655
|
+
const defaultText = resolvePaywallMessage(ctx.content, copy.paywall);
|
|
4656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4657
|
+
Comp,
|
|
4658
|
+
{
|
|
4659
|
+
ref: forwardedRef,
|
|
4660
|
+
"data-solvapay-paywall-message": "",
|
|
4661
|
+
className: className ?? ctx.classNames.message,
|
|
4662
|
+
...rest,
|
|
4663
|
+
children: children ?? defaultText
|
|
4664
|
+
}
|
|
4665
|
+
);
|
|
4666
|
+
});
|
|
4667
|
+
function resolvePaywallMessage(content, paywallCopy) {
|
|
4668
|
+
if (content.kind !== "payment_required") {
|
|
4669
|
+
return content.message;
|
|
4670
|
+
}
|
|
4671
|
+
const balance = content.balance;
|
|
4672
|
+
if (!balance) return content.message;
|
|
4673
|
+
const productName = content.productDetails?.name;
|
|
4674
|
+
const forProduct = productName ? interpolate(paywallCopy.paymentRequiredProductSuffix, { product: productName }) : "";
|
|
4675
|
+
const remaining = balance.remainingUnits ?? 0;
|
|
4676
|
+
if (remaining <= 0) {
|
|
4677
|
+
return interpolate(paywallCopy.paymentRequiredMessage, { forProduct });
|
|
4678
|
+
}
|
|
4679
|
+
return interpolate(paywallCopy.paymentRequiredMessageRemaining, {
|
|
4680
|
+
remaining: String(remaining),
|
|
4681
|
+
pluralSuffix: remaining === 1 ? "" : "s",
|
|
4682
|
+
forProduct
|
|
4683
|
+
});
|
|
4684
|
+
}
|
|
4685
|
+
var ProductContext = (0, import_react32.forwardRef)(function PaywallNoticeProductContext({ asChild, children, className, ...rest }, forwardedRef) {
|
|
4686
|
+
const ctx = usePaywallNoticeCtx("ProductContext");
|
|
4687
|
+
const copy = useCopy();
|
|
4688
|
+
const product = ctx.content.kind === "activation_required" ? ctx.content.productDetails : void 0;
|
|
4689
|
+
if (!product?.name) return null;
|
|
4690
|
+
const label = interpolate(copy.paywall.productContext, { product: product.name });
|
|
4691
|
+
const Comp = asChild ? Slot : "div";
|
|
4692
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4693
|
+
Comp,
|
|
4694
|
+
{
|
|
4695
|
+
ref: forwardedRef,
|
|
4696
|
+
"data-solvapay-paywall-product-context": "",
|
|
4697
|
+
className: className ?? ctx.classNames.productContext,
|
|
4698
|
+
...rest,
|
|
4699
|
+
children: children ?? label
|
|
4700
|
+
}
|
|
4701
|
+
);
|
|
4702
|
+
});
|
|
4703
|
+
var Balance = (0, import_react32.forwardRef)(function PaywallNoticeBalance({ asChild, children, className, ...rest }, forwardedRef) {
|
|
4704
|
+
const ctx = usePaywallNoticeCtx("Balance");
|
|
4705
|
+
const copy = useCopy();
|
|
4706
|
+
const balance = ctx.content.balance;
|
|
4707
|
+
if (ctx.content.kind !== "activation_required" || !balance) return null;
|
|
4708
|
+
const label = interpolate(copy.paywall.balanceLine, {
|
|
4709
|
+
available: String(balance.remainingUnits ?? 0),
|
|
4710
|
+
required: String(balance.creditsPerUnit ?? 1)
|
|
4711
|
+
});
|
|
4712
|
+
const Comp = asChild ? Slot : "div";
|
|
4713
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4714
|
+
Comp,
|
|
4715
|
+
{
|
|
4716
|
+
ref: forwardedRef,
|
|
4717
|
+
"data-solvapay-paywall-balance": "",
|
|
4718
|
+
className: className ?? ctx.classNames.balance,
|
|
4719
|
+
...rest,
|
|
4720
|
+
children: children ?? label
|
|
4721
|
+
}
|
|
4722
|
+
);
|
|
4723
|
+
});
|
|
4724
|
+
function Plans({ className, children }) {
|
|
4725
|
+
const ctx = usePaywallNoticeCtx("Plans");
|
|
4726
|
+
if (ctx.content.kind !== "activation_required") return null;
|
|
4727
|
+
if (!ctx.content.product) return null;
|
|
4728
|
+
const defaultClass = className ?? ctx.classNames.plans ?? "solvapay-paywall-plans";
|
|
4729
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-solvapay-paywall-plans": "", className: defaultClass, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Root, { productRef: ctx.content.product, filter: hidesFreePlan, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
4730
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Grid, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(PlanSelector.Card, { children: [
|
|
4731
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardBadge, {}),
|
|
4732
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardName, {}),
|
|
4733
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardPrice, {}),
|
|
4734
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardInterval, {})
|
|
4735
|
+
] }) }),
|
|
4736
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Loading, {}),
|
|
4737
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Error, {})
|
|
4738
|
+
] }) }) });
|
|
4739
|
+
}
|
|
4740
|
+
function hidesFreePlan(plan) {
|
|
4741
|
+
return plan.requiresPayment !== false;
|
|
4742
|
+
}
|
|
4743
|
+
var HostedCheckoutLink = (0, import_react32.forwardRef)(function PaywallNoticeHostedCheckoutLink({ asChild, children, className, ...rest }, forwardedRef) {
|
|
4744
|
+
const ctx = usePaywallNoticeCtx("HostedCheckoutLink");
|
|
4745
|
+
const copy = useCopy();
|
|
4746
|
+
const href = ctx.content.checkoutUrl;
|
|
4747
|
+
if (!href) return null;
|
|
4748
|
+
const Comp = asChild ? Slot : "a";
|
|
4749
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4750
|
+
Comp,
|
|
4751
|
+
{
|
|
4752
|
+
ref: forwardedRef,
|
|
4753
|
+
href,
|
|
4754
|
+
target: "_blank",
|
|
4755
|
+
rel: "noopener noreferrer",
|
|
4756
|
+
"data-solvapay-paywall-hosted-link": "",
|
|
4757
|
+
className: className ?? ctx.classNames.hostedLink,
|
|
4758
|
+
...rest,
|
|
4759
|
+
children: children ?? copy.paywall.hostedCheckoutButton
|
|
4760
|
+
}
|
|
4761
|
+
);
|
|
4762
|
+
});
|
|
4763
|
+
function EmbeddedCheckout({
|
|
4764
|
+
returnUrl,
|
|
4765
|
+
className,
|
|
4766
|
+
children
|
|
4767
|
+
}) {
|
|
4768
|
+
const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
|
|
4769
|
+
if (!ctx.content.product) return null;
|
|
4770
|
+
const defaultClass = className ?? ctx.classNames.embeddedCheckout ?? "solvapay-paywall-embedded-checkout";
|
|
4771
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-solvapay-paywall-embedded-checkout": "", className: defaultClass, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(PlanSelector.Root, { productRef: ctx.content.product, filter: hidesFreePlan, children: [
|
|
4772
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Grid, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(PlanSelector.Card, { children: [
|
|
4773
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardBadge, {}),
|
|
4774
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardName, {}),
|
|
4775
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardPrice, {}),
|
|
4776
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.CardInterval, {})
|
|
4777
|
+
] }) }),
|
|
4778
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Loading, {}),
|
|
4779
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PlanSelector.Error, {}),
|
|
4780
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaywallSelectedPlanGate, { productRef: ctx.content.product, returnUrl, children })
|
|
4781
|
+
] }) });
|
|
4782
|
+
}
|
|
4783
|
+
function PaywallSelectedPlanGate({
|
|
4784
|
+
productRef,
|
|
4785
|
+
returnUrl,
|
|
4786
|
+
children
|
|
4787
|
+
}) {
|
|
4788
|
+
const { selectedPlan, selectedPlanRef } = usePlanSelector();
|
|
4789
|
+
if (!selectedPlanRef || !selectedPlan) return null;
|
|
4790
|
+
if (isPaygPlan(selectedPlan)) {
|
|
4791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4792
|
+
PaywallPaygGate,
|
|
4793
|
+
{
|
|
4794
|
+
plan: selectedPlan,
|
|
4795
|
+
returnUrl
|
|
4796
|
+
},
|
|
4797
|
+
selectedPlanRef
|
|
4798
|
+
);
|
|
4799
|
+
}
|
|
4800
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaywallPaymentFormGate, { productRef, returnUrl, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
4801
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.Summary, {}),
|
|
4802
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.Loading, {}),
|
|
4803
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.PaymentElement, {}),
|
|
4804
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.Error, {}),
|
|
4805
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.MandateText, {}),
|
|
4806
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PaymentForm.SubmitButton, {})
|
|
4807
|
+
] }) });
|
|
4808
|
+
}
|
|
4809
|
+
function PaywallPaygGate({
|
|
4810
|
+
plan,
|
|
4811
|
+
returnUrl
|
|
4812
|
+
}) {
|
|
4813
|
+
const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
|
|
4814
|
+
const currency = (plan.currency ?? "USD").toUpperCase();
|
|
4815
|
+
const [amountMinor, setAmountMinor] = (0, import_react32.useState)(null);
|
|
4816
|
+
if (amountMinor == null) {
|
|
4817
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-solvapay-paywall-payg-amount": "", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(AmountPicker.Root, { currency, emit: "minor", children: [
|
|
4818
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AmountPicker.Custom, {}),
|
|
4819
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AmountPicker.Confirm, { onConfirm: (minor) => setAmountMinor(minor), children: "Continue" })
|
|
4820
|
+
] }) });
|
|
4821
|
+
}
|
|
4822
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-solvapay-paywall-payg-topup": "", children: [
|
|
4823
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4824
|
+
"button",
|
|
4825
|
+
{
|
|
4826
|
+
type: "button",
|
|
4827
|
+
"data-solvapay-paywall-payg-change-amount": "",
|
|
4828
|
+
onClick: () => setAmountMinor(null),
|
|
4829
|
+
children: "Change amount"
|
|
4830
|
+
}
|
|
4831
|
+
),
|
|
4832
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
4833
|
+
TopupForm.Root,
|
|
4834
|
+
{
|
|
4835
|
+
amount: amountMinor,
|
|
4836
|
+
currency,
|
|
4837
|
+
returnUrl,
|
|
4838
|
+
onSuccess: () => {
|
|
4839
|
+
void ctx.refetch();
|
|
4840
|
+
},
|
|
4841
|
+
children: [
|
|
4842
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TopupForm.Loading, {}),
|
|
4843
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TopupForm.PaymentElement, {}),
|
|
4844
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TopupForm.Error, {}),
|
|
4845
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TopupForm.SubmitButton, {})
|
|
4846
|
+
]
|
|
4847
|
+
}
|
|
4848
|
+
)
|
|
4849
|
+
] });
|
|
4850
|
+
}
|
|
4851
|
+
function PaywallPaymentFormGate({
|
|
4852
|
+
productRef,
|
|
4853
|
+
returnUrl,
|
|
4854
|
+
children
|
|
4855
|
+
}) {
|
|
4856
|
+
const { selectedPlanRef } = usePlanSelector();
|
|
4857
|
+
const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
|
|
4858
|
+
if (!selectedPlanRef) return null;
|
|
4859
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4860
|
+
PaymentForm.Root,
|
|
4861
|
+
{
|
|
4862
|
+
planRef: selectedPlanRef,
|
|
4863
|
+
productRef,
|
|
4864
|
+
returnUrl,
|
|
4865
|
+
requireTermsAcceptance: false,
|
|
4866
|
+
onSuccess: () => {
|
|
4867
|
+
void ctx.refetch();
|
|
4868
|
+
},
|
|
4869
|
+
children
|
|
4870
|
+
},
|
|
4871
|
+
selectedPlanRef
|
|
4872
|
+
);
|
|
4873
|
+
}
|
|
4874
|
+
var Retry = (0, import_react32.forwardRef)(
|
|
4875
|
+
function PaywallNoticeRetry({ asChild, children, className, onClick, ...rest }, forwardedRef) {
|
|
4876
|
+
const ctx = usePaywallNoticeCtx("Retry");
|
|
4877
|
+
const copy = useCopy();
|
|
4878
|
+
const Comp = asChild ? Slot : "button";
|
|
4879
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4880
|
+
Comp,
|
|
4881
|
+
{
|
|
4882
|
+
ref: forwardedRef,
|
|
4883
|
+
type: "button",
|
|
4884
|
+
disabled: !ctx.resolved,
|
|
4885
|
+
"data-solvapay-paywall-retry": "",
|
|
4886
|
+
"data-state": ctx.resolved ? "ready" : "waiting",
|
|
4887
|
+
className: className ?? ctx.classNames.retryButton,
|
|
4888
|
+
onClick: (e) => {
|
|
4889
|
+
onClick?.(e);
|
|
4890
|
+
if (!e.defaultPrevented && ctx.resolved) ctx.onResolved?.();
|
|
4891
|
+
},
|
|
4892
|
+
...rest,
|
|
4893
|
+
children: children ?? copy.paywall.retryButton
|
|
4894
|
+
}
|
|
4895
|
+
);
|
|
4896
|
+
}
|
|
4897
|
+
);
|
|
4898
|
+
var PaywallNotice = Object.assign(Root10, {
|
|
4899
|
+
Root: Root10,
|
|
4900
|
+
Heading: Heading4,
|
|
4901
|
+
Message,
|
|
4902
|
+
ProductContext,
|
|
4903
|
+
Plans,
|
|
4904
|
+
Balance,
|
|
4905
|
+
HostedCheckoutLink,
|
|
4906
|
+
EmbeddedCheckout,
|
|
4907
|
+
Retry
|
|
4908
|
+
});
|
|
4909
|
+
function usePaywallNotice() {
|
|
4910
|
+
return usePaywallNoticeCtx("usePaywallNotice");
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
// src/primitives/UsageMeter.tsx
|
|
4914
|
+
var import_react35 = require("react");
|
|
4915
|
+
|
|
4916
|
+
// src/hooks/useUsage.ts
|
|
4917
|
+
var import_react34 = require("react");
|
|
4918
|
+
|
|
4919
|
+
// src/hooks/useTransport.ts
|
|
4920
|
+
var import_react33 = require("react");
|
|
4921
|
+
function useTransport() {
|
|
4922
|
+
const { _config } = useSolvaPay();
|
|
4923
|
+
return (0, import_react33.useMemo)(() => _config?.transport ?? createHttpTransport(_config), [_config]);
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// src/hooks/useUsage.ts
|
|
4927
|
+
function deriveUsage(purchase) {
|
|
4928
|
+
if (!purchase) return null;
|
|
4929
|
+
const snap = purchase.planSnapshot;
|
|
4930
|
+
const usage = purchase.usage;
|
|
4931
|
+
const meterRef = snap?.meterRef ?? null;
|
|
4932
|
+
const total = typeof snap?.limit === "number" ? snap.limit : null;
|
|
4933
|
+
if (meterRef === null && !usage) return null;
|
|
4934
|
+
const used = typeof usage?.used === "number" ? usage.used : 0;
|
|
4935
|
+
const remaining = total !== null ? Math.max(0, total - used) : null;
|
|
4936
|
+
const percentUsed = total !== null && total > 0 ? Math.min(100, Math.round(used / total * 1e4) / 100) : null;
|
|
4937
|
+
return {
|
|
4938
|
+
meterRef,
|
|
4939
|
+
total,
|
|
4940
|
+
used,
|
|
4941
|
+
remaining,
|
|
4942
|
+
percentUsed,
|
|
4943
|
+
...usage?.periodStart ? { periodStart: usage.periodStart } : {},
|
|
4944
|
+
...usage?.periodEnd ? { periodEnd: usage.periodEnd } : {},
|
|
4945
|
+
...purchase.reference ? { purchaseRef: purchase.reference } : {}
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
function useUsage() {
|
|
4949
|
+
const { activePurchase, refetch: refetchPurchase, loading: purchaseLoading } = usePurchase();
|
|
4950
|
+
const transport = useTransport();
|
|
4951
|
+
const [override, setOverride] = (0, import_react34.useState)(null);
|
|
4952
|
+
const [error, setError] = (0, import_react34.useState)(null);
|
|
4953
|
+
const [transportLoading, setTransportLoading] = (0, import_react34.useState)(false);
|
|
4954
|
+
const derived = (0, import_react34.useMemo)(() => deriveUsage(activePurchase ?? null), [activePurchase]);
|
|
4955
|
+
const activePurchaseRef = activePurchase?.reference ?? null;
|
|
4956
|
+
(0, import_react34.useEffect)(() => {
|
|
4957
|
+
setOverride(null);
|
|
4958
|
+
}, [activePurchaseRef]);
|
|
4959
|
+
const usage = override ?? derived;
|
|
4960
|
+
const refetch = (0, import_react34.useCallback)(async () => {
|
|
4961
|
+
setError(null);
|
|
4962
|
+
if (typeof transport.getUsage === "function") {
|
|
4963
|
+
setTransportLoading(true);
|
|
4964
|
+
try {
|
|
4965
|
+
const next = await transport.getUsage();
|
|
4966
|
+
setOverride(next);
|
|
4967
|
+
} catch (err) {
|
|
4968
|
+
setError(err instanceof Error ? err : new Error("Failed to load usage"));
|
|
4969
|
+
} finally {
|
|
4970
|
+
setTransportLoading(false);
|
|
4971
|
+
}
|
|
4972
|
+
return;
|
|
4973
|
+
}
|
|
4974
|
+
try {
|
|
4975
|
+
await refetchPurchase();
|
|
4976
|
+
} catch (err) {
|
|
4977
|
+
setError(err instanceof Error ? err : new Error("Failed to refetch purchase"));
|
|
4978
|
+
}
|
|
4979
|
+
}, [transport, refetchPurchase]);
|
|
4980
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
4981
|
+
const isApproachingLimit = percentUsed !== null && percentUsed >= 80 && percentUsed < 100;
|
|
4982
|
+
const isAtLimit = percentUsed !== null && percentUsed >= 100;
|
|
4983
|
+
const isUnlimited = usage !== null && usage.total === null;
|
|
4984
|
+
return {
|
|
4985
|
+
usage,
|
|
4986
|
+
loading: purchaseLoading || transportLoading,
|
|
4987
|
+
error,
|
|
4988
|
+
refetch,
|
|
4989
|
+
percentUsed,
|
|
4990
|
+
isApproachingLimit,
|
|
4991
|
+
isAtLimit,
|
|
4992
|
+
isUnlimited,
|
|
4993
|
+
meterRef: usage?.meterRef ?? null
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
// src/primitives/UsageMeter.tsx
|
|
4998
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
4999
|
+
var UsageMeterContext = (0, import_react35.createContext)(null);
|
|
5000
|
+
function useUsageMeterCtx(part) {
|
|
5001
|
+
const ctx = (0, import_react35.useContext)(UsageMeterContext);
|
|
5002
|
+
if (!ctx) {
|
|
5003
|
+
throw new Error(`UsageMeter.${part} must be rendered inside <UsageMeter.Root>.`);
|
|
5004
|
+
}
|
|
5005
|
+
return ctx;
|
|
5006
|
+
}
|
|
5007
|
+
var Root11 = (0, import_react35.forwardRef)(function UsageMeterRoot({
|
|
5008
|
+
warningAt = 75,
|
|
5009
|
+
criticalAt = 90,
|
|
5010
|
+
classNames,
|
|
5011
|
+
asChild,
|
|
5012
|
+
usageOverride,
|
|
5013
|
+
children,
|
|
5014
|
+
className,
|
|
5015
|
+
...rest
|
|
5016
|
+
}, forwardedRef) {
|
|
5017
|
+
const hookResult = useUsage();
|
|
5018
|
+
const usage = usageOverride !== void 0 ? usageOverride : hookResult.usage;
|
|
5019
|
+
const loading = hookResult.loading && !usage;
|
|
5020
|
+
const percentUsed = usage?.percentUsed ?? null;
|
|
5021
|
+
const state = loading ? "loading" : percentUsed === null ? "safe" : percentUsed >= criticalAt ? "critical" : percentUsed >= warningAt ? "warning" : "safe";
|
|
5022
|
+
const ctx = (0, import_react35.useMemo)(
|
|
5023
|
+
() => ({
|
|
5024
|
+
usage,
|
|
5025
|
+
loading,
|
|
5026
|
+
error: hookResult.error,
|
|
5027
|
+
percentUsed,
|
|
5028
|
+
isApproachingLimit: hookResult.isApproachingLimit,
|
|
5029
|
+
isAtLimit: hookResult.isAtLimit,
|
|
5030
|
+
isUnlimited: hookResult.isUnlimited,
|
|
5031
|
+
state,
|
|
5032
|
+
warningAt,
|
|
5033
|
+
criticalAt
|
|
5034
|
+
}),
|
|
5035
|
+
[
|
|
5036
|
+
usage,
|
|
5037
|
+
loading,
|
|
5038
|
+
hookResult.error,
|
|
5039
|
+
percentUsed,
|
|
5040
|
+
hookResult.isApproachingLimit,
|
|
5041
|
+
hookResult.isAtLimit,
|
|
5042
|
+
hookResult.isUnlimited,
|
|
5043
|
+
state,
|
|
5044
|
+
warningAt,
|
|
5045
|
+
criticalAt
|
|
5046
|
+
]
|
|
5047
|
+
);
|
|
5048
|
+
const rootClass = [classNames?.root ?? "solvapay-usage-meter", className].filter(Boolean).join(" ");
|
|
5049
|
+
const Comp = asChild ? Slot : "div";
|
|
5050
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(UsageMeterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5051
|
+
Comp,
|
|
5052
|
+
{
|
|
5053
|
+
ref: forwardedRef,
|
|
5054
|
+
"data-solvapay-usage-meter": "",
|
|
5055
|
+
"data-state": state,
|
|
5056
|
+
className: rootClass,
|
|
5057
|
+
...rest,
|
|
5058
|
+
children
|
|
5059
|
+
}
|
|
5060
|
+
) });
|
|
5061
|
+
});
|
|
5062
|
+
var Bar = (0, import_react35.forwardRef)(function UsageMeterBar({ className, style, ...rest }, forwardedRef) {
|
|
5063
|
+
const ctx = useUsageMeterCtx("Bar");
|
|
5064
|
+
const width = ctx.percentUsed === null ? 0 : Math.max(0, Math.min(100, ctx.percentUsed));
|
|
5065
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5066
|
+
"div",
|
|
5067
|
+
{
|
|
5068
|
+
ref: forwardedRef,
|
|
5069
|
+
"data-solvapay-usage-meter-bar": "",
|
|
5070
|
+
"data-state": ctx.state,
|
|
5071
|
+
role: "progressbar",
|
|
5072
|
+
"aria-valuenow": Math.round(width),
|
|
5073
|
+
"aria-valuemin": 0,
|
|
5074
|
+
"aria-valuemax": 100,
|
|
5075
|
+
className: className ?? "solvapay-usage-meter-bar",
|
|
5076
|
+
style: {
|
|
5077
|
+
// Expose as a CSS custom property so default styles can drive
|
|
5078
|
+
// `width: var(--solvapay-usage-meter-fill)` without inline styling.
|
|
5079
|
+
["--solvapay-usage-meter-fill"]: `${width}%`,
|
|
5080
|
+
...style
|
|
5081
|
+
},
|
|
5082
|
+
...rest
|
|
5083
|
+
}
|
|
5084
|
+
);
|
|
5085
|
+
});
|
|
5086
|
+
var Label = (0, import_react35.forwardRef)(function UsageMeterLabel({ className, children, ...rest }, forwardedRef) {
|
|
5087
|
+
const ctx = useUsageMeterCtx("Label");
|
|
5088
|
+
const copy = useCopy();
|
|
5089
|
+
if (!ctx.usage) return null;
|
|
5090
|
+
if (ctx.isUnlimited) {
|
|
5091
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5092
|
+
"span",
|
|
5093
|
+
{
|
|
5094
|
+
ref: forwardedRef,
|
|
5095
|
+
"data-solvapay-usage-meter-label": "",
|
|
5096
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
5097
|
+
...rest,
|
|
5098
|
+
children: children ?? copy.usage.unlimitedLabel
|
|
5099
|
+
}
|
|
5100
|
+
);
|
|
5101
|
+
}
|
|
5102
|
+
const unit = ctx.usage.meterRef ?? "units";
|
|
5103
|
+
const label = ctx.usage.total !== null ? interpolate(copy.usage.usedLabel, {
|
|
5104
|
+
used: String(ctx.usage.used),
|
|
5105
|
+
total: String(ctx.usage.total),
|
|
5106
|
+
unit
|
|
5107
|
+
}) : interpolate(copy.usage.remainingLabel, {
|
|
5108
|
+
remaining: String(ctx.usage.remaining ?? 0),
|
|
5109
|
+
unit
|
|
5110
|
+
});
|
|
5111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5112
|
+
"span",
|
|
5113
|
+
{
|
|
5114
|
+
ref: forwardedRef,
|
|
5115
|
+
"data-solvapay-usage-meter-label": "",
|
|
5116
|
+
className: className ?? "solvapay-usage-meter-label",
|
|
5117
|
+
...rest,
|
|
5118
|
+
children: children ?? label
|
|
5119
|
+
}
|
|
5120
|
+
);
|
|
5121
|
+
});
|
|
5122
|
+
var Percentage = (0, import_react35.forwardRef)(function UsageMeterPercentage({ className, children, ...rest }, forwardedRef) {
|
|
5123
|
+
const ctx = useUsageMeterCtx("Percentage");
|
|
5124
|
+
const copy = useCopy();
|
|
5125
|
+
if (ctx.percentUsed === null) return null;
|
|
5126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5127
|
+
"span",
|
|
5128
|
+
{
|
|
5129
|
+
ref: forwardedRef,
|
|
5130
|
+
"data-solvapay-usage-meter-percentage": "",
|
|
5131
|
+
className: className ?? "solvapay-usage-meter-percentage",
|
|
5132
|
+
...rest,
|
|
5133
|
+
children: children ?? interpolate(copy.usage.percentUsedLabel, { percent: String(Math.round(ctx.percentUsed)) })
|
|
5134
|
+
}
|
|
5135
|
+
);
|
|
5136
|
+
});
|
|
5137
|
+
function formatResetsIn(copy, periodEnd, nowMs = typeof performance !== "undefined" ? performance.timeOrigin + performance.now() : 0) {
|
|
5138
|
+
const end = new Date(periodEnd).getTime();
|
|
5139
|
+
if (Number.isNaN(end)) return null;
|
|
5140
|
+
const daysLeft = Math.max(0, Math.ceil((end - nowMs) / (24 * 60 * 60 * 1e3)));
|
|
5141
|
+
return daysLeft > 0 ? interpolate(copy.usage.resetsInLabel, { days: String(daysLeft) }) : interpolate(copy.usage.resetsOnLabel, { date: new Date(periodEnd).toLocaleDateString() });
|
|
5142
|
+
}
|
|
5143
|
+
var ResetsIn = (0, import_react35.forwardRef)(function UsageMeterResetsIn({ className, children, ...rest }, forwardedRef) {
|
|
5144
|
+
const ctx = useUsageMeterCtx("ResetsIn");
|
|
5145
|
+
const copy = useCopy();
|
|
5146
|
+
const periodEnd = ctx.usage?.periodEnd;
|
|
5147
|
+
if (!periodEnd) return null;
|
|
5148
|
+
const label = formatResetsIn(copy, periodEnd);
|
|
5149
|
+
if (!label) return null;
|
|
5150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5151
|
+
"span",
|
|
5152
|
+
{
|
|
5153
|
+
ref: forwardedRef,
|
|
5154
|
+
"data-solvapay-usage-meter-resets-in": "",
|
|
5155
|
+
className: className ?? "solvapay-usage-meter-resets-in",
|
|
5156
|
+
...rest,
|
|
5157
|
+
children: children ?? label
|
|
5158
|
+
}
|
|
5159
|
+
);
|
|
5160
|
+
});
|
|
5161
|
+
var Loading7 = (0, import_react35.forwardRef)(function UsageMeterLoading({ className, children, ...rest }, forwardedRef) {
|
|
5162
|
+
const ctx = useUsageMeterCtx("Loading");
|
|
5163
|
+
const copy = useCopy();
|
|
5164
|
+
if (ctx.state !== "loading") return null;
|
|
5165
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5166
|
+
"div",
|
|
5167
|
+
{
|
|
5168
|
+
ref: forwardedRef,
|
|
5169
|
+
"data-solvapay-usage-meter-loading": "",
|
|
5170
|
+
className: className ?? "solvapay-usage-meter-loading",
|
|
5171
|
+
...rest,
|
|
5172
|
+
children: children ?? copy.usage.loadingLabel
|
|
5173
|
+
}
|
|
5174
|
+
);
|
|
5175
|
+
});
|
|
5176
|
+
var Empty = (0, import_react35.forwardRef)(function UsageMeterEmpty({ className, children, ...rest }, forwardedRef) {
|
|
5177
|
+
const ctx = useUsageMeterCtx("Empty");
|
|
5178
|
+
const copy = useCopy();
|
|
5179
|
+
if (ctx.loading || ctx.usage !== null) return null;
|
|
5180
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5181
|
+
"div",
|
|
5182
|
+
{
|
|
5183
|
+
ref: forwardedRef,
|
|
5184
|
+
"data-solvapay-usage-meter-empty": "",
|
|
5185
|
+
className: className ?? "solvapay-usage-meter-empty",
|
|
5186
|
+
...rest,
|
|
5187
|
+
children: children ?? copy.usage.emptyLabel
|
|
5188
|
+
}
|
|
5189
|
+
);
|
|
5190
|
+
});
|
|
5191
|
+
var UsageMeter = Object.assign(Root11, {
|
|
5192
|
+
Root: Root11,
|
|
5193
|
+
Bar,
|
|
5194
|
+
Label,
|
|
5195
|
+
Percentage,
|
|
5196
|
+
ResetsIn,
|
|
5197
|
+
Loading: Loading7,
|
|
5198
|
+
Empty
|
|
5199
|
+
});
|
|
5200
|
+
function useUsageMeter() {
|
|
5201
|
+
return useUsageMeterCtx("useUsageMeter");
|
|
5202
|
+
}
|
|
4161
5203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4162
5204
|
0 && (module.exports = {
|
|
4163
5205
|
ActivationFlow,
|
|
@@ -4212,6 +5254,16 @@ var PlanBadge = ProductBadgeImpl;
|
|
|
4212
5254
|
PaymentFormSubmitButton,
|
|
4213
5255
|
PaymentFormSummary,
|
|
4214
5256
|
PaymentFormTermsCheckbox,
|
|
5257
|
+
PaywallNotice,
|
|
5258
|
+
PaywallNoticeBalance,
|
|
5259
|
+
PaywallNoticeEmbeddedCheckout,
|
|
5260
|
+
PaywallNoticeHeading,
|
|
5261
|
+
PaywallNoticeHostedCheckoutLink,
|
|
5262
|
+
PaywallNoticeMessage,
|
|
5263
|
+
PaywallNoticePlans,
|
|
5264
|
+
PaywallNoticeProductContext,
|
|
5265
|
+
PaywallNoticeRetry,
|
|
5266
|
+
PaywallNoticeRoot,
|
|
4215
5267
|
PlanBadge,
|
|
4216
5268
|
PlanSelector,
|
|
4217
5269
|
PlanSelectorCard,
|
|
@@ -4239,6 +5291,14 @@ var PlanBadge = ProductBadgeImpl;
|
|
|
4239
5291
|
TopupFormPaymentElement,
|
|
4240
5292
|
TopupFormRoot,
|
|
4241
5293
|
TopupFormSubmitButton,
|
|
5294
|
+
UsageMeter,
|
|
5295
|
+
UsageMeterBar,
|
|
5296
|
+
UsageMeterEmpty,
|
|
5297
|
+
UsageMeterLabel,
|
|
5298
|
+
UsageMeterLoading,
|
|
5299
|
+
UsageMeterPercentage,
|
|
5300
|
+
UsageMeterResetsIn,
|
|
5301
|
+
UsageMeterRoot,
|
|
4242
5302
|
composeEventHandlers,
|
|
4243
5303
|
composeRefs,
|
|
4244
5304
|
setRef,
|
|
@@ -4248,7 +5308,9 @@ var PlanBadge = ProductBadgeImpl;
|
|
|
4248
5308
|
useCancelledPlanNotice,
|
|
4249
5309
|
useCheckoutSummary,
|
|
4250
5310
|
useCreditGate,
|
|
5311
|
+
usePaywallNotice,
|
|
4251
5312
|
usePlanSelector,
|
|
4252
5313
|
usePurchaseGate,
|
|
4253
|
-
useTopupForm
|
|
5314
|
+
useTopupForm,
|
|
5315
|
+
useUsageMeter
|
|
4254
5316
|
});
|