@solvapay/react 1.0.0-preview.6 → 1.0.0-preview.7

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/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import React$1 from 'react';
2
2
  import { PaymentIntent, Stripe } from '@stripe/stripe-js';
3
+ import { ProcessPaymentResult } from '@solvapay/server';
3
4
 
4
5
  /**
5
6
  * TypeScript type definitions for @solvapay/react
@@ -11,6 +12,9 @@ interface SubscriptionInfo {
11
12
  agentName: string;
12
13
  status: string;
13
14
  startDate: string;
15
+ endDate?: string;
16
+ cancelledAt?: string;
17
+ cancellationReason?: string;
14
18
  }
15
19
  interface CustomerSubscriptionData {
16
20
  customerRef?: string;
@@ -40,6 +44,12 @@ interface SolvaPayContextValue {
40
44
  planRef: string;
41
45
  customerRef: string;
42
46
  }) => Promise<PaymentIntentResult>;
47
+ processPayment?: (params: {
48
+ paymentIntentId: string;
49
+ agentRef: string;
50
+ customerRef: string;
51
+ planRef?: string;
52
+ }) => Promise<ProcessPaymentResult>;
43
53
  customerRef?: string;
44
54
  updateCustomerRef?: (newCustomerRef: string) => void;
45
55
  }
@@ -49,6 +59,12 @@ interface SolvaPayProviderProps {
49
59
  customerRef: string;
50
60
  }) => Promise<PaymentIntentResult>;
51
61
  checkSubscription: (customerRef: string) => Promise<CustomerSubscriptionData>;
62
+ processPayment?: (params: {
63
+ paymentIntentId: string;
64
+ agentRef: string;
65
+ customerRef: string;
66
+ planRef?: string;
67
+ }) => Promise<ProcessPaymentResult>;
52
68
  customerRef?: string;
53
69
  onCustomerRefUpdate?: (newCustomerRef: string) => void;
54
70
  children: React.ReactNode;
@@ -57,6 +73,8 @@ interface PlanBadgeProps {
57
73
  children?: (props: {
58
74
  subscriptions: SubscriptionInfo[];
59
75
  loading: boolean;
76
+ displayPlan: string | null;
77
+ shouldShow: boolean;
60
78
  }) => React.ReactNode;
61
79
  as?: React.ElementType;
62
80
  className?: string | ((props: {
@@ -79,7 +97,129 @@ interface PaymentError extends Error {
79
97
  type?: string;
80
98
  }
81
99
  /**
82
- * Payment form props with proper Stripe types
100
+ * Plan interface for subscription plans
101
+ */
102
+ interface Plan {
103
+ reference: string;
104
+ name: string;
105
+ description?: string;
106
+ price?: number;
107
+ currency?: string;
108
+ interval?: string;
109
+ features?: string[];
110
+ isFreeTier?: boolean;
111
+ metadata?: Record<string, any>;
112
+ }
113
+ /**
114
+ * Options for usePlans hook
115
+ */
116
+ interface UsePlansOptions {
117
+ /**
118
+ * Fetcher function to retrieve plans
119
+ */
120
+ fetcher: (agentRef: string) => Promise<Plan[]>;
121
+ /**
122
+ * Agent reference to fetch plans for
123
+ */
124
+ agentRef?: string;
125
+ /**
126
+ * Optional filter function to filter plans
127
+ */
128
+ filter?: (plan: Plan) => boolean;
129
+ /**
130
+ * Optional sort function to sort plans
131
+ */
132
+ sortBy?: (a: Plan, b: Plan) => number;
133
+ /**
134
+ * Auto-select first paid plan on load
135
+ */
136
+ autoSelectFirstPaid?: boolean;
137
+ }
138
+ /**
139
+ * Return type for usePlans hook
140
+ */
141
+ interface UsePlansReturn {
142
+ plans: Plan[];
143
+ loading: boolean;
144
+ error: Error | null;
145
+ selectedPlanIndex: number;
146
+ selectedPlan: Plan | null;
147
+ setSelectedPlanIndex: (index: number) => void;
148
+ selectPlan: (planRef: string) => void;
149
+ refetch: () => Promise<void>;
150
+ }
151
+ /**
152
+ * Props for headless PlanSelector component
153
+ */
154
+ interface PlanSelectorProps {
155
+ /**
156
+ * Agent reference to fetch plans for
157
+ */
158
+ agentRef?: string;
159
+ /**
160
+ * Fetcher function to retrieve plans
161
+ */
162
+ fetcher: (agentRef: string) => Promise<Plan[]>;
163
+ /**
164
+ * Optional filter function
165
+ */
166
+ filter?: (plan: Plan) => boolean;
167
+ /**
168
+ * Optional sort function
169
+ */
170
+ sortBy?: (a: Plan, b: Plan) => number;
171
+ /**
172
+ * Auto-select first paid plan on load
173
+ */
174
+ autoSelectFirstPaid?: boolean;
175
+ /**
176
+ * Render prop function
177
+ */
178
+ children: (props: UsePlansReturn & {
179
+ subscriptions: SubscriptionInfo[];
180
+ isPaidPlan: (planName: string) => boolean;
181
+ isCurrentPlan: (planName: string) => boolean;
182
+ }) => React.ReactNode;
183
+ }
184
+ /**
185
+ * Return type for useSubscriptionHelpers hook
186
+ */
187
+ interface SubscriptionHelpersReturn {
188
+ /**
189
+ * Check if a plan name is a paid plan
190
+ */
191
+ isPaidPlan: (planName: string) => boolean;
192
+ /**
193
+ * Get active paid subscription
194
+ */
195
+ activePaidSubscription: SubscriptionInfo | null;
196
+ /**
197
+ * Get cancelled subscription
198
+ */
199
+ cancelledSubscription: SubscriptionInfo | null;
200
+ /**
201
+ * Check if user has any paid subscription
202
+ */
203
+ hasPaidSubscription: boolean;
204
+ /**
205
+ * Check if should show cancelled notice
206
+ */
207
+ shouldShowCancelledNotice: boolean;
208
+ /**
209
+ * Get active plan name
210
+ */
211
+ activePlanName: string | null;
212
+ /**
213
+ * Format a date string
214
+ */
215
+ formatDate: (dateString?: string) => string | null;
216
+ /**
217
+ * Get days until a date
218
+ */
219
+ getDaysUntilExpiration: (endDate?: string) => number | null;
220
+ }
221
+ /**
222
+ * Payment form props - simplified and minimal
83
223
  */
