@solvapay/react 1.0.0-preview.20 → 1.0.0-preview.21

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/README.md CHANGED
@@ -85,10 +85,10 @@ import { SolvaPayProvider } from '@solvapay/react'
85
85
  export default function App() {
86
86
  return (
87
87
  <SolvaPayProvider
88
- createPayment={async ({ planRef, agentRef }) => {
88
+ createPayment={async ({ planRef, productRef }) => {
89
89
  const res = await fetch('/api/custom/payment', {
90
90
  method: 'POST',
91
- body: JSON.stringify({ planRef, agentRef }),
91
+ body: JSON.stringify({ planRef, productRef }),
92
92
  })
93
93
  if (!res.ok) throw new Error('Failed to create payment')
94
94
  return res.json()
@@ -124,9 +124,9 @@ Headless context provider that manages purchase state, payment methods, and cust
124
124
  - `config?: SolvaPayConfig` - Configuration object (optional)
125
125
  - `config.api?` - Custom API route paths
126
126
  - `config.auth?` - Auth adapter configuration
127
- - `createPayment?: (params: { planRef: string; agentRef?: string }) => Promise<PaymentIntentResult>` - Custom payment creation function (optional, overrides config)
127
+ - `createPayment?: (params: { planRef: string; productRef?: string }) => Promise<PaymentIntentResult>` - Custom payment creation function (optional, overrides config)
128
128
  - `checkPurchase?: (customerRef: string) => Promise<CustomerPurchaseData>` - Custom purchase check function (optional, overrides config)
129
- - `processPayment?: (params: { paymentIntentId: string; agentRef: string; planRef?: string }) => Promise<ProcessPaymentResult>` - Custom payment processing function (optional)
129
+ - `processPayment?: (params: { paymentIntentId: string; productRef: string; planRef?: string }) => Promise<ProcessPaymentResult>` - Custom payment processing function (optional)
130
130
  - `children: React.ReactNode` - Child components
131
131
 
132
132
  **Config Options:**
@@ -146,29 +146,29 @@ interface SolvaPayConfig {
146
146
  }
147
147
  ```
148
148
 
149
- ### PlanSelector
149
+ ### PricingSelector
150
150
 
151
- Component for selecting and displaying available plans.
151
+ Component for selecting and displaying available pricing options.
152
152
 
153
153
  **Props:**
154
154
 
155
- - `agentRef?: string` - Agent reference to filter plans
156
- - `fetcher?: (agentRef: string) => Promise<Plan[]>` - Custom plan fetcher function
157
- - `onPlanSelect?: (plan: Plan) => void` - Callback when plan is selected
158
- - `renderPlan?: (plan: Plan) => React.ReactNode` - Custom plan renderer
155
+ - `productRef?: string` - Product reference to filter pricing options
156
+ - `fetcher?: (productRef: string) => Promise<Plan[]>` - Custom fetcher function
157
+ - `onPlanSelect?: (plan: Plan) => void` - Callback when option is selected
158
+ - `renderPlan?: (plan: Plan) => React.ReactNode` - Custom option renderer
159
159
  - `className?: string` - Container className
160
160
 
161
161
  **Example:**
162
162
 
163
163
  ```tsx
164
- import { PlanSelector, usePlans } from '@solvapay/react'
164
+ import { PricingSelector, usePlans } from '@solvapay/react'
165
165
 
166
- function PlansPage() {
167
- const { plans, loading } = usePlans({ agentRef: 'my-agent' })
166
+ function PricingPage() {
167
+ const { plans, loading } = usePlans({ productRef: 'my-product' })
168
168
 
169
169
  return (
170
170
  <div>
171
- {loading ? 'Loading...' : plans.map(plan => <div key={plan.reference}>{plan.name}</div>)}
171
+ {loading ? 'Loading...' : plans.map(plan => <div key={plan.reference}>{plan.price}/{plan.interval}</div>)}
172
172
  </div>
173
173
  )
174
174
  }
@@ -181,7 +181,7 @@ Payment form component using Stripe PaymentElement. Automatically handles Stripe
181
181
  **Props:**
182
182
 
183
183
  - `planRef: string` - Plan reference for the payment
184
- - `agentRef?: string` - Optional agent reference
184
+ - `productRef?: string` - Optional product reference
185
185
  - `onSuccess?: (paymentIntent: PaymentIntent) => void` - Callback on successful payment
186
186
  - `onError?: (error: Error) => void` - Callback on payment error
187
187
  - `returnUrl?: string` - Return URL after payment
@@ -199,16 +199,16 @@ function CheckoutPage() {
199
199
  return (
200
200
  <PaymentForm
201
201
  planRef="pln_YOUR_PLAN"
202
- agentRef="agt_YOUR_AGENT"
202
+ productRef="prd_YOUR_PRODUCT"
203
203
  onSuccess={() => console.log('Payment successful!')}
204
204
  />
205
205
  )
206
206
  }
207
207
  ```
208
208
 
209
- ### PlanBadge
209
+ ### ProductBadge
210
210
 
211
- Displays current purchase plan with render props or className pattern.
211
+ Displays current product subscription with render props or className pattern.
212
212
 
213
213
  **Props:**
214
214
 
@@ -219,7 +219,7 @@ Displays current purchase plan with render props or className pattern.
219
219
  **Example:**
220
220
 
221
221
  ```tsx
222
- <PlanBadge className="badge badge-primary" />
222
+ <ProductBadge className="badge badge-primary" />
223
223
  ```
224
224
 
225
225
  ### PurchaseGate
@@ -228,13 +228,13 @@ Controls access to content based on purchase status.
228
228
 
229
229
  **Props:**
230
230
 
231
- - `requirePlan?: string` - Optional plan name to require
231
+ - `requireProduct?: string` - Optional product name to check for an active purchase
232
232
  - `children: (props) => React.ReactNode` - Render prop function
233
233
 
234
234
  **Example:**
235
235
 
236
236
  ```tsx
237
- <PurchaseGate requirePlan="Pro Plan">
237
+ <PurchaseGate requireProduct="Pro Plan">
238
238
  {({ hasAccess, loading, purchases }) => {
239
239
  if (loading) return <Loading />
240
240
  if (!hasAccess) return <Paywall />
@@ -270,7 +270,7 @@ const {
270
270
  error, // Error object if fetch failed
271
271
  refetch, // Function to refetch plans
272
272
  } = usePlans({
273
- agentRef: 'my-agent', // Optional agent reference
273
+ productRef: 'my-product', // Optional product reference
274
274
  fetcher: customFetcher, // Optional custom fetcher function
275
275
  })
276
276
  ```
package/dist/index.cjs CHANGED
@@ -23,6 +23,8 @@ __export(index_exports, {
23
23
  PaymentForm: () => PaymentForm,
24
24
  PlanBadge: () => PlanBadge,
25
25
  PlanSelector: () => PlanSelector,
26
+ PricingSelector: () => PricingSelector,
27
+ ProductBadge: () => ProductBadge,
26
28
  PurchaseGate: () => PurchaseGate,
27
29
  SolvaPayProvider: () => SolvaPayProvider,
28
30
  Spinner: () => Spinner,
@@ -454,9 +456,14 @@ var SolvaPayProvider = ({
454
456
  email: purchaseData.email,
455
457
  name: purchaseData.name,
456
458
  purchases: purchaseData.purchases,
457
- hasPlan: (planName) => {
459
+ hasProduct: (productName) => {
458
460
  return purchaseData.purchases.some(
459
- (p) => p.planName.toLowerCase() === planName.toLowerCase() && p.status === "active"
461
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
462
+ );
463
+ },
464
+ hasPlan: (productName) => {
465
+ return purchaseData.purchases.some(
466
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
460
467
  );
461
468
  },
462
469
  activePurchase,
@@ -977,10 +984,10 @@ var PaymentForm = ({
977
984
  ) });
978
985
  };
979
986
 
980
- // src/components/PlanBadge.tsx
987
+ // src/components/ProductBadge.tsx
981
988
  var import_react6 = require("react");
982
989
  var import_jsx_runtime5 = require("react/jsx-runtime");
983
- var PlanBadge = ({
990
+ var ProductBadge = ({
984
991
  children,
985
992
  as: Component = "div",
986
993
  className
@@ -991,7 +998,7 @@ var PlanBadge = ({
991
998
  const lastPlanRef = (0, import_react6.useRef)(null);
992
999
  const lastLoadingRef = (0, import_react6.useRef)(true);
993
1000
  const previousPurchasesRef = (0, import_react6.useRef)(purchases);
994
- const currentPlanName = activePurchase?.planName || null;
1001
+ const currentPlanName = activePurchase?.productName || null;
995
1002
  const effectivePlanName = currentPlanName;
996
1003
  (0, import_react6.useEffect)(() => {
997
1004
  if (!loading && !hasLoadedOnce) {
@@ -1044,17 +1051,19 @@ var PlanBadge = ({
1044
1051
  role: "status",
1045
1052
  "aria-live": "polite",
1046
1053
  "aria-busy": loading,
1047
- "aria-label": `Current plan: ${planToDisplay}`,
1054
+ "aria-label": `Current product: ${planToDisplay}`,
1048
1055
  children: planToDisplay
1049
1056
  }
1050
1057
  );
1051
1058
  };
1059
+ var PlanBadge = ProductBadge;
1052
1060
 
1053
1061
  // src/components/PurchaseGate.tsx
1054
1062
  var import_jsx_runtime6 = require("react/jsx-runtime");
1055
1063
  var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
1056
- const { purchases, loading, hasPlan } = usePurchase();
1057
- const hasAccess = requirePlan ? hasPlan(requirePlan) : requireProduct ? purchases.some((p) => p.status === "active" && (p.productName === requireProduct || p.productReference === requireProduct)) : purchases.some((p) => p.status === "active");
1064
+ const { purchases, loading, hasProduct } = usePurchase();
1065
+ const productToCheck = requireProduct || requirePlan;
1066
+ const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
1058
1067
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children({
1059
1068
  hasAccess,
1060
1069
  purchases,
@@ -1062,7 +1071,7 @@ var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
1062
1071
  }) });
1063
1072
  };
1064
1073
 
1065
- // src/components/PlanSelector.tsx
1074
+ // src/components/PricingSelector.tsx
1066
1075
  var import_react8 = require("react");
1067
1076
 
1068
1077
  // src/hooks/usePlans.ts
@@ -1197,9 +1206,9 @@ function usePlans(options) {
1197
1206
  };
1198
1207
  }
1199
1208
 
1200
- // src/components/PlanSelector.tsx
1209
+ // src/components/PricingSelector.tsx
1201
1210
  var import_jsx_runtime7 = require("react/jsx-runtime");
1202
- var PlanSelector = ({
1211
+ var PricingSelector = ({
1203
1212
  productRef,
1204
1213
  fetcher,
1205
1214
  filter,
@@ -1217,8 +1226,8 @@ var PlanSelector = ({
1217
1226
  });
1218
1227
  const { plans } = plansHook;
1219
1228
  const isPaidPlan = (0, import_react8.useCallback)(
1220
- (planName) => {
1221
- const plan = plans.find((p) => p.name === planName);
1229
+ (planRef) => {
1230
+ const plan = plans.find((p) => p.reference === planRef);
1222
1231
  return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
1223
1232
  },
1224
1233
  [plans]
@@ -1230,8 +1239,8 @@ var PlanSelector = ({
1230
1239
  )[0] || null;
1231
1240
  }, [purchases]);
1232
1241
  const isCurrentPlan = (0, import_react8.useCallback)(
1233
- (planName) => {
1234
- return activePurchase?.planName === planName;
1242
+ (planRef) => {
1243
+ return activePurchase?.planSnapshot?.reference === planRef;
1235
1244
  },
1236
1245
  [activePurchase]
1237
1246
  );
@@ -1242,6 +1251,7 @@ var PlanSelector = ({
1242
1251
  isCurrentPlan
1243
1252
  }) });
1244
1253
  };
1254
+ var PlanSelector = PricingSelector;
1245
1255
 
1246
1256
  // src/hooks/usePurchaseStatus.ts
1247
1257
  var import_react9 = require("react");
@@ -1291,6 +1301,8 @@ function usePurchaseStatus() {
1291
1301
  PaymentForm,
1292
1302
  PlanBadge,
1293
1303
  PlanSelector,
1304
+ PricingSelector,
1305
+ ProductBadge,
1294
1306
  PurchaseGate,
1295
1307
  SolvaPayProvider,
1296
1308
  Spinner,
package/dist/index.d.cts CHANGED
@@ -11,8 +11,7 @@ export { defaultAuthAdapter } from './adapters/auth.cjs';
11
11
  interface PurchaseInfo {
12
12
  reference: string;
13
13
  productName: string;
14
- productReference: string;
15
- planName: string;
14
+ productReference?: string;
16
15
  status: string;
17
16
  startDate: string;
18
17
  endDate?: string;
@@ -25,11 +24,22 @@ interface PurchaseInfo {
25
24
  nextBillingDate?: string;
26
25
  billingCycle?: string;
27
26
  transactionId?: string;
27
+ planSnapshot?: {
28
+ reference?: string;
29
+ meterId?: string;
30
+ limit?: number;
31
+ freeUnits?: number;
32
+ pricePerUnit?: number;
33
+ planType?: string;
34
+ billingCycle?: string | null;
35
+ features?: Record<string, unknown> | null;
36
+ };
28
37
  usage?: {
29
38
  used: number;
30
- quota: number | null;
31
- unit: string;
32
- remaining: number | null;
39
+ overageUnits?: number;
40
+ overageCost?: number;
41
+ periodStart?: string;
42
+ periodEnd?: string;
33
43
  };
34
44
  }
35
45
  interface CustomerPurchaseData {
@@ -50,7 +60,9 @@ interface PurchaseStatus {
50
60
  email?: string;
51
61
  name?: string;
52
62
  purchases: PurchaseInfo[];
53
- hasPlan: (planName: string) => boolean;
63
+ hasProduct: (productName: string) => boolean;
64
+ /** @deprecated Use hasProduct instead */
65
+ hasPlan: (productName: string) => boolean;
54
66
  /**
55
67
  * Primary active purchase (paid or free) - most recent purchase with status === 'active'
56
68
  * Backend keeps purchases as 'active' until expiration, even when cancelled.
@@ -174,7 +186,7 @@ interface SolvaPayProviderProps {
174
186
  }) => Promise<ProcessPaymentResult>;
175
187
  children: React.ReactNode;
176
188
  }
177
- interface PlanBadgeProps {
189
+ interface ProductBadgeProps {
178
190
  children?: (props: {
179
191
  purchases: PurchaseInfo[];
180
192
  loading: boolean;
@@ -186,7 +198,10 @@ interface PlanBadgeProps {
186
198
  purchases: PurchaseInfo[];
187
199
  }) => string);
188
200
  }
201
+ /** @deprecated Use ProductBadgeProps instead */
202
+ type PlanBadgeProps = ProductBadgeProps;
189
203
  interface PurchaseGateProps {
204
+ /** @deprecated Use requireProduct instead */
190
205
  requirePlan?: string;
191
206
  requireProduct?: string;
192
207
  children: (props: {
@@ -207,8 +222,6 @@ interface PaymentError extends Error {
207
222
  */
208
223
  interface Plan {
209
224
  reference: string;
210
- name: string;
211
- description?: string;
212
225
  price?: number;
213
226
  currency?: string;
214
227
  interval?: string;
@@ -255,9 +268,9 @@ interface UsePlansReturn {
255
268
  refetch: () => Promise<void>;
256
269
  }
257
270
  /**
258
- * Props for headless PlanSelector component
271
+ * Props for headless PricingSelector component
259
272
  */
260
- interface PlanSelectorProps {
273
+ interface PricingSelectorProps {
261
274
  /**
262
275
  * Product reference to fetch plans for
263
276
  */
@@ -283,10 +296,12 @@ interface PlanSelectorProps {
283
296
  */
284
297
  children: (props: UsePlansReturn & {
285
298
  purchases: PurchaseInfo[];
286
- isPaidPlan: (planName: string) => boolean;
287
- isCurrentPlan: (planName: string) => boolean;
299
+ isPaidPlan: (planRef: string) => boolean;
300
+ isCurrentPlan: (planRef: string) => boolean;
288
301
  }) => React.ReactNode;
289
302
  }
303
+ /** @deprecated Use PricingSelectorProps instead */
304
+ type PlanSelectorProps = PricingSelectorProps;
290
305
  /**
291
306
  * Return type for usePurchaseStatus hook
292
307
  *
@@ -492,33 +507,35 @@ declare const SolvaPayProvider: React$1.FC<SolvaPayProviderProps>;
492
507
  declare const PaymentForm: React$1.FC<PaymentFormProps>;
493
508
 
494
509
  /**
495
- * Headless Plan Badge Component
510
+ * Headless Product Badge Component
496
511
  *
497
512
  * Displays purchase status with complete styling control.
498
513
  * Supports render props, custom components, or className patterns.
499
514
  *
500
515
  * Prevents flickering by hiding the badge during initial load and when no purchase exists.
501
516
  * Shows the badge once loading completes AND an active purchase exists (paid or free).
502
- * Badge only updates when the plan name actually changes (prevents unnecessary re-renders).
517
+ * Badge only updates when the product name actually changes (prevents unnecessary re-renders).
503
518
  *
504
- * Displays the primary active purchase (paid or free) to show current plan status.
519
+ * Displays the primary active purchase (paid or free) to show current product status.
505
520
  *
506
521
  * @example
507
522
  * ```tsx
508
523
  * // Render prop pattern
509
- * <PlanBadge>
524
+ * <ProductBadge>
510
525
  * {({ purchases, loading, displayPlan, shouldShow }) => (
511
526
  * shouldShow ? (
512
527
  * <div>{displayPlan}</div>
513
528
  * ) : null
514
529
  * )}
515
- * </PlanBadge>
530
+ * </ProductBadge>
516
531
  *
517
532
  * //ClassName pattern
518
- * <PlanBadge className="badge badge-primary" />
533
+ * <ProductBadge className="badge badge-primary" />
519
534
  * ```
520
535
  */
521
- declare const PlanBadge: React$1.FC<PlanBadgeProps>;
536
+ declare const ProductBadge: React$1.FC<ProductBadgeProps>;
537
+ /** @deprecated Use ProductBadge instead */
538
+ declare const PlanBadge: React$1.FC<ProductBadgeProps>;
522
539
 
523
540
  /**
524
541
  * Headless Purchase Gate Component
@@ -528,7 +545,7 @@ declare const PlanBadge: React$1.FC<PlanBadgeProps>;
528
545
  *
529
546
  * @example
530
547
  * ```tsx
531
- * <PurchaseGate requirePlan="Pro Plan">
548
+ * <PurchaseGate requireProduct="Pro Plan">
532
549
  * {({ hasAccess, loading }) => {
533
550
  * if (loading) return <Skeleton />;
534
551
  * if (!hasAccess) return <Paywall />;
@@ -540,20 +557,20 @@ declare const PlanBadge: React$1.FC<PlanBadgeProps>;
540
557
  declare const PurchaseGate: React$1.FC<PurchaseGateProps>;
541
558
 
542
559
  /**
543
- * Headless Plan Selector Component
560
+ * Headless Pricing Selector Component
544
561
  *
545
- * Provides plan selection logic with complete styling control via render props.
562
+ * Provides pricing selection logic with complete styling control via render props.
546
563
  * Integrates plan fetching, purchase status, and selection state management.
547
564
  *
548
565
  * Features:
549
- * - Fetches and manages plans
550
- * - Tracks selected plan
551
- * - Provides helpers for checking if plan is current/paid
566
+ * - Fetches and manages pricing options
567
+ * - Tracks selected option
568
+ * - Provides helpers for checking if option is current/paid
552
569
  * - Integrates with purchase context
553
570
  *
554
571
  * @example
555
572
  * ```tsx
556
- * <PlanSelector
573
+ * <PricingSelector
557
574
  * productRef="prd_123"
558
575
  * fetcher={async (productRef) => {
559
576
  * const res = await fetch(`/api/list-plans?productRef=${productRef}`);
@@ -566,25 +583,27 @@ declare const PurchaseGate: React$1.FC<PurchaseGateProps>;
566
583
  * {({ plans, selectedPlan, setSelectedPlanIndex, loading, isPaidPlan, isCurrentPlan }) => (
567
584
  * <div>
568
585
  * {loading ? (
569
- * <div>Loading plans...</div>
586
+ * <div>Loading...</div>
570
587
  * ) : (
571
588
  * plans.map((plan, index) => (
572
589
  * <button
573
590
  * key={plan.reference}
574
591
  * onClick={() => setSelectedPlanIndex(index)}
575
- * disabled={!isPaidPlan(plan.name)}
592
+ * disabled={!isPaidPlan(plan.reference)}
576
593
  * >
577
- * {plan.name} - ${plan.price}
578
- * {isCurrentPlan(plan.name) && ' (Current)'}
594
+ * ${plan.price}/{plan.interval}
595
+ * {isCurrentPlan(plan.reference) && ' (Current)'}
579
596
  * </button>
580
597
  * ))
581
598
  * )}
582
599
  * </div>
583
600
  * )}
584
- * </PlanSelector>
601
+ * </PricingSelector>
585
602
  * ```
586
603
  */
587
- declare const PlanSelector: React$1.FC<PlanSelectorProps>;
604
+ declare const PricingSelector: React$1.FC<PricingSelectorProps>;
605
+ /** @deprecated Use PricingSelector instead */
606
+ declare const PlanSelector: React$1.FC<PricingSelectorProps>;
588
607
 
589
608
  /**
590
609
  * SVG-based spinner component using CSS animations
@@ -905,4 +924,4 @@ declare function getPrimaryPurchase(purchases: PurchaseInfo[]): PurchaseInfo | n
905
924
  */
906
925
  declare function isPaidPurchase(purchase: PurchaseInfo): boolean;
907
926
 
908
- export { AuthAdapter, type CustomerInfo, type CustomerPurchaseData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, type Plan, PlanBadge, type PlanBadgeProps, PlanSelector, type PlanSelectorProps, PurchaseGate, type PurchaseGateProps, type PurchaseInfo, type PurchaseStatus, type PurchaseStatusReturn, type PurchaseStatusValue, type SolvaPayConfig, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, Spinner, StripePaymentFormWrapper, type UsePlansOptions, type UsePlansReturn, filterPurchases, getActivePurchases, getCancelledPurchasesWithEndDate, getMostRecentPurchase, getPrimaryPurchase, isPaidPurchase, useCheckout, useCustomer, usePlans, usePurchase, usePurchaseStatus, useSolvaPay };
927
+ export { AuthAdapter, type CustomerInfo, type CustomerPurchaseData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, type Plan, PlanBadge, type PlanBadgeProps, PlanSelector, type PlanSelectorProps, PricingSelector, type PricingSelectorProps, ProductBadge, type ProductBadgeProps, PurchaseGate, type PurchaseGateProps, type PurchaseInfo, type PurchaseStatus, type PurchaseStatusReturn, type PurchaseStatusValue, type SolvaPayConfig, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, Spinner, StripePaymentFormWrapper, type UsePlansOptions, type UsePlansReturn, filterPurchases, getActivePurchases, getCancelledPurchasesWithEndDate, getMostRecentPurchase, getPrimaryPurchase, isPaidPurchase, useCheckout, useCustomer, usePlans, usePurchase, usePurchaseStatus, useSolvaPay };
package/dist/index.d.ts CHANGED
@@ -11,8 +11,7 @@ export { defaultAuthAdapter } from './adapters/auth.js';
11
11
  interface PurchaseInfo {
12
12
  reference: string;
13
13
  productName: string;
14
- productReference: string;
15
- planName: string;
14
+ productReference?: string;
16
15
  status: string;
17
16
  startDate: string;
18
17
  endDate?: string;
@@ -25,11 +24,22 @@ interface PurchaseInfo {
25
24
  nextBillingDate?: string;
26
25
  billingCycle?: string;
27
26
  transactionId?: string;
27
+ planSnapshot?: {
28
+ reference?: string;
29
+ meterId?: string;
30
+ limit?: number;
31
+ freeUnits?: number;
32
+ pricePerUnit?: number;
33
+ planType?: string;
34
+ billingCycle?: string | null;
35
+ features?: Record<string, unknown> | null;
36
+ };
28
37
  usage?: {
29
38
  used: number;
30
- quota: number | null;
31
- unit: string;
32
- remaining: number | null;
39
+ overageUnits?: number;
40
+ overageCost?: number;
41
+ periodStart?: string;
42
+ periodEnd?: string;
33
43
  };
34
44
  }
35
45
  interface CustomerPurchaseData {
@@ -50,7 +60,9 @@ interface PurchaseStatus {
50
60
  email?: string;
51
61
  name?: string;
52
62
  purchases: PurchaseInfo[];
53
- hasPlan: (planName: string) => boolean;
63
+ hasProduct: (productName: string) => boolean;
64
+ /** @deprecated Use hasProduct instead */
65
+ hasPlan: (productName: string) => boolean;
54
66
  /**
55
67
  * Primary active purchase (paid or free) - most recent purchase with status === 'active'
56
68
  * Backend keeps purchases as 'active' until expiration, even when cancelled.
@@ -174,7 +186,7 @@ interface SolvaPayProviderProps {
174
186
  }) => Promise<ProcessPaymentResult>;
175
187
  children: React.ReactNode;
176
188
  }
177
- interface PlanBadgeProps {
189
+ interface ProductBadgeProps {
178
190
  children?: (props: {
179
191
  purchases: PurchaseInfo[];
180
192
  loading: boolean;
@@ -186,7 +198,10 @@ interface PlanBadgeProps {
186
198
  purchases: PurchaseInfo[];
187
199
  }) => string);
188
200
  }
201
+ /** @deprecated Use ProductBadgeProps instead */
202
+ type PlanBadgeProps = ProductBadgeProps;
189
203
  interface PurchaseGateProps {
204
+ /** @deprecated Use requireProduct instead */
190
205
  requirePlan?: string;
191
206
  requireProduct?: string;
192
207
  children: (props: {
@@ -207,8 +222,6 @@ interface PaymentError extends Error {
207
222
  */
208
223
  interface Plan {
209
224
  reference: string;
210
- name: string;
211
- description?: string;
212
225
  price?: number;
213
226
  currency?: string;
214
227
  interval?: string;
@@ -255,9 +268,9 @@ interface UsePlansReturn {
255
268
  refetch: () => Promise<void>;
256
269
  }
257
270
  /**
258
- * Props for headless PlanSelector component
271
+ * Props for headless PricingSelector component
259
272
  */
260
- interface PlanSelectorProps {
273
+ interface PricingSelectorProps {
261
274
  /**
262
275
  * Product reference to fetch plans for
263
276
  */
@@ -283,10 +296,12 @@ interface PlanSelectorProps {
283
296
  */
284
297
  children: (props: UsePlansReturn & {
285
298
  purchases: PurchaseInfo[];
286
- isPaidPlan: (planName: string) => boolean;
287
- isCurrentPlan: (planName: string) => boolean;
299
+ isPaidPlan: (planRef: string) => boolean;
300
+ isCurrentPlan: (planRef: string) => boolean;
288
301
  }) => React.ReactNode;
289
302
  }
303
+ /** @deprecated Use PricingSelectorProps instead */
304
+ type PlanSelectorProps = PricingSelectorProps;
290
305
  /**
291
306
  * Return type for usePurchaseStatus hook
292
307
  *
@@ -492,33 +507,35 @@ declare const SolvaPayProvider: React$1.FC<SolvaPayProviderProps>;
492
507
  declare const PaymentForm: React$1.FC<PaymentFormProps>;
493
508
 
494
509
  /**
495
- * Headless Plan Badge Component
510
+ * Headless Product Badge Component
496
511
  *
497
512
  * Displays purchase status with complete styling control.
498
513
  * Supports render props, custom components, or className patterns.
499
514
  *
500
515
  * Prevents flickering by hiding the badge during initial load and when no purchase exists.
501
516
  * Shows the badge once loading completes AND an active purchase exists (paid or free).
502
- * Badge only updates when the plan name actually changes (prevents unnecessary re-renders).
517
+ * Badge only updates when the product name actually changes (prevents unnecessary re-renders).
503
518
  *
504
- * Displays the primary active purchase (paid or free) to show current plan status.
519
+ * Displays the primary active purchase (paid or free) to show current product status.
505
520
  *
506
521
  * @example
507
522
  * ```tsx
508
523
  * // Render prop pattern
509
- * <PlanBadge>
524
+ * <ProductBadge>
510
525
  * {({ purchases, loading, displayPlan, shouldShow }) => (
511
526
  * shouldShow ? (
512
527
  * <div>{displayPlan}</div>
513
528
  * ) : null
514
529
  * )}
515
- * </PlanBadge>
530
+ * </ProductBadge>
516
531
  *
517
532
  * //ClassName pattern
518
- * <PlanBadge className="badge badge-primary" />
533
+ * <ProductBadge className="badge badge-primary" />
519
534
  * ```
520
535
  */
521
- declare const PlanBadge: React$1.FC<PlanBadgeProps>;
536
+ declare const ProductBadge: React$1.FC<ProductBadgeProps>;
537
+ /** @deprecated Use ProductBadge instead */
538
+ declare const PlanBadge: React$1.FC<ProductBadgeProps>;
522
539
 
523
540
  /**
524
541
  * Headless Purchase Gate Component
@@ -528,7 +545,7 @@ declare const PlanBadge: React$1.FC<PlanBadgeProps>;
528
545
  *
529
546
  * @example
530
547
  * ```tsx
531
- * <PurchaseGate requirePlan="Pro Plan">
548
+ * <PurchaseGate requireProduct="Pro Plan">
532
549
  * {({ hasAccess, loading }) => {
533
550
  * if (loading) return <Skeleton />;
534
551
  * if (!hasAccess) return <Paywall />;
@@ -540,20 +557,20 @@ declare const PlanBadge: React$1.FC<PlanBadgeProps>;
540
557
  declare const PurchaseGate: React$1.FC<PurchaseGateProps>;
541
558
 
542
559
  /**
543
- * Headless Plan Selector Component
560
+ * Headless Pricing Selector Component
544
561
  *
545
- * Provides plan selection logic with complete styling control via render props.
562
+ * Provides pricing selection logic with complete styling control via render props.
546
563
  * Integrates plan fetching, purchase status, and selection state management.
547
564
  *
548
565
  * Features:
549
- * - Fetches and manages plans
550
- * - Tracks selected plan
551
- * - Provides helpers for checking if plan is current/paid
566
+ * - Fetches and manages pricing options
567
+ * - Tracks selected option
568
+ * - Provides helpers for checking if option is current/paid
552
569
  * - Integrates with purchase context
553
570
  *
554
571
  * @example
555
572
  * ```tsx
556
- * <PlanSelector
573
+ * <PricingSelector
557
574
  * productRef="prd_123"
558
575
  * fetcher={async (productRef) => {
559
576
  * const res = await fetch(`/api/list-plans?productRef=${productRef}`);
@@ -566,25 +583,27 @@ declare const PurchaseGate: React$1.FC<PurchaseGateProps>;
566
583
  * {({ plans, selectedPlan, setSelectedPlanIndex, loading, isPaidPlan, isCurrentPlan }) => (
567
584
  * <div>
568
585
  * {loading ? (
569
- * <div>Loading plans...</div>
586
+ * <div>Loading...</div>
570
587
  * ) : (
571
588
  * plans.map((plan, index) => (
572
589
  * <button
573
590
  * key={plan.reference}
574
591
  * onClick={() => setSelectedPlanIndex(index)}
575
- * disabled={!isPaidPlan(plan.name)}
592
+ * disabled={!isPaidPlan(plan.reference)}
576
593
  * >
577
- * {plan.name} - ${plan.price}
578
- * {isCurrentPlan(plan.name) && ' (Current)'}
594
+ * ${plan.price}/{plan.interval}
595
+ * {isCurrentPlan(plan.reference) && ' (Current)'}
579
596
  * </button>
580
597
  * ))
581
598
  * )}
582
599
  * </div>
583
600
  * )}
584
- * </PlanSelector>
601
+ * </PricingSelector>
585
602
  * ```
586
603
  */
587
- declare const PlanSelector: React$1.FC<PlanSelectorProps>;
604
+ declare const PricingSelector: React$1.FC<PricingSelectorProps>;
605
+ /** @deprecated Use PricingSelector instead */
606
+ declare const PlanSelector: React$1.FC<PricingSelectorProps>;
588
607
 
589
608
  /**
590
609
  * SVG-based spinner component using CSS animations
@@ -905,4 +924,4 @@ declare function getPrimaryPurchase(purchases: PurchaseInfo[]): PurchaseInfo | n
905
924
  */
906
925
  declare function isPaidPurchase(purchase: PurchaseInfo): boolean;
907
926
 
908
- export { AuthAdapter, type CustomerInfo, type CustomerPurchaseData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, type Plan, PlanBadge, type PlanBadgeProps, PlanSelector, type PlanSelectorProps, PurchaseGate, type PurchaseGateProps, type PurchaseInfo, type PurchaseStatus, type PurchaseStatusReturn, type PurchaseStatusValue, type SolvaPayConfig, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, Spinner, StripePaymentFormWrapper, type UsePlansOptions, type UsePlansReturn, filterPurchases, getActivePurchases, getCancelledPurchasesWithEndDate, getMostRecentPurchase, getPrimaryPurchase, isPaidPurchase, useCheckout, useCustomer, usePlans, usePurchase, usePurchaseStatus, useSolvaPay };
927
+ export { AuthAdapter, type CustomerInfo, type CustomerPurchaseData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, type Plan, PlanBadge, type PlanBadgeProps, PlanSelector, type PlanSelectorProps, PricingSelector, type PricingSelectorProps, ProductBadge, type ProductBadgeProps, PurchaseGate, type PurchaseGateProps, type PurchaseInfo, type PurchaseStatus, type PurchaseStatusReturn, type PurchaseStatusValue, type SolvaPayConfig, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, Spinner, StripePaymentFormWrapper, type UsePlansOptions, type UsePlansReturn, filterPurchases, getActivePurchases, getCancelledPurchasesWithEndDate, getMostRecentPurchase, getPrimaryPurchase, isPaidPurchase, useCheckout, useCustomer, usePlans, usePurchase, usePurchaseStatus, useSolvaPay };
package/dist/index.js CHANGED
@@ -391,9 +391,14 @@ var SolvaPayProvider = ({
391
391
  email: purchaseData.email,
392
392
  name: purchaseData.name,
393
393
  purchases: purchaseData.purchases,
394
- hasPlan: (planName) => {
394
+ hasProduct: (productName) => {
395
395
  return purchaseData.purchases.some(
396
- (p) => p.planName.toLowerCase() === planName.toLowerCase() && p.status === "active"
396
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
397
+ );
398
+ },
399
+ hasPlan: (productName) => {
400
+ return purchaseData.purchases.some(
401
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
397
402
  );
398
403
  },
399
404
  activePurchase,
@@ -914,10 +919,10 @@ var PaymentForm = ({
914
919
  ) });
915
920
  };
916
921
 
917
- // src/components/PlanBadge.tsx
922
+ // src/components/ProductBadge.tsx
918
923
  import { useState as useState5, useEffect as useEffect3, useRef as useRef4 } from "react";
919
924
  import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
920
- var PlanBadge = ({
925
+ var ProductBadge = ({
921
926
  children,
922
927
  as: Component = "div",
923
928
  className
@@ -928,7 +933,7 @@ var PlanBadge = ({
928
933
  const lastPlanRef = useRef4(null);
929
934
  const lastLoadingRef = useRef4(true);
930
935
  const previousPurchasesRef = useRef4(purchases);
931
- const currentPlanName = activePurchase?.planName || null;
936
+ const currentPlanName = activePurchase?.productName || null;
932
937
  const effectivePlanName = currentPlanName;
933
938
  useEffect3(() => {
934
939
  if (!loading && !hasLoadedOnce) {
@@ -981,17 +986,19 @@ var PlanBadge = ({
981
986
  role: "status",
982
987
  "aria-live": "polite",
983
988
  "aria-busy": loading,
984
- "aria-label": `Current plan: ${planToDisplay}`,
989
+ "aria-label": `Current product: ${planToDisplay}`,
985
990
  children: planToDisplay
986
991
  }
987
992
  );
988
993
  };
994
+ var PlanBadge = ProductBadge;
989
995
 
990
996
  // src/components/PurchaseGate.tsx
991
997
  import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
992
998
  var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
993
- const { purchases, loading, hasPlan } = usePurchase();
994
- const hasAccess = requirePlan ? hasPlan(requirePlan) : requireProduct ? purchases.some((p) => p.status === "active" && (p.productName === requireProduct || p.productReference === requireProduct)) : purchases.some((p) => p.status === "active");
999
+ const { purchases, loading, hasProduct } = usePurchase();
1000
+ const productToCheck = requireProduct || requirePlan;
1001
+ const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
995
1002
  return /* @__PURE__ */ jsx6(Fragment3, { children: children({
996
1003
  hasAccess,
997
1004
  purchases,
@@ -999,7 +1006,7 @@ var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
999
1006
  }) });
1000
1007
  };
1001
1008
 
1002
- // src/components/PlanSelector.tsx
1009
+ // src/components/PricingSelector.tsx
1003
1010
  import { useCallback as useCallback5, useMemo as useMemo4 } from "react";
1004
1011
 
1005
1012
  // src/hooks/usePlans.ts
@@ -1134,9 +1141,9 @@ function usePlans(options) {
1134
1141
  };
1135
1142
  }
1136
1143
 
1137
- // src/components/PlanSelector.tsx
1144
+ // src/components/PricingSelector.tsx
1138
1145
  import { Fragment as Fragment4, jsx as jsx7 } from "react/jsx-runtime";
1139
- var PlanSelector = ({
1146
+ var PricingSelector = ({
1140
1147
  productRef,
1141
1148
  fetcher,
1142
1149
  filter,
@@ -1154,8 +1161,8 @@ var PlanSelector = ({
1154
1161
  });
1155
1162
  const { plans } = plansHook;
1156
1163
  const isPaidPlan = useCallback5(
1157
- (planName) => {
1158
- const plan = plans.find((p) => p.name === planName);
1164
+ (planRef) => {
1165
+ const plan = plans.find((p) => p.reference === planRef);
1159
1166
  return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
1160
1167
  },
1161
1168
  [plans]
@@ -1167,8 +1174,8 @@ var PlanSelector = ({
1167
1174
  )[0] || null;
1168
1175
  }, [purchases]);
1169
1176
  const isCurrentPlan = useCallback5(
1170
- (planName) => {
1171
- return activePurchase?.planName === planName;
1177
+ (planRef) => {
1178
+ return activePurchase?.planSnapshot?.reference === planRef;
1172
1179
  },
1173
1180
  [activePurchase]
1174
1181
  );
@@ -1179,6 +1186,7 @@ var PlanSelector = ({
1179
1186
  isCurrentPlan
1180
1187
  }) });
1181
1188
  };
1189
+ var PlanSelector = PricingSelector;
1182
1190
 
1183
1191
  // src/hooks/usePurchaseStatus.ts
1184
1192
  import { useMemo as useMemo5, useCallback as useCallback6 } from "react";
@@ -1227,6 +1235,8 @@ export {
1227
1235
  PaymentForm,
1228
1236
  PlanBadge,
1229
1237
  PlanSelector,
1238
+ PricingSelector,
1239
+ ProductBadge,
1230
1240
  PurchaseGate,
1231
1241
  SolvaPayProvider,
1232
1242
  Spinner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/react",
3
- "version": "1.0.0-preview.20",
3
+ "version": "1.0.0-preview.21",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  "jsdom": "^23.0.1",
49
49
  "typescript": "^5.5.4",
50
50
  "vitest": "^2.0.5",
51
- "@solvapay/server": "1.0.0-preview.20",
51
+ "@solvapay/server": "1.0.0-preview.21",
52
52
  "@solvapay/test-utils": "0.0.0"
53
53
  },
54
54
  "scripts": {