diff --git a/Helper/InstallmentHelper.php b/Helper/InstallmentHelper.php index fc17740..c3a110b 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,157 @@ 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; + } + + /** + * Default installment count for current amount, clamped to max available period. + * + * If configured default is higher than what is available, fallback to max available period. + */ + public function getEffectiveDefaultInstallmentCount(float $amount, string $group = ''): int + { + $defaultCount = $this->getDefaultInstallmentCount(); + $targetGroup = $group ?: $this->getGroup(); + + $installmentList = $this->resourceModel->getInstallmentList($amount, $targetGroup); + if (empty($installmentList)) { + return $defaultCount; + } + + $maxPeriod = 0; + foreach ($installmentList as $item) { + if (!isset($item[InstallmentInterface::INSTALLMENT_PERIOD])) { + continue; + } + $maxPeriod = max($maxPeriod, (int) $item[InstallmentInterface::INSTALLMENT_PERIOD]); + } + + if ($maxPeriod > 0 && $defaultCount > $maxPeriod) { + return $maxPeriod; + } + + return $defaultCount; + } + + /** + * 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 * @@ -314,13 +495,33 @@ public function getLowestInstallmentPrice(float $price, $group = '') $group = $this->getGroup(); } - return $this->resourceModel->getLowestInstallment($price, $group, $this->dataHelper->getApiType()); + // If amount is within threshold, return installment amount for configured default count + if ($this->isWithinThreshold($price)) { + $defaultCount = $this->getEffectiveDefaultInstallmentCount($price, (string) $group); + $installmentList = $this->resourceModel->getInstallmentList($price, $group); + + foreach ($installmentList as $installmentData) { + if (!isset( + $installmentData[InstallmentInterface::INSTALLMENT_PERIOD], + $installmentData[InstallmentInterface::INSTALLMENT_AMOUNT] + )) { + continue; + } + + if ((int) $installmentData[InstallmentInterface::INSTALLMENT_PERIOD] === (int) $defaultCount) { + return (string) $installmentData[InstallmentInterface::INSTALLMENT_AMOUNT]; + } + } + } + + return (string) $this->resourceModel->getLowestInstallment($price, $group, $this->dataHelper->getApiType()); } /** * Get installment list * * @param float $price + * @param string $group * @return array */ public function getInstallmentList(float $price, $group = '') @@ -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 '€'; } } @@ -481,6 +682,9 @@ public function getTransitionPrice(string $price, string $currencyCode): string| public function getJsonConfig($amount, $group = '') { $list = $this->getInstallmentList($amount, $group); + if (empty($list)) { + return ''; + } $list = array_values($list); $values = []; $listLength = count($list); @@ -488,12 +692,25 @@ public function getJsonConfig($amount, $group = '') $values[] = $index; } + $defaultIndex = 0; + $targetGroup = $group ?: $this->getGroup(); + $defaultCount = $this->getEffectiveDefaultInstallmentCount((float) $amount, (string) $targetGroup); + foreach ($list as $idx => $row) { + if (isset($row[InstallmentInterface::INSTALLMENT_PERIOD]) && + (int) $row[InstallmentInterface::INSTALLMENT_PERIOD] === (int) $defaultCount + ) { + $defaultIndex = (int) $idx; + break; + } + } + $data = [ 'min' => array_key_first($list), 'max' => array_key_last($list), 'data' => $list, 'value' => $values, 'currency' => $this->getCurrencyCode(), + 'defaultIndex' => $defaultIndex, ]; if ($this->dataHelper->getApiType() === Data::API_ENDPOINT_CROATIA) { @@ -560,6 +777,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 +798,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 +807,131 @@ 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 + { + if (!$amount) { + return 0; + } + + // Important: return max available period even when amount is under threshold. + // Under-threshold selection (default installment count) is handled elsewhere. + $targetGroup = $group ?: $this->getGroup(); + $installmentList = $this->resourceModel->getInstallmentList($amount, $targetGroup); + $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 +952,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/Setup/Patch/Data/UpdateInstallmentAppearanceDefaults.php b/Setup/Patch/Data/UpdateInstallmentAppearanceDefaults.php new file mode 100644 index 0000000..0a7b9c3 --- /dev/null +++ b/Setup/Patch/Data/UpdateInstallmentAppearanceDefaults.php @@ -0,0 +1,91 @@ + + * @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 UpdateInstallmentAppearanceDefaults implements DataPatchInterface +{ + private const UPDATES = [ + [ + 'path' => 'payment/leanpay_installment/color', + 'old' => '#F58466', + 'new' => '#EB5A7A', + ], + [ + 'path' => 'payment/leanpay_installment/background_color', + 'old' => '#F58466', + 'new' => '#EB5A7A', + ], + [ + 'path' => 'payment/leanpay_installment/font_size_product_page', + 'old' => '15', + 'new' => '14', + ], + [ + 'path' => 'payment/leanpay_installment/font_size_catalog_page', + 'old' => '15', + 'new' => '12', + ], + [ + 'path' => 'payment/leanpay_installment/font_size_homepage', + 'old' => '15', + 'new' => '12', + ], + ]; + + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var ResourceConnection + */ + private $resource; + + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + ResourceConnection $resource + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->resource = $resource; + } + + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + + $connection = $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + $table = $connection->getTableName('core_config_data'); + + foreach (self::UPDATES as $update) { + $connection->update( + $table, + ['value' => $update['new']], + ['path = ?' => $update['path'], 'value = ?' => $update['old']] + ); + } + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + public static function getDependencies() + { + return []; + } + + public function getAliases(): array + { + return []; + } +} + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 913f7e6..7b067d1 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -146,12 +146,12 @@ - Leanpay: #F58466 + Leanpay: #EB5A7A - Leanpay: #F58466 + Leanpay: #EB5A7A @@ -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..a93235f 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -22,15 +22,23 @@ Leanpay\Payment\Model\Method\LeanpayInstallment - #F58466 - #F58466 + #EB5A7A + #EB5A7A 15 - 15 - 15 + 14 + 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..181e2ba 100644 --- a/i18n/hr_HR.csv +++ b/i18n/hr_HR.csv @@ -12,3 +12,8 @@ "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" +"mes.","mj." +"Izračun","Számold ki" diff --git a/i18n/ro_RO.csv b/i18n/ro_RO.csv index 08a191c..ac78866 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." +"Izračun","Calculează" diff --git a/i18n/sl_SL.csv b/i18n/sl_SL.csv index 214735e..d34143a 100644 --- a/i18n/sl_SL.csv +++ b/i18n/sl_SL.csv @@ -12,3 +12,9 @@ "Preveri svoj limit", "Več informacij", "ali", +"obroki","obroki" +"od","od" +"from %1 %2 /mesec","od %1 %2 /mesec" +"/mesec","/mesec" +"mes.","mes." +"Izračun", 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..cf38835 100644 --- a/view/frontend/templates/pricing/render/installment.phtml +++ b/view/frontend/templates/pricing/render/installment.phtml @@ -23,41 +23,105 @@ $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); + $isWithinThreshold = $helper->isWithinThreshold($amount); + $currentPeriod = $isWithinThreshold + ? $helper->getEffectiveDefaultInstallmentCount($amount, (string) $vendorProductName) + : $helper->getInstallmentPeriodForPrice($amount, $price, $vendorProductName); + ?> +
+
+
+ +
+
+
+ + + escapeHtml($helper->getProductPriceBlock($amount, $price)); ?> + + 0): ?> + + escapeHtml(' x ' . $currentPeriod . ' ' . ($currentPeriod === 1 ? __('obrok') : __('obroki'))); ?> + + + + + escapeHtml(__('od ') ); ?> + + + escapeHtml($helper->getProductPriceOnly($amount, $price)); ?> + + + escapeHtml(' ' . __('/mesec')); ?> + + +
+ 0): ?> +
+ + escapeHtml(__('Razdeli na do %1 obrokov', $maxPeriod)); ?> + + escapeHtml(__('0% obresti, 0 stroškov')); ?> + + +
+ +
+
@@ -71,78 +135,54 @@ $amount = intval(round($block->getAmount()));
-
- - - - - - -
+