ucpify 1.0.1 → 1.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.
package/dist/cli.js CHANGED
@@ -40,10 +40,11 @@ const path = __importStar(require("path"));
40
40
  const schema_1 = require("./schema");
41
41
  const express_app_1 = require("./express-app");
42
42
  const program = new commander_1.Command();
43
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
43
44
  program
44
45
  .name('ucpify')
45
46
  .description('Generate and run a UCP-compliant server for merchants')
46
- .version('1.0.0');
47
+ .version(pkg.version);
47
48
  program
48
49
  .command('init')
49
50
  .description('Create a sample merchant configuration file')
@@ -278,6 +278,16 @@ function createExpressApp(config, options) {
278
278
  });
279
279
  // Admin dashboard HTML
280
280
  app.get('/admin', (_req, res) => {
281
+ const statsScript = useDb
282
+ ? `fetch('/admin/stats').then(r=>r.json()).then(d=>{
283
+ document.getElementById('stats').innerHTML = \`
284
+ <div class="card"><div class="stat">\${Object.values(d.checkouts).reduce((a,b)=>a+b,0)}</div><div class="label">Total Checkouts</div></div>
285
+ <div class="card"><div class="stat">\${d.checkouts.completed||0}</div><div class="label">Completed</div></div>
286
+ <div class="card"><div class="stat">\${d.orders.today||0}</div><div class="label">Orders Today</div></div>
287
+ <div class="card"><div class="stat">$\${(d.revenue.total_succeeded/100).toFixed(2)}</div><div class="label">Revenue</div></div>
288
+ \`;
289
+ }).catch(e=>document.getElementById('stats').innerHTML='Error loading stats');`
290
+ : `document.getElementById('stats').innerHTML = '<div class="card" style="grid-column:1/-1"><div class="label">Running in <strong>in-memory mode</strong>. Stats require <code>--db</code> mode.</div><div style="margin-top:8px;font-size:0.85em;color:#999">Restart with: <code>ucpify serve config.json</code> (DB is on by default)</div></div>';`;
281
291
  res.send(`<!DOCTYPE html>
282
292
  <html><head><title>UCP Admin</title>
283
293
  <style>
@@ -287,20 +297,12 @@ function createExpressApp(config, options) {
287
297
  .label { color: #666; font-size: 0.9em; }
288
298
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; }
289
299
  h1 { color: #333; }
300
+ code { background: #e5e5e5; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; }
290
301
  </style>
291
302
  </head><body>
292
303
  <h1>🛒 UCP Server Admin</h1>
293
304
  <div class="grid" id="stats">Loading...</div>
294
- <script>
295
- fetch('/admin/stats').then(r=>r.json()).then(d=>{
296
- document.getElementById('stats').innerHTML = \`
297
- <div class="card"><div class="stat">\${Object.values(d.checkouts).reduce((a,b)=>a+b,0)}</div><div class="label">Total Checkouts</div></div>
298
- <div class="card"><div class="stat">\${d.checkouts.completed||0}</div><div class="label">Completed</div></div>
299
- <div class="card"><div class="stat">\${d.orders.today||0}</div><div class="label">Orders Today</div></div>
300
- <div class="card"><div class="stat">$\${(d.revenue.total_succeeded/100).toFixed(2)}</div><div class="label">Revenue</div></div>
301
- \`;
302
- }).catch(e=>document.getElementById('stats').innerHTML='Error loading stats');
303
- </script>
305
+ <script>${statsScript}</script>
304
306
  </body></html>`);
305
307
  });
306
308
  // Health check
