@zendfi/sdk 0.3.1 → 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
@@ -1,17 +1,29 @@
1
1
  # @zendfi/sdk
2
2
 
3
- > Zero-config TypeScript SDK for ZendFi crypto payments
3
+ > Zero-config TypeScript SDK for accepting crypto payments with ZendFi
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@zendfi/sdk.svg)](https://www.npmjs.com/package/@zendfi/sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
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
+
8
12
  ## Features
9
13
 
10
- - **Zero Configuration** — Auto-detects environment and works with sensible defaults
11
- - **Type-Safe** — Full TypeScript types for payments, payment links, subscriptions, escrows, invoices and webhook payloads
12
- - **Auto-Retry** — Built-in retry logic with exponential backoff for network/server errors
13
- - **Idempotency** — Automatic idempotency key generation for safe retries
14
- - **Webhook Helpers** — Auto-verified handlers with optional deduplication and framework adapters
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
+ ---
15
27
 
16
28
  ## Installation
17
29
 
@@ -19,161 +31,639 @@
19
31
  npm install @zendfi/sdk
20
32
  # or
21
33
  pnpm add @zendfi/sdk
34
+ # or
35
+ yarn add @zendfi/sdk
22
36
  ```
23
37
 
38
+ ---
39
+
24
40
  ## Quick Start
25
41
 
26
- ### 1. Set your API key
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
27
47
 
28
48
  ```bash
29
49
  # .env.local or .env
30
50
 
31
- # For development (uses Solana devnet)
32
- ZENDFI_API_KEY=zfi_test_your_test_api_key_here
51
+ # For testing (free devnet SOL, no real money)
52
+ ZENDFI_API_KEY=zfi_test_your_test_key_here
33
53
 
34
- # For production (uses Solana mainnet)
35
- # ZENDFI_API_KEY=zfi_live_your_live_api_key_here
54
+ # For production (real crypto on mainnet)
55
+ # ZENDFI_API_KEY=zfi_live_your_live_key_here
36
56
  ```
37
57
 
38
- **API Key Modes:**
39
- - `zfi_test_*` keys route to **Solana Devnet** (free test SOL, no real money)
40
- - `zfi_live_*` keys route to **Solana Mainnet** (real crypto transactions)
41
-
42
- The SDK automatically detects the mode from your API key prefix.
43
-
44
- ### 2. Create a payment
58
+ ### 3. Create your first payment
45
59
 
46
60
  ```typescript
47
61
  import { zendfi } from '@zendfi/sdk';
48
62
 
49
- // Singleton auto-configured from environment
63
+ // That's it! Auto-configured from ZENDFI_API_KEY
50
64
  const payment = await zendfi.createPayment({
51
65
  amount: 50,
52
66
  description: 'Premium subscription',
67
+ customer_email: 'customer@example.com',
53
68
  });
54
69
 
55
- console.log(payment.checkout_url); // Send this to your customer
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
+ }
56
87
  ```
57
88
 
58
- ### 3. Or use an explicit client (recommended for server code)
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:
59
142
 
60
143
  ```typescript
61
- import { ZendFiClient } from '@zendfi/sdk';
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(...)
62
161
 
63
- const client = new ZendFiClient({
64
- apiKey: process.env.ZENDFI_API_KEY
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,
65
178
  });
66
179
 
