@solvapay/react 1.0.0-preview.1 → 1.0.0-preview.11

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
@@ -1,34 +1,314 @@
1
1
  # @solvapay/react
2
2
 
3
- Payment components for SolvaPay with Stripe integration.
3
+ Headless React components and hooks for SolvaPay payment integration with Stripe.
4
4
 
5
5
  ## Install
6
+
6
7
  ```bash
7
8
  pnpm add @solvapay/react
8
9
  ```
9
10
 
10
- ## Usage
11
+ ## Peer Dependencies
12
+
13
+ - `react` ^18.2.0 || ^19.0.0
14
+ - `react-dom` ^18.2.0 || ^19.0.0
15
+
16
+ ## Quick Start
17
+
18
+ ### Zero-Config Usage (Recommended)
19
+
20
+ ```tsx
21
+ import { SolvaPayProvider, PaymentForm, useSubscription } from '@solvapay/react';
22
+
23
+ export default function App() {
24
+ return (
25
+ <SolvaPayProvider>
26
+ <CheckoutPage />
27
+ </SolvaPayProvider>
28
+ );
29
+ }
30
+ ```
31
+
32
+ By default, `SolvaPayProvider` uses:
33
+ - `/api/check-subscription` for subscription checks
34
+ - `/api/create-payment-intent` for payment creation
35
+ - `/api/process-payment` for payment processing
36
+
37
+ ### Custom API Routes
38
+
11
39
  ```tsx
12
40
  import { SolvaPayProvider, PaymentForm } from '@solvapay/react';
13
41
 
14
- export default function CheckoutPage() {
42
+ export default function App() {
43
+ return (
44
+ <SolvaPayProvider
45
+ config={{
46
+ api: {
47
+ checkSubscription: '/api/custom/subscription',
48
+ createPayment: '/api/custom/payment',
49
+ processPayment: '/api/custom/process',
50
+ },
51
+ }}
52
+ >
53
+ <CheckoutPage />
54
+ </SolvaPayProvider>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ### With Supabase Authentication
60
+
61
+ ```tsx
62
+ import { SolvaPayProvider } from '@solvapay/react';
63
+ import { createSupabaseAuthAdapter } from '@solvapay/react-supabase';
64
+
65
+ export default function App() {
66
+ const adapter = createSupabaseAuthAdapter({
67
+ supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
68
+ supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
69
+ });
70
+
71
+ return (
72
+ <SolvaPayProvider config={{ auth: { adapter } }}>
73
+ <CheckoutPage />
74
+ </SolvaPayProvider>
75
+ );
76
+ }
77
+ ```
78
+
79
+ ### Fully Custom Implementation
80
+
81
+ ```tsx
82
+ import { SolvaPayProvider } from '@solvapay/react';
83
+
84
+ export default function App() {
15
85
  return (
16
86
  <SolvaPayProvider
17
- amount={999}
18
- currency="USD"
19
- planRef="pln_abc123"
87
+ createPayment={async ({ planRef, agentRef }) => {
88
+ const res = await fetch('/api/custom/payment', {
89
+ method: 'POST',
90
+ body: JSON.stringify({ planRef, agentRef }),
91
+ });
92
+ if (!res.ok) throw new Error('Failed to create payment');
93
+ return res.json();
94
+ }}
95
+ checkSubscription={async (customerRef) => {
96
+ const res = await fetch(`/api/custom/subscription?customerRef=${customerRef}`);
97
+ if (!res.ok) throw new Error('Failed to check subscription');
98
+ return res.json();
99
+ }}
20
100
  >
21
- <PaymentForm
22
- returnUrl="/checkout/success"
23
- onSuccess={(paymentIntent) => {
24
- console.log('Payment successful!', paymentIntent);
25
- }}
26
- />
101
+ <CheckoutPage />
27
102
  </SolvaPayProvider>
28
103
  );
29
104
  }
