@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 +1161 -0
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.d.mts +1490 -3
- package/dist/index.d.ts +1490 -3
- package/dist/index.js +2539 -411
- package/dist/index.mjs +2501 -401
- package/dist/nextjs.d.mts +1 -1
- package/dist/nextjs.d.ts +1 -1
- package/dist/webhook-handler-D8wEoYd7.d.mts +869 -0
- package/dist/webhook-handler-D8wEoYd7.d.ts +869 -0
- package/package.json +21 -1
- package/dist/webhook-handler-B9ZczHQn.d.mts +0 -373
- package/dist/webhook-handler-B9ZczHQn.d.ts +0 -373
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
|
+
[](https://www.npmjs.com/package/@zendfi/sdk)
|
|
6
|
+
[](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.*
|