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
1 change: 1 addition & 0 deletions core/components/minishop3/lexicon/en/order.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
$_lang['ms3_order_err_delivery'] = 'Delivery method is not selected';
$_lang['ms3_order_err_payment'] = 'Payment method is not selected';
$_lang['ms3_order_err_payment_not_found'] = 'Payment method not found or inactive';
$_lang['ms3_order_err_payment_delivery'] = 'Payment method is not available for the selected delivery method';
$_lang['ms3_order_delivery_id_nf'] = 'Delivery method not found';
$_lang['ms3_order_payment_id_nf'] = 'Payment method not found';

Expand Down
1 change: 1 addition & 0 deletions core/components/minishop3/lexicon/en/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@
$_lang['ms3_order_err_products'] = 'Order has no products';
$_lang['ms3_order_err_delivery_id'] = 'Delivery method is not selected';
$_lang['ms3_order_err_payment_id'] = 'Payment method is not selected';
$_lang['ms3_order_err_payment_delivery'] = 'Payment method is not available for the selected delivery method';
$_lang['ms3_order_err_customer_id'] = 'Customer is not specified';
$_lang['ms3_order_err_customer_contact'] = 'Email or phone is required to create a customer';
$_lang['ms3_order_err_email'] = 'Email is not specified';
Expand Down
1 change: 1 addition & 0 deletions core/components/minishop3/lexicon/ru/order.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
$_lang['ms3_order_err_delivery'] = 'Не выбран способ доставки';
$_lang['ms3_order_err_payment'] = 'Не выбран способ оплаты';
$_lang['ms3_order_err_payment_not_found'] = 'Способ оплаты не найден или неактивен';
$_lang['ms3_order_err_payment_delivery'] = 'Способ оплаты недоступен для выбранной доставки';
$_lang['ms3_order_delivery_id_nf'] = 'Способ доставки не найден';
$_lang['ms3_order_payment_id_nf'] = 'Способ оплаты не найден';

Expand Down
1 change: 1 addition & 0 deletions core/components/minishop3/lexicon/ru/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@
$_lang['ms3_order_err_products'] = 'В заказе нет товаров';
$_lang['ms3_order_err_delivery_id'] = 'Не выбран способ доставки';
$_lang['ms3_order_err_payment_id'] = 'Не выбран способ оплаты';
$_lang['ms3_order_err_payment_delivery'] = 'Способ оплаты недоступен для выбранной доставки';
$_lang['ms3_order_err_customer_id'] = 'Не указан покупатель';
$_lang['ms3_order_err_customer_contact'] = 'Для создания клиента необходимо указать email или телефон';
$_lang['ms3_order_err_email'] = 'Не указан email';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use MiniShop3\Services\ExtraFields\RepeaterFieldService;
use MiniShop3\Services\CustomerDuplicateChecker;
use MiniShop3\Services\CustomerFactory;
use MiniShop3\Services\Delivery\DeliveryService;
use MiniShop3\Services\FilterConfigManager;
use MiniShop3\Services\Order\ManagerOrderCostRecalculator;
use MiniShop3\Services\Order\OrderLogService;
Expand Down Expand Up @@ -645,6 +646,14 @@ public function create(array $params = []): array
$order->set('cost', $orderService->clampComputedTotal(null, 0.0, $deliveryCost, 0.0));
$order->set('weight', 0);

$pairError = $this->validateDeliveryPaymentPair(
(int) $order->get('delivery_id'),
(int) $order->get('payment_id')
);
if ($pairError !== null) {
return $pairError;
}

if (!$order->save()) {
return Response::error('Failed to create order', HttpStatus::INTERNAL_SERVER_ERROR)->getData();
}
Expand Down Expand Up @@ -849,6 +858,14 @@ public function update(array $params = []): array
$order->set($extraFieldKey, $newValue);
}

$pairError = $this->validateDeliveryPaymentPair(
(int) $order->get('delivery_id'),
(int) $order->get('payment_id')
);
if ($pairError !== null) {
return $pairError;
}

$order->set('updatedon', date('Y-m-d H:i:s'));