@@ -2,6 +2,8 @@ import { MerchantConfig, CheckoutSession, Order, Buyer, Fulfillment } from './sc
2
2
  export declare class UCPServer {
3
3
  private config;
4
4
  private itemsMap;
5
+ private checkoutSessions;
6
+ private orders;
5
7
  constructor(config: MerchantConfig);
6
8
  getProfile(): {
7
9
  ucp: {
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UCPServer = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const schema_1 = require("./schema");
6
- // In-memory stores
7
- const checkoutSessions = new Map();
8
- const orders = new Map();
6
+ const stripe_1 = require("./stripe");
7
+ const paypal_1 = require("./paypal");
8
+ const logger_1 = require("./logger");
9
9
  class UCPServer {
10
10
  config;
11
11
  itemsMap;
12
+ checkoutSessions = new Map();
13
+ orders = new Map();
12
14
  constructor(config) {
13
15
  this.config = config;
14
16
  this.itemsMap = new Map(config.items.map((item) => [item.id, item]));
@@ -156,16 +158,16 @@ class UCPServer {
156
158
  continue_url: `${this.config.domain}/checkout/${id}`,
157
159
  expires_at: expiresAt,
158
160
  };
159
- checkoutSessions.set(id, session);
161
+ this.checkoutSessions.set(id, session);
160
162
  return session;
161
163
  }
162
164
  // Get checkout session
163
165
  getCheckout(id) {
164
- return checkoutSessions.get(id);
166
+ return this.checkoutSessions.get(id);
165
167
  }
166
168
  // Update checkout session
167
169
  updateCheckout(id, data) {
168
- const session = checkoutSessions.get(id);
170
+ const session = this.checkoutSessions.get(id);
169
171
  if (!session)
170
172
  return undefined;
171
173
  // Update buyer
@@ -251,18 +253,58 @@ class UCPServer {
251
253
  const newMessages = this.validateCheckout(session);
252
254
  session.messages = newMessages.length > 0 ? newMessages : undefined;
253
255
  session.status = newMessages.length > 0 ? 'incomplete' : 'ready_for_complete';
254
- checkoutSessions.set(id, session);
256
+ this.checkoutSessions.set(id, session);
255
257
  return session;
256
258
  }
257
259
  // Complete checkout (create order)
258
260
  async completeCheckout(id) {
259
- const session = checkoutSessions.get(id);
261
+ const session = this.checkoutSessions.get(id);
260
262
  if (!session)
261
263
  return { error: 'Checkout session not found' };
262
264
  if (session.status !== 'ready_for_complete') {
263
265
  return { error: 'Checkout is not ready for completion', messages: session.messages };
264
266
  }
265
267
  const orderId = `order_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 12)}`;
268
+ // Get total amount for payment
269
+ const totalAmount = session.totals.find(t => t.type === 'total')?.amount || 0;
270
+ // Payment processing - try Stripe first, then PayPal
271
+ if (totalAmount > 0) {
272
+ if ((0, stripe_1.isStripeConfigured)()) {
273
+ try {
274
+ const paymentIntent = await (0, stripe_1.createPaymentIntent)({
275
+ amount: totalAmount,
276
+ currency: session.currency,
277
+ checkoutId: id,
278
+ customerEmail: session.buyer?.email,
279
+ metadata: { order_id: orderId },
280
+ });
281
+ if (paymentIntent) {
282
+ logger_1.logger.info({ checkoutId: id, paymentIntentId: paymentIntent.id, amount: totalAmount }, 'Stripe payment intent created');
283
+ }
284
+ }
285
+ catch (error) {
286
+ logger_1.logger.error({ error, checkoutId: id }, 'Failed to create Stripe payment intent');
287
+ return { error: 'Payment processing failed' };
288
+ }
289
+ }
290
+ else if ((0, paypal_1.isPayPalConfigured)()) {
291
+ try {
292
+ const ppOrder = await (0, paypal_1.createPayPalOrder)({
293
+ amount: totalAmount,
294
+ currency: session.currency,
295
+ checkoutId: id,
296
+ description: `Order ${orderId}`,
297
+ });
298
+ if (ppOrder) {
299
+ logger_1.logger.info({ checkoutId: id, paypalOrderId: ppOrder.id, amount: totalAmount }, 'PayPal order created');
300
+ }
301
+ }
302
+ catch (error) {
303
+ logger_1.logger.error({ error, checkoutId: id }, 'Failed to create PayPal order');
304
+ return { error: 'Payment processing failed' };
305
+ }
306
+ }
307
+ }
266
308
  const order = {
267
309
  ucp: {
268
310
  version: schema_1.UCP_VERSION,
@@ -294,7 +336,7 @@ class UCPServer {
294
336
  : undefined,
295
337
  created_at: new Date().toISOString(),
296
338
  };
297
- orders.set(orderId, order);
339
+ this.orders.set(orderId, order);
298
340
  // Update session to completed state with order confirmation
299
341
  session.status = 'completed';
300
342
  session.order = {
@@ -303,26 +345,26 @@ class UCPServer {
303
345
  };
304
346
  delete session.continue_url;
305
347
  delete session.messages;
306
- checkoutSessions.set(id, session);
348
+ this.checkoutSessions.set(id, session);
307
349
  return session;
308
350
  }
309
351
  // Cancel checkout
310
352
  cancelCheckout(id) {
311
- const session = checkoutSessions.get(id);
353
+ const session = this.checkoutSessions.get(id);
312
354
  if (!session)
313
355
  return undefined;
314
356
  session.status = 'canceled';
315
357
  delete session.continue_url;
316
- checkoutSessions.set(id, session);
358
+ this.checkoutSessions.set(id, session);
317
359
  return session;
318
360
  }
319
361
  // Get order
320
362
  getOrder(id) {
321
- return orders.get(id);
363
+ return this.orders.get(id);
322
364
  }
323
365
  // List orders
324
366
  listOrders() {
325
- return Array.from(orders.values());
367
+ return Array.from(this.orders.values());
326
368
  }
327
369
  getConfig() {
328
370
  return this.config;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.1",
2
+ "version": "1.1.0",
3
3
  "description": "Generate UCP-compliant servers for merchants from a simple schema",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
@@ -23,8 +23,13 @@
23
23
  "agentic",
24
24
  "ai-commerce"
25
25
  ],
26
- "author": "",
26
+ "author": "Hemanth HM <hemanth@h3manth.com>",
27
27
  "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/hemanth/ucpify.git"
31
+ },
32
+ "homepage": "https://github.com/hemanth/ucpify#readme",
28
33
  "type": "commonjs",
29
34
  "dependencies": {
30
35
  "@paypal/paypal-server-sdk": "^2.2.0",
@@ -50,4 +55,4 @@
50
55
  "vitest": "^4.0.18"
51
56
  },
52
57
  "name": "ucpify"
53
- }
58
+ }
package/src/cli.ts CHANGED
@@ -7,11 +7,12 @@ import { MerchantConfigSchema, MerchantConfig } from './schema';
7
7
  import { createExpressApp } from './express-app';
8
8
 
9
9
  const program = new Command();
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
10
11
 
11
12
  program
12
13
  .name('ucpify')
13
14
  .description('Generate and run a UCP-compliant server for merchants')
14
- .version('1.0.0');
15
+ .version(pkg.version);
15
16
 
16
17
  program
17
18
  .command('init')
@@ -29,8 +29,8 @@ const checkoutLimiter = rateLimit({
29
29
 
30
30
  export function createExpressApp(config: MerchantConfig, options?: { useDb?: boolean }): express.Application {
31
31
  const app = express();
32
- const ucpServer: UCPServerType = options?.useDb
33
- ? new UCPServerDB(config)
32
+ const ucpServer: UCPServerType = options?.useDb
33
+ ? new UCPServerDB(config)
34
34
  : new UCPServer(config);
35
35
  const useDb = options?.useDb ?? false;
36
36
 
@@ -58,7 +58,7 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
58
58
  const paymentIntent = event.data.object as { id: string; metadata?: { checkout_id?: string; order_id?: string } };
59
59
  const checkoutId = paymentIntent.metadata?.checkout_id;
60
60
  const orderId = paymentIntent.metadata?.order_id;
61
-
61
+
62
62
  if (checkoutId && useDb) {
63
63
  db.prepare(`UPDATE checkouts SET payment_status = 'succeeded', updated_at = ? WHERE id = ?`)
64
64
  .run(new Date().toISOString(), checkoutId);
@@ -73,16 +73,16 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
73
73
  const paymentIntent = event.data.object as { id: string; metadata?: { checkout_id?: string; order_id?: string }; last_payment_error?: { message?: string } };
74
74
  const checkoutId = paymentIntent.metadata?.checkout_id;
75
75
  const orderId = paymentIntent.metadata?.order_id;
76
-
76
+
77
77
  if (checkoutId && useDb) {
78
78
  db.prepare(`UPDATE checkouts SET payment_status = 'failed', updated_at = ? WHERE id = ?`)
79
79
  .run(new Date().toISOString(), checkoutId);
80
80
  if (orderId) {
81
81
  db.prepare(`UPDATE orders SET payment_status = 'failed' WHERE id = ?`).run(orderId);
82
82
  }
83
- logger.error({
83
+ logger.error({
84
84
  checkoutId, orderId, paymentIntentId: paymentIntent.id,
85
- error: paymentIntent.last_payment_error?.message
85
+ error: paymentIntent.last_payment_error?.message
86
86
  }, 'Payment failed');
87
87
  }
88
88
  break;
@@ -107,7 +107,7 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
107
107
  // Get the checkout ID from the order
108
108
  const orderDetails = await getPayPalOrder(resource.id);
109
109
  const checkoutId = orderDetails?.customId;
110
-
110
+
111
111
  if (checkoutId && useDb) {
112
112
  const now = new Date().toISOString();
113
113
  db.prepare(`UPDATE checkouts SET payment_status = 'succeeded', updated_at = ? WHERE paypal_order_id = ?`)
@@ -267,17 +267,17 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
267
267
  const checkoutStats = db.prepare(`
268
268
  SELECT status, COUNT(*) as count FROM checkouts GROUP BY status
269
269
  `).all() as { status: string; count: number }[];
270
-
270
+
271
271
  const orderStats = db.prepare(`
272
272
  SELECT payment_status, payment_provider, COUNT(*) as count
273
273
  FROM orders GROUP BY payment_status, payment_provider
274
274
  `).all() as { payment_status: string; payment_provider: string; count: number }[];
275
-
275
+
276
276
  const revenueResult = db.prepare(`
277
277
  SELECT SUM(json_extract(totals_json, '$[?(@.type=="total")].amount')) as total
278
278
  FROM orders WHERE payment_status = 'succeeded'
279
279
  `).get() as { total: number | null };
280
-
280
+
281
281
  const todayOrders = db.prepare(`
282
282
  SELECT COUNT(*) as count FROM orders
283
283
  WHERE date(created_at) = date('now')
@@ -302,6 +302,17 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
302
302
 
303
303
  // Admin dashboard HTML
304
304
  app.get('/admin', (_req: Request, res: Response) => {
305
+ const statsScript = useDb
306
+ ? `fetch('/admin/stats').then(r=>r.json()).then(d=>{
307
+ document.getElementById('stats').innerHTML = \`
308
+ <div class="card"><div class="stat">\${Object.values(d.checkouts).reduce((a,b)=>a+b,0)}</div><div class="label">Total Checkouts</div></div>
309
+ <div class="card"><div class="stat">\${d.checkouts.completed||0}</div><div class="label">Completed</div></div>
310
+ <div class="card"><div class="stat">\${d.orders.today||0}</div><div class="label">Orders Today</div></div>
311
+ <div class="card"><div class="stat">$\${(d.revenue.total_succeeded/100).toFixed(2)}</div><div class="label">Revenue</div></div>
312
+ \`;
313
+ }).catch(e=>document.getElementById('stats').innerHTML='Error loading stats');`
314
+ : `document.getElementById('stats').innerHTML = '<div class="card" style="grid-column:1/-1"><div class="label">Running in <strong>in-memory mode</strong>. Stats require <code>--db</code> mode.</div><div style="margin-top:8px;font-size:0.85em;color:#999">Restart with: <code>ucpify serve config.json</code> (DB is on by default)</div></div>';`;
315
+
305
316
  res.send(`<!DOCTYPE html>
306
317
  <html><head><title>UCP Admin</title>
307
318
  <style>
@@ -311,20 +322,12 @@ export function createExpressApp(config: MerchantConfig, options?: { useDb?: boo
311
322
  .label { color: #666; font-size: 0.9em; }
312
323
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; }
313
324
  h1 { color: #333; }
325
+ code { background: #e5e5e5; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; }
314
326
  </style>
315
327
  </head><body>
316
328
  <h1>🛒 UCP Server Admin</h1>
317
329
  <div class="grid" id="stats">Loading...</div>
318
- <script>
319
- fetch('/admin/stats').then(r=>r.json()).then(d=>{
320
- document.getElementById('stats').innerHTML = \`
321
- <div class="card"><div class="stat">\${Object.values(d.checkouts).reduce((a,b)=>a+b,0)}</div><div class="label">Total Checkouts</div></div>
322
- <div class="card"><div class="stat">\${d.checkouts.completed||0}</div><div class="label">Completed</div></div>
323
- <div class="card"><div class="stat">\${d.orders.today||0}</div><div class="label">Orders Today</div></div>
324
- <div class="card"><div class="stat">$\${(d.revenue.total_succeeded/100).toFixed(2)}</div><div class="label">Revenue</div></div>
325
- \`;
326
- }).catch(e=>document.getElementById('stats').innerHTML='Error loading stats');
327
- </script>
330
+ <script>${statsScript}</script>
328
331
  </body></html>`);
329
332
  });
330
333
 
@@ -332,8 +335,8 @@ fetch('/admin/stats').then(r=>r.json()).then(d=>{
332
335
  app.get('/health', (_req: Request, res: Response) => {
333
336
  const dbHealthy = isDbHealthy();
334
337
  const status = dbHealthy ? 'ok' : 'degraded';
335
- res.status(dbHealthy ? 200 : 503).json({
336
- status,
338
+ res.status(dbHealthy ? 200 : 503).json({
339
+ status,
337
340
  ucp_version: UCP_VERSION,
338
341
  database: dbHealthy ? 'connected' : 'disconnected',
339
342
  timestamp: new Date().toISOString(),
package/src/ucp-server.ts CHANGED
@@ -11,14 +11,15 @@ import {
11
11
  Item,
12
12
  OrderConfirmation,
13
13
  } from './schema';
14
-
15
- // In-memory stores
16
- const checkoutSessions: Map<string, CheckoutSession> = new Map();
17
- const orders: Map<string, Order> = new Map();
14
+ import { createPaymentIntent, isStripeConfigured } from './stripe';
15
+ import { createPayPalOrder, isPayPalConfigured } from './paypal';
16
+ import { logger } from './logger';
18
17
 
19
18
  export class UCPServer {
20
19
  private config: MerchantConfig;
21
20
  private itemsMap: Map<string, Item>;
21
+ private checkoutSessions: Map<string, CheckoutSession> = new Map();
22
+ private orders: Map<string, Order> = new Map();
22
23
 
23
24
  constructor(config: MerchantConfig) {
24
25
  this.config = config;
@@ -122,7 +123,7 @@ export class UCPServer {
122
123
  // Create checkout session
123
124
  createCheckout(data: { line_items: { id?: string; item: { id: string; title?: string; price?: number }; quantity: number }[] }): CheckoutSession {
124
125
  const id = `chk_${uuidv4().replace(/-/g, '').slice(0, 12)}`;
125
-
126
+
126
127
  // Resolve items from catalog or use provided
127
128
  const lineItems: LineItem[] = data.line_items.map((li, idx) => {
128
129
  const catalogItem = this.itemsMap.get(li.item.id);
@@ -131,7 +132,7 @@ export class UCPServer {
131
132
  title: li.item.title || 'Unknown Item',
132
133
  price: li.item.price || 0,
133
134
  };
134
-
135
+
135
136
  const subtotal = item.price * li.quantity;
136
137
  return {
137
138
  id: li.id || `li_${idx + 1}`,
@@ -183,13 +184,13 @@ export class UCPServer {
183
184
  expires_at: expiresAt,
184
185
  };
185
186
 
186
- checkoutSessions.set(id, session);
187
+ this.checkoutSessions.set(id, session);
187
188
  return session;
188
189
  }
189
190
 
190
191
  // Get checkout session
191
192
  getCheckout(id: string): CheckoutSession | undefined {
192
- return checkoutSessions.get(id);
193
+ return this.checkoutSessions.get(id);
193
194
  }
194
195
 
195
196
  // Update checkout session
@@ -198,7 +199,7 @@ export class UCPServer {
198
199
  line_items?: { id?: string; item: { id: string; title?: string; price?: number }; quantity: number }[];
199
200
  fulfillment?: Fulfillment;
200
201
  }): CheckoutSession | undefined {
201
- const session = checkoutSessions.get(id);
202
+ const session = this.checkoutSessions.get(id);
202
203
  if (!session) return undefined;
203
204
 
204
205
  // Update buyer
@@ -239,18 +240,18 @@ export class UCPServer {
239
240
  const groups = method.groups?.length
240
241
  ? method.groups
241
242
  : [
242
- {
243
- id: `group_${mIdx + 1}`,
244
- line_item_ids: session.line_items.map((li) => li.id),
245
- selected_option_id: this.config.shipping_options[0]?.id,
246
- options: this.config.shipping_options.map((opt) => ({
247
- id: opt.id,
248
- title: opt.title,
249
- description: opt.description || opt.estimated_days,
250
- totals: [{ type: 'total', amount: opt.price }],
251
- })),
252
- },
253
- ];
243
+ {
244
+ id: `group_${mIdx + 1}`,
245
+ line_item_ids: session.line_items.map((li) => li.id),
246
+ selected_option_id: this.config.shipping_options[0]?.id,
247
+ options: this.config.shipping_options.map((opt) => ({
248
+ id: opt.id,
249
+ title: opt.title,
250
+ description: opt.description || opt.estimated_days,
251
+ totals: [{ type: 'total', amount: opt.price }],
252
+ })),
253
+ },
254
+ ];
254
255
 
255
256
  return {
256
257
  id: method.id || `method_${mIdx + 1}`,
@@ -293,20 +294,59 @@ export class UCPServer {
293
294
  session.messages = newMessages.length > 0 ? newMessages : undefined;
294
295
  session.status = newMessages.length > 0 ? 'incomplete' : 'ready_for_complete';
295
296
 
296
- checkoutSessions.set(id, session);
297
+ this.checkoutSessions.set(id, session);
297
298
  return session;
298
299
  }
299
300
 
300
301
  // Complete checkout (create order)
301
302
  async completeCheckout(id: string): Promise<CheckoutSession | { error: string }> {
302
- const session = checkoutSessions.get(id);
303
+ const session = this.checkoutSessions.get(id);
303
304
  if (!session) return { error: 'Checkout session not found' };
304
305
  if (session.status !== 'ready_for_complete') {
305
306
  return { error: 'Checkout is not ready for completion', messages: session.messages };
306
307
  }
307
308
 
308
309
  const orderId = `order_${uuidv4().replace(/-/g, '').slice(0, 12)}`;
309
-
310
+
311
+ // Get total amount for payment
312
+ const totalAmount = session.totals.find(t => t.type === 'total')?.amount || 0;
313
+
314
+ // Payment processing - try Stripe first, then PayPal
315
+ if (totalAmount > 0) {
316
+ if (isStripeConfigured()) {
317
+ try {
318
+ const paymentIntent = await createPaymentIntent({
319
+ amount: totalAmount,
320
+ currency: session.currency,
321
+ checkoutId: id,
322
+ customerEmail: session.buyer?.email,
323
+ metadata: { order_id: orderId },
324
+ });
325
+ if (paymentIntent) {
326
+ logger.info({ checkoutId: id, paymentIntentId: paymentIntent.id, amount: totalAmount }, 'Stripe payment intent created');
327
+ }
328
+ } catch (error) {
329
+ logger.error({ error, checkoutId: id }, 'Failed to create Stripe payment intent');
330
+ return { error: 'Payment processing failed' };
331
+ }
332
+ } else if (isPayPalConfigured()) {
333
+ try {
334
+ const ppOrder = await createPayPalOrder({
335
+ amount: totalAmount,
336
+ currency: session.currency,
337
+ checkoutId: id,
338
+ description: `Order ${orderId}`,
339
+ });
340
+ if (ppOrder) {
341
+ logger.info({ checkoutId: id, paypalOrderId: ppOrder.id, amount: totalAmount }, 'PayPal order created');
342
+ }
343
+ } catch (error) {
344
+ logger.error({ error, checkoutId: id }, 'Failed to create PayPal order');
345
+ return { error: 'Payment processing failed' };
346
+ }
347
+ }
348
+ }
349
+
310
350
  const order: Order = {
311
351
  ucp: {
312
352
  version: UCP_VERSION,
@@ -322,25 +362,25 @@ export class UCPServer {
322
362
  totals: session.totals,
323
363
  fulfillment: session.fulfillment
324
364
  ? {
325
- expectations: session.fulfillment.methods.map((m, idx) => ({
326
- id: `exp_${idx + 1}`,
327
- line_items: m.line_item_ids.map((liId) => {
328
- const li = session.line_items.find((l) => l.id === liId);
329
- return { id: liId, quantity: li?.quantity || 1 };
330
- }),
331
- method_type: m.type,
332
- destination: m.destinations.find((d) => d.id === m.selected_destination_id),
333
- description: 'Arrives in 5-7 business days',
334
- fulfillable_on: 'now',
335
- })),
336
- events: [],
337
- }
365
+ expectations: session.fulfillment.methods.map((m, idx) => ({
366
+ id: `exp_${idx + 1}`,
367
+ line_items: m.line_item_ids.map((liId) => {
368
+ const li = session.line_items.find((l) => l.id === liId);
369
+ return { id: liId, quantity: li?.quantity || 1 };
370
+ }),
371
+ method_type: m.type,
372
+ destination: m.destinations.find((d) => d.id === m.selected_destination_id),
373
+ description: 'Arrives in 5-7 business days',
374
+ fulfillable_on: 'now',
375
+ })),
376
+ events: [],
377
+ }
338
378
  : undefined,
339
379
  created_at: new Date().toISOString(),
340
380
  };
341
381
 
342
- orders.set(orderId, order);
343
-
382
+ this.orders.set(orderId, order);
383
+
344
384
  // Update session to completed state with order confirmation
345
385
  session.status = 'completed';
346
386
  session.order = {
@@ -349,29 +389,29 @@ export class UCPServer {
349
389
  };
350
390
  delete session.continue_url;
351
391
  delete session.messages;
352
- checkoutSessions.set(id, session);
392
+ this.checkoutSessions.set(id, session);
353
393
 
354
394
  return session;
355
395
  }
356
396
 
357
397
  // Cancel checkout
358
398
  cancelCheckout(id: string): CheckoutSession | undefined {
359
- const session = checkoutSessions.get(id);
399
+ const session = this.checkoutSessions.get(id);
360
400
  if (!session) return undefined;
361
401
  session.status = 'canceled';
362
402
  delete session.continue_url;
363
- checkoutSessions.set(id, session);
403
+ this.checkoutSessions.set(id, session);
364
404
  return session;
365
405
  }
366
406
 
367
407
  // Get order
368
408
  getOrder(id: string): Order | undefined {
369
- return orders.get(id);
409
+ return this.orders.get(id);
370
410
  }
371
411
 
372
412
  // List orders
373
413
  listOrders(): Order[] {
374
- return Array.from(orders.values());
414
+ return Array.from(this.orders.values());
375
415
  }
376
416
 
377
417
  getConfig(): MerchantConfig {
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ exclude: ['dist/**', 'node_modules/**'],
6
+ },
7
+ });