diff --git a/Block/Promotion/Pixel/Code.php b/Block/Promotion/Pixel/Code.php new file mode 100755 index 00000000..92e9fee4 --- /dev/null +++ b/Block/Promotion/Pixel/Code.php @@ -0,0 +1,646 @@ +storeManager = $context->getStoreManager(); + $this->affirmPixelHelper = $affirmPixelHelper; + $this->coreRegistry = $coreRegistry; + $this->catalogHelper = $catalogHelper; + $this->taxConfig = $taxConfig; + $this->configProvider = $configProvider; + $this->checkoutSession = $checkoutSession; + parent::__construct($context, $data); + } + + /** + * Render GA tracking scripts + * + * @return string + */ + protected function _toHtml() + { + if (!$this->affirmPixelHelper->isTrackPixelEnabledConfig()) { + return ''; + } + + return parent::_toHtml(); + } + + /** + * Get options for + * + * @return string + */ + public function getOptions() + { + $options = []; + $configProvider = $this->configProvider->getConfig(); + if ($configProvider['payment'][ConfigProvider::CODE]) { + $config = $configProvider['payment'][ConfigProvider::CODE]; + if ($config && isset($config['script']) && isset($config['apiKeyPublic'])) { + $options['script'] = $config['script']; + $options['public_api_key'] = $config['apiKeyPublic']; + } + } + return json_encode($options); + } + + /** + * Returns product data needed for dynamic ads tracking. + * + * @return array + */ + public function getProductData() + { + $p = $this->coreRegistry->registry('current_product'); + + $data = array(); + if($p) { + $data['name'] = $this->affirmPixelHelper->escapeSingleQuotes($p->getName()); + $data['productId'] = $this->affirmPixelHelper->escapeSingleQuotes($p->getSku()); + $data['price'] = Util::formatToCents($this->formatPrice($this->getProductPrice($p))); + $data['currency'] = $this->getCurrentCurrencyCode(); + } + return $data; + } + + /** + * Returns cart data needed for tracking. + * + * @return array + */ + public function getCartData() + { + $data = array(); + $items = $this->checkoutSession->getQuote()->getAllVisibleItems(); + if($items) { + foreach ($items as $item) { + $productData = array(); + $productData['productId'] = $this->affirmPixelHelper->escapeSingleQuotes($item->getSku()); + $productData['name'] = $this->affirmPixelHelper->escapeSingleQuotes($item->getName()); + $productData['price'] = Util::formatToCents($item->getPrice()); + $productData['currency'] = $this->getCurrentCurrencyCode(); + $productData['quantity'] = $item->getQty(); + $data[] = $productData; + } + } + return $data; + } + + /** + * Returns quote data needed for checkout started tracking. + * + * @return array|null + */ + public function getQuoteData() + { + $quote= $this->checkoutSession->getQuote(); + $data = array(); + if ($quote) { + $data['checkoutId'] = $quote->getId(); + $data['currency'] = $quote->getQuoteCurrencyCode(); + $data['total'] = Util::formatToCents($quote->getGrandTotal()); + } + return $data; + + } + + /** + * Returns quote products data needed for checkout started tracking. + * + * @return array|null + */ + public function getQuoteProductsData() + { + $quote= $this->checkoutSession->getQuote(); + $data = array(); + if ($quote) { + foreach ($quote->getAllVisibleItems() as $item) { + $productData = array(); + $productData['productId'] = $this->affirmPixelHelper->escapeSingleQuotes($item->getSku()); + $productData['name'] = $this->affirmPixelHelper->escapeSingleQuotes($item->getName()); + $productData['price'] = Util::formatToCents($item->getPrice()); + $productData['currency'] = $this->getCurrentCurrencyCode(); + $productData['quantity'] = $item->getQty(); + $data[] = $productData; + } + } + return $data; + + } + + + /** + * Returns store object + * + * @return \Magento\Store\Model\Store + */ + public function getStore() + { + if ($this->store === null) { + $this->store = $this->storeManager->getStore(); + } + + return $this->store; + } + + /** + * Returns Store Id + * + * @return int + */ + public function getStoreId() + { + if ($this->storeId === null) { + $this->storeId = $this->getStore()->getId(); + } + + return $this->storeId; + } + + /** + * Returns base currency code + * (3 letter currency code like USD, GBP, EUR, etc.) + * + * @return string + */ + public function getBaseCurrencyCode() + { + if ($this->baseCurrencyCode === null) { + $this->baseCurrencyCode = strtoupper( + $this->getStore()->getBaseCurrencyCode() + ); + } + + return $this->baseCurrencyCode; + } + + /** + * Returns current currency code + * (3 letter currency code like USD, GBP, EUR, etc.) + * + * @return string + */ + public function getCurrentCurrencyCode() + { + if ($this->currentCurrencyCode === null) { + $this->currentCurrencyCode = strtoupper( + $this->getStore()->getCurrentCurrencyCode() + ); + } + + return $this->currentCurrencyCode; + } + + /** + * Returns flag based on "Stores > Cofiguration > Sales > Tax + * > Price Display Settings > Display Product Prices In Catalog" + * Returns 0 or 1 instead of 1, 2, 3. + * + * @return int + */ + public function getDisplayTaxFlag() + { + if ($this->taxDisplayFlag === null) { + // Tax Display + // 1 - excluding tax + // 2 - including tax + // 3 - including and excluding tax + $flag = $this->taxConfig->getPriceDisplayType($this->getStoreId()); + + // 0 means price excluding tax, 1 means price including tax + if ($flag == 1) { + $this->taxDisplayFlag = 0; + } else { + $this->taxDisplayFlag = 1; + } + } + + return $this->taxDisplayFlag; + } + + /** + * Returns Stores > Cofiguration > Sales > Tax > Calculation Settings + * > Catalog Prices configuration value + * + * @return int + */ + public function getCatalogTaxFlag() + { + // Are catalog product prices with tax included or excluded? + if ($this->taxCatalogFlag === null) { + $this->taxCatalogFlag = (int) $this->affirmPixelHelper->getConfig( + 'tax/calculation/price_includes_tax', + $this->getStoreId() + ); + } + + // 0 means excluded, 1 means included + return $this->taxCatalogFlag; + } + + /** + * Returns product price. + * + * @param \Magento\Catalog\Model\Product $product + * @return string + */ + public function getProductPrice($product) + { + $price = 0.0; + + switch ($product->getTypeId()) { + case 'bundle': + $price = $this->getBundleProductPrice($product); + break; + case 'configurable': + $price = $this->getConfigurableProductPrice($product); + break; + case 'grouped': + $price = $this->getGroupedProductPrice($product); + break; + default: + $price = $this->getFinalPrice($product); + } + + return $price; + } + + /** + * Returns bundle product price. + * + * @param \Magento\Catalog\Model\Product $product + * @return string + */ + public function getBundleProductPrice($product) + { + $includeTax = (bool) $this->getDisplayTaxFlag(); + + return $this->getFinalPrice( + $product, + $product->getPriceModel()->getTotalPrices( + $product, + 'min', + $includeTax, + 1 + ) + ); + } + + /** + * Returns configurable product price. + * + * @param \Magento\Catalog\Model\Product $product + * @return string + */ + public function getConfigurableProductPrice($product) + { + if ($product->getFinalPrice() === 0) { + $simpleCollection = $product->getTypeInstance() + ->getUsedProducts($product); + + foreach ($simpleCollection as $simpleProduct) { + if ($simpleProduct->getPrice() > 0) { + return $this->getFinalPrice($simpleProduct); + } + } + } + + return $this->getFinalPrice($product); + } + + /** + * Returns grouped product price. + * + * @param \Magento\Catalog\Model\Product $product + * @return string + */ + public function getGroupedProductPrice($product) + { + $assocProducts = $product->getTypeInstance(true) + ->getAssociatedProductCollection($product) + ->addAttributeToSelect('price') + ->addAttributeToSelect('tax_class_id') + ->addAttributeToSelect('tax_percent'); + + $minPrice = INF; + foreach ($assocProducts as $assocProduct) { + $minPrice = min($minPrice, $this->getFinalPrice($assocProduct)); + } + + return $minPrice; + } + + /** + * Returns final price. + * + * @param \Magento\Catalog\Model\Product $product + * @param string $price + * @return string + */ + public function getFinalPrice($product, $price = null) + { + if ($price === null) { + $price = $product->getFinalPrice(); + } + + if ($price === null) { + $price = $product->getData('special_price'); + } + + $productType = $product->getTypeId(); + + // 1. Convert to current currency if needed + + // Convert price if base and current currency are not the same + // Except for configurable products they already have currency converted + if (($this->getBaseCurrencyCode() !== $this->getCurrentCurrencyCode()) + && $productType != 'configurable' + ) { + // Convert to from base currency to current currency + $price = $this->getStore()->getBaseCurrency() + ->convert($price, $this->getCurrentCurrencyCode()); + } + + // 2. Apply tax if needed + + // Simple, Virtual, Downloadable products price is without tax + // Grouped products have associated products without tax + // Bundle products price already have tax included/excluded + // Configurable products price already have tax included/excluded + if ($productType != 'configurable' && $productType != 'bundle') { + // If display tax flag is on and catalog tax flag is off + if ($this->getDisplayTaxFlag() && !$this->getCatalogTaxFlag()) { + $price = $this->catalogHelper->getTaxPrice( + $product, + $price, + true, + null, + null, + null, + $this->getStoreId(), + false, + false + ); + } + } + + // Case when catalog prices are with tax but display tax is set to + // to exclude tax. Applies for all products except for bundle + if ($productType != 'bundle') { + // If display tax flag is off and catalog tax flag is on + if (!$this->getDisplayTaxFlag() && $this->getCatalogTaxFlag()) { + $price = $this->catalogHelper->getTaxPrice( + $product, + $price, + false, + null, + null, + null, + $this->getStoreId(), + true, + false + ); + } + } + + return $price; + } + + /** + * Returns formated price. + * + * @param string $price + * @param string $currencyCode + * @return string + */ + public function formatPrice($price, $currencyCode = '') + { + $formatedPrice = number_format($price, 2, '.', ''); + + if ($currencyCode) { + return $formatedPrice . ' ' . $currencyCode; + } else { + return $formatedPrice; + } + } + + /** + * Returns is pixel placement for search query enabled + * + * @return bool + */ + public function isSearchTrackPixelEnabled() + { + return $this->affirmPixelHelper->isSearchTrackPixelEnabledConfig(); + } + + /** + * Returns is pixel placement for product list page enabled + * + * @return bool + */ + public function isProductListTrackPixelEnabled() + { + return $this->affirmPixelHelper->isProductListTrackPixelEnabledConfig(); + } + + /** + * Returns is pixel placement for product page enabled + * + * @return bool + */ + public function isProductViewTrackPixelEnabled() + { + return $this->affirmPixelHelper->isProductViewTrackPixelEnabledConfig(); + } + + /** + * Returns is pixel placement for add to cart action enabled + * + * @return bool + */ + public function isAddCartTrackPixelEnabled() + { + return $this->affirmPixelHelper->isAddCartTrackPixelEnabledConfig(); + } + + /** + * Returns is pixel placement for checkout start action enabled + * + * @return bool + */ + public function isAddChkStartTrackPixelEnabled() + { + return $this->affirmPixelHelper->isAddChkStartTrackPixelEnabledConfig(); + } + + /** + * Returns category data needed for tracking. + * + * @return array + */ + public function getCategoryData() + { + $_category = $this->coreRegistry->registry('current_category'); + $data = array(); + if ($_category) { + $data['categoryId'] = $_category->getId(); + $data['categoryName'] = $_category->getName(); + } + return $data; + } + + /** + * get query string + * + * @return string + */ + public function getQueryString() + { + return $this->getRequest()->getParam('q'); + } +} diff --git a/Block/Promotion/Pixel/Confirm.php b/Block/Promotion/Pixel/Confirm.php index b5724dcf..b8dbf46f 100644 --- a/Block/Promotion/Pixel/Confirm.php +++ b/Block/Promotion/Pixel/Confirm.php @@ -45,7 +45,7 @@ /** * Class Confirm * - * @package Astound\Affirm\Block\Promotion + * @package Astound\Affirm\Block\Pixel */ class Confirm extends \Magento\Framework\View\Element\Template { @@ -142,7 +142,7 @@ public function getOrdersTrackingCode() */ protected function _toHtml() { - if (!$this->affirmPixelHelper->isAffirmAnalyticsAvailable()) { + if (!$this->affirmPixelHelper->isCheckoutSuccessPixelEnabledConfig()) { return ''; } @@ -163,7 +163,6 @@ public function getOptions() if ($config && isset($config['script']) && isset($config['apiKeyPublic'])) { $options['script'] = $config['script']; $options['public_api_key'] = $config['apiKeyPublic']; - $options['sessionId'] = ($this->getCustomerSessionId())? $this->getCustomerSessionId() : ''; } } return json_encode($options); diff --git a/Helper/Pixel.php b/Helper/Pixel.php index 5be48e20..9bd1d692 100644 --- a/Helper/Pixel.php +++ b/Helper/Pixel.php @@ -66,22 +66,110 @@ public function __construct( $this->affirmPaymentConfig = $configAffirm; } + public function getDateMicrotime() + { + $microtime = explode(' ', microtime()); + $msec = $microtime[0]; + $msecArray = explode('.', $msec); + $date = date('Y-m-d-H-i-s') . '-' . $msecArray[1]; + return $date; + } + + /** + * Returns is pixel enabled + * + * @return bool + */ + public function isTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('enabled'); + } + /** - * Get pixel active status for confirm page + * Returns is pixel placement for search query enabled * + * @return bool + */ + public function isSearchTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_search'); + } + + /** + * Returns is pixel placement for product list page enabled + * + * @return bool + */ + public function isProductListTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_product_list'); + } + + /** + * Returns is pixel placement for product page enabled + * + * @return bool + */ + public function isProductViewTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_product_view'); + } + + /** + * Returns is pixel placement for add to cart action enabled + * + * @return bool + */ + public function isAddCartTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_cart'); + } + + /** + * Returns is pixel placement for checkout start action enabled + * + * @return bool + */ + public function isAddChkStartTrackPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_checkout_start'); + } + + + /** + * Returns is pixel placement for confirmation page enabled + * + * @return bool + */ + public function isCheckoutSuccessPixelEnabledConfig() + { + return $this->affirmPaymentConfig->getPixelValue('add_checkout_success'); + } + + /** + * Add slashes to string and prepares string for javascript. + * + * @param string $str * @return string */ - public function isAffirmAnalyticsAvailable() + public function escapeSingleQuotes($str) { - return $this->affirmPaymentConfig->getPixelValue('active_confirm'); + return str_replace("'", "\'", $str); } - public function getDateMicrotime() + /** + * Based on provided configuration path returns configuration value. + * + * @param string $configPath + * @param string|int $scope + * @return string + */ + public function getConfig($configPath, $scope = 'default') { - $microtime = explode(' ', microtime()); - $msec = $microtime[0]; - $msecArray = explode('.', $msec); - $date = date('Y-m-d-H-i-s') . '-' . $msecArray[1]; - return $date; + return $this->scopeConfig->getValue( + $configPath, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $scope + ); } } diff --git a/composer.json b/composer.json index 61dadc20..639bdd77 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "affirm/magento2", "description": "Affirm's extension for the Magento 2 https://www.affirm.com/", "type": "magento2-module", - "version": "2.1.0", + "version": "2.1.1", "license": [ "BSD-3-Clause" ], diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 77f8e71c..a5dc0e3e 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -287,9 +287,40 @@ - - + + Magento\Config\Model\Config\Source\Yesno + Enable affirm tracking pixels. In addition to enabling this config, please do enable pixel for specific actions using config options listed below. + + + + Magento\Config\Model\Config\Source\Yesno + Enable add to cart tracking + + + + Magento\Config\Model\Config\Source\Yesno + Enable search tracking + + + + Magento\Config\Model\Config\Source\Yesno + Enable product tracking + + + + Magento\Config\Model\Config\Source\Yesno + Enable product list tracking + + + + Magento\Config\Model\Config\Source\Yesno + Enable checkout start tracking + + + + Magento\Config\Model\Config\Source\Yesno + Enable checkout success tracking diff --git a/etc/module.xml b/etc/module.xml index 3816f17c..01d7e127 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -10,7 +10,7 @@ */ --> - + diff --git a/view/frontend/layout/catalog_category_view.xml b/view/frontend/layout/catalog_category_view.xml index 0c2c648b..80ab8b86 100644 --- a/view/frontend/layout/catalog_category_view.xml +++ b/view/frontend/layout/catalog_category_view.xml @@ -51,5 +51,13 @@ + + + diff --git a/view/frontend/layout/catalog_product_view.xml b/view/frontend/layout/catalog_product_view.xml index 89fbd7d2..f3bd9c2e 100644 --- a/view/frontend/layout/catalog_product_view.xml +++ b/view/frontend/layout/catalog_product_view.xml @@ -69,5 +69,13 @@ + + + diff --git a/view/frontend/layout/catalog_product_view_type_bundle.xml b/view/frontend/layout/catalog_product_view_type_bundle.xml index a70d797c..8e108ba2 100644 --- a/view/frontend/layout/catalog_product_view_type_bundle.xml +++ b/view/frontend/layout/catalog_product_view_type_bundle.xml @@ -27,5 +27,12 @@ + + + diff --git a/view/frontend/layout/catalogsearch_advanced_result.xml b/view/frontend/layout/catalogsearch_advanced_result.xml new file mode 100755 index 00000000..b3a5c70a --- /dev/null +++ b/view/frontend/layout/catalogsearch_advanced_result.xml @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/view/frontend/layout/catalogsearch_result_index.xml b/view/frontend/layout/catalogsearch_result_index.xml new file mode 100755 index 00000000..d2ecdfeb --- /dev/null +++ b/view/frontend/layout/catalogsearch_result_index.xml @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/view/frontend/layout/checkout_cart_index.xml b/view/frontend/layout/checkout_cart_index.xml index 945e42ae..568c6706 100644 --- a/view/frontend/layout/checkout_cart_index.xml +++ b/view/frontend/layout/checkout_cart_index.xml @@ -53,5 +53,12 @@ + + + diff --git a/view/frontend/layout/checkout_onepage_success.xml b/view/frontend/layout/checkout_onepage_success.xml index fdfaeed0..4e57d108 100644 --- a/view/frontend/layout/checkout_onepage_success.xml +++ b/view/frontend/layout/checkout_onepage_success.xml @@ -34,8 +34,13 @@ --> - - + + diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js index 59fb81e4..b19d7700 100644 --- a/view/frontend/requirejs-config.js +++ b/view/frontend/requirejs-config.js @@ -19,12 +19,12 @@ var config = { map: { '*': { 'synchPost': 'Astound_Affirm/js/action/send-request', + 'affirmPixel': 'Astound_Affirm/js/affirmPixel', 'affirmWidget': 'Astound_Affirm/js/affirmWidget', 'aslowasPDP': 'Astound_Affirm/js/aslowasPDP', 'aslowasPLP': 'Astound_Affirm/js/aslowasPLP', 'aslowasCC': 'Astound_Affirm/js/aslowasCC', - 'aslowasMC': 'Astound_Affirm/js/aslowasMC', - 'affirmPixel': 'Astound_Affirm/js/affirmPixel' + 'aslowasMC': 'Astound_Affirm/js/aslowasMC' } } }; diff --git a/view/frontend/templates/pixel/code.phtml b/view/frontend/templates/pixel/code.phtml new file mode 100755 index 00000000..2dc3b89b --- /dev/null +++ b/view/frontend/templates/pixel/code.phtml @@ -0,0 +1,182 @@ + + +getOptions(); + +$enableSearchTrack = $block->isSearchTrackPixelEnabled(); +$enableProductListTrack = $block->isProductListTrackPixelEnabled(); +$enableProductViewTrack = $block->isProductViewTrackPixelEnabled(); +$enableAddCartTrack = $block->isAddCartTrackPixelEnabled(); +$enableAddChkStartTrack = $block->isAddChkStartTrackPixelEnabled(); + +$action = $this->getRequest()->getFullActionName(); + +?> + + + + + diff --git a/view/frontend/templates/pixel/confirm.phtml b/view/frontend/templates/pixel/confirm.phtml index e2a0f703..6c3e209d 100644 --- a/view/frontend/templates/pixel/confirm.phtml +++ b/view/frontend/templates/pixel/confirm.phtml @@ -35,20 +35,28 @@ ?> getOptions(); +$pixelCode = $this->getOrdersTrackingCode(); + +$action = $this->getRequest()->getFullActionName(); + ?>