if (!$order->save()) {
Expand Down Expand Up @@ -1833,4 +1850,25 @@ protected function getModelFieldNames(string $model): array

return $names;
}

/**
* Reject incompatible delivery/payment pairs (msDeliveryMember link required).
*
* @return array|null Response error payload or null when valid / incomplete pair
*/
protected function validateDeliveryPaymentPair(int $deliveryId, int $paymentId): ?array
{
if ($deliveryId <= 0 || $paymentId <= 0) {
return null;
}

/** @var DeliveryService $deliveryService */
$deliveryService = $this->modx->services->get('ms3_delivery_service');
$pairError = $deliveryService->getDeliveryPaymentPairError($deliveryId, $paymentId);
if ($pairError === null) {
return null;
}

return Response::error($pairError, HttpStatus::UNPROCESSABLE_ENTITY)->getData();
}
}
12 changes: 3 additions & 9 deletions core/components/minishop3/src/Controllers/Order/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
namespace MiniShop3\Controllers\Order;

use MiniShop3\MiniShop3;
use MiniShop3\Model\msDeliveryMember;
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msOrderLog;
use MiniShop3\Model\msPayment;
use MiniShop3\Services\Order\OrderAddressManager;
use MiniShop3\Services\Order\OrderCostCalculator;
use MiniShop3\Services\Order\OrderDraftManager;
Expand Down Expand Up @@ -489,14 +487,10 @@ public function getNewOrderNum(): string
*/
public function hasPayment(int $delivery, int $payment): bool
{
$q = $this->modx->newQuery(msPayment::class, ['id' => $payment, 'active' => 1]);
$q->innerJoin(
msDeliveryMember::class,
'Member',
'Member.payment_id = msPayment.id AND Member.delivery_id = ' . $delivery
);
/** @var \MiniShop3\Services\Delivery\DeliveryService $deliveryService */
$deliveryService = $this->modx->services->get('ms3_delivery_service');

return (bool)$this->modx->getCount(msPayment::class, $q);
return $deliveryService->isPaymentAvailableForDelivery($delivery, $payment);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,43 @@ public function calculateDeliveryCost(
return (float)$controller->getCost($order, $delivery, $cost);
}

/**
* Check whether payment method is linked to delivery and active.
*/
public function isPaymentAvailableForDelivery(int $deliveryId, int $paymentId): bool
{
if ($deliveryId <= 0 || $paymentId <= 0) {
return false;
}

$payment = $this->modx->getObject(msPayment::class, [
'id' => $paymentId,
'active' => 1,
]);
if (!$payment) {
return false;
}

return (bool) $this->modx->getCount(msDeliveryMember::class, [
'delivery_id' => $deliveryId,
'payment_id' => $paymentId,
]);
}

/**
* Lexicon key when payment is not linked to delivery, or null if pair is valid/incomplete.
*/
public function getDeliveryPaymentPairError(int $deliveryId, int $paymentId): ?string
{
if ($deliveryId <= 0 || $paymentId <= 0) {
return null;
}

return $this->isPaymentAvailableForDelivery($deliveryId, $paymentId)
? null
: 'ms3_order_err_payment_delivery';
}

/**
* Get first active payment method for delivery
*
Expand Down
15 changes: 15 additions & 0 deletions core/components/minishop3/src/Services/Order/OrderFieldManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,21 @@ public function validate(array $orderData, string $key, mixed $value): array
$response = $this->ms3->utils->invokeEvent('msOnValidateOrderValue', $eventParams);
}

if (in_array($key, ['payment_id', 'delivery_id'], true)) {
$deliveryId = (int) ($key === 'delivery_id' ? $response['data']['value'] : ($orderData['delivery_id'] ?? 0));
$paymentId = (int) ($key === 'payment_id' ? $response['data']['value'] : ($orderData['payment_id'] ?? 0));
/** @var \MiniShop3\Services\Delivery\DeliveryService $deliveryService */
$deliveryService = $this->modx->services->get('ms3_delivery_service');
$pairError = $deliveryService->getDeliveryPaymentPairError($deliveryId, $paymentId);
if ($pairError !== null) {
return $this->error('', [
'error' => [
$key => $pairError,
],
]);
}
}

