@zendfi/sdk 0.4.0 → 0.5.0

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
@@ -0,0 +1,1161 @@
1
+ # @zendfi/sdk
2
+
3
+ > Zero-config TypeScript SDK for accepting crypto payments with ZendFi
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@zendfi/sdk.svg)](https://www.npmjs.com/package/@zendfi/sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Accept **SOL, USDC, and USDT** payments in your app with just a few lines of code. Built for developers who want to integrate crypto payments without the complexity.
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - **All-Inclusive Pricing** — 0.6% platform fee covers everything (network fees included!)
15
+ - **Zero Configuration** — Auto-detects environment from your API key
16
+ - **Type-Safe** — Full TypeScript support with auto-completion
17
+ - **Auto-Retry** — Built-in exponential backoff for network errors
18
+ - **Idempotency** — Automatic duplicate prevention for safe retries
19
+ - **Webhook Helpers** — Auto-verified handlers for Next.js, Express, and more
20
+ - **Test Mode** — Free devnet testing with no real money
21
+ - **Multi-Network** — Automatic routing to devnet or mainnet
22
+ - **Agentic Intent Protocol** — AI agent payment capabilities with scoped API keys
23
+ - **PPP Pricing** — Purchasing Power Parity for global reach (27+ countries)
24
+ - **Payment Intents** — Two-phase commit pattern for reliable checkout
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @zendfi/sdk
32
+ # or
33
+ pnpm add @zendfi/sdk
34
+ # or
35
+ yarn add @zendfi/sdk
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Quick Start
41
+
42
+ ### 1. Get your API key
43
+
44
+ Sign up at [zendfi.tech](https://zendfi.tech) and grab your API keys from the dashboard.
45
+
46
+ ### 2. Set environment variables
47
+
48
+ ```bash
49
+ # .env.local or .env
50
+
51
+ # For testing (free devnet SOL, no real money)
52
+ ZENDFI_API_KEY=zfi_test_your_test_key_here
53
+
54
+ # For production (real crypto on mainnet)
55
+ # ZENDFI_API_KEY=zfi_live_your_live_key_here
56
+ ```
57
+
58
+ ### 3. Create your first payment
59
+
60
+ ```typescript
61
+ import { zendfi } from '@zendfi/sdk';
62
+
63
+ // That's it! Auto-configured from ZENDFI_API_KEY
64
+ const payment = await zendfi.createPayment({
65
+ amount: 50,
66
+ description: 'Premium subscription',
67
+ customer_email: 'customer@example.com',
68
+ });
69
+
70
+ // Send customer to checkout
71
+ console.log(payment.payment_url);
72
+ // => https://pay.zendfi.tech/abc123...
73
+ ```
74
+
75
+ **Response includes:**
76
+ ```typescript
77
+ {
78
+ id: "pay_abc123...",
79
+ amount: 50,
80
+ currency: "USD",
81
+ status: "Pending",
82
+ payment_url: "https://pay.zendfi.tech/abc123...",
83
+ qr_code: "data:image/png;base64,...",
84
+ expires_at: "2025-11-08T20:00:00Z",
85
+ mode: "test", // or "live"
86
+ }
87
+ ```
88
+
89
+ ---
90
+
91
+ ## API Key Modes
92
+
93
+ ZendFi uses **smart API keys** that automatically route to the correct network:
94
+
95
+ | Mode | API Key Prefix | Network | Gas Costs | Purpose |
96
+ |------|---------------|---------|-----------|---------|
97
+ | **Test** | `zfi_test_` | Solana Devnet | Free | Development & testing |
98
+ | **Live** | `zfi_live_` | Solana Mainnet | ~$0.0001 | Production |
99
+
100
+ > **Pro Tip:** The SDK auto-detects the mode from your API key prefix. No configuration needed!
101
+
102
+ ### Getting Test SOL
103
+
104
+ For devnet testing:
105
+ 1. Use your `zfi_test_` API key
106
+ 2. Get free SOL from [sol-faucet.com](https://www.sol-faucet.com/)
107
+ 3. All transactions use test tokens (zero value)
108
+
109
+ ### Going Live
110
+
111
+ When ready for production:
112
+ 1. Switch to your `zfi_live_` API key
113
+ 2. **That's it!** The SDK handles everything else automatically
114
+
115
+ ---
116
+
117
+ ## Pricing (The Good News!)
118
+
119
+ **Platform Fee: 0.6%** (all-inclusive)
120
+
121
+ This covers:
122
+ - Network transaction fees (~$0.0001 per transaction)
123
+ - Payment processing
124
+ - Automatic settlements
125
+ - Webhook delivery
126
+ - No hidden costs
127
+
128
+ **Example:**
129
+ - Customer pays: $100 USDC
130
+ - You receive: $99.40 USDC
131
+ - ZendFi fee: $0.60 (covers all network fees + platform)
132
+
133
+ ---
134
+
135
+ ## Agentic Intent Protocol
136
+
137
+ Enable AI agents to make payments autonomously with scoped permissions and spending limits.
138
+
139
+ ### Namespaced APIs
140
+
141
+ The SDK provides namespaced APIs for agentic capabilities:
142
+
143
+ ```typescript
144
+ import { zendfi } from '@zendfi/sdk';
145
+
146
+ // Agent API - Manage agent keys and sessions
147
+ zendfi.agent.createKey(...)
148
+ zendfi.agent.createSession(...)
149
+
150
+ // Payment Intents - Two-phase payment flow
151
+ zendfi.intents.create(...)
152
+ zendfi.intents.confirm(...)
153
+
154
+ // Pricing - PPP and AI pricing
155
+ zendfi.pricing.getPPPFactor(...)
156
+ zendfi.pricing.getSuggestion(...)
157
+
158
+ // Autonomy - Autonomous delegation
159
+ zendfi.autonomy.enable(...)
160
+ zendfi.autonomy.getStatus(...)
161
+
162
+ // Smart Payments - AI-powered routing
163
+ zendfi.smart.execute(...)
164
+ zendfi.smart.submitSigned(...) // For device-bound flows
165
+ ```
166
+
167
+ ### Agent API Keys
168
+
169
+ Create scoped API keys for AI agents with limited permissions:
170
+
171
+ ```typescript
172
+ // Create an agent API key (prefixed with zai_)
173
+ const agentKey = await zendfi.agent.createKey({
174
+ name: 'Shopping Assistant',
175
+ agent_id: 'shopping-assistant-v1',
176
+ scopes: ['create_payments', 'read_analytics'],
177
+ rate_limit_per_hour: 500,
178
+ });
179
+
180
+ // IMPORTANT: Save the full_key now - it won't be shown again!
181
+ console.log(agentKey.full_key); // => "zai_test_abc123..."
182
+
183
+ // List agent keys
184
+ const keys = await zendfi.agent.listKeys();
185
+
186
+ // Revoke a key
187
+ await zendfi.agent.revokeKey(keyId);
188
+ ```
189
+
190
+ **Available Scopes:**
191
+ - `create_payments` - Create new payments
192
+ - `read_payments` - View payment status
193
+ - `read_analytics` - Access analytics data
194
+ - `manage_sessions` - Create/revoke sessions
195
+
196
+ ### Agent Sessions
197
+
198
+ Create sessions with spending limits for user-approved agent actions:
199
+
200
+ ```typescript
201
+ // Create a session with spending limits
202
+ const session = await zendfi.agent.createSession({
203
+ agent_id: 'shopping-assistant-v1',
204
+ user_wallet: 'Hx7B...abc',
205
+ limits: {
206
+ max_per_transaction: 50, // $50 max per payment
207
+ max_per_day: 200, // $200 daily limit
208
+ allowed_merchants: ['merchant_123'], // Optional whitelist
209
+ },
210
+ duration_hours: 24,
211
+ });
212
+
213
+ // List active sessions
214
+ const sessions = await zendfi.agent.listSessions();
215
+
216
+ // Get specific session
217
+ const session = await zendfi.agent.getSession(sessionId);
218
+
219
+ // Revoke session
220
+ await zendfi.agent.revokeSession(sessionId);
221
+ ```
222
+
223
+ ### Payment Intents
224
+
225
+ Modern two-phase payment flow for reliable checkout:
226
+
227
+ ```typescript
228
+ // Step 1: Create intent when user starts checkout
229
+ const intent = await zendfi.intents.create({
230
+ amount: 99.99,
231
+ description: 'Premium subscription',
232
+ capture_method: 'automatic', // or 'manual' for auth-only
233
+ });
234
+
235
+ // Step 2: Pass client_secret to frontend for confirmation
236
+ console.log(intent.client_secret); // cs_abc123...
237
+
238
+ // Step 3: Confirm when user clicks "Pay"
239
+ const confirmed = await zendfi.intents.confirm(intent.id, {
240
+ client_secret: intent.client_secret,
241
+ customer_wallet: 'Hx7B...abc',
242
+ });
243
+
244
+ // Or cancel if user abandons checkout
245
+ await zendfi.intents.cancel(intent.id);
246
+ ```
247
+
248
+ **Intent Statuses:**
249
+ - `requires_payment` - Waiting for confirmation
250
+ - `processing` - Payment in progress
251
+ - `succeeded` - Payment complete
252
+ - `canceled` - Canceled by user/merchant
253
+ - `failed` - Payment failed
254
+
255
+ ### PPP Pricing (Purchasing Power Parity)
256
+
257
+ Automatically adjust prices based on customer location:
258
+
259
+ ```typescript
260
+ // Get PPP factor for a country
261
+ const factor = await zendfi.pricing.getPPPFactor('BR');
262
+ // {
263
+ // country_code: 'BR',
264
+ // country_name: 'Brazil',
265
+ // ppp_factor: 0.35,
266
+ // adjustment_percentage: 35.0,
267
+ // currency_code: 'BRL'
268
+ // }
269
+
270
+ // Calculate localized price
271
+ const basePrice = 100;
272
+ const localPrice = basePrice * factor.ppp_factor;
273
+ console.log(`$${localPrice} for Brazilian customers`); // $35
274
+
275
+ // List all supported countries
276
+ const factors = await zendfi.pricing.listFactors();
277
+
278
+ // Get AI pricing suggestion
279
+ const suggestion = await zendfi.pricing.getSuggestion({
280
+ agent_id: 'my-agent',
281
+ base_price: 99.99,
282
+ user_profile: {
283
+ location_country: 'BR',
284
+ },
285
+ });
286
+ ```
287
+
288
+ **Supported Countries (27+):**
289
+ Argentina, Australia, Brazil, Canada, China, Colombia, Egypt, France, Germany, Ghana, Hong Kong, Hungary, India, Indonesia, Israel, Japan, Kenya, Mexico, Nigeria, Philippines, Poland, South Africa, Thailand, Turkey, Ukraine, United Kingdom, Vietnam, and more.
290
+
291
+ ### Autonomous Delegation
292
+
293
+ Enable agents to make payments without per-transaction approval:
294
+
295
+ ```typescript
296
+ // Enable autonomous mode for a wallet
297
+ const delegate = await zendfi.autonomy.enable({
298
+ wallet_address: 'Hx7B...abc',
299
+ agent_id: 'shopping-assistant',
300
+ max_per_day_usd: 100,
301
+ max_per_transaction_usd: 25,
302
+ duration_hours: 24,
303
+ allowed_categories: ['subscriptions', 'digital_goods'],
304
+ });
305
+
306
+ // Check autonomy status
307
+ const status = await zendfi.autonomy.getStatus(walletAddress);
308
+
309
+ // Revoke delegation
310
+ await zendfi.autonomy.revoke(delegateId);
311
+ ```
312
+
313
+ ### Smart Payments
314
+
315
+ AI-powered payments that automatically apply optimizations:
316
+
317
+ ```typescript
318
+ // Create a smart payment with automatic PPP
319
+ const payment = await zendfi.smart.execute({
320
+ agent_id: 'my-agent',
321
+ user_wallet: 'Hx7B...abc',
322
+ amount_usd: 99.99,
323
+ country_code: 'BR', // Apply PPP automatically
324
+ auto_detect_gasless: true,
325
+ description: 'Pro subscription',
326
+ });
327
+
328
+ // Response includes discount applied
329
+ console.log(`Original: $${payment.original_amount_usd}`);
330
+ console.log(`Final: $${payment.final_amount_usd}`);
331
+ // Original: $99.99
332
+ // Final: $64.99 (35% PPP discount applied)
333
+ ```
334
+
335
+ #### Device-Bound Flow
336
+
337
+ For payments requiring user signatures:
338
+
339
+ ```typescript
340
+ // After user signs the transaction locally
341
+ const result = await zendfi.smart.submitSigned(
342
+ 'pay_123...',
343
+ signedTransactionBase64
344
+ );
345
+
346
+ console.log(result.status); // "confirmed"
347
+ console.log(result.transaction_signature);
348
+ ```
349
+
350
+ > **Tip:** `zendfi.smartPayment()` is also available as an alias for `zendfi.smart.execute()`.
351
+
352
+ ---
353
+
354
+ ## Complete API Reference
355
+
356
+ ### Payments
357
+
358
+ #### Create Payment
359
+
360
+ ```typescript
361
+ const payment = await zendfi.createPayment({
362
+ amount: 99.99,
363
+ currency: 'USD', // Optional, defaults to 'USD'
364
+ token: 'USDC', // 'SOL', 'USDC', or 'USDT'
365
+ description: 'Annual subscription',
366
+ customer_email: 'customer@example.com',
367
+ redirect_url: 'https://yourapp.com/success',
368
+ metadata: {
369
+ orderId: 'ORD-123',
370
+ userId: 'USR-456',
371
+ tier: 'premium',
372
+ },
373
+ });
374
+
375
+ // Redirect customer to payment page
376
+ window.location.href = payment.payment_url;
377
+ ```
378
+
379
+ #### Get Payment Status
380
+
381
+ ```typescript
382
+ const payment = await zendfi.getPayment('pay_abc123...');
383
+
384
+ console.log(payment.status);
385
+ // => "Pending" | "Confirmed" | "Failed" | "Expired"
386
+ ```
387
+
388
+ #### List Payments (with filters)
389
+
390
+ ```typescript
391
+ const payments = await zendfi.listPayments({
392
+ page: 1,
393
+ limit: 50,
394
+ status: 'Confirmed',
395
+ from_date: '2025-01-01',
396
+ to_date: '2025-12-31',
397
+ });
398
+
399
+ console.log(`Found ${payments.pagination.total} payments`);
400
+ payments.data.forEach(payment => {
401
+ console.log(`${payment.id}: $${payment.amount} - ${payment.status}`);
402
+ });
403
+ ```
404
+
405
+ ---
406
+
407
+ ### Payment Links
408
+
409
+ Create shareable checkout URLs that can be reused multiple times.
410
+
411
+ #### Create Payment Link
412
+
413
+ ```typescript
414
+ const link = await zendfi.createPaymentLink({
415
+ amount: 29.99,
416
+ description: 'Premium Course',
417
+ max_uses: 100, // Optional: limit usage
418
+ expires_at: '2025-12-31T23:59:59Z', // Optional
419
+ metadata: {
420
+ product_id: 'course-123',
421
+ },
422
+ });
423
+
424
+ // Share this URL with customers
425
+ console.log(link.hosted_page_url);
426
+ // => https://pay.zendfi.tech/link/abc123
427
+
428
+ // Or use the QR code
429
+ console.log(link.qr_code);
430
+ ```
431
+
432
+ #### Get Payment Link
433
+
434
+ ```typescript
435
+ const link = await zendfi.getPaymentLink('link_abc123');
436
+ console.log(`Used ${link.uses_count}/${link.max_uses} times`);
437
+ ```
438
+
439
+ #### List Payment Links
440
+
441
+ ```typescript
442
+ const links = await zendfi.listPaymentLinks();
443
+ links.forEach(link => {
444
+ console.log(`${link.description}: ${link.hosted_page_url}`);
445
+ });
446
+ ```
447
+
448
+ ---
449
+
450
+ ### Subscriptions
451
+
452
+ Recurring crypto payments made easy.
453
+
454
+ #### Create Subscription Plan
455
+
456
+ ```typescript
457
+ const plan = await zendfi.createSubscriptionPlan({
458
+ name: 'Pro Plan',
459
+ description: 'Premium features + priority support',
460
+ amount: 29.99,
461
+ interval: 'monthly', // 'daily', 'weekly', 'monthly', 'yearly'
462
+ interval_count: 1, // Bill every X intervals
463
+ trial_days: 7, // Optional: free trial
464
+ metadata: {
465
+ features: ['analytics', 'api-access', 'priority-support'],
466
+ },
467
+ });
468
+ ```
469
+
470
+ #### Subscribe a Customer
471
+
472
+ ```typescript
473
+ const subscription = await zendfi.createSubscription({
474
+ plan_id: plan.id,
475
+ customer_email: 'customer@example.com',
476
+ customer_wallet: '6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN',
477
+ metadata: {
478
+ user_id: '12345',
479
+ },
480
+ });
481
+
482
+ console.log(subscription.current_period_end);
483
+ ```
484
+
485
+ #### Cancel Subscription
486
+
487
+ ```typescript
488
+ const cancelled = await zendfi.cancelSubscription(subscription.id);
489
+ console.log(`Cancelled. Active until ${cancelled.current_period_end}`);
490
+ ```
491
+
492
+ ---
493
+
494
+ ### Installment Plans
495
+
496
+ Split large purchases into scheduled payments.
497
+
498
+ #### Create Installment Plan
499
+
500
+ ```typescript
501
+ const plan = await zendfi.createInstallmentPlan({
502
+ total_amount: 500,
503
+ num_installments: 5, // 5 payments of $100 each
504
+ interval_days: 30, // One payment every 30 days
505
+ customer_email: 'customer@example.com',
506
+ customer_wallet: '6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN',
507
+ description: 'MacBook Pro - Installment Plan',
508
+ metadata: {
509
+ product_id: 'macbook-pro-16',
510
+ },
511
+ });
512
+
513
+ console.log(`Created plan with ${plan.num_installments} installments`);
514
+ ```
515
+
516
+ #### Get Installment Plan
517
+
518
+ ```typescript
519
+ const plan = await zendfi.getInstallmentPlan(plan.id);
520
+ console.log(`Status: ${plan.status}`);
521
+ // => "active" | "completed" | "defaulted" | "cancelled"
522
+ ```
523
+
524
+ #### List Customer's Installment Plans
525
+
526
+ ```typescript
527
+ const customerPlans = await zendfi.listCustomerInstallmentPlans(
528
+ '6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN'
529
+ );
530
+ ```
531
+
532
+ #### Cancel Installment Plan
533
+
534
+ ```typescript
535
+ await zendfi.cancelInstallmentPlan(plan.id);
536
+ ```
537
+
538
+ ---
539
+
540
+ ### 🧾 Invoices
541
+
542
+ Professional invoices with crypto payment options.
543
+
544
+ #### Create Invoice
545
+
546
+ ```typescript
547
+ const invoice = await zendfi.createInvoice({
548
+ customer_email: 'client@company.com',
549
+ customer_name: 'Acme Corp',
550
+ due_date: '2025-12-31',
551
+ line_items: [
552
+ {
553
+ description: 'Website Design',
554
+ quantity: 1,
555
+ unit_price: 2500,
556
+ },
557
+ {
558
+ description: 'Logo Design',
559
+ quantity: 3,
560
+ unit_price: 500,
561
+ },
562
+ ],
563
+ notes: 'Payment due within 30 days',
564
+ metadata: {
565
+ project_id: 'proj-456',
566
+ },
567
+ });
568
+
569
+ console.log(`Invoice #${invoice.invoice_number} created`);
570
+ ```
571
+
572
+ #### Send Invoice via Email
573
+
574
+ ```typescript
575
+ const sent = await zendfi.sendInvoice(invoice.id);
576
+ console.log(`Invoice sent to ${sent.sent_to}`);
577
+ console.log(`Payment URL: ${sent.payment_url}`);
578
+ ```
579
+
580
+ #### List Invoices
581
+
582
+ ```typescript
583
+ const invoices = await zendfi.listInvoices();
584
+ invoices.forEach(inv => {
585
+ console.log(`${inv.invoice_number}: $${inv.total_amount} - ${inv.status}`);
586
+ });
587
+ ```
588
+
589
+ ---
590
+
591
+ ## Webhooks
592
+
593
+ Get notified when payments are confirmed, subscriptions renew, etc.
594
+
595
+ ### Supported Events
596
+
597
+ ```typescript
598
+ 'payment.created'
599
+ 'payment.confirmed'
600
+ 'payment.failed'
601
+ 'payment.expired'
602
+ 'subscription.created'
603
+ 'subscription.activated'
604
+ 'subscription.canceled'
605
+ 'subscription.payment_failed'
606
+ 'split.completed'
607
+ 'split.failed'
608
+ 'installment.due'
609
+ 'installment.paid'
610
+ 'installment.late'
611
+ 'escrow.funded'
612
+ 'escrow.released'
613
+ 'escrow.refunded'
614
+ 'escrow.disputed'
615
+ 'invoice.sent'
616
+ 'invoice.paid'
617
+ ```
618
+
619
+ ### Next.js App Router (Recommended)
620
+
621
+ ```typescript
622
+ // app/api/webhooks/zendfi/route.ts
623
+ import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
624
+
625
+ export const POST = createNextWebhookHandler({
626
+ secret: process.env.ZENDFI_WEBHOOK_SECRET!,
627
+ handlers: {
628
+ 'payment.confirmed': async (payment) => {
629
+ // Payment is verified and typed!
630
+ console.log(`💰 Payment confirmed: $${payment.amount}`);
631
+
632
+ // Update your database
633
+ await db.orders.update({
634
+ where: { id: payment.metadata.orderId },
635
+ data: {
636
+ status: 'paid',
637
+ transaction_signature: payment.transaction_signature,
638
+ },
639
+ });
640
+
641
+ // Send confirmation email
642
+ await sendEmail({
643
+ to: payment.customer_email,
644
+ subject: 'Payment Confirmed!',
645
+ template: 'payment-success',
646
+ });
647
+ },
648
+
649
+ 'subscription.activated': async (subscription) => {
650
+ console.log(`✅ Subscription activated for ${subscription.customer_email}`);
651
+ await grantAccess(subscription.customer_email);
652
+ },
653
+
654
+ 'escrow.released': async (escrow) => {
655
+ console.log(`🔓 Escrow released: $${escrow.amount}`);
656
+ await notifySeller(escrow.seller_email);
657
+ },
658
+ },
659
+ });
660
+ ```
661
+
662
+ ### Next.js Pages Router
663
+
664
+ ```typescript
665
+ // pages/api/webhooks/zendfi.ts
666
+ import { createPagesWebhookHandler } from '@zendfi/sdk/nextjs';
667
+
668
+ export default createPagesWebhookHandler({
669
+ secret: process.env.ZENDFI_WEBHOOK_SECRET!,
670
+ handlers: {
671
+ 'payment.confirmed': async (payment) => {
672
+ await fulfillOrder(payment.metadata.orderId);
673
+ },
674
+ },
675
+ });
676
+
677
+ // IMPORTANT: Disable body parser for webhook signature verification
678
+ export const config = {
679
+ api: { bodyParser: false },
680
+ };
681
+ ```
682
+
683
+ ### Express
684
+
685
+ ```typescript
686
+ import express from 'express';
687
+ import { createExpressWebhookHandler } from '@zendfi/sdk/express';
688
+
689
+ const app = express();
690
+
691
+ app.post(
692
+ '/api/webhooks/zendfi',
693
+ express.raw({ type: 'application/json' }), // Preserve raw body!
694
+ createExpressWebhookHandler({
695
+ secret: process.env.ZENDFI_WEBHOOK_SECRET!,
696
+ handlers: {
697
+ 'payment.confirmed': async (payment) => {
698
+ console.log('Payment confirmed:', payment.id);
699
+ },
700
+ },
701
+ })
702
+ );
703
+ ```
704
+
705
+ ### Webhook Deduplication (Production)
706
+
707
+ The handlers use in-memory deduplication by default (fine for development). For production, use Redis or your database:
708
+
709
+ ```typescript
710
+ import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
711
+ import { redis } from './lib/redis';
712
+
713
+ export const POST = createNextWebhookHandler({
714
+ secret: process.env.ZENDFI_WEBHOOK_SECRET!,
715
+
716
+ // Check if webhook was already processed
717
+ isProcessed: async (eventId) => {
718
+ const exists = await redis.exists(`webhook:${eventId}`);
719
+ return exists === 1;
720
+ },
721
+
722
+ // Mark webhook as processed
723
+ onProcessed: async (eventId) => {
724
+ await redis.set(`webhook:${eventId}`, '1', 'EX', 86400); // 24h TTL
725
+ },
726
+
727
+ handlers: {
728
+ 'payment.confirmed': async (payment) => {
729
+ // This will only run once, even if webhook retries
730
+ await processPayment(payment);
731
+ },
732
+ },
733
+ });
734
+ ```
735
+
736
+ ### Manual Webhook Verification
737
+
738
+ For custom implementations:
739
+
740
+ ```typescript
741
+ import { verifyNextWebhook } from '@zendfi/sdk/webhooks';
742
+
743
+ export async function POST(request: Request) {
744
+ const payload = await verifyNextWebhook(request, process.env.ZENDFI_WEBHOOK_SECRET);
745
+
746
+ if (!payload) {
747
+ return new Response('Invalid signature', { status: 401 });
748
+ }
749
+
750
+ // Handle verified payload
751
+ if (payload.event === 'payment.confirmed') {
752
+ await handlePayment(payload.data);
753
+ }
754
+
755
+ return new Response('OK');
756
+ }
757
+ ```
758
+
759
+ **Available verifiers:**
760
+ - `verifyNextWebhook(request, secret?)` — Next.js App Router
761
+ - `verifyExpressWebhook(req, secret?)` — Express
762
+ - `verifyWebhookSignature(payload, signature, secret)` — Low-level
763
+
764
+ ---
765
+
766
+ ## Configuration
767
+
768
+ ### Environment Variables
769
+
770
+ | Variable | Required | Description | Example |
771
+ |----------|----------|-------------|---------|
772
+ | `ZENDFI_API_KEY` | Yes* | Your ZendFi API key | `zfi_test_abc123...` |
773
+ | `ZENDFI_WEBHOOK_SECRET` | For webhooks | Webhook signature verification | `whsec_abc123...` |
774
+ | `ZENDFI_API_URL` | No | Override base URL (for testing) | `http://localhost:3000` |
775
+ | `ZENDFI_ENVIRONMENT` | No | Force environment | `development` |
776
+
777
+ *Required unless you pass `apiKey` directly to `ZendFiClient`
778
+
779
+ ### Custom Client Configuration
780
+
781
+ ```typescript
782
+ import { ZendFiClient } from '@zendfi/sdk';
783
+
784
+ const client = new ZendFiClient({
785
+ apiKey: 'zfi_test_abc123...',
786
+ baseURL: 'https://api.zendfi.tech', // Optional
787
+ timeout: 30000, // 30 seconds (default)
788
+ retries: 3, // Auto-retry attempts (default)
789
+ idempotencyEnabled: true, // Auto idempotency (default)
790
+ debug: false, // Log requests/responses (default: false)
791
+ });
792
+
793
+ // Use custom client
794
+ const payment = await client.createPayment({
795
+ amount: 50,
796
+ description: 'Test payment',
797
+ });
798
+ ```
799
+
800
+ ### Using Multiple Clients (Test + Live)
801
+
802
+ ```typescript
803
+ import { ZendFiClient } from '@zendfi/sdk';
804
+
805
+ // Test client for development
806
+ const testClient = new ZendFiClient({
807
+ apiKey: process.env.ZENDFI_TEST_API_KEY,
808
+ });
809
+
810
+ // Live client for production
811
+ const liveClient = new ZendFiClient({
812
+ apiKey: process.env.ZENDFI_LIVE_API_KEY,
813
+ });
814
+
815
+ // Use the appropriate client based on environment
816
+ const client = process.env.NODE_ENV === 'production' ? liveClient : testClient;
817
+ ```
818
+
819
+ ---
820
+
821
+ ## Error Handling
822
+
823
+ The SDK throws typed errors that you can catch and handle appropriately:
824
+
825
+ ```typescript
826
+ import {
827
+ ZendFiError,
828
+ AuthenticationError,
829
+ ValidationError,
830
+ PaymentError,
831
+ NetworkError,
832
+ RateLimitError,
833
+ } from '@zendfi/sdk';
834
+
835
+ try {
836
+ const payment = await zendfi.createPayment({
837
+ amount: 50,
838
+ description: 'Test',
839
+ });
840
+ } catch (error) {
841
+ if (error instanceof AuthenticationError) {
842
+ // Invalid API key
843
+ console.error('Authentication failed. Check your API key.');
844
+ } else if (error instanceof ValidationError) {
845
+ // Invalid request data
846
+ console.error('Validation error:', error.message);
847
+ } else if (error instanceof PaymentError) {
848
+ // Payment-specific error
849
+ console.error('Payment failed:', error.message);
850
+ } else if (error instanceof NetworkError) {
851
+ // Network/timeout error
852
+ console.error('Network error. Retrying...');
853
+ // SDK auto-retries by default
854
+ } else if (error instanceof RateLimitError) {
855
+ // Rate limit exceeded
856
+ console.error('Rate limit hit. Please slow down.');
857
+ } else {
858
+ // Generic error
859
+ console.error('Unexpected error:', error);
860
+ }
861
+ }
862
+ ```
863
+
864
+ ### Error Types
865
+
866
+ | Error Type | When It Happens | How to Handle |
867
+ |------------|----------------|---------------|
868
+ | `AuthenticationError` | Invalid API key | Check your API key |
869
+ | `ValidationError` | Invalid request data | Fix request parameters |
870
+ | `PaymentError` | Payment processing failed | Show user-friendly message |
871
+ | `NetworkError` | Network/timeout issues | Retry automatically (SDK does this) |
872
+ | `RateLimitError` | Too many requests | Implement exponential backoff |
873
+ | `ApiError` | Generic API error | Log and investigate |
874
+
875
+ ---
876
+
877
+ ## Testing
878
+
879
+ ### Using Test Mode
880
+
881
+ ```typescript
882
+ // .env.local
883
+ ZENDFI_API_KEY=zfi_test_your_test_key
884
+
885
+ // Your code
886
+ const payment = await zendfi.createPayment({
887
+ amount: 100,
888
+ description: 'Test payment',
889
+ });
890
+
891
+ // Payment created on Solana devnet (free test SOL)
892
+ console.log(payment.mode); // "test"
893
+ ```
894
+
895
+ ### Getting Test SOL
896
+
897
+ 1. Go to [sol-faucet.com](https://www.sol-faucet.com/)
898
+ 2. Paste your Solana wallet address
899
+ 3. Click "Airdrop" to get free devnet SOL
900
+ 4. Use this wallet for testing payments
901
+
902
+ ### Test Payment Flow
903
+
904
+ ```typescript
905
+ // 1. Create payment
906
+ const payment = await zendfi.createPayment({
907
+ amount: 10,
908
+ description: 'Test $10 payment',
909
+ });
910
+
911
+ // 2. Open payment URL in browser (or send to customer)
912
+ console.log('Pay here:', payment.payment_url);
913
+
914
+ // 3. Customer pays with devnet SOL/USDC
915
+
916
+ // 4. Check status
917
+ const updated = await zendfi.getPayment(payment.id);
918
+ console.log('Status:', updated.status); // "Confirmed"
919
+
920
+ // 5. Webhook fires automatically
921
+ // Your webhook handler receives 'payment.confirmed' event
922
+ ```
923
+
924
+ ---
925
+
926
+ ## Examples
927
+
928
+ ### E-commerce Checkout
929
+
930
+ ```typescript
931
+ // 1. Customer adds items to cart
932
+ const cart = {
933
+ items: [
934
+ { name: 'T-Shirt', price: 25 },
935
+ { name: 'Hoodie', price: 45 },
936
+ ],
937
+ total: 70,
938
+ };
939
+
940
+ // 2. Create payment
941
+ const payment = await zendfi.createPayment({
942
+ amount: cart.total,
943
+ description: `Order: ${cart.items.map(i => i.name).join(', ')}`,
944
+ customer_email: user.email,
945
+ redirect_url: 'https://yourstore.com/orders/success',
946
+ metadata: {
947
+ cart_id: cart.id,
948
+ user_id: user.id,
949
+ items: cart.items,
950
+ },
951
+ });
952
+
953
+ // 3. Redirect to checkout
954
+ window.location.href = payment.payment_url;
955
+
956
+ // 4. Handle webhook (payment.confirmed)
957
+ // - Mark order as paid
958
+ // - Send confirmation email
959
+ // - Trigger fulfillment
960
+ ```
961
+
962
+ ### SaaS Subscription
963
+
964
+ ```typescript
965
+ // 1. Create subscription plan (one-time setup)
966
+ const plan = await zendfi.createSubscriptionPlan({
967
+ name: 'Pro Plan',
968
+ amount: 29.99,
969
+ interval: 'monthly',
970
+ trial_days: 14,
971
+ });
972
+
973
+ // 2. Subscribe user
974
+ const subscription = await zendfi.createSubscription({
975
+ plan_id: plan.id,
976
+ customer_email: user.email,
977
+ customer_wallet: user.wallet,
978
+ metadata: {
979
+ user_id: user.id,
980
+ },
981
+ });
982
+
983
+ // 3. Handle webhooks
984
+ // - subscription.activated → Grant access
985
+ // - subscription.payment_failed → Send reminder
986
+ // - subscription.canceled → Revoke access
987
+ ```
988
+
989
+ ### Marketplace Escrow
990
+
991
+ ```typescript
992
+ // 1. Buyer purchases from seller
993
+ const escrow = await zendfi.createEscrow({
994
+ amount: 500,
995
+ buyer_email: buyer.email,
996
+ seller_email: seller.email,
997
+ buyer_wallet: buyer.wallet,
998
+ seller_wallet: seller.wallet,
999
+ description: 'Freelance project milestone',
1000
+ metadata: {
1001
+ project_id: project.id,
1002
+ milestone: 'design-complete',
1003
+ },
1004
+ });
1005
+
1006
+ // 2. Buyer pays into escrow
1007
+ // Funds held securely
1008
+
1009
+ // 3. When work is delivered:
1010
+ await zendfi.approveEscrow(escrow.id, {
1011
+ approved_by: buyer.email,
1012
+ });
1013
+ // Funds released to seller
1014
+
1015
+ // OR if there's an issue:
1016
+ await zendfi.disputeEscrow(escrow.id, {
1017
+ dispute_reason: 'Work incomplete',
1018
+ raised_by: buyer.email,
1019
+ });
1020
+ // ZendFi team mediates
1021
+ ```
1022
+
1023
+ ---
1024
+
1025
+ ## Troubleshooting
1026
+
1027
+ ### "Authentication failed" error
1028
+
1029
+ **Problem:** Invalid API key
1030
+
1031
+ **Solution:**
1032
+ ```bash
1033
+ # Check your .env file
1034
+ cat .env | grep ZENDFI_API_KEY
1035
+
1036
+ # Make sure it starts with zfi_test_ or zfi_live_
1037
+ # Get fresh API key from: https://dashboard.zendfi.tech
1038
+ ```
1039
+
1040
+ ### Webhook signature verification fails
1041
+
1042
+ **Problem:** Body parser consuming raw request body
1043
+
1044
+ **Solutions:**
1045
+
1046
+ **Next.js App Router:** No action needed ✅
1047
+
1048
+ **Next.js Pages Router:**
1049
+ ```typescript
1050
+ export const config = {
1051
+ api: { bodyParser: false }, // Add this!
1052
+ };
1053
+ ```
1054
+
1055
+ **Express:**
1056
+ ```typescript
1057
+ app.post(
1058
+ '/webhooks',
1059
+ express.raw({ type: 'application/json' }), // Use raw() not json()
1060
+ webhookHandler
1061
+ );
1062
+ ```
1063
+
1064
+ ### "Payment not found" error
1065
+
1066
+ **Problem:** Using test API key to query live payment (or vice versa)
1067
+
1068
+ **Solution:** Make sure your API key mode matches the payment's mode:
1069
+ - Test payments: use `zfi_test_` key
1070
+ - Live payments: use `zfi_live_` key
1071
+
1072
+ ### Payments stuck in "Pending"
1073
+
1074
+ **Possible causes:**
1075
+ 1. Customer hasn't paid yet
1076
+ 2. Insufficient funds in customer wallet
1077
+ 3. Transaction failed on-chain
1078
+
1079
+ **Debug:**
1080
+ ```typescript
1081
+ const payment = await zendfi.getPayment(payment_id);
1082
+ console.log('Status:', payment.status);
1083
+ console.log('Expires:', payment.expires_at);
1084
+
1085
+ // Payments expire after 15 minutes
1086
+ // Check if it expired before customer paid
1087
+ ```
1088
+
1089
+ ### TypeScript errors with imports
1090
+
1091
+ **Problem:** Module resolution issues
1092
+
1093
+ **Solution:**
1094
+ ```json
1095
+ // tsconfig.json
1096
+ {
1097
+ "compilerOptions": {
1098
+ "moduleResolution": "bundler", // or "node16"
1099
+ "esModuleInterop": true,
1100
+ "allowSyntheticDefaultImports": true
1101
+ }
1102
+ }
1103
+ ```
1104
+
1105
+ ---
1106
+
1107
+ ## Contributing
1108
+
1109
+ We welcome contributions! Here's how to get started:
1110
+
1111
+ ```bash
1112
+ # Clone the repo
1113
+ git clone https://github.com/zendfi/zendfi-toolkit.git
1114
+ cd zendfi-toolkit
1115
+
1116
+ # Install dependencies
1117
+ pnpm install
1118
+
1119
+ # Build the SDK
1120
+ cd packages/sdk
1121
+ pnpm build
1122
+
1123
+ # Run tests (if available)
1124
+ pnpm test
1125
+
1126
+ # Make your changes, then open a PR!
1127
+ ```
1128
+
1129
+ ---
1130
+
1131
+ ## Resources
1132
+
1133
+ - **Documentation:** [docs.zendfi.tech](https://docs.zendfi.tech)
1134
+ - **API Reference:** [docs.zendfi.tech/api](https://docs.zendfi.tech/api)
1135
+ - **Dashboard:** [dashboard.zendfi.tech](https://dashboard.zendfi.tech)
1136
+ - **GitHub:** [github.com/zendfi/zendfi-toolkit](https://github.com/zendfi/zendfi-toolkit)
1137
+ - **Discord:** [discord.gg/zendfi](https://discord.gg/zendfi)
1138
+ - **Email:** dev@zendfi.tech
1139
+
1140
+ ---
1141
+
1142
+ ## License
1143
+
1144
+ MIT © ZendFi
1145
+
1146
+ ---
1147
+
1148
+ ## Support
1149
+
1150
+ Need help? We're here for you!
1151
+
1152
+ - **Discord:** [discord.gg/zendfi](https://discord.gg/zendfi)
1153
+ - **Email:** dev@zendfi.tech
1154
+ - **Bug Reports:** [GitHub Issues](https://github.com/zendfi/zendfi-toolkit/issues)
1155
+ - **Docs:** [docs.zendfi.tech](https://docs.zendfi.tech)
1156
+
1157
+ ---
1158
+
1159
+ **Built with ❤️ by the ZendFi team**
1160
+
1161
+ *Making crypto payments as easy as traditional payments.*