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
package/.env.example
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# UCP Server Configuration
|
|
2
|
+
|
|
3
|
+
# Server
|
|
4
|
+
PORT=3000
|
|
5
|
+
NODE_ENV=production
|
|
6
|
+
|
|
7
|
+
# Database (SQLite path)
|
|
8
|
+
DATABASE_PATH=./data/ucp.db
|
|
9
|
+
|
|
10
|
+
# Logging
|
|
11
|
+
LOG_LEVEL=info
|
|
12
|
+
|
|
13
|
+
# UCP (your public domain)
|
|
14
|
+
UCP_DOMAIN=https://your-store.com
|
|
15
|
+
|
|
16
|
+
# Stripe (optional - for real payments)
|
|
17
|
+
STRIPE_SECRET_KEY=sk_test_...
|
|
18
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
19
|
+
|
|
20
|
+
# PayPal (optional - alternative to Stripe)
|
|
21
|
+
PAYPAL_CLIENT_ID=...
|
|
22
|
+
PAYPAL_CLIENT_SECRET=...
|
|
23
|
+
PAYPAL_MODE=sandbox
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
# Install build dependencies for better-sqlite3
|
|
4
|
+
RUN apk add --no-cache python3 make g++
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
# Copy package files
|
|
9
|
+
COPY package*.json ./
|
|
10
|
+
|
|
11
|
+
# Install dependencies
|
|
12
|
+
RUN npm ci --only=production
|
|
13
|
+
|
|
14
|
+
# Copy source
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
# Build TypeScript
|
|
18
|
+
RUN npm run build
|
|
19
|
+
|
|
20
|
+
# Create data directory
|
|
21
|
+
RUN mkdir -p /app/data
|
|
22
|
+
|
|
23
|
+
# Expose port
|
|
24
|
+
EXPOSE 3000
|
|
25
|
+
|
|
26
|
+
# Set environment
|
|
27
|
+
ENV NODE_ENV=production
|
|
28
|
+
ENV DATABASE_PATH=/app/data/ucp.db
|
|
29
|
+
|
|
30
|
+
# Health check
|
|
31
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
32
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
|
33
|
+
|
|
34
|
+
# Run server
|
|
35
|
+
CMD ["node", "dist/cli.js", "serve", "/app/config/merchant-config.json"]
|
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# ucpify
|
|
2
|
+
|
|
3
|
+
Generate UCP-compliant (Universal Commerce Protocol) servers for merchants from a simple schema.
|
|
4
|
+
|
|
5
|
+
## What is UCP?
|
|
6
|
+
|
|
7
|
+
The [Universal Commerce Protocol](https://ucp.dev) is an open standard for agentic commerce, enabling AI agents, apps, businesses, and payment providers to interact seamlessly. UCP was co-developed by Google, Shopify, Etsy, Wayfair, Target, and Walmart.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Initialize a sample merchant config
|
|
13
|
+
npx ts-node src/cli.ts init
|
|
14
|
+
|
|
15
|
+
# Edit merchant-config.json to add your products, shipping, payments
|
|
16
|
+
|
|
17
|
+
# Start the UCP server
|
|
18
|
+
npx ts-node src/cli.ts serve merchant-config.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Merchant Configuration Schema
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"name": "My Store",
|
|
26
|
+
"domain": "http://localhost:3000",
|
|
27
|
+
"currency": "USD",
|
|
28
|
+
"tax_rate": 0.08,
|
|
29
|
+
"terms_url": "https://example.com/terms",
|
|
30
|
+
"port": 3000,
|
|
31
|
+
"items": [
|
|
32
|
+
{
|
|
33
|
+
"id": "item_001",
|
|
34
|
+
"title": "Classic T-Shirt",
|
|
35
|
+
"description": "A comfortable cotton t-shirt",
|
|
36
|
+
"price": 2500
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"shipping_options": [
|
|
40
|
+
{
|
|
41
|
+
"id": "standard",
|
|
42
|
+
"title": "Standard Shipping",
|
|
43
|
+
"price": 500,
|
|
44
|
+
"estimated_days": "5-7 business days"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"payment_handlers": [
|
|
48
|
+
{
|
|
49
|
+
"namespace": "com.stripe",
|
|
50
|
+
"id": "stripe_handler",
|
|
51
|
+
"config": { "publishable_key": "pk_test_..." }
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## UCP Endpoints
|
|
58
|
+
|
|
59
|
+
Once running, your server exposes these UCP-compliant endpoints:
|
|
60
|
+
|
|
61
|
+
| Endpoint | Method | Description |
|
|
62
|
+
|----------|--------|-------------|
|
|
63
|
+
| `/.well-known/ucp` | GET | UCP Profile (discovery) |
|
|
64
|
+
| `/ucp/v1/checkout-sessions` | POST | Create checkout session |
|
|
65
|
+
| `/ucp/v1/checkout-sessions/:id` | GET | Get checkout session |
|
|
66
|
+
| `/ucp/v1/checkout-sessions/:id` | PUT | Update checkout session |
|
|
67
|
+
| `/ucp/v1/checkout-sessions/:id/complete` | POST | Complete checkout (create order) |
|
|
68
|
+
| `/ucp/v1/checkout-sessions/:id/cancel` | POST | Cancel checkout |
|
|
69
|
+
| `/ucp/v1/orders` | GET | List orders |
|
|
70
|
+
| `/ucp/v1/orders/:id` | GET | Get order |
|
|
71
|
+
| `/ucp/v1/items` | GET | Product catalog |
|
|
72
|
+
|
|
73
|
+
## Example Usage
|
|
74
|
+
|
|
75
|
+
### 1. Create a checkout session
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -X POST http://localhost:3000/ucp/v1/checkout-sessions \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
-d '{
|
|
81
|
+
"line_items": [
|
|
82
|
+
{
|
|
83
|
+
"item": { "id": "item_001" },
|
|
84
|
+
"quantity": 2
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Update with buyer info and shipping
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
curl -X PUT http://localhost:3000/ucp/v1/checkout-sessions/chk_xxx \
|
|
94
|
+
-H "Content-Type: application/json" \
|
|
95
|
+
-d '{
|
|
96
|
+
"buyer": {
|
|
97
|
+
"email": "customer@example.com",
|
|
98
|
+
"first_name": "Jane",
|
|
99
|
+
"last_name": "Doe"
|
|
100
|
+
},
|
|
101
|
+
"line_items": [{ "item": { "id": "item_001" }, "quantity": 2 }],
|
|
102
|
+
"fulfillment": {
|
|
103
|
+
"methods": [{
|
|
104
|
+
"type": "shipping",
|
|
105
|
+
"destinations": [{
|
|
106
|
+
"street_address": "123 Main St",
|
|
107
|
+
"address_locality": "Springfield",
|
|
108
|
+
"address_region": "IL",
|
|
109
|
+
"postal_code": "62701",
|
|
110
|
+
"address_country": "US"
|
|
111
|
+
}]
|
|
112
|
+
}]
|
|
113
|
+
}
|
|
114
|
+
}'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Complete checkout
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
curl -X POST http://localhost:3000/ucp/v1/checkout-sessions/chk_xxx/complete
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## CLI Commands
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Create sample configuration
|
|
127
|
+
ucpify init --output my-store.json
|
|
128
|
+
|
|
129
|
+
# Validate configuration
|
|
130
|
+
ucpify validate my-store.json
|
|
131
|
+
|
|
132
|
+
# Start server
|
|
133
|
+
ucpify serve my-store.json --port 8080
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Programmatic Usage
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { createExpressApp, MerchantConfigSchema } from 'ucpify';
|
|
140
|
+
|
|
141
|
+
const config = MerchantConfigSchema.parse({
|
|
142
|
+
name: 'My Store',
|
|
143
|
+
domain: 'http://localhost:3000',
|
|
144
|
+
currency: 'USD',
|
|
145
|
+
items: [
|
|
146
|
+
{ id: 'prod_1', title: 'Widget', price: 1999 }
|
|
147
|
+
],
|
|
148
|
+
shipping_options: [
|
|
149
|
+
{ id: 'standard', title: 'Standard', price: 500 }
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const app = createExpressApp(config);
|
|
154
|
+
app.listen(3000);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Identity Linking (Bring Your Own OAuth)
|
|
164
|
+
|
|
165
|
+
UCP uses OAuth 2.0 for identity linking between agents and merchants. ucpify does **not** include a built-in OAuth server—instead, integrate your existing OAuth provider.
|
|
166
|
+
|
|
167
|
+
### Required OAuth Endpoints
|
|
168
|
+
|
|
169
|
+
Your OAuth provider must expose these endpoints:
|
|
170
|
+
|
|
171
|
+
| Endpoint | Method | Description |
|
|
172
|
+
|----------|--------|-------------|
|
|
173
|
+
| `/oauth2/authorize` | GET | Authorization page (user consent) |
|
|
174
|
+
| `/oauth2/token` | POST | Token exchange |
|
|
175
|
+
| `/oauth2/revoke` | POST | Token revocation |
|
|
176
|
+
|
|
177
|
+
### OAuth Discovery Endpoint
|
|
178
|
+
|
|
179
|
+
Create `/.well-known/oauth-authorization-server` returning:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"issuer": "https://your-store.com",
|
|
184
|
+
"authorization_endpoint": "https://your-store.com/oauth2/authorize",
|
|
185
|
+
"token_endpoint": "https://your-store.com/oauth2/token",
|
|
186
|
+
"revocation_endpoint": "https://your-store.com/oauth2/revoke",
|
|
187
|
+
"scopes_supported": [
|
|
188
|
+
"ucp:scopes:checkout_session",
|
|
189
|
+
"ucp:scopes:order_read",
|
|
190
|
+
"ucp:scopes:order_manage"
|
|
191
|
+
],
|
|
192
|
+
"response_types_supported": ["code"],
|
|
193
|
+
"grant_types_supported": ["authorization_code", "refresh_token"],
|
|
194
|
+
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
|
|
195
|
+
"service_documentation": "https://your-store.com/docs/oauth"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### UCP OAuth Scopes
|
|
200
|
+
|
|
201
|
+
| Scope | Description |
|
|
202
|
+
|-------|-------------|
|
|
203
|
+
| `ucp:scopes:checkout_session` | Create and manage checkout sessions |
|
|
204
|
+
| `ucp:scopes:order_read` | Read order information |
|
|
205
|
+
| `ucp:scopes:order_manage` | Manage orders (cancel, refund) |
|
|
206
|
+
|
|
207
|
+
### Integration Examples
|
|
208
|
+
|
|
209
|
+
#### Using Auth0
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
// Add to your Express app
|
|
213
|
+
app.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
214
|
+
res.json({
|
|
215
|
+
issuer: 'https://your-tenant.auth0.com',
|
|
216
|
+
authorization_endpoint: 'https://your-tenant.auth0.com/authorize',
|
|
217
|
+
token_endpoint: 'https://your-tenant.auth0.com/oauth/token',
|
|
218
|
+
revocation_endpoint: 'https://your-tenant.auth0.com/oauth/revoke',
|
|
219
|
+
scopes_supported: ['ucp:scopes:checkout_session'],
|
|
220
|
+
response_types_supported: ['code'],
|
|
221
|
+
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
222
|
+
token_endpoint_auth_methods_supported: ['client_secret_basic'],
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### Using Keycloak
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
# Add to your Flask app
|
|
231
|
+
@app.route('/.well-known/oauth-authorization-server')
|
|
232
|
+
def oauth_discovery():
|
|
233
|
+
return jsonify({
|
|
234
|
+
"issuer": "https://keycloak.example.com/realms/merchant",
|
|
235
|
+
"authorization_endpoint": "https://keycloak.example.com/realms/merchant/protocol/openid-connect/auth",
|
|
236
|
+
"token_endpoint": "https://keycloak.example.com/realms/merchant/protocol/openid-connect/token",
|
|
237
|
+
"revocation_endpoint": "https://keycloak.example.com/realms/merchant/protocol/openid-connect/revoke",
|
|
238
|
+
"scopes_supported": ["ucp:scopes:checkout_session"],
|
|
239
|
+
"response_types_supported": ["code"],
|
|
240
|
+
"grant_types_supported": ["authorization_code", "refresh_token"],
|
|
241
|
+
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Using AWS Cognito
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
app.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
249
|
+
const cognitoDomain = 'https://your-domain.auth.us-east-1.amazoncognito.com';
|
|
250
|
+
res.json({
|
|
251
|
+
issuer: cognitoDomain,
|
|
252
|
+
authorization_endpoint: `${cognitoDomain}/oauth2/authorize`,
|
|
253
|
+
token_endpoint: `${cognitoDomain}/oauth2/token`,
|
|
254
|
+
revocation_endpoint: `${cognitoDomain}/oauth2/revoke`,
|
|
255
|
+
scopes_supported: ['ucp:scopes:checkout_session'],
|
|
256
|
+
response_types_supported: ['code'],
|
|
257
|
+
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
258
|
+
token_endpoint_auth_methods_supported: ['client_secret_basic'],
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Protecting UCP Endpoints
|
|
264
|
+
|
|
265
|
+
Add middleware to validate tokens on protected endpoints:
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
// JavaScript/Express
|
|
269
|
+
const validateToken = async (req, res, next) => {
|
|
270
|
+
const authHeader = req.headers.authorization;
|
|
271
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
272
|
+
return res.status(401).json({ error: 'Missing token' });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const token = authHeader.slice(7);
|
|
276
|
+
// Validate with your OAuth provider
|
|
277
|
+
const valid = await verifyTokenWithProvider(token);
|
|
278
|
+
if (!valid) {
|
|
279
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
280
|
+
}
|
|
281
|
+
next();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Apply to checkout endpoints
|
|
285
|
+
app.post('/ucp/v1/checkout-sessions', validateToken, createCheckout);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
# Python/Flask
|
|
290
|
+
from functools import wraps
|
|
291
|
+
|
|
292
|
+
def require_oauth(f):
|
|
293
|
+
@wraps(f)
|
|
294
|
+
def decorated(*args, **kwargs):
|
|
295
|
+
auth_header = request.headers.get('Authorization', '')
|
|
296
|
+
if not auth_header.startswith('Bearer '):
|
|
297
|
+
return jsonify({'error': 'Missing token'}), 401
|
|
298
|
+
|
|
299
|
+
token = auth_header[7:]
|
|
300
|
+
# Validate with your OAuth provider
|
|
301
|
+
if not verify_token_with_provider(token):
|
|
302
|
+
return jsonify({'error': 'Invalid token'}), 401
|
|
303
|
+
return f(*args, **kwargs)
|
|
304
|
+
return decorated
|
|
305
|
+
|
|
306
|
+
@app.route('/ucp/v1/checkout-sessions', methods=['POST'])
|
|
307
|
+
@require_oauth
|
|
308
|
+
def create_checkout():
|
|
309
|
+
...
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Agent Platform Flow
|
|
313
|
+
|
|
314
|
+
1. Agent discovers OAuth endpoints via `/.well-known/oauth-authorization-server`
|
|
315
|
+
2. Agent redirects user to `authorization_endpoint` with scopes
|
|
316
|
+
3. User consents on merchant's OAuth page
|
|
317
|
+
4. Agent receives authorization code
|
|
318
|
+
5. Agent exchanges code for access token at `token_endpoint`
|
|
319
|
+
6. Agent uses access token in `Authorization: Bearer <token>` header
|
|
320
|
+
7. Agent refreshes token when expired using `refresh_token` grant
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
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
ADDED