return $this->success('', ['value' => $response['data']['value']]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ protected function validate(msOrder $order): array

// Check payment is selected
$paymentId = (int) $order->get('payment_id');
$payment = null;
if ($paymentId > 0) {
$payment = $this->modx->getObject(msPayment::class, [
'id' => $paymentId,
Expand All @@ -241,17 +242,24 @@ protected function validate(msOrder $order): array
$errors[] = 'payment_id';
}

// Return early if basic errors found
if (!empty($errors)) {
return $this->error('ms3_order_err_validation', $errors);
}

/** @var \MiniShop3\Services\Delivery\DeliveryService $deliveryService */
$deliveryService = $this->modx->services->get('ms3_delivery_service');
$pairError = $deliveryService->getDeliveryPaymentPairError($deliveryId, $paymentId);
if ($pairError !== null) {
return $this->error($pairError, ['payment_id', 'delivery_id']);
}

// Check customer is linked (optional - manager can create orders without customer)
// $customerId = (int) $order->get('customer_id');
// if ($customerId === 0) {
// $errors[] = 'customer_id';
// }

// Return early if basic errors found
if (!empty($errors)) {
return $this->error('ms3_order_err_validation', $errors);
}

// Check required fields for delivery
$requiredFieldsErrors = $this->validateDeliveryRequiredFields($order);
if (!empty($requiredFieldsErrors)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ public function submit(
return $this->error('ms3_order_err_payment_not_found', ['payment_id' => $orderData['payment_id']]);
}

/** @var \MiniShop3\Services\Delivery\DeliveryService $deliveryService */
$deliveryService = $this->modx->services->get('ms3_delivery_service');
$pairError = $deliveryService->getDeliveryPaymentPairError(
(int) $orderData['delivery_id'],
(int) $orderData['payment_id']
);
if ($pairError !== null) {
return $this->error($pairError, ['payment_id', 'delivery_id']);
}

// Check required fields for delivery
$requiredResponse = $this->fieldManager->getDeliveryRequiredFields($orderData['delivery_id']);
if (!$requiredResponse['success']) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
* Wiring checks for delivery/payment pair validation (#374).
*
* Run: php tests/DeliveryPaymentAvailabilityTest.php
*/

declare(strict_types=1);

$fail = static function (string $message): never {
fwrite(STDERR, "FAIL: {$message}\n");
exit(1);
};

$assertContains = static function (string $needle, string $haystack, string $case) use ($fail): void {
if (!str_contains($haystack, $needle)) {
$fail($case . ': missing ' . $needle);
}
};

$root = dirname(__DIR__);

$deliveryService = file_get_contents($root . '/src/Services/Delivery/DeliveryService.php');
if ($deliveryService === false) {
$fail('unable to read DeliveryService.php');
}
$assertContains('function getDeliveryPaymentPairError', $deliveryService, 'DeliveryService pair error helper');

$files = [
'OrderSubmitHandler.php' => $root . '/src/Services/Order/OrderSubmitHandler.php',
'OrderFinalizeService.php' => $root . '/src/Services/Order/OrderFinalizeService.php',
'OrderFieldManager.php' => $root . '/src/Services/Order/OrderFieldManager.php',
'OrdersController.php' => $root . '/src/Controllers/Api/Manager/OrdersController.php',
];

foreach ($files as $label => $path) {
$src = file_get_contents($path);
if ($src === false) {
$fail('unable to read ' . $label);
}
$assertContains('getDeliveryPaymentPairError', $src, $label);
}

$order = file_get_contents($root . '/src/Controllers/Order/Order.php');
$assertContains("services->get('ms3_delivery_service')", $order, 'Order::hasPayment delegates');
$assertContains('isPaymentAvailableForDelivery', $order, 'Order::hasPayment uses DeliveryService');

fwrite(STDOUT, "OK DeliveryPaymentAvailabilityTest\n");
exit(0);