@mnemopay/sdk 0.8.0 → 0.9.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.
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Commerce Engine for MnemoPay
3
+ *
4
+ * Autonomous shopping for AI agents. An agent can search products, compare
5
+ * prices, create a purchase order, pay via escrow, and confirm delivery.
6
+ * Every purchase is scoped by a ShoppingMandate — cryptographically enforced
7
+ * spend limits, category restrictions, and merchant whitelists.
8
+ *
9
+ * The escrow flow:
10
+ * 1. Agent searches products → returns ranked results
11
+ * 2. Agent (or user) approves a purchase → creates a PurchaseOrder
12
+ * 3. SDK calls charge() to hold funds in escrow
13
+ * 4. Agent executes the purchase via the commerce provider
14
+ * 5. On delivery confirmation → settle() releases funds
15
+ * 6. On failure/cancellation → refund() returns funds
16
+ *
17
+ * Security model:
18
+ * - ShoppingMandate defines what the agent CAN buy (budget, categories, merchants)
19
+ * - Every purchase validated against mandate before escrow is created
20
+ * - Purchases above approvalThreshold require explicit user confirmation
21
+ * - Full audit trail via MnemoPay's existing audit system
22
+ * - Agent remembers user preferences (sizes, brands, past purchases)
23
+ *
24
+ * Usage:
25
+ * const agent = MnemoPay.quick("shopper");
26
+ * const commerce = new CommerceEngine(agent);
27
+ * commerce.setMandate({ budget: 200, categories: ["electronics"] });
28
+ * const results = await commerce.search("USB-C cable under $15");
29
+ * const order = await commerce.purchase(results[0]);
30
+ * // ... later, on delivery ...
31
+ * await commerce.confirmDelivery(order.id);
32
+ */
33
+ export interface ShoppingMandate {
34
+ /** Maximum total budget for this mandate (USD) */
35
+ budget: number;
36
+ /** Maximum per-item spend (USD). Defaults to budget. */
37
+ maxPerItem?: number;
38
+ /** Allowed product categories (empty = any) */
39
+ categories?: string[];
40
+ /** Blocked product categories */
41
+ blockedCategories?: string[];
42
+ /** Allowed merchant domains (empty = any) */
43
+ allowedMerchants?: string[];
44
+ /** Blocked merchant domains */
45
+ blockedMerchants?: string[];
46
+ /** Purchases above this amount require user approval callback (USD) */
47
+ approvalThreshold?: number;
48
+ /** Mandate expiry (ISO timestamp). Defaults to 24h from creation. */
49
+ expiresAt?: string;
50
+ /** Who issued this mandate (user ID or "system") */
51
+ issuedBy: string;
52
+ }
53
+ export interface ProductResult {
54
+ /** Unique product ID from the provider */
55
+ productId: string;
56
+ /** Product title */
57
+ title: string;
58
+ /** Price in USD */
59
+ price: number;
60
+ /** Currency (default USD) */
61
+ currency?: string;
62
+ /** Product URL */
63
+ url: string;
64
+ /** Image URL */
65
+ imageUrl?: string;
66
+ /** Merchant/seller name */
67
+ merchant: string;
68
+ /** Merchant domain */
69
+ merchantDomain?: string;
70
+ /** Product category */
71
+ category?: string;
72
+ /** Rating (0-5) */
73
+ rating?: number;
74
+ /** Number of reviews */
75
+ reviewCount?: number;
76
+ /** Estimated delivery days */
77
+ deliveryDays?: number;
78
+ /** Whether free shipping */
79
+ freeShipping?: boolean;
80
+ /** Raw provider data */
81
+ raw?: Record<string, unknown>;
82
+ }
83
+ export interface PurchaseOrder {
84
+ /** Unique order ID */
85
+ id: string;
86
+ /** Agent ID that placed the order */
87
+ agentId: string;
88
+ /** Product being purchased */
89
+ product: ProductResult;
90
+ /** MnemoPay transaction ID (escrow) */
91
+ txId: string;
92
+ /** Order status */
93
+ status: "pending_approval" | "escrowed" | "purchased" | "shipped" | "delivered" | "cancelled" | "failed";
94
+ /** Shipping address or delivery instructions */
95
+ deliveryInstructions?: string;
96
+ /** Tracking number */
97
+ trackingNumber?: string;
98
+ /** Tracking URL */
99
+ trackingUrl?: string;
100
+ /** Created timestamp */
101
+ createdAt: Date;
102
+ /** Last updated */
103
+ updatedAt: Date;
104
+ /** Mandate snapshot at time of purchase */
105
+ mandate: ShoppingMandate;
106
+ /** User approval status */
107
+ approved: boolean;
108
+ /** Failure reason if failed */
109
+ failureReason?: string;
110
+ }
111
+ export type ApprovalCallback = (order: PurchaseOrder) => Promise<boolean>;
112
+ /**
113
+ * Product search provider interface. Implement this to connect
114
+ * any product catalog (eBay, custom API, local inventory, etc.)
115
+ */
116
+ export interface CommerceProvider {
117
+ readonly name: string;
118
+ /** Search for products matching a query */
119
+ search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
120
+ /** Get full product details by ID */
121
+ getProduct(productId: string): Promise<ProductResult | null>;
122
+ /** Execute a purchase (returns external order reference) */
123
+ executePurchase(product: ProductResult, deliveryInstructions?: string): Promise<{
124
+ externalOrderId: string;
125
+ status: string;
126
+ trackingUrl?: string;
127
+ }>;
128
+ /** Check order/delivery status */
129
+ checkStatus(externalOrderId: string): Promise<{
130
+ status: string;
131
+ trackingNumber?: string;
132
+ trackingUrl?: string;
133
+ deliveredAt?: string;
134
+ }>;
135
+ }
136
+ export interface SearchOptions {
137
+ /** Maximum results to return (default: 10) */
138
+ limit?: number;
139
+ /** Minimum price filter */
140
+ minPrice?: number;
141
+ /** Maximum price filter */
142
+ maxPrice?: number;
143
+ /** Category filter */
144
+ category?: string;
145
+ /** Sort by: "price_asc", "price_desc", "rating", "relevance" */
146
+ sortBy?: "price_asc" | "price_desc" | "rating" | "relevance";
147
+ /** Free shipping only */
148
+ freeShippingOnly?: boolean;
149
+ }
150
+ export declare class MockCommerceProvider implements CommerceProvider {
151
+ readonly name = "mock";
152
+ private counter;
153
+ private catalog;
154
+ search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
155
+ getProduct(productId: string): Promise<ProductResult | null>;
156
+ executePurchase(product: ProductResult, _deliveryInstructions?: string): Promise<{
157
+ externalOrderId: string;
158
+ status: string;
159
+ trackingUrl?: string;
160
+ }>;
161
+ checkStatus(externalOrderId: string): Promise<{
162
+ status: string;
163
+ trackingNumber?: string;
164
+ trackingUrl?: string;
165
+ deliveredAt?: string;
166
+ }>;
167
+ }
168
+ export declare class CommerceEngine {
169
+ private agent;
170
+ private provider;
171
+ private mandate;
172
+ private orders;
173
+ private totalSpent;
174
+ private approvalCallback;
175
+ private externalOrderMap;
176
+ private searchLimiter;
177
+ private purchaseLimiter;
178
+ constructor(agent: any, provider?: CommerceProvider);
179
+ /**
180
+ * Set a shopping mandate that defines what this agent can buy.
181
+ * All purchases are validated against this mandate.
182
+ */
183
+ setMandate(mandate: ShoppingMandate): void;
184
+ /** Get the current active mandate */
185
+ getMandate(): ShoppingMandate | null;
186
+ /** Remaining budget under current mandate */
187
+ get remainingBudget(): number;
188
+ /** Set a callback for user approval of high-value purchases */
189
+ onApprovalRequired(callback: ApprovalCallback): void;
190
+ /**
191
+ * Search for products. Uses agent memory to personalize results
192
+ * (e.g., preferred brands, past purchase satisfaction).
193
+ */
194
+ search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
195
+ /**
196
+ * Create a purchase order for a product. Validates against mandate,
197
+ * creates escrow hold, optionally requests user approval.
198
+ */
199
+ purchase(product: ProductResult, deliveryInstructions?: string): Promise<PurchaseOrder>;
200
+ /**
201
+ * Confirm delivery of an order. Settles the escrow, releasing funds.
202
+ */
203
+ confirmDelivery(orderId: string): Promise<PurchaseOrder>;
204
+ /**
205
+ * Cancel an order and refund the escrow.
206
+ */
207
+ cancelOrder(orderId: string, reason?: string): Promise<PurchaseOrder>;
208
+ /**
209
+ * Check delivery status of an order via the commerce provider.
210
+ */
211
+ checkDeliveryStatus(orderId: string): Promise<PurchaseOrder>;
212
+ /** Get an order by ID */
213
+ getOrder(orderId: string): PurchaseOrder | null;
214
+ /** List all orders */
215
+ listOrders(status?: PurchaseOrder["status"]): PurchaseOrder[];
216
+ /** Get spending summary under current mandate */
217
+ spendingSummary(): {
218
+ totalSpent: number;
219
+ remainingBudget: number;
220
+ orderCount: number;
221
+ deliveredCount: number;
222
+ pendingCount: number;
223
+ };
224
+ /** Validate a product against the current mandate. Returns null if OK, or violation string. */
225
+ private validateProduct;
226
+ }
227
+ //# sourceMappingURL=commerce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,450 @@
1
+ "use strict";
2
+ /**
3
+ * Commerce Engine for MnemoPay
4
+ *
5
+ * Autonomous shopping for AI agents. An agent can search products, compare
6
+ * prices, create a purchase order, pay via escrow, and confirm delivery.
7
+ * Every purchase is scoped by a ShoppingMandate — cryptographically enforced
8
+ * spend limits, category restrictions, and merchant whitelists.
9
+ *
10
+ * The escrow flow:
11
+ * 1. Agent searches products → returns ranked results
12
+ * 2. Agent (or user) approves a purchase → creates a PurchaseOrder
13
+ * 3. SDK calls charge() to hold funds in escrow
14
+ * 4. Agent executes the purchase via the commerce provider
15
+ * 5. On delivery confirmation → settle() releases funds
16
+ * 6. On failure/cancellation → refund() returns funds
17
+ *
18
+ * Security model:
19
+ * - ShoppingMandate defines what the agent CAN buy (budget, categories, merchants)
20
+ * - Every purchase validated against mandate before escrow is created
21
+ * - Purchases above approvalThreshold require explicit user confirmation
22
+ * - Full audit trail via MnemoPay's existing audit system
23
+ * - Agent remembers user preferences (sizes, brands, past purchases)
24
+ *
25
+ * Usage:
26
+ * const agent = MnemoPay.quick("shopper");
27
+ * const commerce = new CommerceEngine(agent);
28
+ * commerce.setMandate({ budget: 200, categories: ["electronics"] });
29
+ * const results = await commerce.search("USB-C cable under $15");
30
+ * const order = await commerce.purchase(results[0]);
31
+ * // ... later, on delivery ...
32
+ * await commerce.confirmDelivery(order.id);
33
+ */
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.CommerceEngine = exports.MockCommerceProvider = void 0;
39
+ const node_crypto_1 = __importDefault(require("node:crypto"));
40
+ // ─── Security: Rate limiter for commerce operations ────────────────────────
41
+ class CommerceRateLimiter {
42
+ maxOps;
43
+ windowMs;
44
+ timestamps = [];
45
+ constructor(maxOps = 10, windowMs = 60_000) {
46
+ this.maxOps = maxOps;
47
+ this.windowMs = windowMs;
48
+ }
49
+ check(operation) {
50
+ const now = Date.now();
51
+ this.timestamps = this.timestamps.filter(t => now - t < this.windowMs);
52
+ if (this.timestamps.length >= this.maxOps) {
53
+ throw new Error(`Rate limit exceeded: max ${this.maxOps} ${operation} ops per ${this.windowMs / 1000}s`);
54
+ }
55
+ this.timestamps.push(now);
56
+ }
57
+ }
58
+ // ─── Mock Commerce Provider (for testing/development) ───────────────────────
59
+ class MockCommerceProvider {
60
+ name = "mock";
61
+ counter = 0;
62
+ catalog = [
63
+ { productId: "mock-001", title: "USB-C Cable 6ft", price: 9.99, url: "https://example.com/usbc", merchant: "TechStore", merchantDomain: "techstore.com", category: "electronics", rating: 4.5, reviewCount: 2340, deliveryDays: 3, freeShipping: true },
64
+ { productId: "mock-002", title: "Wireless Mouse", price: 24.99, url: "https://example.com/mouse", merchant: "TechStore", merchantDomain: "techstore.com", category: "electronics", rating: 4.2, reviewCount: 890, deliveryDays: 2, freeShipping: true },
65
+ { productId: "mock-003", title: "Laptop Stand Adjustable", price: 34.99, url: "https://example.com/stand", merchant: "OfficeGear", merchantDomain: "officegear.com", category: "office", rating: 4.7, reviewCount: 5600, deliveryDays: 5, freeShipping: false },
66
+ { productId: "mock-004", title: "Mechanical Keyboard", price: 79.99, url: "https://example.com/keyboard", merchant: "KeysRUs", merchantDomain: "keysrus.com", category: "electronics", rating: 4.8, reviewCount: 12000, deliveryDays: 3, freeShipping: true },
67
+ { productId: "mock-005", title: "Noise Cancelling Headphones", price: 199.99, url: "https://example.com/headphones", merchant: "AudioWorld", merchantDomain: "audioworld.com", category: "electronics", rating: 4.6, reviewCount: 8900, deliveryDays: 4, freeShipping: true },
68
+ ];
69
+ async search(query, options) {
70
+ const q = query.toLowerCase();
71
+ let results = this.catalog.filter(p => p.title.toLowerCase().includes(q) ||
72
+ p.category?.toLowerCase().includes(q) ||
73
+ q.includes(p.category?.toLowerCase() ?? ""));
74
+ // If no keyword match, return all (simulating broad search)
75
+ if (results.length === 0)
76
+ results = [...this.catalog];
77
+ // Apply filters
78
+ if (options?.minPrice !== undefined)
79
+ results = results.filter(p => p.price >= options.minPrice);
80
+ if (options?.maxPrice !== undefined)
81
+ results = results.filter(p => p.price <= options.maxPrice);
82
+ if (options?.category)
83
+ results = results.filter(p => p.category === options.category);
84
+ if (options?.freeShippingOnly)
85
+ results = results.filter(p => p.freeShipping);
86
+ // Sort
87
+ if (options?.sortBy === "price_asc")
88
+ results.sort((a, b) => a.price - b.price);
89
+ else if (options?.sortBy === "price_desc")
90
+ results.sort((a, b) => b.price - a.price);
91
+ else if (options?.sortBy === "rating")
92
+ results.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0));
93
+ return results.slice(0, options?.limit ?? 10);
94
+ }
95
+ async getProduct(productId) {
96
+ return this.catalog.find(p => p.productId === productId) ?? null;
97
+ }
98
+ async executePurchase(product, _deliveryInstructions) {
99
+ return {
100
+ externalOrderId: `mock_order_${++this.counter}`,
101
+ status: "confirmed",
102
+ trackingUrl: `https://example.com/track/${this.counter}`,
103
+ };
104
+ }
105
+ async checkStatus(externalOrderId) {
106
+ return {
107
+ status: "delivered",
108
+ trackingNumber: `TRACK${externalOrderId.replace(/\D/g, "")}`,
109
+ trackingUrl: `https://example.com/track/${externalOrderId}`,
110
+ deliveredAt: new Date().toISOString(),
111
+ };
112
+ }
113
+ }
114
+ exports.MockCommerceProvider = MockCommerceProvider;
115
+ // ─── Commerce Engine ────────────────────────────────────────────────────────
116
+ class CommerceEngine {
117
+ agent; // MnemoPayLite — avoid circular import
118
+ provider;
119
+ mandate = null;
120
+ orders = new Map();
121
+ totalSpent = 0;
122
+ approvalCallback = null;
123
+ externalOrderMap = new Map(); // externalOrderId → orderId
124
+ searchLimiter = new CommerceRateLimiter(20, 60_000); // 20 searches/min
125
+ purchaseLimiter = new CommerceRateLimiter(5, 60_000); // 5 purchases/min
126
+ constructor(agent, provider) {
127
+ this.agent = agent;
128
+ this.provider = provider ?? new MockCommerceProvider();
129
+ }
130
+ // ── Mandate Management ──────────────────────────────────────────────────
131
+ /**
132
+ * Set a shopping mandate that defines what this agent can buy.
133
+ * All purchases are validated against this mandate.
134
+ */
135
+ setMandate(mandate) {
136
+ if (!mandate.budget || mandate.budget <= 0) {
137
+ throw new Error("Mandate budget must be positive");
138
+ }
139
+ if (!mandate.issuedBy) {
140
+ throw new Error("Mandate must have an issuer (issuedBy)");
141
+ }
142
+ this.mandate = {
143
+ ...mandate,
144
+ maxPerItem: mandate.maxPerItem ?? mandate.budget,
145
+ expiresAt: mandate.expiresAt ?? new Date(Date.now() + 24 * 60 * 60_000).toISOString(),
146
+ };
147
+ this.totalSpent = 0;
148
+ this.agent.audit("commerce:mandate:set", {
149
+ budget: mandate.budget,
150
+ categories: mandate.categories,
151
+ issuedBy: mandate.issuedBy,
152
+ expiresAt: this.mandate.expiresAt,
153
+ });
154
+ }
155
+ /** Get the current active mandate */
156
+ getMandate() {
157
+ return this.mandate;
158
+ }
159
+ /** Remaining budget under current mandate */
160
+ get remainingBudget() {
161
+ if (!this.mandate)
162
+ return 0;
163
+ return Math.max(0, this.mandate.budget - this.totalSpent);
164
+ }
165
+ /** Set a callback for user approval of high-value purchases */
166
+ onApprovalRequired(callback) {
167
+ this.approvalCallback = callback;
168
+ }
169
+ // ── Product Search ──────────────────────────────────────────────────────
170
+ /**
171
+ * Search for products. Uses agent memory to personalize results
172
+ * (e.g., preferred brands, past purchase satisfaction).
173
+ */
174
+ async search(query, options) {
175
+ if (!this.mandate) {
176
+ throw new Error("No shopping mandate set. Call setMandate() first.");
177
+ }
178
+ // Security: rate limit searches
179
+ this.searchLimiter.check("search");
180
+ // Check mandate expiry and clear stale mandate
181
+ if (new Date() > new Date(this.mandate.expiresAt)) {
182
+ this.mandate = null;
183
+ throw new Error("Shopping mandate has expired");
184
+ }
185
+ // Apply mandate constraints to search
186
+ const constrainedOptions = {
187
+ ...options,
188
+ maxPrice: options?.maxPrice
189
+ ? Math.min(options.maxPrice, this.mandate.maxPerItem)
190
+ : this.mandate.maxPerItem,
191
+ };
192
+ // Recall user preferences from memory to enhance search
193
+ let preferences = "";
194
+ try {
195
+ const memories = await this.agent.recall(query, 3);
196
+ if (memories.length > 0) {
197
+ preferences = memories.map((m) => m.content).join("; ");
198
+ }
199
+ }
200
+ catch {
201
+ // Memory recall is best-effort for commerce
202
+ }
203
+ const results = await this.provider.search(query, constrainedOptions);
204
+ // Filter by mandate restrictions
205
+ return results.filter(p => this.validateProduct(p) === null);
206
+ }
207
+ // ── Purchase Flow ───────────────────────────────────────────────────────
208
+ /**
209
+ * Create a purchase order for a product. Validates against mandate,
210
+ * creates escrow hold, optionally requests user approval.
211
+ */
212
+ async purchase(product, deliveryInstructions) {
213
+ if (!this.mandate) {
214
+ throw new Error("No shopping mandate set. Call setMandate() first.");
215
+ }
216
+ // Security: rate limit purchases
217
+ this.purchaseLimiter.check("purchase");
218
+ // Validate mandate expiry and clear stale mandate
219
+ if (new Date() > new Date(this.mandate.expiresAt)) {
220
+ this.mandate = null;
221
+ throw new Error("Shopping mandate has expired");
222
+ }
223
+ // Validate product against mandate
224
+ const violation = this.validateProduct(product);
225
+ if (violation) {
226
+ throw new Error(`Mandate violation: ${violation}`);
227
+ }
228
+ // Check remaining budget
229
+ if (product.price > this.remainingBudget) {
230
+ throw new Error(`Insufficient mandate budget: $${product.price.toFixed(2)} exceeds remaining $${this.remainingBudget.toFixed(2)}`);
231
+ }
232
+ const orderId = `order_${this.agent.agentId}_${Date.now()}_${node_crypto_1.default.randomUUID().slice(0, 8)}`;
233
+ const now = new Date();
234
+ const order = {
235
+ id: orderId,
236
+ agentId: this.agent.agentId,
237
+ product,
238
+ txId: "", // Set after escrow
239
+ status: "pending_approval",
240
+ deliveryInstructions,
241
+ createdAt: now,
242
+ updatedAt: now,
243
+ mandate: { ...this.mandate },
244
+ approved: false,
245
+ };
246
+ // Check if user approval is needed
247
+ const threshold = this.mandate.approvalThreshold;
248
+ if (threshold !== undefined && product.price >= threshold && this.approvalCallback) {
249
+ const approved = await this.approvalCallback(order);
250
+ if (!approved) {
251
+ order.status = "cancelled";
252
+ order.failureReason = "User declined approval";
253
+ this.orders.set(orderId, order);
254
+ this.agent.audit("commerce:purchase:declined", { orderId, product: product.title, price: product.price });
255
+ return order;
256
+ }
257
+ }
258
+ order.approved = true;
259
+ // Create escrow hold via MnemoPay charge
260
+ const tx = await this.agent.charge(product.price, `Purchase: ${product.title} from ${product.merchant}`);
261
+ order.txId = tx.id;
262
+ order.status = "escrowed";
263
+ order.updatedAt = new Date();
264
+ this.orders.set(orderId, order);
265
+ this.agent.audit("commerce:purchase:escrowed", {
266
+ orderId,
267
+ txId: tx.id,
268
+ product: product.title,
269
+ price: product.price,
270
+ merchant: product.merchant,
271
+ });
272
+ // Execute the actual purchase via provider
273
+ try {
274
+ const result = await this.provider.executePurchase(product, deliveryInstructions);
275
+ order.status = "purchased";
276
+ order.trackingUrl = result.trackingUrl;
277
+ order.updatedAt = new Date();
278
+ this.externalOrderMap.set(result.externalOrderId, orderId);
279
+ this.agent.audit("commerce:purchase:executed", {
280
+ orderId,
281
+ externalOrderId: result.externalOrderId,
282
+ status: result.status,
283
+ });
284
+ // Remember the purchase for future preference learning
285
+ await this.agent.remember(`Purchased "${product.title}" from ${product.merchant} for $${product.price.toFixed(2)}. Category: ${product.category ?? "unknown"}.`, { importance: 0.6, tags: ["purchase", product.category ?? "shopping"] });
286
+ this.totalSpent += product.price;
287
+ }
288
+ catch (err) {
289
+ // Purchase failed — refund escrow
290
+ order.status = "failed";
291
+ order.failureReason = err.message;
292
+ order.updatedAt = new Date();
293
+ try {
294
+ await this.agent.refund(tx.id);
295
+ }
296
+ catch {
297
+ // Refund best-effort, log it
298
+ this.agent.audit("commerce:refund:failed", { orderId, txId: tx.id });
299
+ }
300
+ this.agent.audit("commerce:purchase:failed", {
301
+ orderId,
302
+ reason: err.message,
303
+ });
304
+ }
305
+ return order;
306
+ }
307
+ /**
308
+ * Confirm delivery of an order. Settles the escrow, releasing funds.
309
+ */
310
+ async confirmDelivery(orderId) {
311
+ const order = this.orders.get(orderId);
312
+ if (!order)
313
+ throw new Error(`Order not found: ${orderId}`);
314
+ if (order.status === "delivered")
315
+ throw new Error("Order already delivered");
316
+ if (order.status === "cancelled" || order.status === "failed") {
317
+ throw new Error(`Cannot confirm delivery on ${order.status} order`);
318
+ }
319
+ // Settle the escrow
320
+ await this.agent.settle(order.txId);
321
+ order.status = "delivered";
322
+ order.updatedAt = new Date();
323
+ this.agent.audit("commerce:delivery:confirmed", {
324
+ orderId,
325
+ txId: order.txId,
326
+ product: order.product.title,
327
+ price: order.product.price,
328
+ });
329
+ // Reinforce memory of successful purchase
330
+ await this.agent.remember(`Delivery confirmed: "${order.product.title}" from ${order.product.merchant}. Satisfied.`, { importance: 0.5, tags: ["delivery", "satisfied"] });
331
+ return order;
332
+ }
333
+ /**
334
+ * Cancel an order and refund the escrow.
335
+ */
336
+ async cancelOrder(orderId, reason) {
337
+ const order = this.orders.get(orderId);
338
+ if (!order)
339
+ throw new Error(`Order not found: ${orderId}`);
340
+ if (order.status === "delivered")
341
+ throw new Error("Cannot cancel a delivered order");
342
+ if (order.status === "cancelled")
343
+ throw new Error("Order already cancelled");
344
+ // Refund escrow
345
+ if (order.txId) {
346
+ await this.agent.refund(order.txId);
347
+ this.totalSpent = Math.max(0, this.totalSpent - order.product.price);
348
+ }
349
+ order.status = "cancelled";
350
+ order.failureReason = reason ?? "Cancelled by user";
351
+ order.updatedAt = new Date();
352
+ this.agent.audit("commerce:order:cancelled", {
353
+ orderId,
354
+ txId: order.txId,
355
+ reason: order.failureReason,
356
+ });
357
+ return order;
358
+ }
359
+ /**
360
+ * Check delivery status of an order via the commerce provider.
361
+ */
362
+ async checkDeliveryStatus(orderId) {
363
+ const order = this.orders.get(orderId);
364
+ if (!order)
365
+ throw new Error(`Order not found: ${orderId}`);
366
+ // Find external order ID
367
+ let externalOrderId;
368
+ for (const [extId, oId] of this.externalOrderMap) {
369
+ if (oId === orderId) {
370
+ externalOrderId = extId;
371
+ break;
372
+ }
373
+ }
374
+ if (externalOrderId) {
375
+ const status = await this.provider.checkStatus(externalOrderId);
376
+ if (status.trackingNumber)
377
+ order.trackingNumber = status.trackingNumber;
378
+ if (status.trackingUrl)
379
+ order.trackingUrl = status.trackingUrl;
380
+ if (status.status === "delivered" && order.status !== "delivered") {
381
+ order.status = "shipped"; // Mark shipped, user still needs to confirmDelivery()
382
+ }
383
+ else if (status.status === "shipped" && order.status === "purchased") {
384
+ order.status = "shipped";
385
+ }
386
+ order.updatedAt = new Date();
387
+ }
388
+ return order;
389
+ }
390
+ // ── Order Management ────────────────────────────────────────────────────
391
+ /** Get an order by ID */
392
+ getOrder(orderId) {
393
+ return this.orders.get(orderId) ?? null;
394
+ }
395
+ /** List all orders */
396
+ listOrders(status) {
397
+ const all = Array.from(this.orders.values());
398
+ if (status)
399
+ return all.filter(o => o.status === status);
400
+ return all.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
401
+ }
402
+ /** Get spending summary under current mandate */
403
+ spendingSummary() {
404
+ const orders = Array.from(this.orders.values());
405
+ return {
406
+ totalSpent: this.totalSpent,
407
+ remainingBudget: this.remainingBudget,
408
+ orderCount: orders.length,
409
+ deliveredCount: orders.filter(o => o.status === "delivered").length,
410
+ pendingCount: orders.filter(o => ["escrowed", "purchased", "shipped"].includes(o.status)).length,
411
+ };
412
+ }
413
+ // ── Validation ──────────────────────────────────────────────────────────
414
+ /** Validate a product against the current mandate. Returns null if OK, or violation string. */
415
+ validateProduct(product) {
416
+ if (!this.mandate)
417
+ return "No mandate set";
418
+ // Price check
419
+ if (product.price > this.mandate.maxPerItem) {
420
+ return `Price $${product.price.toFixed(2)} exceeds per-item limit $${this.mandate.maxPerItem.toFixed(2)}`;
421
+ }
422
+ // Category check
423
+ const cat = product.category?.toLowerCase();
424
+ if (cat && this.mandate.blockedCategories?.length) {
425
+ if (this.mandate.blockedCategories.some(c => c.toLowerCase() === cat)) {
426
+ return `Category "${product.category}" is blocked`;
427
+ }
428
+ }
429
+ if (cat && this.mandate.categories?.length) {
430
+ if (!this.mandate.categories.some(c => c.toLowerCase() === cat)) {
431
+ return `Category "${product.category}" not in allowed list`;
432
+ }
433
+ }
434
+ // Merchant check — NFKC normalize to prevent Unicode homoglyph bypass
435
+ const domain = product.merchantDomain?.normalize("NFKC").toLowerCase().replace(/[^\x20-\x7E]/g, "");
436
+ if (domain && this.mandate.blockedMerchants?.length) {
437
+ if (this.mandate.blockedMerchants.some(m => m.normalize("NFKC").toLowerCase().replace(/[^\x20-\x7E]/g, "") === domain)) {
438
+ return `Merchant "${product.merchant}" is blocked`;
439
+ }
440
+ }
441
+ if (domain && this.mandate.allowedMerchants?.length) {
442
+ if (!this.mandate.allowedMerchants.some(m => m.normalize("NFKC").toLowerCase().replace(/[^\x20-\x7E]/g, "") === domain)) {
443
+ return `Merchant "${product.merchant}" not in allowed list`;
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+ }
449
+ exports.CommerceEngine = CommerceEngine;
450
+ //# sourceMappingURL=commerce.js.map