@solvapay/next 1.0.0-preview.8 → 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 +21 -0
- package/README.md +271 -20
- package/dist/index.cjs +303 -76
- package/dist/index.d.cts +458 -53
- package/dist/index.d.ts +458 -53
- package/dist/index.js +299 -71
- package/package.json +10 -9
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
|
@@ -12,54 +12,232 @@ npm install @solvapay/next @solvapay/server next
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
All helpers return either a success result or a `NextResponse` error, making them easy to use in Next.js API routes.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Check Purchase
|
|
18
|
+
|
|
19
|
+
Check user purchase status with built-in request deduplication and caching:
|
|
18
20
|
|
|
19
21
|
```typescript
|
|
20
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
21
|
-
import {
|
|
22
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
23
|
+
import { checkPurchase } from '@solvapay/next'
|
|
22
24
|
|
|
23
25
|
export async function GET(request: NextRequest) {
|
|
24
|
-
const result = await
|
|
25
|
-
|
|
26
|
+
const result = await checkPurchase(request)
|
|
27
|
+
|
|
26
28
|
// If result is a NextResponse, it's an error response - return it
|
|
27
29
|
if (result instanceof NextResponse) {
|
|
28
|
-
return result
|
|
30
|
+
return result
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
// Otherwise, return the
|
|
32
|
-
return NextResponse.json(result)
|
|
32
|
+
|
|
33
|
+
// Otherwise, return the purchase data
|
|
34
|
+
return NextResponse.json(result)
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
**Features:**
|
|
37
39
|
|
|
38
40
|
- **Automatic Deduplication**: Prevents duplicate API calls by deduplicating concurrent requests
|
|
39
41
|
- **Caching**: Caches results for 2 seconds to prevent duplicate sequential requests
|
|
40
42
|
- **Automatic Cleanup**: Expired cache entries are automatically cleaned up
|
|
41
43
|
- **Memory Safe**: Maximum cache size limits prevent memory issues
|
|
42
44
|
|
|
45
|
+
### Sync Customer
|
|
46
|
+
|
|
47
|
+
Sync customer with SolvaPay backend (ensures customer exists and returns customer reference):
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
51
|
+
import { syncCustomer } from '@solvapay/next'
|
|
52
|
+
|
|
53
|
+
export async function POST(request: NextRequest) {
|
|
54
|
+
const result = await syncCustomer(request)
|
|
55
|
+
|
|
56
|
+
if (result instanceof NextResponse) {
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return NextResponse.json({ customerRef: result })
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Create Payment Intent
|
|
65
|
+
|
|
66
|
+
Create a Stripe payment intent for checkout:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
70
|
+
import { createPaymentIntent } from '@solvapay/next'
|
|
71
|
+
|
|
72
|
+
export async function POST(request: NextRequest) {
|
|
73
|
+
const { planRef, productRef } = await request.json()
|
|
74
|
+
|
|
75
|
+
if (!planRef || !productRef) {
|
|
76
|
+
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = await createPaymentIntent(request, { planRef, productRef })
|
|
80
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Process Payment
|
|
85
|
+
|
|
86
|
+
Process payment after Stripe confirmation:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
90
|
+
import { processPayment } from '@solvapay/next'
|
|
91
|
+
|
|
92
|
+
export async function POST(request: NextRequest) {
|
|
93
|
+
const { paymentIntentId, productRef, planRef } = await request.json()
|
|
94
|
+
|
|
95
|
+
if (!paymentIntentId || !productRef) {
|
|
96
|
+
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = await processPayment(request, { paymentIntentId, productRef, planRef })
|
|
100
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### List Plans
|
|
105
|
+
|
|
106
|
+
List available plans (public route, no authentication required):
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
110
|
+
import { listPlans } from '@solvapay/next'
|
|
111
|
+
|
|
112
|
+
export async function GET(request: NextRequest) {
|
|
113
|
+
const result = await listPlans(request)
|
|
114
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Cancel Renewal
|
|
119
|
+
|
|
120
|
+
Cancel renewal of a user's purchase:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
124
|
+
import { cancelRenewal } from '@solvapay/next'
|
|
125
|
+
|
|
126
|
+
export async function POST(request: NextRequest) {
|
|
127
|
+
const { purchaseRef, reason } = await request.json()
|
|
128
|
+
|
|
129
|
+
if (!purchaseRef) {
|
|
130
|
+
return NextResponse.json({ error: 'Missing purchaseRef' }, { status: 400 })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = await cancelRenewal(request, { purchaseRef, reason })
|
|
134
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Create Checkout Session
|
|
139
|
+
|
|
140
|
+
Create a hosted checkout session:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
144
|
+
import { createCheckoutSession } from '@solvapay/next'
|
|
145
|
+
|
|
146
|
+
export async function POST(request: NextRequest) {
|
|
147
|
+
const { productRef, planRef } = await request.json()
|
|
148
|
+
|
|
149
|
+
if (!productRef) {
|
|
150
|
+
return NextResponse.json({ error: 'Missing productRef' }, { status: 400 })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = await createCheckoutSession(request, { productRef, planRef })
|
|
154
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Create Customer Session
|
|
159
|
+
|
|
160
|
+
Create a customer portal session:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
164
|
+
import { createCustomerSession } from '@solvapay/next'
|
|
165
|
+
|
|
166
|
+
export async function POST(request: NextRequest) {
|
|
167
|
+
const result = await createCustomerSession(request)
|
|
168
|
+
return result instanceof NextResponse ? result : NextResponse.json(result)
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Get Authenticated User
|
|
173
|
+
|
|
174
|
+
Get authenticated user information (userId, email, name):
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
178
|
+
import { getAuthenticatedUser } from '@solvapay/next'
|
|
179
|
+
|
|
180
|
+
export async function GET(request: NextRequest) {
|
|
181
|
+
const result = await getAuthenticatedUser(request, {
|
|
182
|
+
includeEmail: true,
|
|
183
|
+
includeName: true,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
if (result instanceof NextResponse) {
|
|
187
|
+
return result
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return NextResponse.json(result)
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
43
194
|
### Cache Management
|
|
44
195
|
|
|
45
196
|
```typescript
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} from '@solvapay/next'
|
|
197
|
+
import {
|
|
198
|
+
clearPurchaseCache,
|
|
199
|
+
clearAllPurchaseCache,
|
|
200
|
+
getPurchaseCacheStats,
|
|
201
|
+
} from '@solvapay/next'
|
|
51
202
|
|
|
52
203
|
// Clear cache for a specific user
|
|
53
|
-
|
|
204
|
+
clearPurchaseCache(userId)
|
|
54
205
|
|
|
55
206
|
// Clear all cache entries
|
|
56
|
-
|
|
207
|
+
clearAllPurchaseCache()
|
|
57
208
|
|
|
58
209
|
// Get cache statistics
|
|
59
|
-
const stats =
|
|
60
|
-
console.log(`In-flight: ${stats.inFlight}, Cached: ${stats.cached}`)
|
|
210
|
+
const stats = getPurchaseCacheStats()
|
|
211
|
+
console.log(`In-flight: ${stats.inFlight}, Cached: ${stats.cached}`)
|
|
61
212
|
```
|
|
62
213
|
|
|
214
|
+
## Helper Functions Reference
|
|
215
|
+
|
|
216
|
+
All helper functions follow the same pattern:
|
|
217
|
+
|
|
218
|
+
- Take a `Request` or `NextRequest` as the first parameter
|
|
219
|
+
- Return either the success result or a `NextResponse` error
|
|
220
|
+
- Automatically extract user information from request headers (set by middleware)
|
|
221
|
+
- Support optional configuration options
|
|
222
|
+
|
|
223
|
+
**Available Helpers:**
|
|
224
|
+
|
|
225
|
+
- `checkPurchase(request, options?)` - Check purchase with caching
|
|
226
|
+
- `syncCustomer(request, options?)` - Sync customer with backend
|
|
227
|
+
- `createPaymentIntent(request, body, options?)` - Create payment intent
|
|
228
|
+
- `processPayment(request, body, options?)` - Process payment
|
|
229
|
+
- `listPlans(request)` - List available plans (public)
|
|
230
|
+
- `cancelRenewal(request, body, options?)` - Cancel renewal
|
|
231
|
+
- `createCheckoutSession(request, body, options?)` - Create hosted checkout
|
|
232
|
+
- `createCustomerSession(request, options?)` - Create customer portal
|
|
233
|
+
- `getAuthenticatedUser(request, options?)` - Get user info
|
|
234
|
+
|
|
235
|
+
**Common Options:**
|
|
236
|
+
|
|
237
|
+
- `solvaPay?: SolvaPay` - Custom SolvaPay instance
|
|
238
|
+
- `includeEmail?: boolean` - Include user email (default: true)
|
|
239
|
+
- `includeName?: boolean` - Include user name (default: true)
|
|
240
|
+
|
|
63
241
|
## Requirements
|
|
64
242
|
|
|
65
243
|
- Next.js >= 13.0.0
|
|
@@ -69,3 +247,76 @@ console.log(`In-flight: ${stats.inFlight}, Cached: ${stats.cached}`);
|
|
|
69
247
|
|
|
70
248
|
This package is separate from `@solvapay/server` to keep the server package framework-agnostic. Users who use Express, Fastify, or other frameworks don't need Next.js as a dependency.
|
|
71
249
|
|
|
250
|
+
## Middleware Setup
|
|
251
|
+
|
|
252
|
+
These helpers expect the user ID to be set in the `x-user-id` header by your Next.js middleware/proxy.
|
|
253
|
+
|
|
254
|
+
### Quick Setup with Supabase
|
|
255
|
+
|
|
256
|
+
The easiest way is to use `createSupabaseAuthMiddleware`:
|
|
257
|
+
|
|
258
|
+
**For Next.js 15:**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// middleware.ts (at project root)
|
|
262
|
+
import { createSupabaseAuthMiddleware } from '@solvapay/next'
|
|
263
|
+
|
|
264
|
+
export const middleware = createSupabaseAuthMiddleware({
|
|
265
|
+
publicRoutes: ['/api/list-plans'],
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
export const config = {
|
|
269
|
+
matcher: ['/api/:path*'],
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**For Next.js 16 with `src/` folder:**
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// src/proxy.ts (in src/ folder, not project root)
|
|
277
|
+
import { createSupabaseAuthMiddleware } from '@solvapay/next'
|
|
278
|
+
|
|
279
|
+
// Use 'proxy' export for Next.js 16 (no deprecation warning)
|
|
280
|
+
export const proxy = createSupabaseAuthMiddleware({
|
|
281
|
+
publicRoutes: ['/api/list-plans'],
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
export const config = {
|
|
285
|
+
matcher: ['/api/:path*'],
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**File Location Notes:**
|
|
290
|
+
|
|
291
|
+
- **Next.js 15**: Place `middleware.ts` at project root
|
|
292
|
+
- **Next.js 16 without `src/` folder**: Place `middleware.ts` or `proxy.ts` at project root
|
|
293
|
+
- **Next.js 16 with `src/` folder**: Place `src/proxy.ts` or `src/middleware.ts` (in `src/` folder, not root)
|
|
294
|
+
|
|
295
|
+
> **Note:** Next.js 16 renamed "middleware" to "proxy". You can use either export name, but `proxy` is recommended to avoid deprecation warnings.
|
|
296
|
+
|
|
297
|
+
### Custom Middleware
|
|
298
|
+
|
|
299
|
+
Alternatively, you can create your own middleware:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// middleware.ts (or src/proxy.ts for Next.js 16)
|
|
303
|
+
import { NextResponse } from 'next/server'
|
|
304
|
+
import type { NextRequest } from 'next/server'
|
|
305
|
+
|
|
306
|
+
export async function middleware(request: NextRequest) {
|
|
307
|
+
// Extract user ID from your auth system
|
|
308
|
+
const userId = await getUserIdFromAuth(request)
|
|
309
|
+
|
|
310
|
+
// Clone request and add user ID header
|
|
311
|
+
const requestHeaders = new Headers(request.headers)
|
|
312
|
+
requestHeaders.set('x-user-id', userId)
|
|
313
|
+
|
|
314
|
+
return NextResponse.next({
|
|
315
|
+
request: {
|
|
316
|
+
headers: requestHeaders,
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
You can also use the `requireUserId` utility from `@solvapay/auth` in your middleware.
|