@drip-sdk/node 1.0.2 → 1.0.3
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 +155 -494
- package/dist/core.cjs +3 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +762 -0
- package/dist/core.d.ts +760 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/express.cjs +2 -2
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +3 -3
- package/dist/express.d.ts +3 -3
- package/dist/express.js +2 -2
- package/dist/express.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1266 -66
- package/dist/index.d.ts +1266 -66
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/langchain.cjs +3 -0
- package/dist/langchain.cjs.map +1 -0
- package/dist/langchain.d.cts +290 -0
- package/dist/langchain.d.ts +290 -0
- package/dist/langchain.js +3 -0
- package/dist/langchain.js.map +1 -0
- package/dist/middleware.cjs +2 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +3 -3
- package/dist/middleware.d.ts +3 -3
- package/dist/middleware.js +2 -2
- package/dist/middleware.js.map +1 -1
- package/dist/next.cjs +2 -2
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +3 -3
- package/dist/next.d.ts +3 -3
- package/dist/next.js +2 -2
- package/dist/next.js.map +1 -1
- package/dist/{types-D8mMON4v.d.ts → types-B2qwDadD.d.ts} +1 -1
- package/dist/{types-92iVqLtE.d.cts → types-Bo8SiUdl.d.cts} +1 -1
- package/package.json +23 -1
package/README.md
CHANGED
|
@@ -1,603 +1,264 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Drip SDK (Node.js)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Drip is a lightweight SDK for **usage tracking and execution logging** in systems where cost is tied to computation — AI agents, APIs, background jobs, and infra workloads.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This **Core SDK** is designed for pilots: it records *what ran* and *how much it used*, without handling billing or balances.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**One line to start tracking:** `await drip.trackUsage({ customerId, meter, quantity })`
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@drip-sdk/node)
|
|
8
10
|
[](https://opensource.org/licenses/MIT)
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
---
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
npm install @drip-sdk/node
|
|
14
|
-
```
|
|
14
|
+
## 60-Second Quickstart (Core SDK)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
yarn add @drip-sdk/node
|
|
18
|
-
```
|
|
16
|
+
### 1. Install
|
|
19
17
|
|
|
20
18
|
```bash
|
|
21
|
-
|
|
19
|
+
npm install @drip-sdk/node
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### One-Liner Integration (Recommended)
|
|
27
|
-
|
|
28
|
-
The fastest way to add billing to your API:
|
|
22
|
+
### 2. Set your API key
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// app/api/generate/route.ts
|
|
34
|
-
import { withDrip } from '@drip-sdk/node/next';
|
|
24
|
+
```bash
|
|
25
|
+
# Secret key — full API access (server-side only, never expose publicly)
|
|
26
|
+
export DRIP_API_KEY=sk_test_...
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
quantity: 1,
|
|
39
|
-
}, async (req, { charge, customerId }) => {
|
|
40
|
-
// Your handler - payment already verified!
|
|
41
|
-
console.log(`Charged ${charge.charge.amountUsdc} USDC to ${customerId}`);
|
|
42
|
-
return Response.json({ result: 'success' });
|
|
43
|
-
});
|
|
28
|
+
# Or public key — read/write access for usage, customers, billing (safe for client-side)
|
|
29
|
+
export DRIP_API_KEY=pk_test_...
|
|
44
30
|
```
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
### 3. Track usage (one line)
|
|
47
33
|
|
|
48
34
|
```typescript
|
|
49
|
-
import
|
|
50
|
-
import { dripMiddleware } from '@drip-sdk/node/express';
|
|
51
|
-
|
|
52
|
-
const app = express();
|
|
53
|
-
|
|
54
|
-
app.use('/api/paid', dripMiddleware({
|
|
55
|
-
meter: 'api_calls',
|
|
56
|
-
quantity: 1,
|
|
57
|
-
}));
|
|
35
|
+
import { drip } from '@drip-sdk/node';
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
res.json({ success: true });
|
|
62
|
-
});
|
|
37
|
+
// Track usage - that's it
|
|
38
|
+
await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
|
|
63
39
|
```
|
|
64
40
|
|
|
65
|
-
|
|
41
|
+
The `drip` singleton reads `DRIP_API_KEY` from your environment automatically.
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
### Alternative: Explicit Configuration
|
|
68
44
|
|
|
69
45
|
```typescript
|
|
70
46
|
import { Drip } from '@drip-sdk/node';
|
|
71
47
|
|
|
72
|
-
//
|
|
73
|
-
const drip = new Drip(
|
|
74
|
-
apiKey: process.env.DRIP_API_KEY!,
|
|
75
|
-
});
|
|
48
|
+
// Auto-reads DRIP_API_KEY from environment
|
|
49
|
+
const drip = new Drip();
|
|
76
50
|
|
|
77
|
-
//
|
|
78
|
-
const
|
|
79
|
-
onchainAddress: '0x1234567890abcdef...',
|
|
80
|
-
externalCustomerId: 'user_123',
|
|
81
|
-
});
|
|
51
|
+
// Or pass config explicitly with a secret key (full access)
|
|
52
|
+
const drip = new Drip({ apiKey: 'sk_test_...' });
|
|
82
53
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
customerId: customer.id,
|
|
86
|
-
meter: 'api_calls',
|
|
87
|
-
quantity: 100,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
console.log(`Charged ${result.charge.amountUsdc} USDC`);
|
|
91
|
-
console.log(`TX: ${result.charge.txHash}`);
|
|
54
|
+
// Or with a public key (safe for client-side, limited scope)
|
|
55
|
+
const drip = new Drip({ apiKey: 'pk_test_...' });
|
|
92
56
|
```
|
|
93
57
|
|
|
94
|
-
|
|
58
|
+
### Full Example
|
|
95
59
|
|
|
96
60
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
61
|
+
import { drip } from '@drip-sdk/node';
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
// Verify connectivity
|
|
65
|
+
await drip.ping();
|
|
66
|
+
|
|
67
|
+
// Record usage
|
|
68
|
+
await drip.trackUsage({
|
|
69
|
+
customerId: 'customer_123',
|
|
70
|
+
meter: 'llm_tokens',
|
|
71
|
+
quantity: 842,
|
|
72
|
+
metadata: { model: 'gpt-4o-mini' },
|
|
73
|
+
});
|
|
100
74
|
|
|
101
|
-
//
|
|
102
|
-
|
|
75
|
+
// Record an execution lifecycle
|
|
76
|
+
await drip.recordRun({
|
|
77
|
+
customerId: 'customer_123',
|
|
78
|
+
workflow: 'research-agent',
|
|
79
|
+
events: [
|
|
80
|
+
{ eventType: 'llm.call', model: 'gpt-4', inputTokens: 500, outputTokens: 1200 },
|
|
81
|
+
{ eventType: 'tool.call', name: 'web-search', duration: 1500 },
|
|
82
|
+
],
|
|
83
|
+
status: 'COMPLETED',
|
|
84
|
+
});
|
|
103
85
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
86
|
+
console.log('Usage + run recorded');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main();
|
|
107
90
|
```
|
|
108
91
|
|
|
109
|
-
|
|
92
|
+
**Expected result:**
|
|
93
|
+
- No errors
|
|
94
|
+
- Events appear in your Drip dashboard within seconds
|
|
110
95
|
|
|
111
|
-
|
|
96
|
+
---
|
|
112
97
|
|
|
113
|
-
|
|
98
|
+
## Core Concepts (2-minute mental model)
|
|
114
99
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```
|
|
100
|
+
| Concept | Description |
|
|
101
|
+
|---------|-------------|
|
|
102
|
+
| `customerId` | The end user, API key, or account you're attributing usage to |
|
|
103
|
+
| `meter` | What you're measuring (tokens, requests, seconds, rows, etc.) |
|
|
104
|
+
| `quantity` | Numeric usage for that meter |
|
|
105
|
+
| `run` | A single execution or request lifecycle (success / failure / duration) |
|
|
122
106
|
|
|
123
|
-
|
|
107
|
+
**Status values:** `PENDING` | `RUNNING` | `COMPLETED` | `FAILED`
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
const customer = await drip.getCustomer('cust_abc123');
|
|
127
|
-
```
|
|
109
|
+
**Event schema:** Payloads are schema-flexible. Drip stores events as structured JSON and does not enforce a fixed event taxonomy.
|
|
128
110
|
|
|
129
|
-
|
|
111
|
+
Drip is append-only and idempotent-friendly. You can safely retry events.
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
// List all customers
|
|
133
|
-
const { data: customers } = await drip.listCustomers();
|
|
113
|
+
---
|
|
134
114
|
|
|
135
|
-
|
|
136
|
-
const { data: activeCustomers } = await drip.listCustomers({
|
|
137
|
-
status: 'ACTIVE',
|
|
138
|
-
limit: 50,
|
|
139
|
-
});
|
|
140
|
-
```
|
|
115
|
+
## Idempotency Keys
|
|
141
116
|
|
|
142
|
-
|
|
117
|
+
Every mutating SDK method (`charge`, `trackUsage`, `emitEvent`) accepts an optional `idempotencyKey` parameter. The server uses this key to deduplicate requests — if two requests share the same key, only the first is processed.
|
|
143
118
|
|
|
144
|
-
|
|
145
|
-
const balance = await drip.getBalance('cust_abc123');
|
|
146
|
-
console.log(`Balance: ${balance.balanceUSDC} USDC`);
|
|
147
|
-
```
|
|
119
|
+
`recordRun` generates idempotency keys internally for its batch events (using `externalRunId` when provided, otherwise deterministic keys).
|
|
148
120
|
|
|
149
|
-
###
|
|
121
|
+
### Auto-generated keys (default)
|
|
150
122
|
|
|
151
|
-
|
|
123
|
+
When you omit `idempotencyKey`, the SDK generates one automatically. The auto key is:
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
- **Unique per call** — two separate calls with identical parameters produce different keys (a monotonic counter ensures this).
|
|
126
|
+
- **Stable across retries** — the key is generated once and reused for all retry attempts of that call, so network retries are safely deduplicated.
|
|
127
|
+
- **Deterministic** — no randomness; keys are reproducible for debugging.
|
|
154
128
|
|
|
155
|
-
|
|
156
|
-
const { data: meters } = await drip.listMeters();
|
|
129
|
+
This means you get **free retry safety** with zero configuration.
|
|
157
130
|
|
|
158
|
-
|
|
159
|
-
for (const meter of meters) {
|
|
160
|
-
console.log(` ${meter.meter}: $${meter.unitPriceUsd}/unit`);
|
|
161
|
-
}
|
|
162
|
-
// Output:
|
|
163
|
-
// api_calls: $0.001/unit
|
|
164
|
-
// tokens: $0.00001/unit
|
|
165
|
-
// compute_seconds: $0.01/unit
|
|
166
|
-
```
|
|
131
|
+
> **Note:** `wrapApiCall` generates a time-based key when no explicit `idempotencyKey` is provided. Pass your own key if you need deterministic deduplication with `wrapApiCall`.
|
|
167
132
|
|
|
168
|
-
###
|
|
133
|
+
### When to pass explicit keys
|
|
169
134
|
|
|
170
|
-
|
|
135
|
+
Pass your own `idempotencyKey` when you need **application-level deduplication** — e.g., to guarantee that a specific business operation is billed exactly once, even across process restarts:
|
|
171
136
|
|
|
172
137
|
```typescript
|
|
173
|
-
|
|
174
|
-
customerId: '
|
|
138
|
+
await drip.charge({
|
|
139
|
+
customerId: 'cust_123',
|
|
175
140
|
meter: 'api_calls',
|
|
176
|
-
quantity:
|
|
177
|
-
idempotencyKey:
|
|
178
|
-
metadata: { endpoint: '/v1/chat' },
|
|
141
|
+
quantity: 1,
|
|
142
|
+
idempotencyKey: `order_${orderId}_charge`, // your business-level key
|
|
179
143
|
});
|
|
180
|
-
|
|
181
|
-
if (result.success) {
|
|
182
|
-
console.log(`Charge ID: ${result.charge.id}`);
|
|
183
|
-
console.log(`Amount: ${result.charge.amountUsdc} USDC`);
|
|
184
|
-
console.log(`TX Hash: ${result.charge.txHash}`);
|
|
185
|
-
}
|
|
186
144
|
```
|
|
187
145
|
|
|
188
|
-
|
|
146
|
+
Common patterns:
|
|
147
|
+
- `order_${orderId}` — one charge per order
|
|
148
|
+
- `run_${runId}_step_${stepIndex}` — one charge per pipeline step
|
|
149
|
+
- `invoice_${invoiceId}` — one charge per invoice
|
|
189
150
|
|
|
190
|
-
|
|
191
|
-
const charge = await drip.getCharge('chg_abc123');
|
|
192
|
-
console.log(`Status: ${charge.status}`);
|
|
193
|
-
```
|
|
151
|
+
### StreamMeter
|
|
194
152
|
|
|
195
|
-
|
|
153
|
+
`StreamMeter` also auto-generates idempotency keys per flush. If you provide an `idempotencyKey` in the options, each flush appends a counter (`_flush_0`, `_flush_1`, etc.) to keep multi-flush scenarios safe.
|
|
196
154
|
|
|
197
|
-
|
|
198
|
-
// List all charges
|
|
199
|
-
const { data: charges } = await drip.listCharges();
|
|
200
|
-
|
|
201
|
-
// Filter by customer and status
|
|
202
|
-
const { data: customerCharges } = await drip.listCharges({
|
|
203
|
-
customerId: 'cust_abc123',
|
|
204
|
-
status: 'CONFIRMED',
|
|
205
|
-
limit: 50,
|
|
206
|
-
});
|
|
207
|
-
```
|
|
155
|
+
---
|
|
208
156
|
|
|
209
|
-
|
|
157
|
+
## API Key Types
|
|
210
158
|
|
|
211
|
-
|
|
212
|
-
const status = await drip.getChargeStatus('chg_abc123');
|
|
213
|
-
if (status.status === 'CONFIRMED') {
|
|
214
|
-
console.log('Charge confirmed on-chain!');
|
|
215
|
-
}
|
|
216
|
-
```
|
|
159
|
+
Drip issues two key types per API key pair. Each has different access scopes:
|
|
217
160
|
|
|
218
|
-
|
|
161
|
+
| Key Type | Prefix | Access | Use In |
|
|
162
|
+
|----------|--------|--------|--------|
|
|
163
|
+
| **Secret Key** | `sk_live_` / `sk_test_` | Full API access (all endpoints) | Server-side only |
|
|
164
|
+
| **Public Key** | `pk_live_` / `pk_test_` | Usage tracking, customers, billing, analytics, sessions | Client-side safe |
|
|
219
165
|
|
|
220
|
-
|
|
166
|
+
### What public keys **can** access
|
|
167
|
+
- Usage tracking (`trackUsage`, `recordRun`, `startRun`, `emitEvent`, etc.)
|
|
168
|
+
- Customer management (`createCustomer`, `getCustomer`, `listCustomers`)
|
|
169
|
+
- Billing & charges (`charge`, `getBalance`, `listCharges`, etc.)
|
|
170
|
+
- Pricing plans, sessions, analytics, usage caps, refunds
|
|
221
171
|
|
|
222
|
-
|
|
172
|
+
### What public keys **cannot** access (secret key required)
|
|
173
|
+
- Webhook management (`createWebhook`, `listWebhooks`, `deleteWebhook`, etc.)
|
|
174
|
+
- API key management (create, rotate, revoke keys)
|
|
175
|
+
- Feature flag management
|
|
223
176
|
|
|
224
|
-
The `
|
|
177
|
+
The SDK detects your key type automatically and will throw a `DripError` with code `PUBLIC_KEY_NOT_ALLOWED` (HTTP 403) if you attempt a secret-key-only operation with a public key.
|
|
225
178
|
|
|
226
179
|
```typescript
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await drip.
|
|
232
|
-
await drip.endRun(run.id, { status: 'COMPLETED' });
|
|
233
|
-
|
|
234
|
-
// After: 1 call with recordRun()
|
|
235
|
-
const result = await drip.recordRun({
|
|
236
|
-
customerId: 'cust_123',
|
|
237
|
-
workflow: 'my_agent', // Auto-creates workflow if it doesn't exist
|
|
238
|
-
events: [
|
|
239
|
-
{ eventType: 'agent.start', description: 'Started processing' },
|
|
240
|
-
{ eventType: 'tool.ocr', quantity: 3, units: 'pages', costUnits: 0.15 },
|
|
241
|
-
{ eventType: 'tool.validate', quantity: 1, costUnits: 0.05 },
|
|
242
|
-
{ eventType: 'agent.complete', description: 'Finished successfully' },
|
|
243
|
-
],
|
|
244
|
-
status: 'COMPLETED',
|
|
245
|
-
});
|
|
180
|
+
const drip = new Drip({ apiKey: 'pk_test_...' });
|
|
181
|
+
console.log(drip.keyType); // 'public'
|
|
182
|
+
|
|
183
|
+
// This works fine
|
|
184
|
+
await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
|
|
246
185
|
|
|
247
|
-
|
|
248
|
-
|
|
186
|
+
// This throws DripError(403, 'PUBLIC_KEY_NOT_ALLOWED')
|
|
187
|
+
await drip.createWebhook({ url: '...', events: ['charge.succeeded'] });
|
|
249
188
|
```
|
|
250
189
|
|
|
251
|
-
|
|
190
|
+
---
|
|
252
191
|
|
|
253
|
-
|
|
254
|
-
const result = await drip.recordRun({
|
|
255
|
-
customerId: 'cust_123',
|
|
256
|
-
workflow: 'prescription_intake',
|
|
257
|
-
events: [
|
|
258
|
-
{ eventType: 'agent.start', description: 'Started processing' },
|
|
259
|
-
{ eventType: 'error', description: 'OCR failed: image too blurry' },
|
|
260
|
-
],
|
|
261
|
-
status: 'FAILED',
|
|
262
|
-
errorMessage: 'OCR processing failed',
|
|
263
|
-
errorCode: 'OCR_QUALITY_ERROR',
|
|
264
|
-
});
|
|
192
|
+
## SDK Variants
|
|
265
193
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
194
|
+
| Variant | Description |
|
|
195
|
+
|---------|-------------|
|
|
196
|
+
| **Core SDK** (recommended for pilots) | Usage tracking + execution logging only |
|
|
197
|
+
| **Full SDK** | Includes billing, balances, and workflows (for later stages) |
|
|
269
198
|
|
|
270
|
-
|
|
199
|
+
---
|
|
271
200
|
|
|
272
|
-
|
|
201
|
+
## Core SDK Methods
|
|
273
202
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
203
|
+
| Method | Description |
|
|
204
|
+
|--------|-------------|
|
|
205
|
+
| `ping()` | Verify API connection |
|
|
206
|
+
| `createCustomer(params)` | Create a customer |
|
|
207
|
+
| `getCustomer(customerId)` | Get customer details |
|
|
208
|
+
| `listCustomers(options)` | List all customers |
|
|
209
|
+
| `trackUsage(params)` | Record metered usage |
|
|
210
|
+
| `recordRun(params)` | Log complete agent run (simplified) |
|
|
211
|
+
| `startRun(params)` | Start execution trace |
|
|
212
|
+
| `emitEvent(params)` | Log event within run |
|
|
213
|
+
| `emitEventsBatch(params)` | Batch log events |
|
|
214
|
+
| `endRun(runId, params)` | Complete execution trace |
|
|
215
|
+
| `getRunTimeline(runId)` | Get execution timeline |
|
|
280
216
|
|
|
281
|
-
|
|
282
|
-
console.log(`Webhook secret: ${webhook.secret}`);
|
|
283
|
-
```
|
|
217
|
+
---
|
|
284
218
|
|
|
285
|
-
|
|
219
|
+
## Who This Is For
|
|
286
220
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
292
|
-
```
|
|
221
|
+
- AI agents (token metering, tool calls, execution traces)
|
|
222
|
+
- API companies (per-request billing, endpoint attribution)
|
|
223
|
+
- RPC providers (multi-chain call tracking)
|
|
224
|
+
- Cloud/infra (compute seconds, storage, bandwidth)
|
|
293
225
|
|
|
294
|
-
|
|
226
|
+
---
|
|
295
227
|
|
|
296
|
-
|
|
297
|
-
await drip.deleteWebhook('wh_abc123');
|
|
298
|
-
```
|
|
228
|
+
## Full SDK (Billing, Webhooks, Integrations)
|
|
299
229
|
|
|
300
|
-
|
|
230
|
+
For billing, webhooks, middleware, and advanced features:
|
|
301
231
|
|
|
302
232
|
```typescript
|
|
303
|
-
import express from 'express';
|
|
304
233
|
import { Drip } from '@drip-sdk/node';
|
|
305
|
-
|
|
306
|
-
const app = express();
|
|
307
|
-
|
|
308
|
-
app.post(
|
|
309
|
-
'/webhooks/drip',
|
|
310
|
-
express.raw({ type: 'application/json' }),
|
|
311
|
-
(req, res) => {
|
|
312
|
-
const isValid = Drip.verifyWebhookSignature(
|
|
313
|
-
req.body.toString(),
|
|
314
|
-
req.headers['x-drip-signature'] as string,
|
|
315
|
-
process.env.DRIP_WEBHOOK_SECRET!,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
if (!isValid) {
|
|
319
|
-
return res.status(401).send('Invalid signature');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const event = JSON.parse(req.body.toString());
|
|
323
|
-
|
|
324
|
-
switch (event.type) {
|
|
325
|
-
case 'charge.succeeded':
|
|
326
|
-
console.log('Charge succeeded:', event.data.charge_id);
|
|
327
|
-
break;
|
|
328
|
-
case 'charge.failed':
|
|
329
|
-
console.log('Charge failed:', event.data.failure_reason);
|
|
330
|
-
break;
|
|
331
|
-
case 'customer.balance.low':
|
|
332
|
-
console.log('Low balance alert for:', event.data.customer_id);
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
res.status(200).send('OK');
|
|
337
|
-
},
|
|
338
|
-
);
|
|
339
234
|
```
|
|
340
235
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
| Event | Description |
|
|
344
|
-
| ---------------------------- | ------------------------------------ |
|
|
345
|
-
| `charge.succeeded` | Charge confirmed on-chain |
|
|
346
|
-
| `charge.failed` | Charge failed |
|
|
347
|
-
| `customer.balance.low` | Customer balance below threshold |
|
|
348
|
-
| `customer.deposit.confirmed` | Deposit confirmed on-chain |
|
|
349
|
-
| `customer.withdraw.confirmed`| Withdrawal confirmed |
|
|
350
|
-
| `customer.usage_cap.reached` | Usage cap hit |
|
|
351
|
-
| `customer.created` | New customer created |
|
|
352
|
-
| `usage.recorded` | Usage event recorded |
|
|
353
|
-
| `transaction.created` | Transaction initiated |
|
|
354
|
-
| `transaction.confirmed` | Transaction confirmed on-chain |
|
|
355
|
-
| `transaction.failed` | Transaction failed |
|
|
356
|
-
|
|
357
|
-
## TypeScript Usage
|
|
236
|
+
See **[FULL_SDK.md](./FULL_SDK.md)** for complete documentation.
|
|
358
237
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
import {
|
|
363
|
-
Drip,
|
|
364
|
-
DripConfig,
|
|
365
|
-
DripError,
|
|
366
|
-
Customer,
|
|
367
|
-
Charge,
|
|
368
|
-
ChargeResult,
|
|
369
|
-
ChargeStatus,
|
|
370
|
-
Webhook,
|
|
371
|
-
WebhookEventType,
|
|
372
|
-
} from '@drip-sdk/node';
|
|
373
|
-
|
|
374
|
-
// All types are available for use
|
|
375
|
-
const config: DripConfig = {
|
|
376
|
-
apiKey: process.env.DRIP_API_KEY!,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const drip = new Drip(config);
|
|
380
|
-
|
|
381
|
-
// Type-safe responses
|
|
382
|
-
const customer: Customer = await drip.getCustomer('cust_abc123');
|
|
383
|
-
const result: ChargeResult = await drip.charge({
|
|
384
|
-
customerId: customer.id,
|
|
385
|
-
meter: 'api_calls',
|
|
386
|
-
quantity: 100,
|
|
387
|
-
});
|
|
388
|
-
```
|
|
238
|
+
---
|
|
389
239
|
|
|
390
240
|
## Error Handling
|
|
391
241
|
|
|
392
|
-
The SDK throws `DripError` for API errors:
|
|
393
|
-
|
|
394
242
|
```typescript
|
|
395
243
|
import { Drip, DripError } from '@drip-sdk/node';
|
|
396
244
|
|
|
397
245
|
try {
|
|
398
|
-
|
|
399
|
-
customerId: 'cust_abc123',
|
|
400
|
-
meter: 'api_calls',
|
|
401
|
-
quantity: 100,
|
|
402
|
-
});
|
|
246
|
+
await drip.trackUsage({ ... });
|
|
403
247
|
} catch (error) {
|
|
404
248
|
if (error instanceof DripError) {
|
|
405
|
-
console.error(`
|
|
406
|
-
console.error(`Status Code: ${error.statusCode}`);
|
|
407
|
-
console.error(`Error Code: ${error.code}`);
|
|
408
|
-
|
|
409
|
-
switch (error.code) {
|
|
410
|
-
case 'INSUFFICIENT_BALANCE':
|
|
411
|
-
// Handle low balance
|
|
412
|
-
break;
|
|
413
|
-
case 'CUSTOMER_NOT_FOUND':
|
|
414
|
-
// Handle missing customer
|
|
415
|
-
break;
|
|
416
|
-
case 'RATE_LIMITED':
|
|
417
|
-
// Handle rate limiting
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
249
|
+
console.error(`Error: ${error.message} (${error.code})`);
|
|
420
250
|
}
|
|
421
251
|
}
|
|
422
252
|
```
|
|
423
253
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
| Code | Description |
|
|
427
|
-
| ---------------------- | ---------------------------------------- |
|
|
428
|
-
| `INSUFFICIENT_BALANCE` | Customer doesn't have enough balance |
|
|
429
|
-
| `CUSTOMER_NOT_FOUND` | Customer ID doesn't exist |
|
|
430
|
-
| `DUPLICATE_CUSTOMER` | Customer already exists |
|
|
431
|
-
| `INVALID_API_KEY` | API key is invalid or revoked |
|
|
432
|
-
| `RATE_LIMITED` | Too many requests |
|
|
433
|
-
| `TIMEOUT` | Request timed out |
|
|
434
|
-
|
|
435
|
-
## Idempotency
|
|
436
|
-
|
|
437
|
-
Use idempotency keys to safely retry requests:
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
const result = await drip.charge({
|
|
441
|
-
customerId: 'cust_abc123',
|
|
442
|
-
meter: 'api_calls',
|
|
443
|
-
quantity: 100,
|
|
444
|
-
idempotencyKey: `req_${requestId}`, // Unique per request
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// Retrying with the same key returns the original result
|
|
448
|
-
const retry = await drip.charge({
|
|
449
|
-
customerId: 'cust_abc123',
|
|
450
|
-
meter: 'api_calls',
|
|
451
|
-
quantity: 100,
|
|
452
|
-
idempotencyKey: `req_${requestId}`, // Same key = same result
|
|
453
|
-
});
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
## CommonJS Usage
|
|
457
|
-
|
|
458
|
-
The SDK supports both ESM and CommonJS:
|
|
459
|
-
|
|
460
|
-
```javascript
|
|
461
|
-
// ESM
|
|
462
|
-
import { Drip } from '@drip-sdk/node';
|
|
463
|
-
|
|
464
|
-
// CommonJS
|
|
465
|
-
const { Drip } = require('@drip-sdk/node');
|
|
466
|
-
```
|
|
254
|
+
---
|
|
467
255
|
|
|
468
256
|
## Requirements
|
|
469
257
|
|
|
470
258
|
- Node.js 18.0.0 or higher
|
|
471
|
-
- Native `fetch` support (included in Node.js 18+)
|
|
472
|
-
|
|
473
|
-
## Middleware Reference (withDrip)
|
|
474
|
-
|
|
475
|
-
### Configuration Options
|
|
476
|
-
|
|
477
|
-
| Option | Type | Default | Description |
|
|
478
|
-
|--------|------|---------|-------------|
|
|
479
|
-
| `meter` | `string` | **required** | Usage meter to charge (must match pricing plan) |
|
|
480
|
-
| `quantity` | `number \| (req) => number` | **required** | Quantity to charge (static or dynamic) |
|
|
481
|
-
| `apiKey` | `string` | `DRIP_API_KEY` | Drip API key |
|
|
482
|
-
| `baseUrl` | `string` | `DRIP_API_URL` | Drip API base URL |
|
|
483
|
-
| `customerResolver` | `'header' \| 'query' \| function` | `'header'` | How to identify customers |
|
|
484
|
-
| `skipInDevelopment` | `boolean` | `false` | Skip charging in dev mode |
|
|
485
|
-
| `metadata` | `object \| function` | `undefined` | Custom metadata for charges |
|
|
486
|
-
| `onCharge` | `function` | `undefined` | Callback after successful charge |
|
|
487
|
-
| `onError` | `function` | `undefined` | Custom error handler |
|
|
488
|
-
|
|
489
|
-
### How It Works
|
|
490
|
-
|
|
491
|
-
```
|
|
492
|
-
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
493
|
-
│ Your API │ │ withDrip │ │ Drip Backend │
|
|
494
|
-
│ (Next/Express)│───▶│ Middleware │───▶│ API │
|
|
495
|
-
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
496
|
-
│ │ │
|
|
497
|
-
▼ ▼ ▼
|
|
498
|
-
1. Request 2. Resolve 3. Check balance
|
|
499
|
-
arrives customer & charge
|
|
500
|
-
│ │ │
|
|
501
|
-
▼ ▼ ▼
|
|
502
|
-
6. Response 5. Pass to 4. Return result
|
|
503
|
-
returned handler or 402
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### x402 Payment Flow
|
|
507
|
-
|
|
508
|
-
When a customer has insufficient balance, the middleware returns `402 Payment Required`:
|
|
509
|
-
|
|
510
|
-
```
|
|
511
|
-
HTTP/1.1 402 Payment Required
|
|
512
|
-
X-Payment-Required: true
|
|
513
|
-
X-Payment-Amount: 0.01
|
|
514
|
-
X-Payment-Recipient: 0x...
|
|
515
|
-
X-Payment-Usage-Id: 0x...
|
|
516
|
-
X-Payment-Expires: 1704110400
|
|
517
|
-
X-Payment-Nonce: abc123
|
|
518
|
-
|
|
519
|
-
{
|
|
520
|
-
"error": "Payment required",
|
|
521
|
-
"code": "PAYMENT_REQUIRED",
|
|
522
|
-
"paymentRequest": { ... },
|
|
523
|
-
"instructions": {
|
|
524
|
-
"step1": "Sign the payment request with your session key using EIP-712",
|
|
525
|
-
"step2": "Retry the request with X-Payment-* headers"
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### Advanced Usage
|
|
531
|
-
|
|
532
|
-
#### Dynamic Quantity
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
export const POST = withDrip({
|
|
536
|
-
meter: 'tokens',
|
|
537
|
-
quantity: async (req) => {
|
|
538
|
-
const body = await req.json();
|
|
539
|
-
return body.maxTokens ?? 100;
|
|
540
|
-
},
|
|
541
|
-
}, handler);
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
#### Custom Customer Resolution
|
|
545
|
-
|
|
546
|
-
```typescript
|
|
547
|
-
export const POST = withDrip({
|
|
548
|
-
meter: 'api_calls',
|
|
549
|
-
quantity: 1,
|
|
550
|
-
customerResolver: (req) => {
|
|
551
|
-
const token = req.headers.get('authorization')?.split(' ')[1];
|
|
552
|
-
return decodeJWT(token).customerId;
|
|
553
|
-
},
|
|
554
|
-
}, handler);
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
#### Factory Pattern
|
|
558
|
-
|
|
559
|
-
```typescript
|
|
560
|
-
// lib/drip.ts
|
|
561
|
-
import { createWithDrip } from '@drip-sdk/node/next';
|
|
562
|
-
|
|
563
|
-
export const withDrip = createWithDrip({
|
|
564
|
-
apiKey: process.env.DRIP_API_KEY,
|
|
565
|
-
baseUrl: process.env.DRIP_API_URL,
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// app/api/generate/route.ts
|
|
569
|
-
import { withDrip } from '@/lib/drip';
|
|
570
|
-
export const POST = withDrip({ meter: 'api_calls', quantity: 1 }, handler);
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
### What's Included vs. Missing
|
|
574
|
-
|
|
575
|
-
| Feature | Status | Description |
|
|
576
|
-
|---------|--------|-------------|
|
|
577
|
-
| Next.js App Router | ✅ | `withDrip` wrapper |
|
|
578
|
-
| Express Middleware | ✅ | `dripMiddleware` |
|
|
579
|
-
| x402 Payment Flow | ✅ | Automatic 402 handling |
|
|
580
|
-
| Dynamic Quantity | ✅ | Function-based pricing |
|
|
581
|
-
| Customer Resolution | ✅ | Header, query, or custom |
|
|
582
|
-
| Idempotency | ✅ | Built-in or custom keys |
|
|
583
|
-
| Dev Mode Skip | ✅ | Skip in development |
|
|
584
|
-
| Metadata | ✅ | Attach to charges |
|
|
585
|
-
| TypeScript | ✅ | Full type definitions |
|
|
586
|
-
| Fastify Adapter | ❌ | Coming soon |
|
|
587
|
-
| Rate Limiting | ❌ | Planned |
|
|
588
|
-
| Balance Caching | ❌ | Planned |
|
|
589
|
-
|
|
590
|
-
## Contributing
|
|
591
|
-
|
|
592
|
-
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
593
|
-
|
|
594
|
-
## License
|
|
595
|
-
|
|
596
|
-
MIT - see [LICENSE](./LICENSE)
|
|
597
259
|
|
|
598
260
|
## Links
|
|
599
261
|
|
|
600
|
-
- [
|
|
601
|
-
- [
|
|
602
|
-
- [npm
|
|
603
|
-
- [Documentation](https://docs.drip.dev)
|
|
262
|
+
- [Full SDK Documentation](./FULL_SDK.md)
|
|
263
|
+
- [API Documentation](https://drippay.dev/api-reference)
|
|
264
|
+
- [npm](https://www.npmjs.com/package/@drip-sdk/node)
|