@solvapay/react 2.0.0 → 2.0.1-preview-7d806cc7af767dec26b099517bc6ededc85adf9a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @solvapay/react changelog
2
2
 
3
+ ## 2.0.1-preview-7d806cc7af767dec26b099517bc6ededc85adf9a
4
+
5
+ ### Patch Changes
6
+
7
+ - a47adff: Free plans with an included allowance now count usage and label the meter correctly.
8
+
9
+ `@solvapay/core` adds `countsUsage` (per-unit charge, tier, or limit) and `meterName`
10
+ (per-unit charge's meter, else the first limit's meter). A free tier with a bare
11
+ limit is not priced per unit, but it still needs a usage counter and a meter noun —
12
+ `isMetered` on the wire was the wrong question for both.
13
+
14
+ `planMeterName` uses the new reader, so a recurring allowance no longer renders the
15
+ literal "units". `useAutoActivateFreePlan` no longer requires deprecated `freeUnits`
16
+ on the plan row. Usage surfaces (`useUsage`, `getUsageCore`, plan cards) derive
17
+ from `options[]` instead of trusting `planSnapshot.isMetered` alone.
18
+
19
+ - Updated dependencies [a47adff]
20
+ - @solvapay/core@1.5.0-preview-7d806cc7af767dec26b099517bc6ededc85adf9a
21
+ - @solvapay/mcp-core@0.3.2-preview-7d806cc7af767dec26b099517bc6ededc85adf9a
22
+
3
23
  ## 2.0.0
4
24
 
5
25
  ### Major Changes
@@ -40,7 +40,7 @@ import {
40
40
  useTopupAmountSelector,
41
41
  withPaymentElementDefaults,
42
42
  writeAutoRechargeCache
43
- } from "./chunk-6ABOJOEV.js";
43
+ } from "./chunk-JN5BJ5MI.js";
44
44
 
45
45
  // src/TopupForm.tsx
46
46
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -303,10 +303,10 @@ function createHttpTransport(config) {
303
303
  onErrorContext: "getUsage",
304
304
  errorPrefix: "Failed to load usage"
305
305
  }),
