@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 +947 -112
- package/README.md.old +326 -0
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.d.mts +1653 -4
- package/dist/index.d.ts +1653 -4
- package/dist/index.js +2805 -412
- package/dist/index.mjs +2771 -413
- 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-DG-zic8m.d.mts +0 -390
- package/dist/webhook-handler-DG-zic8m.d.ts +0 -390
package/README.md
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
# @zendfi/sdk
|
|
2
2
|
|
|
3
|
-
> Zero-config TypeScript SDK for
|
|
3
|
+
> Zero-config TypeScript SDK for accepting crypto payments with ZendFi
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@zendfi/sdk)
|
|
6
6
|
[](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
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
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.
|
|
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
|
|
32
|
-
ZENDFI_API_KEY=
|
|
51
|
+
# For testing (free devnet SOL, no real money)
|
|
52
|
+
ZENDFI_API_KEY=zfi_test_your_test_key_here
|
|
33
53
|
|
|
34
|
-
# For production (
|
|
35
|
-
# ZENDFI_API_KEY=
|
|
54
|
+
# For production (real crypto on mainnet)
|
|
55
|
+
# ZENDFI_API_KEY=zfi_live_your_live_key_here
|
|
36
56
|
```
|
|
37
57
|
|
|
38
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
+
### PPP Pricing (Purchasing Power Parity)
|
|
77
256
|
|
|
78
|
-
|
|
257
|
+
Automatically adjust prices based on customer location:
|
|
79
258
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
+
Enable agents to make payments without per-transaction approval:
|
|
93
294
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
313
|
+
### Smart Payments
|
|
100
314
|
|
|
101
|
-
|
|
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
|
|
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', //
|
|
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
|
-
|
|
370
|
+
userId: 'USR-456',
|
|
371
|
+
tier: 'premium',
|
|
118
372
|
},
|
|
119
373
|
});
|
|
120
374
|
|
|
121
|
-
// Redirect customer to
|
|
122
|
-
window.location.href = payment.
|
|
375
|
+
// Redirect customer to payment page
|
|
376
|
+
window.location.href = payment.payment_url;
|
|
123
377
|
```
|
|
124
378
|
|
|
125
|
-
|
|
379
|
+
#### Get Payment Status
|
|
126
380
|
|
|
127
381
|
```typescript
|
|
128
|
-
const
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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: {
|
|
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
|
|
662
|
+
### Next.js Pages Router
|
|
173
663
|
|
|
174
664
|
```typescript
|
|
175
665
|
// pages/api/webhooks/zendfi.ts
|
|
176
|
-
import { createPagesWebhookHandler } from '@zendfi/sdk/
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
829
|
+
ValidationError,
|
|
830
|
+
PaymentError,
|
|
831
|
+
NetworkError,
|
|
832
|
+
RateLimitError,
|
|
279
833
|
} from '@zendfi/sdk';
|
|
280
834
|
|
|
281
835
|
try {
|
|
282
|
-
await zendfi.createPayment({
|
|
836
|
+
const payment = await zendfi.createPayment({
|
|
837
|
+
amount: 50,
|
|
838
|
+
description: 'Test',
|
|
839
|
+
});
|
|
283
840
|
} catch (error) {
|
|
284
841
|
if (error instanceof AuthenticationError) {
|
|
285
|
-
//
|
|
842
|
+
// Invalid API key
|
|
843
|
+
console.error('Authentication failed. Check your API key.');
|
|
286
844
|
} else if (error instanceof ValidationError) {
|
|
287
|
-
//
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
**
|
|
300
|
-
- Verify your `ZENDFI_API_URL` is correct
|
|
301
|
-
- Confirm your API server exposes `GET /api/v1/payment-links`
|
|
1044
|
+
**Solutions:**
|
|
302
1045
|
|
|
303
|
-
**
|
|
304
|
-
|
|
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
|
-
|
|
1109
|
+
We welcome contributions! Here's how to get started:
|
|
309
1110
|
|
|
310
1111
|
```bash
|
|
311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1129
|
+
---
|
|
318
1130
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
- **
|
|
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.*
|