ucpify 1.0.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/.env.example +23 -0
- package/Dockerfile +35 -0
- package/README.md +320 -0
- package/dist/__tests__/api.test.d.ts +1 -0
- package/dist/__tests__/api.test.js +121 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +207 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +22 -0
- package/dist/db.d.ts +4 -0
- package/dist/db.js +129 -0
- package/dist/express-app.d.ts +5 -0
- package/dist/express-app.js +318 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +15 -0
- package/dist/paypal.d.ts +20 -0
- package/dist/paypal.js +111 -0
- package/dist/schema.d.ts +266 -0
- package/dist/schema.js +98 -0
- package/dist/stripe.d.ts +14 -0
- package/dist/stripe.js +103 -0
- package/dist/ucp-server-db.d.ts +72 -0
- package/dist/ucp-server-db.js +364 -0
- package/dist/ucp-server.d.ts +77 -0
- package/dist/ucp-server.js +331 -0
- package/merchant-config.json +64 -0
- package/package.json +53 -0
- package/src/__tests__/api.test.ts +129 -0
- package/src/cli.ts +184 -0
- package/src/config.ts +27 -0
- package/src/db.ts +94 -0
- package/src/express-app.ts +344 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +12 -0
- package/src/paypal.ts +120 -0
- package/src/schema.ts +244 -0
- package/src/stripe.ts +106 -0
- package/src/ucp-server-db.ts +412 -0
- package/src/ucp-server.ts +380 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UCPServerDB = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const db_1 = require("./db");
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
const stripe_1 = require("./stripe");
|
|
8
|
+
const paypal_1 = require("./paypal");
|
|
9
|
+
const schema_1 = require("./schema");
|
|
10
|
+
class UCPServerDB {
|
|
11
|
+
config;
|
|
12
|
+
itemsMap;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.itemsMap = new Map(config.items.map((item) => [item.id, item]));
|
|
16
|
+
}
|
|
17
|
+
getProfile() {
|
|
18
|
+
const paymentHandlers = {};
|
|
19
|
+
for (const handler of this.config.payment_handlers) {
|
|
20
|
+
paymentHandlers[handler.namespace] = [
|
|
21
|
+
{ id: handler.id, version: schema_1.UCP_VERSION, config: handler.config },
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
ucp: {
|
|
26
|
+
version: schema_1.UCP_VERSION,
|
|
27
|
+
services: {
|
|
28
|
+
'dev.ucp.shopping': [
|
|
29
|
+
{
|
|
30
|
+
version: schema_1.UCP_VERSION,
|
|
31
|
+
spec: 'https://ucp.dev/specification/overview',
|
|
32
|
+
transport: 'rest',
|
|
33
|
+
endpoint: `${this.config.domain}/ucp/v1`,
|
|
34
|
+
schema: 'https://ucp.dev/services/shopping/rest.openapi.json',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
capabilities: {
|
|
39
|
+
'dev.ucp.shopping.checkout': [
|
|
40
|
+
{ version: schema_1.UCP_VERSION, spec: 'https://ucp.dev/specification/checkout', schema: 'https://ucp.dev/schemas/shopping/checkout.json' },
|
|
41
|
+
],
|
|
42
|
+
'dev.ucp.shopping.fulfillment': [
|
|
43
|
+
{ version: schema_1.UCP_VERSION, spec: 'https://ucp.dev/specification/fulfillment', schema: 'https://ucp.dev/schemas/shopping/fulfillment.json', extends: 'dev.ucp.shopping.checkout' },
|
|
44
|
+
],
|
|
45
|
+
'dev.ucp.shopping.order': [
|
|
46
|
+
{ version: schema_1.UCP_VERSION, spec: 'https://ucp.dev/specification/order', schema: 'https://ucp.dev/schemas/shopping/order.json' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
payment_handlers: paymentHandlers,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
buildLinks() {
|
|
54
|
+
const links = [];
|
|
55
|
+
if (this.config.terms_url)
|
|
56
|
+
links.push({ type: 'terms_of_service', url: this.config.terms_url });
|
|
57
|
+
if (this.config.privacy_url)
|
|
58
|
+
links.push({ type: 'privacy_policy', url: this.config.privacy_url });
|
|
59
|
+
return links;
|
|
60
|
+
}
|
|
61
|
+
buildPaymentHandlers() {
|
|
62
|
+
const paymentHandlers = {};
|
|
63
|
+
for (const handler of this.config.payment_handlers) {
|
|
64
|
+
paymentHandlers[handler.namespace] = [{ id: handler.id, version: schema_1.UCP_VERSION, config: handler.config }];
|
|
65
|
+
}
|
|
66
|
+
return paymentHandlers;
|
|
67
|
+
}
|
|
68
|
+
validateCheckout(session) {
|
|
69
|
+
const messages = [];
|
|
70
|
+
if (!session.buyer?.email) {
|
|
71
|
+
messages.push({ type: 'error', code: 'missing', path: '$.buyer.email', content: 'Buyer email is required', severity: 'recoverable' });
|
|
72
|
+
}
|
|
73
|
+
if (!session.fulfillment?.methods?.[0]?.selected_destination_id) {
|
|
74
|
+
messages.push({ type: 'error', code: 'missing', path: '$.fulfillment.methods[0].selected_destination_id', content: 'Shipping address is required', severity: 'recoverable' });
|
|
75
|
+
}
|
|
76
|
+
return messages;
|
|
77
|
+
}
|
|
78
|
+
calculateTotals(lineItems, shippingTotal = 0) {
|
|
79
|
+
const subtotal = lineItems.reduce((sum, li) => sum + li.item.price * li.quantity, 0);
|
|
80
|
+
const tax = Math.round(subtotal * this.config.tax_rate);
|
|
81
|
+
const totals = [
|
|
82
|
+
{ type: 'subtotal', amount: subtotal },
|
|
83
|
+
{ type: 'tax', amount: tax },
|
|
84
|
+
{ type: 'total', amount: subtotal + shippingTotal + tax },
|
|
85
|
+
];
|
|
86
|
+
if (shippingTotal > 0) {
|
|
87
|
+
totals.splice(1, 0, { type: 'shipping', amount: shippingTotal });
|
|
88
|
+
}
|
|
89
|
+
return totals;
|
|
90
|
+
}
|
|
91
|
+
sessionFromRow(row) {
|
|
92
|
+
if (!row)
|
|
93
|
+
return undefined;
|
|
94
|
+
const lineItems = db_1.db.prepare('SELECT * FROM line_items WHERE checkout_id = ?').all(row.id);
|
|
95
|
+
return {
|
|
96
|
+
ucp: {
|
|
97
|
+
version: schema_1.UCP_VERSION,
|
|
98
|
+
capabilities: { 'dev.ucp.shopping.checkout': [{ version: schema_1.UCP_VERSION }] },
|
|
99
|
+
payment_handlers: this.buildPaymentHandlers(),
|
|
100
|
+
},
|
|
101
|
+
id: row.id,
|
|
102
|
+
status: row.status,
|
|
103
|
+
currency: row.currency,
|
|
104
|
+
buyer: row.buyer_json ? JSON.parse(row.buyer_json) : undefined,
|
|
105
|
+
line_items: lineItems.map((li) => ({
|
|
106
|
+
id: li.id,
|
|
107
|
+
item: JSON.parse(li.item_json),
|
|
108
|
+
quantity: li.quantity,
|
|
109
|
+
totals: li.totals_json ? JSON.parse(li.totals_json) : undefined,
|
|
110
|
+
})),
|
|
111
|
+
totals: JSON.parse(row.totals_json),
|
|
112
|
+
links: JSON.parse(row.links_json),
|
|
113
|
+
fulfillment: row.fulfillment_json ? JSON.parse(row.fulfillment_json) : undefined,
|
|
114
|
+
payment: row.payment_json ? JSON.parse(row.payment_json) : undefined,
|
|
115
|
+
messages: row.messages_json ? JSON.parse(row.messages_json) : undefined,
|
|
116
|
+
continue_url: row.continue_url || undefined,
|
|
117
|
+
expires_at: row.expires_at || undefined,
|
|
118
|
+
order: row.order_json ? JSON.parse(row.order_json) : undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
createCheckout(data) {
|
|
122
|
+
const id = `chk_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 12)}`;
|
|
123
|
+
const now = new Date().toISOString();
|
|
124
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
|
|
125
|
+
const lineItems = data.line_items.map((li, idx) => {
|
|
126
|
+
const catalogItem = this.itemsMap.get(li.item.id);
|
|
127
|
+
const item = catalogItem || { id: li.item.id, title: li.item.title || 'Unknown Item', price: li.item.price || 0 };
|
|
128
|
+
const subtotal = item.price * li.quantity;
|
|
129
|
+
return {
|
|
130
|
+
id: li.id || `li_${idx + 1}`,
|
|
131
|
+
item,
|
|
132
|
+
quantity: li.quantity,
|
|
133
|
+
totals: [{ type: 'subtotal', amount: subtotal }, { type: 'total', amount: subtotal }],
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
const totals = this.calculateTotals(lineItems);
|
|
137
|
+
const links = this.buildLinks();
|
|
138
|
+
const messages = this.validateCheckout({});
|
|
139
|
+
const status = messages.length > 0 ? 'incomplete' : 'ready_for_complete';
|
|
140
|
+
const continueUrl = `${this.config.domain}/checkout/${id}`;
|
|
141
|
+
const insertCheckout = db_1.db.prepare(`
|
|
142
|
+
INSERT INTO checkouts (id, status, currency, totals_json, links_json, messages_json, continue_url, expires_at, created_at, updated_at)
|
|
143
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
144
|
+
`);
|
|
145
|
+
const insertLineItem = db_1.db.prepare(`
|
|
146
|
+
INSERT INTO line_items (id, checkout_id, item_json, quantity, totals_json)
|
|
147
|
+
VALUES (?, ?, ?, ?, ?)
|
|
148
|
+
`);
|
|
149
|
+
db_1.db.transaction(() => {
|
|
150
|
+
insertCheckout.run(id, status, this.config.currency, JSON.stringify(totals), JSON.stringify(links), messages.length > 0 ? JSON.stringify(messages) : null, continueUrl, expiresAt, now, now);
|
|
151
|
+
for (const li of lineItems) {
|
|
152
|
+
insertLineItem.run(li.id, id, JSON.stringify(li.item), li.quantity, JSON.stringify(li.totals));
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
logger_1.logger.info({ checkoutId: id, lineItems: lineItems.length }, 'Checkout created');
|
|
156
|
+
return this.getCheckout(id);
|
|
157
|
+
}
|
|
158
|
+
getCheckout(id) {
|
|
159
|
+
const row = db_1.db.prepare('SELECT * FROM checkouts WHERE id = ?').get(id);
|
|
160
|
+
return this.sessionFromRow(row);
|
|
161
|
+
}
|
|
162
|
+
updateCheckout(id, data) {
|
|
163
|
+
const session = this.getCheckout(id);
|
|
164
|
+
if (!session)
|
|
165
|
+
return undefined;
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
let buyer = session.buyer;
|
|
168
|
+
let lineItems = session.line_items;
|
|
169
|
+
let fulfillment = session.fulfillment;
|
|
170
|
+
if (data.buyer) {
|
|
171
|
+
buyer = { ...buyer, ...data.buyer };
|
|
172
|
+
}
|
|
173
|
+
if (data.line_items) {
|
|
174
|
+
lineItems = data.line_items.map((li, idx) => {
|
|
175
|
+
const catalogItem = this.itemsMap.get(li.item.id);
|
|
176
|
+
const item = catalogItem || { id: li.item.id, title: li.item.title || 'Unknown Item', price: li.item.price || 0 };
|
|
177
|
+
const subtotal = item.price * li.quantity;
|
|
178
|
+
return {
|
|
179
|
+
id: li.id || `li_${idx + 1}`,
|
|
180
|
+
item,
|
|
181
|
+
quantity: li.quantity,
|
|
182
|
+
totals: [{ type: 'subtotal', amount: subtotal }, { type: 'total', amount: subtotal }],
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (data.fulfillment) {
|
|
187
|
+
const methods = data.fulfillment.methods.map((method, mIdx) => {
|
|
188
|
+
const destinations = method.destinations.map((dest, dIdx) => ({ ...dest, id: dest.id || `dest_${dIdx + 1}` }));
|
|
189
|
+
const groups = method.groups?.length ? method.groups : [{
|
|
190
|
+
id: `group_${mIdx + 1}`,
|
|
191
|
+
line_item_ids: lineItems.map((li) => li.id),
|
|
192
|
+
selected_option_id: this.config.shipping_options[0]?.id,
|
|
193
|
+
options: this.config.shipping_options.map((opt) => ({
|
|
194
|
+
id: opt.id, title: opt.title, description: opt.description || opt.estimated_days,
|
|
195
|
+
totals: [{ type: 'total', amount: opt.price }],
|
|
196
|
+
})),
|
|
197
|
+
}];
|
|
198
|
+
return {
|
|
199
|
+
id: method.id || `method_${mIdx + 1}`,
|
|
200
|
+
type: method.type,
|
|
201
|
+
line_item_ids: method.line_item_ids || lineItems.map((li) => li.id),
|
|
202
|
+
selected_destination_id: method.selected_destination_id || destinations[0]?.id,
|
|
203
|
+
destinations,
|
|
204
|
+
groups,
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
fulfillment = { methods };
|
|
208
|
+
}
|
|
209
|
+
// Calculate shipping total
|
|
210
|
+
let shippingTotal = 0;
|
|
211
|
+
if (fulfillment) {
|
|
212
|
+
for (const method of fulfillment.methods) {
|
|
213
|
+
for (const group of method.groups) {
|
|
214
|
+
const selectedOption = group.options.find((o) => o.id === group.selected_option_id);
|
|
215
|
+
if (selectedOption) {
|
|
216
|
+
const total = selectedOption.totals.find((t) => t.type === 'total');
|
|
217
|
+
shippingTotal += total?.amount || 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const totals = this.calculateTotals(lineItems, shippingTotal);
|
|
223
|
+
const messages = this.validateCheckout({ buyer, fulfillment });
|
|
224
|
+
const status = messages.length > 0 ? 'incomplete' : 'ready_for_complete';
|
|
225
|
+
db_1.db.transaction(() => {
|
|
226
|
+
db_1.db.prepare(`
|
|
227
|
+
UPDATE checkouts SET status = ?, buyer_json = ?, fulfillment_json = ?, totals_json = ?, messages_json = ?, updated_at = ?
|
|
228
|
+
WHERE id = ?
|
|
229
|
+
`).run(status, buyer ? JSON.stringify(buyer) : null, fulfillment ? JSON.stringify(fulfillment) : null, JSON.stringify(totals), messages.length > 0 ? JSON.stringify(messages) : null, now, id);
|
|
230
|
+
db_1.db.prepare('DELETE FROM line_items WHERE checkout_id = ?').run(id);
|
|
231
|
+
const insertLineItem = db_1.db.prepare('INSERT INTO line_items (id, checkout_id, item_json, quantity, totals_json) VALUES (?, ?, ?, ?, ?)');
|
|
232
|
+
for (const li of lineItems) {
|
|
233
|
+
insertLineItem.run(li.id, id, JSON.stringify(li.item), li.quantity, JSON.stringify(li.totals));
|
|
234
|
+
}
|
|
235
|
+
})();
|
|
236
|
+
logger_1.logger.info({ checkoutId: id, status }, 'Checkout updated');
|
|
237
|
+
return this.getCheckout(id);
|
|
238
|
+
}
|
|
239
|
+
async completeCheckout(id) {
|
|
240
|
+
const session = this.getCheckout(id);
|
|
241
|
+
if (!session)
|
|
242
|
+
return { error: 'Checkout session not found' };
|
|
243
|
+
if (session.status !== 'ready_for_complete') {
|
|
244
|
+
return { error: 'Checkout is not ready for completion', messages: session.messages };
|
|
245
|
+
}
|
|
246
|
+
const orderId = `order_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 12)}`;
|
|
247
|
+
const now = new Date().toISOString();
|
|
248
|
+
// Get total amount for payment
|
|
249
|
+
const totalAmount = session.totals.find(t => t.type === 'total')?.amount || 0;
|
|
250
|
+
// Payment processing - try Stripe first, then PayPal
|
|
251
|
+
let paymentIntentId = null;
|
|
252
|
+
let paypalOrderId = null;
|
|
253
|
+
let paymentProvider = null;
|
|
254
|
+
let paymentStatus = 'none';
|
|
255
|
+
if (totalAmount > 0) {
|
|
256
|
+
// Try Stripe first
|
|
257
|
+
if ((0, stripe_1.isStripeConfigured)()) {
|
|
258
|
+
try {
|
|
259
|
+
const paymentIntent = await (0, stripe_1.createPaymentIntent)({
|
|
260
|
+
amount: totalAmount,
|
|
261
|
+
currency: session.currency,
|
|
262
|
+
checkoutId: id,
|
|
263
|
+
customerEmail: session.buyer?.email,
|
|
264
|
+
metadata: { order_id: orderId },
|
|
265
|
+
});
|
|
266
|
+
if (paymentIntent) {
|
|
267
|
+
paymentIntentId = paymentIntent.id;
|
|
268
|
+
paymentProvider = 'stripe';
|
|
269
|
+
paymentStatus = 'pending';
|
|
270
|
+
logger_1.logger.info({ checkoutId: id, paymentIntentId, amount: totalAmount }, 'Stripe payment intent created');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
logger_1.logger.error({ error, checkoutId: id }, 'Failed to create Stripe payment intent');
|
|
275
|
+
return { error: 'Payment processing failed' };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Fall back to PayPal if Stripe not configured
|
|
279
|
+
else if ((0, paypal_1.isPayPalConfigured)()) {
|
|
280
|
+
try {
|
|
281
|
+
const ppOrder = await (0, paypal_1.createPayPalOrder)({
|
|
282
|
+
amount: totalAmount,
|
|
283
|
+
currency: session.currency,
|
|
284
|
+
checkoutId: id,
|
|
285
|
+
description: `Order ${orderId}`,
|
|
286
|
+
});
|
|
287
|
+
if (ppOrder) {
|
|
288
|
+
paypalOrderId = ppOrder.id;
|
|
289
|
+
paymentProvider = 'paypal';
|
|
290
|
+
paymentStatus = 'pending';
|
|
291
|
+
logger_1.logger.info({ checkoutId: id, paypalOrderId, amount: totalAmount }, 'PayPal order created');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
logger_1.logger.error({ error, checkoutId: id }, 'Failed to create PayPal order');
|
|
296
|
+
return { error: 'Payment processing failed' };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const orderFulfillment = session.fulfillment ? {
|
|
301
|
+
expectations: session.fulfillment.methods.map((m, idx) => ({
|
|
302
|
+
id: `exp_${idx + 1}`,
|
|
303
|
+
line_items: m.line_item_ids.map((liId) => {
|
|
304
|
+
const li = session.line_items.find((l) => l.id === liId);
|
|
305
|
+
return { id: liId, quantity: li?.quantity || 1 };
|
|
306
|
+
}),
|
|
307
|
+
method_type: m.type,
|
|
308
|
+
destination: m.destinations.find((d) => d.id === m.selected_destination_id),
|
|
309
|
+
description: 'Arrives in 5-7 business days',
|
|
310
|
+
fulfillable_on: 'now',
|
|
311
|
+
})),
|
|
312
|
+
events: [],
|
|
313
|
+
} : undefined;
|
|
314
|
+
const orderConfirmation = {
|
|
315
|
+
id: orderId,
|
|
316
|
+
permalink_url: `${this.config.domain}/orders/${orderId}`,
|
|
317
|
+
};
|
|
318
|
+
db_1.db.transaction(() => {
|
|
319
|
+
db_1.db.prepare(`
|
|
320
|
+
INSERT INTO orders (id, checkout_id, permalink_url, buyer_json, line_items_json, totals_json, fulfillment_json, payment_intent_id, paypal_order_id, payment_provider, payment_status, created_at)
|
|
321
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
322
|
+
`).run(orderId, id, orderConfirmation.permalink_url, session.buyer ? JSON.stringify(session.buyer) : null, JSON.stringify(session.line_items), JSON.stringify(session.totals), orderFulfillment ? JSON.stringify(orderFulfillment) : null, paymentIntentId, paypalOrderId, paymentProvider, paymentStatus, now);
|
|
323
|
+
db_1.db.prepare(`
|
|
324
|
+
UPDATE checkouts SET status = 'completed', order_json = ?, continue_url = NULL, messages_json = NULL, payment_intent_id = ?, paypal_order_id = ?, payment_provider = ?, payment_status = ?, updated_at = ?
|
|
325
|
+
WHERE id = ?
|
|
326
|
+
`).run(JSON.stringify(orderConfirmation), paymentIntentId, paypalOrderId, paymentProvider, paymentStatus, now, id);
|
|
327
|
+
})();
|
|
328
|
+
logger_1.logger.info({ checkoutId: id, orderId, paymentProvider, paymentIntentId, paypalOrderId }, 'Checkout completed, order created');
|
|
329
|
+
return this.getCheckout(id);
|
|
330
|
+
}
|
|
331
|
+
cancelCheckout(id) {
|
|
332
|
+
const session = this.getCheckout(id);
|
|
333
|
+
if (!session)
|
|
334
|
+
return undefined;
|
|
335
|
+
db_1.db.prepare(`UPDATE checkouts SET status = 'canceled', continue_url = NULL, updated_at = ? WHERE id = ?`)
|
|
336
|
+
.run(new Date().toISOString(), id);
|
|
337
|
+
logger_1.logger.info({ checkoutId: id }, 'Checkout canceled');
|
|
338
|
+
return this.getCheckout(id);
|
|
339
|
+
}
|
|
340
|
+
getOrder(id) {
|
|
341
|
+
const row = db_1.db.prepare('SELECT * FROM orders WHERE id = ?').get(id);
|
|
342
|
+
if (!row)
|
|
343
|
+
return undefined;
|
|
344
|
+
return {
|
|
345
|
+
ucp: { version: schema_1.UCP_VERSION, capabilities: { 'dev.ucp.shopping.order': [{ version: schema_1.UCP_VERSION }] } },
|
|
346
|
+
id: row.id,
|
|
347
|
+
checkout_id: row.checkout_id,
|
|
348
|
+
permalink_url: row.permalink_url,
|
|
349
|
+
line_items: JSON.parse(row.line_items_json),
|
|
350
|
+
buyer: row.buyer_json ? JSON.parse(row.buyer_json) : undefined,
|
|
351
|
+
totals: JSON.parse(row.totals_json),
|
|
352
|
+
fulfillment: row.fulfillment_json ? JSON.parse(row.fulfillment_json) : undefined,
|
|
353
|
+
created_at: row.created_at,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
listOrders() {
|
|
357
|
+
const rows = db_1.db.prepare('SELECT * FROM orders ORDER BY created_at DESC').all();
|
|
358
|
+
return rows.map((row) => this.getOrder(row.id));
|
|
359
|
+
}
|
|
360
|
+
getConfig() {
|
|
361
|
+
return this.config;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
exports.UCPServerDB = UCPServerDB;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { MerchantConfig, CheckoutSession, Order, Buyer, Fulfillment } from './schema';
|
|
2
|
+
export declare class UCPServer {
|
|
3
|
+
private config;
|
|
4
|
+
private itemsMap;
|
|
5
|
+
constructor(config: MerchantConfig);
|
|
6
|
+
getProfile(): {
|
|
7
|
+
ucp: {
|
|
8
|
+
version: string;
|
|
9
|
+
services: {
|
|
10
|
+
'dev.ucp.shopping': {
|
|
11
|
+
version: string;
|
|
12
|
+
spec: string;
|
|
13
|
+
transport: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
schema: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
capabilities: {
|
|
19
|
+
'dev.ucp.shopping.checkout': {
|
|
20
|
+
version: string;
|
|
21
|
+
spec: string;
|
|
22
|
+
schema: string;
|
|
23
|
+
}[];
|
|
24
|
+
'dev.ucp.shopping.fulfillment': {
|
|
25
|
+
version: string;
|
|
26
|
+
spec: string;
|
|
27
|
+
schema: string;
|
|
28
|
+
extends: string;
|
|
29
|
+
}[];
|
|
30
|
+
'dev.ucp.shopping.order': {
|
|
31
|
+
version: string;
|
|
32
|
+
spec: string;
|
|
33
|
+
schema: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
payment_handlers: Record<string, {
|
|
37
|
+
id: string;
|
|
38
|
+
version: string;
|
|
39
|
+
config?: Record<string, unknown>;
|
|
40
|
+
}[]>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
private calculateLineItemTotals;
|
|
44
|
+
private validateCheckout;
|
|
45
|
+
createCheckout(data: {
|
|
46
|
+
line_items: {
|
|
47
|
+
id?: string;
|
|
48
|
+
item: {
|
|
49
|
+
id: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
price?: number;
|
|
52
|
+
};
|
|
53
|
+
quantity: number;
|
|
54
|
+
}[];
|
|
55
|
+
}): CheckoutSession;
|
|
56
|
+
getCheckout(id: string): CheckoutSession | undefined;
|
|
57
|
+
updateCheckout(id: string, data: {
|
|
58
|
+
buyer?: Buyer;
|
|
59
|
+
line_items?: {
|
|
60
|
+
id?: string;
|
|
61
|
+
item: {
|
|
62
|
+
id: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
price?: number;
|
|
65
|
+
};
|
|
66
|
+
quantity: number;
|
|
67
|
+
}[];
|
|
68
|
+
fulfillment?: Fulfillment;
|
|
69
|
+
}): CheckoutSession | undefined;
|
|
70
|
+
completeCheckout(id: string): Promise<CheckoutSession | {
|
|
71
|
+
error: string;
|
|
72
|
+
}>;
|
|
73
|
+
cancelCheckout(id: string): CheckoutSession | undefined;
|
|
74
|
+
getOrder(id: string): Order | undefined;
|
|
75
|
+
listOrders(): Order[];
|
|
76
|
+
getConfig(): MerchantConfig;
|
|
77
|
+
}
|