<?php declare(strict_types=1);
namespace Acris\Tax\Storefront\Subscriber;
use Acris\Tax\Components\Service\VatIdValidationService;
use Acris\Tax\Core\Checkout\Customer\Validation\Constraint\CustomerVatId;
use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
use Shopware\Core\Checkout\Customer\Event\CustomerRegisterEvent;
use Shopware\Core\Checkout\Customer\Event\GuestCustomerRegisterEvent;
use Shopware\Core\Framework\Validation\BuildValidationEvent;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
use Shopware\Core\Framework\Validation\DataValidationDefinition;
use Shopware\Core\Framework\Validation\DataValidator;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\Country\CountryEntity;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SalesChannel\SalesChannelEntity;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class VatIdValidationSubscriber implements EventSubscriberInterface
{
public const CUSTOMER_LOGIN_CAUSE = 'customer-login';
public const PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTION_ALWAYS = 'always';
public const PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTION_IF_NOT_VALIDATED = 'ifNotValidated';
public const PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTIONS = [
self::PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTION_ALWAYS,
self::PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTION_IF_NOT_VALIDATED
];
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var VatIdValidationService
*/
private $vatIdValidationService;
/**
* @var array
*/
private $vatIds;
/**
* @var bool
*/
private $addressAllowed;
/**
* @var SalesChannelContextServiceInterface
*/
private $salesChannelContextService;
/**
* @var DataValidator
*/
private $validator;
private int $count;
public function __construct(SystemConfigService $systemConfigService, RequestStack $requestStack, VatIdValidationService $vatIdValidationService, SalesChannelContextServiceInterface $salesChannelContextService, DataValidator $validator)
{
$this->systemConfigService = $systemConfigService;
$this->requestStack = $requestStack;
$this->vatIdValidationService = $vatIdValidationService;
$this->salesChannelContextService = $salesChannelContextService;
$this->validator = $validator;
$this->vatIds = [];
$this->addressAllowed = false;
$this->count = 0;
}
public static function getSubscribedEvents(): array
{
return [
'framework.validation.customer.profile.update' => 'onValidateCustomerUpdate',
'framework.validation.customer.create' => 'onValidateCustomerCreate',
'framework.validation.address.create' => 'onValidateAddress',
'framework.validation.address.update' => 'onValidateAddress',
'framework.validation.order.update' => 'onValidateOrder',
'framework.validation.order.create' => 'onValidateOrder',
CustomerRegisterEvent::class => 'onCustomerRegister',
GuestCustomerRegisterEvent::class => 'onGuestCustomerRegister',
CustomerLoginEvent::class => 'onCustomerLogin'
];
}
public function onValidateCustomerUpdate(BuildValidationEvent $event): void
{
if(empty($this->requestStack) === true || empty($request = $this->requestStack->getCurrentRequest()) === true) {
return;
}
$salesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
$salesChannelId = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
if(!$salesChannelContext instanceof SalesChannelContext || empty($salesChannelId) === true || empty($request->get('vatIds')) === true || !is_array($request->get('vatIds'))) {
return;
}
$customer = $salesChannelContext->getCustomer();
if(!$customer instanceof CustomerEntity) {
return;
}
$billingAddress = $customer->getDefaultBillingAddress();
if (empty($billingAddress) === true) {
return;
}
$billingAddressArray = $this->convertAddress($billingAddress, $customer->getId(), $customer->getCompany(), $event->getData());
if (empty($billingAddressArray) === true || !array_key_exists('company', $billingAddressArray) || empty($billingAddressArray['company'])) {
return;
}
$this->vatIdValidationService->checkVatIdsForCustomer($request->get('vatIds'), $customer, $salesChannelContext);
$this->checkVatIdValidation($billingAddressArray, $event, $salesChannelId, $salesChannelContext, $request->get('vatIds'), 'customer-update', false, 'customerUpdate');
}
public function onValidateCustomerCreate(BuildValidationEvent $event): void
{
if(empty($this->requestStack) === true || empty($request = $this->requestStack->getCurrentRequest()) === true) {
return;
}
$salesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
$salesChannelId = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
if(!$salesChannelContext instanceof SalesChannelContext || empty($salesChannelId) === true || empty($request->get('vatIds')) === true || !is_array($request->get('vatIds'))) {
return;
}
$billingAddress = $request->get('billingAddress');
if (empty($billingAddress) === true) {
$billingAddress = $request->get('address');
}
if (empty($billingAddress) === true || !array_key_exists('company', $billingAddress) || empty($billingAddress['company'])) {
return;
}
$this->checkVatIdValidation($billingAddress, $event, $salesChannelId, $salesChannelContext, $request->get('vatIds'), 'customer-create', false, 'customerCreate');
}
public function onValidateAddress(BuildValidationEvent $event): void
{
$this->count++;
$currentAddress = 'billingAddress';
if(empty($this->requestStack) === true || empty($request = $this->requestStack->getCurrentRequest()) === true) {
return;
}
$salesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
$salesChannelId = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
if(!$salesChannelContext instanceof SalesChannelContext || empty($salesChannelId) === true) {
return;
}
//check address
$address = $request->get('shippingAddress');
if (!empty($address)) $currentAddress = 'shippingAddress';
if ($this->addressAllowed !== true) {
$this->addressAllowed = true;
if (!empty($address)) {
return;
}
}
if (!empty($address)) {
if (!$request->request->has('differentShippingAddress') || boolval($request->request->get('differentShippingAddress')) !== true) {
return;
}
}
if (empty($address) === true) {
$address = $request->get('address');
}
if (empty($address) === true || !array_key_exists('company', $address) || empty($address['company'])) {
return;
}
if (!empty($address) && array_key_exists('customFields', $address) && !empty($address['customFields'])) {
$customFields = $address['customFields'];
if (is_array($customFields) && array_key_exists('acris_address_vat_id', $customFields) && empty($customFields['acris_address_vat_id']) !== true) {
$addressVatId = $customFields['acris_address_vat_id'];
if (empty($addressVatId) || !is_string($addressVatId) || in_array($addressVatId, $this->vatIds)) return;
$this->vatIds[] = $addressVatId;
$this->checkVatIdValidation($address, $event, $salesChannelId, $salesChannelContext, [$addressVatId], 'address-save-or-update', false, $currentAddress);
}
}
}
public function onValidateOrder(BuildValidationEvent $event)
{
if(empty($this->requestStack) === true || empty($request = $this->requestStack->getCurrentRequest()) === true) {
return;
}
$salesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
$salesChannelId = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
if(!$salesChannelContext instanceof SalesChannelContext || empty($salesChannelId) === true) {
return;
}
if (!$this->systemConfigService->get('AcrisTaxCS.config.validateVatIDOnOrder', $salesChannelId)) {
return;
}
// validate personal data and billing address
if(empty($salesChannelContext->getCustomer()) !== true && !empty($salesChannelContext->getCustomer()->getDefaultBillingAddress())) {
$billingAddressVatId = !empty($salesChannelContext->getCustomer() && !empty($salesChannelContext->getCustomer()->getDefaultBillingAddress())) ? $this->getBillingAddressVatId($salesChannelContext->getCustomer()->getDefaultBillingAddress()) : null;
$billingAddressArray = $this->convertAddress($salesChannelContext->getCustomer()->getDefaultBillingAddress(), $salesChannelContext->getCustomer()->getId(), $salesChannelContext->getCustomer()->getCompany());
if (empty($billingAddressArray) !== true) {
if (!empty($salesChannelContext->getCustomer()->getVatIds()) && array_key_exists('company', $billingAddressArray) && !empty($billingAddressArray['company']) && is_array($salesChannelContext->getCustomer()->getVatIds())) {
$billingAddressArray['vatIdValidationEntry'] = 'personalData';
$this->checkVatIdValidation($billingAddressArray, $event, $salesChannelId, $salesChannelContext, $salesChannelContext->getCustomer()->getVatIds(), 'order-save-or-update', true, 'orderAddress');
}
if (!empty($billingAddressVatId) && array_key_exists('addressCompany', $billingAddressArray) && !empty($billingAddressArray['addressCompany']) && is_string($billingAddressVatId)) {
$billingAddressArray['vatIdValidationEntry'] = 'billingAddress';
$billingAddressArray['company'] = $billingAddressArray['addressCompany'];
$this->checkVatIdValidation($billingAddressArray, $event, $salesChannelId, $salesChannelContext, [$billingAddressVatId], 'order-save-or-update', true, 'orderAddress');
}
}
}
// validate shipping address
if(empty($salesChannelContext->getCustomer()) !== true && !empty($salesChannelContext->getCustomer()->getDefaultShippingAddress())) {
$shippingAddressVatId = !empty($salesChannelContext->getCustomer() && !empty($salesChannelContext->getCustomer()->getDefaultShippingAddress())) ? $this->getBillingAddressVatId($salesChannelContext->getCustomer()->getDefaultShippingAddress()) : null;
$shippingAddressArray = $this->convertAddress($salesChannelContext->getCustomer()->getDefaultShippingAddress(), $salesChannelContext->getCustomer()->getId(), $salesChannelContext->getCustomer()->getCompany());
if (empty($shippingAddressArray) !== true) {
if (!empty($shippingAddressVatId) && array_key_exists('addressCompany', $shippingAddressArray) && !empty($shippingAddressArray['addressCompany']) && is_string($shippingAddressVatId)) {
$shippingAddressArray['vatIdValidationEntry'] = 'shippingAddress';
$shippingAddressArray['company'] = $shippingAddressArray['addressCompany'];
$this->checkVatIdValidation($shippingAddressArray, $event, $salesChannelId, $salesChannelContext, [$shippingAddressVatId], 'order-save-or-update', true, 'orderAddress');
}
}
}
}
public function onCustomerLogin(CustomerLoginEvent $event): void
{
list($data, $validation) = $this->checkForVatIdValidation($event);
if (empty($data) || !$data instanceof DataBag || empty($validation) || !$validation instanceof DataValidationDefinition) {
return;
}
$this->validator->validate($data->all(), $validation);
}
public function onCustomerRegister(CustomerRegisterEvent $event): void
{
if (empty($event->getCustomerId())) return;
$this->vatIdValidationService->assignTheCustomerToLogEntity($event->getCustomer(), $event->getSalesChannelContext());
}
public function onGuestCustomerRegister(GuestCustomerRegisterEvent $event): void
{
if (empty($event->getCustomerId())) return;
$this->vatIdValidationService->assignTheCustomerToLogEntity($event->getCustomer(), $event->getSalesChannelContext());
}
private function convertAddress(CustomerAddressEntity $addressEntity, string $customerId, ?string $company, ?DataBag $dataBag = null): array
{
$addressData = [
'customerId' => $customerId,
'street' => $addressEntity->getStreet(),
'city' => $addressEntity->getCity(),
'zipcode' => $addressEntity->getZipcode(),
'countryId' => $addressEntity->getCountryId(),
'id' => $addressEntity->getId()
];
if (!empty($addressEntity->getCompany())) $addressData['addressCompany'] = $addressEntity->getCompany();
if (!empty($company)) {
$addressData['company'] = $company;
}
if (!empty($dataBag) && !empty($dataBag->get('company'))) {
$addressData['company'] = $dataBag->get('company');
}
return $addressData;
}
private function validateVatIds($vatIds, array $billingAddress, BuildValidationEvent $event, SalesChannelContext $salesChannelContext, ?CountryEntity $country, string $cause, bool $checkOrder, string $currentAddress)
{
if(empty($vatIds)) {
return;
}
foreach ($vatIds as $vatId) {
if(empty($vatId)) {
continue;
}
$options = [
'address' => $billingAddress,
'vatId' => $vatId,
'context' => $event->getContext(),
'salesChannelContext' => $salesChannelContext,
'country' => $country,
'cause' => $cause,
'isCheckOrder' => $checkOrder,
'currentAddress' => $currentAddress
];
$properties = $event->getDefinition()->getProperties();
if (!array_key_exists('vatIds', $properties) || $cause = 'customer-update') {
$event->getDefinition()->add('vatIds', new CustomerVatId($options));
}
}
}
private function checkVatIdValidation(array $address, $event, string $salesChannelId, SalesChannelContext $salesChannelContext, ?array $vatIds, string $cause, bool $checkOrder, string $currentAddress): void
{
if (empty($vatIds)) return;
$countryId = array_key_exists('countryId', $address) && !empty($address['countryId']) ? $address['countryId'] : null;
if (empty($countryId)) return;
$country = $this->vatIdValidationService->getCountry($countryId, $event->getContext());
$vatIdFormatValidation = $this->systemConfigService->get('AcrisTaxCS.config.validateVatIDFormatAllEUCountries', $salesChannelId);
$vatIdApiServiceValidation = $this->systemConfigService->get('AcrisTaxCS.config.VatIDAPIValidationAllEUCountries', $salesChannelId);
if ((!empty($vatIdFormatValidation) && ($vatIdFormatValidation === VatIdValidationService::DEFAULT_ENABLE_FORMAT_VAT_ID_ALL_EU_COUNTRIES || ($vatIdFormatValidation === VatIdValidationService::DEFAULT_OPTION_STANDARD && $this->vatIdValidationService->checkCountryFormatValidation($country))))
|| (!empty($vatIdApiServiceValidation) && (($vatIdApiServiceValidation === VatIdValidationService::DEFAULT_ENABLE_VAT_ID_VALIDATION_API_SERVICE_ALL_EU_COUNTRIES || $vatIdApiServiceValidation === VatIdValidationService::DEFAULT_ENABLE_VAT_ID_COMPANY_VALIDATION_API_SERVICE_ALL_EU_COUNTRIES
|| $vatIdApiServiceValidation === VatIdValidationService::DEFAULT_ENABLE_VAT_ID_ADDRESS_VALIDATION_API_SERVICE_ALL_EU_COUNTRIES || $vatIdApiServiceValidation === VatIdValidationService::DEFAULT_ENABLE_VAT_ID_COMPANY_ADDRESS_VALIDATION_API_SERVICE_ALL_EU_COUNTRIES)
|| ($vatIdApiServiceValidation === VatIdValidationService::DEFAULT_OPTION_STANDARD && $this->vatIdValidationService->checkCountryVatIdValidationApiService($country))))) {
$this->validateVatIds($vatIds, $address, $event, $salesChannelContext, $country, $cause, $checkOrder, $currentAddress);
}
}
private function getBillingAddressVatId(CustomerAddressEntity $address): ?string
{
if (empty($address->getCustomFields()) || !is_array($address->getCustomFields()) || !array_key_exists('acris_address_vat_id', $address->getCustomFields()) || empty($address->getCustomFields()['acris_address_vat_id'])) return null;
return $address->getCustomFields()['acris_address_vat_id'];
}
private function checkForVatIdValidation(CustomerLoginEvent $event): array
{
$context = $event->getSalesChannelContext();
$salesChannelContext = $this->salesChannelContextService->get(
new SalesChannelContextServiceParameters(
$context->getSalesChannelId(),
$event->getContextToken(),
$context->getLanguageIdChain()[0],
$context->getCurrencyId(),
$context->getDomainId(),
$context->getContext()
)
);
$salesChannelId = !empty($salesChannelContext) && !empty($salesChannelContext->getSalesChannel()) && $salesChannelContext->getSalesChannel() instanceof SalesChannelEntity ? $salesChannelContext->getSalesChannel()->getId() : null;
if(!$salesChannelContext instanceof SalesChannelContext || empty($salesChannelId) === true) {
return [null, null];
}
$validateOnLogin = $this->systemConfigService->get('AcrisTaxCS.config.VatIDValidateOnLogin', $salesChannelId);
if (empty($validateOnLogin) || !is_string($validateOnLogin) || !in_array($validateOnLogin, self::PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTIONS)) {
return [null, null];
}
$customer = $salesChannelContext->getCustomer();
$validation = new DataValidationDefinition('customer.login.vatId.validation');
$data = new DataBag();
$validationEvent = new BuildValidationEvent($validation, $data, $context->getContext());
list($validationPersonalData, $validationBilling, $validationShipping) = $this->checkFieldsForValidation($validateOnLogin, $customer);
// validate personal data and billing address
if(empty($customer) !== true && !empty($customer->getDefaultBillingAddress())) {
$billingAddressVatId = !empty($customer && !empty($customer->getDefaultBillingAddress())) ? $this->getBillingAddressVatId($customer->getDefaultBillingAddress()) : null;
$billingAddressArray = $this->convertAddress($customer->getDefaultBillingAddress(), $customer->getId(), $customer->getCompany());
if (empty($billingAddressArray) !== true) {
if ($validationPersonalData === true && array_key_exists('company', $billingAddressArray) && !empty($billingAddressArray['company']) && !empty($customer->getVatIds()) && is_array($customer->getVatIds())) {
$billingAddressArray['vatIdValidationEntry'] = 'personalData';
$this->checkVatIdValidation($billingAddressArray, $validationEvent, $salesChannelId, $salesChannelContext, $customer->getVatIds(), self::CUSTOMER_LOGIN_CAUSE, true, 'customerUpdate');
}
if ($validationBilling === true && array_key_exists('addressCompany', $billingAddressArray) && !empty($billingAddressArray['addressCompany']) && !empty($billingAddressVatId) && is_string($billingAddressVatId)) {
$billingAddressArray['vatIdValidationEntry'] = 'billingAddress';
if (array_key_exists('customerId', $billingAddressArray)) {
unset($billingAddressArray['customerId']);
}
$billingAddressArray['company'] = $billingAddressArray['addressCompany'];
$this->checkVatIdValidation($billingAddressArray, $validationEvent, $salesChannelId, $salesChannelContext, [$billingAddressVatId], self::CUSTOMER_LOGIN_CAUSE, true, 'billingAddress');
}
}
}
// validate shipping address
if(empty($customer) !== true && !empty($customer->getDefaultShippingAddress())) {
$shippingAddressVatId = !empty($customer && !empty($customer->getDefaultShippingAddress())) ? $this->getBillingAddressVatId($customer->getDefaultShippingAddress()) : null;
$shippingAddressArray = $this->convertAddress($customer->getDefaultShippingAddress(), $customer->getId(), $customer->getCompany());
if (empty($shippingAddressArray) !== true) {
if ($validationShipping === true && array_key_exists('addressCompany', $shippingAddressArray) && !empty($shippingAddressArray['addressCompany']) && !empty($shippingAddressVatId) && is_string($shippingAddressVatId)) {
$shippingAddressArray['vatIdValidationEntry'] = 'shippingAddress';
if (array_key_exists('customerId', $shippingAddressArray)) {
unset($shippingAddressArray['customerId']);
}
$shippingAddressArray['company'] = $shippingAddressArray['addressCompany'];
$this->checkVatIdValidation($shippingAddressArray, $validationEvent, $salesChannelId, $salesChannelContext, [$shippingAddressVatId], self::CUSTOMER_LOGIN_CAUSE, true, 'shippingAddress');
}
}
}
return [$data, $validation];
}
private function checkFieldsForValidation(?string $validation, CustomerEntity $customer): array
{
$validationPersonalData = true;
$validationBilling = true;
$validationShipping = true;
if ($validation !== self::PLUGIN_CONFIG_VALIDATE_ON_LOGIN_OPTION_IF_NOT_VALIDATED) {
return [$validationPersonalData, $validationBilling, $validationShipping];
}
if (!empty($customer->getCustomFields()) && is_array($customer->getCustomFields()) && array_key_exists('acris_tax_vat_id_status', $customer->getCustomFields()) && is_string($customer->getCustomFields()['acris_tax_vat_id_status'])) {
$validationPersonalData = false;
}
if (!empty($customer->getDefaultBillingAddress()) && $customer->getDefaultBillingAddress() instanceof CustomerAddressEntity) {
$validationBilling = $this->checkAddressField($customer->getDefaultBillingAddress());
}
if (!empty($customer->getDefaultShippingAddress()) && $customer->getDefaultShippingAddress() instanceof CustomerAddressEntity) {
$validationShipping = $this->checkAddressField($customer->getDefaultShippingAddress());
}
return [$validationPersonalData, $validationBilling, $validationShipping];
}
private function checkAddressField(CustomerAddressEntity $address): bool
{
if (!empty($address->getCustomFields()) && is_array($address->getCustomFields()) && array_key_exists('acris_tax_vat_id_status', $address->getCustomFields()) && is_string($address->getCustomFields()['acris_tax_vat_id_status'])) {
return false;
}
return true;
}
}