@portel/photon-core 1.5.0 → 2.1.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.
Files changed (163) hide show
  1. package/dist/auto-ui.js +1 -1
  2. package/dist/auto-ui.js.map +1 -1
  3. package/dist/base.d.ts +1 -1
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +2 -2
  6. package/dist/base.js.map +1 -1
  7. package/dist/cli-ui-renderer.js +1 -1
  8. package/dist/cli-ui-renderer.js.map +1 -1
  9. package/dist/design-system/index.d.ts +21 -0
  10. package/dist/design-system/index.d.ts.map +1 -0
  11. package/dist/design-system/index.js +27 -0
  12. package/dist/design-system/index.js.map +1 -0
  13. package/dist/design-system/tokens.d.ts +149 -0
  14. package/dist/design-system/tokens.d.ts.map +1 -0
  15. package/dist/design-system/tokens.js +413 -0
  16. package/dist/design-system/tokens.js.map +1 -0
  17. package/dist/design-system/transaction-ui.d.ts +70 -0
  18. package/dist/design-system/transaction-ui.d.ts.map +1 -0
  19. package/dist/design-system/transaction-ui.js +982 -0
  20. package/dist/design-system/transaction-ui.js.map +1 -0
  21. package/dist/generator.d.ts +56 -6
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/index.d.ts +6 -7
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +46 -56
  27. package/dist/index.js.map +1 -1
  28. package/dist/io.d.ts +103 -2
  29. package/dist/io.d.ts.map +1 -1
  30. package/dist/io.js +37 -1
  31. package/dist/io.js.map +1 -1
  32. package/dist/rendering/components.d.ts +29 -0
  33. package/dist/rendering/components.d.ts.map +1 -0
  34. package/dist/rendering/components.js +773 -0
  35. package/dist/rendering/components.js.map +1 -0
  36. package/dist/rendering/field-analyzer.d.ts +48 -0
  37. package/dist/rendering/field-analyzer.d.ts.map +1 -0
  38. package/dist/rendering/field-analyzer.js +270 -0
  39. package/dist/rendering/field-analyzer.js.map +1 -0
  40. package/dist/rendering/field-renderers.d.ts +64 -0
  41. package/dist/rendering/field-renderers.d.ts.map +1 -0
  42. package/dist/rendering/field-renderers.js +317 -0
  43. package/dist/rendering/field-renderers.js.map +1 -0
  44. package/dist/rendering/index.d.ts +28 -0
  45. package/dist/rendering/index.d.ts.map +1 -0
  46. package/dist/rendering/index.js +60 -0
  47. package/dist/rendering/index.js.map +1 -0
  48. package/dist/rendering/layout-selector.d.ts +48 -0
  49. package/dist/rendering/layout-selector.d.ts.map +1 -0
  50. package/dist/rendering/layout-selector.js +347 -0
  51. package/dist/rendering/layout-selector.js.map +1 -0
  52. package/dist/rendering/template-engine.d.ts +41 -0
  53. package/dist/rendering/template-engine.d.ts.map +1 -0
  54. package/dist/rendering/template-engine.js +236 -0
  55. package/dist/rendering/template-engine.js.map +1 -0
  56. package/dist/schema-extractor.d.ts +30 -0
  57. package/dist/schema-extractor.d.ts.map +1 -1
  58. package/dist/schema-extractor.js +205 -12
  59. package/dist/schema-extractor.js.map +1 -1
  60. package/dist/stateful.js +1 -1
  61. package/dist/stateful.js.map +1 -1
  62. package/dist/types.d.ts +9 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/dist/ucp/ap2/handlers.d.ts +242 -0
  66. package/dist/ucp/ap2/handlers.d.ts.map +1 -0
  67. package/dist/ucp/ap2/handlers.js +482 -0
  68. package/dist/ucp/ap2/handlers.js.map +1 -0
  69. package/dist/ucp/ap2/mandates.d.ts +95 -0
  70. package/dist/ucp/ap2/mandates.d.ts.map +1 -0
  71. package/dist/ucp/ap2/mandates.js +234 -0
  72. package/dist/ucp/ap2/mandates.js.map +1 -0
  73. package/dist/ucp/ap2/types.d.ts +305 -0
  74. package/dist/ucp/ap2/types.d.ts.map +1 -0
  75. package/dist/ucp/ap2/types.js +8 -0
  76. package/dist/ucp/ap2/types.js.map +1 -0
  77. package/dist/ucp/capabilities/checkout.d.ts +118 -0
  78. package/dist/ucp/capabilities/checkout.d.ts.map +1 -0
  79. package/dist/ucp/capabilities/checkout.js +344 -0
  80. package/dist/ucp/capabilities/checkout.js.map +1 -0
  81. package/dist/ucp/capabilities/identity.d.ts +130 -0
  82. package/dist/ucp/capabilities/identity.d.ts.map +1 -0
  83. package/dist/ucp/capabilities/identity.js +290 -0
  84. package/dist/ucp/capabilities/identity.js.map +1 -0
  85. package/dist/ucp/capabilities/order.d.ts +142 -0
  86. package/dist/ucp/capabilities/order.d.ts.map +1 -0
  87. package/dist/ucp/capabilities/order.js +383 -0
  88. package/dist/ucp/capabilities/order.js.map +1 -0
  89. package/dist/ucp/index.d.ts +18 -0
  90. package/dist/ucp/index.d.ts.map +1 -0
  91. package/dist/ucp/index.js +19 -0
  92. package/dist/ucp/index.js.map +1 -0
  93. package/dist/ucp/manifest.d.ts +62 -0
  94. package/dist/ucp/manifest.d.ts.map +1 -0
  95. package/dist/ucp/manifest.js +180 -0
  96. package/dist/ucp/manifest.js.map +1 -0
  97. package/dist/ucp/types.d.ts +327 -0
  98. package/dist/ucp/types.d.ts.map +1 -0
  99. package/dist/ucp/types.js +8 -0
  100. package/dist/ucp/types.js.map +1 -0
  101. package/package.json +3 -4
  102. package/src/auto-ui.ts +1 -1
  103. package/src/base.ts +2 -2
  104. package/src/cli-ui-renderer.ts +1 -1
  105. package/src/design-system/index.ts +30 -0
  106. package/src/design-system/tokens.ts +451 -0
  107. package/src/design-system/transaction-ui.ts +1038 -0
  108. package/src/generator.ts +58 -2
  109. package/src/index.ts +135 -124
  110. package/src/io.ts +108 -3
  111. package/src/rendering/components.ts +785 -0
  112. package/src/rendering/field-analyzer.ts +299 -0
  113. package/src/rendering/field-renderers.ts +356 -0
  114. package/src/rendering/index.ts +63 -0
  115. package/src/rendering/layout-selector.ts +390 -0
  116. package/src/rendering/template-engine.ts +254 -0
  117. package/src/schema-extractor.ts +225 -12
  118. package/src/stateful.ts +1 -1
  119. package/src/types.ts +10 -1
  120. package/src/ucp/ap2/handlers.ts +779 -0
  121. package/src/ucp/ap2/mandates.ts +354 -0
  122. package/src/ucp/ap2/types.ts +441 -0
  123. package/src/ucp/capabilities/checkout.ts +497 -0
  124. package/src/ucp/capabilities/identity.ts +425 -0
  125. package/src/ucp/capabilities/order.ts +549 -0
  126. package/src/ucp/index.ts +27 -0
  127. package/src/ucp/manifest.ts +257 -0
  128. package/src/ucp/types.ts +454 -0
  129. package/dist/cli-formatter.d.ts +0 -92
  130. package/dist/cli-formatter.d.ts.map +0 -1
  131. package/dist/cli-formatter.js +0 -486
  132. package/dist/cli-formatter.js.map +0 -1
  133. package/dist/context.d.ts +0 -6
  134. package/dist/context.d.ts.map +0 -1
  135. package/dist/context.js +0 -3
  136. package/dist/context.js.map +0 -1
  137. package/dist/elicit.d.ts +0 -93
  138. package/dist/elicit.d.ts.map +0 -1
  139. package/dist/elicit.js +0 -373
  140. package/dist/elicit.js.map +0 -1
  141. package/dist/mcp-client.d.ts +0 -218
  142. package/dist/mcp-client.d.ts.map +0 -1
  143. package/dist/mcp-client.js +0 -424
  144. package/dist/mcp-client.js.map +0 -1
  145. package/dist/mcp-sdk-transport.d.ts +0 -88
  146. package/dist/mcp-sdk-transport.d.ts.map +0 -1
  147. package/dist/mcp-sdk-transport.js +0 -360
  148. package/dist/mcp-sdk-transport.js.map +0 -1
  149. package/dist/photon-config.d.ts +0 -86
  150. package/dist/photon-config.d.ts.map +0 -1
  151. package/dist/photon-config.js +0 -156
  152. package/dist/photon-config.js.map +0 -1
  153. package/dist/progress.d.ts +0 -93
  154. package/dist/progress.d.ts.map +0 -1
  155. package/dist/progress.js +0 -195
  156. package/dist/progress.js.map +0 -1
  157. package/src/cli-formatter.ts +0 -579
  158. package/src/context.ts +0 -7
  159. package/src/elicit.ts +0 -438
  160. package/src/mcp-client.ts +0 -561
  161. package/src/mcp-sdk-transport.ts +0 -449
  162. package/src/photon-config.ts +0 -201
  163. package/src/progress.ts +0 -224