67
- const payment = await client.createPayment({
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({
68
230
  amount: 99.99,
69
- currency: 'USD',
70
- description: 'Annual subscription',
231
+ description: 'Premium subscription',
232
+ capture_method: 'automatic', // or 'manual' for auth-only
71
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);
72
246
  ```
73
247
 
74
- That's it! The SDK handles everything else automatically. 🎉
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
75
254
 
76
- ## Test vs Live Mode
255
+ ### PPP Pricing (Purchasing Power Parity)
77
256
 
78
- ZendFi provides separate API keys for testing and production:
257
+ Automatically adjust prices based on customer location:
79
258
 
80
- | Mode | API Key Prefix | Network | Purpose |
81
- |------|---------------|---------|---------|
82
- | **Test** | `zfi_test_` | Solana Devnet | Development & testing with fake SOL |
83
- | **Live** | `zfi_live_` | Solana Mainnet | Production with real crypto |
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
+ ```
84
287
 
85
- ### Getting Test SOL
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.
86
290
 
87
- For testing on devnet:
88
- 1. Use your `zfi_test_` API key
89
- 2. Get free devnet SOL from [sol-faucet.com](https://www.sol-faucet.com/) or `solana airdrop`
90
- 3. All transactions use test tokens (no real value)
291
+ ### Autonomous Delegation
91
292
 
92
- ### Going Live
293
+ Enable agents to make payments without per-transaction approval:
93
294
 
94
- When ready for production:
95
- 1. Switch to your `zfi_live_` API key
96
- 2. All transactions will use real crypto on mainnet
97
- 3. Customers pay with real SOL/USDC/USDT
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
+ ```
98
312
 
99
- The SDK automatically routes to the correct network based on your API key prefix!
313
+ ### Smart Payments
100
314
 
101
- ## Usage
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
102
355
 
103
356
  ### Payments
104
357
 
105
- #### Create a Payment
358
+ #### Create Payment
106
359
 
107
360
  ```typescript
108
361
  const payment = await zendfi.createPayment({
109
362
  amount: 99.99,
110
363
  currency: 'USD', // Optional, defaults to 'USD'
111
- token: 'USDC', // Optional, defaults to 'USDC'
364
+ token: 'USDC', // 'SOL', 'USDC', or 'USDT'
112
365
  description: 'Annual subscription',
113
366
  customer_email: 'customer@example.com',
114
367
  redirect_url: 'https://yourapp.com/success',
115
368
  metadata: {
116
369
  orderId: 'ORD-123',
117
- customerId: 'CUST-456',
370
+ userId: 'USR-456',
371
+ tier: 'premium',
118
372
  },
119
373
  });
120
374
 
121
- // Redirect customer to checkout
122
- window.location.href = payment.checkout_url;
375
+ // Redirect customer to payment page
376
+ window.location.href = payment.payment_url;
123
377
  ```
124
378
 
125
- ### Payment Links
379
+ #### Get Payment Status
126
380
 
127
381
  ```typescript
128
- const client = new ZendFiClient({
129
- apiKey: process.env.ZENDFI_API_KEY
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',
130
397
  });
131
398
 
132
- // Create a payment link
133
- const link = await client.createPaymentLink({
134
- amount: 20,
135
- currency: 'USD',
136
- description: 'Product purchase',
399
+ console.log(`Found ${payments.pagination.total} payments`);
400
+ payments.data.forEach(payment => {
401
+ console.log(`${payment.id}: $${payment.amount} - ${payment.status}`);
137
402
  });
403
+ ```
138
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
139
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
+ });
140
568
 
141
- // List all payment links
142
- const links = await client.listPaymentLinks();
143
- console.log(`Total links: ${links.length}`);
569
+ console.log(`Invoice #${invoice.invoice_number} created`);
144
570
  ```
145
571
 
146
- > **Note:** If you get a 405 error when calling `listPaymentLinks()` in tests, confirm that `ZENDFI_API_URL` / `baseURL` and your API key point to a server that exposes `GET /api/v1/payment-links`.
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
+ ---
147
590
 
148
591
  ## Webhooks
149
592
 
150
- The SDK includes robust webhook processing with signature verification, optional deduplication, and typed handler dispatch.
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
+ ```
151
618
 
152
619
  ### Next.js App Router (Recommended)
153
620
 
154
621
  ```typescript
155
622
  // app/api/webhooks/zendfi/route.ts
156
- import { createNextWebhookHandler } from '@zendfi/sdk/next';
623
+ import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
157
624
 
158
625
  export const POST = createNextWebhookHandler({
159
626
  secret: process.env.ZENDFI_WEBHOOK_SECRET!,
160
627
  handlers: {
161
628
  'payment.confirmed': async (payment) => {
162
- // Payment is already verified and typed
629
+ // Payment is verified and typed!
630
+ console.log(`💰 Payment confirmed: $${payment.amount}`);
631
+
632
+ // Update your database
163
633
  await db.orders.update({
164
634
  where: { id: payment.metadata.orderId },
165
- data: { status: 'paid' },
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',
166
646
  });
167
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
+ },
168
658
  },
169
659
  });
170
660
  ```
171
661
 
172
- ### Next.js Pages Router (Legacy)
662
+ ### Next.js Pages Router
173
663
 
174
664
  ```typescript
175
665
  // pages/api/webhooks/zendfi.ts
176
- import { createPagesWebhookHandler } from '@zendfi/sdk/next';
666
+ import { createPagesWebhookHandler } from '@zendfi/sdk/nextjs';
177
667
 
178
668
  export default createPagesWebhookHandler({
179
669
  secret: process.env.ZENDFI_WEBHOOK_SECRET!,
@@ -184,7 +674,7 @@ export default createPagesWebhookHandler({
184
674
  },
185
675
  });
186
676
 
187
- // Important: Disable body parser for signature verification
677
+ // IMPORTANT: Disable body parser for webhook signature verification
188
678
  export const config = {
189
679
  api: { bodyParser: false },
190
680
  };
@@ -200,127 +690,472 @@ const app = express();
200
690
 
201
691
  app.post(
202
692
  '/api/webhooks/zendfi',
203
- express.raw({ type: 'application/json' }), // Preserve raw body
693
+ express.raw({ type: 'application/json' }), // Preserve raw body!
204
694
  createExpressWebhookHandler({
205
695
  secret: process.env.ZENDFI_WEBHOOK_SECRET!,
206
696
  handlers: {
207
697
  'payment.confirmed': async (payment) => {
208
- // Handle confirmed payment
698
+ console.log('Payment confirmed:', payment.id);
209
699
  },
210
700
  },
211
701
  })
212
702
  );
213
703
  ```
214
704
 
215
- ### Webhook Deduplication
705
+ ### Webhook Deduplication (Production)
216
706
 
217
- By default, the handler uses an in-memory Set for deduplication (suitable for development). For production, supply `isProcessed` and `onProcessed` hooks (or the aliases `checkDuplicate` / `markProcessed`) backed by Redis or your database:
707
+ The handlers use in-memory deduplication by default (fine for development). For production, use Redis or your database:
218
708
 
219
709
  ```typescript
710
+ import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
711
+ import { redis } from './lib/redis';
712
+
220
713
  export const POST = createNextWebhookHandler({
221
714
  secret: process.env.ZENDFI_WEBHOOK_SECRET!,
715
+
716
+ // Check if webhook was already processed
222
717
  isProcessed: async (eventId) => {
223
- return await redis.exists(`webhook:${eventId}`);
718
+ const exists = await redis.exists(`webhook:${eventId}`);
719
+ return exists === 1;
224
720
  },
721
+
722
+ // Mark webhook as processed
225
723
  onProcessed: async (eventId) => {
226
- await redis.set(`webhook:${eventId}`, '1', 'EX', 86400);
724
+ await redis.set(`webhook:${eventId}`, '1', 'EX', 86400); // 24h TTL
227
725
  },
726
+
228
727
  handlers: {
229
728
  'payment.confirmed': async (payment) => {
230
- // Handle payment
729
+ // This will only run once, even if webhook retries
730
+ await processPayment(payment);
231
731
  },
232
732
  },
233
733
  });
234
734
  ```
235
735
 
236
- When deduplication is enabled, duplicate requests are rejected with HTTP 409.
237
-
238
736
  ### Manual Webhook Verification
239
737
 
240
- If you prefer manual verification:
738
+ For custom implementations:
241
739
 
242
740
  ```typescript
243
741
  import { verifyNextWebhook } from '@zendfi/sdk/webhooks';
244
742
 
245
743
  export async function POST(request: Request) {
246
- const payload = await verifyNextWebhook(request);
744
+ const payload = await verifyNextWebhook(request, process.env.ZENDFI_WEBHOOK_SECRET);
745
+
247
746
  if (!payload) {
248
747
  return new Response('Invalid signature', { status: 401 });
249
748
  }
250
749
 
251
750
  // Handle verified payload
751
+ if (payload.event === 'payment.confirmed') {
752
+ await handlePayment(payload.data);
753
+ }
754
+
252
755
  return new Response('OK');
253
756
  }
254
757
  ```
255
758
 
256
- **Available helpers:**
759
+ **Available verifiers:**
257
760
  - `verifyNextWebhook(request, secret?)` — Next.js App Router
258
761
  - `verifyExpressWebhook(req, secret?)` — Express
259
- - `verifyWebhookSignature(payload, signature, secret)` — Low-level verifier
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
+ });
260
809
 
261
- ## Configuration & Environment Variables
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
+ ```
262
818
 
263
- | Variable | Required | Description |
264
- |----------|----------|-------------|
265
- | `ZENDFI_API_KEY` | Yes* | Your ZendFi API key (*unless passed to `ZendFiClient`) |
266
- | `ZENDFI_WEBHOOK_SECRET` | No | Used by webhook adapters for auto-verification |
267
- | `ZENDFI_API_URL` | No | Override API base URL (useful for local testing) |
268
- | `ZENDFI_ENVIRONMENT` | No | Optional environment override |
819
+ ---
269
820
 
270
821
  ## Error Handling
271
822
 
272
- The SDK throws typed errors that you can import and check with `instanceof`:
823
+ The SDK throws typed errors that you can catch and handle appropriately:
273
824
 
274
825
  ```typescript
275
826
  import {
827
+ ZendFiError,
276
828
  AuthenticationError,
277
- ValidationError,
278
- NetworkError
829
+ ValidationError,
830
+ PaymentError,
831
+ NetworkError,
832
+ RateLimitError,
279
833
  } from '@zendfi/sdk';
280
834
 
281
835
  try {
282
- await zendfi.createPayment({ amount: 50 });
836
+ const payment = await zendfi.createPayment({
837
+ amount: 50,
838
+ description: 'Test',
839
+ });
283
840
  } catch (error) {
284
841
  if (error instanceof AuthenticationError) {
285
- // Handle authentication error
842
+ // Invalid API key
843
+ console.error('Authentication failed. Check your API key.');
286
844
  } else if (error instanceof ValidationError) {
287
- // Handle validation error
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);
288
860
  }
289
861
  }
290
862
  ```
291
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
+
292
1025
  ## Troubleshooting
293
1026
 
294
- **Webhook verification failures:**
295
- - Ensure you're using `express.raw({ type: 'application/json' })` for Express
296
- - For Next.js Pages Router, set `export const config = { api: { bodyParser: false } }`
297
- - Middleware consuming the raw body will break signature verification
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
298
1043
 
299
- **405 errors on `listPaymentLinks()`:**
300
- - Verify your `ZENDFI_API_URL` is correct
301
- - Confirm your API server exposes `GET /api/v1/payment-links`
1044
+ **Solutions:**
302
1045
 
303
- **tsup warnings about types condition:**
304
- - This is a packaging order issue that doesn't affect runtime behavior
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
+ ---
305
1106
 
306
1107
  ## Contributing
307
1108
 
308
- Run the SDK build and tests locally before opening a PR:
1109
+ We welcome contributions! Here's how to get started:
309
1110
 
310
1111
  ```bash
311
- cd packages/sdk
1112
+ # Clone the repo
1113
+ git clone https://github.com/zendfi/zendfi-toolkit.git
1114
+ cd zendfi-toolkit
1115
+
1116
+ # Install dependencies
312
1117
  pnpm install
313
- pnpm run build
1118
+
1119
+ # Build the SDK
1120
+ cd packages/sdk
1121
+ pnpm build
1122
+
1123
+ # Run tests (if available)
314
1124
  pnpm test
1125
+
1126
+ # Make your changes, then open a PR!
315
1127
  ```
316
1128
 
317
- ## Support
1129
+ ---
318
1130
 
319
- - **Documentation:** https://docs.zendfi.tech
320
- - **API Reference:** https://docs.zendfi.tech/api
321
- - **GitHub Issues:** https://github.com/zendfi/zendfi-toolkit/issues
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)
322
1138
  - **Email:** dev@zendfi.tech
323
1139
 
1140
+ ---
1141
+
324
1142
  ## License
325
1143
 
326
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.*