84
224
  interface PaymentFormProps {
85
225
  /**
@@ -87,30 +227,34 @@ interface PaymentFormProps {
87
227
  * including Stripe initialization and payment intent creation.
88
228
  */
89
229
  planRef: string;
230
+ /**
231
+ * Agent reference. Required for processing payment after confirmation.
232
+ */
233
+ agentRef?: string;
234
+ /**
235
+ * Callback when payment succeeds
236
+ */
90
237
  onSuccess?: (paymentIntent: PaymentIntent) => void;
238
+ /**
239
+ * Callback when payment fails
240
+ */
91
241
  onError?: (error: Error) => void;
92
242
  /**
93
243
  * Return URL after payment completion. Defaults to current page URL if not provided.
94
244
  */
95
245
  returnUrl?: string;
246
+ /**
247
+ * Text for the submit button. Defaults to "Pay Now"
248
+ */
96
249
  submitButtonText?: string;
97
250
  /**
98
- * Optional className for the form container (wraps form, messages, and cancel button)
251
+ * Optional className for the form container
99
252
  */
100
253
  className?: string;
101
254
  /**
102
255
  * Optional className for the submit button
103
256
  */
104
257
  buttonClassName?: string;
105
- /**
106
- * Text for the initial checkout button. If provided, shows a button first instead of
107
- * auto-starting checkout.
108
- */
109
- initialButtonText?: string;
110
- /**
111
- * Text for the cancel button. Set to empty string to hide cancel button.
112
- */
113
- cancelButtonText?: string;
114
258
  }
115
259
 
116
260
  /**
@@ -144,9 +288,8 @@ declare const SolvaPayProvider: React$1.FC<SolvaPayProviderProps>;
144
288
  /**
145
289
  * SolvaPay Payment Form Component
146
290
  *
147
- * Handles the entire checkout flow internally including Stripe initialization,
148
- * payment intent creation, and form rendering. Simply provide a planRef and
149
- * handle success/error callbacks.
291
+ * A simplified, minimal payment form that handles the checkout flow.
292
+ * Automatically initializes Stripe and displays the payment form.
150
293
  *
151
294
  * @example
152
295
  * ```tsx
@@ -154,6 +297,7 @@ declare const SolvaPayProvider: React$1.FC<SolvaPayProviderProps>;
154
297
  *
155
298
  * <PaymentForm
156
299
  * planRef="pro_plan"
300
+ * agentRef="agent_123"
157
301
  * onSuccess={(paymentIntent) => {
158
302
  * console.log('Payment successful!');
159
303
  * }}
@@ -171,14 +315,18 @@ declare const PaymentForm: React$1.FC<PaymentFormProps>;
171
315
  * Displays subscription status with complete styling control.
172
316
  * Supports render props, custom components, or className patterns.
173
317
  *
318
+ * Prevents flickering by hiding the badge during initial load and when no subscription exists.
319
+ * Only shows the badge once loading completes AND a subscription value is available.
320
+ * Badge only updates when the plan name actually changes (prevents unnecessary re-renders).
321
+ *
174
322
  * @example
175
323
  * ```tsx
176
324
  * // Render prop pattern
177
325
  * <PlanBadge>
178
- * {({ subscriptions, loading }) => (
179
- * <div>
180
- * {subscriptions.map(sub => <span key={sub.reference}>{sub.planName}</span>)}
181
- * </div>
326
+ * {({ subscriptions, loading, displayPlan, shouldShow }) => (
327
+ * shouldShow ? (
328
+ * <div>{displayPlan}</div>
329
+ * ) : null
182
330
  * )}
183
331
  * </PlanBadge>
184
332
  *
@@ -207,6 +355,75 @@ declare const PlanBadge: React$1.FC<PlanBadgeProps>;
207
355
  */
