@su-record/vibe 2.6.14 → 2.6.16
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/CLAUDE.md +1 -394
- package/LICENSE +21 -21
- package/README.md +203 -497
- package/agents/architect-low.md +41 -41
- package/agents/architect-medium.md +59 -59
- package/agents/architect.md +80 -80
- package/agents/build-error-resolver.md +115 -115
- package/agents/compounder.md +261 -261
- package/agents/diagrammer.md +178 -178
- package/agents/e2e-tester.md +266 -266
- package/agents/explorer-low.md +42 -42
- package/agents/explorer-medium.md +59 -59
- package/agents/explorer.md +48 -48
- package/agents/implementer-low.md +43 -43
- package/agents/implementer-medium.md +52 -52
- package/agents/implementer.md +54 -54
- package/agents/refactor-cleaner.md +143 -143
- package/agents/research/best-practices-agent.md +199 -199
- package/agents/research/codebase-patterns-agent.md +157 -157
- package/agents/research/framework-docs-agent.md +188 -188
- package/agents/research/security-advisory-agent.md +213 -213
- package/agents/review/architecture-reviewer.md +107 -107
- package/agents/review/complexity-reviewer.md +116 -116
- package/agents/review/data-integrity-reviewer.md +88 -88
- package/agents/review/git-history-reviewer.md +103 -103
- package/agents/review/performance-reviewer.md +86 -86
- package/agents/review/python-reviewer.md +150 -150
- package/agents/review/rails-reviewer.md +139 -139
- package/agents/review/react-reviewer.md +144 -144
- package/agents/review/security-reviewer.md +80 -80
- package/agents/review/simplicity-reviewer.md +140 -140
- package/agents/review/test-coverage-reviewer.md +116 -116
- package/agents/review/typescript-reviewer.md +127 -127
- package/agents/searcher.md +54 -54
- package/agents/simplifier.md +120 -120
- package/agents/tester.md +49 -49
- package/agents/ui-previewer.md +268 -268
- package/commands/vibe.analyze.md +356 -356
- package/commands/vibe.reason.md +329 -329
- package/commands/vibe.review.md +412 -412
- package/commands/vibe.run.md +1266 -1266
- package/commands/vibe.spec.md +1054 -1054
- package/commands/vibe.spec.review.md +319 -319
- package/commands/vibe.trace.md +161 -161
- package/commands/vibe.utils.md +376 -376
- package/commands/vibe.verify.md +375 -375
- package/dist/cli/collaborator.js +52 -52
- package/dist/cli/detect.js +32 -32
- package/dist/cli/hud.js +20 -20
- package/dist/cli/index.js +118 -118
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/llm.js +144 -144
- package/dist/cli/mcp.d.ts +49 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +169 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/postinstall.js +858 -858
- package/dist/cli/setup/ProjectSetup.d.ts +3 -3
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +76 -30
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/lib/DeepInit.js +24 -24
- package/dist/lib/IterationTracker.js +11 -11
- package/dist/lib/PythonParser.js +108 -108
- package/dist/lib/ReviewRace.js +96 -96
- package/dist/lib/SkillFrontmatter.js +28 -28
- package/dist/lib/SkillQualityGate.js +9 -9
- package/dist/lib/SkillRepository.js +159 -159
- package/dist/lib/UltraQA.js +77 -77
- package/dist/lib/gemini-api.js +5 -5
- package/dist/lib/gemini-mcp.d.ts +10 -0
- package/dist/lib/gemini-mcp.d.ts.map +1 -0
- package/dist/lib/gemini-mcp.js +353 -0
- package/dist/lib/gemini-mcp.js.map +1 -0
- package/dist/lib/gpt-api.js +4 -4
- package/dist/lib/gpt-mcp.d.ts +10 -0
- package/dist/lib/gpt-mcp.d.ts.map +1 -0
- package/dist/lib/gpt-mcp.js +352 -0
- package/dist/lib/gpt-mcp.js.map +1 -0
- package/dist/lib/memory/KnowledgeGraph.js +4 -4
- package/dist/lib/memory/MemorySearch.js +20 -20
- package/dist/lib/memory/MemoryStorage.js +64 -64
- package/dist/orchestrator/AgentManager.js +12 -12
- package/dist/orchestrator/MultiLlmResearch.js +8 -8
- package/dist/orchestrator/SmartRouter.js +11 -11
- package/dist/orchestrator/parallelResearch.js +24 -24
- package/dist/tools/analytics/getUsageAnalytics.d.ts +10 -0
- package/dist/tools/analytics/getUsageAnalytics.d.ts.map +1 -0
- package/dist/tools/analytics/getUsageAnalytics.js +246 -0
- package/dist/tools/analytics/getUsageAnalytics.js.map +1 -0
- package/dist/tools/analytics/index.d.ts +5 -0
- package/dist/tools/analytics/index.d.ts.map +1 -0
- package/dist/tools/analytics/index.js +5 -0
- package/dist/tools/analytics/index.js.map +1 -0
- package/dist/tools/convention/analyzeComplexity.test.js +115 -115
- package/dist/tools/convention/getCodingGuide.d.ts +7 -0
- package/dist/tools/convention/getCodingGuide.d.ts.map +1 -0
- package/dist/tools/convention/getCodingGuide.js +69 -0
- package/dist/tools/convention/getCodingGuide.js.map +1 -0
- package/dist/tools/convention/validateCodeQuality.test.js +104 -104
- package/dist/tools/planning/analyzeRequirements.d.ts +9 -0
- package/dist/tools/planning/analyzeRequirements.d.ts.map +1 -0
- package/dist/tools/planning/analyzeRequirements.js +171 -0
- package/dist/tools/planning/analyzeRequirements.js.map +1 -0
- package/dist/tools/planning/createUserStories.d.ts +9 -0
- package/dist/tools/planning/createUserStories.d.ts.map +1 -0
- package/dist/tools/planning/createUserStories.js +124 -0
- package/dist/tools/planning/createUserStories.js.map +1 -0
- package/dist/tools/planning/featureRoadmap.d.ts +10 -0
- package/dist/tools/planning/featureRoadmap.d.ts.map +1 -0
- package/dist/tools/planning/featureRoadmap.js +207 -0
- package/dist/tools/planning/featureRoadmap.js.map +1 -0
- package/dist/tools/planning/generatePrd.d.ts +11 -0
- package/dist/tools/planning/generatePrd.d.ts.map +1 -0
- package/dist/tools/planning/generatePrd.js +161 -0
- package/dist/tools/planning/generatePrd.js.map +1 -0
- package/dist/tools/planning/index.d.ts +8 -0
- package/dist/tools/planning/index.d.ts.map +1 -0
- package/dist/tools/planning/index.js +8 -0
- package/dist/tools/planning/index.js.map +1 -0
- package/dist/tools/prompt/analyzePrompt.d.ts +7 -0
- package/dist/tools/prompt/analyzePrompt.d.ts.map +1 -0
- package/dist/tools/prompt/analyzePrompt.js +150 -0
- package/dist/tools/prompt/analyzePrompt.js.map +1 -0
- package/dist/tools/prompt/enhancePrompt.d.ts +8 -0
- package/dist/tools/prompt/enhancePrompt.d.ts.map +1 -0
- package/dist/tools/prompt/enhancePrompt.js +110 -0
- package/dist/tools/prompt/enhancePrompt.js.map +1 -0
- package/dist/tools/prompt/enhancePromptGemini.d.ts +8 -0
- package/dist/tools/prompt/enhancePromptGemini.d.ts.map +1 -0
- package/dist/tools/prompt/enhancePromptGemini.js +332 -0
- package/dist/tools/prompt/enhancePromptGemini.js.map +1 -0
- package/dist/tools/prompt/index.d.ts +7 -0
- package/dist/tools/prompt/index.d.ts.map +1 -0
- package/dist/tools/prompt/index.js +7 -0
- package/dist/tools/prompt/index.js.map +1 -0
- package/dist/tools/reasoning/applyReasoningFramework.d.ts +8 -0
- package/dist/tools/reasoning/applyReasoningFramework.d.ts.map +1 -0
- package/dist/tools/reasoning/applyReasoningFramework.js +266 -0
- package/dist/tools/reasoning/applyReasoningFramework.js.map +1 -0
- package/dist/tools/reasoning/index.d.ts +5 -0
- package/dist/tools/reasoning/index.d.ts.map +1 -0
- package/dist/tools/reasoning/index.js +5 -0
- package/dist/tools/reasoning/index.js.map +1 -0
- package/dist/tools/spec/prdParser.test.js +171 -171
- package/dist/tools/spec/specGenerator.js +169 -169
- package/dist/tools/spec/traceabilityMatrix.js +64 -64
- package/dist/tools/spec/traceabilityMatrix.test.js +28 -28
- package/dist/tools/thinking/analyzeProblem.d.ts +7 -0
- package/dist/tools/thinking/analyzeProblem.d.ts.map +1 -0
- package/dist/tools/thinking/analyzeProblem.js +55 -0
- package/dist/tools/thinking/analyzeProblem.js.map +1 -0
- package/dist/tools/thinking/breakDownProblem.d.ts +8 -0
- package/dist/tools/thinking/breakDownProblem.d.ts.map +1 -0
- package/dist/tools/thinking/breakDownProblem.js +145 -0
- package/dist/tools/thinking/breakDownProblem.js.map +1 -0
- package/dist/tools/thinking/createThinkingChain.d.ts +7 -0
- package/dist/tools/thinking/createThinkingChain.d.ts.map +1 -0
- package/dist/tools/thinking/createThinkingChain.js +44 -0
- package/dist/tools/thinking/createThinkingChain.js.map +1 -0
- package/dist/tools/thinking/formatAsPlan.d.ts +9 -0
- package/dist/tools/thinking/formatAsPlan.d.ts.map +1 -0
- package/dist/tools/thinking/formatAsPlan.js +78 -0
- package/dist/tools/thinking/formatAsPlan.js.map +1 -0
- package/dist/tools/thinking/index.d.ts +10 -0
- package/dist/tools/thinking/index.d.ts.map +1 -0
- package/dist/tools/thinking/index.js +10 -0
- package/dist/tools/thinking/index.js.map +1 -0
- package/dist/tools/thinking/stepByStepAnalysis.d.ts +8 -0
- package/dist/tools/thinking/stepByStepAnalysis.d.ts.map +1 -0
- package/dist/tools/thinking/stepByStepAnalysis.js +63 -0
- package/dist/tools/thinking/stepByStepAnalysis.js.map +1 -0
- package/dist/tools/thinking/thinkAloudProcess.d.ts +8 -0
- package/dist/tools/thinking/thinkAloudProcess.d.ts.map +1 -0
- package/dist/tools/thinking/thinkAloudProcess.js +80 -0
- package/dist/tools/thinking/thinkAloudProcess.js.map +1 -0
- package/hooks/hooks.json +222 -222
- package/hooks/scripts/code-check.js +22 -22
- package/hooks/scripts/code-review.js +22 -22
- package/hooks/scripts/complexity.js +22 -22
- package/hooks/scripts/compound.js +23 -23
- package/hooks/scripts/context-save.js +33 -33
- package/hooks/scripts/gemini-ui-gen.js +281 -281
- package/hooks/scripts/generate-brand-assets.js +474 -474
- package/hooks/scripts/hud-multiline.js +262 -262
- package/hooks/scripts/hud-status.js +291 -291
- package/hooks/scripts/keyword-detector.js +214 -214
- package/hooks/scripts/llm-orchestrate.js +171 -171
- package/hooks/scripts/post-edit.js +97 -97
- package/hooks/scripts/post-tool-verify.js +210 -210
- package/hooks/scripts/pre-tool-guard.js +125 -125
- package/hooks/scripts/recall.js +22 -22
- package/hooks/scripts/session-start.js +30 -30
- package/hooks/scripts/skill-injector.js +191 -191
- package/hooks/scripts/utils.js +97 -97
- package/languages/csharp-unity.md +515 -515
- package/languages/gdscript-godot.md +470 -470
- package/languages/ruby-rails.md +489 -489
- package/languages/typescript-angular.md +433 -433
- package/languages/typescript-astro.md +416 -416
- package/languages/typescript-electron.md +406 -406
- package/languages/typescript-nestjs.md +524 -524
- package/languages/typescript-svelte.md +407 -407
- package/languages/typescript-tauri.md +365 -365
- package/package.json +84 -84
- package/skills/brand-assets.md +141 -141
- package/skills/commerce-patterns.md +361 -361
- package/skills/context7-usage.md +102 -102
- package/skills/e2e-commerce.md +304 -304
- package/skills/frontend-design.md +92 -92
- package/skills/git-worktree.md +181 -181
- package/skills/parallel-research.md +77 -77
- package/skills/priority-todos.md +239 -239
- package/skills/seo-checklist.md +244 -244
- package/skills/tool-fallback.md +190 -190
- package/skills/vibe-capabilities.md +161 -161
- package/vibe/constitution.md +227 -227
- package/vibe/rules/core/communication-guide.md +98 -98
- package/vibe/rules/core/development-philosophy.md +52 -52
- package/vibe/rules/core/quick-start.md +102 -102
- package/vibe/rules/quality/bdd-contract-testing.md +393 -393
- package/vibe/rules/quality/checklist.md +276 -276
- package/vibe/rules/quality/testing-strategy.md +440 -440
- package/vibe/rules/standards/anti-patterns.md +541 -541
- package/vibe/rules/standards/code-structure.md +291 -291
- package/vibe/rules/standards/complexity-metrics.md +313 -313
- package/vibe/rules/standards/naming-conventions.md +198 -198
- package/vibe/setup.sh +31 -31
- package/vibe/templates/constitution-template.md +252 -252
- package/vibe/templates/contract-backend-template.md +526 -526
- package/vibe/templates/contract-frontend-template.md +599 -599
- package/vibe/templates/feature-template.md +96 -96
- package/vibe/templates/spec-template.md +221 -221
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: commerce-patterns
|
|
3
|
-
description: "E-commerce domain patterns - cart, payment, inventory with transaction safety"
|
|
4
|
-
triggers: [commerce, ecommerce, cart, payment, checkout, inventory, stock, order, pg, toss, stripe]
|
|
5
|
-
priority: 70
|
|
6
|
-
---
|
|
7
|
-
# Commerce Patterns Skill
|
|
8
|
-
|
|
9
|
-
E-commerce domain patterns for reliable transactions.
|
|
10
|
-
|
|
11
|
-
## When to Use
|
|
12
|
-
|
|
13
|
-
- Shopping cart implementation
|
|
14
|
-
- Payment integration (PG, Stripe, Toss)
|
|
15
|
-
- Inventory/stock management
|
|
16
|
-
- Order processing systems
|
|
17
|
-
|
|
18
|
-
## Core Patterns
|
|
19
|
-
|
|
20
|
-
### 1. Cart (Shopping Cart)
|
|
21
|
-
|
|
22
|
-
#### State Model
|
|
23
|
-
```typescript
|
|
24
|
-
interface CartItem {
|
|
25
|
-
productId: string;
|
|
26
|
-
variantId?: string;
|
|
27
|
-
quantity: number;
|
|
28
|
-
price: number; // Snapshot at add time
|
|
29
|
-
originalPrice: number; // For comparison
|
|
30
|
-
addedAt: Date;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface Cart {
|
|
34
|
-
id: string;
|
|
35
|
-
userId?: string; // null for guest
|
|
36
|
-
sessionId: string; // For guest merge
|
|
37
|
-
items: CartItem[];
|
|
38
|
-
couponCode?: string;
|
|
39
|
-
updatedAt: Date;
|
|
40
|
-
expiresAt: Date; // Cart expiration
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
#### Key Patterns
|
|
45
|
-
|
|
46
|
-
| Pattern | Description |
|
|
47
|
-
|---------|-------------|
|
|
48
|
-
| **Guest → User Merge** | Merge localStorage cart on login |
|
|
49
|
-
| **Price Snapshot** | Store price at add time, revalidate at checkout |
|
|
50
|
-
| **Expiration** | Clear abandoned carts after N days |
|
|
51
|
-
| **Validation** | Check stock/price at checkout entry |
|
|
52
|
-
|
|
53
|
-
#### Implementation
|
|
54
|
-
```typescript
|
|
55
|
-
class CartService {
|
|
56
|
-
async addItem(cartId: string, item: AddItemRequest): Promise<Cart> {
|
|
57
|
-
// 1. Validate product exists and in stock
|
|
58
|
-
const product = await this.productService.get(item.productId);
|
|
59
|
-
if (!product || product.stock < item.quantity) {
|
|
60
|
-
throw new OutOfStockError();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 2. Snapshot current price
|
|
64
|
-
const cartItem: CartItem = {
|
|
65
|
-
...item,
|
|
66
|
-
price: product.currentPrice,
|
|
67
|
-
originalPrice: product.originalPrice,
|
|
68
|
-
addedAt: new Date(),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// 3. Add or update quantity
|
|
72
|
-
return this.cartRepository.upsertItem(cartId, cartItem);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async mergeGuestCart(userId: string, sessionId: string): Promise<Cart> {
|
|
76
|
-
const guestCart = await this.cartRepository.findBySession(sessionId);
|
|
77
|
-
const userCart = await this.cartRepository.findByUser(userId);
|
|
78
|
-
|
|
79
|
-
if (!guestCart) return userCart;
|
|
80
|
-
|
|
81
|
-
// Merge: user cart takes priority for duplicates
|
|
82
|
-
const merged = this.mergeItems(userCart.items, guestCart.items);
|
|
83
|
-
await this.cartRepository.delete(guestCart.id);
|
|
84
|
-
|
|
85
|
-
return this.cartRepository.update(userCart.id, { items: merged });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### 2. Payment
|
|
91
|
-
|
|
92
|
-
#### State Machine
|
|
93
|
-
```
|
|
94
|
-
PENDING → PROCESSING → AUTHORIZED → CAPTURED → COMPLETED
|
|
95
|
-
↘ FAILED
|
|
96
|
-
↘ CANCELED
|
|
97
|
-
COMPLETED → REFUND_REQUESTED → REFUNDED (partial/full)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
#### Idempotency Pattern (Critical)
|
|
101
|
-
```typescript
|
|
102
|
-
interface PaymentRequest {
|
|
103
|
-
orderId: string;
|
|
104
|
-
amount: number;
|
|
105
|
-
currency: string;
|
|
106
|
-
idempotencyKey: string; // REQUIRED: `order_${orderId}_${timestamp}`
|
|
107
|
-
paymentMethod: PaymentMethod;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
class PaymentService {
|
|
111
|
-
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
|
|
112
|
-
// 1. Check idempotency - prevent duplicate charges
|
|
113
|
-
const existing = await this.paymentRepository.findByIdempotencyKey(
|
|
114
|
-
request.idempotencyKey
|
|
115
|
-
);
|
|
116
|
-
if (existing) {
|
|
117
|
-
return existing.result; // Return cached result
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 2. Create payment record (PENDING)
|
|
121
|
-
const payment = await this.paymentRepository.create({
|
|
122
|
-
...request,
|
|
123
|
-
status: 'PENDING',
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
// 3. Call PG adapter
|
|
128
|
-
const pgResult = await this.pgAdapter.authorize(request);
|
|
129
|
-
|
|
130
|
-
// 4. Update status
|
|
131
|
-
return this.paymentRepository.update(payment.id, {
|
|
132
|
-
status: pgResult.success ? 'AUTHORIZED' : 'FAILED',
|
|
133
|
-
pgTransactionId: pgResult.transactionId,
|
|
134
|
-
result: pgResult,
|
|
135
|
-
});
|
|
136
|
-
} catch (error) {
|
|
137
|
-
await this.paymentRepository.update(payment.id, {
|
|
138
|
-
status: 'FAILED',
|
|
139
|
-
error: error.message,
|
|
140
|
-
});
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### PG Adapter Pattern
|
|
148
|
-
```typescript
|
|
149
|
-
interface PGAdapter {
|
|
150
|
-
authorize(request: PaymentRequest): Promise<PGResult>;
|
|
151
|
-
capture(transactionId: string): Promise<PGResult>;
|
|
152
|
-
cancel(transactionId: string): Promise<PGResult>;
|
|
153
|
-
refund(transactionId: string, amount?: number): Promise<PGResult>;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Implementations
|
|
157
|
-
class TossPaymentsAdapter implements PGAdapter { /* ... */ }
|
|
158
|
-
class StripeAdapter implements PGAdapter { /* ... */ }
|
|
159
|
-
class PortOneAdapter implements PGAdapter { /* ... */ }
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
#### Webhook Handling
|
|
163
|
-
```typescript
|
|
164
|
-
class PaymentWebhookHandler {
|
|
165
|
-
async handle(event: WebhookEvent): Promise<void> {
|
|
166
|
-
// 1. Verify signature
|
|
167
|
-
if (!this.verifySignature(event)) {
|
|
168
|
-
throw new UnauthorizedError();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 2. Idempotency check - process each event only once
|
|
172
|
-
const processed = await this.eventStore.find(event.id);
|
|
173
|
-
if (processed) {
|
|
174
|
-
return; // Already processed
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 3. Process event
|
|
178
|
-
await this.processEvent(event);
|
|
179
|
-
|
|
180
|
-
// 4. Mark as processed
|
|
181
|
-
await this.eventStore.save({
|
|
182
|
-
eventId: event.id,
|
|
183
|
-
processedAt: new Date(),
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 3. Inventory (Stock Management)
|
|
190
|
-
|
|
191
|
-
#### Reservation Pattern (Two-Phase)
|
|
192
|
-
```typescript
|
|
193
|
-
interface StockReservation {
|
|
194
|
-
id: string;
|
|
195
|
-
productId: string;
|
|
196
|
-
quantity: number;
|
|
197
|
-
orderId: string;
|
|
198
|
-
status: 'RESERVED' | 'COMMITTED' | 'RELEASED';
|
|
199
|
-
expiresAt: Date; // Auto-release if not committed
|
|
200
|
-
createdAt: Date;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class InventoryService {
|
|
204
|
-
// Phase 1: Reserve stock (at checkout start)
|
|
205
|
-
async reserve(orderId: string, items: OrderItem[]): Promise<void> {
|
|
206
|
-
for (const item of items) {
|
|
207
|
-
// Atomic decrement with check
|
|
208
|
-
const result = await this.db.query(`
|
|
209
|
-
UPDATE products
|
|
210
|
-
SET reserved_stock = reserved_stock + $1
|
|
211
|
-
WHERE id = $2
|
|
212
|
-
AND (available_stock - reserved_stock) >= $1
|
|
213
|
-
RETURNING *
|
|
214
|
-
`, [item.quantity, item.productId]);
|
|
215
|
-
|
|
216
|
-
if (result.rowCount === 0) {
|
|
217
|
-
// Rollback previous reservations
|
|
218
|
-
await this.releaseAll(orderId);
|
|
219
|
-
throw new InsufficientStockError(item.productId);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
await this.reservationRepository.create({
|
|
223
|
-
orderId,
|
|
224
|
-
productId: item.productId,
|
|
225
|
-
quantity: item.quantity,
|
|
226
|
-
status: 'RESERVED',
|
|
227
|
-
expiresAt: addMinutes(new Date(), 15), // 15min hold
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Phase 2: Commit stock (after payment success)
|
|
233
|
-
async commit(orderId: string): Promise<void> {
|
|
234
|
-
const reservations = await this.reservationRepository.findByOrder(orderId);
|
|
235
|
-
|
|
236
|
-
for (const reservation of reservations) {
|
|
237
|
-
await this.db.query(`
|
|
238
|
-
UPDATE products
|
|
239
|
-
SET
|
|
240
|
-
available_stock = available_stock - $1,
|
|
241
|
-
reserved_stock = reserved_stock - $1
|
|
242
|
-
WHERE id = $2
|
|
243
|
-
`, [reservation.quantity, reservation.productId]);
|
|
244
|
-
|
|
245
|
-
await this.reservationRepository.update(reservation.id, {
|
|
246
|
-
status: 'COMMITTED',
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Rollback: Release stock (payment failed or timeout)
|
|
252
|
-
async release(orderId: string): Promise<void> {
|
|
253
|
-
const reservations = await this.reservationRepository.findByOrder(orderId);
|
|
254
|
-
|
|
255
|
-
for (const reservation of reservations) {
|
|
256
|
-
if (reservation.status === 'RESERVED') {
|
|
257
|
-
await this.db.query(`
|
|
258
|
-
UPDATE products
|
|
259
|
-
SET reserved_stock = reserved_stock - $1
|
|
260
|
-
WHERE id = $2
|
|
261
|
-
`, [reservation.quantity, reservation.productId]);
|
|
262
|
-
|
|
263
|
-
await this.reservationRepository.update(reservation.id, {
|
|
264
|
-
status: 'RELEASED',
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
#### Concurrency Control
|
|
273
|
-
```typescript
|
|
274
|
-
// Option 1: Optimistic Locking
|
|
275
|
-
await db.query(`
|
|
276
|
-
UPDATE products
|
|
277
|
-
SET stock = stock - $1, version = version + 1
|
|
278
|
-
WHERE id = $2 AND version = $3 AND stock >= $1
|
|
279
|
-
`, [quantity, productId, expectedVersion]);
|
|
280
|
-
|
|
281
|
-
// Option 2: Pessimistic Locking (for high contention)
|
|
282
|
-
await db.query(`
|
|
283
|
-
SELECT * FROM products WHERE id = $1 FOR UPDATE
|
|
284
|
-
`, [productId]);
|
|
285
|
-
|
|
286
|
-
// Option 3: Redis Distributed Lock
|
|
287
|
-
const lock = await redlock.lock(`stock:${productId}`, 5000);
|
|
288
|
-
try {
|
|
289
|
-
// Update stock
|
|
290
|
-
} finally {
|
|
291
|
-
await lock.unlock();
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## Order Flow (Complete)
|
|
296
|
-
|
|
297
|
-
```
|
|
298
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
299
|
-
│ CHECKOUT FLOW │
|
|
300
|
-
├─────────────────────────────────────────────────────────────┤
|
|
301
|
-
│ │
|
|
302
|
-
│ 1. Cart Validation │
|
|
303
|
-
│ └─ Revalidate prices, check stock availability │
|
|
304
|
-
│ │
|
|
305
|
-
│ 2. Order Creation (PENDING) │
|
|
306
|
-
│ └─ Generate orderId, snapshot cart │
|
|
307
|
-
│ │
|
|
308
|
-
│ 3. Stock Reservation │
|
|
309
|
-
│ └─ Reserve inventory (15min hold) │
|
|
310
|
-
│ │
|
|
311
|
-
│ 4. Payment Processing │
|
|
312
|
-
│ ├─ Success → Order PAID, Stock COMMIT │
|
|
313
|
-
│ └─ Failure → Order FAILED, Stock RELEASE │
|
|
314
|
-
│ │
|
|
315
|
-
│ 5. Order Confirmation │
|
|
316
|
-
│ └─ Send notification, trigger fulfillment │
|
|
317
|
-
│ │
|
|
318
|
-
└─────────────────────────────────────────────────────────────┘
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Common Bugs & Prevention
|
|
322
|
-
|
|
323
|
-
| Bug | Cause | Prevention |
|
|
324
|
-
|-----|-------|------------|
|
|
325
|
-
| **Duplicate payment** | User double-click, webhook retry | Idempotency key |
|
|
326
|
-
| **Negative stock** | Race condition | Atomic update with check |
|
|
327
|
-
| **Price mismatch** | Price changed during checkout | Snapshot + revalidation |
|
|
328
|
-
| **Orphan reservation** | Payment timeout without release | TTL + scheduled cleanup |
|
|
329
|
-
| **Lost webhook** | Network failure | Retry + idempotent handler |
|
|
330
|
-
| **Order state corruption** | Concurrent updates | State machine + versioning |
|
|
331
|
-
|
|
332
|
-
## Integration with /vibe.run
|
|
333
|
-
|
|
334
|
-
During commerce feature implementation:
|
|
335
|
-
|
|
336
|
-
1. **Phase 1**: Define domain models (Cart, Order, Payment, Inventory)
|
|
337
|
-
2. **Phase 2**: Implement core services with patterns above
|
|
338
|
-
3. **Phase 3**: Add PG adapter and webhook handlers
|
|
339
|
-
4. **Phase 4**: Implement compensating transactions (Saga)
|
|
340
|
-
5. **Phase 5**: E2E test critical flows
|
|
341
|
-
|
|
342
|
-
## Checklist
|
|
343
|
-
|
|
344
|
-
### Cart
|
|
345
|
-
- [ ] Guest/user cart merge on login
|
|
346
|
-
- [ ] Price snapshot at add time
|
|
347
|
-
- [ ] Stock validation at checkout
|
|
348
|
-
- [ ] Cart expiration cleanup
|
|
349
|
-
|
|
350
|
-
### Payment
|
|
351
|
-
- [ ] Idempotency key on all requests
|
|
352
|
-
- [ ] Webhook signature verification
|
|
353
|
-
- [ ] Duplicate event handling
|
|
354
|
-
- [ ] Timeout/retry strategy
|
|
355
|
-
- [ ] Refund flow tested
|
|
356
|
-
|
|
357
|
-
### Inventory
|
|
358
|
-
- [ ] Two-phase reservation (reserve → commit/release)
|
|
359
|
-
- [ ] Atomic stock updates
|
|
360
|
-
- [ ] Reservation TTL and cleanup job
|
|
361
|
-
- [ ] Concurrency control tested
|
|
1
|
+
---
|
|
2
|
+
name: commerce-patterns
|
|
3
|
+
description: "E-commerce domain patterns - cart, payment, inventory with transaction safety"
|
|
4
|
+
triggers: [commerce, ecommerce, cart, payment, checkout, inventory, stock, order, pg, toss, stripe]
|
|
5
|
+
priority: 70
|
|
6
|
+
---
|
|
7
|
+
# Commerce Patterns Skill
|
|
8
|
+
|
|
9
|
+
E-commerce domain patterns for reliable transactions.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- Shopping cart implementation
|
|
14
|
+
- Payment integration (PG, Stripe, Toss)
|
|
15
|
+
- Inventory/stock management
|
|
16
|
+
- Order processing systems
|
|
17
|
+
|
|
18
|
+
## Core Patterns
|
|
19
|
+
|
|
20
|
+
### 1. Cart (Shopping Cart)
|
|
21
|
+
|
|
22
|
+
#### State Model
|
|
23
|
+
```typescript
|
|
24
|
+
interface CartItem {
|
|
25
|
+
productId: string;
|
|
26
|
+
variantId?: string;
|
|
27
|
+
quantity: number;
|
|
28
|
+
price: number; // Snapshot at add time
|
|
29
|
+
originalPrice: number; // For comparison
|
|
30
|
+
addedAt: Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Cart {
|
|
34
|
+
id: string;
|
|
35
|
+
userId?: string; // null for guest
|
|
36
|
+
sessionId: string; // For guest merge
|
|
37
|
+
items: CartItem[];
|
|
38
|
+
couponCode?: string;
|
|
39
|
+
updatedAt: Date;
|
|
40
|
+
expiresAt: Date; // Cart expiration
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Key Patterns
|
|
45
|
+
|
|
46
|
+
| Pattern | Description |
|
|
47
|
+
|---------|-------------|
|
|
48
|
+
| **Guest → User Merge** | Merge localStorage cart on login |
|
|
49
|
+
| **Price Snapshot** | Store price at add time, revalidate at checkout |
|
|
50
|
+
| **Expiration** | Clear abandoned carts after N days |
|
|
51
|
+
| **Validation** | Check stock/price at checkout entry |
|
|
52
|
+
|
|
53
|
+
#### Implementation
|
|
54
|
+
```typescript
|
|
55
|
+
class CartService {
|
|
56
|
+
async addItem(cartId: string, item: AddItemRequest): Promise<Cart> {
|
|
57
|
+
// 1. Validate product exists and in stock
|
|
58
|
+
const product = await this.productService.get(item.productId);
|
|
59
|
+
if (!product || product.stock < item.quantity) {
|
|
60
|
+
throw new OutOfStockError();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. Snapshot current price
|
|
64
|
+
const cartItem: CartItem = {
|
|
65
|
+
...item,
|
|
66
|
+
price: product.currentPrice,
|
|
67
|
+
originalPrice: product.originalPrice,
|
|
68
|
+
addedAt: new Date(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// 3. Add or update quantity
|
|
72
|
+
return this.cartRepository.upsertItem(cartId, cartItem);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async mergeGuestCart(userId: string, sessionId: string): Promise<Cart> {
|
|
76
|
+
const guestCart = await this.cartRepository.findBySession(sessionId);
|
|
77
|
+
const userCart = await this.cartRepository.findByUser(userId);
|
|
78
|
+
|
|
79
|
+
if (!guestCart) return userCart;
|
|
80
|
+
|
|
81
|
+
// Merge: user cart takes priority for duplicates
|
|
82
|
+
const merged = this.mergeItems(userCart.items, guestCart.items);
|
|
83
|
+
await this.cartRepository.delete(guestCart.id);
|
|
84
|
+
|
|
85
|
+
return this.cartRepository.update(userCart.id, { items: merged });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Payment
|
|
91
|
+
|
|
92
|
+
#### State Machine
|
|
93
|
+
```
|
|
94
|
+
PENDING → PROCESSING → AUTHORIZED → CAPTURED → COMPLETED
|
|
95
|
+
↘ FAILED
|
|
96
|
+
↘ CANCELED
|
|
97
|
+
COMPLETED → REFUND_REQUESTED → REFUNDED (partial/full)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Idempotency Pattern (Critical)
|
|
101
|
+
```typescript
|
|
102
|
+
interface PaymentRequest {
|
|
103
|
+
orderId: string;
|
|
104
|
+
amount: number;
|
|
105
|
+
currency: string;
|
|
106
|
+
idempotencyKey: string; // REQUIRED: `order_${orderId}_${timestamp}`
|
|
107
|
+
paymentMethod: PaymentMethod;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class PaymentService {
|
|
111
|
+
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
|
|
112
|
+
// 1. Check idempotency - prevent duplicate charges
|
|
113
|
+
const existing = await this.paymentRepository.findByIdempotencyKey(
|
|
114
|
+
request.idempotencyKey
|
|
115
|
+
);
|
|
116
|
+
if (existing) {
|
|
117
|
+
return existing.result; // Return cached result
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 2. Create payment record (PENDING)
|
|
121
|
+
const payment = await this.paymentRepository.create({
|
|
122
|
+
...request,
|
|
123
|
+
status: 'PENDING',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// 3. Call PG adapter
|
|
128
|
+
const pgResult = await this.pgAdapter.authorize(request);
|
|
129
|
+
|
|
130
|
+
// 4. Update status
|
|
131
|
+
return this.paymentRepository.update(payment.id, {
|
|
132
|
+
status: pgResult.success ? 'AUTHORIZED' : 'FAILED',
|
|
133
|
+
pgTransactionId: pgResult.transactionId,
|
|
134
|
+
result: pgResult,
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
await this.paymentRepository.update(payment.id, {
|
|
138
|
+
status: 'FAILED',
|
|
139
|
+
error: error.message,
|
|
140
|
+
});
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### PG Adapter Pattern
|
|
148
|
+
```typescript
|
|
149
|
+
interface PGAdapter {
|
|
150
|
+
authorize(request: PaymentRequest): Promise<PGResult>;
|
|
151
|
+
capture(transactionId: string): Promise<PGResult>;
|
|
152
|
+
cancel(transactionId: string): Promise<PGResult>;
|
|
153
|
+
refund(transactionId: string, amount?: number): Promise<PGResult>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Implementations
|
|
157
|
+
class TossPaymentsAdapter implements PGAdapter { /* ... */ }
|
|
158
|
+
class StripeAdapter implements PGAdapter { /* ... */ }
|
|
159
|
+
class PortOneAdapter implements PGAdapter { /* ... */ }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Webhook Handling
|
|
163
|
+
```typescript
|
|
164
|
+
class PaymentWebhookHandler {
|
|
165
|
+
async handle(event: WebhookEvent): Promise<void> {
|
|
166
|
+
// 1. Verify signature
|
|
167
|
+
if (!this.verifySignature(event)) {
|
|
168
|
+
throw new UnauthorizedError();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2. Idempotency check - process each event only once
|
|
172
|
+
const processed = await this.eventStore.find(event.id);
|
|
173
|
+
if (processed) {
|
|
174
|
+
return; // Already processed
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. Process event
|
|
178
|
+
await this.processEvent(event);
|
|
179
|
+
|
|
180
|
+
// 4. Mark as processed
|
|
181
|
+
await this.eventStore.save({
|
|
182
|
+
eventId: event.id,
|
|
183
|
+
processedAt: new Date(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 3. Inventory (Stock Management)
|
|
190
|
+
|
|
191
|
+
#### Reservation Pattern (Two-Phase)
|
|
192
|
+
```typescript
|
|
193
|
+
interface StockReservation {
|
|
194
|
+
id: string;
|
|
195
|
+
productId: string;
|
|
196
|
+
quantity: number;
|
|
197
|
+
orderId: string;
|
|
198
|
+
status: 'RESERVED' | 'COMMITTED' | 'RELEASED';
|
|
199
|
+
expiresAt: Date; // Auto-release if not committed
|
|
200
|
+
createdAt: Date;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class InventoryService {
|
|
204
|
+
// Phase 1: Reserve stock (at checkout start)
|
|
205
|
+
async reserve(orderId: string, items: OrderItem[]): Promise<void> {
|
|
206
|
+
for (const item of items) {
|
|
207
|
+
// Atomic decrement with check
|
|
208
|
+
const result = await this.db.query(`
|
|
209
|
+
UPDATE products
|
|
210
|
+
SET reserved_stock = reserved_stock + $1
|
|
211
|
+
WHERE id = $2
|
|
212
|
+
AND (available_stock - reserved_stock) >= $1
|
|
213
|
+
RETURNING *
|
|
214
|
+
`, [item.quantity, item.productId]);
|
|
215
|
+
|
|
216
|
+
if (result.rowCount === 0) {
|
|
217
|
+
// Rollback previous reservations
|
|
218
|
+
await this.releaseAll(orderId);
|
|
219
|
+
throw new InsufficientStockError(item.productId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await this.reservationRepository.create({
|
|
223
|
+
orderId,
|
|
224
|
+
productId: item.productId,
|
|
225
|
+
quantity: item.quantity,
|
|
226
|
+
status: 'RESERVED',
|
|
227
|
+
expiresAt: addMinutes(new Date(), 15), // 15min hold
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Phase 2: Commit stock (after payment success)
|
|
233
|
+
async commit(orderId: string): Promise<void> {
|
|
234
|
+
const reservations = await this.reservationRepository.findByOrder(orderId);
|
|
235
|
+
|
|
236
|
+
for (const reservation of reservations) {
|
|
237
|
+
await this.db.query(`
|
|
238
|
+
UPDATE products
|
|
239
|
+
SET
|
|
240
|
+
available_stock = available_stock - $1,
|
|
241
|
+
reserved_stock = reserved_stock - $1
|
|
242
|
+
WHERE id = $2
|
|
243
|
+
`, [reservation.quantity, reservation.productId]);
|
|
244
|
+
|
|
245
|
+
await this.reservationRepository.update(reservation.id, {
|
|
246
|
+
status: 'COMMITTED',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Rollback: Release stock (payment failed or timeout)
|
|
252
|
+
async release(orderId: string): Promise<void> {
|
|
253
|
+
const reservations = await this.reservationRepository.findByOrder(orderId);
|
|
254
|
+
|
|
255
|
+
for (const reservation of reservations) {
|
|
256
|
+
if (reservation.status === 'RESERVED') {
|
|
257
|
+
await this.db.query(`
|
|
258
|
+
UPDATE products
|
|
259
|
+
SET reserved_stock = reserved_stock - $1
|
|
260
|
+
WHERE id = $2
|
|
261
|
+
`, [reservation.quantity, reservation.productId]);
|
|
262
|
+
|
|
263
|
+
await this.reservationRepository.update(reservation.id, {
|
|
264
|
+
status: 'RELEASED',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Concurrency Control
|
|
273
|
+
```typescript
|
|
274
|
+
// Option 1: Optimistic Locking
|
|
275
|
+
await db.query(`
|
|
276
|
+
UPDATE products
|
|
277
|
+
SET stock = stock - $1, version = version + 1
|
|
278
|
+
WHERE id = $2 AND version = $3 AND stock >= $1
|
|
279
|
+
`, [quantity, productId, expectedVersion]);
|
|
280
|
+
|
|
281
|
+
// Option 2: Pessimistic Locking (for high contention)
|
|
282
|
+
await db.query(`
|
|
283
|
+
SELECT * FROM products WHERE id = $1 FOR UPDATE
|
|
284
|
+
`, [productId]);
|
|
285
|
+
|
|
286
|
+
// Option 3: Redis Distributed Lock
|
|
287
|
+
const lock = await redlock.lock(`stock:${productId}`, 5000);
|
|
288
|
+
try {
|
|
289
|
+
// Update stock
|
|
290
|
+
} finally {
|
|
291
|
+
await lock.unlock();
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Order Flow (Complete)
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
299
|
+
│ CHECKOUT FLOW │
|
|
300
|
+
├─────────────────────────────────────────────────────────────┤
|
|
301
|
+
│ │
|
|
302
|
+
│ 1. Cart Validation │
|
|
303
|
+
│ └─ Revalidate prices, check stock availability │
|
|
304
|
+
│ │
|
|
305
|
+
│ 2. Order Creation (PENDING) │
|
|
306
|
+
│ └─ Generate orderId, snapshot cart │
|
|
307
|
+
│ │
|
|
308
|
+
│ 3. Stock Reservation │
|
|
309
|
+
│ └─ Reserve inventory (15min hold) │
|
|
310
|
+
│ │
|
|
311
|
+
│ 4. Payment Processing │
|
|
312
|
+
│ ├─ Success → Order PAID, Stock COMMIT │
|
|
313
|
+
│ └─ Failure → Order FAILED, Stock RELEASE │
|
|
314
|
+
│ │
|
|
315
|
+
│ 5. Order Confirmation │
|
|
316
|
+
│ └─ Send notification, trigger fulfillment │
|
|
317
|
+
│ │
|
|
318
|
+
└─────────────────────────────────────────────────────────────┘
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Common Bugs & Prevention
|
|
322
|
+
|
|
323
|
+
| Bug | Cause | Prevention |
|
|
324
|
+
|-----|-------|------------|
|
|
325
|
+
| **Duplicate payment** | User double-click, webhook retry | Idempotency key |
|
|
326
|
+
| **Negative stock** | Race condition | Atomic update with check |
|
|
327
|
+
| **Price mismatch** | Price changed during checkout | Snapshot + revalidation |
|
|
328
|
+
| **Orphan reservation** | Payment timeout without release | TTL + scheduled cleanup |
|
|
329
|
+
| **Lost webhook** | Network failure | Retry + idempotent handler |
|
|
330
|
+
| **Order state corruption** | Concurrent updates | State machine + versioning |
|
|
331
|
+
|
|
332
|
+
## Integration with /vibe.run
|
|
333
|
+
|
|
334
|
+
During commerce feature implementation:
|
|
335
|
+
|
|
336
|
+
1. **Phase 1**: Define domain models (Cart, Order, Payment, Inventory)
|
|
337
|
+
2. **Phase 2**: Implement core services with patterns above
|
|
338
|
+
3. **Phase 3**: Add PG adapter and webhook handlers
|
|
339
|
+
4. **Phase 4**: Implement compensating transactions (Saga)
|
|
340
|
+
5. **Phase 5**: E2E test critical flows
|
|
341
|
+
|
|
342
|
+
## Checklist
|
|
343
|
+
|
|
344
|
+
### Cart
|
|
345
|
+
- [ ] Guest/user cart merge on login
|
|
346
|
+
- [ ] Price snapshot at add time
|
|
347
|
+
- [ ] Stock validation at checkout
|
|
348
|
+
- [ ] Cart expiration cleanup
|
|
349
|
+
|
|
350
|
+
### Payment
|
|
351
|
+
- [ ] Idempotency key on all requests
|
|
352
|
+
- [ ] Webhook signature verification
|
|
353
|
+
- [ ] Duplicate event handling
|
|
354
|
+
- [ ] Timeout/retry strategy
|
|
355
|
+
- [ ] Refund flow tested
|
|
356
|
+
|
|
357
|
+
### Inventory
|
|
358
|
+
- [ ] Two-phase reservation (reserve → commit/release)
|
|
359
|
+
- [ ] Atomic stock updates
|
|
360
|
+
- [ ] Reservation TTL and cleanup job
|
|
361
|
+
- [ ] Concurrency control tested
|