@@ -0,0 +1,549 @@
1
+ /**
2
+ * UCP Order Management Capability Implementation
3
+ *
4
+ * Provides order tracking, returns, and webhook handling.
5
+ */
6
+
7
+ import * as crypto from 'crypto';
8
+ import {
9
+ Order,
10
+ OrderStatus,
11
+ Shipment,
12
+ ShipmentStatus,
13
+ TrackingEvent,
14
+ ReturnRequest,
15
+ Adjustment,
16
+ WebhookRegistration,
17
+ WebhookEventType,
18
+ WebhookPayload,
19
+ Money
20
+ } from '../types.js';
21
+
22
+ // ============================================================================
23
+ // Order Storage Interface
24
+ // ============================================================================
25
+
26
+ export interface OrderStorage {
27
+ get(orderId: string): Promise<Order | null>;
28
+ set(order: Order): Promise<void>;
29
+ list(params?: {
30
+ merchantId?: string;
31
+ status?: OrderStatus[];
32
+ limit?: number;
33
+ offset?: number;
34
+ }): Promise<{ orders: Order[]; total: number }>;
35
+ delete(orderId: string): Promise<void>;
36
+ }
37
+
38
+ /**
39
+ * In-memory order storage (for development/testing)
40
+ */
41
+ export class MemoryOrderStorage implements OrderStorage {
42
+ private orders = new Map<string, Order>();
43
+
44
+ async get(orderId: string): Promise<Order | null> {
45
+ return this.orders.get(orderId) || null;
46
+ }
47
+
48
+ async set(order: Order): Promise<void> {
49
+ this.orders.set(order.id, order);
50
+ }
51
+
52
+ async list(params?: {
53
+ merchantId?: string;
54
+ status?: OrderStatus[];
55
+ limit?: number;
56
+ offset?: number;
57
+ }): Promise<{ orders: Order[]; total: number }> {
58
+ let orders = Array.from(this.orders.values());
59
+
60
+ if (params?.merchantId) {
61
+ orders = orders.filter(o => o.merchantId === params.merchantId);
62
+ }
63
+
64
+ if (params?.status?.length) {
65
+ orders = orders.filter(o => params.status!.includes(o.status));
66
+ }
67
+
68
+ // Sort by creation date descending
69
+ orders.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
70
+
71
+ const total = orders.length;
72
+ const offset = params?.offset || 0;
73
+ const limit = params?.limit || 50;
74
+
75
+ return {
76
+ orders: orders.slice(offset, offset + limit),
77
+ total
78
+ };
79
+ }
80
+
81
+ async delete(orderId: string): Promise<void> {
82
+ this.orders.delete(orderId);
83
+ }
84
+ }
85
+
86
+ // ============================================================================
87
+ // Webhook Dispatcher
88
+ // ============================================================================
89
+
90
+ export interface WebhookDispatcher {
91
+ dispatch(webhook: WebhookRegistration, payload: WebhookPayload): Promise<boolean>;
92
+ }
93
+
94
+ /**
95
+ * Default webhook dispatcher using fetch
96
+ */
97
+ export class HttpWebhookDispatcher implements WebhookDispatcher {
98
+ async dispatch(webhook: WebhookRegistration, payload: WebhookPayload): Promise<boolean> {
99
+ try {
100
+ // Generate signature
101
+ const signature = this.generateSignature(payload, webhook.secret);
102
+
103
+ const response = await fetch(webhook.url, {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'X-UCP-Signature': signature,
108
+ 'X-UCP-Event': payload.type
109
+ },
110
+ body: JSON.stringify(payload)
111
+ });
112
+
113
+ return response.ok;
114
+ } catch (error) {
115
+ console.error(`Webhook dispatch failed: ${error}`);
116
+ return false;
117
+ }
118
+ }
119
+
120
+ private generateSignature(payload: WebhookPayload, secret: string): string {
121
+ const hmac = crypto.createHmac('sha256', secret);
122
+ hmac.update(JSON.stringify(payload));
123
+ return `sha256=${hmac.digest('hex')}`;
124
+ }
125
+ }
126
+
127
+ // ============================================================================
128
+ // Order Service
129
+ // ============================================================================
130
+
131
+ export interface OrderServiceConfig {
132
+ merchantId: string;
133
+ }
134
+
135
+ export class OrderService {
136
+ private storage: OrderStorage;
137
+ private webhooks: WebhookRegistration[] = [];
138
+ private dispatcher: WebhookDispatcher;
139
+ private config: OrderServiceConfig;
140
+
141
+ constructor(
142
+ config: OrderServiceConfig,
143
+ storage?: OrderStorage,
144
+ dispatcher?: WebhookDispatcher
145
+ ) {
146
+ this.config = config;
147
+ this.storage = storage || new MemoryOrderStorage();
148
+ this.dispatcher = dispatcher || new HttpWebhookDispatcher();
149
+ }
150
+
151
+ // --------------------------------------------------------------------------
152
+ // Order Retrieval
153
+ // --------------------------------------------------------------------------
154
+
155
+ /**
156
+ * Get order by ID
157
+ */
158
+ async getOrder(orderId: string): Promise<Order> {
159
+ const order = await this.storage.get(orderId);
160
+ if (!order) {
161
+ throw new Error(`Order not found: ${orderId}`);
162
+ }
163
+ return order;
164
+ }
165
+
166
+ /**
167
+ * List orders with optional filters
168
+ */
169
+ async listOrders(params?: {
170
+ status?: OrderStatus[];
171
+ limit?: number;
172
+ cursor?: string;
173
+ }): Promise<{ orders: Order[]; nextCursor?: string }> {
174
+ const offset = params?.cursor ? parseInt(params.cursor, 10) : 0;
175
+ const limit = params?.limit || 20;
176
+
177
+ const result = await this.storage.list({
178
+ merchantId: this.config.merchantId,
179
+ status: params?.status,
180
+ limit: limit + 1, // Fetch one extra to check for more
181
+ offset
182
+ });
183
+
184
+ const hasMore = result.orders.length > limit;
185
+ const orders = hasMore ? result.orders.slice(0, limit) : result.orders;
186
+
187
+ return {
188
+ orders,
189
+ nextCursor: hasMore ? String(offset + limit) : undefined
190
+ };
191
+ }
192
+
193
+ // --------------------------------------------------------------------------
194
+ // Order Updates
195
+ // --------------------------------------------------------------------------
196
+
197
+ /**
198
+ * Update order status
199
+ */
200
+ async updateStatus(orderId: string, status: OrderStatus, reason?: string): Promise<Order> {
201
+ const order = await this.getOrder(orderId);
202
+ const previousStatus = order.status;
203
+
204
+ order.status = status;
205
+ order.updatedAt = new Date().toISOString();
206
+
207
+ if (reason && status === 'cancelled') {
208
+ order.adjustments.push({
209
+ id: `adj_${crypto.randomUUID()}`,
210
+ type: 'refund',
211
+ amount: order.totals.total,
212
+ reason,
213
+ createdAt: new Date().toISOString()
214
+ });
215
+ }
216
+
217
+ await this.storage.set(order);
218
+
219
+ // Dispatch webhook
220
+ await this.dispatchWebhook('order.updated', {
221
+ order,
222
+ previousStatus,
223
+ newStatus: status
224
+ });
225
+
226
+ return order;
227
+ }
228
+
229
+ /**
230
+ * Cancel order
231
+ */
232
+ async cancelOrder(orderId: string, reason?: string): Promise<Order> {
233
+ const order = await this.getOrder(orderId);
234
+
235
+ if (!['pending', 'confirmed', 'processing'].includes(order.status)) {
236
+ throw new Error(`Cannot cancel order in ${order.status} status`);
237
+ }
238
+
239
+ return this.updateStatus(orderId, 'cancelled', reason || 'Cancelled by customer');
240
+ }
241
+
242
+ // --------------------------------------------------------------------------
243
+ // Shipment Tracking
244
+ // --------------------------------------------------------------------------
245
+
246
+ /**
247
+ * Add shipment to order
248
+ */
249
+ async addShipment(orderId: string, shipment: Omit<Shipment, 'id' | 'events'>): Promise<Order> {
250
+ const order = await this.getOrder(orderId);
251
+
252
+ const newShipment: Shipment = {
253
+ ...shipment,
254
+ id: `ship_${crypto.randomUUID()}`,
255
+ events: [{
256
+ timestamp: new Date().toISOString(),
257
+ status: 'label_created',
258
+ description: 'Shipping label created'
259
+ }]
260
+ };
261
+
262
+ order.shipments.push(newShipment);
263
+ order.status = 'shipped';
264
+ order.updatedAt = new Date().toISOString();
265
+
266
+ await this.storage.set(order);
267
+
268
+ // Dispatch webhook
269
+ await this.dispatchWebhook('shipment.created', {
270
+ order,
271
+ shipment: newShipment
272
+ });
273
+
274
+ return order;
275
+ }
276
+
277
+ /**
278
+ * Update shipment status
279
+ */
280
+ async updateShipment(
281
+ orderId: string,
282
+ shipmentId: string,
283
+ update: {
284
+ status: ShipmentStatus;
285
+ location?: string;
286
+ description: string;
287
+ }
288
+ ): Promise<Order> {
289
+ const order = await this.getOrder(orderId);
290
+ const shipment = order.shipments.find(s => s.id === shipmentId);
291
+
292
+ if (!shipment) {
293
+ throw new Error(`Shipment not found: ${shipmentId}`);
294
+ }
295
+
296
+ shipment.status = update.status;
297
+ shipment.events.push({
298
+ timestamp: new Date().toISOString(),
299
+ status: update.status,
300
+ location: update.location,
301
+ description: update.description
302
+ });
303
+
304
+ // Update order status if delivered
305
+ if (update.status === 'delivered') {
306
+ const allDelivered = order.shipments.every(s => s.status === 'delivered');
307
+ if (allDelivered) {
308
+ order.status = 'delivered';
309
+ }
310
+ }
311
+
312
+ order.updatedAt = new Date().toISOString();
313
+ await this.storage.set(order);
314
+
315
+ // Dispatch webhook
316
+ const eventType = update.status === 'delivered' ? 'delivery.completed' : 'shipment.updated';
317
+ await this.dispatchWebhook(eventType, {
318
+ order,
319
+ shipment
320
+ });
321
+
322
+ return order;
323
+ }
324
+
325
+ /**
326
+ * Get shipment tracking info
327
+ */
328
+ async trackShipment(orderId: string, shipmentId?: string): Promise<Shipment[]> {
329
+ const order = await this.getOrder(orderId);
330
+
331
+ if (shipmentId) {
332
+ const shipment = order.shipments.find(s => s.id === shipmentId);
333
+ if (!shipment) {
334
+ throw new Error(`Shipment not found: ${shipmentId}`);
335
+ }
336
+ return [shipment];
337
+ }
338
+
339
+ return order.shipments;
340
+ }
341
+
342
+ // --------------------------------------------------------------------------
343
+ // Returns & Refunds
344
+ // --------------------------------------------------------------------------
345
+
346
+ /**
347
+ * Initiate return request
348
+ */
349
+ async initiateReturn(orderId: string, params: {
350
+ items: { lineItemId: string; quantity: number; reason: string }[];
351
+ }): Promise<ReturnRequest> {
352
+ const order = await this.getOrder(orderId);
353
+
354
+ if (order.status !== 'delivered') {
355
+ throw new Error('Can only return delivered orders');
356
+ }
357
+
358
+ // Validate items
359
+ for (const item of params.items) {
360
+ const lineItem = order.lineItems.find(li => li.id === item.lineItemId);
361
+ if (!lineItem) {
362
+ throw new Error(`Line item not found: ${item.lineItemId}`);
363
+ }
364
+ if (item.quantity > lineItem.quantity) {
365
+ throw new Error(`Return quantity exceeds order quantity for ${lineItem.label}`);
366
+ }
367
+ }
368
+
369
+ // Calculate refund amount
370
+ const refundAmount: Money = {
371
+ amount: params.items.reduce((sum, item) => {
372
+ const lineItem = order.lineItems.find(li => li.id === item.lineItemId)!;
373
+ return sum + (lineItem.unitPrice.amount * item.quantity);
374
+ }, 0),
375
+ currency: order.totals.total.currency
376
+ };
377
+
378
+ const returnRequest: ReturnRequest = {
379
+ id: `ret_${crypto.randomUUID()}`,
380
+ status: 'requested',
381
+ items: params.items,
382
+ createdAt: new Date().toISOString(),
383
+ updatedAt: new Date().toISOString(),
384
+ refundAmount
385
+ };
386
+
387
+ order.returns.push(returnRequest);
388
+ order.updatedAt = new Date().toISOString();
389
+
390
+ await this.storage.set(order);
391
+
392
+ // Dispatch webhook
393
+ await this.dispatchWebhook('return.requested', {
394
+ order,
395
+ return: returnRequest
396
+ });
397
+
398
+ return returnRequest;
399
+ }
400
+
401
+ /**
402
+ * Approve return request
403
+ */
404
+ async approveReturn(orderId: string, returnId: string, params?: {
405
+ returnLabel?: {
406
+ carrier: string;
407
+ trackingNumber: string;
408
+ labelUrl: string;
409
+ };
410
+ }): Promise<ReturnRequest> {
411
+ const order = await this.getOrder(orderId);
412
+ const returnRequest = order.returns.find(r => r.id === returnId);
413
+
414
+ if (!returnRequest) {
415
+ throw new Error(`Return request not found: ${returnId}`);
416
+ }
417
+
418
+ if (returnRequest.status !== 'requested') {
419
+ throw new Error(`Cannot approve return in ${returnRequest.status} status`);
420
+ }
421
+
422
+ returnRequest.status = 'approved';
423
+ returnRequest.updatedAt = new Date().toISOString();
424
+ returnRequest.returnLabel = params?.returnLabel;
425
+
426
+ order.updatedAt = new Date().toISOString();
427
+ await this.storage.set(order);
428
+
429
+ // Dispatch webhook
430
+ await this.dispatchWebhook('return.approved', {
431
+ order,
432
+ return: returnRequest
433
+ });
434
+
435
+ return returnRequest;
436
+ }
437
+
438
+ /**
439
+ * Process refund for return
440
+ */
441
+ async processRefund(orderId: string, returnId: string): Promise<Adjustment> {
442
+ const order = await this.getOrder(orderId);
443
+ const returnRequest = order.returns.find(r => r.id === returnId);
444
+
445
+ if (!returnRequest) {
446
+ throw new Error(`Return request not found: ${returnId}`);
447
+ }
448
+
449
+ if (returnRequest.status !== 'received' && returnRequest.status !== 'approved') {
450
+ throw new Error(`Cannot process refund for return in ${returnRequest.status} status`);
451
+ }
452
+
453
+ const adjustment: Adjustment = {
454
+ id: `adj_${crypto.randomUUID()}`,
455
+ type: 'refund',
456
+ amount: returnRequest.refundAmount!,
457
+ reason: `Return refund for ${returnRequest.items.length} item(s)`,
458
+ createdAt: new Date().toISOString(),
459
+ lineItemIds: returnRequest.items.map(i => i.lineItemId)
460
+ };
461
+
462
+ returnRequest.status = 'refunded';
463
+ returnRequest.updatedAt = new Date().toISOString();
464
+
465
+ order.adjustments.push(adjustment);
466
+ order.updatedAt = new Date().toISOString();
467
+
468
+ // Check if fully refunded
469
+ const totalRefunded = order.adjustments
470
+ .filter(a => a.type === 'refund')
471
+ .reduce((sum, a) => sum + a.amount.amount, 0);
472
+
473
+ if (totalRefunded >= order.totals.total.amount) {
474
+ order.status = 'refunded';
475
+ }
476
+
477
+ await this.storage.set(order);
478
+
479
+ // Dispatch webhook
480
+ await this.dispatchWebhook('refund.created', {
481
+ order,
482
+ adjustment
483
+ });
484
+
485
+ return adjustment;
486
+ }
487
+
488
+ // --------------------------------------------------------------------------
489
+ // Webhooks
490
+ // --------------------------------------------------------------------------
491
+
492
+ /**
493
+ * Register webhook
494
+ */
495
+ async registerWebhook(params: {
496
+ url: string;
497
+ events: WebhookEventType[];
498
+ secret: string;
499
+ }): Promise<WebhookRegistration> {
500
+ const webhook: WebhookRegistration = {
501
+ id: `wh_${crypto.randomUUID()}`,
502
+ url: params.url,
503
+ events: params.events,
504
+ secret: params.secret,
505
+ active: true,
506
+ createdAt: new Date().toISOString()
507
+ };
508
+
509
+ this.webhooks.push(webhook);
510
+ return webhook;
511
+ }
512
+
513
+ /**
514
+ * List webhooks
515
+ */
516
+ async listWebhooks(): Promise<WebhookRegistration[]> {
517
+ return this.webhooks.filter(w => w.active);
518
+ }
519
+
520
+ /**
521
+ * Delete webhook
522
+ */
523
+ async deleteWebhook(webhookId: string): Promise<void> {
524
+ const index = this.webhooks.findIndex(w => w.id === webhookId);
525
+ if (index !== -1) {
526
+ this.webhooks[index].active = false;
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Dispatch webhook to registered endpoints
532
+ */
533
+ private async dispatchWebhook(type: WebhookEventType, data: any): Promise<void> {
534
+ const payload: WebhookPayload = {
535
+ id: `evt_${crypto.randomUUID()}`,
536
+ type,
537
+ timestamp: new Date().toISOString(),
538
+ data
539
+ };
540
+
541
+ const relevantWebhooks = this.webhooks.filter(
542
+ w => w.active && w.events.includes(type)
543
+ );
544
+
545
+ await Promise.all(
546
+ relevantWebhooks.map(w => this.dispatcher.dispatch(w, payload))
547
+ );
548
+ }
549
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Universal Commerce Protocol (UCP) Module
3
+ *
4
+ * Provides UCP support for Photon, enabling agentic commerce
5
+ * with checkout, identity, order management, and AP2 payments.
6
+ */
7
+
8
+ // Core UCP Types
9
+ export * from './types.js';
10
+
11
+ // AP2 Payment Types & Helpers
12
+ export * from './ap2/types.js';
13
+ export * from './ap2/mandates.js';
14
+ export * from './ap2/handlers.js';
15
+
16
+ // Manifest Generation
17
+ export * from './manifest.js';
18
+
19
+ // Capabilities
20
+ export { CheckoutService, MemorySessionStorage } from './capabilities/checkout.js';
21
+ export type { SessionStorage, CheckoutConfig, TaxCalculator, DiscountValidator, FulfillmentProvider } from './capabilities/checkout.js';
22
+
23
+ export { OrderService, MemoryOrderStorage, HttpWebhookDispatcher } from './capabilities/order.js';
24
+ export type { OrderStorage, WebhookDispatcher, OrderServiceConfig } from './capabilities/order.js';
25
+
26
+ export { IdentityService, MemoryTokenStorage } from './capabilities/identity.js';
27
+ export type { TokenStorage, IdentityServiceConfig } from './capabilities/identity.js';