@codihaus/claude-skills 1.0.0 → 1.3.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/knowledge/domains/_index.md +105 -0
- package/knowledge/domains/ecommerce/_index.md +499 -0
- package/knowledge/domains/saas/_index.md +371 -0
- package/knowledge/stacks/_index.md +101 -0
- package/knowledge/stacks/directus/_index.md +349 -0
- package/knowledge/stacks/nextjs/_index.md +654 -0
- package/knowledge/stacks/nuxt/_index.md +469 -0
- package/package.json +3 -1
- package/project-scripts/graph.py +330 -0
- package/skills/_registry.md +61 -0
- package/skills/dev-coding/SKILL.md +16 -5
- package/skills/dev-coding-backend/SKILL.md +116 -251
- package/skills/dev-coding-frontend/SKILL.md +134 -388
- package/skills/dev-review/SKILL.md +13 -2
- package/skills/dev-scout/SKILL.md +180 -2
- package/skills/dev-scout/references/stack-patterns.md +371 -0
- package/skills/dev-specs/SKILL.md +74 -2
- package/src/commands/init.js +89 -12
- package/src/utils/project-setup.js +444 -0
- package/src/utils/skills.js +87 -1
- /package/{skills/dev-coding-frontend/references/nextjs.md → knowledge/stacks/nextjs/references/performance.md} +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Domain Knowledge
|
|
2
|
+
|
|
3
|
+
Business domain knowledge. Answers: **"WHAT do I build for this business?"**
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
When skills work on business-specific features, they load the relevant domain folder for:
|
|
8
|
+
- Business terminology and concepts
|
|
9
|
+
- Common entities and data models
|
|
10
|
+
- Workflow and state machines
|
|
11
|
+
- Validation rules and business logic
|
|
12
|
+
- Compliance and regulatory requirements
|
|
13
|
+
|
|
14
|
+
## Available Domains
|
|
15
|
+
|
|
16
|
+
| Domain | Folder | Description | Status |
|
|
17
|
+
|--------|--------|-------------|--------|
|
|
18
|
+
| SaaS | `saas/` | Subscriptions, multi-tenancy, billing | Ready |
|
|
19
|
+
| E-commerce | `ecommerce/` | Products, carts, orders, fulfillment | Ready |
|
|
20
|
+
| Insurance | `insurance/` | Policies, claims, underwriting | Planned |
|
|
21
|
+
| Healthcare | `healthcare/` | Patient data, HIPAA compliance | Planned |
|
|
22
|
+
|
|
23
|
+
## Folder Structure
|
|
24
|
+
|
|
25
|
+
Each domain follows this structure:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
domains/{domain-name}/
|
|
29
|
+
├── _index.md # Main knowledge file
|
|
30
|
+
│ ├── Overview # What it is, key concepts
|
|
31
|
+
│ ├── Terminology # Business terms
|
|
32
|
+
│ ├── Common Entities # Data models
|
|
33
|
+
│ ├── Workflows # State machines, lifecycles
|
|
34
|
+
│ ├── For /dev-specs # Spec writing guidance
|
|
35
|
+
│ ├── For /dev-coding # Implementation patterns
|
|
36
|
+
│ ├── For /dev-review # Review checklists
|
|
37
|
+
│ └── Integration # Common service integrations
|
|
38
|
+
│
|
|
39
|
+
├── references/ # Detailed documentation
|
|
40
|
+
│ ├── workflows.md # Detailed state machines
|
|
41
|
+
│ ├── compliance.md # Regulatory requirements
|
|
42
|
+
│ └── glossary.md # Extended terminology
|
|
43
|
+
│
|
|
44
|
+
└── assets/ # Code templates
|
|
45
|
+
├── schema.prisma # Example database schema
|
|
46
|
+
├── types.ts # TypeScript types
|
|
47
|
+
└── validation.ts # Validation schemas
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How Skills Use Domain Knowledge
|
|
51
|
+
|
|
52
|
+
### /dev-specs
|
|
53
|
+
```
|
|
54
|
+
1. Identify domain from BRD/use cases
|
|
55
|
+
2. Read domains/{domain}/_index.md
|
|
56
|
+
3. Use correct terminology
|
|
57
|
+
4. Follow domain workflows
|
|
58
|
+
5. Include domain-specific validation
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### /dev-coding
|
|
62
|
+
```
|
|
63
|
+
1. Read specs → identify domain
|
|
64
|
+
2. Read domains/{domain}/_index.md
|
|
65
|
+
3. Use "For /dev-coding" section
|
|
66
|
+
4. Implement domain state machines correctly
|
|
67
|
+
5. Follow domain business rules
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### /dev-review
|
|
71
|
+
```
|
|
72
|
+
1. Identify domain from code
|
|
73
|
+
2. Read domains/{domain}/_index.md
|
|
74
|
+
3. Use "For /dev-review" section
|
|
75
|
+
4. Verify domain rules implemented correctly
|
|
76
|
+
5. Check compliance requirements
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Detecting Domain
|
|
80
|
+
|
|
81
|
+
Skills detect domain from:
|
|
82
|
+
1. BRD mentions (subscription, cart, policy)
|
|
83
|
+
2. Entity names (Tenant, Order, Claim)
|
|
84
|
+
3. User explicitly states domain
|
|
85
|
+
4. Project type indicators
|
|
86
|
+
|
|
87
|
+
## Adding New Domains
|
|
88
|
+
|
|
89
|
+
1. Create folder: `domains/{name}/`
|
|
90
|
+
2. Create `_index.md` following the structure above
|
|
91
|
+
3. Add `references/` with detailed docs
|
|
92
|
+
4. Add `assets/` with code templates
|
|
93
|
+
5. Update this index
|
|
94
|
+
|
|
95
|
+
## Stack vs Domain
|
|
96
|
+
|
|
97
|
+
| Type | Answers | Examples | Location |
|
|
98
|
+
|------|---------|----------|----------|
|
|
99
|
+
| **Stack** | HOW to build | Directus, Nuxt, Next.js | `knowledge/stacks/` |
|
|
100
|
+
| **Domain** (this folder) | WHAT to build | SaaS, E-commerce, Insurance | `knowledge/domains/` |
|
|
101
|
+
|
|
102
|
+
Stack knowledge is about the **technology**.
|
|
103
|
+
Domain knowledge is about the **business**.
|
|
104
|
+
|
|
105
|
+
Both inform better specs, coding, and reviews.
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# E-commerce Domain Knowledge
|
|
2
|
+
|
|
3
|
+
> Online retail business patterns and implementation guidance
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**What it is:**
|
|
8
|
+
- Online product/service sales
|
|
9
|
+
- Shopping cart and checkout flow
|
|
10
|
+
- Payment processing
|
|
11
|
+
- Order fulfillment and shipping
|
|
12
|
+
|
|
13
|
+
**Key concepts:**
|
|
14
|
+
- **SKU** = Stock Keeping Unit (unique product variant)
|
|
15
|
+
- **Cart** = Temporary collection of items
|
|
16
|
+
- **Order** = Confirmed purchase
|
|
17
|
+
- **Fulfillment** = Picking, packing, shipping
|
|
18
|
+
- **Inventory** = Stock levels and tracking
|
|
19
|
+
|
|
20
|
+
## Terminology
|
|
21
|
+
|
|
22
|
+
| Term | Definition |
|
|
23
|
+
|------|------------|
|
|
24
|
+
| SKU | Unique identifier for product variant |
|
|
25
|
+
| AOV | Average Order Value |
|
|
26
|
+
| GMV | Gross Merchandise Value |
|
|
27
|
+
| Conversion Rate | % of visitors who purchase |
|
|
28
|
+
| Cart Abandonment | % of carts not completed |
|
|
29
|
+
| COGS | Cost of Goods Sold |
|
|
30
|
+
| Backorder | Order for out-of-stock item |
|
|
31
|
+
|
|
32
|
+
## Common Entities
|
|
33
|
+
|
|
34
|
+
### Product Entities
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Product
|
|
38
|
+
├── id, name, slug, description
|
|
39
|
+
├── status (draft, active, archived)
|
|
40
|
+
├── category_id
|
|
41
|
+
├── base_price
|
|
42
|
+
├── images[]
|
|
43
|
+
└── metadata (JSON)
|
|
44
|
+
|
|
45
|
+
ProductVariant (SKU)
|
|
46
|
+
├── product_id
|
|
47
|
+
├── sku (unique)
|
|
48
|
+
├── name (Size: M, Color: Red)
|
|
49
|
+
├── price (override or null)
|
|
50
|
+
├── attributes (JSON)
|
|
51
|
+
└── inventory_quantity
|
|
52
|
+
|
|
53
|
+
Category
|
|
54
|
+
├── id, name, slug
|
|
55
|
+
├── parent_id (hierarchical)
|
|
56
|
+
└── sort_order
|
|
57
|
+
|
|
58
|
+
Inventory
|
|
59
|
+
├── variant_id
|
|
60
|
+
├── location_id (warehouse)
|
|
61
|
+
├── quantity
|
|
62
|
+
├── reserved_quantity
|
|
63
|
+
└── reorder_point
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Order Entities
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Cart
|
|
70
|
+
├── id, session_id or user_id
|
|
71
|
+
├── items[]
|
|
72
|
+
├── expires_at
|
|
73
|
+
└── metadata
|
|
74
|
+
|
|
75
|
+
CartItem
|
|
76
|
+
├── cart_id
|
|
77
|
+
├── variant_id
|
|
78
|
+
├── quantity
|
|
79
|
+
├── price_at_add
|
|
80
|
+
└── metadata
|
|
81
|
+
|
|
82
|
+
Order
|
|
83
|
+
├── id, order_number
|
|
84
|
+
├── user_id, email
|
|
85
|
+
├── status (pending, paid, shipped, delivered, canceled)
|
|
86
|
+
├── subtotal, tax, shipping, total
|
|
87
|
+
├── shipping_address
|
|
88
|
+
├── billing_address
|
|
89
|
+
└── metadata
|
|
90
|
+
|
|
91
|
+
OrderItem
|
|
92
|
+
├── order_id
|
|
93
|
+
├── variant_id
|
|
94
|
+
├── quantity
|
|
95
|
+
├── unit_price
|
|
96
|
+
├── total_price
|
|
97
|
+
└── fulfilled_quantity
|
|
98
|
+
|
|
99
|
+
Payment
|
|
100
|
+
├── order_id
|
|
101
|
+
├── amount, currency
|
|
102
|
+
├── status (pending, completed, failed, refunded)
|
|
103
|
+
├── provider (stripe, paypal)
|
|
104
|
+
├── provider_id
|
|
105
|
+
└── metadata
|
|
106
|
+
|
|
107
|
+
Shipment
|
|
108
|
+
├── order_id
|
|
109
|
+
├── tracking_number
|
|
110
|
+
├── carrier
|
|
111
|
+
├── status (pending, shipped, delivered)
|
|
112
|
+
├── shipped_at
|
|
113
|
+
└── items[]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Order Lifecycle
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
┌────────────┐
|
|
120
|
+
│ Cart │
|
|
121
|
+
└─────┬──────┘
|
|
122
|
+
│ (checkout)
|
|
123
|
+
┌─────▼──────┐
|
|
124
|
+
│ Pending │ (awaiting payment)
|
|
125
|
+
└─────┬──────┘
|
|
126
|
+
│
|
|
127
|
+
┌─────▼──────┐ (payment fails) ┌──────────┐
|
|
128
|
+
│ Paid │─────────────────────► │ Failed │
|
|
129
|
+
└─────┬──────┘ └──────────┘
|
|
130
|
+
│
|
|
131
|
+
┌─────▼──────┐
|
|
132
|
+
│ Processing │ (preparing fulfillment)
|
|
133
|
+
└─────┬──────┘
|
|
134
|
+
│
|
|
135
|
+
┌─────▼──────┐ (partial ship) ┌───────────────┐
|
|
136
|
+
│ Shipped │─────────────────────► │ Partially │
|
|
137
|
+
└─────┬──────┘ │ Shipped │
|
|
138
|
+
│ └───────────────┘
|
|
139
|
+
┌─────▼──────┐
|
|
140
|
+
│ Delivered │
|
|
141
|
+
└─────┬──────┘
|
|
142
|
+
│ (return requested)
|
|
143
|
+
┌─────▼──────┐
|
|
144
|
+
│ Returned │
|
|
145
|
+
└────────────┘
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## For /dev-specs
|
|
149
|
+
|
|
150
|
+
### Cart & Checkout Spec Template
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
## Feature: Shopping Cart
|
|
154
|
+
|
|
155
|
+
### Entities
|
|
156
|
+
- Cart: session-based or user-based
|
|
157
|
+
- CartItem: product + quantity
|
|
158
|
+
|
|
159
|
+
### API Endpoints
|
|
160
|
+
| Method | Path | Purpose |
|
|
161
|
+
|--------|------|---------|
|
|
162
|
+
| GET | /api/cart | Get current cart |
|
|
163
|
+
| POST | /api/cart/items | Add item |
|
|
164
|
+
| PATCH | /api/cart/items/:id | Update quantity |
|
|
165
|
+
| DELETE | /api/cart/items/:id | Remove item |
|
|
166
|
+
| POST | /api/cart/checkout | Begin checkout |
|
|
167
|
+
|
|
168
|
+
### Business Rules
|
|
169
|
+
1. Cart expires after 7 days of inactivity
|
|
170
|
+
2. Price locked when added (show if changed)
|
|
171
|
+
3. Quantity limited by inventory
|
|
172
|
+
4. Merge guest cart on login
|
|
173
|
+
5. Reserve inventory during checkout (15 min)
|
|
174
|
+
|
|
175
|
+
### Validation
|
|
176
|
+
- Quantity > 0
|
|
177
|
+
- Variant must be in stock
|
|
178
|
+
- Cannot exceed max quantity per SKU
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Inventory Spec Template
|
|
182
|
+
|
|
183
|
+
```markdown
|
|
184
|
+
## Feature: Inventory Management
|
|
185
|
+
|
|
186
|
+
### Stock States
|
|
187
|
+
- Available = quantity - reserved
|
|
188
|
+
- Reserved = items in active checkouts
|
|
189
|
+
- Backordered = ordered but not in stock
|
|
190
|
+
|
|
191
|
+
### Rules
|
|
192
|
+
1. Decrement on order confirmation (not cart add)
|
|
193
|
+
2. Reserve during checkout (timeout 15 min)
|
|
194
|
+
3. Release reserved if checkout abandoned
|
|
195
|
+
4. Allow backorder for specific products only
|
|
196
|
+
5. Send alert when below reorder_point
|
|
197
|
+
|
|
198
|
+
### Operations
|
|
199
|
+
| Operation | Effect |
|
|
200
|
+
|-----------|--------|
|
|
201
|
+
| Add to cart | No change |
|
|
202
|
+
| Begin checkout | Reserve quantity |
|
|
203
|
+
| Abandon checkout | Release reserved |
|
|
204
|
+
| Order confirmed | Move reserved to sold |
|
|
205
|
+
| Cancel order | Return to available |
|
|
206
|
+
| Receive shipment | Increase quantity |
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## For /dev-coding
|
|
210
|
+
|
|
211
|
+
### Cart Operations
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Add to cart
|
|
215
|
+
const addToCart = async (cartId: string, variantId: string, quantity: number) => {
|
|
216
|
+
// Check inventory
|
|
217
|
+
const variant = await getVariant(variantId);
|
|
218
|
+
const available = variant.inventory_quantity - variant.reserved_quantity;
|
|
219
|
+
|
|
220
|
+
if (quantity > available) {
|
|
221
|
+
throw new Error(`Only ${available} available`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check existing item
|
|
225
|
+
const existingItem = await db.cartItems.findFirst({
|
|
226
|
+
where: { cart_id: cartId, variant_id: variantId },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (existingItem) {
|
|
230
|
+
return db.cartItems.update({
|
|
231
|
+
where: { id: existingItem.id },
|
|
232
|
+
data: { quantity: existingItem.quantity + quantity },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return db.cartItems.create({
|
|
237
|
+
data: {
|
|
238
|
+
cart_id: cartId,
|
|
239
|
+
variant_id: variantId,
|
|
240
|
+
quantity,
|
|
241
|
+
price_at_add: variant.price,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Get cart with totals
|
|
247
|
+
const getCart = async (cartId: string) => {
|
|
248
|
+
const cart = await db.carts.findUnique({
|
|
249
|
+
where: { id: cartId },
|
|
250
|
+
include: {
|
|
251
|
+
items: {
|
|
252
|
+
include: { variant: { include: { product: true } } },
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const subtotal = cart.items.reduce(
|
|
258
|
+
(sum, item) => sum + item.price_at_add * item.quantity,
|
|
259
|
+
0
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return { ...cart, subtotal };
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Checkout Flow
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Begin checkout - reserve inventory
|
|
270
|
+
const beginCheckout = async (cartId: string) => {
|
|
271
|
+
const cart = await getCart(cartId);
|
|
272
|
+
|
|
273
|
+
// Check all items still available
|
|
274
|
+
for (const item of cart.items) {
|
|
275
|
+
const available = await getAvailableQuantity(item.variant_id);
|
|
276
|
+
if (item.quantity > available) {
|
|
277
|
+
throw new Error(`${item.variant.name} has only ${available} in stock`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Reserve inventory
|
|
282
|
+
await db.$transaction(async (tx) => {
|
|
283
|
+
for (const item of cart.items) {
|
|
284
|
+
await tx.variants.update({
|
|
285
|
+
where: { id: item.variant_id },
|
|
286
|
+
data: { reserved_quantity: { increment: item.quantity } },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await tx.carts.update({
|
|
291
|
+
where: { id: cartId },
|
|
292
|
+
data: {
|
|
293
|
+
checkout_started_at: new Date(),
|
|
294
|
+
checkout_expires_at: addMinutes(new Date(), 15),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return cart;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Complete checkout - create order
|
|
303
|
+
const completeCheckout = async (cartId: string, paymentIntentId: string) => {
|
|
304
|
+
const cart = await getCart(cartId);
|
|
305
|
+
|
|
306
|
+
const order = await db.$transaction(async (tx) => {
|
|
307
|
+
// Create order
|
|
308
|
+
const order = await tx.orders.create({
|
|
309
|
+
data: {
|
|
310
|
+
user_id: cart.user_id,
|
|
311
|
+
status: 'paid',
|
|
312
|
+
subtotal: cart.subtotal,
|
|
313
|
+
total: cart.subtotal + cart.tax + cart.shipping,
|
|
314
|
+
items: {
|
|
315
|
+
create: cart.items.map(item => ({
|
|
316
|
+
variant_id: item.variant_id,
|
|
317
|
+
quantity: item.quantity,
|
|
318
|
+
unit_price: item.price_at_add,
|
|
319
|
+
total_price: item.price_at_add * item.quantity,
|
|
320
|
+
})),
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Decrement inventory (move from reserved to sold)
|
|
326
|
+
for (const item of cart.items) {
|
|
327
|
+
await tx.variants.update({
|
|
328
|
+
where: { id: item.variant_id },
|
|
329
|
+
data: {
|
|
330
|
+
inventory_quantity: { decrement: item.quantity },
|
|
331
|
+
reserved_quantity: { decrement: item.quantity },
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Delete cart
|
|
337
|
+
await tx.carts.delete({ where: { id: cartId } });
|
|
338
|
+
|
|
339
|
+
return order;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return order;
|
|
343
|
+
};
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Price Calculation
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Calculate order totals
|
|
350
|
+
const calculateTotals = (items: CartItem[], address: Address) => {
|
|
351
|
+
const subtotal = items.reduce(
|
|
352
|
+
(sum, item) => sum + item.price * item.quantity,
|
|
353
|
+
0
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const tax = calculateTax(subtotal, address);
|
|
357
|
+
const shipping = calculateShipping(items, address);
|
|
358
|
+
const discount = calculateDiscount(items, cart.coupon_code);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
subtotal,
|
|
362
|
+
tax,
|
|
363
|
+
shipping,
|
|
364
|
+
discount,
|
|
365
|
+
total: subtotal + tax + shipping - discount,
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Tax calculation (simplified)
|
|
370
|
+
const calculateTax = (subtotal: number, address: Address) => {
|
|
371
|
+
const rate = getTaxRate(address.state, address.country);
|
|
372
|
+
return Math.round(subtotal * rate);
|
|
373
|
+
};
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Inventory Reservation Release (Cron Job)
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Run every 5 minutes
|
|
380
|
+
const releaseExpiredReservations = async () => {
|
|
381
|
+
const expiredCarts = await db.carts.findMany({
|
|
382
|
+
where: {
|
|
383
|
+
checkout_expires_at: { lt: new Date() },
|
|
384
|
+
checkout_started_at: { not: null },
|
|
385
|
+
},
|
|
386
|
+
include: { items: true },
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
for (const cart of expiredCarts) {
|
|
390
|
+
await db.$transaction(async (tx) => {
|
|
391
|
+
for (const item of cart.items) {
|
|
392
|
+
await tx.variants.update({
|
|
393
|
+
where: { id: item.variant_id },
|
|
394
|
+
data: { reserved_quantity: { decrement: item.quantity } },
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
await tx.carts.update({
|
|
399
|
+
where: { id: cart.id },
|
|
400
|
+
data: {
|
|
401
|
+
checkout_started_at: null,
|
|
402
|
+
checkout_expires_at: null,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## For /dev-review
|
|
411
|
+
|
|
412
|
+
### Cart & Checkout Checklist
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
[ ] Cart persists across sessions (logged in users)
|
|
416
|
+
[ ] Guest cart merges on login
|
|
417
|
+
[ ] Price changes shown to user
|
|
418
|
+
[ ] Inventory checked before checkout
|
|
419
|
+
[ ] Inventory reserved during checkout
|
|
420
|
+
[ ] Reserved inventory released on timeout
|
|
421
|
+
[ ] Cannot checkout with 0 quantity items
|
|
422
|
+
[ ] Order confirmation email sent
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Inventory Checklist
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
[ ] Inventory decremented on order, not cart
|
|
429
|
+
[ ] Reserved quantity tracked separately
|
|
430
|
+
[ ] Overselling prevented (race condition safe)
|
|
431
|
+
[ ] Low stock alerts configured
|
|
432
|
+
[ ] Backorder clearly indicated to user
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Payment Checklist
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
[ ] Payment amount matches order total
|
|
439
|
+
[ ] Payment verified server-side
|
|
440
|
+
[ ] Partial payments handled (if applicable)
|
|
441
|
+
[ ] Refunds update inventory
|
|
442
|
+
[ ] Payment failures don't create orders
|
|
443
|
+
[ ] Webhook signatures validated
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Security Checklist
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
[ ] Price calculated server-side (never trust client)
|
|
450
|
+
[ ] Coupon codes validated server-side
|
|
451
|
+
[ ] PCI compliance for card data
|
|
452
|
+
[ ] Address validation
|
|
453
|
+
[ ] Fraud detection (velocity checks)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Common Anti-Patterns
|
|
457
|
+
|
|
458
|
+
| Anti-Pattern | Problem | Better Approach |
|
|
459
|
+
|--------------|---------|-----------------|
|
|
460
|
+
| Trust client price | Price manipulation | Calculate server-side |
|
|
461
|
+
| Decrement on cart add | Inventory issues | Decrement on order confirm |
|
|
462
|
+
| No reservation timeout | Stock locked forever | 15 min timeout + cron |
|
|
463
|
+
| Single inventory field | Can't track reserved | Separate available/reserved |
|
|
464
|
+
| Validate stock once | Race conditions | Validate in transaction |
|
|
465
|
+
| Hardcoded tax rates | Compliance issues | Tax service API |
|
|
466
|
+
|
|
467
|
+
## Integration
|
|
468
|
+
|
|
469
|
+
### With Stripe
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// Create payment intent
|
|
473
|
+
const createPaymentIntent = async (cart: Cart) => {
|
|
474
|
+
const totals = calculateTotals(cart.items, cart.shipping_address);
|
|
475
|
+
|
|
476
|
+
return stripe.paymentIntents.create({
|
|
477
|
+
amount: totals.total, // in cents
|
|
478
|
+
currency: 'usd',
|
|
479
|
+
metadata: { cart_id: cart.id },
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Webhook: payment succeeded
|
|
484
|
+
app.post('/webhooks/stripe', async (req, res) => {
|
|
485
|
+
const event = stripe.webhooks.constructEvent(...);
|
|
486
|
+
|
|
487
|
+
if (event.type === 'payment_intent.succeeded') {
|
|
488
|
+
const { cart_id } = event.data.object.metadata;
|
|
489
|
+
await completeCheckout(cart_id, event.data.object.id);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Resources
|
|
495
|
+
|
|
496
|
+
### References in this folder
|
|
497
|
+
- `references/checkout-flows.md` - Detailed checkout state machines
|
|
498
|
+
- `references/shipping.md` - Shipping calculation patterns
|
|
499
|
+
- `assets/order-schema.prisma` - Example Prisma schema
|