30
105
  ```
31
106
 
32
- Peer deps: react, react-dom
107
+ ## Components
108
+
109
+ ### SolvaPayProvider
110
+
111
+ Headless context provider that manages subscription state, payment methods, and customer references.
112
+
113
+ **Features:**
114
+ - Zero-config with sensible defaults
115
+ - Auto-fetches subscriptions on mount
116
+ - Built-in localStorage caching with user validation
117
+ - Supports auth adapters for extracting user IDs and tokens
118
+ - Customizable API routes via config
119
+
120
+ **Props:**
121
+ - `config?: SolvaPayConfig` - Configuration object (optional)
122
+ - `config.api?` - Custom API route paths
123
+ - `config.auth?` - Auth adapter configuration
124
+ - `createPayment?: (params: { planRef: string; agentRef?: string }) => Promise<PaymentIntentResult>` - Custom payment creation function (optional, overrides config)
125
+ - `checkSubscription?: (customerRef: string) => Promise<CustomerSubscriptionData>` - Custom subscription check function (optional, overrides config)
126
+ - `processPayment?: (params: { paymentIntentId: string; agentRef: string; planRef?: string }) => Promise<ProcessPaymentResult>` - Custom payment processing function (optional)
127
+ - `children: React.ReactNode` - Child components
128
+
129
+ **Config Options:**
130
+ ```tsx
131
+ interface SolvaPayConfig {
132
+ api?: {
133
+ checkSubscription?: string; // Default: '/api/check-subscription'
134
+ createPayment?: string; // Default: '/api/create-payment-intent'
135
+ processPayment?: string; // Default: '/api/process-payment'
136
+ };
137
+ auth?: {
138
+ adapter?: AuthAdapter; // Auth adapter for extracting user ID/token
139
+ getToken?: () => Promise<string | null>; // Deprecated: use adapter
140
+ getUserId?: () => Promise<string | null>; // Deprecated: use adapter
141
+ };
142
+ }
143
+ ```
144
+
145
+ ### PlanSelector
146
+
147
+ Component for selecting and displaying available plans.
148
+
149
+ **Props:**
150
+ - `agentRef?: string` - Agent reference to filter plans
151
+ - `fetcher?: (agentRef: string) => Promise<Plan[]>` - Custom plan fetcher function
152
+ - `onPlanSelect?: (plan: Plan) => void` - Callback when plan is selected
153
+ - `renderPlan?: (plan: Plan) => React.ReactNode` - Custom plan renderer
154
+ - `className?: string` - Container className
155
+
156
+ **Example:**
157
+ ```tsx
158
+ import { PlanSelector, usePlans } from '@solvapay/react';
159
+
160
+ function PlansPage() {
161
+ const { plans, loading } = usePlans({ agentRef: 'my-agent' });
162
+
163
+ return (
164
+ <div>
165
+ {loading ? 'Loading...' : plans.map(plan => (
166
+ <div key={plan.reference}>{plan.name}</div>
167
+ ))}
168
+ </div>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ### PaymentForm
174
+
175
+ Payment form component using Stripe PaymentElement. Automatically handles Stripe Elements provider setup.
176
+
177
+ **Props:**
178
+ - `planRef: string` - Plan reference for the payment
179
+ - `agentRef?: string` - Optional agent reference
180
+ - `onSuccess?: (paymentIntent: PaymentIntent) => void` - Callback on successful payment
181
+ - `onError?: (error: Error) => void` - Callback on payment error
182
+ - `returnUrl?: string` - Return URL after payment
183
+ - `submitButtonText?: string` - Submit button text (default: "Pay Now")
184
+ - `formClassName?: string` - Form element className
185
+ - `messageClassName?: string` - Message container className
186
+ - `buttonClassName?: string` - Submit button className
187
+
188
+ **Example:**
189
+ ```tsx
190
+ import { PaymentForm } from '@solvapay/react';
191
+
192
+ function CheckoutPage() {
193
+ return (
194
+ <PaymentForm
195
+ planRef="pln_YOUR_PLAN"
196
+ agentRef="agt_YOUR_AGENT"
197
+ onSuccess={() => console.log('Payment successful!')}
198
+ />
199
+ );
200
+ }
201
+ ```
202
+
203
+ ### PlanBadge
204
+
205
+ Displays current subscription plan with render props or className pattern.
206
+
207
+ **Props:**
208
+ - `children?: (props) => React.ReactNode` - Render prop function
209
+ - `as?: React.ElementType` - Component to render (default: "div")
210
+ - `className?: string | ((props) => string)` - ClassName or function
211
+
212
+ **Example:**
213
+ ```tsx
214
+ <PlanBadge className="badge badge-primary" />
215
+ ```
216
+
217
+ ### SubscriptionGate
218
+
219
+ Controls access to content based on subscription status.
220
+
221
+ **Props:**
222
+ - `requirePlan?: string` - Optional plan name to require
223
+ - `children: (props) => React.ReactNode` - Render prop function
224
+
225
+ **Example:**
226
+ ```tsx
227
+ <SubscriptionGate requirePlan="Pro Plan">
228
+ {({ hasAccess, loading, subscriptions }) => {
229
+ if (loading) return <Loading />;
230
+ if (!hasAccess) return <Paywall />;
231
+ return <PremiumContent />;
232
+ }}
233
+ </SubscriptionGate>
234
+ ```
235
+
236
+ ## Hooks
237
+
238
+ ### useSubscription
239
+
240
+ Access subscription status, active subscriptions, and helper functions.
241
+
242
+ ```tsx
243
+ const {
244
+ subscriptions, // Array of all subscriptions
245
+ loading, // Loading state
246
+ hasPaidSubscription, // Boolean: has any paid subscription
247
+ activeSubscription, // Most recent active subscription
248
+ refetch // Function to refetch subscriptions
249
+ } = useSubscription();
250
+ ```
251
+
252
+ ### usePlans
253
+
254
+ Fetch and manage available plans.
255
+
256
+ ```tsx
257
+ const {
258
+ plans, // Array of available plans
259
+ loading, // Loading state
260
+ error, // Error object if fetch failed
261
+ refetch // Function to refetch plans
262
+ } = usePlans({
263
+ agentRef: 'my-agent', // Optional agent reference
264
+ fetcher: customFetcher // Optional custom fetcher function
265
+ });
266
+ ```
267
+
268
+ ### useSubscriptionStatus
269
+
270
+ Advanced subscription status helpers.
271
+
272
+ ```tsx
273
+ const {
274
+ cancelledSubscription, // Most recent cancelled subscription
275
+ shouldShowCancelledNotice, // Boolean: should show cancellation notice
276
+ formatDate, // Helper to format dates
277
+ getDaysUntilExpiration // Helper to get days until expiration
278
+ } = useSubscriptionStatus();
279
+ ```
280
+
281
+ ### useCheckout
282
+
283
+ Manage checkout flow for a specific plan.
284
+
285
+ ```tsx
286
+ const { loading, error, startCheckout, reset } = useCheckout('plan_ref');
287
+ ```
288
+
289
+ ### useSolvaPay
290
+
291
+ Access SolvaPay context directly.
292
+
293
+ ```tsx
294
+ const {
295
+ subscriptionData, // Full subscription data
296
+ loading, // Loading state
297
+ createPayment, // Payment creation function
298
+ processPayment, // Payment processing function
299
+ customerRef, // Current customer reference
300
+ updateCustomerRef // Function to update customer reference
301
+ } = useSolvaPay();
302
+ ```
303
+
304
+ ## TypeScript
305
+
306
+ All components and hooks are fully typed. Import types as needed:
307
+
308
+ ```tsx
309
+ import type { PaymentFormProps, SubscriptionStatus, PaymentIntentResult } from '@solvapay/react';
310
+ ```
311
+
312
+ ## More Information
33
313
 
34
- More: docs/architecture.md
314
+ See `docs/architecture.md` for detailed architecture documentation.
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters/auth.ts
21
+ var auth_exports = {};
22
+ __export(auth_exports, {
23
+ defaultAuthAdapter: () => defaultAuthAdapter
24
+ });
25
+ module.exports = __toCommonJS(auth_exports);
26
+ var defaultAuthAdapter = {
27
+ async getToken() {
28
+ if (typeof window === "undefined") return null;
29
+ const token = localStorage.getItem("auth_token");
30
+ return token || null;
31
+ },
32
+ async getUserId() {
33
+ const token = await this.getToken();
34
+ if (!token) return null;
35
+ try {
36
+ const parts = token.split(".");
37
+ if (parts.length === 3) {
38
+ const payload = JSON.parse(atob(parts[1]));
39
+ return payload.sub || payload.user_id || null;
40
+ }
41
+ } catch {
42
+ }
43
+ return null;
44
+ }
45
+ };
46
+ // Annotate the CommonJS export names for ESM import in node:
47
+ 0 && (module.exports = {
48
+ defaultAuthAdapter
49
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Auth Adapter Interface for Client-Side Authentication
3
+ *
4
+ * Defines the contract for authentication adapters used by SolvaPayProvider.
5
+ * Adapters handle token retrieval and user ID extraction in the browser.
6
+ */
7
+ /**
8
+ * Auth adapter interface for client-side authentication
9
+ *
10
+ * Used by SolvaPayProvider to get auth tokens and user IDs.
11
+ * Adapters should handle their own error cases and return null when
12
+ * authentication is not available or fails.
13
+ */
14
+ interface AuthAdapter {
15
+ /**
16
+ * Get the authentication token
17
+ *
18
+ * @returns The auth token string if available, null otherwise
19
+ *
20
+ * @remarks
21
+ * This method should never throw. If authentication fails or is missing,
22
+ * return null and let the caller decide how to handle unauthenticated requests.
23
+ */
24
+ getToken: () => Promise<string | null>;
25
+ /**
26
+ * Get the authenticated user ID
27
+ *
28
+ * @returns The user ID string if authenticated, null otherwise
29
+ *
30
+ * @remarks
31
+ * This method should never throw. If authentication fails or is missing,
32
+ * return null and let the caller decide how to handle unauthenticated requests.
33
+ */
34
+ getUserId: () => Promise<string | null>;
35
+ }
36
+ /**
37
+ * Default auth adapter that only checks localStorage
38
+ *
39
+ * This is a fallback adapter that doesn't depend on any specific auth provider.
40
+ * It checks for a token in localStorage under the 'auth_token' key.
41
+ */
42
+ declare const defaultAuthAdapter: AuthAdapter;
43
+
44
+ export { type AuthAdapter, defaultAuthAdapter };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Auth Adapter Interface for Client-Side Authentication
3
+ *
4
+ * Defines the contract for authentication adapters used by SolvaPayProvider.
5
+ * Adapters handle token retrieval and user ID extraction in the browser.
6
+ */
7
+ /**
8
+ * Auth adapter interface for client-side authentication
9
+ *
10
+ * Used by SolvaPayProvider to get auth tokens and user IDs.
11
+ * Adapters should handle their own error cases and return null when
12
+ * authentication is not available or fails.
13
+ */
14
+ interface AuthAdapter {
15
+ /**
16
+ * Get the authentication token
17
+ *
18
+ * @returns The auth token string if available, null otherwise
19
+ *
20
+ * @remarks
21
+ * This method should never throw. If authentication fails or is missing,
22
+ * return null and let the caller decide how to handle unauthenticated requests.
23
+ */
24
+ getToken: () => Promise<string | null>;
25
+ /**
26
+ * Get the authenticated user ID
27
+ *
28
+ * @returns The user ID string if authenticated, null otherwise
29
+ *
30
+ * @remarks
31
+ * This method should never throw. If authentication fails or is missing,
32
+ * return null and let the caller decide how to handle unauthenticated requests.
33
+ */
34
+ getUserId: () => Promise<string | null>;
35
+ }
36
+ /**
37
+ * Default auth adapter that only checks localStorage
38
+ *
39
+ * This is a fallback adapter that doesn't depend on any specific auth provider.
40
+ * It checks for a token in localStorage under the 'auth_token' key.
41
+ */
42
+ declare const defaultAuthAdapter: AuthAdapter;
43
+
44
+ export { type AuthAdapter, defaultAuthAdapter };
@@ -0,0 +1,6 @@
1
+ import {
2
+ defaultAuthAdapter
3
+ } from "../chunk-OUSEQRCT.js";
4
+ export {
5
+ defaultAuthAdapter
6
+ };
@@ -0,0 +1,25 @@
1
+ // src/adapters/auth.ts
2
+ var defaultAuthAdapter = {
3
+ async getToken() {
4
+ if (typeof window === "undefined") return null;
5
+ const token = localStorage.getItem("auth_token");
6
+ return token || null;
7
+ },
8
+ async getUserId() {
9
+ const token = await this.getToken();
10
+ if (!token) return null;
11
+ try {
12
+ const parts = token.split(".");
13
+ if (parts.length === 3) {
14
+ const payload = JSON.parse(atob(parts[1]));
15
+ return payload.sub || payload.user_id || null;
16
+ }
17
+ } catch {
18
+ }
19
+ return null;
20
+ }
21
+ };
22
+
23
+ export {
24
+ defaultAuthAdapter
25
+ };