@solvapay/react 1.0.0-preview.9 → 1.0.1-preview.1

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SolvaPay Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -15,31 +15,93 @@ pnpm add @solvapay/react
15
15
 
16
16
  ## Quick Start
17
17
 
18
+ ### Zero-Config Usage (Recommended)
19
+
20
+ ```tsx
21
+ import { SolvaPayProvider, PaymentForm, usePurchase } 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
+
34
+ - `/api/check-purchase` for purchase checks
35
+ - `/api/create-payment-intent` for payment creation
36
+ - `/api/process-payment` for payment processing
37
+
38
+ ### Custom API Routes
39
+
40
+ ```tsx
41
+ import { SolvaPayProvider, PaymentForm } from '@solvapay/react'
42
+
43
+ export default function App() {
44
+ return (
45
+ <SolvaPayProvider
46
+ config={{
47
+ api: {
48
+ checkPurchase: '/api/custom/purchase',
49
+ createPayment: '/api/custom/payment',
50
+ processPayment: '/api/custom/process',
51
+ },
52
+ }}
53
+ >
54
+ <CheckoutPage />
55
+ </SolvaPayProvider>
56
+ )
57
+ }
58
+ ```
59
+
60
+ ### With Supabase Authentication
61
+
18
62
  ```tsx
19
- import { SolvaPayProvider, UpgradeButton, useSubscription } from '@solvapay/react';
63
+ import { SolvaPayProvider } from '@solvapay/react'
64
+ import { createSupabaseAuthAdapter } from '@solvapay/react-supabase'
65
+
66
+ export default function App() {
67
+ const adapter = createSupabaseAuthAdapter({
68
+ supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
69
+ supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
70
+ })
71
+
72
+ return (
73
+ <SolvaPayProvider config={{ auth: { adapter } }}>
74
+ <CheckoutPage />
75
+ </SolvaPayProvider>
76
+ )
77
+ }
78
+ ```
79
+
80
+ ### Fully Custom Implementation
81
+
82
+ ```tsx
83
+ import { SolvaPayProvider } from '@solvapay/react'
20
84
 
