Totals Collector System
TotalsCollector Architecture
Magento uses a collector pattern to calculate each total:
// Magento\Quote\Model\Quote\TotalsCollector
namespace Magento\Quote\Model\Quote;
class TotalsCollector
{
/**
* Collect all totals
*/
public function collect(
\Magento\Quote\Model\Quote $quote
): \Magento\Quote\Model\Quote {
$quote->getShippingAddress()->setCollectShippingRates(true);
foreach ($this->getCollectors() as $collector) {
$collector->collect($quote);
}
return $quote;
}
}
Available Collectors
// Configured intotals_collector.xml
Magento\Sales\Model\Quote\Address\Total\Collector
// Built-in collectors:
// - subtotal: Product prices × qty
// - discount: Cart price rule discounts
// - shipping: Shipping cost
// - tax: Tax calculations
// - grand_total: Sum of all totals
Custom Total Collector
namespace Vendor\Tax\Model\Quote\Address\Total;
class CustomFee implements \Magento\Quote\Api\Data\TotalInterface
{
public function collect(
\Magento\Quote\Model\Quote $quote,
\Magento\Quote\Model\Quote\Address\Total\Collector $totalCollector
): self {
$customFee = $this->calculateCustomFee($quote);
$total->setCustomFee($customFee);
$total->setBaseCustomFee($customFee);
$quote->getShippingAddress()->addTotal([
'code' => 'custom_fee',
'title' => 'Custom Fee',
'value' => $customFee,
]);
return $this;
}
public function fetch(
\Magento\Quote\Model\Quote $quote,
\Magento\Quote\Model\Quote\Address\Total $total
): self {
$total->addTotal(['code' => 'custom_fee', 'value' => $total->getCustomFee()]);
return $this;
}
}
Tax Calculation
Tax Calculation Process
// Magento\Tax\Model\Calculation
namespace Magento\Tax\Model;
class Calculation
{
public function __construct(
private \Magento\Tax\Model\Calculation\Rate $rate,
private \Magento\Tax\Model\Calculation\Rule $rule
) {}
/**
* Get tax rate for address and customer tax class
*/
public function getRate(
\Magento\Tax\Model\Calculation\Request $request
): float {
$taxRate = $this->rate->loadByCustomerTaxClassAndPostcode(
$request->getCustomerTaxClassId(),
$request->getProductTaxClassId(),
$request->getCountryId(),
$request->getRegionId(),
$request->getPostcode()
);
return $taxRate->getRate();
}
}
Tax Configuration
<!-- app/code/Vendor/Tax/etc/config.xml -->
<config>
<default>
<tax>
<catalog/prices>1</catalog/prices>
<sales/display/price>1</sales/display/price>
<sales/display/shipping>1</sales/display/shipping>
<sales/display/subtotal>1</sales/display/subtotal>
</tax>
</default>
</config>
Tax Classes
// Product Tax Class
$product->setTaxClassId(2); // Taxable Goods
// Customer Tax Class
$customer->setTaxClassId(3); // Retail Customer
// Tax rules combine product + customer classes with rates
Discount Calculation
Discount Processing
// Magento\Quote\Model\Quote\Address\Total\Discount
namespace Magento\Quote\Model\Quote\Address\Total;
class Discount
{
public function collect(
\Magento\Quote\Model\Quote $quote,
\Magento\Quote\Model\Quote\Address\Total\Collector $total
): self {
$discountAmount = 0;
foreach ($quote->getItems() as $item) {
$discountAmount += $item->getDiscountAmount();
}
$shippingDiscount = $quote->getShippingAddress()->getDiscountAmount();
$discountAmount += $shippingDiscount;
$total->setDiscountAmount($discountAmount);
$total->setBaseDiscountAmount($discountAmount);
$quote->getShippingAddress()->addTotal([
'code' => 'discount',
'title' => 'Discount',
'value' => -$discountAmount,
]);
return $this;
}
}
Applying Cart Price Rules
// Cart price rules are applied during totals collection
$rule = $this->ruleFactory->create()->load($ruleId);
// Check if rule applies to quote
if ($rule->validate($quote)) {
// Calculate discount
$discount = $rule->calcDiscount($quote->getSubtotal());
// Apply to quote items
foreach ($quote->getItems() as $item) {
$item->setDiscountAmount($discount);
$item->setBaseDiscountAmount($discount);
}
}
Grand Total Computation
Grand Total Calculation Flow
// Totals are collected in this order:
// 1. Subtotal (items × qty × price)
// 2. Discounts (cart price rules)
// 3. Shipping
// 4. Tax
// 5. Grand Total (sum of all)
// Example totals structure:
$totals = [
'subtotal' => [
'code' => 'subtotal',
'title' => 'Subtotal',
'value' => 100.00,
],
'discount' => [
'code' => 'discount',
'title' => 'Discount (10%)',
'value' => -10.00,
],
'shipping' => [
'code' => 'shipping',
'title' => 'Shipping',
'value' => 5.99,
],
'tax' => [
'code' => 'tax',
'title' => 'Tax (8%)',
'value' => 7.68,
],
'grand_total' => [
'code' => 'grand_total',
'title' => 'Grand Total',
'value' => 103.67,
],
];
Reading Totals
$quote = $this->cartRepository->get($cartId);
// Get specific totals
$subtotal = $quote->getShippingAddress()->getSubtotal();
$discount = $quote->getShippingAddress()->getDiscountAmount();
$tax = $quote->getShippingAddress()->getTaxAmount();
$grandTotal = $quote->getShippingAddress()->getGrandTotal();
// Get all totals
$allTotals = $quote->getShippingAddress()->getTotals();
foreach ($allTotals as $total) {
echo $total->getCode() . ': ' . $total->getValue();
}
Modifying Totals
// Add custom total
$quote->getShippingAddress()->addTotal([
'code' => 'custom_fee',
'title' => 'Handling Fee',
'value' => 2.50,
'area' => 'shipping',
]);
// Recalculate grand total
$quote->getShippingAddress()->collectTotals();
Quiz
1. What is the order of totals collection?
2. How are taxes calculated?
3. What does the totals collector pattern provide?
Flashcards
Question
What is TotalsCollector?
Click to reveal answer
Answer
System that iterates through total collectors to calculate subtotal, discounts, shipping, tax, and grand total
Question
Tax calculation inputs?
Click to reveal answer
Answer
Product tax class + customer tax class + destination (country/region/postcode)
Question
Discount sources?
Click to reveal answer
Answer
Cart price rules applied during totals collection to items and shipping
Question
Grand total formula?
Click to reveal answer
Answer
Subtotal - Discounts + Shipping + Tax = Grand Total
Revision Notes
Key Takeaways
- 1. TotalsCollector iterates through pluggable collector classes
- 2. Each collector handles one total type: subtotal, discount, shipping, tax, grand total
- 3. Tax calculation uses product class, customer class, and destination
- 4. Discounts come from cart price rules applied during collection
- 5. Custom totals can be added by implementing TotalInterface
Interview Tips
- • Explain the collector pattern and why it's used for totals
- • Describe how tax rates are determined from product/customer classes and geography
- • Discuss adding custom fees by implementing a new total collector
Cheat Sheet
Totals Flow:
Subtotal → Discounts → Shipping → Tax → Grand Total
Collector Pattern:
Each total type has its own collector class
implement TotalInterface: collect() + fetch()
Tax Calculation:
Product Tax Class + Customer Tax Class + Location = Tax Rate
Custom Totals:
Implement TotalInterface, register in totals_collector.xml