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