ucpify 1.1.0 → 1.2.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.
- package/package.json +18 -18
- package/src/cli.ts +30 -1
- package/src/db.ts +35 -0
- package/src/express-app.ts +206 -1
- package/src/oauth.ts +270 -0
- package/src/schema.ts +18 -0
- package/src/ucp-server-db.ts +10 -5
- package/src/ucp-server.ts +8 -0
- package/dist/__tests__/api.test.d.ts +0 -1
- package/dist/__tests__/api.test.js +0 -121
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -208
- package/dist/config.d.ts +0 -14
- package/dist/config.js +0 -22
- package/dist/db.d.ts +0 -4
- package/dist/db.js +0 -129
- package/dist/express-app.d.ts +0 -5
- package/dist/express-app.js +0 -320
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -12
- package/dist/logger.d.ts +0 -3
- package/dist/logger.js +0 -15
- package/dist/paypal.d.ts +0 -20
- package/dist/paypal.js +0 -111
- package/dist/schema.d.ts +0 -266
- package/dist/schema.js +0 -98
- package/dist/stripe.d.ts +0 -14
- package/dist/stripe.js +0 -103
- package/dist/ucp-server-db.d.ts +0 -72
- package/dist/ucp-server-db.js +0 -364
- package/dist/ucp-server.d.ts +0 -79
- package/dist/ucp-server.js +0 -373
package/src/schema.ts
CHANGED
|
@@ -30,6 +30,23 @@ export const PaymentHandlerSchema = z.object({
|
|
|
30
30
|
config: z.record(z.string(), z.unknown()).optional(),
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
// OAuth Configuration Schema
|
|
34
|
+
export const OAuthConfigSchema = z.discriminatedUnion('provider', [
|
|
35
|
+
z.object({
|
|
36
|
+
provider: z.literal('built-in'),
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
provider: z.literal('external'),
|
|
40
|
+
issuer: z.string().url(),
|
|
41
|
+
authorization_endpoint: z.string().url(),
|
|
42
|
+
token_endpoint: z.string().url(),
|
|
43
|
+
revocation_endpoint: z.string().url().optional(),
|
|
44
|
+
jwks_uri: z.string().url().optional(),
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export type OAuthConfig = z.infer<typeof OAuthConfigSchema>;
|
|
49
|
+
|
|
33
50
|
// Merchant Configuration Schema
|
|
34
51
|
export const MerchantConfigSchema = z.object({
|
|
35
52
|
name: z.string(),
|
|
@@ -42,6 +59,7 @@ export const MerchantConfigSchema = z.object({
|
|
|
42
59
|
payment_handlers: z.array(PaymentHandlerSchema).default([]),
|
|
43
60
|
tax_rate: z.number().min(0).max(1).default(0), // e.g., 0.08 for 8%
|
|
44
61
|
port: z.number().default(3000),
|
|
62
|
+
oauth: OAuthConfigSchema.optional(),
|
|
45
63
|
});
|
|
46
64
|
|
|
47
65
|
export type Item = z.infer<typeof ItemSchema>;
|
package/src/ucp-server-db.ts
CHANGED
|
@@ -57,6 +57,11 @@ export class UCPServerDB {
|
|
|
57
57
|
'dev.ucp.shopping.order': [
|
|
58
58
|
{ version: UCP_VERSION, spec: 'https://ucp.dev/specification/order', schema: 'https://ucp.dev/schemas/shopping/order.json' },
|
|
59
59
|
],
|
|
60
|
+
...(this.config.oauth ? {
|
|
61
|
+
'dev.ucp.identity_linking': [
|
|
62
|
+
{ version: UCP_VERSION, spec: 'https://ucp.dev/specification/identity-linking' },
|
|
63
|
+
],
|
|
64
|
+
} : {}),
|
|
60
65
|
},
|
|
61
66
|
payment_handlers: paymentHandlers,
|
|
62
67
|
},
|
|
@@ -105,9 +110,9 @@ export class UCPServerDB {
|
|
|
105
110
|
|
|
106
111
|
private sessionFromRow(row: any): CheckoutSession | undefined {
|
|
107
112
|
if (!row) return undefined;
|
|
108
|
-
|
|
113
|
+
|
|
109
114
|
const lineItems = db.prepare('SELECT * FROM line_items WHERE checkout_id = ?').all(row.id) as any[];
|
|
110
|
-
|
|
115
|
+
|
|
111
116
|
return {
|
|
112
117
|
ucp: {
|
|
113
118
|
version: UCP_VERSION,
|
|
@@ -169,7 +174,7 @@ export class UCPServerDB {
|
|
|
169
174
|
`);
|
|
170
175
|
|
|
171
176
|
db.transaction(() => {
|
|
172
|
-
insertCheckout.run(id, status, this.config.currency, JSON.stringify(totals), JSON.stringify(links),
|
|
177
|
+
insertCheckout.run(id, status, this.config.currency, JSON.stringify(totals), JSON.stringify(links),
|
|
173
178
|
messages.length > 0 ? JSON.stringify(messages) : null, continueUrl, expiresAt, now, now);
|
|
174
179
|
for (const li of lineItems) {
|
|
175
180
|
insertLineItem.run(li.id, id, JSON.stringify(li.item), li.quantity, JSON.stringify(li.totals));
|
|
@@ -284,13 +289,13 @@ export class UCPServerDB {
|
|
|
284
289
|
|
|
285
290
|
// Get total amount for payment
|
|
286
291
|
const totalAmount = session.totals.find(t => t.type === 'total')?.amount || 0;
|
|
287
|
-
|
|
292
|
+
|
|
288
293
|
// Payment processing - try Stripe first, then PayPal
|
|
289
294
|
let paymentIntentId: string | null = null;
|
|
290
295
|
let paypalOrderId: string | null = null;
|
|
291
296
|
let paymentProvider: string | null = null;
|
|
292
297
|
let paymentStatus = 'none';
|
|
293
|
-
|
|
298
|
+
|
|
294
299
|
if (totalAmount > 0) {
|
|
295
300
|
// Try Stripe first
|
|
296
301
|
if (isStripeConfigured()) {
|
package/src/ucp-server.ts
CHANGED
|
@@ -76,6 +76,14 @@ export class UCPServer {
|
|
|
76
76
|
schema: 'https://ucp.dev/schemas/shopping/order.json',
|
|
77
77
|
},
|
|
78
78
|
],
|
|
79
|
+
...(this.config.oauth ? {
|
|
80
|
+
'dev.ucp.identity_linking': [
|
|
81
|
+
{
|
|
82
|
+
version: UCP_VERSION,
|
|
83
|
+
spec: 'https://ucp.dev/specification/identity-linking',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
} : {}),
|
|
79
87
|
},
|
|
80
88
|
payment_handlers: paymentHandlers,
|
|
81
89
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const vitest_1 = require("vitest");
|
|
7
|
-
const supertest_1 = __importDefault(require("supertest"));
|
|
8
|
-
const express_app_1 = require("../express-app");
|
|
9
|
-
const testConfig = {
|
|
10
|
-
name: 'Test Store',
|
|
11
|
-
domain: 'https://test.example.com',
|
|
12
|
-
currency: 'USD',
|
|
13
|
-
port: 3000,
|
|
14
|
-
items: [
|
|
15
|
-
{ id: 'item-1', title: 'Test Item', price: 1000 },
|
|
16
|
-
{ id: 'item-2', title: 'Another Item', price: 2500 },
|
|
17
|
-
],
|
|
18
|
-
shipping_options: [
|
|
19
|
-
{ id: 'standard', title: 'Standard Shipping', price: 500, estimated_days: '5-7 days' },
|
|
20
|
-
],
|
|
21
|
-
payment_handlers: [
|
|
22
|
-
{ namespace: 'com.stripe', id: 'stripe-test' },
|
|
23
|
-
],
|
|
24
|
-
tax_rate: 0.08,
|
|
25
|
-
};
|
|
26
|
-
(0, vitest_1.describe)('UCP Server API', () => {
|
|
27
|
-
const app = (0, express_app_1.createExpressApp)(testConfig, { useDb: false });
|
|
28
|
-
(0, vitest_1.describe)('GET /.well-known/ucp', () => {
|
|
29
|
-
(0, vitest_1.it)('returns UCP profile', async () => {
|
|
30
|
-
const res = await (0, supertest_1.default)(app).get('/.well-known/ucp');
|
|
31
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
32
|
-
(0, vitest_1.expect)(res.body.ucp.version).toBe('2026-01-11');
|
|
33
|
-
(0, vitest_1.expect)(res.body.ucp.services['dev.ucp.shopping']).toBeDefined();
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
(0, vitest_1.describe)('GET /health', () => {
|
|
37
|
-
(0, vitest_1.it)('returns health status', async () => {
|
|
38
|
-
const res = await (0, supertest_1.default)(app).get('/health');
|
|
39
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
40
|
-
(0, vitest_1.expect)(res.body.status).toBeDefined();
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
(0, vitest_1.describe)('POST /ucp/v1/checkout-sessions', () => {
|
|
44
|
-
(0, vitest_1.it)('creates a checkout session', async () => {
|
|
45
|
-
const res = await (0, supertest_1.default)(app)
|
|
46
|
-
.post('/ucp/v1/checkout-sessions')
|
|
47
|
-
.send({
|
|
48
|
-
line_items: [{ item: { id: 'item-1' }, quantity: 2 }],
|
|
49
|
-
});
|
|
50
|
-
(0, vitest_1.expect)(res.status).toBe(201);
|
|
51
|
-
(0, vitest_1.expect)(res.body.id).toBeDefined();
|
|
52
|
-
(0, vitest_1.expect)(res.body.status).toBe('incomplete');
|
|
53
|
-
(0, vitest_1.expect)(res.body.line_items).toHaveLength(1);
|
|
54
|
-
(0, vitest_1.expect)(res.body.line_items[0].quantity).toBe(2);
|
|
55
|
-
});
|
|
56
|
-
(0, vitest_1.it)('rejects invalid requests', async () => {
|
|
57
|
-
const res = await (0, supertest_1.default)(app)
|
|
58
|
-
.post('/ucp/v1/checkout-sessions')
|
|
59
|
-
.send({ line_items: [] }); // Empty array
|
|
60
|
-
(0, vitest_1.expect)(res.status).toBe(400);
|
|
61
|
-
(0, vitest_1.expect)(res.body.error).toBe('Validation failed');
|
|
62
|
-
});
|
|
63
|
-
(0, vitest_1.it)('rejects excessive quantity', async () => {
|
|
64
|
-
const res = await (0, supertest_1.default)(app)
|
|
65
|
-
.post('/ucp/v1/checkout-sessions')
|
|
66
|
-
.send({
|
|
67
|
-
line_items: [{ item: { id: 'item-1' }, quantity: 999 }],
|
|
68
|
-
});
|
|
69
|
-
(0, vitest_1.expect)(res.status).toBe(400);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
(0, vitest_1.describe)('Checkout flow', () => {
|
|
73
|
-
let checkoutId;
|
|
74
|
-
(0, vitest_1.it)('creates checkout', async () => {
|
|
75
|
-
const res = await (0, supertest_1.default)(app)
|
|
76
|
-
.post('/ucp/v1/checkout-sessions')
|
|
77
|
-
.send({
|
|
78
|
-
line_items: [{ item: { id: 'item-1' }, quantity: 1 }],
|
|
79
|
-
});
|
|
80
|
-
checkoutId = res.body.id;
|
|
81
|
-
(0, vitest_1.expect)(res.body.status).toBe('incomplete');
|
|
82
|
-
});
|
|
83
|
-
(0, vitest_1.it)('updates with buyer and fulfillment', async () => {
|
|
84
|
-
const res = await (0, supertest_1.default)(app)
|
|
85
|
-
.put(`/ucp/v1/checkout-sessions/${checkoutId}`)
|
|
86
|
-
.send({
|
|
87
|
-
buyer: { email: 'test@example.com' },
|
|
88
|
-
fulfillment: {
|
|
89
|
-
methods: [{
|
|
90
|
-
type: 'shipping',
|
|
91
|
-
destinations: [{
|
|
92
|
-
street_address: '123 Main St',
|
|
93
|
-
address_locality: 'Anytown',
|
|
94
|
-
address_region: 'CA',
|
|
95
|
-
postal_code: '12345',
|
|
96
|
-
address_country: 'US',
|
|
97
|
-
}],
|
|
98
|
-
}],
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
102
|
-
(0, vitest_1.expect)(res.body.status).toBe('ready_for_complete');
|
|
103
|
-
});
|
|
104
|
-
(0, vitest_1.it)('completes checkout', async () => {
|
|
105
|
-
const res = await (0, supertest_1.default)(app)
|
|
106
|
-
.post(`/ucp/v1/checkout-sessions/${checkoutId}/complete`);
|
|
107
|
-
(0, vitest_1.expect)(res.status).toBe(201);
|
|
108
|
-
(0, vitest_1.expect)(res.body.status).toBe('completed');
|
|
109
|
-
(0, vitest_1.expect)(res.body.order).toBeDefined();
|
|
110
|
-
(0, vitest_1.expect)(res.body.order.id).toMatch(/^order_/);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
(0, vitest_1.describe)('GET /ucp/v1/items', () => {
|
|
114
|
-
(0, vitest_1.it)('returns product catalog', async () => {
|
|
115
|
-
const res = await (0, supertest_1.default)(app).get('/ucp/v1/items');
|
|
116
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
117
|
-
(0, vitest_1.expect)(res.body).toHaveLength(2);
|
|
118
|
-
(0, vitest_1.expect)(res.body[0].id).toBe('item-1');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
package/dist/cli.d.ts
DELETED
package/dist/cli.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
const commander_1 = require("commander");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
|
-
const schema_1 = require("./schema");
|
|
41
|
-
const express_app_1 = require("./express-app");
|
|
42
|
-
const program = new commander_1.Command();
|
|
43
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
44
|
-
program
|
|
45
|
-
.name('ucpify')
|
|
46
|
-
.description('Generate and run a UCP-compliant server for merchants')
|
|
47
|
-
.version(pkg.version);
|
|
48
|
-
program
|
|
49
|
-
.command('init')
|
|
50
|
-
.description('Create a sample merchant configuration file')
|
|
51
|
-
.option('-o, --output <file>', 'Output file path', 'merchant-config.json')
|
|
52
|
-
.action((options) => {
|
|
53
|
-
const sampleConfig = {
|
|
54
|
-
name: 'My Store',
|
|
55
|
-
domain: 'http://localhost:3000',
|
|
56
|
-
currency: 'USD',
|
|
57
|
-
terms_url: 'https://example.com/terms',
|
|
58
|
-
privacy_url: 'https://example.com/privacy',
|
|
59
|
-
tax_rate: 0.08,
|
|
60
|
-
port: 3000,
|
|
61
|
-
items: [
|
|
62
|
-
{
|
|
63
|
-
id: 'item_001',
|
|
64
|
-
title: 'Classic T-Shirt',
|
|
65
|
-
description: 'A comfortable cotton t-shirt',
|
|
66
|
-
price: 2500, // $25.00 in cents
|
|
67
|
-
sku: 'TSH-001',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: 'item_002',
|
|
71
|
-
title: 'Premium Hoodie',
|
|
72
|
-
description: 'Warm and stylish hoodie',
|
|
73
|
-
price: 5999, // $59.99 in cents
|
|
74
|
-
sku: 'HOO-001',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
shipping_options: [
|
|
78
|
-
{
|
|
79
|
-
id: 'standard',
|
|
80
|
-
title: 'Standard Shipping',
|
|
81
|
-
description: 'Arrives in 5-7 business days',
|
|
82
|
-
price: 500, // $5.00
|
|
83
|
-
estimated_days: '5-7 business days',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'express',
|
|
87
|
-
title: 'Express Shipping',
|
|
88
|
-
description: 'Arrives in 2-3 business days',
|
|
89
|
-
price: 1500, // $15.00
|
|
90
|
-
estimated_days: '2-3 business days',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: 'overnight',
|
|
94
|
-
title: 'Overnight Shipping',
|
|
95
|
-
description: 'Arrives next business day',
|
|
96
|
-
price: 2999, // $29.99
|
|
97
|
-
estimated_days: '1 business day',
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
payment_handlers: [
|
|
101
|
-
{
|
|
102
|
-
namespace: 'com.stripe',
|
|
103
|
-
id: 'stripe_handler',
|
|
104
|
-
config: {
|
|
105
|
-
publishable_key: 'pk_test_...',
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
namespace: 'com.paypal',
|
|
110
|
-
id: 'paypal_handler',
|
|
111
|
-
config: {
|
|
112
|
-
client_id: 'your_paypal_client_id',
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
};
|
|
117
|
-
fs.writeFileSync(options.output, JSON.stringify(sampleConfig, null, 2));
|
|
118
|
-
console.log(`✅ Created sample config at: ${options.output}`);
|
|
119
|
-
console.log('\n📝 Edit this file to configure your products, shipping, and payment handlers.');
|
|
120
|
-
console.log(`\n🚀 Run: npx ts-node src/cli.ts serve ${options.output}`);
|
|
121
|
-
});
|
|
122
|
-
program
|
|
123
|
-
.command('serve')
|
|
124
|
-
.description('Start the UCP server from a configuration file')
|
|
125
|
-
.argument('<config>', 'Path to merchant configuration JSON file')
|
|
126
|
-
.option('-p, --port <number>', 'Port to run on (overrides config)')
|
|
127
|
-
.option('--no-db', 'Use in-memory storage instead of SQLite')
|
|
128
|
-
.action((configPath, options) => {
|
|
129
|
-
try {
|
|
130
|
-
const configFile = path.resolve(configPath);
|
|
131
|
-
if (!fs.existsSync(configFile)) {
|
|
132
|
-
console.error(`❌ Config file not found: ${configFile}`);
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
const rawConfig = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
136
|
-
const config = schema_1.MerchantConfigSchema.parse(rawConfig);
|
|
137
|
-
if (options.port) {
|
|
138
|
-
config.port = parseInt(options.port, 10);
|
|
139
|
-
}
|
|
140
|
-
const useDb = options.db !== false;
|
|
141
|
-
const app = (0, express_app_1.createExpressApp)(config, { useDb });
|
|
142
|
-
const port = config.port;
|
|
143
|
-
const storageType = useDb ? 'SQLite' : 'In-memory';
|
|
144
|
-
app.listen(port, () => {
|
|
145
|
-
console.log(`
|
|
146
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
147
|
-
║ 🛒 UCP Server Running ║
|
|
148
|
-
╠══════════════════════════════════════════════════════════════╣
|
|
149
|
-
║ Merchant: ${config.name.padEnd(49)}║
|
|
150
|
-
║ Domain: ${config.domain.padEnd(49)}║
|
|
151
|
-
║ Port: ${String(port).padEnd(49)}║
|
|
152
|
-
║ Storage: ${storageType.padEnd(49)}║
|
|
153
|
-
╠══════════════════════════════════════════════════════════════╣
|
|
154
|
-
║ Endpoints: ║
|
|
155
|
-
║ • GET /.well-known/ucp - UCP Profile ║
|
|
156
|
-
║ • POST /ucp/v1/checkout-sessions - Create Checkout ║
|
|
157
|
-
║ • GET /ucp/v1/checkout-sessions/:id - Get Checkout ║
|
|
158
|
-
║ • PUT /ucp/v1/checkout-sessions/:id - Update Checkout ║
|
|
159
|
-
║ • POST /ucp/v1/checkout-sessions/:id/complete - Complete ║
|
|
160
|
-
║ • POST /ucp/v1/checkout-sessions/:id/cancel - Cancel ║
|
|
161
|
-
║ • GET /ucp/v1/orders - List Orders ║
|
|
162
|
-
║ • GET /ucp/v1/orders/:id - Get Order ║
|
|
163
|
-
║ • GET /ucp/v1/items - Product Catalog ║
|
|
164
|
-
║ • GET /health - Health Check ║
|
|
165
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
166
|
-
|
|
167
|
-
🔗 UCP Profile: http://localhost:${port}/.well-known/ucp
|
|
168
|
-
📦 Products: ${config.items.length} items loaded
|
|
169
|
-
🚚 Shipping: ${config.shipping_options.length} options available
|
|
170
|
-
💳 Payment Handlers: ${config.payment_handlers.length} configured
|
|
171
|
-
💾 Storage: ${storageType}${useDb ? ' (./data/ucp.db)' : ''}
|
|
172
|
-
`);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
console.error('❌ Error:', error);
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
program
|
|
181
|
-
.command('validate')
|
|
182
|
-
.description('Validate a merchant configuration file')
|
|
183
|
-
.argument('<config>', 'Path to merchant configuration JSON file')
|
|
184
|
-
.action((configPath) => {
|
|
185
|
-
try {
|
|
186
|
-
const configFile = path.resolve(configPath);
|
|
187
|
-
if (!fs.existsSync(configFile)) {
|
|
188
|
-
console.error(`❌ Config file not found: ${configFile}`);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
const rawConfig = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
192
|
-
const config = schema_1.MerchantConfigSchema.parse(rawConfig);
|
|
193
|
-
console.log('✅ Configuration is valid!');
|
|
194
|
-
console.log(`\n📊 Summary:`);
|
|
195
|
-
console.log(` • Name: ${config.name}`);
|
|
196
|
-
console.log(` • Domain: ${config.domain}`);
|
|
197
|
-
console.log(` • Currency: ${config.currency}`);
|
|
198
|
-
console.log(` • Tax Rate: ${(config.tax_rate * 100).toFixed(2)}%`);
|
|
199
|
-
console.log(` • Products: ${config.items.length}`);
|
|
200
|
-
console.log(` • Shipping Options: ${config.shipping_options.length}`);
|
|
201
|
-
console.log(` • Payment Handlers: ${config.payment_handlers.length}`);
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
console.error('❌ Validation failed:', error);
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
program.parse();
|
package/dist/config.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
export declare const config: {
|
|
3
|
-
port: number;
|
|
4
|
-
nodeEnv: string;
|
|
5
|
-
databasePath: string;
|
|
6
|
-
logLevel: string;
|
|
7
|
-
domain: string;
|
|
8
|
-
stripeSecretKey: string;
|
|
9
|
-
stripeWebhookSecret: string;
|
|
10
|
-
paypalClientId: string;
|
|
11
|
-
paypalClientSecret: string;
|
|
12
|
-
paypalMode: "sandbox" | "live";
|
|
13
|
-
};
|
|
14
|
-
export type Config = typeof config;
|
package/dist/config.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.config = void 0;
|
|
4
|
-
require("dotenv/config");
|
|
5
|
-
exports.config = {
|
|
6
|
-
// Server
|
|
7
|
-
port: parseInt(process.env.PORT || '3000', 10),
|
|
8
|
-
nodeEnv: process.env.NODE_ENV || 'development',
|
|
9
|
-
// Database
|
|
10
|
-
databasePath: process.env.DATABASE_PATH || './data/ucp.db',
|
|
11
|
-
// Logging
|
|
12
|
-
logLevel: process.env.LOG_LEVEL || 'info',
|
|
13
|
-
// UCP
|
|
14
|
-
domain: process.env.UCP_DOMAIN || 'http://localhost:3000',
|
|
15
|
-
// Stripe
|
|
16
|
-
stripeSecretKey: process.env.STRIPE_SECRET_KEY || '',
|
|
17
|
-
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET || '',
|
|
18
|
-
// PayPal
|
|
19
|
-
paypalClientId: process.env.PAYPAL_CLIENT_ID || '',
|
|
20
|
-
paypalClientSecret: process.env.PAYPAL_CLIENT_SECRET || '',
|
|
21
|
-
paypalMode: (process.env.PAYPAL_MODE || 'sandbox'),
|
|
22
|
-
};
|
package/dist/db.d.ts
DELETED
package/dist/db.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.db = void 0;
|
|
40
|
-
exports.getDb = getDb;
|
|
41
|
-
exports.isDbHealthy = isDbHealthy;
|
|
42
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
43
|
-
const config_1 = require("./config");
|
|
44
|
-
const logger_1 = require("./logger");
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
46
|
-
const path = __importStar(require("path"));
|
|
47
|
-
// Ensure data directory exists
|
|
48
|
-
const dataDir = path.dirname(config_1.config.databasePath);
|
|
49
|
-
if (!fs.existsSync(dataDir)) {
|
|
50
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
51
|
-
}
|
|
52
|
-
exports.db = new better_sqlite3_1.default(config_1.config.databasePath);
|
|
53
|
-
// Enable WAL mode for better concurrent access
|
|
54
|
-
exports.db.pragma('journal_mode = WAL');
|
|
55
|
-
// Initialize schema
|
|
56
|
-
const schema = `
|
|
57
|
-
CREATE TABLE IF NOT EXISTS checkouts (
|
|
58
|
-
id TEXT PRIMARY KEY,
|
|
59
|
-
status TEXT NOT NULL,
|
|
60
|
-
currency TEXT NOT NULL,
|
|
61
|
-
buyer_json TEXT,
|
|
62
|
-
fulfillment_json TEXT,
|
|
63
|
-
payment_json TEXT,
|
|
64
|
-
totals_json TEXT NOT NULL,
|
|
65
|
-
links_json TEXT NOT NULL,
|
|
66
|
-
messages_json TEXT,
|
|
67
|
-
continue_url TEXT,
|
|
68
|
-
expires_at TEXT,
|
|
69
|
-
order_json TEXT,
|
|
70
|
-
payment_intent_id TEXT,
|
|
71
|
-
paypal_order_id TEXT,
|
|
72
|
-
payment_provider TEXT,
|
|
73
|
-
payment_status TEXT DEFAULT 'none',
|
|
74
|
-
created_at TEXT NOT NULL,
|
|
75
|
-
updated_at TEXT NOT NULL
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
CREATE TABLE IF NOT EXISTS line_items (
|
|
79
|
-
id TEXT NOT NULL,
|
|
80
|
-
checkout_id TEXT NOT NULL,
|
|
81
|
-
item_json TEXT NOT NULL,
|
|
82
|
-
quantity INTEGER NOT NULL,
|
|
83
|
-
totals_json TEXT,
|
|
84
|
-
PRIMARY KEY (id, checkout_id),
|
|
85
|
-
FOREIGN KEY (checkout_id) REFERENCES checkouts(id) ON DELETE CASCADE
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
CREATE TABLE IF NOT EXISTS orders (
|
|
89
|
-
id TEXT PRIMARY KEY,
|
|
90
|
-
checkout_id TEXT NOT NULL,
|
|
91
|
-
permalink_url TEXT,
|
|
92
|
-
buyer_json TEXT,
|
|
93
|
-
line_items_json TEXT NOT NULL,
|
|
94
|
-
totals_json TEXT NOT NULL,
|
|
95
|
-
fulfillment_json TEXT,
|
|
96
|
-
payment_intent_id TEXT,
|
|
97
|
-
paypal_order_id TEXT,
|
|
98
|
-
payment_provider TEXT,
|
|
99
|
-
payment_status TEXT DEFAULT 'pending',
|
|
100
|
-
created_at TEXT NOT NULL,
|
|
101
|
-
FOREIGN KEY (checkout_id) REFERENCES checkouts(id)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
CREATE TABLE IF NOT EXISTS inventory (
|
|
105
|
-
item_id TEXT PRIMARY KEY,
|
|
106
|
-
stock INTEGER NOT NULL DEFAULT 0,
|
|
107
|
-
reserved INTEGER NOT NULL DEFAULT 0,
|
|
108
|
-
updated_at TEXT NOT NULL
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
CREATE INDEX IF NOT EXISTS idx_line_items_checkout ON line_items(checkout_id);
|
|
112
|
-
CREATE INDEX IF NOT EXISTS idx_orders_checkout ON orders(checkout_id);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_checkouts_status ON checkouts(status);
|
|
114
|
-
`;
|
|
115
|
-
exports.db.exec(schema);
|
|
116
|
-
logger_1.logger.info({ path: config_1.config.databasePath }, 'Database initialized');
|
|
117
|
-
// Helper functions
|
|
118
|
-
function getDb() {
|
|
119
|
-
return exports.db;
|
|
120
|
-
}
|
|
121
|
-
function isDbHealthy() {
|
|
122
|
-
try {
|
|
123
|
-
exports.db.prepare('SELECT 1').get();
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|