@apitoll/seller-sdk 0.1.0-beta.1
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 +131 -0
- package/dist/analytics.d.ts +62 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +157 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analytics.test.d.ts +2 -0
- package/dist/analytics.test.d.ts.map +1 -0
- package/dist/analytics.test.js +222 -0
- package/dist/analytics.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware-express.d.ts +43 -0
- package/dist/middleware-express.d.ts.map +1 -0
- package/dist/middleware-express.js +239 -0
- package/dist/middleware-express.js.map +1 -0
- package/dist/middleware-hono.d.ts +30 -0
- package/dist/middleware-hono.d.ts.map +1 -0
- package/dist/middleware-hono.js +192 -0
- package/dist/middleware-hono.js.map +1 -0
- package/dist/payment.d.ts +42 -0
- package/dist/payment.d.ts.map +1 -0
- package/dist/payment.js +130 -0
- package/dist/payment.js.map +1 -0
- package/dist/payment.test.d.ts +2 -0
- package/dist/payment.test.d.ts.map +1 -0
- package/dist/payment.test.js +221 -0
- package/dist/payment.test.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @apitoll/seller-sdk
|
|
2
|
+
|
|
3
|
+
Monetize any API with x402 micropayments. Add per-request USDC payments in 3 lines of code.
|
|
4
|
+
|
|
5
|
+
Built on the [x402 HTTP Payment Protocol](https://www.x402.org/) — settled instantly on Base.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @apitoll/seller-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start (Express)
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import express from "express";
|
|
17
|
+
import { paymentMiddleware } from "@apitoll/seller-sdk";
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
|
|
21
|
+
app.use(paymentMiddleware({
|
|
22
|
+
walletAddress: "0xYourWallet...",
|
|
23
|
+
endpoints: {
|
|
24
|
+
"GET /api/data": {
|
|
25
|
+
price: "0.001",
|
|
26
|
+
chains: ["base"],
|
|
27
|
+
description: "Premium data feed",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
app.get("/api/data", (req, res) => {
|
|
33
|
+
res.json({ data: "You paid for this!" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
app.listen(3000);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start (Hono)
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { Hono } from "hono";
|
|
43
|
+
import { honoPaymentMiddleware } from "@apitoll/seller-sdk";
|
|
44
|
+
|
|
45
|
+
const app = new Hono();
|
|
46
|
+
|
|
47
|
+
app.use("*", honoPaymentMiddleware({
|
|
48
|
+
walletAddress: "0xYourWallet...",
|
|
49
|
+
endpoints: {
|
|
50
|
+
"GET /api/joke": {
|
|
51
|
+
price: "0.001",
|
|
52
|
+
chains: ["base"],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
1. Agent requests your API endpoint
|
|
61
|
+
2. Middleware returns `402 Payment Required` with USDC price and chain info
|
|
62
|
+
3. Agent pays via the x402 facilitator
|
|
63
|
+
4. Agent retries with `X-PAYMENT` header containing the signed payment
|
|
64
|
+
5. Middleware verifies payment and lets the request through
|
|
65
|
+
6. You get paid instantly in USDC on Base
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- **Express & Hono** middleware out of the box
|
|
70
|
+
- **Per-endpoint pricing** — different prices for different routes
|
|
71
|
+
- **Multi-chain** — Base and Solana support
|
|
72
|
+
- **Platform fees** — automatic fee splitting (3% default)
|
|
73
|
+
- **Analytics** — built-in transaction reporting to the API Toll dashboard
|
|
74
|
+
- **Rate limiting** — optional Redis-backed rate limiting
|
|
75
|
+
- **Circuit breaker** — automatic protection against payment verification failures
|
|
76
|
+
|
|
77
|
+
## Platform Fee Splitting
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
app.use(paymentMiddleware({
|
|
81
|
+
walletAddress: "0xYourWallet...",
|
|
82
|
+
endpoints: { ... },
|
|
83
|
+
platformFee: {
|
|
84
|
+
feeBps: 300, // 3%
|
|
85
|
+
feeCollector: "0xPlatformWallet...",
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Analytics Reporting
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { AnalyticsReporter } from "@apitoll/seller-sdk";
|
|
94
|
+
|
|
95
|
+
const reporter = new AnalyticsReporter({
|
|
96
|
+
apiKey: "your-seller-api-key",
|
|
97
|
+
dashboardUrl: "https://cheery-parrot-104.convex.cloud",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Transactions are automatically batched and reported
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### `paymentMiddleware(config)`
|
|
106
|
+
Express middleware. Intercepts requests, returns 402 for unpaid endpoints, verifies payments.
|
|
107
|
+
|
|
108
|
+
### `honoPaymentMiddleware(config)`
|
|
109
|
+
Same as above, but for Hono framework.
|
|
110
|
+
|
|
111
|
+
### `buildPaymentRequirements(endpoint, config)`
|
|
112
|
+
Build x402-compliant payment requirement objects.
|
|
113
|
+
|
|
114
|
+
### `verifyPayment(header, requirements)`
|
|
115
|
+
Verify a signed payment header against requirements.
|
|
116
|
+
|
|
117
|
+
### `AnalyticsReporter`
|
|
118
|
+
Batch transaction reporter for the API Toll dashboard.
|
|
119
|
+
|
|
120
|
+
## Part of API Toll
|
|
121
|
+
|
|
122
|
+
API Toll is the payment infrastructure for autonomous AI agents. Learn more:
|
|
123
|
+
|
|
124
|
+
- [apitoll.com](https://apitoll.com) — Dashboard & marketplace
|
|
125
|
+
- [`@apitoll/buyer-sdk`](https://www.npmjs.com/package/@apitoll/buyer-sdk) — For AI agent builders
|
|
126
|
+
- [x402 Protocol](https://www.x402.org/) — The HTTP payment standard
|
|
127
|
+
- [GitHub](https://github.com/TasnidChain/APITOLL)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type PaymentReceipt, type FeeBreakdown } from "@apitoll/shared";
|
|
2
|
+
export interface ReporterConfig {
|
|
3
|
+
/** Platform API key for authentication */
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
/** Seller ID on the platform */
|
|
6
|
+
sellerId?: string;
|
|
7
|
+
/** Custom platform API URL */
|
|
8
|
+
platformUrl?: string;
|
|
9
|
+
/** Webhook URL for real-time notifications */
|
|
10
|
+
webhookUrl?: string;
|
|
11
|
+
/** Webhook signing secret for HMAC-SHA256 verification */
|
|
12
|
+
webhookSecret?: string;
|
|
13
|
+
/** Enable local logging */
|
|
14
|
+
verbose?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface TransactionReport {
|
|
17
|
+
/** The endpoint that was called */
|
|
18
|
+
endpoint: string;
|
|
19
|
+
/** HTTP method */
|
|
20
|
+
method: string;
|
|
21
|
+
/** Payment receipt from verification */
|
|
22
|
+
receipt: PaymentReceipt;
|
|
23
|
+
/** Response HTTP status */
|
|
24
|
+
responseStatus: number;
|
|
25
|
+
/** Request-to-response latency in ms */
|
|
26
|
+
latencyMs: number;
|
|
27
|
+
/** Fee breakdown (if platform fee enabled) */
|
|
28
|
+
feeBreakdown?: FeeBreakdown;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Analytics reporter that sends transaction data to the Apitoll platform.
|
|
32
|
+
* Now includes platform fee tracking for revenue reporting.
|
|
33
|
+
* Falls back to local logging if no API key is configured.
|
|
34
|
+
*/
|
|
35
|
+
export declare class AnalyticsReporter {
|
|
36
|
+
private config;
|
|
37
|
+
private queue;
|
|
38
|
+
private flushTimer;
|
|
39
|
+
constructor(config?: ReporterConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Report a completed transaction (with optional fee breakdown).
|
|
42
|
+
*/
|
|
43
|
+
report(report: TransactionReport): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Report a failed/rejected payment (no receipt).
|
|
46
|
+
*/
|
|
47
|
+
reportRejection(endpoint: string, method: string, reason: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Flush queued transactions to the platform.
|
|
50
|
+
*/
|
|
51
|
+
private flush;
|
|
52
|
+
/**
|
|
53
|
+
* Send a real-time webhook notification to the Apitoll platform (Convex).
|
|
54
|
+
* Formats the transaction in the Convex /webhook/transactions format.
|
|
55
|
+
*/
|
|
56
|
+
private sendWebhook;
|
|
57
|
+
/**
|
|
58
|
+
* Cleanup: flush remaining transactions and clear timer.
|
|
59
|
+
*/
|
|
60
|
+
destroy(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAoC,MAAM,iBAAiB,CAAC;AAI7H,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,OAAO,EAAE,cAAc,CAAC;IACxB,2BAA2B;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AASD;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,UAAU,CAA+C;gBAErD,MAAM,GAAE,cAAmB;IAavC;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDtD;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtF;;OAEG;YACW,KAAK;IA6BnB;;;OAGG;YACW,WAAW;IAyCzB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { generateId, computeHmacSignature } from "@apitoll/shared";
|
|
2
|
+
const PLATFORM_API_URL = "https://api.apitoll.com";
|
|
3
|
+
/**
|
|
4
|
+
* Analytics reporter that sends transaction data to the Apitoll platform.
|
|
5
|
+
* Now includes platform fee tracking for revenue reporting.
|
|
6
|
+
* Falls back to local logging if no API key is configured.
|
|
7
|
+
*/
|
|
8
|
+
export class AnalyticsReporter {
|
|
9
|
+
config;
|
|
10
|
+
queue = [];
|
|
11
|
+
flushTimer = null;
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
platformUrl: PLATFORM_API_URL,
|
|
15
|
+
verbose: false,
|
|
16
|
+
...config,
|
|
17
|
+
};
|
|
18
|
+
// Batch flush every 5 seconds
|
|
19
|
+
if (this.config.apiKey) {
|
|
20
|
+
this.flushTimer = setInterval(() => this.flush(), 5000);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Report a completed transaction (with optional fee breakdown).
|
|
25
|
+
*/
|
|
26
|
+
async report(report) {
|
|
27
|
+
const transaction = {
|
|
28
|
+
id: generateId("tx"),
|
|
29
|
+
txHash: report.receipt.txHash,
|
|
30
|
+
agentAddress: report.receipt.from,
|
|
31
|
+
sellerId: this.config.sellerId || "unknown",
|
|
32
|
+
endpoint: report.endpoint,
|
|
33
|
+
method: report.method,
|
|
34
|
+
amount: report.receipt.amount,
|
|
35
|
+
chain: report.receipt.chain,
|
|
36
|
+
status: report.responseStatus < 400 ? "settled" : "failed",
|
|
37
|
+
requestedAt: report.receipt.timestamp,
|
|
38
|
+
settledAt: report.receipt.timestamp,
|
|
39
|
+
responseStatus: report.responseStatus,
|
|
40
|
+
latencyMs: report.latencyMs,
|
|
41
|
+
// Fee tracking
|
|
42
|
+
platformFee: report.feeBreakdown?.platformFee,
|
|
43
|
+
sellerAmount: report.feeBreakdown?.sellerAmount,
|
|
44
|
+
feeBps: report.feeBreakdown?.feeBps,
|
|
45
|
+
};
|
|
46
|
+
if (this.config.verbose) {
|
|
47
|
+
const feeInfo = report.feeBreakdown
|
|
48
|
+
? ` fee=$${report.feeBreakdown.platformFee} seller=$${report.feeBreakdown.sellerAmount}`
|
|
49
|
+
: "";
|
|
50
|
+
console.log(`[apitoll] tx=${transaction.id} endpoint=${transaction.endpoint} amount=$${transaction.amount}${feeInfo} chain=${transaction.chain} status=${transaction.status}`);
|
|
51
|
+
}
|
|
52
|
+
// Queue for batch send
|
|
53
|
+
this.queue.push(transaction);
|
|
54
|
+
// Send webhook immediately if configured
|
|
55
|
+
if (this.config.webhookUrl) {
|
|
56
|
+
this.sendWebhook(transaction).catch((err) => {
|
|
57
|
+
if (this.config.verbose) {
|
|
58
|
+
console.error(`[apitoll] webhook error:`, err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Flush immediately if queue is large
|
|
63
|
+
if (this.queue.length >= 50) {
|
|
64
|
+
await this.flush();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Report a failed/rejected payment (no receipt).
|
|
69
|
+
*/
|
|
70
|
+
async reportRejection(endpoint, method, reason) {
|
|
71
|
+
if (this.config.verbose) {
|
|
72
|
+
console.log(`[apitoll] rejected endpoint=${endpoint} reason=${reason}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Flush queued transactions to the platform.
|
|
77
|
+
*/
|
|
78
|
+
async flush() {
|
|
79
|
+
if (this.queue.length === 0)
|
|
80
|
+
return;
|
|
81
|
+
if (!this.config.apiKey) {
|
|
82
|
+
this.queue = [];
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
86
|
+
try {
|
|
87
|
+
await fetch(`${this.config.platformUrl}/v1/transactions/batch`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({ transactions: batch }),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
// Re-queue on failure (with limit to prevent memory leaks)
|
|
98
|
+
if (this.queue.length < 500) {
|
|
99
|
+
this.queue.unshift(...batch);
|
|
100
|
+
}
|
|
101
|
+
if (this.config.verbose) {
|
|
102
|
+
console.error(`[apitoll] flush error:`, err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Send a real-time webhook notification to the Apitoll platform (Convex).
|
|
108
|
+
* Formats the transaction in the Convex /webhook/transactions format.
|
|
109
|
+
*/
|
|
110
|
+
async sendWebhook(transaction) {
|
|
111
|
+
if (!this.config.webhookUrl)
|
|
112
|
+
return;
|
|
113
|
+
const body = JSON.stringify({
|
|
114
|
+
transactions: [
|
|
115
|
+
{
|
|
116
|
+
txHash: transaction.txHash,
|
|
117
|
+
agentAddress: transaction.agentAddress,
|
|
118
|
+
endpointPath: transaction.endpoint,
|
|
119
|
+
method: transaction.method,
|
|
120
|
+
amount: parseFloat(transaction.amount),
|
|
121
|
+
chain: transaction.chain,
|
|
122
|
+
status: transaction.status,
|
|
123
|
+
latencyMs: transaction.latencyMs,
|
|
124
|
+
requestedAt: transaction.requestedAt,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
const headers = {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
};
|
|
131
|
+
// Include seller API key for Convex authentication
|
|
132
|
+
if (this.config.apiKey) {
|
|
133
|
+
headers["X-Seller-Key"] = this.config.apiKey;
|
|
134
|
+
}
|
|
135
|
+
// Sign webhook payload if secret is configured
|
|
136
|
+
if (this.config.webhookSecret) {
|
|
137
|
+
const signature = await computeHmacSignature(body, this.config.webhookSecret);
|
|
138
|
+
headers["X-Webhook-Signature"] = signature;
|
|
139
|
+
}
|
|
140
|
+
await fetch(this.config.webhookUrl, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers,
|
|
143
|
+
body,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cleanup: flush remaining transactions and clear timer.
|
|
148
|
+
*/
|
|
149
|
+
async destroy() {
|
|
150
|
+
if (this.flushTimer) {
|
|
151
|
+
clearInterval(this.flushTimer);
|
|
152
|
+
this.flushTimer = null;
|
|
153
|
+
}
|
|
154
|
+
await this.flush();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.test.d.ts","sourceRoot":"","sources":["../src/analytics.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { AnalyticsReporter } from "./analytics";
|
|
3
|
+
import { verifyHmacSignature } from "@apitoll/shared";
|
|
4
|
+
const TEST_RECEIPT = {
|
|
5
|
+
txHash: "0xabc123",
|
|
6
|
+
chain: "base",
|
|
7
|
+
amount: "0.005000",
|
|
8
|
+
from: "0xPayer",
|
|
9
|
+
to: "0xSeller",
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
};
|
|
12
|
+
function makeReport(overrides = {}) {
|
|
13
|
+
return {
|
|
14
|
+
endpoint: "GET /api/data",
|
|
15
|
+
method: "GET",
|
|
16
|
+
receipt: TEST_RECEIPT,
|
|
17
|
+
responseStatus: 200,
|
|
18
|
+
latencyMs: 50,
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// ─── Tests using fake timers (batch/flush/retry) ────────────────
|
|
23
|
+
describe("AnalyticsReporter (batching)", () => {
|
|
24
|
+
const mockFetch = vi.fn();
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
27
|
+
mockFetch.mockResolvedValue({ ok: true });
|
|
28
|
+
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
vi.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
it("queues transactions and flushes on interval", async () => {
|
|
35
|
+
const reporter = new AnalyticsReporter({
|
|
36
|
+
apiKey: "test-key",
|
|
37
|
+
platformUrl: "https://test-api.com",
|
|
38
|
+
});
|
|
39
|
+
await reporter.report(makeReport());
|
|
40
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
41
|
+
const batchCall = mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"));
|
|
42
|
+
expect(batchCall).toBeDefined();
|
|
43
|
+
expect(batchCall[1].headers.Authorization).toBe("Bearer test-key");
|
|
44
|
+
await reporter.destroy();
|
|
45
|
+
});
|
|
46
|
+
it("flushes immediately when queue hits 50", async () => {
|
|
47
|
+
const reporter = new AnalyticsReporter({
|
|
48
|
+
apiKey: "test-key",
|
|
49
|
+
platformUrl: "https://test-api.com",
|
|
50
|
+
});
|
|
51
|
+
for (let i = 0; i < 50; i++) {
|
|
52
|
+
await reporter.report(makeReport());
|
|
53
|
+
}
|
|
54
|
+
const batchCall = mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"));
|
|
55
|
+
expect(batchCall).toBeDefined();
|
|
56
|
+
await reporter.destroy();
|
|
57
|
+
});
|
|
58
|
+
it("discards queue when no API key is configured", async () => {
|
|
59
|
+
const reporter = new AnalyticsReporter({
|
|
60
|
+
platformUrl: "https://test-api.com",
|
|
61
|
+
});
|
|
62
|
+
await reporter.report(makeReport());
|
|
63
|
+
const batchCalls = mockFetch.mock.calls.filter((c) => c[0]?.includes?.("/v1/transactions/batch"));
|
|
64
|
+
expect(batchCalls).toHaveLength(0);
|
|
65
|
+
await reporter.destroy();
|
|
66
|
+
});
|
|
67
|
+
it("marks 2xx responses as settled", async () => {
|
|
68
|
+
const reporter = new AnalyticsReporter({
|
|
69
|
+
apiKey: "key",
|
|
70
|
+
platformUrl: "https://test-api.com",
|
|
71
|
+
});
|
|
72
|
+
await reporter.report(makeReport({ responseStatus: 200 }));
|
|
73
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
74
|
+
const body = JSON.parse(mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"))[1].body);
|
|
75
|
+
expect(body.transactions[0].status).toBe("settled");
|
|
76
|
+
await reporter.destroy();
|
|
77
|
+
});
|
|
78
|
+
it("marks 4xx/5xx responses as failed", async () => {
|
|
79
|
+
const reporter = new AnalyticsReporter({
|
|
80
|
+
apiKey: "key",
|
|
81
|
+
platformUrl: "https://test-api.com",
|
|
82
|
+
});
|
|
83
|
+
await reporter.report(makeReport({ responseStatus: 500 }));
|
|
84
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
85
|
+
const body = JSON.parse(mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"))[1].body);
|
|
86
|
+
expect(body.transactions[0].status).toBe("failed");
|
|
87
|
+
await reporter.destroy();
|
|
88
|
+
});
|
|
89
|
+
it("includes fee breakdown in transaction data", async () => {
|
|
90
|
+
const reporter = new AnalyticsReporter({
|
|
91
|
+
apiKey: "key",
|
|
92
|
+
platformUrl: "https://test-api.com",
|
|
93
|
+
});
|
|
94
|
+
await reporter.report(makeReport({
|
|
95
|
+
feeBreakdown: {
|
|
96
|
+
totalAmount: "0.005000",
|
|
97
|
+
sellerAmount: "0.004850",
|
|
98
|
+
platformFee: "0.000150",
|
|
99
|
+
feeBps: 300,
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
103
|
+
const body = JSON.parse(mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"))[1].body);
|
|
104
|
+
expect(body.transactions[0].platformFee).toBe("0.000150");
|
|
105
|
+
expect(body.transactions[0].sellerAmount).toBe("0.004850");
|
|
106
|
+
expect(body.transactions[0].feeBps).toBe(300);
|
|
107
|
+
await reporter.destroy();
|
|
108
|
+
});
|
|
109
|
+
it("re-queues transactions on flush failure", async () => {
|
|
110
|
+
mockFetch.mockRejectedValueOnce(new Error("Server down"));
|
|
111
|
+
mockFetch.mockResolvedValueOnce({ ok: true });
|
|
112
|
+
const reporter = new AnalyticsReporter({
|
|
113
|
+
apiKey: "key",
|
|
114
|
+
platformUrl: "https://test-api.com",
|
|
115
|
+
});
|
|
116
|
+
await reporter.report(makeReport());
|
|
117
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
118
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
119
|
+
const batchCalls = mockFetch.mock.calls.filter((c) => c[0].includes("/v1/transactions/batch"));
|
|
120
|
+
expect(batchCalls.length).toBeGreaterThanOrEqual(2);
|
|
121
|
+
await reporter.destroy();
|
|
122
|
+
});
|
|
123
|
+
it("flushes remaining queue on destroy", async () => {
|
|
124
|
+
const reporter = new AnalyticsReporter({
|
|
125
|
+
apiKey: "key",
|
|
126
|
+
platformUrl: "https://test-api.com",
|
|
127
|
+
});
|
|
128
|
+
await reporter.report(makeReport());
|
|
129
|
+
await reporter.destroy();
|
|
130
|
+
const batchCall = mockFetch.mock.calls.find((c) => c[0].includes("/v1/transactions/batch"));
|
|
131
|
+
expect(batchCall).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
it("clears flush timer on destroy", async () => {
|
|
134
|
+
const reporter = new AnalyticsReporter({ apiKey: "key" });
|
|
135
|
+
await reporter.destroy();
|
|
136
|
+
await vi.advanceTimersByTimeAsync(10000);
|
|
137
|
+
// No errors — timer was cleaned up
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// ─── Tests using real timers (webhook / HMAC) ───────────────────
|
|
141
|
+
describe("AnalyticsReporter (webhooks)", () => {
|
|
142
|
+
const mockFetch = vi.fn();
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
145
|
+
mockFetch.mockResolvedValue({ ok: true });
|
|
146
|
+
});
|
|
147
|
+
afterEach(() => {
|
|
148
|
+
vi.restoreAllMocks();
|
|
149
|
+
});
|
|
150
|
+
it("sends webhook immediately when configured", async () => {
|
|
151
|
+
const reporter = new AnalyticsReporter({
|
|
152
|
+
webhookUrl: "https://hooks.example.com/tx",
|
|
153
|
+
});
|
|
154
|
+
await reporter.report(makeReport());
|
|
155
|
+
// Webhook is fire-and-forget but without HMAC it's synchronous fetch call
|
|
156
|
+
// Give the microtask queue a tick
|
|
157
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
158
|
+
const webhookCall = mockFetch.mock.calls.find((c) => c[0] === "https://hooks.example.com/tx");
|
|
159
|
+
expect(webhookCall).toBeDefined();
|
|
160
|
+
const body = JSON.parse(webhookCall[1].body);
|
|
161
|
+
expect(body.type).toBe("transaction.settled");
|
|
162
|
+
expect(body.data.txHash).toBe("0xabc123");
|
|
163
|
+
await reporter.destroy();
|
|
164
|
+
});
|
|
165
|
+
it("does not send webhook when not configured", async () => {
|
|
166
|
+
const reporter = new AnalyticsReporter({});
|
|
167
|
+
await reporter.report(makeReport());
|
|
168
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
169
|
+
await reporter.destroy();
|
|
170
|
+
});
|
|
171
|
+
it("includes HMAC signature when webhookSecret is set", async () => {
|
|
172
|
+
// Capture the webhook call via a deferred promise
|
|
173
|
+
let capturedArgs = null;
|
|
174
|
+
let resolveCapture;
|
|
175
|
+
const captured = new Promise((resolve) => { resolveCapture = resolve; });
|
|
176
|
+
mockFetch.mockImplementation((...args) => {
|
|
177
|
+
capturedArgs = args;
|
|
178
|
+
resolveCapture();
|
|
179
|
+
return Promise.resolve({ ok: true });
|
|
180
|
+
});
|
|
181
|
+
const secret = "my-webhook-secret";
|
|
182
|
+
const reporter = new AnalyticsReporter({
|
|
183
|
+
webhookUrl: "https://hooks.example.com/tx",
|
|
184
|
+
webhookSecret: secret,
|
|
185
|
+
});
|
|
186
|
+
// Don't await report — it fires webhook as fire-and-forget
|
|
187
|
+
reporter.report(makeReport());
|
|
188
|
+
// Wait for the async webhook (HMAC computation + fetch) to complete
|
|
189
|
+
await captured;
|
|
190
|
+
expect(capturedArgs).not.toBeNull();
|
|
191
|
+
expect(capturedArgs[0]).toBe("https://hooks.example.com/tx");
|
|
192
|
+
const headers = capturedArgs[1].headers;
|
|
193
|
+
const signature = headers["X-Webhook-Signature"];
|
|
194
|
+
expect(signature).toBeDefined();
|
|
195
|
+
expect(signature).toMatch(/^[a-f0-9]{64}$/);
|
|
196
|
+
// Verify the signature matches the payload
|
|
197
|
+
const body = capturedArgs[1].body;
|
|
198
|
+
const isValid = await verifyHmacSignature(body, secret, signature);
|
|
199
|
+
expect(isValid).toBe(true);
|
|
200
|
+
await reporter.destroy();
|
|
201
|
+
});
|
|
202
|
+
it("does not include signature header when no secret", async () => {
|
|
203
|
+
const reporter = new AnalyticsReporter({
|
|
204
|
+
webhookUrl: "https://hooks.example.com/tx",
|
|
205
|
+
});
|
|
206
|
+
await reporter.report(makeReport());
|
|
207
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
208
|
+
const webhookCall = mockFetch.mock.calls.find((c) => c[0] === "https://hooks.example.com/tx");
|
|
209
|
+
expect(webhookCall[1].headers["X-Webhook-Signature"]).toBeUndefined();
|
|
210
|
+
await reporter.destroy();
|
|
211
|
+
});
|
|
212
|
+
it("swallows webhook errors without crashing", async () => {
|
|
213
|
+
mockFetch.mockRejectedValueOnce(new Error("Network error"));
|
|
214
|
+
const reporter = new AnalyticsReporter({
|
|
215
|
+
webhookUrl: "https://hooks.example.com/tx",
|
|
216
|
+
verbose: false,
|
|
217
|
+
});
|
|
218
|
+
await expect(reporter.report(makeReport())).resolves.not.toThrow();
|
|
219
|
+
await reporter.destroy();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=analytics.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.test.js","sourceRoot":"","sources":["../src/analytics.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { paymentMiddleware, getPaymentReceipt, getX402Context } from "./middleware-express";
|
|
2
|
+
export { paymentMiddleware as honoPaymentMiddleware } from "./middleware-hono";
|
|
3
|
+
export { buildPaymentRequirements, encodePaymentRequired, verifyPayment, findEndpointConfig, getEndpointFeeBreakdown, type VerifyPaymentOptions, type VerificationResult, } from "./payment";
|
|
4
|
+
export { AnalyticsReporter, type ReporterConfig, type TransactionReport } from "./analytics";
|
|
5
|
+
export type { SellerConfig, EndpointConfig, EndpointRegistry, PaymentRequirement, PaymentReceipt, SupportedChain, ChainConfig, PlatformFeeConfig, FeeBreakdown, PlanTier, PlanLimits, } from "@apitoll/shared";
|
|
6
|
+
export { calculateFeeBreakdown, getPlatformWallet, getPlanLimits, checkPlanLimit, PLAN_LIMITS, } from "@apitoll/shared";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG7F,YAAY,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,UAAU,GACX,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,WAAW,GACZ,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Core exports
|
|
2
|
+
export { paymentMiddleware, getPaymentReceipt, getX402Context } from "./middleware-express";
|
|
3
|
+
export { paymentMiddleware as honoPaymentMiddleware } from "./middleware-hono";
|
|
4
|
+
// Payment utilities
|
|
5
|
+
export { buildPaymentRequirements, encodePaymentRequired, verifyPayment, findEndpointConfig, getEndpointFeeBreakdown, } from "./payment";
|
|
6
|
+
// Analytics
|
|
7
|
+
export { AnalyticsReporter } from "./analytics";
|
|
8
|
+
// Re-export shared utilities
|
|
9
|
+
export { calculateFeeBreakdown, getPlatformWallet, getPlanLimits, checkPlanLimit, PLAN_LIMITS, } from "@apitoll/shared";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,oBAAoB;AACpB,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,uBAAuB,GAGxB,MAAM,WAAW,CAAC;AAEnB,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAA+C,MAAM,aAAa,CAAC;AAiB7F,6BAA6B;AAC7B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,WAAW,GACZ,MAAM,iBAAiB,CAAC"}
|