Skip to content
advanced Phase 90 · Shipping Deep Dive

Shipping Methods Deep Dive

Shipping methods deep dive - carrier configuration, rate calculation, handling fees

45m
0 problems
Topic Progress 0%

Carrier Configuration

Carrier Configuration Structure

<!-- app/code/Vendor/Shipping/etc/system.xml -->
<config>
    <section id="carriers" translate="label" sortOrder="320">
        <group id="custom_rate" translate="label" type="text" sortOrder="100">
            <label>Custom Rate Shipping</label>
            <field id="active" translate="label" type="select" sortOrder="1">
                <label>Enabled</label>
                <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
            </field>
            <field id="title" translate="label" type="text" sortOrder="10">
                <label>Title</label>
            </field>
            <field id="name" translate="label" type="text" sortOrder="20">
                <label>Method Name</label>
            </field>
            <field id="price" translate="label" type="text" sortOrder="30">
                <label>Default Price</label>
                <validate>validate-number validate-zero-or-greater</validate>
            </field>
            <field id="handling_type" translate="label" type="select" sortOrder="40">
                <label>Calculate Handling Fee</label>
                <source_model>Magento\Shipping\Model\Config\Source\Handling\Type</source_model>
            </field>
            <field id="handling_fee" translate="label" type="text" sortOrder="50">
                <label>Handling Fee</label>
            </field>
            <field id="sallowspecific" translate="label" type="select" sortOrder="60">
                <label>Ship to Applicable Countries</label>
                <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model>
            </field>
            <field id="specificcountry" translate="label" type="multiselect" sortOrder="70">
                <label>Ship to Specific Countries</label>
                <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
            </field>
        </group>
    </section>
</config>

Carrier Model

namespace Vendor\Shipping\Model\Carrier;

class CustomRate extends \Magento\Shipping\Model\Carrier\AbstractCarrier
{
    protected $_code = 'custom_rate';
    protected $_isFixed = false;

    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Quote\Model\Quote\Address\RateResult\Factory $rateFactory,
        \Magento\Shipping\Model\Rate\Result\Factory $resultFactory,
        array $data = []
    ) {
        $this->rateFactory = $rateFactory;
        $this->resultFactory = $resultFactory;
        parent::__construct($scopeConfig, $data);
    }

    public function collectRates(\Magento\Shipping\Model\Request\Data $request)
    {
        $result = $this->resultFactory->create();

        if (!$this->getConfigData('active')) {
            return $result;
        }

        $rate = $this->rateFactory->create();
        $rate->setCarrier($this->_code);
        $rate->setMethod($this->_code . '_flat');
        $rate->setCarrierTitle($this->getConfigData('title'));
        $rate->setMethodTitle('Flat Rate');
        $rate->setPrice($this->getConfigData('price'));
        $rate->setCost(0);

        $result->append($rate);

        return $result;
    }
}

Rate Calculation Logic

Complex Rate Calculation

namespace Vendor\Shipping\Model\Carrier;

class WeightBasedRate extends \Magento\Shipping\Model\Carrier\AbstractCarrier
{
    protected $_code = 'weight_rate';

    public function collectRates(\Magento\Shipping\Model\Request\Data $request)
    {
        $result = $this->resultFactory->create();

        $totalWeight = $request->getPackageWeight();
        $destCountry = $request->getDestCountryId();
        $destZip = $request->getDestPostcode();

        // Rate tiers
        $rates = [
            ['min' => 0, 'max' => 1, 'price' => 5.00],
            ['min' => 1, 'max' => 5, 'price' => 10.00],
            ['min' => 5, 'max' => 10, 'price' => 15.00],
            ['min' => 10, 'max' => 50, 'price' => 25.00],
        ];

        foreach ($rates as $tier) {
            if ($totalWeight >= $tier['min'] && $totalWeight < $tier['max']) {
                $price = $tier['price'];

                // Zone-based adjustment
                if ($destCountry === 'US') {
                    $price *= 1.0;
                } elseif ($destCountry === 'CA') {
                    $price *= 1.5;
                } else {
                    $price *= 2.0;
                }

                // Handling fee
                $handlingFee = (float) $this->getConfigData('handling_fee');
                $handlingType = $this->getConfigData('handling_type');
                if ($handlingType === 'F') {
                    $price += $handlingFee;
                } else {
                    $price += ($price * $handlingFee / 100);
                }

                $rate = $this->rateFactory->create();
                $rate->setCarrier($this->_code);
                $rate->setMethod($this->_code . '_weight');
                $rate->setCarrierTitle($this->getTitle());
                $rate->setMethodTitle('Weight Based');
                $rate->setPrice($price);
                $rate->setCost($price * 0.5);

                $result->append($rate);
            }
        }

        return $result;
    }
}

