@mnemopay/sdk 0.8.0 → 0.9.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.
@@ -0,0 +1,225 @@
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
+ constructor(agent: any, provider?: CommerceProvider);
177
+ /**
178
+ * Set a shopping mandate that defines what this agent can buy.
179
+ * All purchases are validated against this mandate.
180
+ */
181
+ setMandate(mandate: ShoppingMandate): void;
182
+ /** Get the current active mandate */
183
+ getMandate(): ShoppingMandate | null;
184
+ /** Remaining budget under current mandate */
185
+ get remainingBudget(): number;
186
+ /** Set a callback for user approval of high-value purchases */
187
+ onApprovalRequired(callback: ApprovalCallback): void;
188
+ /**
189
+ * Search for products. Uses agent memory to personalize results
190
+ * (e.g., preferred brands, past purchase satisfaction).
191
+ */
192
+ search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
193
+ /**
194
+ * Create a purchase order for a product. Validates against mandate,
195
+ * creates escrow hold, optionally requests user approval.
196
+ */
197
+ purchase(product: ProductResult, deliveryInstructions?: string): Promise<PurchaseOrder>;
198
+ /**
199
+ * Confirm delivery of an order. Settles the escrow, releasing funds.
200
+ */
201
+ confirmDelivery(orderId: string): Promise<PurchaseOrder>;
202
+ /**
203
+ * Cancel an order and refund the escrow.
204
+ */
205
+ cancelOrder(orderId: string, reason?: string): Promise<PurchaseOrder>;
206
+ /**
207
+ * Check delivery status of an order via the commerce provider.
208
+ */
209
+ checkDeliveryStatus(orderId: string): Promise<PurchaseOrder>;
210
+ /** Get an order by ID */
211
+ getOrder(orderId: string): PurchaseOrder | null;
212
+ /** List all orders */
213
+ listOrders(status?: PurchaseOrder["status"]): PurchaseOrder[];
214
+ /** Get spending summary under current mandate */
215
+ spendingSummary(): {
216
+ totalSpent: number;
217
+ remainingBudget: number;
218
+ orderCount: number;
219
+ deliveredCount: number;
220
+ pendingCount: number;
221
+ };
222
+ /** Validate a product against the current mandate. Returns null if OK, or violation string. */
223
+ private validateProduct;
224
+ }
225
+ //# 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,420 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.CommerceEngine = exports.MockCommerceProvider = void 0;
36
+ // ─── Mock Commerce Provider (for testing/development) ───────────────────────
37
+ class MockCommerceProvider {
38
+ name = "mock";
39
+ counter = 0;
40
+ catalog = [
41
+ { 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 },
42
+ { 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 },
43
+ { 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 },
44
+ { 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 },
45
+ { 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 },
46
+ ];
47
+ async search(query, options) {
48
+ const q = query.toLowerCase();
49
+ let results = this.catalog.filter(p => p.title.toLowerCase().includes(q) ||
50
+ p.category?.toLowerCase().includes(q) ||
51
+ q.includes(p.category?.toLowerCase() ?? ""));
52
+ // If no keyword match, return all (simulating broad search)
53
+ if (results.length === 0)
54
+ results = [...this.catalog];
55
+ // Apply filters
56
+ if (options?.minPrice !== undefined)
57
+ results = results.filter(p => p.price >= options.minPrice);
58
+ if (options?.maxPrice !== undefined)
59
+ results = results.filter(p => p.price <= options.maxPrice);
60
+ if (options?.category)
61
+ results = results.filter(p => p.category === options.category);
62
+ if (options?.freeShippingOnly)
63
+ results = results.filter(p => p.freeShipping);
64
+ // Sort
65
+ if (options?.sortBy === "price_asc")
66
+ results.sort((a, b) => a.price - b.price);
67
+ else if (options?.sortBy === "price_desc")
68
+ results.sort((a, b) => b.price - a.price);
69
+ else if (options?.sortBy === "rating")
70
+ results.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0));
71
+ return results.slice(0, options?.limit ?? 10);
72
+ }
73
+ async getProduct(productId) {
74
+ return this.catalog.find(p => p.productId === productId) ?? null;
75
+ }
76
+ async executePurchase(product, _deliveryInstructions) {
77
+ return {
78
+ externalOrderId: `mock_order_${++this.counter}`,
79
+ status: "confirmed",
80
+ trackingUrl: `https://example.com/track/${this.counter}`,
81
+ };
82
+ }
83
+ async checkStatus(externalOrderId) {
84
+ return {
85
+ status: "delivered",
86
+ trackingNumber: `TRACK${externalOrderId.replace(/\D/g, "")}`,
87
+ trackingUrl: `https://example.com/track/${externalOrderId}`,
88
+ deliveredAt: new Date().toISOString(),
89
+ };
90
+ }
91
+ }
92
+ exports.MockCommerceProvider = MockCommerceProvider;
93
+ // ─── Commerce Engine ────────────────────────────────────────────────────────
94
+ class CommerceEngine {
95
+ agent; // MnemoPayLite — avoid circular import
96
+ provider;
97
+ mandate = null;
98
+ orders = new Map();
99
+ totalSpent = 0;
100
+ approvalCallback = null;
101
+ externalOrderMap = new Map(); // externalOrderId → orderId
102
+ constructor(agent, provider) {
103
+ this.agent = agent;
104
+ this.provider = provider ?? new MockCommerceProvider();
105
+ }
106
+ // ── Mandate Management ──────────────────────────────────────────────────
107
+ /**
108
+ * Set a shopping mandate that defines what this agent can buy.
109
+ * All purchases are validated against this mandate.
110
+ */
111
+ setMandate(mandate) {
112
+ if (!mandate.budget || mandate.budget <= 0) {
113
+ throw new Error("Mandate budget must be positive");
114
+ }
115
+ if (!mandate.issuedBy) {
116
+ throw new Error("Mandate must have an issuer (issuedBy)");
117
+ }
118
+ this.mandate = {
119
+ ...mandate,
120
+ maxPerItem: mandate.maxPerItem ?? mandate.budget,
121
+ expiresAt: mandate.expiresAt ?? new Date(Date.now() + 24 * 60 * 60_000).toISOString(),
122
+ };
123
+ this.totalSpent = 0;
124
+ this.agent.audit("commerce:mandate:set", {
125
+ budget: mandate.budget,
126
+ categories: mandate.categories,
127
+ issuedBy: mandate.issuedBy,
128
+ expiresAt: this.mandate.expiresAt,
129
+ });
130
+ }
131
+ /** Get the current active mandate */
132
+ getMandate() {
133
+ return this.mandate;
134
+ }
135
+ /** Remaining budget under current mandate */
136
+ get remainingBudget() {
137
+ if (!this.mandate)
138
+ return 0;
139
+ return Math.max(0, this.mandate.budget - this.totalSpent);
140
+ }
141
+ /** Set a callback for user approval of high-value purchases */
142
+ onApprovalRequired(callback) {
143
+ this.approvalCallback = callback;
144
+ }
145
+ // ── Product Search ──────────────────────────────────────────────────────
146
+ /**
147
+ * Search for products. Uses agent memory to personalize results
148
+ * (e.g., preferred brands, past purchase satisfaction).
149
+ */
150
+ async search(query, options) {
151
+ if (!this.mandate) {
152
+ throw new Error("No shopping mandate set. Call setMandate() first.");
153
+ }
154
+ // Check mandate expiry
155
+ if (new Date() > new Date(this.mandate.expiresAt)) {
156
+ throw new Error("Shopping mandate has expired");
157
+ }
158
+ // Apply mandate constraints to search
159
+ const constrainedOptions = {
160
+ ...options,
161
+ maxPrice: options?.maxPrice
162
+ ? Math.min(options.maxPrice, this.mandate.maxPerItem)
163
+ : this.mandate.maxPerItem,
164
+ };
165
+ // Recall user preferences from memory to enhance search
166
+ let preferences = "";
167
+ try {
168
+ const memories = await this.agent.recall(query, 3);
169
+ if (memories.length > 0) {
170
+ preferences = memories.map((m) => m.content).join("; ");
171
+ }
172
+ }
173
+ catch {
174
+ // Memory recall is best-effort for commerce
175
+ }
176
+ const results = await this.provider.search(query, constrainedOptions);
177
+ // Filter by mandate restrictions
178
+ return results.filter(p => this.validateProduct(p) === null);
179
+ }
180
+ // ── Purchase Flow ───────────────────────────────────────────────────────
181
+ /**
182
+ * Create a purchase order for a product. Validates against mandate,
183
+ * creates escrow hold, optionally requests user approval.
184
+ */
185
+ async purchase(product, deliveryInstructions) {
186
+ if (!this.mandate) {
187
+ throw new Error("No shopping mandate set. Call setMandate() first.");
188
+ }
189
+ // Validate mandate expiry
190
+ if (new Date() > new Date(this.mandate.expiresAt)) {
191
+ throw new Error("Shopping mandate has expired");
192
+ }
193
+ // Validate product against mandate
194
+ const violation = this.validateProduct(product);
195
+ if (violation) {
196
+ throw new Error(`Mandate violation: ${violation}`);
197
+ }
198
+ // Check remaining budget
199
+ if (product.price > this.remainingBudget) {
200
+ throw new Error(`Insufficient mandate budget: $${product.price.toFixed(2)} exceeds remaining $${this.remainingBudget.toFixed(2)}`);
201
+ }
202
+ const orderId = `order_${this.agent.agentId}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
203
+ const now = new Date();
204
+ const order = {
205
+ id: orderId,
206
+ agentId: this.agent.agentId,
207
+ product,
208
+ txId: "", // Set after escrow
209
+ status: "pending_approval",
210
+ deliveryInstructions,
211
+ createdAt: now,
212
+ updatedAt: now,
213
+ mandate: { ...this.mandate },
214
+ approved: false,
215
+ };
216
+ // Check if user approval is needed
217
+ const threshold = this.mandate.approvalThreshold;
218
+ if (threshold !== undefined && product.price >= threshold && this.approvalCallback) {
219
+ const approved = await this.approvalCallback(order);
220
+ if (!approved) {
221
+ order.status = "cancelled";
222
+ order.failureReason = "User declined approval";
223
+ this.orders.set(orderId, order);
224
+ this.agent.audit("commerce:purchase:declined", { orderId, product: product.title, price: product.price });
225
+ return order;
226
+ }
227
+ }
228
+ order.approved = true;
229
+ // Create escrow hold via MnemoPay charge
230
+ const tx = await this.agent.charge(product.price, `Purchase: ${product.title} from ${product.merchant}`);
231
+ order.txId = tx.id;
232
+ order.status = "escrowed";
233
+ order.updatedAt = new Date();
234
+ this.orders.set(orderId, order);
235
+ this.agent.audit("commerce:purchase:escrowed", {
236
+ orderId,
237
+ txId: tx.id,
238
+ product: product.title,
239
+ price: product.price,
240
+ merchant: product.merchant,
241
+ });
242
+ // Execute the actual purchase via provider
243
+ try {
244
+ const result = await this.provider.executePurchase(product, deliveryInstructions);
245
+ order.status = "purchased";
246
+ order.trackingUrl = result.trackingUrl;
247
+ order.updatedAt = new Date();
248
+ this.externalOrderMap.set(result.externalOrderId, orderId);
249
+ this.agent.audit("commerce:purchase:executed", {
250
+ orderId,
251
+ externalOrderId: result.externalOrderId,
252
+ status: result.status,
253
+ });
254
+ // Remember the purchase for future preference learning
255
+ 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"] });
256
+ this.totalSpent += product.price;
257
+ }
258
+ catch (err) {
259
+ // Purchase failed — refund escrow
260
+ order.status = "failed";
261
+ order.failureReason = err.message;
262
+ order.updatedAt = new Date();
263
+ try {
264
+ await this.agent.refund(tx.id);
265
+ }
266
+ catch {
267
+ // Refund best-effort, log it
268
+ this.agent.audit("commerce:refund:failed", { orderId, txId: tx.id });
269
+ }
270
+ this.agent.audit("commerce:purchase:failed", {
271
+ orderId,
272
+ reason: err.message,
273
+ });
274
+ }
275
+ return order;
276
+ }
277
+ /**
278
+ * Confirm delivery of an order. Settles the escrow, releasing funds.
279
+ */
280
+ async confirmDelivery(orderId) {
281
+ const order = this.orders.get(orderId);
282
+ if (!order)
283
+ throw new Error(`Order not found: ${orderId}`);
284
+ if (order.status === "delivered")
285
+ throw new Error("Order already delivered");
286
+ if (order.status === "cancelled" || order.status === "failed") {
287
+ throw new Error(`Cannot confirm delivery on ${order.status} order`);
288
+ }
289
+ // Settle the escrow
290
+ await this.agent.settle(order.txId);
291
+ order.status = "delivered";
292
+ order.updatedAt = new Date();
293
+ this.agent.audit("commerce:delivery:confirmed", {
294
+ orderId,
295
+ txId: order.txId,
296
+ product: order.product.title,
297
+ price: order.product.price,
298
+ });
299
+ // Reinforce memory of successful purchase
300
+ await this.agent.remember(`Delivery confirmed: "${order.product.title}" from ${order.product.merchant}. Satisfied.`, { importance: 0.5, tags: ["delivery", "satisfied"] });
301
+ return order;
302
+ }
303
+ /**
304
+ * Cancel an order and refund the escrow.
305
+ */
306
+ async cancelOrder(orderId, reason) {
307
+ const order = this.orders.get(orderId);
308
+ if (!order)
309
+ throw new Error(`Order not found: ${orderId}`);
310
+ if (order.status === "delivered")
311
+ throw new Error("Cannot cancel a delivered order");
312
+ if (order.status === "cancelled")
313
+ throw new Error("Order already cancelled");
314
+ // Refund escrow
315
+ if (order.txId) {
316
+ await this.agent.refund(order.txId);
317
+ this.totalSpent = Math.max(0, this.totalSpent - order.product.price);
318
+ }
319
+ order.status = "cancelled";
320
+ order.failureReason = reason ?? "Cancelled by user";
321
+ order.updatedAt = new Date();
322
+ this.agent.audit("commerce:order:cancelled", {
323
+ orderId,
324
+ txId: order.txId,
325
+ reason: order.failureReason,
326
+ });
327
+ return order;
328
+ }
329
+ /**
330
+ * Check delivery status of an order via the commerce provider.
331
+ */
332
+ async checkDeliveryStatus(orderId) {
333
+ const order = this.orders.get(orderId);
334
+ if (!order)
335
+ throw new Error(`Order not found: ${orderId}`);
336
+ // Find external order ID
337
+ let externalOrderId;
338
+ for (const [extId, oId] of this.externalOrderMap) {
339
+ if (oId === orderId) {
340
+ externalOrderId = extId;
341
+ break;
342
+ }
343
+ }
344
+ if (externalOrderId) {
345
+ const status = await this.provider.checkStatus(externalOrderId);
346
+ if (status.trackingNumber)
347
+ order.trackingNumber = status.trackingNumber;
348
+ if (status.trackingUrl)
349
+ order.trackingUrl = status.trackingUrl;
350
+ if (status.status === "delivered" && order.status !== "delivered") {
351
+ order.status = "shipped"; // Mark shipped, user still needs to confirmDelivery()
352
+ }
353
+ else if (status.status === "shipped" && order.status === "purchased") {
354
+ order.status = "shipped";
355
+ }
356
+ order.updatedAt = new Date();
357
+ }
358
+ return order;
359
+ }
360
+ // ── Order Management ────────────────────────────────────────────────────
361
+ /** Get an order by ID */
362
+ getOrder(orderId) {
363
+ return this.orders.get(orderId) ?? null;
364
+ }
365
+ /** List all orders */
366
+ listOrders(status) {
367
+ const all = Array.from(this.orders.values());
368
+ if (status)
369
+ return all.filter(o => o.status === status);
370
+ return all.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
371
+ }
372
+ /** Get spending summary under current mandate */
373
+ spendingSummary() {
374
+ const orders = Array.from(this.orders.values());
375
+ return {
376
+ totalSpent: this.totalSpent,
377
+ remainingBudget: this.remainingBudget,
378
+ orderCount: orders.length,
379
+ deliveredCount: orders.filter(o => o.status === "delivered").length,
380
+ pendingCount: orders.filter(o => ["escrowed", "purchased", "shipped"].includes(o.status)).length,
381
+ };
382
+ }
383
+ // ── Validation ──────────────────────────────────────────────────────────
384
+ /** Validate a product against the current mandate. Returns null if OK, or violation string. */
385
+ validateProduct(product) {
386
+ if (!this.mandate)
387
+ return "No mandate set";
388
+ // Price check
389
+ if (product.price > this.mandate.maxPerItem) {
390
+ return `Price $${product.price.toFixed(2)} exceeds per-item limit $${this.mandate.maxPerItem.toFixed(2)}`;
391
+ }
392
+ // Category check
393
+ const cat = product.category?.toLowerCase();
394
+ if (cat && this.mandate.blockedCategories?.length) {
395
+ if (this.mandate.blockedCategories.some(c => c.toLowerCase() === cat)) {
396
+ return `Category "${product.category}" is blocked`;
397
+ }
398
+ }
399
+ if (cat && this.mandate.categories?.length) {
400
+ if (!this.mandate.categories.some(c => c.toLowerCase() === cat)) {
401
+ return `Category "${product.category}" not in allowed list`;
402
+ }
403
+ }
404
+ // Merchant check
405
+ const domain = product.merchantDomain?.toLowerCase();
406
+ if (domain && this.mandate.blockedMerchants?.length) {
407
+ if (this.mandate.blockedMerchants.some(m => m.toLowerCase() === domain)) {
408
+ return `Merchant "${product.merchant}" is blocked`;
409
+ }
410
+ }
411
+ if (domain && this.mandate.allowedMerchants?.length) {
412
+ if (!this.mandate.allowedMerchants.some(m => m.toLowerCase() === domain)) {
413
+ return `Merchant "${product.merchant}" not in allowed list`;
414
+ }
415
+ }
416
+ return null;
417
+ }
418
+ }
419
+ exports.CommerceEngine = CommerceEngine;
420
+ //# sourceMappingURL=commerce.js.map