208
356
  declare const SubscriptionGate: React$1.FC<SubscriptionGateProps>;
209
357
 
358
+ /**
359
+ * Headless Plan Selector Component
360
+ *
361
+ * Provides plan selection logic with complete styling control via render props.
362
+ * Integrates plan fetching, subscription status, and selection state management.
363
+ *
364
+ * Features:
365
+ * - Fetches and manages plans
366
+ * - Tracks selected plan
367
+ * - Provides helpers for checking if plan is current/paid
368
+ * - Integrates with subscription context
369
+ *
370
+ * @example
371
+ * ```tsx
372
+ * <PlanSelector
373
+ * agentRef="agent_123"
374
+ * fetcher={async (agentRef) => {
375
+ * const res = await fetch(`/api/list-plans?agentRef=${agentRef}`);
376
+ * const data = await res.json();
377
+ * return data.plans;
378
+ * }}
379
+ * sortBy={(a, b) => (a.price || 0) - (b.price || 0)}
380
+ * autoSelectFirstPaid
381
+ * >
382
+ * {({ plans, selectedPlan, setSelectedPlanIndex, loading, isPaidPlan, isCurrentPlan }) => (
383
+ * <div>
384
+ * {loading ? (
385
+ * <div>Loading plans...</div>
386
+ * ) : (
387
+ * plans.map((plan, index) => (
388
+ * <button
389
+ * key={plan.reference}
390
+ * onClick={() => setSelectedPlanIndex(index)}
391
+ * disabled={!isPaidPlan(plan.name)}
392
+ * >
393
+ * {plan.name} - ${plan.price}
394
+ * {isCurrentPlan(plan.name) && ' (Current)'}
395
+ * </button>
396
+ * ))
397
+ * )}
398
+ * </div>
399
+ * )}
400
+ * </PlanSelector>
401
+ * ```
402
+ */
403
+ declare const PlanSelector: React$1.FC<PlanSelectorProps>;
404
+
405
+ /**
406
+ * Simple spinner component using CSS animations
407
+ */
408
+ declare const Spinner: React$1.FC<{
409
+ className?: string;
410
+ size?: 'sm' | 'md' | 'lg';
411
+ }>;
412
+
413
+ interface StripePaymentFormWrapperProps {
414
+ onSuccess?: (paymentIntent: any) => void;
415
+ onError?: (error: Error) => void;
416
+ returnUrl?: string;
417
+ submitButtonText?: string;
418
+ buttonClassName?: string;
419
+ }
420
+ /**
421
+ * Stripe Payment Form Wrapper Component
422
+ * Renders inside Stripe Elements context and handles the payment flow
423
+ * All hooks are called unconditionally to comply with React Rules of Hooks
424
+ */
425
+ declare const StripePaymentFormWrapper: React$1.FC<StripePaymentFormWrapperProps>;
426
+
210
427
  /**
211
428
  * Hook to access subscription status
212
429
  * Returns the current subscription state and a refetch function
@@ -237,4 +454,101 @@ declare function useCheckout(planRef: string): UseCheckoutReturn;
237
454
  */
238
455
  declare function useSolvaPay(): SolvaPayContextValue;
239
456
 
