@drip-sdk/node 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Drip Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,603 @@
1
+ # @drip-sdk/node
2
+
3
+ The official Node.js SDK for **Drip** - Usage-based billing for AI agents.
4
+
5
+ Drip enables real-time, per-request billing using USDC on blockchain. Perfect for AI APIs, compute platforms, and any service with variable usage patterns.
6
+
7
+ [![npm version](https://badge.fury.io/js/@drip-sdk/node.svg)](https://www.npmjs.com/package/@drip-sdk/node)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @drip-sdk/node
14
+ ```
15
+
16
+ ```bash
17
+ yarn add @drip-sdk/node
18
+ ```
19
+
20
+ ```bash
21
+ pnpm add @drip-sdk/node
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### One-Liner Integration (Recommended)
27
+
28
+ The fastest way to add billing to your API:
29
+
30
+ #### Next.js App Router
31
+
32
+ ```typescript
33
+ // app/api/generate/route.ts
34
+ import { withDrip } from '@drip-sdk/node/next';
35
+
36
+ export const POST = withDrip({
37
+ meter: 'api_calls',
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
+ });
44
+ ```
45
+
46
+ #### Express
47
+
48
+ ```typescript
49
+ import express from 'express';
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
+ }));
58
+
59
+ app.post('/api/paid/generate', (req, res) => {
60
+ console.log(`Charged: ${req.drip.charge.charge.amountUsdc} USDC`);
61
+ res.json({ success: true });
62
+ });
63
+ ```
64
+
65
+ ### Manual Integration
66
+
67
+ For more control, use the SDK directly:
68
+
69
+ ```typescript
70
+ import { Drip } from '@drip-sdk/node';
71
+
72
+ // Initialize the client
73
+ const drip = new Drip({
74
+ apiKey: process.env.DRIP_API_KEY!,
75
+ });
76
+
77
+ // Create a customer
78
+ const customer = await drip.createCustomer({
79
+ onchainAddress: '0x1234567890abcdef...',
80
+ externalCustomerId: 'user_123',
81
+ });
82
+
83
+ // Record usage and charge
84
+ const result = await drip.charge({
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}`);
92
+ ```
93
+
94
+ ## Configuration
95
+
96
+ ```typescript
97
+ const drip = new Drip({
98
+ // Required: Your Drip API key
99
+ apiKey: 'drip_live_abc123...',
100
+
101
+ // Optional: API base URL (for staging/development)
102
+ baseUrl: 'https://api.drip.dev/v1',
103
+
104
+ // Optional: Request timeout in milliseconds (default: 30000)
105
+ timeout: 30000,
106
+ });
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### Customer Management
112
+
113
+ #### Create a Customer
114
+
115
+ ```typescript
116
+ const customer = await drip.createCustomer({
117
+ onchainAddress: '0x1234567890abcdef...',
118
+ externalCustomerId: 'user_123', // Your internal user ID
119
+ metadata: { plan: 'pro' },
120
+ });
121
+ ```
122
+
123
+ #### Get a Customer
124
+
125
+ ```typescript
126
+ const customer = await drip.getCustomer('cust_abc123');
127
+ ```
128
+
129
+ #### List Customers
130
+
131
+ ```typescript
132
+ // List all customers
133
+ const { data: customers } = await drip.listCustomers();
134
+
135
+ // With filters
136
+ const { data: activeCustomers } = await drip.listCustomers({
137
+ status: 'ACTIVE',
138
+ limit: 50,
139
+ });
140
+ ```
141
+
142
+ #### Get Customer Balance
143
+
144
+ ```typescript
145
+ const balance = await drip.getBalance('cust_abc123');
146
+ console.log(`Balance: ${balance.balanceUSDC} USDC`);
147
+ ```
148
+
149
+ ### Meters (Usage Types)
150
+
151
+ #### List Available Meters
152
+
153
+ Discover what meter names are valid for charging. Meters are defined by your pricing plans.
154
+
155
+ ```typescript
156
+ const { data: meters } = await drip.listMeters();
157
+
158
+ console.log('Available meters:');
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
+ ```
167
+
168
+ ### Charging & Usage
169
+
170
+ #### Record Usage and Charge
171
+
172
+ ```typescript
173
+ const result = await drip.charge({
174
+ customerId: 'cust_abc123',
175
+ meter: 'api_calls',
176
+ quantity: 100,
177
+ idempotencyKey: 'req_unique_123', // Prevents duplicate charges
178
+ metadata: { endpoint: '/v1/chat' },
179
+ });
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
+ ```
187
+
188
+ #### Get Charge Details
189
+
190
+ ```typescript
191
+ const charge = await drip.getCharge('chg_abc123');
192
+ console.log(`Status: ${charge.status}`);
193
+ ```
194
+
195
+ #### List Charges
196
+
197
+ ```typescript
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
+ ```
208
+
209
+ #### Check Charge Status
210
+
211
+ ```typescript
212
+ const status = await drip.getChargeStatus('chg_abc123');
213
+ if (status.status === 'CONFIRMED') {
214
+ console.log('Charge confirmed on-chain!');
215
+ }
216
+ ```
217
+
218
+ ### Run Tracking (Simplified API)
219
+
220
+ Track agent executions with a single API call instead of multiple separate calls.
221
+
222
+ #### Record a Complete Run
223
+
224
+ The `recordRun()` method combines workflow creation, run tracking, event emission, and completion into one call:
225
+
226
+ ```typescript
227
+ // Before: 4+ separate API calls
228
+ const workflow = await drip.createWorkflow({ name: 'My Agent', slug: 'my_agent' });
229
+ const run = await drip.startRun({ customerId, workflowId: workflow.id });
230
+ await drip.emitEvent({ runId: run.id, eventType: 'step1', ... });
231
+ await drip.emitEvent({ runId: run.id, eventType: 'step2', ... });
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
+ });
246
+
247
+ console.log(result.summary);
248
+ // Output: "✓ My Agent: 4 events recorded (250ms)"
249
+ ```
250
+
251
+ #### Record a Failed Run
252
+
253
+ ```typescript
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
+ });
265
+
266
+ console.log(result.summary);
267
+ // Output: "✗ Prescription Intake: 2 events recorded (150ms)"
268
+ ```
269
+
270
+ ### Webhooks
271
+
272
+ #### Create a Webhook
273
+
274
+ ```typescript
275
+ const webhook = await drip.createWebhook({
276
+ url: 'https://api.yourapp.com/webhooks/drip',
277
+ events: ['charge.succeeded', 'charge.failed', 'customer.balance.low'],
278
+ description: 'Main webhook endpoint',
279
+ });
280
+
281
+ // IMPORTANT: Save the secret securely!
282
+ console.log(`Webhook secret: ${webhook.secret}`);
283
+ ```
284
+
285
+ #### List Webhooks
286
+
287
+ ```typescript
288
+ const { data: webhooks } = await drip.listWebhooks();
289
+ webhooks.forEach((wh) => {
290
+ console.log(`${wh.url}: ${wh.stats?.successfulDeliveries} successful`);
291
+ });
292
+ ```
293
+
294
+ #### Delete a Webhook
295
+
296
+ ```typescript
297
+ await drip.deleteWebhook('wh_abc123');
298
+ ```
299
+
300
+ #### Verify Webhook Signatures
301
+
302
+ ```typescript
303
+ import express from 'express';
304
+ 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
+ ```
340
+
341
+ ### Available Webhook Events
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
358
+
359
+ The SDK is written in TypeScript and includes full type definitions.
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
+ ```
389
+
390
+ ## Error Handling
391
+
392
+ The SDK throws `DripError` for API errors:
393
+
394
+ ```typescript
395
+ import { Drip, DripError } from '@drip-sdk/node';
396
+
397
+ try {
398
+ const result = await drip.charge({
399
+ customerId: 'cust_abc123',
400
+ meter: 'api_calls',
401
+ quantity: 100,
402
+ });
403
+ } catch (error) {
404
+ if (error instanceof DripError) {
405
+ console.error(`API Error: ${error.message}`);
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
+ }
420
+ }
421
+ }
422
+ ```
423
+
424
+ ### Common Error Codes
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
+ ```
467
+
468
+ ## Requirements
469
+
470
+ - 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
+
598
+ ## Links
599
+
600
+ - [GitHub Repository](https://github.com/MichaelLevin5908/drip-sdk)
601
+ - [Issue Tracker](https://github.com/MichaelLevin5908/drip-sdk/issues)
602
+ - [npm Package](https://www.npmjs.com/package/@drip-sdk/node)
603
+ - [Documentation](https://docs.drip.dev)
@@ -0,0 +1,3 @@
1
+ 'use strict';var crypto=require('crypto');var f=class t extends Error{constructor(r,s,n){super(r);this.statusCode=s;this.code=n;this.name="DripError",Object.setPrototypeOf(this,t.prototype);}},x=class{apiKey;baseUrl;timeout;constructor(e){if(!e.apiKey)throw new Error("Drip API key is required");this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://api.drip.dev/v1",this.timeout=e.timeout||3e4;}async request(e,r={}){let s=new AbortController,n=setTimeout(()=>s.abort(),this.timeout);try{let i=await fetch(`${this.baseUrl}${e}`,{...r,signal:s.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...r.headers}});if(i.status===204)return {success:!0};let o=await i.json();if(!i.ok)throw new f(o.message||o.error||"Request failed",i.status,o.code);return o}catch(i){throw i instanceof f?i:i instanceof Error&&i.name==="AbortError"?new f("Request timed out",408,"TIMEOUT"):new f(i instanceof Error?i.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(n);}}async createCustomer(e){return this.request("/customers",{method:"POST",body:JSON.stringify(e)})}async getCustomer(e){return this.request(`/customers/${e}`)}async listCustomers(e){let r=new URLSearchParams;e?.limit&&r.set("limit",e.limit.toString()),e?.status&&r.set("status",e.status);let s=r.toString(),n=s?`/customers?${s}`:"/customers";return this.request(n)}async getBalance(e){return this.request(`/customers/${e}/balance`)}async charge(e){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,metadata:e.metadata})})}async getCharge(e){return this.request(`/charges/${e}`)}async listCharges(e){let r=new URLSearchParams;e?.customerId&&r.set("customerId",e.customerId),e?.status&&r.set("status",e.status),e?.limit&&r.set("limit",e.limit.toString()),e?.offset&&r.set("offset",e.offset.toString());let s=r.toString(),n=s?`/charges?${s}`:"/charges";return this.request(n)}async getChargeStatus(e){return this.request(`/charges/${e}/status`)}async checkout(e){let r=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:e.customerId,external_customer_id:e.externalCustomerId,amount:e.amount,return_url:e.returnUrl,cancel_url:e.cancelUrl,metadata:e.metadata})});return {id:r.id,url:r.url,expiresAt:r.expires_at,amountUsd:r.amount_usd}}async createWebhook(e){return this.request("/webhooks",{method:"POST",body:JSON.stringify(e)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(e){return this.request(`/webhooks/${e}`)}async deleteWebhook(e){return this.request(`/webhooks/${e}`,{method:"DELETE"})}async testWebhook(e){return this.request(`/webhooks/${e}/test`,{method:"POST"})}async rotateWebhookSecret(e){return this.request(`/webhooks/${e}/rotate-secret`,{method:"POST"})}async createWorkflow(e){return this.request("/workflows",{method:"POST",body:JSON.stringify(e)})}async listWorkflows(){return this.request("/workflows")}async startRun(e){return this.request("/runs",{method:"POST",body:JSON.stringify(e)})}async endRun(e,r){return this.request(`/runs/${e}`,{method:"PATCH",body:JSON.stringify(r)})}async getRunTimeline(e){return this.request(`/runs/${e}`)}async emitEvent(e){return this.request("/events",{method:"POST",body:JSON.stringify(e)})}async emitEventsBatch(e){return this.request("/events/batch",{method:"POST",body:JSON.stringify({events:e})})}async listMeters(){let e=await this.request("/pricing-plans");return {data:e.data.map(r=>({id:r.id,name:r.name,meter:r.unitType,unitPriceUsd:r.unitPriceUsd,isActive:r.isActive})),count:e.count}}async recordRun(e){let r=Date.now(),s=e.workflow,n=e.workflow;if(!e.workflow.startsWith("wf_"))try{let y=(await this.listWorkflows()).data.find(d=>d.slug===e.workflow||d.id===e.workflow);if(y)s=y.id,n=y.name;else {let d=await this.createWorkflow({name:e.workflow.replace(/[_-]/g," ").replace(/\b\w/g,E=>E.toUpperCase()),slug:e.workflow,productSurface:"AGENT"});s=d.id,n=d.name;}}catch{s=e.workflow;}let i=await this.startRun({customerId:e.customerId,workflowId:s,externalRunId:e.externalRunId,correlationId:e.correlationId,metadata:e.metadata}),o=0,c=0;if(e.events.length>0){let l=e.events.map((d,E)=>({runId:i.id,eventType:d.eventType,quantity:d.quantity,units:d.units,description:d.description,costUnits:d.costUnits,metadata:d.metadata,idempotencyKey:e.externalRunId?`${e.externalRunId}:${d.eventType}:${E}`:void 0})),y=await this.emitEventsBatch(l);o=y.created,c=y.duplicates;}let m=await this.endRun(i.id,{status:e.status,errorMessage:e.errorMessage,errorCode:e.errorCode}),g=Date.now()-r,u=e.events.length>0?`${o} events recorded`:"no events",h=`${e.status==="COMPLETED"?"\u2713":e.status==="FAILED"?"\u2717":"\u25CB"} ${n}: ${u} (${m.durationMs??g}ms)`;return {run:{id:i.id,workflowId:s,workflowName:n,status:e.status,durationMs:m.durationMs},events:{created:o,duplicates:c},totalCostUnits:m.totalCostUnits,summary:h}}static generateIdempotencyKey(e){let r=[e.customerId,e.runId??"no_run",e.stepName,String(e.sequence??0)],s=0,n=r.join("|");for(let i=0;i<n.length;i++){let o=n.charCodeAt(i);s=(s<<5)-s+o,s=s&s;}return `drip_${Math.abs(s).toString(36)}_${e.stepName.slice(0,16)}`}static verifyWebhookSignature(e,r,s){let n=new TextEncoder,i=n.encode(e),o=n.encode(s),c=0;for(let g=0;g<i.length;g++)c=(c<<5)-c+i[g]+o[g%o.length]|0;let m=Math.abs(c).toString(16);return r===m||r.includes(m)}};var p=class t extends Error{constructor(r,s,n,i){super(r);this.code=s;this.statusCode=n;this.details=i;this.name="DripMiddlewareError",Object.setPrototypeOf(this,t.prototype);}};var v=300,A=300,S=["x-payment-signature","x-payment-session-key","x-payment-smart-account","x-payment-timestamp","x-payment-amount","x-payment-recipient","x-payment-usage-id","x-payment-nonce"];function N(t){return t.toLowerCase()}function R(t,e){let r=N(e);if(t[r]!==void 0){let s=t[r];return Array.isArray(s)?s[0]:s}for(let[s,n]of Object.entries(t))if(s.toLowerCase()===r)return Array.isArray(n)?n[0]:n}function P(t){return S.every(e=>R(t,e)!==void 0)}function O(t){let e=R(t,"x-payment-signature"),r=R(t,"x-payment-session-key"),s=R(t,"x-payment-smart-account"),n=R(t,"x-payment-timestamp"),i=R(t,"x-payment-amount"),o=R(t,"x-payment-recipient"),c=R(t,"x-payment-usage-id"),m=R(t,"x-payment-nonce");if(!e||!r||!s||!n||!i||!o||!c||!m)return null;let g=parseInt(n,10);if(isNaN(g)||Math.floor(Date.now()/1e3)-g>A)return null;let a=(h,l)=>{if(!h.startsWith("0x"))return false;let y=h.slice(2);return y.length<l?false:/^[a-fA-F0-9]+$/.test(y)};return !a(e,130)||!a(r,64)||!a(s,40)?null:{signature:e,sessionKeyId:r,smartAccount:s,timestamp:g,amount:i,recipient:o,usageId:c,nonce:m}}function U(t){let e=Math.floor(Date.now()/1e3),r=e+(t.expiresInSec??v),s=`${e}-${crypto.randomBytes(16).toString("hex")}`,n=t.usageId;n.startsWith("0x")||(n=b(n));let i={"X-Payment-Required":"true","X-Payment-Amount":t.amount,"X-Payment-Recipient":t.recipient,"X-Payment-Usage-Id":n,"X-Payment-Description":t.description??"API usage charge","X-Payment-Expires":String(r),"X-Payment-Nonce":s,"X-Payment-Timestamp":String(e)},o={amount:t.amount,recipient:t.recipient,usageId:n,description:t.description??"API usage charge",expiresAt:r,nonce:s,timestamp:e};return {headers:i,paymentRequest:o}}function b(t){let e=5381,r=52711;for(let i=0;i<t.length;i++){let o=t.charCodeAt(i);e=(e<<5)+e^o,r=(r<<5)+r^o;}return `0x${Math.abs(e*31+r).toString(16).padStart(16,"0").slice(0,16).padEnd(64,"0")}`}async function M(t,e){let r=e.customerResolver??"header";if(typeof r=="function")return r(t);if(r==="header"){let s=R(t.headers,"x-drip-customer-id")??R(t.headers,"x-customer-id");if(!s)throw new p("Missing customer ID. Include X-Drip-Customer-Id header.","CUSTOMER_RESOLUTION_FAILED",400);return s}if(r==="query"){let s=t.query??{},n=s.drip_customer_id??s.customer_id,i=Array.isArray(n)?n[0]:n;if(!i)throw new p("Missing customer ID. Include drip_customer_id query parameter.","CUSTOMER_RESOLUTION_FAILED",400);return i}throw new p(`Invalid customer resolver: ${r}`,"CONFIGURATION_ERROR",500)}async function _(t,e){return typeof e.quantity=="function"?e.quantity(t):e.quantity}async function W(t,e,r){if(r.idempotencyKey)return r.idempotencyKey(t);let s=Date.now(),n=[t.method,t.url,e,s];return `drip_${b(n.join("|")).slice(2,18)}`}function C(t){let e=t.apiKey??process.env.DRIP_API_KEY;if(!e)throw new p("Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.","CONFIGURATION_ERROR",500);return new x({apiKey:e,baseUrl:t.baseUrl??process.env.DRIP_API_URL})}async function I(t,e){if(e.skipInDevelopment&&process.env.NODE_ENV==="development"){console.warn("[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.");let r=C(e),s={success:true,usageEventId:"dev_usage_event",isReplay:false,charge:{id:"dev_charge",amountUsdc:"0.00",amountToken:"0",txHash:"0x0",status:"CONFIRMED"}};return {success:true,state:{customerId:"dev_customer",quantity:typeof e.quantity=="number"?e.quantity:1,idempotencyKey:"dev_idempotency",hasPaymentProof:false},charge:s,drip:r,isReplay:false}}try{let r=C(e),s=await M(t,e),n=await _(t,e),i=await W(t,s,e),o=P(t.headers),c=o?O(t.headers):void 0,m={customerId:s,quantity:n,idempotencyKey:i,hasPaymentProof:o,paymentProof:c??void 0},g=typeof e.metadata=="function"?e.metadata(t):e.metadata;try{let u=await r.charge({customerId:s,meter:e.meter,quantity:n,idempotencyKey:i,metadata:g});return e.onCharge&&await e.onCharge(u,t),{success:!0,state:m,charge:u,drip:r,isReplay:u.isReplay??!1}}catch(u){if(u instanceof f){if(u.statusCode===402){let a=process.env.DRIP_RECIPIENT_ADDRESS;if(!a)throw new p("DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.","CONFIGURATION_ERROR",500);let h="0.01",l=u.message.match(/amount[:\s]+([0-9.]+)/i);l?h=l[1]:h=(n*1e-4).toFixed(6);let{headers:y,paymentRequest:d}=U({amount:h,recipient:a,usageId:i,description:`${e.meter} usage charge`});return {success:!1,error:new p("Insufficient balance. Payment required.","PAYMENT_REQUIRED",402),paymentRequired:{headers:y,paymentRequest:d}}}throw e.onError&&await e.onError(u,t),new p(u.message,"CHARGE_FAILED",u.statusCode,{code:u.code})}throw u}}catch(r){if(r instanceof p)return {success:false,error:r};let s=r instanceof Error?r.message:"Unknown error";return {success:false,error:new p(s,"INTERNAL_ERROR",500)}}}function q(t){let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=Array.isArray(s)?s[0]:s;return e}function L(t,e,r){t.status(402).set(e).json({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:r,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}});}function X(t,e,r,s,n){t.status(s).json({error:e,code:r,...n&&{details:n}});}function D(t){let e=t.attachToRequest??true;return async(r,s,n)=>{let i={method:r.method,url:r.originalUrl||r.url,headers:q(r.headers),query:r.query},o=typeof t.quantity=="function"?await t.quantity(r):t.quantity,c;if(typeof t.customerResolver=="function"){let l=t.customerResolver;c=async()=>l(r);}else c=t.customerResolver;let m;if(typeof t.idempotencyKey=="function"){let l=t.idempotencyKey;m=async()=>l(r);}let g=typeof t.metadata=="function"?t.metadata(r):t.metadata,u={meter:t.meter,quantity:o,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:c,idempotencyKey:m,metadata:g,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},a=await I(i,u);if(!a.success){if(t.errorHandler&&await t.errorHandler(a.error,r,s))return;if(a.paymentRequired){L(s,a.paymentRequired.headers,a.paymentRequired.paymentRequest);return}X(s,a.error.message,a.error.code,a.error.statusCode,a.error.details);return}t.onCharge&&await t.onCharge(a.charge,r);let h={drip:a.drip,customerId:a.state.customerId,charge:a.charge,isReplay:a.isReplay};e&&(r.drip=h),n();}}function K(t){return e=>D({...t,...e})}function $(t){return P(q(t.headers))}function k(t){return "drip"in t&&typeof t.drip=="object"}function F(t){if(!k(t))throw new Error("Drip context not found on request. Ensure dripMiddleware is applied before this route.");return t.drip}
2
+ exports.Drip=x;exports.DripError=f;exports.DripMiddlewareError=p;exports.createDripMiddleware=K;exports.dripMiddleware=D;exports.getDripContext=F;exports.hasDripContext=k;exports.hasPaymentProofHeaders=$;//# sourceMappingURL=express.cjs.map
3
+ //# sourceMappingURL=express.cjs.map