Magento B2B is a separate module suite for business-to-business selling. Company accounts, Shared Catalogs with customer-specific pricing, Negotiable Quotes, and Requisition Lists are the core features. I show the architecture, how each feature connects to the core Magento data model, and how to extend them with custom plugins.
B2B module architecture
| Module | Feature | Key tables |
|---|---|---|
| Company | Company accounts, user hierarchy, credit | company, company_advanced_customer_entity |
| SharedCatalog | Customer group pricing and product visibility | shared_catalog, shared_catalog_product_item |
| NegotiableQuote | Quote management with custom pricing | negotiable_quote, negotiable_quote_item |
| QuickOrder | Order by SKU list | Uses standard quote |
| RequisitionList | Saved shopping lists | purchase_order_requisition_list |
| PurchaseOrder | Internal approval workflows | purchase_order, purchase_order_item |
Company – the foundation of B2B
<?php
declare(strict_types=1);
use Magento\Company\Api\CompanyRepositoryInterface;
use Magento\Company\Api\Data\CompanyInterface;
class CompanyService
{
public function __construct(
private CompanyRepositoryInterface $companyRepository,
private \Magento\Company\Api\CompanyManagementInterface $companyManagement,
private \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
) {}
public function getCompanyByCustomer(int $customerId): ?CompanyInterface
{
// Each customer can belong to one company
return $this->companyManagement->getByCustomerId($customerId);
}
public function getCreditLimit(int $customerId): float
{
$company = $this->getCompanyByCustomer($customerId);
if ($company === null) return 0.0;
$credit = $company->getExtensionAttributes()?->getCompanyCredit();
return (float) ($credit?->getCreditLimit() ?? 0.0);
}
public function getCompaniesWithLowCredit(float $threshold): array
{
$sc = $this->searchCriteriaBuilder->create();
$companies = $this->companyRepository->getList($sc)->getItems();
return array_filter($companies, function($company) use ($threshold) {
$credit = $company->getExtensionAttributes()?->getCompanyCredit();
$available = ($credit?->getCreditLimit() ?? 0) - ($credit?->getBalance() ?? 0);
return $available < $threshold;
});
}
}
Shared Catalog - customer-specific pricing and visibility
<?php
declare(strict_types=1);
use Magento\SharedCatalog\Api\SharedCatalogRepositoryInterface;
use Magento\SharedCatalog\Api\ProductManagementInterface;
use Magento\SharedCatalog\Api\PriceManagementInterface;
class SharedCatalogService
{
public function __construct(
private SharedCatalogRepositoryInterface $sharedCatalogRepository,
private ProductManagementInterface $productManagement,
private PriceManagementInterface $priceManagement,
private \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
) {}
// Assign products to a shared catalog
public function assignProducts(int $catalogId, array $skus): void
{
$catalog = $this->sharedCatalogRepository->get($catalogId);
$products = array_map(fn($sku) => $this->buildProductPayload($sku), $skus);
$this->productManagement->assignProducts($catalog, $products);
}
// Set custom tier prices for a shared catalog
public function setCustomPrices(int $catalogId, array $priceData): void
{
// priceData: [['sku' => 'SKU-001', 'price' => 29.99], ...]
$catalog = $this->sharedCatalogRepository->get($catalogId);
$tierPrices = array_map(fn($item) => [
'price_type' => 'fixed',
'website_id' => 0,
'sku' => $item['sku'],
'qty' => 1,
'value' => $item['price'],
'customer_group_id' => $catalog->getCustomerGroupId(),
], $priceData);
$this->priceManagement->saveStore($catalog, $tierPrices, []);
}
}
Negotiable Quote - B2B pricing workflow
<?php
declare(strict_types=1);
use Magento\NegotiableQuote\Api\NegotiableQuoteRepositoryInterface;
use Magento\NegotiableQuote\Api\NegotiableQuoteManagementInterface;
class NegotiableQuoteService
{
public function __construct(
private NegotiableQuoteRepositoryInterface $quoteRepository,
private NegotiableQuoteManagementInterface $quoteManagement
) {}
// Apply custom discount to a negotiable quote
public function applyDiscount(int $quoteId, float $discountPercent, string $note): void
{
$quote = $this->quoteRepository->getById($quoteId);
$negotiableQuote = $quote->getExtensionAttributes()?->getNegotiableQuote();
if ($negotiableQuote === null) {
throw new \RuntimeException("Quote {$quoteId} is not negotiable");
}
// Set discount
$negotiableQuote->setNegotiatedPriceType(
\Magento\NegotiableQuote\Api\Data\NegotiableQuoteInterface::NEGOTIATED_PRICE_TYPE_PERCENTAGE_DISCOUNT
);
$negotiableQuote->setNegotiatedPriceValue($discountPercent);
$negotiableQuote->setComments($note);
$negotiableQuote->setStatus(
\Magento\NegotiableQuote\Api\Data\NegotiableQuoteInterface::STATUS_SUBMITTED_BY_ADMIN
);
$this->quoteManagement->saveByCustomer($quote, $negotiableQuote);
}
}
Custom plugin - block order if over credit limit
<?php
declare(strict_types=1);
namespace Vendor\B2bExtension\Plugin;
class CreditLimitOrderPlugin
{
public function __construct(
private \Magento\Company\Api\CompanyManagementInterface $companyManagement,
private \Psr\Log\LoggerInterface $logger
) {}
public function beforePlace(
\Magento\Sales\Api\OrderManagementInterface $subject,
\Magento\Sales\Api\Data\OrderInterface $order
): array {
$customerId = (int) $order->getCustomerId();
if ($customerId === 0) return [$order]; // guest checkout
$company = $this->companyManagement->getByCustomerId($customerId);
if ($company === null) return [$order]; // not a B2B customer
$credit = $company->getExtensionAttributes()?->getCompanyCredit();
$limit = (float) ($credit?->getCreditLimit() ?? PHP_FLOAT_MAX);
$balance = (float) ($credit?->getBalance() ?? 0.0);
$available = $limit - $balance;
if ($order->getGrandTotal() > $available) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Order total exceeds your available credit limit of %1.', number_format($available, 2))
);
}
return [$order];
}
}
Summary
Magento B2B is a powerful but complex suite. Company accounts create the organisational structure; Shared Catalogs control what each company sees and at what price; Negotiable Quotes enable the sales rep workflow. Custom extensions via plugins follow the same patterns as regular Magento development - the B2B interfaces are well-defined and injectable. The credit limit plugin example shows how to intercept order placement to enforce B2B-specific business rules.
