Domain: Checkout
Scenario: Cart → Checkout → Order Creation
1. Business Goal
Checkout transforms shopping cart items into a paid order. The flow includes:
- Cart management: Add/remove items, update quantities
- Checkout creation: Convert cart to checkout with pricing
- Promotions: Apply coupons, automatic discounts
- Points: Redeem loyalty points
- Payment: Process Stripe/PayPal payment
- Order creation: Convert checkout to order on success
2. Trigger & Entry
| API | Method | Description | Request Body |
|---|---|---|---|
/cart/promoter |
POST | Add to cart | { promoterVariantId, quantity, postId, customFields } |
/cart |
PUT | Update cart | { cartItemId, quantity } |
/cart |
GET | Get cart items | { selected } |
/order/checkout |
POST | Create checkout | { cartItemIds[] } |
/order/promotions |
POST | Apply promotion | { promotionIds[] } |
/order/points-and-promotions |
POST | Apply points & promos | { promotionIds[], usePoints } |
/order/buy-now |
POST | Express checkout (skip cart) | { promoterVariantId, quantity, postId } |
3. Data Flow (Cart → Checkout → Order)
sequenceDiagram
participant Consumer as Consumer
participant Cart as CartService
participant Checkout as CheckoutService
participant Payment as PaymentService
participant Order as OrdersService
participant DB as PostgreSQL
participant Stripe as Stripe API
Note over Consumer: STEP 1: Add to Cart
Consumer->>Cart: POST /cart/promoter
Cart->>DB: SELECT PromoterProductVariant
Cart->>DB: UPSERT Cart (consumerId, variantId, quantity)
Cart-->>Consumer: { cart }
Note over Consumer: STEP 2: Create Checkout
Consumer->>Checkout: POST /order/checkout
Checkout->>Cart: getCartMapBySelf(consumerId)
Cart-->>Checkout: { carts, variants, posts }
Note over Checkout: Delete existing draft checkout
Checkout->>DB: DELETE Checkout WHERE consumerId AND status=DRAFT
Note over Checkout: Create checkout session
Checkout->>DB: INSERT Checkout (id=orderId, orderNumber, status=DRAFT)
Checkout->>DB: INSERT CheckoutLineItem[]
Note over Checkout: Run 8 checkout status processors
Checkout->>Checkout: checkoutOnInit()
Checkout->>Checkout: checkoutOnPostLimitQuantity()
Checkout->>Checkout: checkoutOnVariantQuantityConstraints()
Checkout->>Checkout: checkoutOnSampleOrSubscriptionDiscount()
Checkout->>Checkout: checkoutOnPostFreeShipping()
Checkout->>Checkout: checkoutOnAutomaticCoupon()
Checkout->>Checkout: checkoutOnSubscriptionFreeShipping()
Checkout->>Checkout: checkoutOnTax()
Note over Checkout: Calculate totals
Checkout->>Checkout: calcCheckoutResponse()
Checkout->>DB: UPDATE Checkout (totals)
Checkout-->>Consumer: { checkout, lineItems, totals }
Note over Consumer: STEP 3: Apply Promotions (optional)
Consumer->>Checkout: POST /order/promotions
Checkout->>Checkout: applyPromotions()
Checkout->>DB: UPDATE CheckoutLineItem (discounts)
Checkout->>DB: UPDATE Checkout (totals)
Checkout-->>Consumer: Updated checkout
Note over Consumer: STEP 4: Process Payment
Consumer->>Payment: POST /payment/stripe/intent
Payment->>Stripe: POST /v1/payment_intents
Stripe-->>Payment: { clientSecret, amount }
Payment-->>Consumer: { clientSecret }
Note over Consumer: STEP 5: Confirm & Create Order
Consumer->>Order: POST /order (with payment confirmation)
Order->>DB: SELECT Checkout WHERE id
Order->>Order: validatePayment()
Note over Order: Create Order record
Order->>DB: INSERT Order (id=checkout.id, status=IN_PROGRESS)
Order->>DB: INSERT OrderLineItem[] (from CheckoutLineItem)
Order->>DB: UPDATE Checkout (status=COMPLETED)
Note over Order: Create MerchantOrders
Order->>DB: INSERT MerchantOrder[] (per merchant)
Order->>DB: INSERT MerchantLineItem[]
Note over Order: Create PromoterOrder (if applicable)
Order->>DB: INSERT PromoterOrder
Order->>DB: INSERT PromoterLineItem[]
Note over Order: Clear cart
Order->>DB: DELETE Cart WHERE consumerId AND lineItemIds
Order-->>Consumer: { order, orderNumber }
4. DB Operations (Chronological)
| Step | Table | Action | PK/FK Touched | Notes |
|---|---|---|---|---|
| 1 | Cart |
UPSERT | consumerId (FK), promoterProductVariantId (FK) |
Add/update cart item |
| 2 | Checkout |
INSERT | id (PK), consumerId (FK) |
status=DRAFT, orderNumber generated |
| 3 | CheckoutLineItem |
INSERT | id (PK), checkoutId (FK) |
Cart items converted |
| 4 | Checkout |
UPDATE | id (PK) |
After promotions/tax calculation |
| 5 | Order |
INSERT | id (PK), consumerId (FK) |
status=IN_PROGRESS |
| 6 | OrderLineItem |
INSERT | id (PK), orderId (FK) |
From CheckoutLineItem |
| 7 | MerchantOrder |
INSERT | id (PK), orderId (FK), merchantId (FK) |
Per merchant |
| 8 | MerchantLineItem |
INSERT | id (PK), merchantOrderId (FK) |
Commission calc |
| 9 | PromoterOrder |
INSERT (if promo) | id (PK), orderId (FK), promoterId (FK) |
If promoter involved |
| 10 | Cart |
DELETE | consumerId (FK) |
Clear purchased items |
5. Pricing Calculation
Checkout Line Item Price:
// Base price
lineItemPrice = unitPrice * quantity
// Discounts (stackable)
sampleDiscount = (product.isSample) ? sampleDiscountAmount : 0
subscriptionDiscount = (user.hasSubscription) ? subscriptionDiscountAmount : 0
couponDiscount = appliedCouponDiscount
unitDiscount = sampleDiscount + subscriptionDiscount + couponDiscount
// Subtotal
lineItemSubtotal = lineItemPrice - unitDiscount
// Commission (if applicable)
commissionAmount = lineItemSubtotal * commissionRate
// Final
lineItemTotal = lineItemSubtotal + lineItemTax - lineItemPointsDollars
Checkout Totals:
totalLineItemPrice = SUM(lineItemPrice)
totalLineItemDiscount = SUM(unitDiscount * quantity)
totalLineItemSubtotal = SUM(lineItemSubtotal)
totalShippingSubtotal = calculatedShipping
totalShippingDiscount = (freeShipping) ? totalShippingSubtotal : 0
totalProductTax = SUM(lineItemTax)
totalShippingTax = calculatedShippingTax
totalTax = totalProductTax + totalShippingTax
totalToPay = totalLineItemSubtotal + totalShippingSubtotal + totalTax
- totalLineItemPointsDollars
6. Checkout Status Processors
// 8 status processors run in sequence
checkoutOnInit()
→ Initialize default values
checkoutOnPostLimitQuantity()
→ Enforce post-level quantity limits
→ Error if exceeded
checkoutOnVariantQuantityConstraints()
→ Validate min/max purchase quantities
→ Apply pack size requirements
checkoutOnSampleOrSubscriptionDiscount()
→ Apply sample product discounts
→ Apply subscription discounts
checkoutOnPostFreeShipping()
→ Apply post-level free shipping
→ Check post.freeShippingEnabled
checkoutOnAutomaticCoupon()
→ Apply auto-applied coupons
→ Check promotion.autoApply
checkoutOnSubscriptionFreeShipping()
→ Apply subscription free shipping
checkoutOnTax()
→ Calculate product tax (if enabled)
→ Calculate shipping tax
→ Use TaxJar or merchant tax settings
7. Event/Queue Messages
| Message | Producer → Consumer | Payload Snippet |
|---|---|---|
checkout.created |
OrderModule → Queue | { checkoutId, consumerId } |
payment.completed |
PaymentModule → OrderModule | { checkoutId, paymentIntentId } |
order.created |
OrderModule → Queue | { orderId, orderNumber } |
cart.updated |
CartService → Queue | { consumerId, cartItems[] } |
8. File Map (Top 5 Must-Read)
| File | Purpose |
|---|---|
src/cart/cart.service.ts |
Cart CRUD operations |
src/order/checkout.service.ts |
Checkout creation & processors |
src/payment/payment.service.ts |
Stripe/PayPal integration |
src/order/orders.service.ts |
Order creation logic |
src/order/checkout.entity.ts |
Checkout domain entity |
9. DB Schema Snippet (Prisma)
model Cart {
id String @id @default(uuid()) @db.Uuid
consumerId String @map("consumer_id") @db.Uuid
promoterProductVariantId String @map("promoter_product_variant_id") @db.Uuid
variantInfo Json @map("variant_info")
price Float
quantity Int
subtotal Float
commissionRate Float @map("commission_rate")
isGuest Boolean @default(false) @map("is_guest")
selected Boolean @default(true)
customFields Json? @map("custom_fields")
postId String? @map("post_id") @db.Uuid
subscriptionPlanOptionId String? @map("subscription_plan_option_id") @db.Uuid
subdomainUserId String? @map("subdomain_user_id") @db.Uuid
affiliateCode String? @map("affiliate_code")
@@unique([consumerId, promoterProductVariantId, postId, customFields])
}
model Checkout {
id String @id @default(uuid()) @db.Uuid
consumerId String @map("consumer_id") @db.Uuid
status CheckoutStatus @default(DRAFT)
orderNumber String @unique @map("order_number")
shippingAddressInfo Json? @map("shipping_address_info")
// Pricing
totalToPay Float @default(0.0) @map("total_to_pay")
totalPaid Float @default(0.0) @map("total_paid")
totalLineItemPrice Float @default(0.0) @map("total_line_item_price")
totalLineItemDiscount Float @default(0.0) @map("total_line_item_discount")
totalLineItemSubtotal Float @default(0.0) @map("total_line_item_subtotal")
// Shipping
totalShippingPrice Float @default(0.0) @map("total_shipping_price")
totalShippingDiscount Float @default(0.0) @map("total_shipping_discount")
totalShippingSubtotal Float @default(0.0) @map("total_shipping_subtotal")
// Tax
totalTax Float @default(0.0)
totalProductTax Float @default(0.0) @map("total_product_tax")
totalShippingTax Float @default(0.0) @map("total_shipping_tax")
// Points
totalLineItemPointsUsed Int @default(0) @map("total_line_item_points_used")
totalLineItemPointsDollars Float @default(0.0) @map("total_line_item_points_dollars")
// Promotions
promotionIds String[] @map("promotion_ids")
promotions Json?
lineItems CheckoutLineItem[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
}
model CheckoutLineItem {
id String @id @default(uuid()) @db.Uuid
checkoutId String @map("checkout_id") @db.Uuid
lineItemNumber String @map("line_item_number")
Checkout Checkout @relation(fields: [checkoutId], references: [id], onDelete: Cascade)
// Product references
promoterVariantId String @map("promoter_variant_id") @db.Uuid
merchantVariantId String @map("merchant_variant_id") @db.Uuid
variantInfo Json @map("variant_info")
// Participants
consumerId String @map("consumer_id") @db.Uuid
merchantId String @map("merchant_id") @db.Uuid
promoterId String? @map("promoter_id") @db.Uuid
title String?
// Quantity & Pricing
quantity Int @default(1)
unitPrice Float @default(0.0) @map("unit_price")
lineItemPrice Float @default(0.0) @map("line_item_price")
// Discounts
sampleDiscount Float @default(0.0) @map("sample_discount")
subscriptionDiscount Float @default(0.0) @map("subscription_discount")
couponDiscount Float @default(0.0) @map("coupon_discount")
unitDiscount Float @default(0.0) @map("unit_discount")
lineItemDiscount Float @default(0.0) @map("line_item_discount")
// Totals
lineItemSubtotal Float @default(0.0) @map("line_item_subtotal")
// Points
lineItemPointsUsed Int @default(0) @map("line_item_points_used")
lineItemPointsDollars Float @default(0.0) @map("line_item_points_dollars")
// Commission
commissionAmount Float @default(0.0) @map("commission_amount")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([checkoutId])
}
enum CheckoutStatus {
DRAFT
COMPLETED
FAILED
}
10. Express Checkout (Buy Now)
Skip cart, go directly to checkout:
// POST /order/buy-now
interface BuyNowRequest {
promoterVariantId: string;
quantity: number;
postId?: string;
customFields?: Record<string, any>;
}
// Creates checkout directly without cart
// Same flow as regular checkout after checkout creation