Handling Fees

Handling Fee Calculator

namespace Vendor\Shipping\Fee;

class HandlingFeeCalculator
{
    public function calculate(
        float $basePrice,
        string $feeType,
        float $feeValue,
        array $conditions = []
    ): float {
        $fee = 0;

        if ($feeType === 'F') {
            // Fixed fee
            $fee = $feeValue;
        } elseif ($feeType === 'P') {
            // Percentage fee
            $fee = $basePrice * ($feeValue / 100);
        }

        // Apply conditions
        if (!empty($conditions['min_order'])) {
            $minOrder = $conditions['min_order'];
            if ($basePrice >= $minOrder) {
                $fee = 0; // Free shipping above threshold
            }
        }

        return round($fee, 2);
    }
}

Handling Fee Configuration

<field id="handling_type" translate="label" type="select" sortOrder="40">
    <label>Calculate Handling Fee</label>
    <source_model>Magento\Shipping\Model\Config\Source\Handling\Type</source_model>
</field>
<field id="handling_fee" translate="label" type="text" sortOrder="50">
    <label>Handling Fee</label>
    <validate>validate-number validate-zero-or-greater</validate>
</field>

Free Shipping Rules

namespace Vendor\Shipping\Free;

class FreeShippingRule
{
    public function isEligible(
        CartInterface $cart,
        array $config
    ): bool {
        $minOrderAmount = $config['min_order_amount'] ?? 0;
        $freeMethods = $config['free_methods'] ?? [];

        if ($cart->getGrandTotal() >= $minOrderAmount) {
            return true;
        }

        return false;
    }
}

Shipping Method Rules

Rule-Based Method Selection

namespace Vendor\Shipping\Rule;

interface ShippingRuleInterface
{
    public function matches(CartInterface $cart): bool;
    public function getMethods(): array;
}

class WeightRule implements ShippingRuleInterface
{
    private float $minWeight;
    private float $maxWeight;
    private array $methods;

    public function __construct(float $min, float $max, array $methods)
    {
        $this->minWeight = $min;
        $this->maxWeight = $max;
        $this->methods = $methods;
    }

    public function matches(CartInterface $cart): bool
    {
        return $cart->getWeight() >= $this->minWeight
            && $cart->getWeight() < $this->maxWeight;
    }

    public function getMethods(): array
    {
        return $this->methods;
    }
}

class CountryRule implements ShippingRuleInterface
{
    private array $allowedCountries;
    private array $methods;

    public function matches(CartInterface $cart): bool
    {
        return in_array($cart->getShippingAddress()->getCountryId(), $this->allowedCountries);
    }

    public function getMethods(): array
    {
        return $this->methods;
    }
}

Method Availability Checker

namespace Vendor\Shipping\Rule;

class MethodAvailability
{
    private array $rules;

    public function getAvailableMethods(CartInterface $cart): array
    {
        $available = [];

        foreach ($this->rules as $rule) {
            if ($rule->matches($cart)) {
                $available = array_merge($available, $rule->getMethods());
            }
        }

        return array_unique($available);
    }
}

Quiz

1. What does handling_type 'F' mean?

Question 1 options

2. How do you restrict shipping to specific countries?

Question 2 options

3. What is zone-based rate adjustment?

Question 3 options

Flashcards

Question

What does collectRates() return?

Answer

Rate\Result object with available shipping methods

Question

What is handling_type 'F' vs 'P'?

Answer

F = fixed amount, P = percentage of price

Question

How to restrict shipping countries?

Answer

sallowspecific + specificcountry in system.xml

Question

What are shipping rules?

Answer

Conditions that determine available shipping methods

Revision Notes

Key Takeaways

  • 1. Carrier configuration in system.xml defines shipping settings
  • 2. Rate calculation can use weight, price, or custom logic
  • 3. Handling fees can be fixed or percentage-based
  • 4. Country restrictions limit where carriers are available
  • 5. Rule-based method selection enables complex shipping logic

Interview Tips

  • Explain the rate calculation flow in collectRates()
  • Discuss handling fee calculation types
  • Describe country restriction configuration
  • Talk about designing complex shipping rules

Cheat Sheet

Shipping Methods:
  carrier_code → unique identifier
  method_code → specific service
  collectRates() → returns Rate\Result

Rate Calculation:
  Weight-based → tiers by weight
  Price-based → tiers by cart total
  Zone-based → adjust by destination

Handling Fees:
  Type F → fixed amount
  Type P → percentage

Country Restriction:
  sallowspecific=1
  specificcountry=US,CA,GB