vendor/shopware/core/System/SalesChannel/Context/SalesChannelContextFactory.php line 87

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\SalesChannel\Context;
  3. use Shopware\Core\Checkout\Cart\Delivery\Struct\ShippingLocation;
  4. use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
  5. use Shopware\Core\Checkout\Cart\Tax\TaxDetector;
  6. use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
  7. use Shopware\Core\Checkout\Customer\CustomerEntity;
  8. use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
  9. use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
  10. use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;
  11. use Shopware\Core\Framework\Context;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Pricing\CashRoundingConfig;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  17. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  18. use Shopware\Core\System\Currency\Aggregate\CurrencyCountryRounding\CurrencyCountryRoundingEntity;
  19. use Shopware\Core\System\SalesChannel\BaseContext;
  20. use Shopware\Core\System\SalesChannel\Event\SalesChannelContextPermissionsChangedEvent;
  21. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  22. use Shopware\Core\System\Tax\Aggregate\TaxRule\TaxRuleCollection;
  23. use Shopware\Core\System\Tax\Aggregate\TaxRule\TaxRuleEntity;
  24. use Shopware\Core\System\Tax\TaxCollection;
  25. use Shopware\Core\System\Tax\TaxRuleType\TaxRuleTypeFilterInterface;
  26. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  27. use function array_unique;
  28. class SalesChannelContextFactory extends AbstractSalesChannelContextFactory
  29. {
  30.     private EntityRepositoryInterface $customerRepository;
  31.     private EntityRepositoryInterface $customerGroupRepository;
  32.     private EntityRepositoryInterface $addressRepository;
  33.     private EntityRepositoryInterface $paymentMethodRepository;
  34.     private TaxDetector $taxDetector;
  35.     /**
  36.      * @var iterable|TaxRuleTypeFilterInterface[]
  37.      */
  38.     private $taxRuleTypeFilter;
  39.     private EventDispatcherInterface $eventDispatcher;
  40.     private EntityRepositoryInterface $currencyCountryRepository;
  41.     private AbstractBaseContextFactory $baseContextFactory;
  42.     /**
  43.      * @internal
  44.      */
  45.     public function __construct(
  46.         EntityRepositoryInterface $customerRepository,
  47.         EntityRepositoryInterface $customerGroupRepository,
  48.         EntityRepositoryInterface $addressRepository,
  49.         EntityRepositoryInterface $paymentMethodRepository,
  50.         TaxDetector $taxDetector,
  51.         iterable $taxRuleTypeFilter,
  52.         EventDispatcherInterface $eventDispatcher,
  53.         EntityRepositoryInterface $currencyCountryRepository,
  54.         AbstractBaseContextFactory $baseContextFactory
  55.     ) {
  56.         $this->customerRepository = $customerRepository;
  57.         $this->customerGroupRepository = $customerGroupRepository;
  58.         $this->addressRepository = $addressRepository;
  59.         $this->paymentMethodRepository = $paymentMethodRepository;
  60.         $this->taxDetector = $taxDetector;
  61.         $this->taxRuleTypeFilter = $taxRuleTypeFilter;
  62.         $this->eventDispatcher = $eventDispatcher;
  63.         $this->currencyCountryRepository = $currencyCountryRepository;
  64.         $this->baseContextFactory = $baseContextFactory;
  65.     }
  66.     public function getDecorated(): AbstractSalesChannelContextFactory
  67.     {
  68.         throw new DecorationPatternException(self::class);
  69.     }
  70.     public function create(string $token, string $salesChannelId, array $options = []): SalesChannelContext
  71.     {
  72.         // we split the context generation to allow caching of the base context
  73.         $base = $this->baseContextFactory->create($salesChannelId, $options);
  74.         // customer
  75.         $customer = null;
  76.         if (\array_key_exists(SalesChannelContextService::CUSTOMER_ID, $options) && $options[SalesChannelContextService::CUSTOMER_ID] !== null) {
  77.             //load logged in customer and set active addresses
  78.             $customer = $this->loadCustomer($options, $base->getContext());
  79.         }
  80.         $shippingLocation = $base->getShippingLocation();
  81.         if ($customer) {
  82.             /** @var CustomerAddressEntity $activeShippingAddress */
  83.             $activeShippingAddress = $customer->getActiveShippingAddress();
  84.             $shippingLocation = ShippingLocation::createFromAddress($activeShippingAddress);
  85.         }
  86.         $customerGroup = $base->getCurrentCustomerGroup();
  87.         if ($customer) {
  88.             $criteria = new Criteria([$customer->getGroupId()]);
  89.             $criteria->setTitle('context-factory::customer-group');
  90.             $customerGroup = $this->customerGroupRepository->search($criteria, $base->getContext())->first() ?? $customerGroup;
  91.         }
  92.         //loads tax rules based on active customer and delivery address
  93.         $taxRules = $this->getTaxRules($base, $customer, $shippingLocation);
  94.         //detect active payment method, first check if checkout defined other payment method, otherwise validate if customer logged in, at least use shop default
  95.         $payment = $this->getPaymentMethod($options, $base, $customer);
  96.         [$itemRounding, $totalRounding] = $this->getCashRounding($base, $shippingLocation);
  97.         $context = new Context(
  98.             $base->getContext()->getSource(),
  99.             [],
  100.             $base->getCurrencyId(),
  101.             $base->getContext()->getLanguageIdChain(),
  102.             $base->getContext()->getVersionId(),
  103.             $base->getCurrency()->getFactor(),
  104.             true,
  105.             CartPrice::TAX_STATE_GROSS,
  106.             $itemRounding
  107.         );
  108.         $salesChannelContext = new SalesChannelContext(
  109.             $context,
  110.             $token,
  111.             $options[SalesChannelContextService::DOMAIN_ID] ?? null,
  112.             $base->getSalesChannel(),
  113.             $base->getCurrency(),
  114.             $customerGroup,
  115.             $base->getFallbackCustomerGroup(),
  116.             $taxRules,
  117.             $payment,
  118.             $base->getShippingMethod(),
  119.             $shippingLocation,
  120.             $customer,
  121.             $itemRounding,
  122.             $totalRounding,
  123.             []
  124.         );
  125.         if (\array_key_exists(SalesChannelContextService::PERMISSIONS, $options)) {
  126.             $salesChannelContext->setPermissions($options[SalesChannelContextService::PERMISSIONS]);
  127.             $event = new SalesChannelContextPermissionsChangedEvent($salesChannelContext, $options[SalesChannelContextService::PERMISSIONS]);
  128.             $this->eventDispatcher->dispatch($event);
  129.             $salesChannelContext->lockPermissions();
  130.         }
  131.         $salesChannelContext->setTaxState($this->taxDetector->getTaxState($salesChannelContext));
  132.         return $salesChannelContext;
  133.     }
  134.     private function getTaxRules(BaseContext $context, ?CustomerEntity $customer, ShippingLocation $shippingLocation): TaxCollection
  135.     {
  136.         $taxes = $context->getTaxRules()->getElements();
  137.         foreach ($taxes as $tax) {
  138.             $taxRules = $tax->getRules();
  139.             if ($taxRules === null) {
  140.                 continue;
  141.             }
  142.             $taxRules = $taxRules->filter(function (TaxRuleEntity $taxRule) use ($customer, $shippingLocation) {
  143.                 foreach ($this->taxRuleTypeFilter as $ruleTypeFilter) {
  144.                     if ($ruleTypeFilter->match($taxRule, $customer, $shippingLocation)) {
  145.                         return true;
  146.                     }
  147.                 }
  148.                 return false;
  149.             });
  150.             $taxRules->sortByTypePosition();
  151.             $taxRule = $taxRules->first();
  152.             $matchingRules = new TaxRuleCollection();
  153.             if ($taxRule) {
  154.                 $matchingRules->add($taxRule);
  155.             }
  156.             $tax->setRules($matchingRules);
  157.         }
  158.         return new TaxCollection($taxes);
  159.     }
  160.     private function getPaymentMethod(array $options, BaseContext $context, ?CustomerEntity $customer): PaymentMethodEntity
  161.     {
  162.         if ($customer === null || isset($options[SalesChannelContextService::PAYMENT_METHOD_ID])) {
  163.             return $context->getPaymentMethod();
  164.         }
  165.         $id = $customer->getLastPaymentMethodId() ?? $customer->getDefaultPaymentMethodId();
  166.         if ($id === $context->getPaymentMethod()->getId()) {
  167.             return $context->getPaymentMethod();
  168.         }
  169.         $criteria = new Criteria([$id]);
  170.         $criteria->addAssociation('media');
  171.         $criteria->setTitle('context-factory::payment-method');
  172.         $paymentMethod = $this->paymentMethodRepository->search($criteria, $context->getContext())->get($id);
  173.         if (!$paymentMethod) {
  174.             throw new UnknownPaymentMethodException($id);
  175.         }
  176.         return $paymentMethod;
  177.     }
  178.     private function loadCustomer(array $options, Context $context): ?CustomerEntity
  179.     {
  180.         $customerId = $options[SalesChannelContextService::CUSTOMER_ID];
  181.         $criteria = new Criteria([$customerId]);
  182.         $criteria->setTitle('context-factory::customer');
  183.         $criteria->addAssociation('salutation');
  184.         $criteria->addAssociation('defaultPaymentMethod');
  185.         /** @var SalesChannelApiSource $source */
  186.         $source = $context->getSource();
  187.         $criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR, [
  188.             new EqualsFilter('customer.boundSalesChannelId', null),
  189.             new EqualsFilter('customer.boundSalesChannelId', $source->getSalesChannelId()),
  190.         ]));
  191.         /** @var CustomerEntity|null $customer */
  192.         $customer = $this->customerRepository->search($criteria, $context)->get($customerId);
  193.         if (!$customer) {
  194.             return null;
  195.         }
  196.         $activeBillingAddressId = $options[SalesChannelContextService::BILLING_ADDRESS_ID] ?? $customer->getDefaultBillingAddressId();
  197.         $activeShippingAddressId = $options[SalesChannelContextService::SHIPPING_ADDRESS_ID] ?? $customer->getDefaultShippingAddressId();
  198.         $addressIds[] = $activeBillingAddressId;
  199.         $addressIds[] = $activeShippingAddressId;
  200.         $addressIds[] = $customer->getDefaultBillingAddressId();
  201.         $addressIds[] = $customer->getDefaultShippingAddressId();
  202.         $criteria = new Criteria(array_unique($addressIds));
  203.         $criteria->setTitle('context-factory::addresses');
  204.         $criteria->addAssociation('salutation');
  205.         $criteria->addAssociation('country');
  206.         $criteria->addAssociation('countryState');
  207.         $addresses = $this->addressRepository->search($criteria, $context);
  208.         $customer->setActiveBillingAddress($addresses->get($activeBillingAddressId));
  209.         $customer->setActiveShippingAddress($addresses->get($activeShippingAddressId));
  210.         $customer->setDefaultBillingAddress($addresses->get($customer->getDefaultBillingAddressId()));
  211.         $customer->setDefaultShippingAddress($addresses->get($customer->getDefaultShippingAddressId()));
  212.         return $customer;
  213.     }
  214.     /**
  215.      * @return CashRoundingConfig[]
  216.      */
  217.     private function getCashRounding(BaseContext $context, ShippingLocation $shippingLocation): array
  218.     {
  219.         if ($context->getShippingLocation()->getCountry()->getId() === $shippingLocation->getCountry()->getId()) {
  220.             return [$context->getItemRounding(), $context->getTotalRounding()];
  221.         }
  222.         $criteria = new Criteria();
  223.         $criteria->setTitle('context-factory::cash-rounding');
  224.         $criteria->setLimit(1);
  225.         $criteria->addFilter(new EqualsFilter('currencyId', $context->getCurrencyId()));
  226.         $criteria->addFilter(new EqualsFilter('countryId', $shippingLocation->getCountry()->getId()));
  227.         /** @var CurrencyCountryRoundingEntity|null $countryConfig */
  228.         $countryConfig = $this->currencyCountryRepository
  229.             ->search($criteria, $context->getContext())
  230.             ->first();
  231.         if ($countryConfig) {
  232.             return [$countryConfig->getItemRounding(), $countryConfig->getTotalRounding()];
  233.         }
  234.         return [$context->getCurrency()->getItemRounding(), $context->getCurrency()->getTotalRounding()];
  235.     }
  236. }