21
85
  export default function App() {
22
86
  return (
23
87
  <SolvaPayProvider
24
- customerRef={user?.id}
25
- createPayment={async ({ planRef, customerRef }) => {
26
- const res = await fetch('/api/payments/create', {
88
+ createPayment={async ({ planRef, productRef }) => {
89
+ const res = await fetch('/api/custom/payment', {
27
90
  method: 'POST',
28
- headers: { 'Content-Type': 'application/json' },
29
- body: JSON.stringify({ planRef, customerRef }),
30
- });
31
- if (!res.ok) throw new Error('Failed to create payment');
32
- return res.json();
91
+ body: JSON.stringify({ planRef, productRef }),
92
+ })
93
+ if (!res.ok) throw new Error('Failed to create payment')
94
+ return res.json()
33
95
  }}
34
- checkSubscription={async (customerRef) => {
35
- const res = await fetch(`/api/subscriptions/${customerRef}`);
36
- if (!res.ok) throw new Error('Failed to check subscription');
37
- return res.json();
96
+ checkPurchase={async customerRef => {
97
+ const res = await fetch(`/api/custom/purchase?customerRef=${customerRef}`)
98
+ if (!res.ok) throw new Error('Failed to check purchase')
99
+ return res.json()
38
100
  }}
39
101
  >
40
102
  <CheckoutPage />
41
103
  </SolvaPayProvider>
42
- );
104
+ )
43
105
  }
44
106
  ```
45
107
 
@@ -47,95 +109,183 @@ export default function App() {
47
109
 
48
110
  ### SolvaPayProvider
49
111
 
50
- Headless context provider that manages subscription state and payment methods.
112
+ Headless context provider that manages purchase state, payment methods, and customer references.
113
+
114
+ **Features:**
115
+
116
+ - Zero-config with sensible defaults
117
+ - Auto-fetches purchases on mount
118
+ - Built-in localStorage caching with user validation
119
+ - Supports auth adapters for extracting user IDs and tokens
120
+ - Customizable API routes via config
51
121
 
52
122
  **Props:**
53
- - `createPayment: (params: { planRef: string; customerRef: string }) => Promise<PaymentIntentResult>` - Function to create payment intent
54
- - `checkSubscription: (customerRef: string) => Promise<CustomerSubscriptionData>` - Function to check subscription status
55
- - `customerRef?: string` - Optional customer reference
56
- - `onCustomerRefUpdate?: (newCustomerRef: string) => void` - Callback when customer ref updates
123
+
124
+ - `config?: SolvaPayConfig` - Configuration object (optional)
125
+ - `config.api?` - Custom API route paths
126
+ - `config.auth?` - Auth adapter configuration
127
+ - `createPayment?: (params: { planRef: string; productRef?: string }) => Promise<PaymentIntentResult>` - Custom payment creation function (optional, overrides config)
128
+ - `checkPurchase?: (customerRef: string) => Promise<CustomerPurchaseData>` - Custom purchase check function (optional, overrides config)
129
+ - `processPayment?: (params: { paymentIntentId: string; productRef: string; planRef?: string }) => Promise<ProcessPaymentResult>` - Custom payment processing function (optional)
57
130
  - `children: React.ReactNode` - Child components
58
131
 
59
- ### UpgradeButton
132
+ **Config Options:**
133
+
134
+ ```tsx
135
+ interface SolvaPayConfig {
136
+ api?: {
137
+ checkPurchase?: string // Default: '/api/check-purchase'
138
+ createPayment?: string // Default: '/api/create-payment-intent'
139
+ processPayment?: string // Default: '/api/process-payment'
140
+ }
141
+ auth?: {
142
+ adapter?: AuthAdapter // Auth adapter for extracting user ID/token
143
+ getToken?: () => Promise<string | null> // Deprecated: use adapter
144
+ getUserId?: () => Promise<string | null> // Deprecated: use adapter
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### PricingSelector
60
150
 
61
- Headless component for handling upgrade flows with render props pattern.
151
+ Component for selecting and displaying available pricing options.
62
152
 
63
153
  **Props:**
64
- - `planRef: string` - Plan reference to upgrade to
65
- - `onSuccess?: () => void` - Callback on successful payment
66
- - `onError?: (error: Error) => void` - Callback on payment error
67
- - `children: (props) => React.ReactNode` - Render prop function
68
- - `renderPaymentForm?: (props) => React.ReactNode` - Custom payment form renderer
69
- - `paymentFormContainerClassName?: string` - Optional className for container
70
- - `cancelButtonClassName?: string` - Optional className for cancel button
154
+
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
+ - `className?: string` - Container className
71
160
 
72
161
  **Example:**
162
+
73
163
  ```tsx
74
- <UpgradeButton planRef="pro_plan">
75
- {({ onClick, loading, disabled, error }) => (
76
- <button onClick={onClick} disabled={disabled}>
77
- {loading ? 'Processing...' : 'Upgrade to Pro'}
78
- {error && <span>Error: {error.message}</span>}
79
- </button>
80
- )}
81
- </UpgradeButton>
164
+ import { PricingSelector, usePlans } from '@solvapay/react'
165
+
166
+ function PricingPage() {
167
+ const { plans, loading } = usePlans({ productRef: 'my-product' })
168
+
169
+ return (
170
+ <div>
171
+ {loading ? 'Loading...' : plans.map(plan => <div key={plan.reference}>{plan.price}/{plan.interval}</div>)}
172
+ </div>
173
+ )
174
+ }
82
175
  ```
83
176
 
84
177
  ### PaymentForm
85
178
 
86
- Payment form component using Stripe PaymentElement. Must be wrapped in Stripe Elements provider (provided by UpgradeButton or manually).
179
+ Payment form component using Stripe PaymentElement. Automatically handles Stripe Elements provider setup.
87
180
 
88
181
  **Props:**
182
+
183
+ - `planRef: string` - Plan reference for the payment
184
+ - `productRef?: string` - Optional product reference
89
185
  - `onSuccess?: (paymentIntent: PaymentIntent) => void` - Callback on successful payment
90
186
  - `onError?: (error: Error) => void` - Callback on payment error
91
187
  - `returnUrl?: string` - Return URL after payment
92
188
  - `submitButtonText?: string` - Submit button text (default: "Pay Now")
93
- - `className?: string` - Form className (legacy, use formClassName)
94
189
  - `formClassName?: string` - Form element className
95
190
  - `messageClassName?: string` - Message container className
96
191
  - `buttonClassName?: string` - Submit button className
97
192
 
98
- ### PlanBadge
193
+ **Example:**
194
+
195
+ ```tsx
196
+ import { PaymentForm } from '@solvapay/react'
197
+
198
+ function CheckoutPage() {
199
+ return (
200
+ <PaymentForm
201
+ planRef="pln_YOUR_PLAN"
202
+ productRef="prd_YOUR_PRODUCT"
203
+ onSuccess={() => console.log('Payment successful!')}
204
+ />
205
+ )
206
+ }
207
+ ```
208
+
209
+ ### ProductBadge
99
210
 
100
- Displays current subscription plan with render props or className pattern.
211
+ Displays current product subscription with render props or className pattern.
101
212
 
102
213
  **Props:**
214
+
103
215
  - `children?: (props) => React.ReactNode` - Render prop function
104
216
  - `as?: React.ElementType` - Component to render (default: "div")
105
217
  - `className?: string | ((props) => string)` - ClassName or function
106
218
 
107
219
  **Example:**
220
+
108
221
  ```tsx
109
- <PlanBadge className="badge badge-primary" />
222
+ <ProductBadge className="badge badge-primary" />
110
223
  ```
111
224
 
112
- ### SubscriptionGate
225
+ ### PurchaseGate
113
226
 
114
- Controls access to content based on subscription status.
227
+ Controls access to content based on purchase status.
115
228
 
116
229
  **Props:**
117
- - `requirePlan?: string` - Optional plan name to require
230
+
231
+ - `requireProduct?: string` - Optional product name to check for an active purchase
118
232
  - `children: (props) => React.ReactNode` - Render prop function
119
233
 
120
234
  **Example:**
235
+
121
236
  ```tsx
122
- <SubscriptionGate requirePlan="Pro Plan">
123
- {({ hasAccess, loading, subscriptions }) => {
124
- if (loading) return <Loading />;
125
- if (!hasAccess) return <Paywall />;
126
- return <PremiumContent />;
237
+ <PurchaseGate requireProduct="Pro Plan">
238
+ {({ hasAccess, loading, purchases }) => {
239
+ if (loading) return <Loading />
240
+ if (!hasAccess) return <Paywall />
241
+ return <PremiumContent />
127
242
  }}
128
- </SubscriptionGate>
243
+ </PurchaseGate>
129
244
  ```
130
245
 
131
246
  ## Hooks
132
247
 
133
- ### useSubscription
248
+ ### usePurchase
249
+
250
+ Access purchase status, active purchases, and helper functions.
251
+
252
+ ```tsx
253
+ const {
254
+ purchases, // Array of all purchases
255
+ loading, // Loading state
256
+ hasPaidPurchase, // Boolean: has any paid purchase
257
+ activePurchase, // Most recent active purchase
258
+ refetch, // Function to refetch purchases
259
+ } = usePurchase()
260
+ ```
261
+
262
+ ### usePlans
263
+
264
+ Fetch and manage available plans.
265
+
266
+ ```tsx
267
+ const {
268
+ plans, // Array of available plans
269
+ loading, // Loading state
270
+ error, // Error object if fetch failed
271
+ refetch, // Function to refetch plans
272
+ } = usePlans({
273
+ productRef: 'my-product', // Optional product reference
274
+ fetcher: customFetcher, // Optional custom fetcher function
275
+ })
276
+ ```
277
+
278
+ ### usePurchaseStatus
134
279
 
135
- Access subscription status and refetch function.
280
+ Advanced purchase status helpers.
136
281
 
137
282
  ```tsx
138
- const { subscriptions, loading, hasActiveSubscription, hasPlan, refetch } = useSubscription();
283
+ const {
284
+ cancelledPurchase, // Most recent cancelled purchase
285
+ shouldShowCancelledNotice, // Boolean: should show cancellation notice
286
+ formatDate, // Helper to format dates
287
+ getDaysUntilExpiration, // Helper to get days until expiration
288
+ } = usePurchaseStatus()
139
289
  ```
140
290
 
141
291
  ### useCheckout
@@ -143,7 +293,7 @@ const { subscriptions, loading, hasActiveSubscription, hasPlan, refetch } = useS
143
293
  Manage checkout flow for a specific plan.
144
294
 
145
295
  ```tsx
146
- const { loading, error, startCheckout, reset } = useCheckout('plan_ref');
296
+ const { loading, error, startCheckout, reset } = useCheckout('plan_ref')
147
297
  ```
148
298
 
149
299
  ### useSolvaPay
@@ -151,7 +301,14 @@ const { loading, error, startCheckout, reset } = useCheckout('plan_ref');
151
301
  Access SolvaPay context directly.
152
302
 
153
303
  ```tsx
154
- const { subscription, createPayment, customerRef } = useSolvaPay();
304
+ const {
305
+ purchaseData, // Full purchase data
306
+ loading, // Loading state
307
+ createPayment, // Payment creation function
308
+ processPayment, // Payment processing function
309
+ customerRef, // Current customer reference
310
+ updateCustomerRef, // Function to update customer reference
311
+ } = useSolvaPay()
155
312
  ```
156
313
 
157
314
  ## TypeScript
@@ -159,9 +316,9 @@ const { subscription, createPayment, customerRef } = useSolvaPay();
159
316
  All components and hooks are fully typed. Import types as needed:
160
317
 
161
318
  ```tsx
162
- import type { PaymentFormProps, SubscriptionStatus, PaymentIntentResult } from '@solvapay/react';
319
+ import type { PaymentFormProps, PurchaseStatus, PaymentIntentResult } from '@solvapay/react'
163
320
  ```
164
321
 
165
322
  ## More Information
166
323
 
167
- See `docs/architecture.md` for detailed architecture documentation.
324
+ See [`docs/guides/architecture.md`](../../docs/guides/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
+ };