<?php declare(strict_types=1);
namespace FourtwosixThemeExtension\Controller\Hub;
use Exception;
use FourtwosixThemeExtension\Exception\CountryNotFoundException;
use FourtwosixThemeExtension\Exception\InvalidSalesChannelId;
use FourtwosixThemeExtension\Exception\NullCountryIsoException;
use FourtwosixThemeExtension\Exception\NullCustomerEmailException;
use FourtwosixThemeExtension\Exception\NullZipCodeException;
use FourtwosixThemeExtension\Service\VirtualCartService;
use Shopware\Core\Checkout\Cart\Delivery\Struct\ShippingLocation;
use Shopware\Core\Checkout\Cart\Order\OrderConversionContext;
use Shopware\Core\Checkout\Cart\Order\OrderConverter;
use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Defaults;
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\Uuid\Uuid;
use Shopware\Core\System\Country\CountryEntity;
use Shopware\Core\System\SalesChannel\Context\AbstractBaseContextFactory;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(defaults={"_routeScope"={"storefront"}})
*/
class GetPriceController extends StorefrontController
{
protected SystemConfigService $systemConfigService;
protected ?string $webhookSecret = null;
protected VirtualCartService $virtualCart;
protected AbstractBaseContextFactory $baseContextFactory;
protected EntityRepositoryInterface $countryRepository;
protected EntityRepositoryInterface $salesChannelRepository;
protected array $allowedSalesChannel;
protected OrderConverter $orderConverter;
public function __construct(
SystemConfigService $systemConfigService,
VirtualCartService $virtualCart,
AbstractBaseContextFactory $baseContextFactory,
EntityRepositoryInterface $countryRepository,
EntityRepositoryInterface $salesChannelRepository,
OrderConverter $orderConverter
) {
$this->systemConfigService = $systemConfigService;
$this->virtualCart = $virtualCart;
$this->baseContextFactory = $baseContextFactory;
$this->countryRepository = $countryRepository;
$this->salesChannelRepository = $salesChannelRepository;
$this->orderConverter = $orderConverter;
$this->allowedSalesChannel = $this->systemConfigService->get('FourtwosixThemeExtension.config.allowedSalesChannel');
$this->webhookSecret = $this->systemConfigService->get('FourtwosixThemeExtension.config.shopSecret');
}
/**
* @Route("/calculate-price", name="calculate.hub.price", defaults={"csrf_protected"=false}, methods={"POST"})
* @throws Exception
*/
public function calculatePrice(Request $request, SalesChannelContext $context): Response
{
$secretMatches = $request->get("secret") === $this->webhookSecret;
if (!$secretMatches) {
throw new HttpException(500, "Wrong secret");
}
try {
$payload = json_decode(
$request->getContent(),
true
);
$zipcode = $payload["customer"]["zipcode"] ?? null;
$countryIso = $payload["customer"]["country_iso"] ?? null;
$salesChannelId = $payload["customer"]["sales_channel_id"] ?? null;
$customerEmail = $payload["customer"]["email"] ?? null;
$this->validatePayloadData(
$customerEmail,
$zipcode,
$countryIso,
$salesChannelId,
);
// Also customer not registered in SW should be able to get discounts calculation and shipping
$customer = $this->virtualCart->getCustomer($customerEmail, $context->getContext());
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter("iso", $countryIso));
$country = $this->countryRepository->search($criteria, $context->getContext())->first();
if (is_null($country)) {
throw new CountryNotFoundException();
}
// Create a fake address where insert data passed via payload to create a new salesChannelContext
$customerAddress = $this->createFakeAddress($zipcode, $country);
$shippingLocation = new ShippingLocation($country, null, $customerAddress);
$salesChannelContext = $this->recreateSalesChannelContext(
$context,
$salesChannelId,
$shippingLocation,
$customer,
$country
);
$this->virtualCart->addLineItemsToCart($salesChannelContext, $payload["order_lines"]);
$conversionContext = (new OrderConversionContext())
->setIncludeCustomer(false)
->setIncludeBillingAddress(false)
->setIncludeDeliveries(true)
->setIncludeTransactions(false)
->setIncludeOrderDate(false);
$data = $this->orderConverter->convertToOrder(
$this->virtualCart->getCart($salesChannelContext),
$salesChannelContext,
$conversionContext
);
$this->virtualCart->destroy($salesChannelContext);
$success = true;
$response = $data;
} catch (Exception $e) {
$success = false;
$response = $e->getMessage();
}
return $this->json([
"success" => $success,
"response" => $response,
], $success ? 200 : 500);
}
/**
* @param string|null $customerEmail
* @param string|null $zipcode
* @param string|null $countryIso
* @param string|null $salesChannelId
* @return void
* @throws InvalidSalesChannelId
* @throws NullCountryIsoException
* @throws NullCustomerEmailException
* @throws NullZipCodeException
*/
public function validatePayloadData(
?string $customerEmail,
?string $zipcode,
?string $countryIso,
?string $salesChannelId
): void {
if (is_null($customerEmail)) {
throw new NullCustomerEmailException();
}
if (is_null($zipcode)) {
throw new NullZipCodeException();
}
if (is_null($countryIso)) {
throw new NullCountryIsoException();
}
if (is_null($salesChannelId) || !in_array($salesChannelId, $this->allowedSalesChannel)) {
throw new InvalidSalesChannelId();
}
}
/**
* @param $zipcode
* @param $country
* @return CustomerAddressEntity
*/
public function createFakeAddress($zipcode, $country): CustomerAddressEntity
{
$customerAddress = new CustomerAddressEntity();
$customerAddress->setId(Uuid::randomHex());
$customerAddress->setFirstName('Max');
$customerAddress->setLastName('Mustermann');
$customerAddress->setStreet('Musterstraße 1');
$customerAddress->setCity('Schöppingen');
$customerAddress->setZipcode($zipcode);
$customerAddress->setSalutationId(Defaults::SALUTATION);
$customerAddress->setCountryId($country->getId());
$customerAddress->setCountry($country);
return $customerAddress;
}
/**
* @param SalesChannelContext $context
* @param string $salesChannelId
* @param ShippingLocation $shippingLocation
* @param CustomerEntity|null $customer
* @param CountryEntity $country
* @return SalesChannelContext
* @throws Exception
*/
public function recreateSalesChannelContext(
SalesChannelContext $context,
string $salesChannelId,
ShippingLocation $shippingLocation,
?CustomerEntity $customer,
CountryEntity $country
): SalesChannelContext {
$base = $this->baseContextFactory->create($context->getSalesChannelId(), []);
// Getting existing saleschannel
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter("id", $salesChannelId));
$salesChannel = $this->salesChannelRepository->search($criteria, $context->getContext())->first();
$salesChannel = is_null($salesChannel) ? $base->getSalesChannel() : $salesChannel;
$shippingMethod = $this->virtualCart->getShipping($country, $context);
if(is_null($shippingMethod)){
throw new Exception("customer_invalid ssasd");
}
return new SalesChannelContext(
$context->getContext(),
$context->getToken(),
null,
$salesChannel,
$base->getCurrency(),
$base->getCurrentCustomerGroup(),
$base->getFallbackCustomerGroup(),
$context->getTaxRules(),
$context->getPaymentMethod(),
$shippingMethod,
$shippingLocation,
$customer,
$base->getItemRounding(),
$base->getTotalRounding(),
$context->getRuleIds()
);
}
}