Introduction

Shopware 6.7 introduces significant enhancements to its commerce platform, particularly in the area of shipping method customization and rate calculation. As e-commerce businesses continue to grow in complexity, the ability to implement custom shipping logic becomes crucial for maintaining competitive advantage. This technical deep dive explores how to create a custom shipping method with sophisticated rate calculation capabilities in Shopware 6.7.

Understanding Shopware 6.7 Shipping Architecture

Before diving into implementation details, it's essential to understand the core architecture of Shopware 6.7's shipping system. The platform utilizes a robust event-driven architecture where shipping methods are registered as services and can be extended through various hooks and events.

The shipping calculation process in Shopware 6.7 follows these key steps:

  1. Cart validation and preparation
  2. Shipping method selection
  3. Rate calculation based on configured rules
  4. Tax calculation integration
  5. Final shipping cost application

Setting Up the Custom Shipping Method

Service Registration

To implement a custom shipping method, we first need to register our service in the dependency injection container. Create a new service class that extends the core shipping functionality:

<?php declare(strict_types=1);

namespace MyCompany\CustomShipping\Service;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryInformation;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection;
use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodTranslation\ShippingMethodTranslationDefinition;
use Shopware\Core\Checkout\Shipping\ShippingMethodEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

class CustomShippingService
{
    public function calculateCustomRate(
        Cart $cart,
        ShippingMethodEntity $shippingMethod,
        SalesChannelContext $context
    ): ?CalculatedPrice {
        // Implementation details will follow
    }
}

Plugin Structure

Create the necessary plugin structure in your custom plugin directory:

# config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/System/SystemConfig/Schema/config.xsd">
    
    <elements>
        <element name="customShippingBaseFee" type="number">
            <label>Base Fee</label>
            <helpText>Base shipping fee for all orders</helpText>
        </element>
        
        <element name="customShippingWeightRate" type="number">
            <label>Weight Rate</label>
            <helpText>Additional fee per kilogram</helpText>
        </element>
    </elements>
</config>

Advanced Rate Calculation Logic

Weight-Based Pricing Implementation

The core of our custom shipping method lies in the rate calculation logic. Here's how we implement sophisticated weight-based pricing:

public function calculateCustomRate(
    Cart $cart,
    ShippingMethodEntity $shippingMethod,
    SalesChannelContext $context
): ?CalculatedPrice {
    $cartItems = $cart->getLineItems();
    $totalWeight = 0;
    
    // Calculate total weight from cart items
    foreach ($cartItems as $item) {
        if ($item->getPayloadValue('weight')) {
            $totalWeight += $item->getQuantity() * $item->getPayloadValue('weight');
        }
    }
    
    // Retrieve configuration values
    $baseFee = (float)$this->systemConfigService->get('MyCompanyCustomShipping.config.customShippingBaseFee');
    $weightRate = (float)$this->systemConfigService->get('MyCompanyCustomShipping.config.customShippingWeightRate');
    
    // Apply tiered pricing based on weight
    $shippingCost = $baseFee;
    if ($totalWeight > 0) {
        $shippingCost += $totalWeight * $weightRate;
    }
    
    // Apply dynamic adjustments based on delivery zone
    $deliveryZone = $this->getDeliveryZone($context);
    $shippingCost = $this->applyZoneAdjustments($shippingCost, $deliveryZone);
    
    // Apply tax calculation
    $taxRate = $context->getTaxRules()->getTaxRateForPriceIncl($shippingMethod->getTaxId());
    
    return new CalculatedPrice(
        $shippingCost,
        $shippingCost,
        new PriceCollection(),
        $this->calculateTax($shippingCost, $taxRate),
        1
    );
}

Complex Pricing Rules

Shopware 6.7 supports advanced rule-based pricing through its RuleBuilder system. We can implement complex pricing logic that considers multiple factors:

private function applyAdvancedPricingRules(
    float $basePrice,
    Cart $cart,
    SalesChannelContext $context
): float {
    $rules = [
        'customer_group' => $context->getCustomer()->getGroupId(),
        'order_amount' => $cart->getPrice()->getTotalPrice(),
        'shipping_country' => $context->getShippingLocation()->getCountry()->getId(),
        'product_categories' => $this->extractProductCategories($cart),
    ];
    
    // Evaluate rules and apply discounts
    foreach ($this->pricingRules as $rule) {
        if ($this->evaluateRule($rule, $rules)) {
            $basePrice = $this->applyRuleDiscount($basePrice, $rule);
        }
    }
    
    return $basePrice;
}

Integration with Cart Events

Event Subscriber Implementation

To ensure our custom shipping method integrates seamlessly with the checkout process, we need to implement event subscribers:

<?php declare(strict_types=1);

