diff --git a/Helper/InstallmentHelper.php b/Helper/InstallmentHelper.php
index 19ed169..e05fae1 100644
--- a/Helper/InstallmentHelper.php
+++ b/Helper/InstallmentHelper.php
@@ -85,6 +85,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
@@ -255,6 +285,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
*
@@ -353,14 +534,34 @@ public function getLowestInstallmentPrice(float $price, $group = '')
}
$roundedPrice = $this->applyProductRounding($price);
-
- return $this->resourceModel->getLowestInstallment($roundedPrice, $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($roundedPrice, $group, $this->dataHelper->getApiType());
}
/**
* Get installment list
*
* @param float $price
+ * @param string $group
* @return array
*/
public function getInstallmentList(float $price, $group = '')
@@ -483,11 +684,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 '€';
}
}
@@ -525,6 +726,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);
@@ -532,12 +736,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) {
@@ -604,6 +821,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 {
@@ -614,7 +842,7 @@ public function getCategoryPriceBlock(float $amount, $preCalculatedValue = 0): \
return __(
'od %1 %2 / %3 %4 mjesečno',
$price,
- 'EUR',
+ '€',
$this->getTransitionPrice($price, 'HRK'),
'HRK'
);
@@ -623,6 +851,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
@@ -643,7 +996,7 @@ public function getProductPriceBlock(float $amount, $preCalculatedValue = 0): \M
__(
'%1 %2 / %3 %4',
$price,
- 'EUR',
+ '€',
$this->getTransitionPrice($price, 'HRK'),
'HRK'
);
diff --git a/Model/Method/LeanpayInstallment.php b/Model/Method/LeanpayInstallment.php
index e4fffc6..21e5a63 100644
--- a/Model/Method/LeanpayInstallment.php
+++ b/Model/Method/LeanpayInstallment.php
@@ -6,4 +6,11 @@
class LeanpayInstallment extends Leanpay
{
public const CODE = 'leanpay_installment';
+
+ /**
+ * Payment system code
+ *
+ * @var string
+ */
+ protected $_code = self::CODE;
}
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/composer.json b/composer.json
index d6f53e6..d7dd754 100644
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
"magento/framework": ">=103.0.1"
},
"type": "magento2-module",
- "version": "0.13.0",
+ "version": "0.14.0",
"autoload": {
"files": [
"registration.php"
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 3edf449..e8579b8 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -146,12 +146,12 @@
- Leanpay: #F58466
+ Leanpay: #EB5A7A
- Leanpay: #F58466
+ Leanpay: #EB5A7A
@@ -199,6 +199,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 466be7c..68edb6c 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
+
+
1
diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml
index be5ed67..5b650c8 100644
--- a/etc/csp_whitelist.xml
+++ b/etc/csp_whitelist.xml
@@ -11,6 +11,8 @@
https://vendor.leanpay.ro
https://stage-checkout.leanpay.si
https://stage-checkout.leanpay.ro
+ https://checkout.leanpay.ro
+ https://app.leanpay.ro
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);
?>
-
-
+
= $escaper->escapeHtml($helper->getCategoryPriceBlock($amount, $price)); ?>
+
; ?>)
+
+
+
+
= $escaper->escapeHtml($helper->getUnderThresholdText()); ?>
+
-
= $block->escapeHtml(__('ali')); ?>
-
-
- getInstallmentList($amount, $vendorProductName); ?>
-
- = $escaper->escapeHtml(__('Hitro in enostavno obročno odplačevanje')); ?>
-
-
-
- = $escaper->escapeHtml(__('Že od')); ?>
-
- = $escaper->escapeHtml($helper->getProductPriceBlock($amount, $price)); ?>
-
-
-
- = $escaper->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);
+ ?>
+
+
+
+
; ?>)
+
+
+
+
+
+ = $escaper->escapeHtml($helper->getProductPriceBlock($amount, $price)); ?>
+
+ 0): ?>
+
+ = $escaper->escapeHtml(' x ' . $currentPeriod . ' ' . ($currentPeriod === 1 ? __('obrok') : __('obroki'))); ?>
+
+
+
+
+ = $escaper->escapeHtml(__('od ') ); ?>
+
+
+ = $escaper->escapeHtml($helper->getProductPriceOnly($amount, $price)); ?>
+
+
+ = $escaper->escapeHtml(' ' . __('/mesec')); ?>
+
+
+
+ 0): ?>
+
+
+ = $escaper->escapeHtml(__('Razdeli na do %1 obrokov', $maxPeriod)); ?>
+
+ = $escaper->escapeHtml(__('0% obresti, 0 stroškov')); ?>
+
+
; ?>)
+
+
+
+
@@ -71,78 +135,54 @@ $amount = intval(round($block->getAmount()));
-
-
- = __('Izračun obrokov'); ?>
-
-
-
-
-
+
+
; ?>)
+ alt="leanpay-logo"/>
+
+ = $escaper->escapeHtml($helper->getProductPriceBlock($amount, $price)); ?>
+ = $escaper->escapeHtml(__('/mesec')); ?>
+
-
- shouldRenderTooltipPriceBlock($amount, true)): ?>
-
- = $escaper->escapeHtml(__('Želim čim nižji obrok')); ?>
- = $escaper->escapeHtml($helper->getTooltipPriceBlock($amount, true, $vendorProductName)); ?>
-
-
-
-
- shouldRenderTooltipPriceBlock($amount, false)): ?>
-
- = $escaper->escapeHtml(__('Odplačati želim čim prej')); ?>
- = $escaper->escapeHtml($helper->getTooltipPriceBlock($amount, false, $vendorProductName)); ?>
-
-
-
-
-
- = $escaper->escapeHtml(__('Želim si izbrati svoje obroke')); ?>
-
-
-
-
-
-
+
-
-
- = $escaper->escapeHtml(__('Informativni znesek za plačilo')); ?>
-
-
-
- = $escaper->escapeHtml(
- __('Leanpay omogoča hitro in enostavno obročno odplačevanje preko spleta. ') .
- __('Za obročno plačilo v košarici izberi Leanpay. ')); ?>
-
-
diff --git a/view/frontend/web/css/leanpay.css b/view/frontend/web/css/leanpay.css
index e2fef7e..a2ddb84 100644
--- a/view/frontend/web/css/leanpay.css
+++ b/view/frontend/web/css/leanpay.css
@@ -1,9 +1,161 @@
/** General Purpose **/
+/* Legacy: Logo replaced with payment method title in checkout template
.leanpay img.payment-logo {
vertical-align: middle;
margin-right: 5px;
height: 35px;
}
+*/
+
+.leanpay .payment-method-name {
+ font-weight: 600;
+ font-size: 1em;
+}
+
+/** PLP Installment Price **/
+.installment-price.plp-installment {
+ border-radius: 6px;
+ padding: 8px 12px;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.installment-price.plp-installment .installment-logo {
+ width: auto;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+/** PDP Installment Block **/
+.installment-pdp-block {
+ border-radius: 8px;
+ padding: 12px 16px;
+ margin-bottom: 10px;
+}
+
+.installment-pdp-content {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ justify-content: space-between;
+}
+
+.installment-pdp-content .installment-pdp-left {
+ width: 50px;
+ height: 50px;
+ flex: 0 0 50px;
+}
+
+.installment-pdp-content .installment-pdp-left img {
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: contain;
+}
+
+.installment-pdp-text {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+}
+
+.installment-pdp-price {
+ font-weight: 600;
+}
+
+/* Mobile-first: base styles for mobile */
+.installment-price-amount {
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.installment-price-count {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.installment-price-from {
+ font-size: 16px;
+ font-weight: 400;
+}
+
+.installment-price-month {
+ font-size: 14px;
+}
+
+@media screen and (min-width: 768px) {
+ .installment-price-month {
+ font-size: 16px;
+ }
+}
+
+
+.installment-pdp-period {
+ display: flex;
+ gap: 8px;
+ opacity: 0.9;
+}
+
+.installment-pdp-period .installment-logo {
+ vertical-align: bottom;
+}
+
+.installment-pdp-logo {
+ flex-shrink: 0;
+}
+
+.installment-pdp-logo .installment-logo {
+ width: auto;
+ height: auto;
+ display: block;
+}
+
+.installment-calculate-btn {
+ background-color: #ffffff;
+ opacity: 0.9;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 6px;
+ padding: 4px 8px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: 600;
+ color: #EB5A7A;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ flex-shrink: 0;
+ transition: background-color 0.2s ease;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+}
+
+.installment-calculate-btn:hover,
+.installment-calculate-btn:active,
+.installment-calculate-btn:focus {
+ opacity: 1;
+ color: #EB5A7A;
+ background-color: #fff;
+}
+
+.installment-calculate-btn:active {
+ opacity: 0.8;
+}
+
+.installment-calculate-arrow {
+ width: 30px;
+ height: 30px;
+ display: block;
+ color: #EB5A7A;
+ transition: transform 0.2s ease;
+ flex-shrink: 0;
+ transform: rotate(90deg);
+}
+
+.installment-calculate-btn.active .installment-calculate-arrow {
+ transform: rotate(180deg);
+}
.installment-logo-wrapper {
cursor: pointer;
@@ -40,11 +192,8 @@
}
.leanpay .instructions {
- margin: 0;
-}
-
-.installment-additional-wrapper .installment-tooltip.hidden {
- display: none;
+ margin: 8px 0 0 0;
+ display: block;
}
.installment-min-price .installment-price {
@@ -89,8 +238,7 @@
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
- -ms-flex-direction: column;
- flex-direction: column;
+
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
@@ -103,19 +251,107 @@
.installment-additional-wrapper .installment-tooltip {
-webkit-box-shadow: -2px -1px 10px 3px #a7a7a7;
box-shadow: -2px -1px 10px 3px #a7a7a7;
- border-radius: 3px;
+ border-radius: 4px;
background: #ffffff;
position: absolute;
z-index: 1000;
padding: 15px;
max-width: 75%;
+ transition: all 0.3s ease;
}
-.installment-wrapper {
+/* Tooltip when opened via Calculate button */
+.installment-wrapper.tooltip-opened .installment-tooltip {
position: relative;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+ margin-top: 12px;
+ padding: 20px;
+ max-width: 100%;
+ display: block;
+ background: #ffffff;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 20px;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-title img {
+ height: 30px;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-title-price {
+ font-weight: 600;
+ font-size: 1.1em;
+ color: #333;
+}
+
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-slider-term {
+ margin: 16px 20px;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-quick-information {
+ margin: 15px 0;
+ font-size: 14px;
+ color: #767676;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-links {
+ margin-top: 15px;
+ padding-top: 15px;
border-top: 1px solid #e4e4e4;
}
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-links a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: fit-content;
+ text-align: center;
+ background-color: #EB5A7A;
+ color: #ffffff;
+ padding: 12px 20px;
+ border-radius: 6px;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.installment-wrapper.tooltip-opened .installment-tooltip .installment-links a:hover {
+ opacity: 0.9;
+}
+
+.installment-additional-wrapper .installment-tooltip.hidden {
+ display: none;
+}
+
+.installment-link-arrow {
+ width: 16px;
+ height: 16px;
+ display: block;
+ flex-shrink: 0;
+}
+
+
+
+.installment-wrapper {
+ position: relative;
+ transition: all 0.3s ease;
+}
+
+.installment-tooltip-header {
+ display: none;
+}
+
+.installment-wrapper.tooltip-opened {
+ border-top: none;
+}
+
+
.installment-additional-wrapper .installment-title,
.installment-slider-term .term-html,
.installment-slider-term .total {
@@ -127,6 +363,12 @@
margin: 5px 0;
}
+.installment-additional-wrapper .installment-quick-information .link {
+ text-decoration: underline;
+ color: #333333;
+ font-weight: 600;
+}
+
.installment-wrapper-start {
position: absolute;
top: -11px;
@@ -139,11 +381,6 @@
color: #9c9b9b;
}
-.installment-links a:last-child {
- border-left: 1px solid #a7a7a7;
- padding-left: 5px;
-}
-
.installment-info img {
cursor: pointer;
}
@@ -160,6 +397,115 @@
margin: 10px;
}
+.installment-wrapper.tooltip-opened .installment-slider-term {
+ position: relative;
+ padding: 34px 0 22px;
+ margin: 20px 0;
+}
+
+/* Slider track */
+.installment-wrapper.tooltip-opened .installment-slider.ui-slider-horizontal {
+ background: #F8ACB6;
+ height: 6px;
+ border: none;
+ border-radius: 3px;
+ margin: 0;
+ position: relative;
+ z-index: 2;
+}
+
+/* Slider filled range */
+.installment-wrapper.tooltip-opened .installment-slider .ui-widget-header.ui-corner-all.ui-slider-range-min {
+ height: 6px;
+ background: #EB5A7A;
+ border-radius: 3px;
+}
+
+/* Slider handle */
+.installment-wrapper.tooltip-opened .installment-slider .ui-slider-handle.ui-state-default.ui-corner-all {
+ height: 16px;
+ width: 16px;
+ top: -6px;
+ background: #EB5A7A;
+ margin-left: -10px;
+ border: 2px solid #ffffff;
+ border-radius: 50%;
+ cursor: pointer;
+ outline: none;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
+ z-index: 4;
+}
+
+/* Ticks + labels */
+.installment-wrapper.tooltip-opened .installment-slider-scale {
+ position: absolute;
+ top: 10px;
+ left: 0;
+ right: 0;
+ height: 32px;
+ pointer-events: none;
+ z-index: 3;
+}
+
+.installment-wrapper.tooltip-opened .installment-slider-tick {
+ position: absolute;
+ top: 0;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.installment-wrapper.tooltip-opened .installment-slider-tick-label {
+ font-size: 14px;
+ color: #777;
+ line-height: 1;
+}
+
+.installment-wrapper.tooltip-opened .installment-slider-tick-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: #F8ACB6;
+ /* white ring so dots don't visually connect with the track */
+ box-shadow: 0 0 0 2px #ffffff;
+}
+
+
+/* Remove ring only for the "last active" dot (the one under the handle). CSS-only. */
+.installment-wrapper.tooltip-opened .installment-slider-tick.active:has(+ .installment-slider-tick:not(.active)) .installment-slider-tick-dot,
+.installment-wrapper.tooltip-opened .installment-slider-tick.active:last-child .installment-slider-tick-dot {
+ box-shadow: none;
+}
+
+/* Only highlight the "last active" label (the one under the handle) */
+.installment-wrapper.tooltip-opened .installment-slider-tick.active:has(+ .installment-slider-tick:not(.active)) .installment-slider-tick-label,
+.installment-wrapper.tooltip-opened .installment-slider-tick.active:last-child .installment-slider-tick-label {
+ color: #ffffff;
+ background: #EB5A7A;
+ padding: 2px 4px;
+ margin-top: -4px;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.installment-wrapper.tooltip-opened .installment-slider-tick.active .installment-slider-tick-dot {
+ background: #EB5A7A;
+}
+
+
+.installment-wrapper.tooltip-opened .installment-slider-selected-unit {
+ position: absolute;
+ top: 50px;
+ transform: translateX(-50%);
+ font-size: 12px;
+ color: #777;
+ pointer-events: none;
+ white-space: nowrap;
+ margin-left: 4px;
+}
+
.installment-slider-term .total-html {
display: -webkit-box;
display: -ms-flexbox;
@@ -181,13 +527,13 @@
height: 20px;
width: 20px;
top: -5px;
- background: #F58466;
+ background: #EB5A7A;
cursor: pointer;
}
.installment-slider .ui-widget-header.ui-corner-all.ui-slider-range-min {
height: 10px;
- background: #F58466;
+ background: #EB5A7A;
}
/** Checkout **/
@@ -237,18 +583,216 @@
order: 2;
}
-.checkout-index-index .installment-logo{
- top: 0;
- height: 30px;
+.checkout-index-index .installment-min-price {
+ display: flex;
+ flex-wrap: wrap;
}
-.checkout-index-index .installment-min-price {
+.checkout-index-index .payment-method-title {
display: flex;
+ flex-direction: row;
+ align-items: flex-start;
flex-wrap: wrap;
+ gap: 8px;
+}
+
+.checkout-index-index .payment-method-title > .radio {
+ margin-right: 8px;
+ flex-shrink: 0;
+}
+
+.checkout-index-index .payment-method-title > .label {
+ display: inline-flex;
+ align-items: center;
+ flex: 1;
+}
+
+.checkout-index-index .payment-method-title .installment-checkout-wrapper {
+ width: 100%;
+ order: 2;
+}
+
+.checkout-index-index .payment-method-title .instructions {
+ width: 100%;
+ margin-top: 0;
+ order: 3;
+}
+
+.checkout-index-index .installment-title-price {
+ margin-left: auto;
+}
+
+@media screen and (max-width: 767px) {
+ .installment-pdp-content {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-row-gap: 8px;
+ grid-column-gap: 4px;
+ align-items: start;
+ }
+
+ .installment-pdp-content .installment-pdp-left {
+ display: flex;
+ align-items: center;
+ }
+
+ .installment-pdp-content .installment-pdp-left img {
+ width: 100%;
+ height: 100%;
+ }
+
+ .installment-calculate-btn {
+ grid-column: 1 / -1;
+ width: 100%;
+ justify-content: center;
+ margin-top: 4px;
+ }
+
+ .installment-calculate-arrow {
+ display: block;
+ }
+
+ .installment-wrapper.tooltip-opened {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 10000;
+ }
+
+ .installment-wrapper.tooltip-opened::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 9999;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ max-width: 100%;
+ padding: 0;
+ border-radius: 0;
+ margin: 0;
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
+ max-height: 90vh;
+ overflow-y: auto;
+ z-index: 10001;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #e4e4e4;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip-title {
+ font-weight: 600;
+ font-size: 18px;
+ color: #333;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip-close {
+ background: none;
+ border: none;
+ font-size: 28px;
+ line-height: 1;
+ color: #333;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip-close:hover {
+ opacity: 0.7;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip-close span {
+ line-height: 1;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip > .installment-title {
+ padding: 0 20px;
+ margin-bottom: 16px;
+ margin-top: 20px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip .installment-title-price {
+ font-size: 1em;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider-term {
+ padding: 20px 0 16px;
+ margin: 16px 0;
+ touch-action: pan-x;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip .installment-quick-information {
+ padding: 0 20px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-tooltip .installment-links {
+ padding: 0 20px 20px;
+ margin-top: 20px;
+ padding-top: 20px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider {
+ touch-action: pan-x;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider .ui-slider-handle {
+ touch-action: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+
+ .installment-wrapper.tooltip-opened .installment-tooltip .installment-links a {
+ text-align: center;
+ background-color: #EB5A7A;
+ padding: 12px 0;
+ border-radius: 6px;
+ text-decoration: none;
+ font-weight: 600;
+ width: auto;
+ }
+
+ /* Keep the last tick/label and selected pill inside bounds */
+ .installment-wrapper.tooltip-opened .installment-slider.ui-slider-horizontal {
+ margin: 0 12px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider-scale {
+ top: -4px;
+ left: 12px;
+ right: 12px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider-selected {
+ top: -12px;
+ }
+
+ .installment-wrapper.tooltip-opened .installment-slider-selected-unit {
+ top: 42px;
+ margin-left: 12px;
+ }
+
}
@media screen and (min-width: 1200px) {
- .checkout-index-index .installment-info{
+ .checkout-index-index .installment-info {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
@@ -274,8 +818,7 @@
.checkout-index-index .installment-additional-wrapper {
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
- -ms-flex-direction: row;
- flex-direction: row;
+
max-width: 540px;
}
}
diff --git a/view/frontend/web/images/arrow-right.svg b/view/frontend/web/images/arrow-right.svg
new file mode 100644
index 0000000..cb96803
--- /dev/null
+++ b/view/frontend/web/images/arrow-right.svg
@@ -0,0 +1,5 @@
+
diff --git a/view/frontend/web/images/dropdown.svg b/view/frontend/web/images/dropdown.svg
new file mode 100644
index 0000000..df07f80
--- /dev/null
+++ b/view/frontend/web/images/dropdown.svg
@@ -0,0 +1,3 @@
+
diff --git a/view/frontend/web/images/leanpat_short_logo_0.png b/view/frontend/web/images/leanpat_short_logo_0.png
new file mode 100644
index 0000000..f5ac9a9
Binary files /dev/null and b/view/frontend/web/images/leanpat_short_logo_0.png differ
diff --git a/view/frontend/web/images/leanpay-original.svg b/view/frontend/web/images/leanpay-original.svg
new file mode 100644
index 0000000..1bfeb5c
--- /dev/null
+++ b/view/frontend/web/images/leanpay-original.svg
@@ -0,0 +1,44 @@
+
diff --git a/view/frontend/web/images/leanpay.svg b/view/frontend/web/images/leanpay.svg
index 7991555..7501b69 100644
--- a/view/frontend/web/images/leanpay.svg
+++ b/view/frontend/web/images/leanpay.svg
@@ -1,191 +1,15 @@
-