Cart Management
The Cart Management submodule handles shopping cart operations, checkout orchestration, invoice preview generation, and promotional code management for the DashClicks Store platform.
API Endpoints Overview
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/store/cart | Add item to cart |
GET | /v1/store/cart | Get cart items with invoice preview |
PUT | /v1/store/cart/:id | Update cart item quantity |
DELETE | /v1/store/cart/:id | Remove item from cart |
POST | /v1/store/cart/checkout | Process cart checkout |
POST | /v1/store/cart/single-purchase | Direct purchase without cart |
POST | /v1/store/cart/promo | Add promo code to cart |
DELETE | /v1/store/cart/promo/:id | Remove promo code from cart |
MongoDB Collections Used
Primary Collections
_store.cart- Shopping cart items and promo codes_store.prices- Product pricing information_store.products- Product catalog_store.subscriptions- Active subscriptions for upgrades_store.promo.codes- Promotional codescrm.contacts- Business and person contact information_accounts- Account information and settingsqueues- Background job processing for fulfillment
Related Collections
_store.orders- Created orders during checkout_store.subscription-feedback- Subscription cancellation feedbackprojects-typeforms- Onboarding form requests_loyalty.program.orders- Loyalty program order tracking
Core Cart Workflows
Add to Cart Flow
graph TD
A[Add Item Request] --> B[Validate Cart Limit 60 items]
B --> C{Is Bundle?}
C -->|Yes| D[Create Multiple Cart Items]
C -->|No| E[Create Single Cart Item]
D --> F[Check Duplicate Items]
E --> F
F --> G[Validate Business/Person Relationships]
G --> H{Is Software Product?}
H -->|Yes| I[Check for Existing Software Subscriptions]
H -->|No| J[Validate Service Availability]
I --> K{Has Conflicting Software?}
K -->|Yes| L[Return Error: Mutual Exclusivity]
K -->|No| M[Store Onboarding Preferences]
J --> M
M --> N[Save Cart Items]
N --> O[Return Created Items]
Checkout Processing Flow
graph TD
A[Checkout Request] --> B[Create Checkout Lock]
B --> C{Lock Created?}
C -->|No| D[Return Error: Concurrent Checkout]
C -->|Yes| E[Retrieve Cart Items]
E --> F{Has Items?}
F -->|No| G[Return Error: Empty Cart]
F -->|Yes| H[Create Sub-Accounts for New Businesses]
H --> I[Group Items by Billing Interval]
I --> J[For Each Billing Group]
J --> K{Has Existing Subscription?}
K -->|Yes| L[Upgrade Existing Subscription]
K -->|No| M[Create New Subscription]
L --> N[Apply Loyalty Coupons]
M --> N
N --> O[Attach Setup Fees to First Subscription]
O --> P[Create Order Records]
P --> Q[Send Onboarding Invites]
Q --> R[Queue Fulfillment Jobs]
R --> S[Clear Cart Items]
S --> T[Delete Checkout Lock]
T --> U[Return Created Subscriptions]
Service Methods & Functionality
Cart Operations
newCartItem(req, res, next) - Add item to cart
- Purpose: Add product to shopping cart with validation
- Parameters:
business(ObjectId): Business contact IDperson(ObjectId, optional): Person contact IDprice(ObjectId): Price ID to purchasebundle(String, optional): Bundle identifierexternal_action(Object, optional): Service-specific metadataonboarding_preference(String, optional): Onboarding workflow preference
- Business Logic:
- Cart Limit Validation: Enforces 60-item maximum per account
- Duplicate Check: Prevents adding same price for same business multiple times
- Business Validation: Verifies business exists and belongs to account
- Person Validation: If provided, validates person-business relationship
- Software Exclusivity: Prevents multiple software subscriptions (mutual exclusivity rule)
- Service Availability:
- For listings: Validates available slots via external API
- For websites: Checks domain availability
- For phone numbers: Validates area code availability
- Bundle Handling: Creates multiple cart items with shared
bundle_idUUID - Onboarding Preferences: Stores form preferences for later processing
- Returns: Created cart item(s) or array of items for bundles
- Error Cases:
400: Cart limit exceeded, duplicate item, invalid business/person404: Business or person not found409: Conflicting software subscription exists
getCartItems(req, res, next) - Retrieve cart with invoice preview
- Purpose: Get aggregated cart items with Stripe invoice preview
- Business Logic:
- Cart Aggregation: Calls
aggregateCart()for complex data processing - Invoice Preview: Retrieves Stripe upcoming invoice for each billing interval
- Promo Validation: Validates and expires invalid promo codes
- Loyalty Discounts: Applies account loyalty program benefits
- Bundle Merging: Combines bundle items into single line items with total quantities
- Totals Calculation: Computes subtotals, discounts, taxes, and final totals
- Cart Aggregation: Calls
- Returns: Cart object with structure:
{
items: [...], // Cart items grouped by price
subtotal: 0, // Item total before discounts
setup_fee: 0, // One-time setup fees
discount: 0, // Total discounts applied
tax: 0, // Sales tax if applicable
total: 0, // Final total after all adjustments
promo_code: {...}, // Applied promo code details
upcoming_invoices: [...] // Stripe invoice previews by interval
}
updateCartItems(req, res, next) - Update cart item quantity
- Purpose: Modify cart item quantity with validation
- Parameters:
quantity(Number): New quantity (min 1)
- Business Logic:
- Lock Check: Some items (upgrades, software) cannot change quantity
- Bundle Validation: Ensures bundle items maintain consistency
- Quantity Update: Updates all items in bundle if applicable
- Returns: Updated cart item(s)
deleteCartItem(req, res, next) - Remove item from cart
- Purpose: Delete cart item and associated bundle items
- Business Logic:
- If item is part of bundle, deletes all bundle items
- If item is promo code, removes promo
- Single item deletion otherwise
- Returns: Deletion confirmation
Checkout Operations
checkout(req, res, next) - Process complete checkout
-
Purpose: Execute full checkout flow with subscription creation
-
Parameters:
card(String, optional): Payment method ID for new payment method
-
Business Logic:
-
Checkout Locking:
- Creates lock document:
{account_id, type: 'checkout'} - Prevents concurrent checkouts causing duplicate subscriptions
- Lock automatically expires after processing
- Creates lock document:
-
Cart Retrieval:
- Fetches all cart items for account
- Validates cart is not empty
- Groups by buyer (business contact)
-
Sub-Account Creation:
- For new businesses (accounts with
pending: true) - Creates sub-account linked to parent
- Generates affiliate code
- Sets up account-business relationship
- For new businesses (accounts with
-
Subscription Grouping:
- Groups items by billing interval (monthly, quarterly, semi-annually, annually)
- Creates separate subscription for each interval group
- Ensures proper billing cycle management
-
Stripe Integration:
- Resolves Stripe account (platform vs connected)
- Creates/updates customer in Stripe
- Attaches payment method if provided
- Creates subscription with line items:
{
price: priceId,
quantity: itemQuantity,
metadata: {
business_id: businessId,
person_id: personId,
external_action: {...}
}
}
-
Application Fees (for connected accounts):
- Calculates wholesale amount from
additional_info.wholesale_unit_amount - Adds percentage fees:
ADDITIONAL_APP_FEE_PERCENTAGE+ADDITIONAL_APP_FEE_SUBSCRIPTION_PERCENTAGE - Applies to subscription:
application_fee_percent
- Calculates wholesale amount from
-
Discount Application:
- Applies loyalty program coupons if eligible
- Applies promo codes from cart
- Ensures coupon compatibility (Stripe limitations)
-
Setup Fees:
- Extracts one-time setup fees from prices
- Creates invoice items attached to first subscription only
- Prevents duplicate setup charges across intervals
-
Order Generation:
- Creates order record for each subscription
- Links subscription → order → business
- Stores product metadata and external actions
-
Onboarding Triggers:
- Sends onboarding invites if
onboarding_preferenceprovided - Creates typeform requests for product-specific forms
- Queues onboarding notification emails
- Sends onboarding invites if
-
Fulfillment:
- Queues fulfillment jobs for each product type
- Passes
external_actionmetadata to queues - Triggers provisioning workflows
-
Cleanup:
- Deletes all cart items
- Removes promo code from cart
- Deletes checkout lock document
-
-
Returns: Array of created subscriptions with orders:
[
{
subscription: {...}, // Stripe subscription
order: {...}, // Created order record
business: {...} // Business contact
}
]
singlePurchase(req, res, next) - Direct purchase without cart
- Purpose: Single-item purchase with immediate checkout
- Parameters:
type(String): 'preview' | 'purchase'business(ObjectId): Business IDprice(ObjectId): Price IDsubscription(ObjectId, optional): For upgradesexternal_action(Object, optional): Service metadatawaive_setup(Boolean): Skip setup fees
- Business Logic:
- Temporary Cart: Creates temporary cart item
- Invoice Preview: If
type === 'preview', returns Stripe invoice preview - Immediate Checkout: If
type === 'purchase', executes full checkout - Upgrade Handling: For existing subscriptions, prorates changes
- Phone Number Quantity: For phone products, calculates quantity via external API
- Cleanup: Removes temporary cart item after processing
- Returns: Invoice preview object or completed purchase
Promo Code Operations
addPromoToCart(req, res, next) - Apply promo code
- Purpose: Add promotional code to cart
- Parameters:
promo_code(String): Stripe promotion code ID
- Business Logic:
- Validates promo code exists in Stripe
- Removes existing promo code if present
- Creates cart document with
type: 'promocode' - Stores promo code reference
- Returns: Success confirmation
deletePromoFromCart(req, res, next) - Remove promo code
- Purpose: Remove promo code from cart
- Returns: Deletion confirmation
Utility Methods
validatePromocode(promocodeId, stripe, customerId) - Validate promo code
- Purpose: Check if promo code is valid and usable
- Business Logic:
- Active Check: Verifies promo code
active === true - Coupon Expiry: Checks coupon not expired (
expires_at) - Redemption Limit: Validates
times_redeemed < max_redemptions - Customer-Specific: If promo has
restrictions.first_time_transaction, validates customer eligibility - Auto-Expiry: If expired, sets
active: falsein Stripe
- Active Check: Verifies promo code
- Returns:
trueif valid,falseif invalid
aggregateCart(account_id, req) - Complex cart aggregation
-
Purpose: MongoDB aggregation pipeline for cart data
-
Business Logic:
-
Item Grouping:
$group: {
_id: {
buyer: '$buyer',
price: '$price'
},
quantity: { $sum: 1 },
items: { $push: '$$ROOT' }
} -
Price Lookup: Populates price details with product info
-
Subscription Check: Determines if items are upgrades:
$lookup: {
from: '_store.subscriptions',
localField: 'buyer',
foreignField: 'business',
as: 'existing_subscriptions'
} -
Transaction Type: Sets
new,upgrade, ordowngrade -
Quantity Lock: Locks items that can't change quantity (software, upgrades)
-
Subtotal Calculation:
item_subtotal: price.unit_amount * quantity;
setup_subtotal: price.setup_fee * quantity; -
Bundle Merging:
- Groups items by
bundle_nameandbundle_id - Combines quantities:
{bundle: 'name', total_quantity: 5, items: [...]}
- Groups items by
-
Stripe Invoice Preview:
- Groups by billing interval
- Calls Stripe for upcoming invoice per group
- Merges discount and tax information
-
Promo Validation:
- Retrieves promo code from cart
- Validates using
validatePromocode() - Removes if expired
-
-
Returns: Aggregated cart object with all computed fields
phoneNumQuantity(params) - Calculate phone number quantity
- Purpose: Query external API for phone number pricing
- Parameters:
account_id,uid,parent_accountexternal_action: Contains area code and search paramsprice_id: Price to match
- Business Logic:
- Generates JWT token for external API authentication
- Calls Twilio integration for available numbers search
- Matches price from pricing response array
- Returns
unit_amount(price in cents) as quantity
- Returns: Number quantity or
nullif not found
Technical Implementation Details
Checkout Locking Mechanism
Problem: Multiple concurrent checkout requests can create duplicate subscriptions
Solution: Database-level locking with cart documents
const lock = await Cart.findOneAndUpdate(
{ account_id, type: 'checkout' },
{ account_id, type: 'checkout', created_at: new Date() },
{ upsert: true, new: true },
);
if (!lock) {
throw new Error('Another checkout is in progress');
}
try {
// Process checkout
} finally {
await Cart.deleteOne({ _id: lock._id });
}
Bundle Handling
Bundles are groups of products sold together with shared identifier:
-
Creation: Single API call creates multiple cart items
const bundle_id = uuidv4();
for (const priceId of bundlePrices) {
await Cart.create({
account_id,
buyer,
price: priceId,
bundle_id,
bundle_name: 'Bundle Name',
});
} -
Aggregation: Bundle items merged in display
{
bundle_name: 'Starter Pack',
items: [
{ price: 'Website', quantity: 1 },
{ price: 'SEO', quantity: 1 }
],
total_quantity: 2,
total_amount: 29900
} -
Deletion: Removing one bundle item removes all
Invoice Preview System
Multi-Interval Previews: Cart can have multiple billing intervals
const intervals = ['month', 'quarter', 'semi-annual', 'year'];
const previews = {};
for (const interval of intervals) {
const itemsForInterval = cartItems.filter(i => i.price.recurring.interval === interval);
if (itemsForInterval.length > 0) {
previews[interval] = await stripe.invoices.retrieveUpcoming({
customer: customerId,
subscription_items: itemsForInterval.map(i => ({
price: i.price.stripe_id,
quantity: i.quantity,
})),
coupon: promoCode?.coupon?.id,
});
}
}
Software Subscription Exclusivity
Rule: Account can only have one active software subscription
const existingSoftware = await Subscription.findOne({
account_id,
'product.type': 'software',
status: { $in: ['active', 'trialing'] },
});
if (existingSoftware && newItemType === 'software') {
throw new ApiError(409, 'Cannot add multiple software subscriptions');
}
Connected Account Routing
Platform vs Connected Accounts:
let stripe, application_fee_percent;
if (account.main) {
// Platform account
stripe = Stripe(process.env.STRIPE_SECRET_KEY);
application_fee_percent = 0;
} else {
// Connected account (white-label)
const stripe_keys = await StripeKey.findOne({
account_id: account.parent_account,
});
stripe = Stripe(stripe_keys.token.access_token);
// Calculate application fee
application_fee_percent = calculateAppFee(prices);
}
Error Handling
Common Error Codes
- 400 Bad Request: Invalid parameters, cart limit exceeded, duplicate items
- 404 Not Found: Business, person, price, or promo code not found
- 409 Conflict: Conflicting software subscription, concurrent checkout
- 422 Unprocessable: Invalid external_action metadata, service unavailable
- 500 Server Error: Stripe API errors, database failures
Validation Patterns
// Cart item validation schema (Joi)
{
business: Joi.objectId().required(),
person: Joi.objectId().optional(),
price: Joi.objectId().required(),
bundle: Joi.string().optional(),
external_action: Joi.object().optional(),
onboarding_preference: Joi.string().valid('skip', 'send')
}
Integration Points
External Services
- Stripe API: Subscriptions, invoices, customers, payment methods, promo codes
- Twilio Integration: Phone number availability and pricing
- Listing APIs: Business listing availability checks
- Website Service: Domain availability validation
Internal Services
- Queue Manager: Fulfillment job processing
- Notification Service: Onboarding invite emails
- Subscription Service: Subscription creation and management
- Order Service: Order record generation
- Account Service: Sub-account creation
PubSub Events
// Published after checkout
pubsub.publish('store:checkout:complete', {
account_id,
subscriptions: createdSubscriptions,
orders: createdOrders,
total_amount: checkoutTotal,
});
Performance Considerations
Optimization Strategies
- Aggregation Pipeline: Single database query for cart retrieval
- Batch Operations: Create multiple cart items in single transaction
- Cached Lookups: Price and product data cached during aggregation
- Concurrent Stripe Calls: Invoice previews fetched in parallel by interval
- Indexed Queries: Indexes on
account_id,buyer,bundle_id,type
Database Indexes
// Cart collection indexes
{
account_id: 1,
buyer: 1,
price: 1
}
{
account_id: 1,
type: 1 // For checkout lock
}
{
bundle_id: 1 // For bundle operations
}
Related Documentation
- Subscription Management - Subscription lifecycle
- Order Management - Order tracking
- Product Management - Product catalog
- Store Module Overview - Architecture and configuration