Shopware 6.7 introduces exciting capabilities for developers looking to create sophisticated B2B storefront extensions. This comprehensive guide will walk you through the technical implementation of a custom B2B extension that enhances the shopping experience for business customers while maintaining seamless integration with existing Shopware architecture.
Understanding B2B Requirements
Before diving into implementation, it's crucial to understand the core B2B requirements that differentiate business commerce from standard retail. These include multi-level pricing, order approval workflows, account management, and custom user roles. Shopware 6.7's modular architecture provides excellent foundation for implementing these features without compromising system performance.
Extension Structure and Setup
Creating a B2B extension in Shopware 6.7 begins with proper project structure. The extension should be created as a standard Shopware plugin following the naming convention your-company-b2b-extension. The main directory structure includes:
src/
├── Resources/
│ ├── config/
│ ├── framework/
│ └── snippet/
├── Core/
│ ├── Command/
│ ├── Content/
│ └── Framework/
└── Storefront/
├── Controller/
├── Page/
└── Subscriber/
Custom Route Registration
The foundation of any storefront extension lies in proper routing. In Shopware 6.7, we register custom routes using the routes.xml configuration file within the Resources/config directory:
<?xml version="1.0" encoding="UTF-8"?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="storefront.b2b.dashboard" path="/b2b/dashboard">
<default key="_controller">YourCompany\B2BExtension\Controller\DashboardController::indexAction</default>
</route>
<route id="storefront.b2b.order.approval" path="/b2b/approval/{orderId}">
<default key="_controller">YourCompany\B2BExtension\Controller\OrderApprovalController::approvalAction</default>
<requirement key="orderId">\d+</requirement>
</route>
</routes>
Custom Page Controller Implementation
The core functionality of our B2B extension resides in custom page controllers that extend Shopware's existing functionality. Here's a practical example of implementing a B2B dashboard controller:
<?php declare(strict_types=1);
namespace YourCompany\B2BExtension\Storefront\Controller;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[RouteScope(paths: ['storefront'])]
class DashboardController extends StorefrontController
{
#[Route(path: '/b2b/dashboard', name: 'storefront.b2b.dashboard', methods: ['GET'])]
public function indexAction(Request $request): Response
{
// Get current customer
$customer = $this->getCustomerFromRequest($request);
// Fetch B2B-specific data
$b2bData = $this->fetchB2BData($customer);
return $this->renderStorefront('@YourCompanyB2BExtension/storefront/page/dashboard.html.twig', [
'customer' => $customer,
'b2bData' => $b2bData
]);
}
}
Custom Page Loading Service
Creating a robust B2B extension requires implementing custom page loading services that fetch business-specific data. This service integrates with Shopware's existing repository system:
<?php declare(strict_types=1);
namespace YourCompany\B2BExtension\Core\Service;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
class B2BDashboardService
{
private EntityRepositoryInterface $orderRepository;
private EntityRepositoryInterface $customerGroupRepository;
public function __construct(
EntityRepositoryInterface $orderRepository,
EntityRepositoryInterface $customerGroupRepository
) {
$this->orderRepository = $orderRepository;
$this->customerGroupRepository = $customerGroupRepository;
}
public function getDashboardData(string $customerId): array
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderId', $customerId));
$criteria->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
$criteria->setLimit(10);
$orders = $this->orderRepository->search($criteria, Context::createDefaultContext());
return [
'recentOrders' => $orders->getEntities(),
'totalSpent' => $this->calculateTotalSpent($orders),
'pendingApprovals' => $this->getPendingApprovals($customerId)
];
}
private function calculateTotalSpent(SearchResult $orders): float
{
$total = 0;
foreach ($orders as $order) {
$total += $order->getPrice()->getTotalPrice();
}
return $total;
}
}
Template Integration and Twig Extensions
Shopware 6.7's template system allows for rich B2B features through custom Twig extensions. The storefront templates need to be enhanced with business-specific logic:
{# @YourCompanyB2BExtension/storefront/page/dashboard.html.twig #}
{% extends '@Storefront/storefront/page/account/dashboard.html.twig' %}
{% block page_account_dashboard_content %}
<div class="b2b-dashboard">
{% if b2bData.pendingApprovals|length > 0 %}
<div class="alert alert-warning">
<h4>Pending Approvals</h4>
<ul>
{% for approval in b2bData.pendingApprovals %}
<li>
Order #{{ approval.orderNumber }} - {{ approval.amount }}
<a href="{{ path('storefront.b2b.order.approval', {orderId: approval.id}) }}">
Review
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="dashboard-stats">
<h3>Recent Orders</h3>
{% for order in b2bData.recentOrders %}
<div class="order-card">
<span>{{ order.orderNumber }}</span>
<span>{{ order.price.totalPrice|format_currency }}</span>
<span>{{ order.createdAt|date('Y-m-d') }}</span>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
Custom User Role Management
B2B extensions often require custom user roles and permissions. Shopware 6.7 provides robust access control through the acl system:
<?php declare(strict_types=1);
namespace YourCompany\B2BExtension\Core\Acl;
use Shopware\Core\Framework\DataAbstractionLayer\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityIdTrait;
use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
class B2BCustomerRoleDefinition extends Entity
{
use EntityIdTrait;
protected string $customerId;
protected string $roleName;
protected array $permissions;
public function getCustomerId(): string
{
return $this->customerId;
}
public function setCustomerId(string $customerId): void
{
$this->customerId = $customerId;
}
public function getRoleName(): string
{
return $this->roleName;
}
public function setRoleName(string $roleName): void
{
$this->roleName = $roleName;
}
public function getPermissions(): array
{
return $this->permissions;
}
public function setPermissions(array $permissions): void
{
$this->permissions = $permissions;
}
}
Performance Optimization Considerations
When building B2B extensions, performance becomes critical due to the increased data complexity. Shopware 6.7's caching system should be leveraged extensively:
<?php declare(strict_types=1);
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
class OptimizedB2BService
{
private TagAwareCacheInterface $cache;
public function __construct(TagAwareCacheInterface $cache)
{
$this->cache = $cache;
}
public function getCustomerDashboardData(string $customerId): array
{
$cacheKey = 'b2b_dashboard_' . $customerId;
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($customerId) {
$item->tag(['customer-dashboard', 'customer-' . $customerId]);
$item->expiresAfter(3600); // 1 hour
return $this->fetchDashboardDataFromDatabase($customerId);
});
}
}
Integration with Existing Shopware Features
A successful B2B extension must integrate seamlessly with existing Shopware features. This includes:
- Customer group management
- Order workflow customization
- Pricing rule integration
- Inventory management enhancements
The key is using Shopware's event system and service decoration patterns to extend functionality without breaking core operations.
Testing and Quality Assurance
Shopware 6.7 provides excellent testing capabilities for B2B extensions through its built-in test framework:
<?php declare(strict_types=1);
namespace YourCompany\B2BExtension\Tests\Unit;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
class B2BDashboardServiceTest extends TestCase
{
use IntegrationTestBehaviour;
public function testDashboardDataRetrieval(): void
{
$service = $this->createContainer()->get(B2BDashboardService::class);
$result = $service->getDashboardData('test-customer-id');
$this->assertArrayHasKey('recentOrders', $result);
$this->assertArrayHasKey('totalSpent', $result);
}
}
Conclusion
Building B2B storefront extensions in Shopware 6.7 requires a deep understanding of the platform's architecture, but offers tremendous flexibility for creating business-specific commerce solutions. By leveraging proper routing, service implementation, template customization, and performance optimization techniques, developers can create robust extensions that enhance the shopping experience for business customers while maintaining system integrity.
The key to success lies in understanding Shopware 6.7's modular approach, utilizing its powerful caching mechanisms, and ensuring seamless integration with existing commerce workflows. This foundation provides a scalable platform for building sophisticated B2B features that meet enterprise requirements while maintaining optimal performance.