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