Skip to content

Execution Flows

Step-by-step traces of cart operations with events and plugins

1. Add to Cart Flow

Adding a product to the shopping cart via CartItemRepositoryInterface::save()

1

Resolve Quote

Get or create active quote for customer/guest

$quote = $this->quoteRepository->getActive($cartId);
// For guest: resolves masked ID to quote ID first
2

Load Product

Fetch product with required options validation

$product = $this->productRepository->getById(
    $cartItem->getSku(),
    false,
    $quote->getStoreId()
);
3

Quote::addProduct()

Add product to quote with buy request processing

$result = $quote->addProduct($product, $request);
// Dispatches: sales_quote_product_add_after
// Creates Quote\Item with options
4

Collect Totals

Recalculate all cart totals with new item

$quote->collectTotals();
// Runs all collectors: subtotal, discount, shipping, tax, grand_total
5

Save Quote

Persist quote with items, addresses, and totals

$this->quoteRepository->save($quote);
// Dispatches: sales_quote_save_after

Events Fired

sales_quote_product_add_after, sales_quote_item_qty_set_after, sales_quote_collect_totals_before, sales_quote_collect_totals_after, sales_quote_save_after

2. Totals Collection Flow

The complete totals calculation sequence when Quote::collectTotals() is called

1

Initialize Collection

Reset totals and prepare addresses for collection

// Dispatches: sales_quote_collect_totals_before
$this->setTotalsCollectedFlag(false);
$this->setSubtotal(0)->setBaseSubtotal(0);
2

Collect Per Address

Each address runs its own totals collection

foreach ($this->getAllAddresses() as $address) {
    $address->setCollectShippingRates(true);
    $address->collectTotals();
}
3

Run Collectors in Order

Each collector modifies the quote/address totals

// Collector order from sales.xml:
// 1. subtotal    - Calculate item prices
// 2. discount    - Apply cart rules/coupons
// 3. shipping    - Calculate shipping cost
// 4. tax         - Calculate taxes
// 5. grand_total - Sum everything
4

Aggregate Quote Totals

Sum address totals into quote-level totals

$this->setSubtotal(
    $this->getSubtotal() + $address->getSubtotal()
);
$this->setGrandTotal(
    $this->getGrandTotal() + $address->getGrandTotal()
);
5

Finalize Collection

Mark totals as collected and dispatch event

$this->setTotalsCollectedFlag(true);
// Dispatches: sales_quote_collect_totals_after

3. Quote to Order Conversion

The order placement flow via CartManagementInterface::placeOrder()

1

Validate Quote

Run all validation rules before order creation

$validationResult = $this->quoteValidator->validate($quote);
// Checks: addresses, shipping method, payment, min amount
if (!$validationResult->isValid()) {
    throw new LocalizedException($validationResult->getErrors());
}
2

Acquire Quote Lock

Prevent concurrent order placement (QuoteMutex)

$this->quoteMutex->execute(
    [$cartId],
    function () use ($quote, $paymentMethod) {
        return $this->submitQuote($quote, $paymentMethod);
    }
);
3

Submit Quote

Convert quote to order using QuoteManagement

$order = $this->quoteManagement->submit($quote);
// Dispatches: sales_model_service_quote_submit_before
// Creates Order from Quote data
// Dispatches: sales_model_service_quote_submit_success
4

Deactivate Quote

Mark quote as inactive and link to order

$quote->setIsActive(false);
$quote->setReservedOrderId($order->getIncrementId());
$this->quoteRepository->save($quote);
5

SubmitObserver Triggers

Observers handle post-order tasks

// SubmitObserver handles:
// - Inventory deduction (via sales_order_place_after)
// - Email notifications
// - Customer balance deduction

Critical: Quote Mutex

The QuoteMutex prevents race conditions where a customer might double-click "Place Order" and create duplicate orders. Always use the mutex when modifying quote state during order placement.

4. Guest Cart Creation Flow

Creating and managing anonymous shopping carts with masked IDs

1

Create Empty Cart

GuestCartManagementInterface::createEmptyCart()

// POST /V1/guest-carts
$quoteId = $this->quoteManagement->createEmptyCart();
// Creates quote with customer_id = NULL, is_active = 1
2

Generate Masked ID

Create UUID token for anonymous access

$maskedId = $this->quoteIdMaskFactory->create();
$maskedId->setQuoteId($quoteId)
         ->setMaskedId($this->randomGenerator->getUniqueHash());
$maskedId->save();
// Returns masked ID like: "pwa-abc123xyz789"
3

Resolve Masked ID

All guest operations resolve masked ID first

// MaskedQuoteIdToQuoteIdInterface
$quoteId = $this->maskedQuoteIdToQuoteId->execute($maskedCartId);
// Throws NoSuchEntityException if not found
4

Merge on Login

Guest cart merges with customer cart on authentication

// GuestCartManagementInterface::assignCustomer()
$this->quoteManagement->assignCustomer(
    $guestQuoteId,
    $customerId,
    $storeId
);
// Items from guest cart are merged into customer's active cart

5. Coupon Application Flow

Applying discount codes via CouponManagementInterface::set()

1

Validate Coupon Code

Check code exists and is active

$quote->getShippingAddress()->setCollectShippingRates(true);
$quote->setCouponCode($couponCode);
$quote->collectTotals();
2

SalesRule Collector Runs

Discount collector processes cart rules

// Magento_SalesRule handles discount calculation
// - Finds rules matching coupon code
// - Validates conditions (cart amount, customer group, etc.)
// - Calculates discount per item/shipping
3

Verify Application

Confirm coupon was actually applied

if ($quote->getCouponCode() !== $couponCode) {
    throw new NoSuchEntityException(
        __('The coupon code isn\'t valid.')
    );
}
$this->quoteRepository->save($quote);

Why Verification?

The coupon may be rejected if conditions aren't met (wrong customer group, cart total too low, expired, usage limit reached). The collector will silently clear the coupon if invalid, so we must verify it persisted.