306
- getLimits: async ({ productRef, meterName }) => {
306
+ getLimits: async ({ productRef, meterName: meterName2 }) => {
307
307
  const base = routeFor(config, "getLimits");
308
308
  const params = new URLSearchParams({ productRef });
309
- if (meterName) params.set("meterName", meterName);
309
+ if (meterName2) params.set("meterName", meterName2);
310
310
  const url = `${base}?${params.toString()}`;
311
311
  const data = await request(config, url, {
312
312
  method: "GET",
@@ -3010,7 +3010,8 @@ function normalizeOneTimePurchase(input) {
3010
3010
  planSnapshot: {
3011
3011
  price: input.amount,
3012
3012
  currency: input.currency,
3013
- isMetered: false
3013
+ isMetered: false,
3014
+ options: []
3014
3015
  }
3015
3016
  };
3016
3017
  }
@@ -5796,12 +5797,13 @@ function usePurchaseActions() {
5796
5797
  }
5797
5798
 
5798
5799
  // src/primitives/CancelPlanButton.tsx
5800
+ import { countsUsage } from "@solvapay/core";
5799
5801
  import { forwardRef as forwardRef10, useCallback as useCallback16, useContext as useContext13 } from "react";
5800
5802
  import { Fragment as Fragment9, jsx as jsx17 } from "react/jsx-runtime";
5801
5803
  function resolveConfirmText(confirm, purchase, defaults) {
5802
5804
  if (confirm === false) return null;
5803
5805
  if (typeof confirm === "string") return confirm;
5804
- const isUsageBased = purchase?.planSnapshot?.isMetered === true && purchase.isRecurring !== true;
5806
+ const isUsageBased = (countsUsage(purchase?.planSnapshot) || purchase?.planSnapshot?.isMetered === true) && purchase?.isRecurring !== true;
5805
5807
  if (isUsageBased) return defaults.usageBased;
5806
5808
  return defaults.recurring;
5807
5809
  }
@@ -6098,19 +6100,19 @@ import { useCallback as useCallback19, useEffect as useEffect12, useRef as useRe
6098
6100
  var CACHE_TTL_MS = 1e4;
6099
6101
  var OPTIMISTIC_GRACE_MS = 8e3;
6100
6102
  var limitsCache = /* @__PURE__ */ new Map();
6101
- function cacheKey(customerRef, productRef, meterName) {
6102
- return `${customerRef ?? "anonymous"}:${productRef}:${meterName}`;
6103
+ function cacheKey(customerRef, productRef, meterName2) {
6104
+ return `${customerRef ?? "anonymous"}:${productRef}:${meterName2}`;
6103
6105
  }
6104
6106
  function isUnlimited(remaining) {
6105
6107
  return remaining < 0;
6106
6108
  }
6107
6109
  function useLimits(options) {
6108
6110
  const { productRef, meterName: rawMeterName, enabled = true } = options;
6109
- const meterName = rawMeterName || "requests";
6111
+ const meterName2 = rawMeterName || "requests";
6110
6112
  const transport = useTransport();
6111
6113
  const { customerRef } = useCustomer();
6112
6114
  const { purchase } = useSolvaPay();
6113
- const currentKey = !productRef || !enabled ? null : cacheKey(customerRef, productRef, meterName);
6115
+ const currentKey = !productRef || !enabled ? null : cacheKey(customerRef, productRef, meterName2);
6114
6116
  const [data, setData] = useState15(() => {
6115
6117
  if (!currentKey) return null;
6116
6118
  const cached = limitsCache.get(currentKey);
@@ -6165,7 +6167,7 @@ function useLimits(options) {
6165
6167
  setLoading(false);
6166
6168
  return;
6167
6169
  }
6168
- const key = cacheKey(customerRef, productRef, meterName);
6170
+ const key = cacheKey(customerRef, productRef, meterName2);
6169
6171
  const cached = limitsCache.get(key);
6170
6172
  const now = Date.now();
6171
6173
  if (cached?.promise) {
@@ -6178,7 +6180,7 @@ function useLimits(options) {
6178
6180
  setError(null);
6179
6181
  return;
6180
6182
  }
6181
- const promise = transport.getLimits({ productRef, meterName });
6183
+ const promise = transport.getLimits({ productRef, meterName: meterName2 });
6182
6184
  limitsCache.set(key, { data: cached?.data ?? null, timestamp: now, promise });
6183
6185
  const fresh = await attachToFetch(promise);
6184
6186
  if (fresh !== null) {
@@ -6187,7 +6189,7 @@ function useLimits(options) {
6187
6189
  limitsCache.delete(key);
6188
6190
  }
6189
6191
  },
6190
- [transport, customerRef, productRef, meterName, enabled, attachToFetch]
6192
+ [transport, customerRef, productRef, meterName2, enabled, attachToFetch]
6191
6193
  );
6192
6194
  useEffect12(() => {
6193
6195
  fetchLimits(false);
@@ -6205,7 +6207,7 @@ function useLimits(options) {
6205
6207
  (delta) => {
6206
6208
  if (!productRef || !enabled) return;
6207
6209
  if (unlimitedRef.current) return;
6208
- const key = cacheKey(customerRef, productRef, meterName);
6210
+ const key = cacheKey(customerRef, productRef, meterName2);
6209
6211
  setData((prev) => {
6210
6212
  const cached = limitsCache.get(key);
6211
6213
  const baseline = prev ?? cached?.data ?? null;
@@ -6232,7 +6234,7 @@ function useLimits(options) {
6232
6234
  });
6233
6235
  }, OPTIMISTIC_GRACE_MS);
6234
6236
  },
6235
- [customerRef, productRef, meterName, enabled, fetchLimits]
6237
+ [customerRef, productRef, meterName2, enabled, fetchLimits]
6236
6238
  );
6237
6239
  return {
6238
6240
  remaining: data?.remaining ?? null,
@@ -6248,11 +6250,14 @@ function useLimits(options) {
6248
6250
  }
6249
6251
 
6250
6252
  // src/hooks/useUsage.ts
6253
+ import { countsUsage as countsUsage2 } from "@solvapay/core";
6251
6254
  import { useCallback as useCallback20, useEffect as useEffect13, useMemo as useMemo14, useState as useState16 } from "react";
6252
6255
  function deriveUsage(purchase, limits) {
6253
6256
  if (!purchase) return null;
6254
6257
  const usage = purchase.usage;
6255
- if (purchase.planSnapshot?.isMetered !== true && !usage) return null;
6258
+ if (!countsUsage2(purchase.planSnapshot) && purchase.planSnapshot?.isMetered !== true && !usage) {
6259
+ return null;
6260
+ }
6256
6261
  const used = typeof usage?.used === "number" ? usage.used : 0;
6257
6262
  const hasFiniteCap = limits.unlimited === false && limits.remaining !== null;
6258
6263
  const remaining = hasFiniteCap ? limits.remaining : null;
@@ -6275,18 +6280,18 @@ function useUsage() {
6275
6280
  const [override, setOverride] = useState16(null);
6276
6281
  const [error, setError] = useState16(null);
6277
6282
  const [transportLoading, setTransportLoading] = useState16(false);
6278
- const isMetered = activePurchase?.planSnapshot?.isMetered === true;
6283
+ const usageCounted = countsUsage2(activePurchase?.planSnapshot) || activePurchase?.planSnapshot?.isMetered === true;
6279
6284
  const {
6280
6285
  remaining: limitRemaining,
6281
6286
  unlimited,
6282
- meterName
6287
+ meterName: meterName2
6283
6288
  } = useLimits({
6284
6289
  productRef: activePurchase?.productRef,
6285
- enabled: isMetered
6290
+ enabled: usageCounted
6286
6291
  });
6287
6292
  const derived = useMemo14(
6288
- () => deriveUsage(activePurchase ?? null, { remaining: limitRemaining, unlimited, meterName }),
6289
- [activePurchase, limitRemaining, unlimited, meterName]
6293
+ () => deriveUsage(activePurchase ?? null, { remaining: limitRemaining, unlimited, meterName: meterName2 }),
6294
+ [activePurchase, limitRemaining, unlimited, meterName2]
6290
6295
  );
6291
6296
  const activePurchaseRef = activePurchase?.reference ?? null;
6292
6297
  useEffect13(() => {
@@ -6547,6 +6552,7 @@ import {
6547
6552
  billingCycle as readBillingCycle,
6548
6553
  creditsPerUnitFromBalance,
6549
6554
  includedUnits,
6555
+ meterName,
6550
6556
  perUnitCharge
6551
6557
  } from "@solvapay/core";
6552
6558
  function isPayg(plan) {
@@ -6607,7 +6613,7 @@ function inferIncludedUnits(plan) {
6607
6613
  return cap != null && cap > 0 ? cap : null;
6608
6614
  }
6609
6615
  function planMeterName(plan) {
6610
- return perUnitCharge(plan)?.meter ?? null;
6616
+ return meterName(plan);
6611
6617
  }
6612
6618
  function shortCycle(cycle) {
6613
6619
  if (!cycle) return "mo";
@@ -14,7 +14,7 @@ import {
14
14
  usePurchaseStatus,
15
15
  useSolvaPay,
16
16
  useTransport
17
- } from "./chunk-6ABOJOEV.js";
17
+ } from "./chunk-JN5BJ5MI.js";
18
18
 
19
19
  // src/components/CancelledPlanNotice.tsx
20
20
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -362,6 +362,7 @@ var UpdatePaymentMethodButton = forwardRef2(function UpdatePaymentMethodButton2(
362
362
  });
363
363
 
364
364
  // src/components/CurrentPlanCard.tsx
365
+ import { countsUsage } from "@solvapay/core";
365
366
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
366
367
  function PlanTypeLine({
367
368
  purchase,
@@ -369,7 +370,7 @@ function PlanTypeLine({
369
370
  className
370
371
  }) {
371
372
  const copy = useCopy();
372
- const isMetered = purchase.planSnapshot?.isMetered === true;
373
+ const isMetered = countsUsage(purchase.planSnapshot) || purchase.planSnapshot?.isMetered === true;
373
374
  if (purchase.isRecurring) {
374
375
  const date2 = formatDate(purchase.nextBillingDate);
375
376
  if (!date2) return null;
@@ -429,7 +430,7 @@ var CurrentPlanCard = ({
429
430
  const { formatDate, shouldShowCancelledNotice } = usePurchaseStatus();
430
431
  const { paymentMethod } = usePaymentMethod();
431
432
  if (!activePurchase) return null;
432
- const isUsageBased = activePurchase.planSnapshot?.isMetered === true;
433
+ const isUsageBased = countsUsage(activePurchase.planSnapshot) || activePurchase.planSnapshot?.isMetered === true;
433
434
  const planType = activePurchase.isRecurring ? "recurring" : isUsageBased ? "usage-based" : "one-time";
434
435
  const amount = activePurchase.originalAmount ?? activePurchase.amount ?? 0;
435
436
  const currency = activePurchase.currency ?? "usd";
@@ -527,15 +527,6 @@ declare const ActivationFlow: {
527
527
  };
528
528
  declare function useActivationFlow(): ActivationFlowContextValue;
529
529
 
530
- /**
531
- * CancelPlanButton leaf primitive.
532
- *
533
- * Cancels the active (or specified) purchase via `usePurchaseActions`.
534
- * Emits `data-state=idle|cancelling` and auto-derives the confirm dialog
535
- * copy from the plan type (`recurring` vs. `usage-based`) unless overridden
536
- * via the `confirm` prop (`false` to skip, string to replace).
537
- */
538
-
539
530
  declare const CancelPlanButton: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & {
540
531
  asChild?: boolean;
541
532
  /** Defaults to the active purchase from `usePurchase()`. */
@@ -527,15 +527,6 @@ declare const ActivationFlow: {
527
527
  };
528
528
  declare function useActivationFlow(): ActivationFlowContextValue;
529
529
 
530
- /**
531
- * CancelPlanButton leaf primitive.
532
- *
533
- * Cancels the active (or specified) purchase via `usePurchaseActions`.
534
- * Emits `data-state=idle|cancelling` and auto-derives the confirm dialog
535
- * copy from the plan type (`recurring` vs. `usage-based`) unless overridden
536
- * via the `confirm` prop (`false` to skip, string to replace).
537
- */
538
-
539
530
  declare const CancelPlanButton: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & {
540
531
  asChild?: boolean;
541
532
  /** Defaults to the active purchase from `usePurchase()`. */
package/dist/index.cjs CHANGED
@@ -544,10 +544,10 @@ function createHttpTransport(config) {
544
544
  onErrorContext: "getUsage",
545
545
  errorPrefix: "Failed to load usage"
546
546
  }),
547
- getLimits: async ({ productRef, meterName }) => {
547
+ getLimits: async ({ productRef, meterName: meterName2 }) => {
548
548
  const base = routeFor(config, "getLimits");
549
549
  const params = new URLSearchParams({ productRef });
550
- if (meterName) params.set("meterName", meterName);
550
+ if (meterName2) params.set("meterName", meterName2);
551
551
  const url = `${base}?${params.toString()}`;
552
552
  const data = await request(config, url, {
553
553
  method: "GET",
@@ -3128,7 +3128,8 @@ function normalizeOneTimePurchase(input) {
3128
3128
  planSnapshot: {
3129
3129
  price: input.amount,
3130
3130
  currency: input.currency,
3131
- isMetered: false
3131
+ isMetered: false,
3132
+ options: []
3132
3133
  }
3133
3134
  };
3134
3135
  }
@@ -6812,6 +6813,7 @@ var DefaultTree3 = ({ showCreditEstimate }) => {
6812
6813
  };
6813
6814
 
6814
6815
  // src/primitives/CancelPlanButton.tsx
6816
+ var import_core6 = require("@solvapay/core");
6815
6817
  var import_react32 = require("react");
6816
6818
 
6817
6819
  // src/hooks/usePurchaseActions.ts
@@ -6869,7 +6871,7 @@ var import_jsx_runtime27 = require("react/jsx-runtime");
6869
6871
  function resolveConfirmText(confirm, purchase, defaults) {
6870
6872
  if (confirm === false) return null;
6871
6873
  if (typeof confirm === "string") return confirm;
6872
- const isUsageBased = purchase?.planSnapshot?.isMetered === true && purchase.isRecurring !== true;
6874
+ const isUsageBased = ((0, import_core6.countsUsage)(purchase?.planSnapshot) || purchase?.planSnapshot?.isMetered === true) && purchase?.isRecurring !== true;
6873
6875
  if (isUsageBased) return defaults.usageBased;
6874
6876
  return defaults.recurring;
6875
6877
  }
@@ -7320,6 +7322,9 @@ var CreditGate2 = ({
7320
7322
  );
7321
7323
  };
7322
7324
 
7325
+ // src/components/CurrentPlanCard.tsx
7326
+ var import_core8 = require("@solvapay/core");
7327
+
7323
7328
  // src/hooks/usePaymentMethod.ts
7324
7329
  var import_react36 = require("react");
7325
7330
  var paymentMethodCache = /* @__PURE__ */ new Map();
@@ -7649,6 +7654,7 @@ var UpdatePaymentMethodButton = (0, import_react40.forwardRef)(function UpdatePa
7649
7654
  var import_react43 = require("react");
7650
7655
 
7651
7656
  // src/hooks/useUsage.ts
7657
+ var import_core7 = require("@solvapay/core");
7652
7658
  var import_react42 = require("react");
7653
7659
 
7654
7660
  // src/hooks/useLimits.ts
@@ -7656,19 +7662,19 @@ var import_react41 = require("react");
7656
7662
  var CACHE_TTL_MS = 1e4;
7657
7663
  var OPTIMISTIC_GRACE_MS = 8e3;
7658
7664
  var limitsCache = /* @__PURE__ */ new Map();
7659
- function cacheKey(customerRef, productRef, meterName) {
7660
- return `${customerRef ?? "anonymous"}:${productRef}:${meterName}`;
7665
+ function cacheKey(customerRef, productRef, meterName2) {
7666
+ return `${customerRef ?? "anonymous"}:${productRef}:${meterName2}`;
7661
7667
  }
7662
7668
  function isUnlimited(remaining) {
7663
7669
  return remaining < 0;
7664
7670
  }
7665
7671
  function useLimits(options) {
7666
7672
  const { productRef, meterName: rawMeterName, enabled = true } = options;
7667
- const meterName = rawMeterName || "requests";
7673
+ const meterName2 = rawMeterName || "requests";
7668
7674
  const transport = useTransport();
7669
7675
  const { customerRef } = useCustomer();
7670
7676
  const { purchase } = useSolvaPay();
7671
- const currentKey = !productRef || !enabled ? null : cacheKey(customerRef, productRef, meterName);
7677
+ const currentKey = !productRef || !enabled ? null : cacheKey(customerRef, productRef, meterName2);
7672
7678
  const [data, setData] = (0, import_react41.useState)(() => {
7673
7679
  if (!currentKey) return null;
7674
7680
  const cached = limitsCache.get(currentKey);
@@ -7723,7 +7729,7 @@ function useLimits(options) {
7723
7729
  setLoading(false);
7724
7730
  return;
7725
7731
  }
7726
- const key = cacheKey(customerRef, productRef, meterName);
7732
+ const key = cacheKey(customerRef, productRef, meterName2);
7727
7733
  const cached = limitsCache.get(key);
7728
7734
  const now = Date.now();
7729
7735
  if (cached?.promise) {
@@ -7736,7 +7742,7 @@ function useLimits(options) {
7736
7742
  setError(null);
7737
7743
  return;
7738
7744
  }
7739
- const promise = transport.getLimits({ productRef, meterName });
7745
+ const promise = transport.getLimits({ productRef, meterName: meterName2 });
7740
7746
  limitsCache.set(key, { data: cached?.data ?? null, timestamp: now, promise });
7741
7747
  const fresh = await attachToFetch(promise);
7742
7748
  if (fresh !== null) {
@@ -7745,7 +7751,7 @@ function useLimits(options) {
7745
7751
  limitsCache.delete(key);
7746
7752
  }
7747
7753
  },
7748
- [transport, customerRef, productRef, meterName, enabled, attachToFetch]
7754
+ [transport, customerRef, productRef, meterName2, enabled, attachToFetch]
7749
7755
  );
7750
7756
  (0, import_react41.useEffect)(() => {
7751
7757
  fetchLimits(false);
@@ -7763,7 +7769,7 @@ function useLimits(options) {
7763
7769
  (delta) => {
7764
7770
  if (!productRef || !enabled) return;
7765
7771
  if (unlimitedRef.current) return;
7766
- const key = cacheKey(customerRef, productRef, meterName);
7772
+ const key = cacheKey(customerRef, productRef, meterName2);
7767
7773
  setData((prev) => {
7768
7774
  const cached = limitsCache.get(key);
7769
7775
  const baseline = prev ?? cached?.data ?? null;
@@ -7790,7 +7796,7 @@ function useLimits(options) {
7790
7796
  });
7791
7797
  }, OPTIMISTIC_GRACE_MS);
7792
7798
  },
7793
- [customerRef, productRef, meterName, enabled, fetchLimits]
7799
+ [customerRef, productRef, meterName2, enabled, fetchLimits]
7794
7800
  );
7795
7801
  return {
7796
7802
  remaining: data?.remaining ?? null,
@@ -7809,7 +7815,9 @@ function useLimits(options) {
7809
7815
  function deriveUsage(purchase, limits) {
7810
7816
  if (!purchase) return null;
7811
7817
  const usage = purchase.usage;
7812
- if (purchase.planSnapshot?.isMetered !== true && !usage) return null;
7818
+ if (!(0, import_core7.countsUsage)(purchase.planSnapshot) && purchase.planSnapshot?.isMetered !== true && !usage) {
7819
+ return null;
7820
+ }
7813
7821
  const used = typeof usage?.used === "number" ? usage.used : 0;
7814
7822
  const hasFiniteCap = limits.unlimited === false && limits.remaining !== null;
7815
7823
  const remaining = hasFiniteCap ? limits.remaining : null;
@@ -7832,18 +7840,18 @@ function useUsage() {
7832
7840
  const [override, setOverride] = (0, import_react42.useState)(null);
7833
7841
  const [error, setError] = (0, import_react42.useState)(null);
7834
7842
  const [transportLoading, setTransportLoading] = (0, import_react42.useState)(false);
7835
- const isMetered = activePurchase?.planSnapshot?.isMetered === true;
7843
+ const usageCounted = (0, import_core7.countsUsage)(activePurchase?.planSnapshot) || activePurchase?.planSnapshot?.isMetered === true;
7836
7844
  const {
7837
7845
  remaining: limitRemaining,
7838
7846
  unlimited,
7839
- meterName
7847
+ meterName: meterName2
7840
7848
  } = useLimits({
7841
7849
  productRef: activePurchase?.productRef,
7842
- enabled: isMetered
7850
+ enabled: usageCounted
7843
7851
  });
7844
7852
  const derived = (0, import_react42.useMemo)(
7845
- () => deriveUsage(activePurchase ?? null, { remaining: limitRemaining, unlimited, meterName }),
7846
- [activePurchase, limitRemaining, unlimited, meterName]
7853
+ () => deriveUsage(activePurchase ?? null, { remaining: limitRemaining, unlimited, meterName: meterName2 }),
7854
+ [activePurchase, limitRemaining, unlimited, meterName2]
7847
7855
  );
7848
7856
  const activePurchaseRef = activePurchase?.reference ?? null;
7849
7857
  (0, import_react42.useEffect)(() => {
@@ -8100,7 +8108,7 @@ function PlanTypeLine({
8100
8108
  className
8101
8109
  }) {
8102
8110
  const copy = useCopy();
8103
- const isMetered = purchase.planSnapshot?.isMetered === true;
8111
+ const isMetered = (0, import_core8.countsUsage)(purchase.planSnapshot) || purchase.planSnapshot?.isMetered === true;
8104
8112
  if (purchase.isRecurring) {
8105
8113
  const date2 = formatDate(purchase.nextBillingDate);
8106
8114
  if (!date2) return null;
@@ -8160,7 +8168,7 @@ var CurrentPlanCard = ({
8160
8168
  const { formatDate, shouldShowCancelledNotice } = usePurchaseStatus();
8161
8169
  const { paymentMethod } = usePaymentMethod();
8162
8170
  if (!activePurchase) return null;
8163
- const isUsageBased = activePurchase.planSnapshot?.isMetered === true;
8171
+ const isUsageBased = (0, import_core8.countsUsage)(activePurchase.planSnapshot) || activePurchase.planSnapshot?.isMetered === true;
8164
8172
  const planType = activePurchase.isRecurring ? "recurring" : isUsageBased ? "usage-based" : "one-time";
8165
8173
  const amount = activePurchase.originalAmount ?? activePurchase.amount ?? 0;
8166
8174
  const currency = activePurchase.currency ?? "usd";
@@ -8519,7 +8527,7 @@ function stripSetupIntentParams() {
8519
8527
  }
8520
8528
 
8521
8529
  // src/utils/credit-estimation.ts
8522
- var import_core6 = require("@solvapay/core");
8530
+ var import_core9 = require("@solvapay/core");
8523
8531
  function estimateCredits(resolvedAmountMajor, currency, creditsPerMinorUnit, displayExchangeRate) {
8524
8532
  if (resolvedAmountMajor == null || resolvedAmountMajor <= 0) return null;
8525
8533
  if (creditsPerMinorUnit == null || creditsPerMinorUnit <= 0) return null;
@@ -8531,7 +8539,7 @@ function estimateCurrencyMajorFromCredits(credits, currency, creditsPerMinorUnit
8531
8539
  if (credits == null || credits <= 0) return null;
8532
8540
  if (creditsPerMinorUnit == null || creditsPerMinorUnit <= 0) return null;
8533
8541
  const rate = displayExchangeRate ?? 1;
8534
- const displayMinor = (0, import_core6.creditsToDisplayMinorUnits)({
8542
+ const displayMinor = (0, import_core9.creditsToDisplayMinorUnits)({
8535
8543
  credits,
8536
8544
  creditsPerMinorUnit,
8537
8545
  displayExchangeRate: rate,
@@ -10087,10 +10095,12 @@ function useAutoActivateFreePlan(options) {
10087
10095
  });
10088
10096
  const { plans } = usePlans({ productRef });
10089
10097
  const { activate, error: activationError } = useActivation();
10090
- const freePlan = (0, import_react46.useMemo)(
10091
- () => plans.find((p) => p.requiresPayment === false && (p.freeUnits ?? 0) > 0),
10092
- [plans]
10093
- );
10098
+ const freePlan = (0, import_react46.useMemo)(() => {
10099
+ const free = plans.filter((p) => p.requiresPayment === false);
10100
+ if (free.length === 0) return void 0;
10101
+ if (free.length === 1) return free[0];
10102
+ return free.find((p) => p.options?.some((option) => option.kind === "autoAssigned")) ?? free.find((p) => p.default === true) ?? free[0];
10103
+ }, [plans]);
10094
10104
  const attemptedKeyRef = (0, import_react46.useRef)(null);
10095
10105
  const [activated, setActivated] = (0, import_react46.useState)(false);
10096
10106
  const onActivatedRef = (0, import_react46.useRef)(onActivated);
@@ -10164,7 +10174,7 @@ function usePaywallResolver(content) {
10164
10174
  var import_react48 = require("react");
10165
10175
 
10166
10176
  // src/primitives/checkout/shared.ts
10167
- var import_core7 = require("@solvapay/core");
10177
+ var import_core10 = require("@solvapay/core");
10168
10178
  function isPayg(plan) {
10169
10179
  return isPaygPlan(plan ?? null);
10170
10180
  }
@@ -10205,25 +10215,25 @@ function formatContinueLabel(plan, locale, pricingOption) {
10205
10215
  return `Continue with ${plan.name ?? "Plan"} \u2014 ${priceLabel}${cycle}`;
10206
10216
  }
10207
10217
  function planBillingInterval(plan) {
10208
- return (0, import_core7.billingCycle)(plan)?.interval ?? null;
10218
+ return (0, import_core10.billingCycle)(plan)?.interval ?? null;
10209
10219
  }
10210
10220
  function formatPaygRate(plan, locale, balance) {
10211
- const credits = (0, import_core7.creditsPerUnitFromBalance)(plan, balance);
10221
+ const credits = (0, import_core10.creditsPerUnitFromBalance)(plan, balance);
10212
10222
  if (credits != null) {
10213
10223
  return `${credits.toLocaleString(locale)} ${credits === 1 ? "credit" : "credits"} / call`;
10214
10224
  }
10215
- const charge = (0, import_core7.perUnitCharge)(plan);
10225
+ const charge = (0, import_core10.perUnitCharge)(plan);
10216
10226
  if (charge && charge.amountMinor > 0) {
10217
10227
  return `${formatPrice(charge.amountMinor, charge.currency.toUpperCase(), { locale })} / call`;
10218
10228
  }
10219
10229
  return null;
10220
10230
  }
10221
10231
  function inferIncludedUnits(plan) {
10222
- const cap = (0, import_core7.includedUnits)(plan);
10232
+ const cap = (0, import_core10.includedUnits)(plan);
10223
10233
  return cap != null && cap > 0 ? cap : null;
10224
10234
  }
10225
10235
  function planMeterName(plan) {
10226
- return (0, import_core7.perUnitCharge)(plan)?.meter ?? null;
10236
+ return (0, import_core10.meterName)(plan);
10227
10237
  }
10228
10238
  function shortCycle(cycle) {
10229
10239
  if (!cycle) return "mo";
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { S as SolvaPayProviderProps, P as PaymentFormProps, a as PrefillCustomer, T as TopupFormProps, C as CheckoutResult, b as Plan, A as ActivationResult, c as SaveAutoRechargeResponse, d as PurchaseStatus, e as SolvaPayContextValue, U as UsePlansOptions, f as UsePlansReturn, g as UsePlanOptions, h as UsePlanReturn, i as UseProductReturn, j as UseMerchantReturn, k as SolvaPayCopy, l as PurchaseStatusReturn, m as CancelResult, R as ReactivateResult, n as ActivatePlanResult, o as UseTopupOptions, p as UseTopupReturn, B as BalanceStatus, q as UseTopupAmountSelectorOptions, r as UseTopupAmountSelectorReturn, s as UsePaymentMethodReturn, t as AutoRechargeConfig, u as SaveAutoRechargeInput, v as SolvaPayTransport, w as PartialSolvaPayCopy, x as PurchaseInfo, y as Product, z as SolvaPayConfig } from './shared-A_Sg-fJE.cjs';
3
3
  export { D as BootstrapPlanLike, E as CheckoutStep, F as CustomerPurchaseData, M as MandateContext, G as MandateTemplate, H as Merchant, I as PaymentError, J as PaymentIntentResult, K as PaymentMethodInfo, L as PaymentResult, N as PurchaseStatusValue, O as SuccessMeta, Q as TopupPaymentResult, V as TransportBalanceResult, W as TransportCheckoutSessionResult, X as TransportCustomerSessionResult, Y as TransportLimitsResult, Z as UnsupportedTransportMethodError } from './shared-A_Sg-fJE.cjs';
4
- import { P as PaymentFormSummary, a as PaymentFormCustomerFields, b as PaymentFormPaymentElement, c as PaymentFormCardElement, d as PaymentFormMandateText, e as PaymentFormTermsCheckbox, f as PaymentFormSubmitButton, g as PaymentFormLoading, h as PaymentFormError, i as PaymentFormLegalFooter, j as PaymentFormBusinessDetails, k as PaymentFormTaxSummary, A as AutoRechargeInputPayload, l as PaywallStructuredContent, C as CheckoutVariant } from './index-D6KKDguz.cjs';
5
- export { m as ActivationFlowStep, B as BalanceBadge, n as CancelPlanButton, o as CheckoutErrorPhase, p as CheckoutStatus, q as CheckoutSteps, r as CheckoutSummary, s as CheckoutSummaryProps, M as MandateText, t as MandateTextProps, u as PlanBadge, v as ProductBadge, w as PurchaseGate, U as UsageSnapshot, x as UseCheckoutFlowOptions, y as UseCheckoutFlowReturn, z as UseUsageReturn, D as configToAutoRechargeInput, E as deriveVariant, F as useCheckoutFlow, G as useCheckoutStepsContext, H as useUsage } from './index-D6KKDguz.cjs';
4
+ import { P as PaymentFormSummary, a as PaymentFormCustomerFields, b as PaymentFormPaymentElement, c as PaymentFormCardElement, d as PaymentFormMandateText, e as PaymentFormTermsCheckbox, f as PaymentFormSubmitButton, g as PaymentFormLoading, h as PaymentFormError, i as PaymentFormLegalFooter, j as PaymentFormBusinessDetails, k as PaymentFormTaxSummary, A as AutoRechargeInputPayload, l as PaywallStructuredContent, C as CheckoutVariant } from './index-BuVbt1j1.cjs';
5
+ export { m as ActivationFlowStep, B as BalanceBadge, n as CancelPlanButton, o as CheckoutErrorPhase, p as CheckoutStatus, q as CheckoutSteps, r as CheckoutSummary, s as CheckoutSummaryProps, M as MandateText, t as MandateTextProps, u as PlanBadge, v as ProductBadge, w as PurchaseGate, U as UsageSnapshot, x as UseCheckoutFlowOptions, y as UseCheckoutFlowReturn, z as UseUsageReturn, D as configToAutoRechargeInput, E as deriveVariant, F as useCheckoutFlow, G as useCheckoutStepsContext, H as useUsage } from './index-BuVbt1j1.cjs';
6
6
  import { PaymentIntent, Stripe, StripeElements } from '@stripe/stripe-js';
7
7
  import { BusinessDetailsInput, TaxBreakdown } from '@solvapay/core';
8
8
  export { AuthAdapter, createAnonymousAuthAdapter, defaultAuthAdapter, getOrCreateAnonymousCustomerRef, resetAnonymousCustomerRef } from './adapters/auth.cjs';
@@ -290,26 +290,6 @@ interface CreditGateProps {
290
290
  }
291
291
  declare const CreditGate: React.FC<CreditGateProps>;
292
292
 
293
- /**
294
- * `<CurrentPlanCard>` — summary card for the customer's active purchase.
295
- *
296
- * Pure projection of existing provider state (`usePurchase`,
297
- * `usePurchaseStatus`, `useBalance`, `usePaymentMethod`) plus Phase 1
298
- * action components (`<CancelPlanButton>`, Phase 2's
299
- * `<UpdatePaymentMethodButton>`). No Stripe Elements dependency, so the
300
- * default tree renders identically inside an MCP host sandbox and a
301
- * standalone HTTP app.
302
- *
303
- * Returns `null` when `usePurchase()` reports no active purchase, so
304
- * integrators can drop it into account pages without wrapping in
305
- * `{hasPaidPurchase && ...}`.
306
- *
307
- * Plan-type-aware lines:
308
- * - `recurring` — "Next billing: {date}"
309
- * - `one-time` — "Expires {date}" or "Valid indefinitely"
310
- * - `usage-based` — `<BalanceBadge>` line; no date
311
- */
312
-
313
293
  interface CurrentPlanCardClassNames {
314
294
  root?: string;
315
295
  heading?: string;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { S as SolvaPayProviderProps, P as PaymentFormProps, a as PrefillCustomer, T as TopupFormProps, C as CheckoutResult, b as Plan, A as ActivationResult, c as SaveAutoRechargeResponse, d as PurchaseStatus, e as SolvaPayContextValue, U as UsePlansOptions, f as UsePlansReturn, g as UsePlanOptions, h as UsePlanReturn, i as UseProductReturn, j as UseMerchantReturn, k as SolvaPayCopy, l as PurchaseStatusReturn, m as CancelResult, R as ReactivateResult, n as ActivatePlanResult, o as UseTopupOptions, p as UseTopupReturn, B as BalanceStatus, q as UseTopupAmountSelectorOptions, r as UseTopupAmountSelectorReturn, s as UsePaymentMethodReturn, t as AutoRechargeConfig, u as SaveAutoRechargeInput, v as SolvaPayTransport, w as PartialSolvaPayCopy, x as PurchaseInfo, y as Product, z as SolvaPayConfig } from './shared-CAxz5wWs.js';
3
3
  export { D as BootstrapPlanLike, E as CheckoutStep, F as CustomerPurchaseData, M as MandateContext, G as MandateTemplate, H as Merchant, I as PaymentError, J as PaymentIntentResult, K as PaymentMethodInfo, L as PaymentResult, N as PurchaseStatusValue, O as SuccessMeta, Q as TopupPaymentResult, V as TransportBalanceResult, W as TransportCheckoutSessionResult, X as TransportCustomerSessionResult, Y as TransportLimitsResult, Z as UnsupportedTransportMethodError } from './shared-CAxz5wWs.js';
4
- import { P as PaymentFormSummary, a as PaymentFormCustomerFields, b as PaymentFormPaymentElement, c as PaymentFormCardElement, d as PaymentFormMandateText, e as PaymentFormTermsCheckbox, f as PaymentFormSubmitButton, g as PaymentFormLoading, h as PaymentFormError, i as PaymentFormLegalFooter, j as PaymentFormBusinessDetails, k as PaymentFormTaxSummary, A as AutoRechargeInputPayload, l as PaywallStructuredContent, C as CheckoutVariant } from './index-DomaahLG.js';
5
- export { m as ActivationFlowStep, B as BalanceBadge, n as CancelPlanButton, o as CheckoutErrorPhase, p as CheckoutStatus, q as CheckoutSteps, r as CheckoutSummary, s as CheckoutSummaryProps, M as MandateText, t as MandateTextProps, u as PlanBadge, v as ProductBadge, w as PurchaseGate, U as UsageSnapshot, x as UseCheckoutFlowOptions, y as UseCheckoutFlowReturn, z as UseUsageReturn, D as configToAutoRechargeInput, E as deriveVariant, F as useCheckoutFlow, G as useCheckoutStepsContext, H as useUsage } from './index-DomaahLG.js';
4
+ import { P as PaymentFormSummary, a as PaymentFormCustomerFields, b as PaymentFormPaymentElement, c as PaymentFormCardElement, d as PaymentFormMandateText, e as PaymentFormTermsCheckbox, f as PaymentFormSubmitButton, g as PaymentFormLoading, h as PaymentFormError, i as PaymentFormLegalFooter, j as PaymentFormBusinessDetails, k as PaymentFormTaxSummary, A as AutoRechargeInputPayload, l as PaywallStructuredContent, C as CheckoutVariant } from './index-DHK9Op-i.js';
5
+ export { m as ActivationFlowStep, B as BalanceBadge, n as CancelPlanButton, o as CheckoutErrorPhase, p as CheckoutStatus, q as CheckoutSteps, r as CheckoutSummary, s as CheckoutSummaryProps, M as MandateText, t as MandateTextProps, u as PlanBadge, v as ProductBadge, w as PurchaseGate, U as UsageSnapshot, x as UseCheckoutFlowOptions, y as UseCheckoutFlowReturn, z as UseUsageReturn, D as configToAutoRechargeInput, E as deriveVariant, F as useCheckoutFlow, G as useCheckoutStepsContext, H as useUsage } from './index-DHK9Op-i.js';
6
6
  import { PaymentIntent, Stripe, StripeElements } from '@stripe/stripe-js';
7
7
  import { BusinessDetailsInput, TaxBreakdown } from '@solvapay/core';
8
8
  export { AuthAdapter, createAnonymousAuthAdapter, defaultAuthAdapter, getOrCreateAnonymousCustomerRef, resetAnonymousCustomerRef } from './adapters/auth.js';
@@ -290,26 +290,6 @@ interface CreditGateProps {
290
290
  }
291
291
  declare const CreditGate: React.FC<CreditGateProps>;
292
292
 
293
- /**
294
- * `<CurrentPlanCard>` — summary card for the customer's active purchase.
295
- *
296
- * Pure projection of existing provider state (`usePurchase`,
297
- * `usePurchaseStatus`, `useBalance`, `usePaymentMethod`) plus Phase 1
298
- * action components (`<CancelPlanButton>`, Phase 2's
299
- * `<UpdatePaymentMethodButton>`). No Stripe Elements dependency, so the
300
- * default tree renders identically inside an MCP host sandbox and a
301
- * standalone HTTP app.
302
- *
303
- * Returns `null` when `usePurchase()` reports no active purchase, so
304
- * integrators can drop it into account pages without wrapping in
305
- * `{hasPaidPurchase && ...}`.
306
- *
307
- * Plan-type-aware lines:
308
- * - `recurring` — "Next billing: {date}"
309
- * - `one-time` — "Expires {date}" or "Valid indefinitely"
310
- * - `usage-based` — `<BalanceBadge>` line; no date
311
- */
312
-
313
293
  interface CurrentPlanCardClassNames {
314
294
  root?: string;
315
295
  heading?: string;
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  LaunchCustomerPortalButton,
8
8
  UpdatePaymentMethodButton,
9
9
  usePaymentMethod
10
- } from "./chunk-D6TOFFNL.js";
10
+ } from "./chunk-VUGPLAS4.js";
11
11
  import {
12
12
  ActivationFlow,
13
13
  AutoRecharge,
@@ -23,7 +23,7 @@ import {
23
23
  useCheckoutSteps,
24
24
  useCreditGate,
25
25
  usePaywallResolver
26
- } from "./chunk-PKZJIV3G.js";
26
+ } from "./chunk-J4VSGFAF.js";
27
27
  import {
28
28
  AmountPicker,
29
29
  BalanceBadge,
@@ -97,7 +97,7 @@ import {
97
97
  useTopupAmountSelector,
98
98
  useTransport,
99
99
  useUsage
100
- } from "./chunk-6ABOJOEV.js";
100
+ } from "./chunk-JN5BJ5MI.js";
101
101
  import {
102
102
  createAnonymousAuthAdapter,
103
103
  defaultAuthAdapter,
@@ -809,10 +809,12 @@ function useAutoActivateFreePlan(options) {
809
809
  });
810
810
  const { plans } = usePlans({ productRef });
811
811
  const { activate, error: activationError } = useActivation();
812
- const freePlan = useMemo2(
813
- () => plans.find((p) => p.requiresPayment === false && (p.freeUnits ?? 0) > 0),
814
- [plans]
815
- );
812
+ const freePlan = useMemo2(() => {
813
+ const free = plans.filter((p) => p.requiresPayment === false);
814
+ if (free.length === 0) return void 0;
815
+ if (free.length === 1) return free[0];
816
+ return free.find((p) => p.options?.some((option) => option.kind === "autoAssigned")) ?? free.find((p) => p.default === true) ?? free[0];
817
+ }, [plans]);
816
818
  const attemptedKeyRef = useRef2(null);
817
819
  const [activated, setActivated] = useState3(false);
818
820
  const onActivatedRef = useRef2(onActivated);