ap2-payment-handler 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/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # ap2-payment-handler
2
+
3
+ > Non-custodial crypto payment handler for the AP2 Agent Payment Protocol. The first open-source, zero-escrow AP2 implementation.
4
+
5
+ [![npm version](https://badge.fury.io/js/ap2-payment-handler.svg)](https://www.npmjs.com/package/ap2-payment-handler)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## What is AP2?
9
+
10
+ AP2 (Agent Payment Protocol) is Google's emerging standard for agentic commerce — enabling AI agents to negotiate and execute payments autonomously on behalf of users. This package implements the AP2 mandate lifecycle with a non-custodial, crypto-native approach.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install ap2-payment-handler
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import { AP2PaymentHandler } from 'ap2-payment-handler';
22
+
23
+ const handler = new AP2PaymentHandler({
24
+ supportedMethods: ['usdc_base', 'x402'],
25
+ });
26
+
27
+ // Create an intent mandate (agent-initiated)
28
+ const mandate = handler.createIntentMandate({
29
+ agentId: 'my-agent-001',
30
+ merchantId: 'merchant-xyz',
31
+ maxAmount: 10.0,
32
+ currency: 'USDC',
33
+ ttl: Date.now() + 5 * 60_000, // 5 minutes
34
+ });
35
+
36
+ // Process payment
37
+ const response = await handler.handle({
38
+ mandate,
39
+ preferredMethod: 'usdc_base',
40
+ });
41
+
42
+ if (response.success) {
43
+ console.log('Payment initiated:', response.transactionId);
44
+ console.log('Audit trail:', response.paymentMandate?.auditTrail);
45
+ }
46
+ ```
47
+
48
+ ## Cart Payments
49
+
50
+ ```typescript
51
+ const cartMandate = handler.createCartMandate({
52
+ agentId: 'my-agent-001',
53
+ lineItems: [
54
+ { name: 'API Credits', price: 5.0, qty: 2 },
55
+ { name: 'Priority Queue', price: 3.0, qty: 1 },
56
+ ],
57
+ total: 13.0,
58
+ currency: 'USDC',
59
+ });
60
+
61
+ const response = await handler.handle({
62
+ mandate: cartMandate,
63
+ preferredMethod: 'usdc_base',
64
+ });
65
+ ```
66
+
67
+ ## HTTP 402 Payment Required (x402)
68
+
69
+ ```typescript
70
+ import { X402Bridge } from 'ap2-payment-handler';
71
+
72
+ const bridge = new X402Bridge();
73
+
74
+ // When your agent receives a 402 response
75
+ const paymentResponse = await bridge.handleResponse({
76
+ status: 402,
77
+ headers: response.headers,
78
+ });
79
+
80
+ if (paymentResponse) {
81
+ // Build proof and retry request
82
+ const proof = await bridge.buildPaymentProof({
83
+ amount: '5.00',
84
+ currency: 'USDC',
85
+ address: '0x...',
86
+ agentId: 'my-agent-001',
87
+ });
88
+ }
89
+ ```
90
+
91
+ ## Supported Payment Methods
92
+
93
+ | Method | Network | Description |
94
+ |--------|---------|-------------|
95
+ | `usdc_base` | Base (Coinbase L2) | USDC on Base — low fees, fast finality |
96
+ | `x402` | Any | HTTP 402 Payment Required flow |
97
+ | `usdc_arbitrum` | Arbitrum | USDC on Arbitrum One |
98
+ | `usdc_optimism` | Optimism | USDC on Optimism |
99
+
100
+ ## Non-Custodial Design
101
+
102
+ This package is **zero-escrow** by design:
103
+
104
+ - **No private keys stored** — signing is delegated to your wallet/signer
105
+ - **No funds held** — payment proofs reference on-chain transactions
106
+ - **Full audit trail** — every mandate lifecycle step is logged
107
+ - **EIP-712 compatible** — structured signing for human-readable approval
108
+
109
+ All `PaymentMandate` objects carry `isNonCustodial: true` as a type-level guarantee.
110
+
111
+ ## API Reference
112
+
113
+ ### `AP2PaymentHandler`
114
+
115
+ - `constructor({ supportedMethods })` — initialize with supported payment methods
116
+ - `handle(request)` — process an AP2 payment request
117
+ - `createIntentMandate(params)` — create an agent-initiated intent mandate
118
+ - `createCartMandate(params)` — create a cart mandate from line items
119
+ - `getSupportedMethods()` — list configured methods
120
+
121
+ ### `X402Bridge`
122
+
123
+ - `parsePaymentRequired(headers)` — extract payment details from 402 headers
124
+ - `buildPaymentProof(params)` — create a non-custodial payment proof
125
+ - `handleResponse(response)` — handle HTTP responses, returns AP2PaymentResponse for 402
126
+
127
+ ### `validateMandate(mandate)`
128
+
129
+ Validates a mandate and throws `AP2ValidationError` on failure.
130
+
131
+ ## License
132
+
133
+ MIT © [AI Agent Economy](https://ai-agent-economy.com)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=handler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/handler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const handler_1 = require("../handler");
4
+ const x402_1 = require("../x402");
5
+ const validation_1 = require("../validation");
6
+ const futureTime = Date.now() + 60000; // 1 minute from now
7
+ const pastTime = Date.now() - 60000; // 1 minute ago
8
+ function makeIntentMandate(overrides = {}) {
9
+ return {
10
+ type: 'intent',
11
+ agentId: 'agent-123',
12
+ merchantId: 'merchant-456',
13
+ maxAmount: 10.0,
14
+ currency: 'USDC',
15
+ ttl: futureTime,
16
+ isAgentInitiated: true,
17
+ isNonCustodial: true,
18
+ ...overrides,
19
+ };
20
+ }
21
+ function makeCartMandate(overrides = {}) {
22
+ return {
23
+ type: 'cart',
24
+ agentId: 'agent-123',
25
+ lineItems: [
26
+ { name: 'Widget', price: 5.0, qty: 2 },
27
+ ],
28
+ total: 10.0,
29
+ currency: 'USDC',
30
+ ...overrides,
31
+ };
32
+ }
33
+ describe('AP2PaymentHandler', () => {
34
+ let handler;
35
+ beforeEach(() => {
36
+ handler = new handler_1.AP2PaymentHandler({ supportedMethods: ['usdc_base', 'x402'] });
37
+ });
38
+ // 1. IntentMandate creation with correct flags
39
+ test('createIntentMandate sets isAgentInitiated and isNonCustodial', () => {
40
+ const mandate = handler.createIntentMandate({
41
+ agentId: 'a1',
42
+ merchantId: 'm1',
43
+ maxAmount: 5,
44
+ currency: 'USDC',
45
+ ttl: futureTime,
46
+ });
47
+ expect(mandate.isAgentInitiated).toBe(true);
48
+ expect(mandate.isNonCustodial).toBe(true);
49
+ expect(mandate.type).toBe('intent');
50
+ });
51
+ // 2. CartMandate total validation (correct sum)
52
+ test('validateMandate accepts CartMandate with correct total', () => {
53
+ const cart = makeCartMandate();
54
+ expect(() => (0, validation_1.validateMandate)(cart)).not.toThrow();
55
+ });
56
+ // 3. CartMandate total validation (wrong sum — should throw)
57
+ test('validateMandate rejects CartMandate with wrong total', () => {
58
+ const cart = makeCartMandate({ total: 99.0 });
59
+ expect(() => (0, validation_1.validateMandate)(cart)).toThrow(validation_1.AP2ValidationError);
60
+ expect(() => (0, validation_1.validateMandate)(cart)).toThrow(/mismatch/);
61
+ });
62
+ // 4. IntentMandate TTL validation (future TTL — valid)
63
+ test('validateMandate accepts IntentMandate with future TTL', () => {
64
+ const intent = makeIntentMandate({ ttl: futureTime });
65
+ expect(() => (0, validation_1.validateMandate)(intent)).not.toThrow();
66
+ });
67
+ // 5. IntentMandate TTL validation (past TTL — invalid)
68
+ test('validateMandate rejects IntentMandate with expired TTL', () => {
69
+ const intent = makeIntentMandate({ ttl: pastTime });
70
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(validation_1.AP2ValidationError);
71
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(/expired/);
72
+ });
73
+ // 6. Handler with usdc_base method
74
+ test('handle succeeds with usdc_base method', async () => {
75
+ const response = await handler.handle({
76
+ mandate: makeIntentMandate(),
77
+ preferredMethod: 'usdc_base',
78
+ });
79
+ expect(response.success).toBe(true);
80
+ expect(response.paymentMandate?.method).toBe('usdc_base');
81
+ });
82
+ // 7. Handler with x402 method
83
+ test('handle succeeds with x402 method', async () => {
84
+ const response = await handler.handle({
85
+ mandate: makeIntentMandate(),
86
+ preferredMethod: 'x402',
87
+ });
88
+ expect(response.success).toBe(true);
89
+ expect(response.paymentMandate?.method).toBe('x402');
90
+ });
91
+ // 8. Handler with unsupported method falls back
92
+ test('handle falls back to first supported method when preferred is unsupported', async () => {
93
+ const response = await handler.handle({
94
+ mandate: makeIntentMandate(),
95
+ preferredMethod: 'usdc_arbitrum',
96
+ });
97
+ expect(response.success).toBe(true);
98
+ expect(response.paymentMandate?.method).toBe('usdc_base');
99
+ });
100
+ // 9. Handler returns success with transactionId
101
+ test('handle returns transactionId on success', async () => {
102
+ const response = await handler.handle({
103
+ mandate: makeIntentMandate(),
104
+ preferredMethod: 'usdc_base',
105
+ });
106
+ expect(response.success).toBe(true);
107
+ expect(response.transactionId).toBeDefined();
108
+ expect(typeof response.transactionId).toBe('string');
109
+ });
110
+ // 10. Handler audit trail includes entries
111
+ test('handle returns paymentMandate with non-empty auditTrail', async () => {
112
+ const response = await handler.handle({
113
+ mandate: makeIntentMandate(),
114
+ preferredMethod: 'usdc_base',
115
+ });
116
+ expect(response.paymentMandate?.auditTrail.length).toBeGreaterThan(0);
117
+ });
118
+ // 11. PaymentMandate has isNonCustodial flag
119
+ test('paymentMandate has isNonCustodial set to true', async () => {
120
+ const response = await handler.handle({
121
+ mandate: makeIntentMandate(),
122
+ preferredMethod: 'usdc_base',
123
+ });
124
+ expect(response.paymentMandate?.isNonCustodial).toBe(true);
125
+ });
126
+ // 16. Validation rejects missing agentId
127
+ test('validateMandate rejects missing agentId', () => {
128
+ const intent = makeIntentMandate({ agentId: '' });
129
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(validation_1.AP2ValidationError);
130
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(/agentId/);
131
+ });
132
+ // 17. Validation rejects negative amount
133
+ test('validateMandate rejects negative maxAmount on IntentMandate', () => {
134
+ const intent = makeIntentMandate({ maxAmount: -5 });
135
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(validation_1.AP2ValidationError);
136
+ expect(() => (0, validation_1.validateMandate)(intent)).toThrow(/positive/);
137
+ });
138
+ // 18. createIntentMandate sets correct defaults
139
+ test('createIntentMandate preserves all provided params', () => {
140
+ const mandate = handler.createIntentMandate({
141
+ agentId: 'myAgent',
142
+ merchantId: 'myMerchant',
143
+ maxAmount: 25,
144
+ currency: 'USDC',
145
+ ttl: futureTime,
146
+ });
147
+ expect(mandate.agentId).toBe('myAgent');
148
+ expect(mandate.merchantId).toBe('myMerchant');
149
+ expect(mandate.maxAmount).toBe(25);
150
+ expect(mandate.currency).toBe('USDC');
151
+ });
152
+ // 19. createCartMandate with multiple items
153
+ test('createCartMandate with multiple line items', () => {
154
+ const mandate = handler.createCartMandate({
155
+ agentId: 'agent-1',
156
+ lineItems: [
157
+ { name: 'Item A', price: 3, qty: 2 },
158
+ { name: 'Item B', price: 4, qty: 1 },
159
+ ],
160
+ total: 10,
161
+ currency: 'USDC',
162
+ });
163
+ expect(mandate.type).toBe('cart');
164
+ expect(mandate.lineItems.length).toBe(2);
165
+ expect(mandate.total).toBe(10);
166
+ });
167
+ // 20. getSupportedMethods returns config
168
+ test('getSupportedMethods returns configured methods', () => {
169
+ const methods = handler.getSupportedMethods();
170
+ expect(methods).toContain('usdc_base');
171
+ expect(methods).toContain('x402');
172
+ expect(methods.length).toBe(2);
173
+ });
174
+ });
175
+ describe('X402Bridge', () => {
176
+ let bridge;
177
+ beforeEach(() => {
178
+ bridge = new x402_1.X402Bridge();
179
+ });
180
+ // 12. X402Bridge parsePaymentRequired
181
+ test('parsePaymentRequired extracts payment details from headers', async () => {
182
+ const headers = {
183
+ 'x-payment-amount': '5.00',
184
+ 'x-payment-currency': 'USDC',
185
+ 'x-payment-address': '0xabc123',
186
+ };
187
+ const result = await bridge.parsePaymentRequired(headers);
188
+ expect(result.amount).toBe('5.00');
189
+ expect(result.currency).toBe('USDC');
190
+ expect(result.address).toBe('0xabc123');
191
+ });
192
+ // 13. X402Bridge buildPaymentProof
193
+ test('buildPaymentProof returns a proof and timestamp', async () => {
194
+ const result = await bridge.buildPaymentProof({
195
+ amount: '5.00',
196
+ currency: 'USDC',
197
+ address: '0xabc123',
198
+ agentId: 'agent-1',
199
+ });
200
+ expect(result.proof).toBeTruthy();
201
+ expect(typeof result.proof).toBe('string');
202
+ expect(result.timestamp).toBeGreaterThan(0);
203
+ });
204
+ // 14. X402Bridge handleResponse for 402 status
205
+ test('handleResponse returns AP2PaymentResponse for 402 status', async () => {
206
+ const response = await bridge.handleResponse({
207
+ status: 402,
208
+ headers: {
209
+ 'x-payment-amount': '10.00',
210
+ 'x-payment-currency': 'USDC',
211
+ 'x-payment-address': '0xdef456',
212
+ },
213
+ });
214
+ expect(response).not.toBeNull();
215
+ expect(response?.success).toBe(false);
216
+ expect(response?.error).toContain('Payment required');
217
+ });
218
+ // 15. X402Bridge handleResponse for 200 status (null)
219
+ test('handleResponse returns null for non-402 status', async () => {
220
+ const response = await bridge.handleResponse({
221
+ status: 200,
222
+ headers: {},
223
+ });
224
+ expect(response).toBeNull();
225
+ });
226
+ // Additional: parsePaymentRequired with JSON header
227
+ test('parsePaymentRequired parses JSON x-payment-required header', async () => {
228
+ const headers = {
229
+ 'x-payment-required': JSON.stringify({
230
+ amount: '7.50',
231
+ currency: 'USDC',
232
+ address: '0xfeed',
233
+ }),
234
+ };
235
+ const result = await bridge.parsePaymentRequired(headers);
236
+ expect(result.amount).toBe('7.50');
237
+ expect(result.address).toBe('0xfeed');
238
+ });
239
+ // Additional: buildPaymentProof encodes agentId
240
+ test('buildPaymentProof encodes agentId in proof', async () => {
241
+ const result = await bridge.buildPaymentProof({
242
+ amount: '1.00',
243
+ currency: 'USDC',
244
+ address: '0xabc',
245
+ agentId: 'agent-special',
246
+ });
247
+ const decoded = JSON.parse(Buffer.from(result.proof, 'base64').toString());
248
+ expect(decoded.agentId).toBe('agent-special');
249
+ });
250
+ });
251
+ //# sourceMappingURL=handler.test.js.map
@@ -0,0 +1,14 @@
1
+ import { AP2PaymentMethod, AP2PaymentRequest, AP2PaymentResponse, IntentMandate, CartMandate } from './types';
2
+ export declare class AP2PaymentHandler {
3
+ private supportedMethods;
4
+ constructor(config: {
5
+ supportedMethods: AP2PaymentMethod[];
6
+ });
7
+ getSupportedMethods(): AP2PaymentMethod[];
8
+ createIntentMandate(params: Omit<IntentMandate, 'type' | 'isAgentInitiated' | 'isNonCustodial'>): IntentMandate;
9
+ createCartMandate(params: Omit<CartMandate, 'type'>): CartMandate;
10
+ private selectMethod;
11
+ private getAmount;
12
+ handle(request: AP2PaymentRequest): Promise<AP2PaymentResponse>;
13
+ }
14
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,WAAW,EAEZ,MAAM,SAAS,CAAC;AAOjB,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,gBAAgB,CAAqB;gBAEjC,MAAM,EAAE;QAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAA;KAAE;IAI5D,mBAAmB,IAAI,gBAAgB,EAAE;IAIzC,mBAAmB,CACjB,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,gBAAgB,CAAC,GAC1E,aAAa;IAShB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,WAAW;IAOjE,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,SAAS;IAOX,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAgDtE"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AP2PaymentHandler = void 0;
4
+ const validation_1 = require("./validation");
5
+ function generateId() {
6
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
7
+ }
8
+ class AP2PaymentHandler {
9
+ constructor(config) {
10
+ this.supportedMethods = config.supportedMethods;
11
+ }
12
+ getSupportedMethods() {
13
+ return [...this.supportedMethods];
14
+ }
15
+ createIntentMandate(params) {
16
+ return {
17
+ type: 'intent',
18
+ isAgentInitiated: true,
19
+ isNonCustodial: true,
20
+ ...params,
21
+ };
22
+ }
23
+ createCartMandate(params) {
24
+ return {
25
+ type: 'cart',
26
+ ...params,
27
+ };
28
+ }
29
+ selectMethod(preferred) {
30
+ if (this.supportedMethods.includes(preferred)) {
31
+ return preferred;
32
+ }
33
+ // Fallback to first supported method
34
+ return this.supportedMethods.length > 0 ? this.supportedMethods[0] : null;
35
+ }
36
+ getAmount(mandate) {
37
+ if (mandate.type === 'intent') {
38
+ return mandate.maxAmount;
39
+ }
40
+ return mandate.total;
41
+ }
42
+ async handle(request) {
43
+ try {
44
+ (0, validation_1.validateMandate)(request.mandate);
45
+ }
46
+ catch (err) {
47
+ return {
48
+ success: false,
49
+ error: err.message,
50
+ };
51
+ }
52
+ const method = this.selectMethod(request.preferredMethod);
53
+ if (!method) {
54
+ return {
55
+ success: false,
56
+ error: 'No supported payment methods available',
57
+ };
58
+ }
59
+ const mandateId = generateId();
60
+ const timestamp = Date.now();
61
+ const amount = this.getAmount(request.mandate);
62
+ const auditTrail = [
63
+ `[${new Date(timestamp).toISOString()}] Mandate received: type=${request.mandate.type}, agentId=${request.mandate.agentId}`,
64
+ `[${new Date(timestamp).toISOString()}] Payment method selected: ${method}`,
65
+ `[${new Date(timestamp).toISOString()}] Non-custodial payment initiated: amount=${amount} ${request.mandate.currency}`,
66
+ `[${new Date(timestamp).toISOString()}] MandateId assigned: ${mandateId}`,
67
+ ];
68
+ const paymentMandate = {
69
+ type: 'payment',
70
+ mandateId,
71
+ method,
72
+ amount,
73
+ currency: request.mandate.currency,
74
+ auditTrail,
75
+ timestamp,
76
+ isNonCustodial: true,
77
+ };
78
+ const transactionId = `tx_${generateId()}`;
79
+ return {
80
+ success: true,
81
+ transactionId,
82
+ paymentMandate,
83
+ };
84
+ }
85
+ }
86
+ exports.AP2PaymentHandler = AP2PaymentHandler;
87
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './handler';
3
+ export * from './x402';
4
+ export * from './validation';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./handler"), exports);
19
+ __exportStar(require("./x402"), exports);
20
+ __exportStar(require("./validation"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ export type AP2PaymentMethod = 'usdc_base' | 'x402' | 'usdc_arbitrum' | 'usdc_optimism';
2
+ export type MandateType = 'intent' | 'cart' | 'payment';
3
+ export interface IntentMandate {
4
+ type: 'intent';
5
+ agentId: string;
6
+ merchantId: string;
7
+ maxAmount: number;
8
+ currency: string;
9
+ ttl: number;
10
+ isAgentInitiated: true;
11
+ isNonCustodial: true;
12
+ signature?: string;
13
+ }
14
+ export interface LineItem {
15
+ name: string;
16
+ price: number;
17
+ qty: number;
18
+ }
19
+ export interface CartMandate {
20
+ type: 'cart';
21
+ agentId: string;
22
+ lineItems: LineItem[];
23
+ total: number;
24
+ currency: string;
25
+ signature?: string;
26
+ }
27
+ export interface PaymentMandate {
28
+ type: 'payment';
29
+ mandateId: string;
30
+ method: AP2PaymentMethod;
31
+ amount: number;
32
+ currency: string;
33
+ auditTrail: string[];
34
+ timestamp: number;
35
+ isNonCustodial: true;
36
+ }
37
+ export interface AP2PaymentRequest {
38
+ mandate: IntentMandate | CartMandate;
39
+ preferredMethod: AP2PaymentMethod;
40
+ }
41
+ export interface AP2PaymentResponse {
42
+ success: boolean;
43
+ transactionId?: string;
44
+ error?: string;
45
+ paymentMandate?: PaymentMandate;
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,MAAM,GAAG,eAAe,GAAG,eAAe,CAAC;AAExF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,IAAI,CAAC;IACvB,cAAc,EAAE,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,aAAa,GAAG,WAAW,CAAC;IACrC,eAAe,EAAE,gBAAgB,CAAC;CACnC;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,6 @@
1
+ import { IntentMandate, CartMandate } from './types';
2
+ export declare class AP2ValidationError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function validateMandate(mandate: IntentMandate | CartMandate): void;
6
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAErD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,GAAG,IAAI,CAmD1E"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AP2ValidationError = void 0;
4
+ exports.validateMandate = validateMandate;
5
+ class AP2ValidationError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'AP2ValidationError';
9
+ }
10
+ }
11
+ exports.AP2ValidationError = AP2ValidationError;
12
+ function validateMandate(mandate) {
13
+ if (!mandate.agentId) {
14
+ throw new AP2ValidationError('Missing required field: agentId');
15
+ }
16
+ if (mandate.type === 'intent') {
17
+ if (!mandate.merchantId) {
18
+ throw new AP2ValidationError('Missing required field: merchantId');
19
+ }
20
+ if (mandate.maxAmount === undefined || mandate.maxAmount === null) {
21
+ throw new AP2ValidationError('Missing required field: maxAmount');
22
+ }
23
+ if (typeof mandate.maxAmount !== 'number' || mandate.maxAmount <= 0) {
24
+ throw new AP2ValidationError('maxAmount must be a positive number');
25
+ }
26
+ if (!mandate.currency) {
27
+ throw new AP2ValidationError('Missing required field: currency');
28
+ }
29
+ if (!mandate.ttl) {
30
+ throw new AP2ValidationError('Missing required field: ttl');
31
+ }
32
+ if (mandate.ttl < Date.now()) {
33
+ throw new AP2ValidationError('IntentMandate TTL has expired');
34
+ }
35
+ }
36
+ else if (mandate.type === 'cart') {
37
+ if (!mandate.lineItems || mandate.lineItems.length === 0) {
38
+ throw new AP2ValidationError('CartMandate must have at least one line item');
39
+ }
40
+ if (!mandate.currency) {
41
+ throw new AP2ValidationError('Missing required field: currency');
42
+ }
43
+ if (mandate.total === undefined || mandate.total === null) {
44
+ throw new AP2ValidationError('Missing required field: total');
45
+ }
46
+ if (typeof mandate.total !== 'number' || mandate.total <= 0) {
47
+ throw new AP2ValidationError('total must be a positive number');
48
+ }
49
+ const computedTotal = mandate.lineItems.reduce((sum, item) => {
50
+ return sum + item.price * item.qty;
51
+ }, 0);
52
+ const diff = Math.abs(computedTotal - mandate.total);
53
+ if (diff > 0.001) {
54
+ throw new AP2ValidationError(`CartMandate total mismatch: expected ${computedTotal.toFixed(4)}, got ${mandate.total.toFixed(4)}`);
55
+ }
56
+ }
57
+ else {
58
+ throw new AP2ValidationError('Unknown mandate type');
59
+ }
60
+ }
61
+ //# sourceMappingURL=validation.js.map