@moneydevkit/nextjs 0.11.0-beta.3 → 0.11.0-beta.5
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 +178 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -234,4 +234,181 @@ export default function SuccessPage() {
|
|
|
234
234
|
</div>
|
|
235
235
|
)
|
|
236
236
|
}
|
|
237
|
-
```
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## MDK402: Pay-per-call API Endpoints
|
|
240
|
+
|
|
241
|
+
Gate any API route behind a Lightning payment using the HTTP 402 protocol. No accounts, no subscriptions — clients pay a Lightning invoice and get immediate access.
|
|
242
|
+
|
|
243
|
+
### How it works
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
Client Your Server Lightning
|
|
247
|
+
│ │ │
|
|
248
|
+
│ GET /api/premium │ │
|
|
249
|
+
│──────────────────────────────► │ │
|
|
250
|
+
│ │ │
|
|
251
|
+
│ 402 Payment Required │ │
|
|
252
|
+
│ { invoice, token, amount } │ │
|
|
253
|
+
│ ◄──────────────────────────── │ │
|
|
254
|
+
│ │ │
|
|
255
|
+
│ pay invoice ──────────────────┼────────────────────────────► │
|
|
256
|
+
│ ◄── preimage ─────────────────┼──────────────────────────────│
|
|
257
|
+
│ │ │
|
|
258
|
+
│ GET /api/premium │ │
|
|
259
|
+
│ Authorization: MDK402 <token>:<preimage> │
|
|
260
|
+
│──────────────────────────────► │ │
|
|
261
|
+
│ │ verify token + preimage │
|
|
262
|
+
│ 200 OK { data } │ │
|
|
263
|
+
│ ◄──────────────────────────── │ │
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
1. Client requests a protected endpoint without credentials
|
|
267
|
+
2. Server returns **402** with a Lightning invoice and a signed token
|
|
268
|
+
3. Client pays the invoice and receives a preimage (proof of payment)
|
|
269
|
+
4. Client retries with `Authorization: MDK402 <token>:<preimage>`
|
|
270
|
+
5. Server verifies the token, expiry, and preimage — then forwards to the handler
|
|
271
|
+
|
|
272
|
+
### Setup
|
|
273
|
+
|
|
274
|
+
Make sure `MDK_ACCESS_TOKEN` is set in your environment (same key used for checkout):
|
|
275
|
+
|
|
276
|
+
```env
|
|
277
|
+
MDK_ACCESS_TOKEN=your_api_key_here
|
|
278
|
+
MDK_MNEMONIC=your_mnemonic_here
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Basic usage
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
// app/api/premium/route.ts
|
|
285
|
+
import { withPayment } from '@moneydevkit/nextjs/server'
|
|
286
|
+
|
|
287
|
+
const handler = async (req: Request) => {
|
|
288
|
+
return Response.json({ content: 'Premium data' })
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const GET = withPayment(
|
|
292
|
+
{ amount: 100, currency: 'SAT' },
|
|
293
|
+
handler,
|
|
294
|
+
)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Every `GET /api/premium` request without valid credentials returns a 402 with a Lightning invoice. After payment, the same request with the authorization header returns the premium data.
|
|
298
|
+
|
|
299
|
+
### Dynamic pricing
|
|
300
|
+
|
|
301
|
+
Pass a function instead of a fixed number to compute the price from the request:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// app/api/ai/route.ts
|
|
305
|
+
import { withPayment } from '@moneydevkit/nextjs/server'
|
|
306
|
+
|
|
307
|
+
const handler = async (req: Request) => {
|
|
308
|
+
const { model } = await req.json()
|
|
309
|
+
const result = await runInference(model)
|
|
310
|
+
return Response.json({ result })
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const POST = withPayment(
|
|
314
|
+
{
|
|
315
|
+
amount: (req: Request) => {
|
|
316
|
+
const url = new URL(req.url)
|
|
317
|
+
const tier = url.searchParams.get('tier')
|
|
318
|
+
if (tier === 'pro') return 500
|
|
319
|
+
return 100
|
|
320
|
+
},
|
|
321
|
+
currency: 'SAT',
|
|
322
|
+
},
|
|
323
|
+
handler,
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The pricing function is evaluated both when creating the invoice and when verifying the token. If the price changes between issuance and verification (e.g., the client replays a cheap token on an expensive tier), the request is rejected with `amount_mismatch`.
|
|
328
|
+
|
|
329
|
+
### Fiat pricing
|
|
330
|
+
|
|
331
|
+
Use `currency: 'USD'` to price in US cents. The SDK converts to sats at the current exchange rate when generating the invoice:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
export const GET = withPayment(
|
|
335
|
+
{ amount: 50, currency: 'USD' }, // $0.50
|
|
336
|
+
handler,
|
|
337
|
+
)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Token expiry
|
|
341
|
+
|
|
342
|
+
Tokens (and their invoices) expire after 15 minutes by default. Override with `expirySeconds`:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
export const GET = withPayment(
|
|
346
|
+
{ amount: 100, currency: 'SAT', expirySeconds: 300 }, // 5 minutes
|
|
347
|
+
handler,
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Client integration
|
|
352
|
+
|
|
353
|
+
Any HTTP client can consume an MDK402 endpoint:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# 1. Request the protected resource
|
|
357
|
+
curl -s https://example.com/api/premium
|
|
358
|
+
|
|
359
|
+
# Response: 402
|
|
360
|
+
# {
|
|
361
|
+
# "token": "eyJ...",
|
|
362
|
+
# "invoice": "lnbc...",
|
|
363
|
+
# "paymentHash": "abc123...",
|
|
364
|
+
# "amountSats": 100,
|
|
365
|
+
# "expiresAt": 1234567890
|
|
366
|
+
# }
|
|
367
|
+
|
|
368
|
+
# 2. Pay the invoice with any Lightning wallet and get the preimage
|
|
369
|
+
|
|
370
|
+
# 3. Retry with the token and preimage
|
|
371
|
+
curl -s https://example.com/api/premium \
|
|
372
|
+
-H "Authorization: MDK402 eyJ...:ff00aa..."
|
|
373
|
+
|
|
374
|
+
# Response: 200 { "content": "Premium data" }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The `WWW-Authenticate` header also contains the token and invoice:
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
WWW-Authenticate: MDK402 token="eyJ...", invoice="lnbc..."
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Programmatic client (Node.js / agent)
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
async function callPaidEndpoint(url: string, payFn: (invoice: string) => Promise<string>) {
|
|
387
|
+
// Step 1: get the 402 challenge
|
|
388
|
+
const challenge = await fetch(url)
|
|
389
|
+
if (challenge.status !== 402) return challenge
|
|
390
|
+
|
|
391
|
+
const { token, invoice } = await challenge.json()
|
|
392
|
+
|
|
393
|
+
// Step 2: pay the invoice (returns preimage)
|
|
394
|
+
const preimage = await payFn(invoice)
|
|
395
|
+
|
|
396
|
+
// Step 3: retry with proof of payment
|
|
397
|
+
return fetch(url, {
|
|
398
|
+
headers: { Authorization: `MDK402 ${token}:${preimage}` },
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Error codes
|
|
404
|
+
|
|
405
|
+
| Status | Code | Meaning |
|
|
406
|
+
|--------|------|---------|
|
|
407
|
+
| 402 | `payment_required` | No valid credentials — pay the returned invoice |
|
|
408
|
+
| 401 | `invalid_token` | Token is malformed or has a bad signature |
|
|
409
|
+
| 401 | `invalid_payment_proof` | Preimage does not match the payment hash |
|
|
410
|
+
| 403 | `resource_mismatch` | Token was issued for a different endpoint |
|
|
411
|
+
| 403 | `amount_mismatch` | Token was issued for a different price |
|
|
412
|
+
| 500 | `configuration_error` | `MDK_ACCESS_TOKEN` is not set |
|
|
413
|
+
| 500 | `pricing_error` | Dynamic pricing function threw an error |
|
|
414
|
+
| 502 | `checkout_creation_failed` | Failed to create the checkout or invoice |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moneydevkit/nextjs",
|
|
3
|
-
"version": "0.11.0-beta.
|
|
3
|
+
"version": "0.11.0-beta.5",
|
|
4
4
|
"title": "@moneydevkit/nextjs",
|
|
5
5
|
"description": "moneydevkit checkout components for Next.js.",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vaul": "^1.1.2",
|
|
65
65
|
"zod": "^3.25.42",
|
|
66
66
|
"@moneydevkit/api-contract": "0.1.21",
|
|
67
|
-
"@moneydevkit/core": "0.11.0-beta.
|
|
67
|
+
"@moneydevkit/core": "0.11.0-beta.5"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/node": "^20.10.5",
|