240
- export { type CustomerSubscriptionData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, PlanBadge, type PlanBadgeProps, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, SubscriptionGate, type SubscriptionGateProps, type SubscriptionInfo, type SubscriptionStatus, useCheckout, useSolvaPay, useSubscription };
457
+ /**
458
+ * Hook to manage plan fetching and selection
459
+ *
460
+ * Provides a reusable way to fetch, filter, sort and select subscription plans.
461
+ * Handles loading and error states automatically.
462
+ *
463
+ * @example
464
+ * ```tsx
465
+ * const plans = usePlans({
466
+ * agentRef: 'agent_123',
467
+ * fetcher: async (agentRef) => {
468
+ * const res = await fetch(`/api/list-plans?agentRef=${agentRef}`);
469
+ * const data = await res.json();
470
+ * return data.plans;
471
+ * },
472
+ * sortBy: (a, b) => (a.price || 0) - (b.price || 0),
473
+ * autoSelectFirstPaid: true,
474
+ * });
475
+ *
476
+ * // Use in component
477
+ * if (plans.loading) return <div>Loading...</div>;
478
+ * if (plans.error) return <div>Error: {plans.error.message}</div>;
479
+ * ```
480
+ */
481
+ declare function usePlans(options: UsePlansOptions): UsePlansReturn;
482
+
483
+ /**
484
+ * Hook providing helper functions for subscription management
485
+ *
486
+ * Provides utilities for checking subscription status, formatting dates,
487
+ * and working with paid vs free plans.
488
+ *
489
+ * @param plans - Array of available plans to determine if a subscription is paid
490
+ *
491
+ * @example
492
+ * ```tsx
493
+ * const helpers = useSubscriptionHelpers(plans);
494
+ *
495
+ * if (helpers.isPaidPlan('Pro Plan')) {
496
+ * // Handle paid plan
497
+ * }
498
+ *
499
+ * const daysLeft = helpers.getDaysUntilExpiration(subscription.endDate);
500
+ * ```
501
+ */
502
+ declare function useSubscriptionHelpers(plans: Plan[]): SubscriptionHelpersReturn;
503
+
504
+ /**
505
+ * Subscription utility functions
506
+ *
507
+ * Provides shared logic for filtering and prioritizing subscriptions
508
+ */
509
+
510
+ /**
511
+ * Filter subscriptions based on cancellation status and endDate
512
+ *
513
+ * Rules:
514
+ * - Keep all non-cancelled subscriptions
515
+ * - A subscription is considered cancelled if:
516
+ * 1. status === 'cancelled', OR
517
+ * 2. cancelledAt is set (even if status is still 'active' - this is expected behavior)
518
+ * - Keep cancelled subscriptions only if they have an endDate in the future
519
+ * (meaning the subscription is cancelled but still active until the endDate)
520
+ * - Filter out cancelled subscriptions without endDate or with past endDate
521
+ */
522
+ declare function filterSubscriptions(subscriptions: SubscriptionInfo[]): SubscriptionInfo[];
523
+ /**
524
+ * Get active subscriptions (excluding cancelled ones)
525
+ * Also excludes subscriptions with cancelledAt set, even if status is still 'active'
526
+ */
527
+ declare function getActiveSubscriptions(subscriptions: SubscriptionInfo[]): SubscriptionInfo[];
528
+ /**
529
+ * Get cancelled subscriptions with valid endDate (not expired)
530
+ * Includes subscriptions with cancelledAt set, even if status is still 'active'
531
+ */
532
+ declare function getCancelledSubscriptionsWithEndDate(subscriptions: SubscriptionInfo[]): SubscriptionInfo[];
533
+ /**
534
+ * Get the most recent subscription by startDate
535
+ */
536
+ declare function getMostRecentSubscription(subscriptions: SubscriptionInfo[]): SubscriptionInfo | null;
537
+ /**
538
+ * Get the primary subscription to display
539
+ *
540
+ * Prioritization:
541
+ * 1. Active subscriptions (most recent by startDate)
542
+ * 2. Cancelled subscriptions with endDate in the future (most recent by startDate)
543
+ * 3. null if no valid subscriptions
544
+ */
545
+ declare function getPrimarySubscription(subscriptions: SubscriptionInfo[]): SubscriptionInfo | null;
546
+ /**
547
+ * Check if user has an active paid subscription
548
+ *
549
+ * @param subscriptions - Array of subscriptions
550
+ * @param isPaidPlan - Function to check if a plan name represents a paid plan
551
+ */
552
+ declare function hasActivePaidSubscription(subscriptions: SubscriptionInfo[], isPaidPlan: (planName: string) => boolean): boolean;
553
+
554
+ export { type CustomerSubscriptionData, type PaymentError, PaymentForm, type PaymentFormProps, type PaymentIntentResult, type Plan, PlanBadge, type PlanBadgeProps, PlanSelector, type PlanSelectorProps, type SolvaPayContextValue, SolvaPayProvider, type SolvaPayProviderProps, Spinner, StripePaymentFormWrapper, SubscriptionGate, type SubscriptionGateProps, type SubscriptionHelpersReturn, type SubscriptionInfo, type SubscriptionStatus, type UsePlansOptions, type UsePlansReturn, filterSubscriptions, getActiveSubscriptions, getCancelledSubscriptionsWithEndDate, getMostRecentSubscription, getPrimarySubscription, hasActivePaidSubscription, useCheckout, usePlans, useSolvaPay, useSubscription, useSubscriptionHelpers };