Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

## Навигация

- **Текущий месяц:** [Апрель 2026](#апрель-2026) (ниже)
- **Предыдущий месяц:** [Март 2026](#март-2026) (ниже)
- **Текущий месяц:** [Май 2026](#май-2026) (ниже)
- **Предыдущий месяц:** [Апрель 2026](#апрель-2026) (ниже)
- **Ещё раньше:** [Февраль 2026](#февраль-2026), [Январь 2026](#январь-2026) (ниже)
- **Архив по месяцам:**
- [Декабрь 2025](changelogs/2025-12.md)
Expand All @@ -15,6 +15,19 @@

---

## Май 2026

### Разработка

#### ✨ Добавлено

**Отрицательная доп. стоимость доставки и оплаты (#211):**
- Поле `price` у способов доставки и оплаты теперь поддерживает отрицательные фиксированные значения и проценты (`-100%`…`100%`).
- Сохранение настроек доставки и оплаты использует общий нормализатор доп. стоимости, чтобы одинаково обрабатывать `-10`, `-10%`, запятые и лишние символы.
- Подсказки Vue-админки уточняют, что доп. стоимость может быть отрицательной и процентной.

---

## Апрель 2026

### [2026-04-27] 🚀 Версия 1.10.1-beta1
Expand Down
4 changes: 2 additions & 2 deletions core/components/minishop3/lexicon/en/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@
$_lang['ms3_validation_rules'] = 'Validation Rules';
$_lang['ms3_order_validation_rules_help'] = 'JSON validation rules for order fields';
$_lang['ms3_add_cost'] = 'Additional Cost';
$_lang['ms3_add_cost_help'] = 'Additional delivery cost';
$_lang['ms3_add_cost_help'] = 'Additional delivery cost. Can be negative, percentages allowed.';
$_lang['ms3_weight_price_help'] = 'Price per weight unit';
$_lang['ms3_free_delivery_amount_help'] = 'Order amount for free delivery';
$_lang['ms3_distance_price'] = 'Distance Price';
Expand All @@ -800,7 +800,7 @@
$_lang['payment_class'] = 'Handler class';
$_lang['payment_class_placeholder'] = 'Full class name, e.g.: MiniShop3\\Payment\\Cash';
$_lang['ms3_payment'] = 'Payment';
$_lang['ms3_payment_add_cost_help'] = 'Additional cost when selecting this payment method';
$_lang['ms3_payment_add_cost_help'] = 'Additional cost when selecting this payment method. Can be negative, percentages allowed.';
$_lang['payment_name_required'] = 'Payment method name is required';
$_lang['payment_created'] = 'Payment method created';
$_lang['payment_updated'] = 'Payment method updated';
Expand Down
4 changes: 2 additions & 2 deletions core/components/minishop3/lexicon/ru/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@
$_lang['ms3_validation_rules'] = 'Правила валидации';
$_lang['ms3_order_validation_rules_help'] = 'JSON правила валидации полей заказа';
$_lang['ms3_add_cost'] = 'Доп. стоимость';
$_lang['ms3_add_cost_help'] = 'Дополнительная стоимость доставки';
$_lang['ms3_add_cost_help'] = 'Дополнительная стоимость доставки. Может быть отрицательной, можно указывать проценты.';
$_lang['ms3_weight_price_help'] = 'Цена за единицу веса';
$_lang['ms3_free_delivery_amount_help'] = 'Сумма заказа для бесплатной доставки';
$_lang['ms3_distance_price'] = 'Цена за расстояние';
Expand All @@ -805,7 +805,7 @@
$_lang['payment_class'] = 'Класс обработчика';
$_lang['payment_class_placeholder'] = 'Полное имя класса, например: MiniShop3\\Payment\\Cash';
$_lang['ms3_payment'] = 'Оплата';
$_lang['ms3_payment_add_cost_help'] = 'Дополнительная стоимость при выборе этого способа оплаты';
$_lang['ms3_payment_add_cost_help'] = 'Дополнительная стоимость при выборе этого способа оплаты. Может быть отрицательной, можно указывать проценты.';
$_lang['payment_name_required'] = 'Укажите название способа оплаты';
$_lang['payment_created'] = 'Способ оплаты создан';
$_lang['payment_updated'] = 'Способ оплаты обновлён';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MiniShop3\Model\msDelivery;
use MiniShop3\Model\msDeliveryMember;
use MiniShop3\Router\Response;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\modX;

/**
Expand Down Expand Up @@ -131,7 +132,7 @@ public function create(array $data = []): array

foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$delivery->set($field, $data[$field]);
$delivery->set($field, $this->prepareFieldValue($field, $data[$field]));
}
}

Expand Down Expand Up @@ -184,7 +185,7 @@ public function update(array $data = []): array

foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$delivery->set($field, $data[$field]);
$delivery->set($field, $this->prepareFieldValue($field, $data[$field]));
}
}

Expand Down Expand Up @@ -490,4 +491,9 @@ protected function updateDeliveryPayments(int $deliveryId, array $paymentIds): v
$member->save();
}
}

protected function prepareFieldValue(string $field, $value)
{
return $field === 'price' ? PriceAdjustment::normalize($value) : $value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MiniShop3\Model\msPayment;
use MiniShop3\Model\msDeliveryMember;
use MiniShop3\Router\Response;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\modX;

/**
Expand Down Expand Up @@ -122,7 +123,7 @@ public function create(array $data = []): array

foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$payment->set($field, $data[$field]);
$payment->set($field, $this->prepareFieldValue($field, $data[$field]));
}
}

Expand Down Expand Up @@ -171,7 +172,7 @@ public function update(array $data = []): array

foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$payment->set($field, $data[$field]);
$payment->set($field, $this->prepareFieldValue($field, $data[$field]));
}
}

Expand Down Expand Up @@ -446,4 +447,9 @@ public function removeDelivery(array $params = []): array

return Response::success([], 'Delivery removed from payment')->getData();
}

protected function prepareFieldValue(string $field, $value)
{
return $field === 'price' ? PriceAdjustment::normalize($value) : $value;
}
}
31 changes: 10 additions & 21 deletions core/components/minishop3/src/Controllers/Delivery/Delivery.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MiniShop3\MiniShop3;
use MiniShop3\Model\msDelivery;
use MiniShop3\Model\msOrder;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\modX;

/**
Expand Down Expand Up @@ -126,34 +127,22 @@ public function getCost(msOrder $order, msDelivery $delivery, float $cost): floa
return $deliveryCost;
}

// Percentage cost
if (str_ends_with($addPrice, '%')) {
$percent = (float)str_replace('%', '', $addPrice);

// Validate 0-100% range
if ($percent < 0 || $percent > 100) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"[Delivery] Invalid percent value for delivery #{$delivery->get('id')}: {$percent}%. Must be 0-100%."
);
return $deliveryCost;
}

$addPrice = $cost / 100 * $percent;
} else {
// Fixed cost
$addPrice = (float)$addPrice;

if ($addPrice < 0) {
if (PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"[Delivery] Invalid fixed price for delivery #{$delivery->get('id')}: {$addPrice}. Must be >= 0."
sprintf(
'[Delivery] Invalid percent value for delivery #%s: %s%%. Must be between -100%% and 100%%.',
$delivery->get('id'),
$percent
)
);
return $deliveryCost;
}
}

return $deliveryCost + $addPrice;
return $deliveryCost + PriceAdjustment::calculate($cost, $addPrice);
}

/**
Expand Down
36 changes: 12 additions & 24 deletions core/components/minishop3/src/Controllers/Payment/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MiniShop3\MiniShop3;
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msPayment;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\modX;

/**
Expand Down Expand Up @@ -141,41 +142,28 @@ abstract public function receive(msOrder $order): array;
*/
public function getCost(msOrder $order, msPayment $payment, float $cost): float
{
$add_price = $payment->get('price');
$addPrice = $payment->get('price');

if (empty($add_price)) {
if (empty($addPrice)) {
return $cost;
}

// Percentage fee
if (str_ends_with($add_price, '%')) {
$percent = (float)str_replace('%', '', $add_price);

// Validate 0-100% range
if ($percent < 0 || $percent > 100) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"[Payment] Invalid percent value for payment #{$payment->get('id')}: {$percent}%. Must be 0-100%."
);
return $cost;
}

$add_price = $cost / 100 * $percent;
} else {
// Fixed fee
$add_price = (float)$add_price;

// Validate non-negative
if ($add_price < 0) {
if (PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
"[Payment] Invalid fixed price for payment #{$payment->get('id')}: {$add_price}. Must be >= 0."
sprintf(
'[Payment] Invalid percent value for payment #%s: %s%%. Must be between -100%% and 100%%.',
$payment->get('id'),
$percent
)
);
return $cost;
}
}

return $cost + $add_price;
return $cost + PriceAdjustment::calculate($cost, $addPrice);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
namespace MiniShop3\Processors\Settings\Delivery;

use MiniShop3\Model\msDelivery;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\Processors\Model\CreateProcessor;

class Create extends CreateProcessor
class Create extends CreateProcessor
{
/** @var msDelivery $object */
public $object;
Expand Down Expand Up @@ -33,8 +34,7 @@ public function beforeSet()

$prices = ['price', 'distance_price', 'weight_price', 'free_delivery_amount'];
foreach ($prices as $field) {
$tmp = $this->preparePrice($tmp);
$this->setProperty($field, $tmp);
$this->setProperty($field, PriceAdjustment::normalize($this->getProperty($field, 0)));
}

return !$this->hasErrors();
Expand All @@ -52,23 +52,4 @@ public function beforeSave()

return parent::beforeSave();
}

public function preparePrice($price = 0)
{
$sign = '';
$price = preg_replace(['#[^\d%\-,\.]#', '#,#'], ['', '.'], $price);
if (strpos($price, '-') !== false) {
$price = str_replace('-', '', $price);
$sign = '-';
}
if (strpos($price, '%') !== false) {
$price = str_replace('%', '', $price) . '%';
}
$price = $sign . $price;
if (empty($price)) {
$price = 0;
}

return $price;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MiniShop3\Processors\Settings\Delivery;

use MiniShop3\Model\msDelivery;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\Processors\Model\UpdateProcessor;

class Update extends UpdateProcessor
Expand Down Expand Up @@ -38,31 +39,12 @@ public function beforeSet()

$prices = ['price', 'distance_price', 'weight_price', 'free_delivery_amount'];
foreach ($prices as $field) {
if ($tmp = $this->getProperty($field)) {
$tmp = $this->preparePrice($tmp);
$this->setProperty($field, $tmp);
$price = $this->getProperty($field);
if ($price !== null) {
$this->setProperty($field, PriceAdjustment::normalize($price));
}
}

return !$this->hasErrors();
}

public function preparePrice($price = 0)
{
$sign = '';
$price = preg_replace(['#[^\d%\-,\.]#', '#,#'], ['', '.'], $price);
if (strpos($price, '-') !== false) {
$price = str_replace('-', '', $price);
$sign = '-';
}
if (strpos($price, '%') !== false) {
$price = str_replace('%', '', $price) . '%';
}
$price = $sign . $price;
if (empty($price)) {
$price = 0;
}

return $price;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace MiniShop3\Processors\Settings\Payment;

use MiniShop3\Model\msDelivery;
use MiniShop3\Model\msPayment;
use MiniShop3\Utils\PriceAdjustment;
use MODX\Revolution\Processors\Model\CreateProcessor;

class Create extends CreateProcessor
class Create extends CreateProcessor
{
/** @var msPayment $object */
public $object;
Expand Down Expand Up @@ -34,10 +34,7 @@ public function beforeSet()

$prices = ['price'];
foreach ($prices as $field) {
if ($tmp = $this->getProperty($field)) {
$tmp = $this->preparePrice($tmp);
$this->setProperty($field, $tmp);
}
$this->setProperty($field, PriceAdjustment::normalize($this->getProperty($field, 0)));
}

return !$this->hasErrors();
Expand All @@ -55,23 +52,4 @@ public function beforeSave()

return parent::beforeSave();
}

public function preparePrice($price = 0)
{
$sign = '';
$price = preg_replace(['#[^\d%\-,\.]#', '#,#'], ['', '.'], $price);
if (strpos($price, '-') !== false) {
$price = str_replace('-', '', $price);
$sign = '-';
}
if (strpos($price, '%') !== false) {
$price = str_replace('%', '', $price) . '%';
}
$price = $sign . $price;
if (empty($price)) {
$price = 0;
}

return $price;
}
}
Loading