/** * If module is enabled, don't run collect totals for shipping * * Tax calculation for shipping is handled in this class * @see \ClassyLlama\AvaTax\Model\Tax\Sales\Total\Quote\Tax::collect() * Since this extension doesn't support applying discounts *after* tax, we don't need to run a separate collect * process. * * @param \Magento\Tax\Model\Sales\Total\Quote\Shipping $subject * @param \Closure $proceed * @param \Magento\Quote\Model\Quote $quote * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment * @param \Magento\Quote\Model\Quote\Address\Total $total * @return mixed */ public function aroundCollect(\Magento\Tax\Model\Sales\Total\Quote\Shipping $subject, \Closure $proceed, \Magento\Quote\Model\Quote $quote, \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment, \Magento\Quote\Model\Quote\Address\Total $total) { $storeId = $quote->getStoreId(); // If quote is virtual, getShipping will return billing address, so no need to check if quote is virtual $address = $shippingAssignment->getShipping()->getAddress(); if (!$this->config->isModuleEnabled($storeId) || $this->config->getTaxMode($storeId) == Config::TAX_MODE_NO_ESTIMATE_OR_SUBMIT || !$this->config->isAddressTaxable($address, $storeId)) { return $proceed($quote, $shippingAssignment, $total); } }
/** * @param \Magento\Sales\Model\Spi\InvoiceResourceInterface $subject * @param \Closure $proceed * * Include both the extended AbstractModel and implemented Interface here for the IDE's benefit * @param \Magento\Framework\Model\AbstractModel|\Magento\Sales\Api\Data\InvoiceInterface $entity * @param mixed $value * @param string $field field to load by (defaults to model id) * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function aroundLoad(InvoiceResourceInterface $subject, \Closure $proceed, AbstractModel $entity, $value, $field = null) { /** @var \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resultEntity */ $resultEntity = $proceed($entity, $value, $field); // Load AvaTax extension attributes if ($this->avaTaxConfig->isModuleEnabled($entity->getStoreId())) { // Get the AvaTax Attributes from the AbstractModel $avataxIsUnbalanced = $entity->getData('avatax_is_unbalanced'); $baseAvataxTaxAmount = $entity->getData('base_avatax_tax_amount'); // Check the AvaTax Entity to see if we need to add extension attributes if ($avataxIsUnbalanced !== null || $baseAvataxTaxAmount !== null) { // Get any existing extension attributes or create a new one $entityExtension = $entity->getExtensionAttributes(); if (!$entityExtension) { $entityExtension = $this->invoiceExtensionFactory->create(); } // Set the attributes if ($avataxIsUnbalanced !== null) { $entityExtension->setAvataxIsUnbalanced($avataxIsUnbalanced); } if ($baseAvataxTaxAmount !== null) { $entityExtension->setBaseAvataxTaxAmount($baseAvataxTaxAmount); } // save the ExtensionAttributes on the entity object $entity->setExtensionAttributes($entityExtension); } } return $resultEntity; }
/** * Ping AvaTax using configured live/production mode * * @param $scopeId * @param $scopeType * @return array */ protected function sendPing($scopeId, $scopeType) { $errors = []; if (!$this->config->isModuleEnabled($scopeId, $scopeType)) { return $errors; } $message = ''; $type = $this->config->getLiveMode() ? Config::API_PROFILE_NAME_PROD : Config::API_PROFILE_NAME_DEV; try { $result = $this->interactionTax->getTaxService($type, $scopeId, $scopeType)->ping(); if (is_object($result) && $result->getResultCode() != \AvaTax\SeverityLevel::$Success) { foreach ($result->getMessages() as $messages) { $message .= $messages->getName() . ': ' . $messages->getSummary() . "\n"; } } elseif (is_object($result) && $result->getResultCode() == \AvaTax\SeverityLevel::$Success) { $this->messageManager->addSuccess(__('Successfully connected to AvaTax using the ' . '<a href="#row_tax_avatax_connection_settings_header">%1 credentials</a>', $type)); } } catch (\Exception $exception) { $message = $exception->getMessage(); } if ($message) { $errors[] = __('Error connecting to AvaTax using the ' . '<a href="#row_tax_avatax_connection_settings_header">%1 credentials</a>: %2', $type, $message); } return $errors; }
/** * Overrides payment component and adds config variable to be used in the component and template * * This class takes the place of a layout config change to checkout_index_index.xml. Making the changes to the * layout this way is necessary because in the process of merging the layout files, layout overrides are * applied over existing nodes in alphabetical order by Namespace_ModuleName. So Magento_Checkout overrides * ClassyLlama_AvaTax because Magento_Checkout layout files are merged after ClassyLlama_AvaTax layout files. The * solution is to set the value of the converted object after the layout files have been merged. Additionally, * because the config fields must be accessed from PHP, the most efficient method of setting the config node values * is with PHP as the following code does. * * @param array $jsLayout * @return array */ public function process($jsLayout) { if ($this->config->isModuleEnabled()) { if ($this->config->isAddressValidationEnabled($this->storeManager->getStore())) { $userHasChoice = $this->config->allowUserToChooseAddress($this->storeManager->getStore()); if ($userHasChoice) { $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['config']['instructions'] = $this->config->getAddressValidationInstructionsWithChoice($this->storeManager->getStore()); } else { $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['config']['instructions'] = $this->config->getAddressValidationInstructionsWithoutChoice($this->storeManager->getStore()); } $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['config']['errorInstructions'] = $this->config->getAddressValidationErrorInstructions($this->storeManager->getStore()); $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['config']['choice'] = $userHasChoice; $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['component'] = self::COMPONENT_PATH; } } return $jsLayout; }
/** * Check to see if there are any native tax rules created that may affect AvaTax * * @return array */ public function checkNativeTaxRules() { $errors = []; if ($this->avaTaxConfig->isModuleEnabled() && $this->avaTaxConfig->getTaxMode($this->storeManager->getDefaultStoreView()) != Config::TAX_MODE_NO_ESTIMATE_OR_SUBMIT && !$this->avaTaxConfig->isNativeTaxRulesIgnored()) { $taxRules = $this->taxRuleRepository->getList($this->searchCriteriaBuilder->create()); if (count($taxRules->getItems())) { $errors[] = __('You have %1 native Magento Tax Rule(s) configured. ' . 'Please <a href="%2">review the tax rule(s)</a> and delete any that you do not specifically want enabled. ' . 'You should only have rules setup if you want to use them as backup rules in case of AvaTax ' . 'errors (see <a href="#row_tax_avatax_error_handling_header">Error Action setting</a>) ' . 'or if you need to support VAT tax. ' . '<a href="%3">Ignore this notification</a>.', count($taxRules->getItems()), $this->backendUrl->getUrl('tax/rule'), $this->backendUrl->getUrl('avatax/tax/ignoreTaxRuleNotification')); } } return $errors; }
/** * Check whether notification is displayed * * @return bool */ public function isDisplayed() { // Check configuration to see if this should be evaluated further if ($this->avaTaxConfig->isModuleEnabled() == false || $this->avaTaxConfig->getTaxMode($this->storeManager->getDefaultStoreView()) != Config::TAX_MODE_ESTIMATE_AND_SUBMIT || $this->avaTaxConfig->getQueueAdminNotificationEnabled() == false) { return false; } // Query the database to get some stats about the queue $this->loadQueueStats(); // Determine if we need to notify the admin user if ($this->authorization->isAllowed('ClassyLlama_AvaTax::manage_avatax') && $this->statQueueCount > 0) { return true; } else { return false; } }
/** * Map extra taxables associated with quote. Add AvaTax details to extension objects. * * @param QuoteDetailsItemInterfaceFactory $itemDataObjectFactory * @param Address $address * @param bool $useBaseCurrency * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface[] */ public function mapQuoteExtraTaxables(QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, Address $address, $useBaseCurrency) { $itemDataObjects = parent::mapQuoteExtraTaxables($itemDataObjectFactory, $address, $useBaseCurrency); $storeId = $address->getQuote()->getStore()->getId(); if (!$this->config->isModuleEnabled($storeId) || $this->config->getTaxMode($storeId) == Config::TAX_MODE_NO_ESTIMATE_OR_SUBMIT || !$this->config->isAddressTaxable($address, $storeId)) { return $itemDataObjects; } foreach ($itemDataObjects as $itemDataObject) { switch ($itemDataObject->getType()) { case Giftwrapping::QUOTE_TYPE: $itemCode = $this->config->getSkuGiftWrapOrder($storeId); $taxCode = $this->taxClassHelper->getAvataxTaxCodeForGiftOptions($storeId); $this->addExtensionAttributesToTaxQuoteDetailsItem($itemDataObject, $itemCode, $taxCode, \ClassyLlama\AvaTax\Framework\Interaction\Line::GIFT_WRAP_ORDER_LINE_DESCRIPTION); break; case Giftwrapping::PRINTED_CARD_TYPE: $itemCode = $this->config->getSkuShippingGiftWrapCard($storeId); $taxCode = $this->taxClassHelper->getAvataxTaxCodeForGiftOptions($storeId); $this->addExtensionAttributesToTaxQuoteDetailsItem($itemDataObject, $itemCode, $taxCode, \ClassyLlama\AvaTax\Framework\Interaction\Line::GIFT_WRAP_ORDER_LINE_DESCRIPTION); break; } } return $itemDataObjects; }
public function aroundSaveAddressInformation(\Magento\Checkout\Model\ShippingInformationManagement $subject, \Closure $proceed, $cartId, ShippingInformationInterface $addressInformation) { // Only validate address if module is enabled $quote = $this->quoteRepository->getActive($cartId); $storeId = $quote->getStoreId(); if (!$this->config->isModuleEnabled($storeId)) { $paymentDetails = $proceed($cartId, $addressInformation); $this->ensureTaxCalculationSuccess($storeId); return $paymentDetails; } // Only validate address if address validation is enabled if (!$this->config->isAddressValidationEnabled($storeId)) { $paymentDetails = $proceed($cartId, $addressInformation); $this->ensureTaxCalculationSuccess($storeId); return $paymentDetails; } // If quote is virtual, getShippingAddress will return billing address, so no need to check if quote is virtual $shippingAddress = $addressInformation->getShippingAddress(); $shippingInformationExtension = $addressInformation->getExtensionAttributes(); $errorMessage = null; $validAddress = null; $customerAddress = null; $quoteAddress = null; $shouldValidateAddress = true; if (!is_null($shippingInformationExtension)) { $shouldValidateAddress = $shippingInformationExtension->getShouldValidateAddress(); } $customerAddressId = $shippingAddress->getCustomerAddressId(); $enabledAddressValidationCountries = explode(',', $this->config->getAddressValidationCountriesEnabled($storeId)); if (!in_array($shippingAddress->getCountryId(), $enabledAddressValidationCountries)) { $shouldValidateAddress = false; } if ($shouldValidateAddress) { try { $validAddress = $this->validationInteraction->validateAddress($shippingAddress, $storeId); } catch (AddressValidateException $e) { $errorMessage = $e->getMessage(); } catch (\SoapFault $e) { // If there is a SoapFault, it will have already been logged, so just disable address validation, as we // don't want to display SoapFault error message to user $shouldValidateAddress = false; } catch (\Exception $e) { $this->avaTaxLogger->error('Error in validating address in aroundSaveAddressInformation: ' . $e->getMessage()); // Continue without address validation $shouldValidateAddress = false; } } // Determine which address to save to the customer or shipping addresses if (!is_null($validAddress)) { $quoteAddress = $validAddress; } else { $quoteAddress = $shippingAddress; } try { /* * Regardless of whether address was validated by AvaTax, if the address is a customer address then we need * to save that address on the customer record. The reason for this is that when a user is on the "Review * & Payments" step and they are selecting between "Valid" and "Original" address options, the selected * address information is submitted to this API so that the customer address is updated and tax * calculation is affected accordingly. */ if ($customerAddressId) { // Update the customer address $customerAddress = $this->customerAddressRepository->getById($customerAddressId); $mergedCustomerAddress = $this->addressInteraction->copyQuoteAddressToCustomerAddress($quoteAddress, $customerAddress); $this->customerAddressRepository->save($mergedCustomerAddress); } else { // Update the shipping address $addressInformation->setShippingAddress($quoteAddress); } } catch (\Exception $e) { // There may be scenarios in which the above address updating may fail, in which case we should just do // nothing $this->avaTaxLogger->error('Error in saving address: ' . $e->getMessage()); // Continue without address validation $shouldValidateAddress = false; } $returnValue = $proceed($cartId, $addressInformation); $this->ensureTaxCalculationSuccess($storeId); if (!$shouldValidateAddress) { return $returnValue; } $paymentDetailsExtension = $returnValue->getExtensionAttributes(); if (is_null($paymentDetailsExtension)) { $paymentDetailsExtension = $this->paymentDetailsExtensionFactory->create(); } if (!is_null($validAddress)) { $paymentDetailsExtension->setValidAddress($validAddress); } else { $paymentDetailsExtension->setErrorMessage($errorMessage); } $paymentDetailsExtension->setOriginalAddress($shippingAddress); $returnValue->setExtensionAttributes($paymentDetailsExtension); return $returnValue; }
/** * Check whether notification is displayed * * @return bool */ public function isDisplayed() { return $this->getText() && $this->isTaxConfigPage() && $this->config->isModuleEnabled(); }
/** * Checks whether the given record will be handled by this handler. * * Uses the admin configuration settings to determine if the record should be handled * * @param array $record * @return Boolean */ public function isHandling(array $record) { return $this->avaTaxConfig->isModuleEnabled() && $record['level'] >= $this->avaTaxConfig->getLogDbLevel(); }
/** * Check whether notification is displayed * * @return bool */ public function isDisplayed() { return $this->getText() && $this->isQueuePage() && $this->config->isModuleEnabled() && $this->config->getTaxMode($this->storeManager->getDefaultStoreView()) != Config::TAX_MODE_ESTIMATE_AND_SUBMIT; }