/** * 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); } }
/** * If AvaTax GetTaxRequest failed and if configuration is set to prevent checkout, throw exception * * @param \Magento\Framework\Event\Observer $observer * @return $this * @throws LocalizedException */ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var \Magento\Sales\Api\Data\OrderInterface $order */ $order = $observer->getOrder(); if ($this->coreRegistry->registry(Tax::AVATAX_GET_TAX_REQUEST_ERROR)) { $errorMessage = $this->config->getErrorActionDisableCheckoutMessage($order->getStoreId()); throw new LocalizedException($errorMessage); } return $this; }
/** * Writes the log to the database by utilizing the Log model * * @param $record array * @return void */ public function write(array $record) { # Log to database /** @var \ClassyLlama\AvaTax\Model\Log $log */ $log = $this->logFactory->create(); $log->setData('level', isset($record['level_name']) ? $record['level_name'] : null); $log->setData('message', isset($record['message']) ? $record['message'] : null); if (isset($record['extra']['store_id'])) { $log->setData('store_id', $record['extra']['store_id']); unset($record['extra']['store_id']); } if (isset($record['extra']['class']) && isset($record['extra']['line'])) { $log->setData('source', $record['extra']['class'] . " [line:" . $record['extra']['line'] . "]"); } if ($this->avaTaxConfig->getLogDbDetail() == LogDetail::MINIMAL && $record['level'] >= Logger::WARNING) { $log->setData('request', $this->getRequest($record)); $log->setData('result', $this->getResult($record)); } elseif ($this->avaTaxConfig->getLogDbDetail() == LogDetail::NORMAL) { $log->setData('request', $this->getRequest($record)); $log->setData('result', $this->getResult($record)); $log->setData('additional', $this->getContextVarExport($record)); } elseif ($this->avaTaxConfig->getLogDbDetail() == LogDetail::EXTRA) { $log->setData('request', $this->getRequest($record)); $log->setData('result', $this->getResult($record)); $log->setData('additional', $this->getExtraVarExport($record) . (strlen($this->getExtraVarExport($record)) > 0 ? "\n" : '') . $this->getContextVarExport($record)); } $log->save(); }
/** * @return void */ public function prepare() { $config = $this->getData('config'); if (isset($config['options'])) { $options = []; foreach ($config['options'] as $option) { $option['url'] = $this->urlBuilder->getUrl($option['url']); $options[] = $option; } $config['options'] = $options; } $store = $this->storeManager->getStore(); $config['validationEnabled'] = $this->config->isAddressValidationEnabled($store); $hasChoice = $this->config->allowUserToChooseAddress($store); if ($hasChoice) { $instructions = $this->config->getAddressValidationInstructionsWithChoice($store); } else { $instructions = $this->config->getAddressValidationInstructionsWithOutChoice($store); } $config['instructions'] = $instructions; $config['errorInstructions'] = $this->config->getAddressValidationErrorInstructions($store); $config['countriesEnabled'] = $this->config->getAddressValidationCountriesEnabled($store); $config['baseUrl'] = $this->urlBuilder->getUrl(self::VALIDATE_ADDRESS_PATH); $this->setData('config', $config); parent::prepare(); }
/** * 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; }
/** * Get Ref2 code for product * * @param \Magento\Catalog\Model\Product $product * @return mixed|null */ public function getRef2ForProduct(\Magento\Catalog\Model\Product $product) { if ($this->config->getRef2Attribute() && $product->getData($this->config->getRef2Attribute())) { return $product->getData($this->config->getRef2Attribute()); } return null; }
/** * @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; }
/** * Check to see if there was an error during tax calculation, and if so, throw exception to prevent further progress * * @param $storeId * @return void * @throws LocalizedException */ protected function ensureTaxCalculationSuccess($storeId) { if ($this->coreRegistry->registry(Tax::AVATAX_GET_TAX_REQUEST_ERROR)) { $errorMessage = $this->config->getErrorActionDisableCheckoutMessage($storeId); throw new LocalizedException($errorMessage); } }
/** * @param $store * @param $address * @param \Magento\Customer\Api\Data\CustomerInterface|null $customer * @return null */ protected function getBusinessIdentificationNumber($store, $address, $customer) { if ($customer && $customer->getTaxvat()) { return $customer->getTaxvat(); } if ($this->config->getUseBusinessIdentificationNumber($store)) { return $address->getVatId(); } return null; }
/** * 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; }
/** * Get address service by type and cache instances by type to avoid duplicate instantiation * * @param string $type * @param $storeId * @return AddressServiceSoap */ public function getAddressService($type = null, $storeId = null) { if (is_null($type)) { $type = $this->config->getLiveMode() ? Config::API_PROFILE_NAME_PROD : Config::API_PROFILE_NAME_DEV; } if (!isset($this->addressServiceSoap[$type])) { $this->config->createAvaTaxProfile($storeId); $this->addressServiceSoap[$type] = $this->addressServiceSoapFactory->create(['configurationName' => $type]); } return $this->addressServiceSoap[$type]; }
/** * Clear the queue of complete and failed records */ public function clearLogs() { $this->avaTaxLogger->debug(__('Starting queue clearing')); /** @var $collection \ClassyLlama\AvaTax\Model\ResourceModel\Log\Collection */ $collection = $this->logCollectionFactory->create(); // Get configuration for record lifetime $lifetimeDays = $this->avaTaxConfig->getLogDbLifetime(); // Calculate the number of seconds to adjust the filter // 86400 seconds == 60 seconds * 60 minutes * 24 hours == 1 day $secondsBeforeNow = $lifetimeDays * 60 * 60 * 24; // Add filters $collection->addCreatedAtBeforeFilter($secondsBeforeNow); // Process each queued entity /** @var $log Log */ foreach ($collection as $log) { // Remove the queue record $log->delete(); $this->deleteCount++; } $this->avaTaxLogger->debug(__('Finished clearing log entries'), ['delete_count' => $this->deleteCount]); }
/** * Append attributes to the list of attributes loaded on the quote_items collection * * @param QuoteConfig $config * @param $attributes * @return array */ public function afterGetProductAttributes(QuoteConfig $config, $attributes) { if ($this->config->getRef1Attribute()) { $attributes[] = $this->config->getRef1Attribute(); } if ($this->config->getRef2Attribute()) { $attributes[] = $this->config->getRef2Attribute(); } if ($this->config->getUpcAttribute()) { $attributes[] = $this->config->getUpcAttribute(); } return array_unique($attributes); }
/** * 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; } }
/** * Get country list * * @return array */ protected function getCountryList() { // It seems odd to check the parameters directly, but it's the same pattern being used in Magento_Backend if ($this->request->getParam('store')) { $scopeId = $this->request->getParam('store'); $scopeType = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; } elseif ($this->request->getParam('website')) { $scopeId = $this->request->getParam('website'); $scopeType = \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE; } elseif ($this->request->getParam('group')) { $scopeId = $this->request->getParam('website'); $scopeType = \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE; } else { $scopeId = $this->storeManager->getDefaultStoreView(); $scopeType = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; } return explode(',', $this->config->getTaxCalculationCountriesEnabled($scopeId, $scopeType)); }
/** * 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; }
/** * Accepts an invoice or creditmemo and returns an \AvaTax\Line object * * @param \Magento\Sales\Api\Data\InvoiceInterface|\Magento\Sales\Api\Data\CreditmemoInterface $data * @return \AvaTax\Line|bool */ public function getNegativeAdjustmentLine($data) { $amount = $data->getBaseAdjustmentNegative(); if ($amount == 0) { return false; } $storeId = $data->getStoreId(); $itemCode = $this->config->getSkuAdjustmentNegative($storeId); $data = ['No' => $this->getLineNumber(), 'ItemCode' => $itemCode, 'Description' => self::ADJUSTMENT_NEGATIVE_LINE_DESCRIPTION, 'Qty' => 1, 'Amount' => $amount, 'Discounted' => false, 'TaxIncluded' => true]; try { $data = $this->metaDataObject->validateData($data); } catch (ValidationException $e) { $this->avaTaxLogger->error('Error validating line: ' . $e->getMessage(), ['data' => var_export($data, true)]); } /** @var $line \AvaTax\Line */ $line = $this->lineFactory->create(); $this->populateLine($data, $line); return $line; }
/** * @param Queue $queue * @param string $message * @param \Magento\Sales\Api\Data\InvoiceInterface|\Magento\Sales\Api\Data\CreditmemoInterface $entity */ protected function resetQueueingForProcessing(Queue $queue, $message, $entity) { // Check retry attempts and determine if we need to fail processing // Add a comment to the order indicating what has been done if ($queue->getAttempts() >= $this->avaTaxConfig->getQueueMaxRetryAttempts()) { $message .= __(' The processing has failed due to reaching the maximum number of attempts to retry. ' . 'Any corrective measures will need to be initiated manually'); // fail processing later by setting queue status to pending $this->failQueueProcessing($queue, $message); // Add comment to order $this->addOrderComment($entity->getOrderId(), $message); } else { $message .= __(' The processing is set to automatically retry on the next processing attempt.'); // retry processing later by setting queue status to pending $queue->setMessage($message); $queue->setQueueStatus(Queue::QUEUE_STATUS_PENDING); $queue->save(); // Add comment to order $this->addOrderComment($entity->getOrderId(), $message); } }
/** * Clear the queue of failed records based on config lifetime */ protected function clearFailedQueue() { // Initialize the queue collection /** @var $queueCollection \ClassyLlama\AvaTax\Model\ResourceModel\Queue\Collection */ $queueCollection = $this->queueCollectionFactory->create(); $queueCollection->addQueueStatusFilter(Queue::QUEUE_STATUS_FAILED); // Get configuration for record lifetime $completeDays = $this->avaTaxConfig->getQueueFailedLifetime(); // Calculate the number of seconds to adjust the filter // 86400 seconds == 60 seconds * 60 minutes * 24 hours == 1 day $secondsBeforeNow = $completeDays * 60 * 60 * 24; // Add filters $queueCollection->addCreatedAtBeforeFilter($secondsBeforeNow); $queueCollection->addUpdatedAtBeforeFilter($secondsBeforeNow); // Process each queued entity /** @var $queue Queue */ foreach ($queueCollection as $queue) { // Remove the queue record $queue->delete(); $this->deleteFailedCount++; } }
/** * Writes the log record to a file based on admin configuration settings * * @param $record array * @return void */ public function write(array $record) { // Filter the log details if ($this->avaTaxConfig->getLogFileDetail() == LogDetail::MINIMAL && $record['level'] >= Logger::WARNING) { if (isset($record['context']['extra'])) { unset($record['context']['extra']); } } elseif ($this->avaTaxConfig->getLogFileDetail() == LogDetail::NORMAL) { if (isset($record['context']['extra'])) { unset($record['context']['extra']); } } elseif ($this->avaTaxConfig->getLogFileDetail() == LogDetail::EXTRA) { // do not remove any of the context data } else { if (isset($record['context']['request'])) { unset($record['context']['request']); } if (isset($record['context']['result'])) { unset($record['context']['result']); } if (isset($record['context']['additional'])) { unset($record['context']['additional']); } if (isset($record['context']['extra'])) { unset($record['context']['extra']); } } // Write the log file if ($this->avaTaxConfig->getLogFileMode() == LogFileMode::COMBINED) { // forward the record to the default system handler for processing instead $this->systemHandler->handle($record); } elseif ($this->avaTaxConfig->getLogFileMode() == LogFileMode::SEPARATE && $this->avaTaxConfig->getLogFileBuiltinRotateEnabled()) { $this->writeWithRotation($record); } else { // write the log to the custom log file parent::write($record); } }
/** * 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; }
/** * @return mixed */ public function getCountriesEnabled() { return $this->config->getAddressValidationCountriesEnabled($this->_storeManager->getStore()); }
/** * 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; }
/** * Check whether notification is displayed * * @return bool */ public function isDisplayed() { return $this->getText() && $this->isTaxConfigPage() && $this->config->isModuleEnabled(); }