From 5c79e00acb5d6217e01481e3b748a37977b68064 Mon Sep 17 00:00:00 2001 From: Ilvars Svanda Date: Wed, 21 Jan 2026 10:24:59 +0200 Subject: [PATCH 1/6] LMM-104 Restyle leanpay --- Helper/InstallmentHelper.php | 344 ++++++++++- .../Data/MigrateInstallmentAdvancedConfig.php | 79 +++ etc/adminhtml/system.xml | 38 ++ etc/config.xml | 14 +- i18n/hr_HR.csv | 6 + i18n/ro_RO.csv | 5 + i18n/sl_SL.csv | 5 + .../frontend/layout/catalog_category_view.xml | 7 + .../layout/catalogsearch_result_index.xml | 7 + view/frontend/layout/cms_index_index.xml | 7 + .../pricing/render/installment.phtml | 185 +++--- view/frontend/web/css/leanpay.css | 561 +++++++++++++++++- view/frontend/web/images/arrow-right.svg | 5 + view/frontend/web/images/dropdown.svg | 3 + view/frontend/web/images/leanpay-original.svg | 44 ++ view/frontend/web/images/leanpay.svg | 204 +------ .../web/images/leanpay_short_logo.png | Bin 0 -> 2135 bytes view/frontend/web/js/installment.js | 304 +++++++++- .../web/template/payment/leanpay.html | 2 +- 19 files changed, 1513 insertions(+), 307 deletions(-) create mode 100644 Setup/Patch/Data/MigrateInstallmentAdvancedConfig.php create mode 100644 view/frontend/layout/catalog_category_view.xml create mode 100644 view/frontend/layout/catalogsearch_result_index.xml create mode 100644 view/frontend/layout/cms_index_index.xml create mode 100644 view/frontend/web/images/arrow-right.svg create mode 100644 view/frontend/web/images/dropdown.svg create mode 100644 view/frontend/web/images/leanpay-original.svg create mode 100644 view/frontend/web/images/leanpay_short_logo.png diff --git a/Helper/InstallmentHelper.php b/Helper/InstallmentHelper.php index fc17740..e804302 100644 --- a/Helper/InstallmentHelper.php +++ b/Helper/InstallmentHelper.php @@ -80,6 +80,36 @@ class InstallmentHelper extends AbstractHelper */ public const LEANPAY_INSTALLMENT_MAX = 'payment/leanpay/max_order_total'; + /** + * Leanpay Installment amount threshold + */ + public const LEANPAY_INSTALLMENT_AMOUNT_THRESHOLD = 'payment/leanpay_installment/advanced/amount_threshold'; + + /** + * Leanpay Installment default installment count for amounts <= threshold + */ + public const LEANPAY_INSTALLMENT_DEFAULT_COUNT = 'payment/leanpay_installment/advanced/default_installment_count'; + + /** + * Leanpay Installment under threshold text + */ + public const LEANPAY_INSTALLMENT_UNDER_THRESHOLD_TEXT = 'payment/leanpay_installment/advanced/under_threshold_text'; + + /** + * Leanpay Installment PLP background color + */ + public const LEANPAY_INSTALLMENT_PLP_BACKGROUND_COLOR = 'payment/leanpay_installment/advanced/plp_background_color'; + + /** + * Leanpay Installment PDP text color + */ + public const LEANPAY_INSTALLMENT_PDP_TEXT_COLOR = 'payment/leanpay_installment/advanced/pdp_text_color'; + + /** + * Leanpay Installment tooltip quick information text (PDP) + */ + public const LEANPAY_INSTALLMENT_QUICK_INFORMATION = 'payment/leanpay_installment/advanced/quick_information'; + /** * Installment view options @@ -250,6 +280,127 @@ public function getBackgroundColor() return (string) $this->scopeConfig->getValue(self::LEANPAY_INSTALLMENT_BACKGROUND_COLOR); } + /** + * Get amount threshold + * + * @return float|null + */ + public function getAmountThreshold() + { + $threshold = $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_AMOUNT_THRESHOLD, + ScopeInterface::SCOPE_STORE + ); + + if ($threshold === null || $threshold === '') { + return 300.0; // Default threshold + } + + return (float) $threshold; + } + + /** + * Get default installment count for amounts <= threshold + * + * @return int + */ + public function getDefaultInstallmentCount(): int + { + $count = $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_DEFAULT_COUNT, + ScopeInterface::SCOPE_STORE + ); + + if ($count === null || $count === '') { + return 3; // Default count + } + + return (int) $count; + } + + /** + * Check if amount is within threshold (<= threshold) + * + * @param float $amount + * @return bool + */ + public function isWithinThreshold(float $amount): bool + { + $threshold = $this->getAmountThreshold(); + return $amount <= $threshold; + } + + /** + * Check if amount meets threshold requirement + * + * @param float $amount + * @return bool + */ + public function meetsAmountThreshold(float $amount): bool + { + $threshold = $this->getAmountThreshold(); + + if ($threshold === null) { + return true; // No threshold set, show for all amounts + } + + return $amount >= $threshold; + } + + /** + * Get under threshold text + * + * @return string + */ + public function getUnderThresholdText(): string + { + $text = (string) $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_UNDER_THRESHOLD_TEXT, + ScopeInterface::SCOPE_STORE + ); + + return (string) preg_replace('/\bEUR\b/u', '€', $text); + } + + /** + * Get PLP background color + * + * @return string + */ + public function getPlpBackgroundColor(): string + { + return (string) $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_PLP_BACKGROUND_COLOR, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get PDP text color + * + * @return string + */ + public function getPdpTextColor(): string + { + return (string) $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_PDP_TEXT_COLOR, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get tooltip quick information (PDP) + */ + public function getQuickInformation(): string + { + $value = (string) $this->scopeConfig->getValue( + self::LEANPAY_INSTALLMENT_QUICK_INFORMATION, + ScopeInterface::SCOPE_STORE + ); + + return $value; + } + /** * Check if can show installment * @@ -285,6 +436,7 @@ public function canShowInstallment(string $view): bool * Get lowest installment price * * @param float $price + * @param string $group * @return string */ public function getLowestInstallmentPrice(float $price, $group = '') @@ -313,7 +465,25 @@ public function getLowestInstallmentPrice(float $price, $group = '') if (!$group) { $group = $this->getGroup(); } + // If amount is within threshold, return price for default installment count + if ($this->isWithinThreshold($price)) { + $defaultCount = $this->getDefaultInstallmentCount(); + $installmentList = $this->resourceModel->getInstallmentList($price, $group); + + // Search for installment matching the default count + foreach ($installmentList as $installmentData) { + if (isset($installmentData[InstallmentInterface::INSTALLMENT_PERIOD]) && + isset($installmentData[InstallmentInterface::INSTALLMENT_AMOUNT]) && + (int)$installmentData[InstallmentInterface::INSTALLMENT_PERIOD] === $defaultCount) { + return (string) $installmentData[InstallmentInterface::INSTALLMENT_AMOUNT]; + } + } + + // Fallback: if exact match not found, return lowest installment + return $this->resourceModel->getLowestInstallment($price, $group, $this->dataHelper->getApiType()); + } + // For amounts above threshold, return lowest installment price return $this->resourceModel->getLowestInstallment($price, $group, $this->dataHelper->getApiType()); } @@ -321,15 +491,46 @@ public function getLowestInstallmentPrice(float $price, $group = '') * Get installment list * * @param float $price + * @param string $group * @return array */ public function getInstallmentList(float $price, $group = '') { - if ($group) { - return $this->resourceModel->getInstallmentList($price, $group); + if (!$price) { + return []; + } + + // Determine which group to use + $targetGroup = $group ?: $this->getGroup(); + + // Get full installment list + $installmentList = $this->resourceModel->getInstallmentList($price, $targetGroup); + + // If amount is within threshold, return only the default installment + if ($this->isWithinThreshold($price)) { + $defaultCount = $this->getDefaultInstallmentCount(); + + // Search for installment matching the default count + foreach ($installmentList as $period => $installmentData) { + if (isset($installmentData[InstallmentInterface::INSTALLMENT_PERIOD]) && + (int)$installmentData[InstallmentInterface::INSTALLMENT_PERIOD] === $defaultCount) { + // Return array with single entry matching the default count + return [$period => $installmentData]; + } + } + + // Fallback: if exact match not found, return first available installment + if (!empty($installmentList)) { + $firstKey = array_key_first($installmentList); + return [$firstKey => $installmentList[$firstKey]]; + } + + // Final fallback: return empty array + return []; } - return $this->resourceModel->getInstallmentList($price, $this->getGroup()); + // For amounts above threshold, return full list + return $installmentList; } /** @@ -439,11 +640,11 @@ public function getCurrency(): string public function getCurrencyCode(): string { if ($this->dataHelper->getApiType() === Data::API_ENDPOINT_CROATIA) { - return 'EUR'; + return '€'; } elseif ($this->dataHelper->getApiType() === Data::API_ENDPOINT_ROMANIA) { return 'RON'; } else { - return 'EUR'; + return '€'; } } @@ -560,6 +761,17 @@ public function shouldRenderTooltipPriceBlock(float $amount, $useTerm = false): */ public function getCategoryPriceBlock(float $amount, $preCalculatedValue = 0): \Magento\Framework\Phrase { + // Check if amount meets threshold + if (!$this->meetsAmountThreshold($amount)) { + $underThresholdText = $this->getUnderThresholdText(); + if ($underThresholdText) { + return __($underThresholdText, $amount); + } + // Fallback to just showing the amount if no custom text is configured + return __((string) $amount); + } + + // Amount meets threshold, show installment price if ($preCalculatedValue) { $price = $preCalculatedValue; } else { @@ -570,7 +782,7 @@ public function getCategoryPriceBlock(float $amount, $preCalculatedValue = 0): \ return __( 'od %1 %2 / %3 %4 mjesečno', $price, - 'EUR', + '€', $this->getTransitionPrice($price, 'HRK'), 'HRK' ); @@ -579,6 +791,124 @@ public function getCategoryPriceBlock(float $amount, $preCalculatedValue = 0): \ return __('ali od %1 %2 / mesec', $price, $this->getCurrencyCode()); } + /** + * Get installment period for a given price + * + * @param float $amount + * @param float|string $installmentPrice + * @param string $group + * @return int + */ + public function getInstallmentPeriodForPrice(float $amount, $installmentPrice, $group = ''): int + { + $installmentList = $this->getInstallmentList($amount, $group); + $installmentPriceFloat = (float) $installmentPrice; + + // Search for installment matching the price + foreach ($installmentList as $item) { + if (isset($item[InstallmentInterface::INSTALLMENT_PERIOD]) && + isset($item[InstallmentInterface::INSTALLMENT_AMOUNT])) { + $itemAmount = (float) $item[InstallmentInterface::INSTALLMENT_AMOUNT]; + + // Compare with small tolerance for floating point precision + if (abs($itemAmount - $installmentPriceFloat) < 0.01) { + return (int) $item[InstallmentInterface::INSTALLMENT_PERIOD]; + } + } + } + + // If within threshold and no match found, use default count + if ($this->isWithinThreshold($amount)) { + return $this->getDefaultInstallmentCount(); + } + + return 0; + } + + /** + * Get maximum installment period from list + * + * @param float $amount + * @param string $group + * @return int + */ + public function getMaxInstallmentPeriod(float $amount, $group = ''): int + { + $installmentList = $this->getInstallmentList($amount, $group); + $maxPeriod = 0; + + foreach ($installmentList as $item) { + if (isset($item[InstallmentInterface::INSTALLMENT_PERIOD])) { + $period = (int) $item[InstallmentInterface::INSTALLMENT_PERIOD]; + $maxPeriod = max($maxPeriod, $period); + } + } + + return $maxPeriod; + } + + /** + * Get product price only (without "from" and /mesec) for amounts above threshold + * + * @param float $amount + * @param int $preCalculatedValue + * @return Phrase + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function getProductPriceOnly(float $amount, $preCalculatedValue = 0): \Magento\Framework\Phrase + { + if ($preCalculatedValue) { + $price = $preCalculatedValue; + } else { + $price = $this->getLowestInstallmentPrice($amount); + } + + if ($this->dataHelper->getApiType() === Data::API_ENDPOINT_CROATIA) { + return + __( + '%1 %2 / %3 %4', + $price, + '€', + $this->getTransitionPrice($price, 'HRK'), + 'HRK' + ); + } + + return __('%1 %2', $price, $this->getCurrencyCode()); + } + + /** + * Get product price block with /mesec format for amounts above threshold + * + * @param float $amount + * @param int $preCalculatedValue + * @return Phrase + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function getProductPriceBlockWithMonth(float $amount, $preCalculatedValue = 0): \Magento\Framework\Phrase + { + if ($preCalculatedValue) { + $price = $preCalculatedValue; + } else { + $price = $this->getLowestInstallmentPrice($amount); + } + + if ($this->dataHelper->getApiType() === Data::API_ENDPOINT_CROATIA) { + return + __( + 'from %1 %2 / %3 %4 /mesec', + $price, + '€', + $this->getTransitionPrice($price, 'HRK'), + 'HRK' + ); + } + + return __('from %1 %2 /mesec', $price, $this->getCurrencyCode()); + } + /** * @param float $amount * @param int $preCalculatedValue @@ -599,7 +929,7 @@ public function getProductPriceBlock(float $amount, $preCalculatedValue = 0): \M __( '%1 %2 / %3 %4', $price, - 'EUR', + '€', $this->getTransitionPrice($price, 'HRK'), 'HRK' ); diff --git a/Setup/Patch/Data/MigrateInstallmentAdvancedConfig.php b/Setup/Patch/Data/MigrateInstallmentAdvancedConfig.php new file mode 100644 index 0000000..bd72c1d --- /dev/null +++ b/Setup/Patch/Data/MigrateInstallmentAdvancedConfig.php @@ -0,0 +1,79 @@ + + * @copyright Copyright (c) Magebit, Ltd. (https://magebit.com) + * @license https://magebit.com/code-license + */ +declare(strict_types=1); + +namespace Leanpay\Payment\Setup\Patch\Data; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +class MigrateInstallmentAdvancedConfig implements DataPatchInterface +{ + private const PATH_MIGRATIONS = [ + 'payment/leanpay_installment_advanced/amount_threshold' => 'payment/leanpay_installment/advanced/amount_threshold', + 'payment/leanpay_installment_advanced/default_installment_count' => 'payment/leanpay_installment/advanced/default_installment_count', + 'payment/leanpay_installment_advanced/under_threshold_text' => 'payment/leanpay_installment/advanced/under_threshold_text', + 'payment/leanpay_installment_advanced/plp_background_color' => 'payment/leanpay_installment/advanced/plp_background_color', + 'payment/leanpay_installment_advanced/pdp_text_color' => 'payment/leanpay_installment/advanced/pdp_text_color', + 'payment/leanpay_installment_advanced/quick_information' => 'payment/leanpay_installment/advanced/quick_information', + ]; + + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var ResourceConnection + */ + private $resource; + + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + ResourceConnection $resource + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->resource = $resource; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + $connection = $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + $table = $connection->getTableName('core_config_data'); + + foreach (self::PATH_MIGRATIONS as $oldPath => $newPath) { + $connection->update( + $table, + ['path' => $newPath], + ['path = ?' => $oldPath] + ); + } + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases(): array + { + return []; + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 913f7e6..50cd4c6 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -193,6 +193,44 @@ Leanpay\Payment\Block\System\Config\RefreshButton + + + These settings are for advanced customization. Change only if you know what you're doing. + advanced + + + Amount threshold at which the dynamic behavior changes. For amounts up to and including this value, the default installment count will be used. For amounts above this value, the maximum available installments will be selected. Default: 300 + validate-number + + + + Default number of installments for amounts up to and including the threshold. Default: 3 + validate-number + + + + Text to display when amount is below threshold. Use %1 as placeholder for amount. Example: "%1 EUR" + + + + Background color for Product Listing Page (Category page) installment display + + + + Text color for Product Detail Page installment display + + + + Shown inside the installment tooltip on PDP. + + diff --git a/etc/config.xml b/etc/config.xml index 44f9d76..101c70f 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -22,15 +22,23 @@ Leanpay\Payment\Model\Method\LeanpayInstallment - #F58466 - #F58466 + #EB5A7A + #EB5A7A1A 15 15 - 15 + 12 https://www.leanpay.si/ https://app.leanpay.si/vendor/pre-qualified 0 SPLET - REDNA PONUDBA + + 300 + 3 + 0% obresti, 0 stroškov + #EB5A7A1A + #FFFFFF + + diff --git a/i18n/hr_HR.csv b/i18n/hr_HR.csv index d5dd497..7dba8ea 100644 --- a/i18n/hr_HR.csv +++ b/i18n/hr_HR.csv @@ -12,3 +12,9 @@ "Preveri svoj limit","Provjerite svoj limit" "Več informacij","Više informacija" "ali","ili" +"obroki","obroci" +"from %1 %2 /mesec","od %1 %2 /mjesečno" +"/mesec","/mjesečno" +"from","od" +"mes.","mj." +"Calculate","Izračunaj" \ No newline at end of file diff --git a/i18n/ro_RO.csv b/i18n/ro_RO.csv index 08a191c..4f905eb 100644 --- a/i18n/ro_RO.csv +++ b/i18n/ro_RO.csv @@ -13,3 +13,8 @@ "Več informacij","Mai multe informații" "ali od %1 %2 / mesec","de la %1 %2 / lună" "ali","sau" +"obroki","rate" +"from %1 %2 /mesec","de la %1 %2 /lună" +"/mesec","/lună" +"mes.","lun." +"Calculate","Calculează" \ No newline at end of file diff --git a/i18n/sl_SL.csv b/i18n/sl_SL.csv index 214735e..d92dee9 100644 --- a/i18n/sl_SL.csv +++ b/i18n/sl_SL.csv @@ -12,3 +12,8 @@ "Preveri svoj limit", "Več informacij", "ali", +"obroki","obroki" +"from %1 %2 /mesec","od %1 %2 /mesec" +"/mesec","/mesec" +"mes.","mes." +"Calculate","Izračunaj" \ No newline at end of file diff --git a/view/frontend/layout/catalog_category_view.xml b/view/frontend/layout/catalog_category_view.xml new file mode 100644 index 0000000..f631a90 --- /dev/null +++ b/view/frontend/layout/catalog_category_view.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/view/frontend/layout/catalogsearch_result_index.xml b/view/frontend/layout/catalogsearch_result_index.xml new file mode 100644 index 0000000..f631a90 --- /dev/null +++ b/view/frontend/layout/catalogsearch_result_index.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/view/frontend/layout/cms_index_index.xml b/view/frontend/layout/cms_index_index.xml new file mode 100644 index 0000000..f631a90 --- /dev/null +++ b/view/frontend/layout/cms_index_index.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/view/frontend/templates/pricing/render/installment.phtml b/view/frontend/templates/pricing/render/installment.phtml index af094f3..d300358 100644 --- a/view/frontend/templates/pricing/render/installment.phtml +++ b/view/frontend/templates/pricing/render/installment.phtml @@ -23,41 +23,102 @@ $helper = $block->getInstallmentHelper(); $canShow = $helper->canShowInstallment($viewKey); $fontSize = $helper->getFontSize($viewKey); $color = $helper->getInstallmentColor(); + $amount = intval(round($block->getAmount())); +$meetsThreshold = $helper->meetsAmountThreshold($amount); ?> - -
+
escapeHtml($helper->getCategoryPriceBlock($amount, $price)); ?> + +
+ +
+ escapeHtml($helper->getUnderThresholdText()); ?> +
- escapeHtml(__('ali')); ?>
-
-
- getInstallmentList($amount, $vendorProductName); ?> - - escapeHtml(__('Hitro in enostavno obročno odplačevanje')); ?> - - - - escapeHtml(__('Že od')); ?> - - escapeHtml($helper->getProductPriceBlock($amount, $price)); ?> - - - - escapeHtml(__('Vaš mesečni obrok')); ?> - - + getInstallmentList($amount, $vendorProductName); + $maxPeriod = $helper->getMaxInstallmentPeriod($amount, $vendorProductName); + $currentPeriod = $helper->getInstallmentPeriodForPrice($amount, $price, $vendorProductName); + $isWithinThreshold = $helper->isWithinThreshold($amount); + ?> +
+
+
+ +
+
+
+ + + escapeHtml($helper->getProductPriceBlock($amount, $price)); ?> + + 0): ?> + + escapeHtml(' x ' . $currentPeriod . ' ' . __('obroki')); ?> + + + + + escapeHtml(__('from') . ' '); ?> + + + escapeHtml($helper->getProductPriceOnly($amount, $price)); ?> + + + escapeHtml(' ' . __('/mesec')); ?> + + +
+ 0): ?> +
+ + escapeHtml(__('Razdeli na do %1 obrokov', $maxPeriod)); ?> + + escapeHtml(__('0% obresti, 0 stroškov')); ?> + + +
+ +
+ + +
@@ -71,78 +132,54 @@ $amount = intval(round($block->getAmount()));
-
- - - - - - -
+
-
-
- +