namespace MyCompany\CustomShipping\Event;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Event\BeforeCalculatePriceEvent;
use Shopware\Core\Framework\Context;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ShippingCalculationSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            BeforeCalculatePriceEvent::class => 'onBeforeCalculatePrice',
        ];
    }
    
    public function onBeforeCalculatePrice(BeforeCalculatePriceEvent $event): void
    {
        $cart = $event->getCart();
        $context = $event->getSalesChannelContext();
        
        // Custom logic to validate shipping eligibility
        if ($this->shouldApplyCustomShipping($cart, $context)) {
            $this->applyCustomShippingLogic($cart, $context);
        }
    }
    
    private function shouldApplyCustomShipping(Cart $cart, Context $context): bool
    {
        // Logic to determine when custom shipping should be applied
        return true;
    }
}

Database Integration and Configuration

Shipping Method Definition

To register our custom shipping method with Shopware's database:

// In your plugin's migration
public function update(Connection $connection): void
{
    $connection->insert('shipping_method', [
        'id' => Uuid::randomHex(),
        'name' => 'Custom Express Shipping',
        'description' => 'Advanced custom shipping with rate calculation',
        'active' => true,
        'position' => 10,
        'tax_id' => $this->getTaxId($connection),
        'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_FORMAT),
    ]);
}

Configuration Management

Shopware 6.7 provides robust configuration management through the SystemConfigService:

public function getShippingConfiguration(
    SalesChannelContext $context
): array {
    return [
        'base_fee' => $this->systemConfigService->get('MyCompanyCustomShipping.config.customShippingBaseFee', $context->getSalesChannelId()),
        'weight_rate' => $this->systemConfigService->get('MyCompanyCustomShipping.config.customShippingWeightRate', $context->getSalesChannelId()),
        'minimum_order_value' => $this->systemConfigService->get('MyCompanyCustomShipping.config.minimumOrderValue', $context->getSalesChannelId()),
        'maximum_weight' => $this->systemConfigService->get('MyCompanyCustomShipping.config.maximumWeight', $context->getSalesChannelId()),
    ];
}

Performance Optimization

Caching Strategies

Given that shipping calculations occur frequently during checkout, performance optimization is crucial:

private function calculateWithCaching(
    Cart $cart,
    ShippingMethodEntity $shippingMethod,
    SalesChannelContext $context
): ?CalculatedPrice {
    $cacheKey = $this->generateCacheKey($cart, $shippingMethod, $context);
    
    if ($this->cache->has($cacheKey)) {
        return $this->cache->get($cacheKey);
    }
    
    $result = $this->calculateRate($cart, $shippingMethod, $context);
    
    // Cache for 1 hour
    $this->cache->set($cacheKey, $result, 3600);
    
    return $result;
}

Asynchronous Processing

For complex calculations, consider implementing asynchronous processing:

public function calculateAsync(
    Cart $cart,
    ShippingMethodEntity $shippingMethod,
    SalesChannelContext $context
): PromiseInterface {
    return $this->asyncExecutor->execute(function() use ($cart, $shippingMethod, $context) {
        return $this->calculateRate($cart, $shippingMethod, $context);
    });
}

Testing and Validation

Unit Testing

Comprehensive testing ensures our custom shipping method works correctly:

public function testWeightBasedCalculation(): void
{
    $cart = $this->createMockCart();
    $shippingMethod = $this->createMockShippingMethod();
    $context = $this->createMockContext();
    
    // Test with 5kg weight
    $result = $this->customShippingService->calculateCustomRate($cart, $shippingMethod, $context);
    
    $this->assertInstanceOf(CalculatedPrice::class, $result);
    $this->assertEquals(15.0, $result->getTotalPrice());
}

Integration Testing

Testing within the full Shopware environment ensures compatibility:

public function testShippingMethodIntegration(): void
{
    $this->client->request('POST', '/api/v3/checkout/cart/line-item', [
        'data' => [
            'type' => 'line_item',
            'attributes' => [
                'referencedId' => $productId,
                'quantity' => 2,
                'payload' => ['weight' => 1.5]
            ]
        ]
    ]);
    
    $response = $this->client->request('GET', '/api/v3/checkout/cart');
    $cart = json_decode($response->getContent(), true);
    
    $this->assertArrayHasKey('deliveries', $cart['data']);
}

Conclusion

Implementing a custom shipping method with rate calculation in Shopware 6.7 requires understanding of the platform's architecture, event system, and configuration management. By leveraging the enhanced features introduced in version 6.7, developers can create sophisticated shipping solutions that adapt to business requirements while maintaining optimal performance.

The key advantages of this approach include:

  • Flexible configuration through the admin panel
  • Integration with existing Shopware rules and tax systems
  • Performance optimization through caching and asynchronous processing
  • Comprehensive testing capabilities for reliability

As e-commerce continues to evolve, custom shipping methods will remain essential for businesses seeking to optimize their logistics operations and provide competitive delivery options to customers. Shopware 6.7's enhanced capabilities make it easier than ever to implement these sophisticated solutions while maintaining the platform's robustness and scalability.