diff --git a/core/components/minishop3/lexicon/en/order.inc.php b/core/components/minishop3/lexicon/en/order.inc.php index a9e1f690..9d3a39b3 100644 --- a/core/components/minishop3/lexicon/en/order.inc.php +++ b/core/components/minishop3/lexicon/en/order.inc.php @@ -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'; diff --git a/core/components/minishop3/lexicon/en/vue.inc.php b/core/components/minishop3/lexicon/en/vue.inc.php index ce59b211..83bde809 100644 --- a/core/components/minishop3/lexicon/en/vue.inc.php +++ b/core/components/minishop3/lexicon/en/vue.inc.php @@ -775,6 +775,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'; diff --git a/core/components/minishop3/lexicon/ru/order.inc.php b/core/components/minishop3/lexicon/ru/order.inc.php index a020f495..d318ed9a 100644 --- a/core/components/minishop3/lexicon/ru/order.inc.php +++ b/core/components/minishop3/lexicon/ru/order.inc.php @@ -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'] = 'Способ оплаты не найден'; diff --git a/core/components/minishop3/lexicon/ru/vue.inc.php b/core/components/minishop3/lexicon/ru/vue.inc.php index fcb1d219..fb2b22bb 100644 --- a/core/components/minishop3/lexicon/ru/vue.inc.php +++ b/core/components/minishop3/lexicon/ru/vue.inc.php @@ -774,6 +774,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'; diff --git a/core/components/minishop3/src/Controllers/Api/Manager/OrdersController.php b/core/components/minishop3/src/Controllers/Api/Manager/OrdersController.php index 283e347f..2d621c12 100644 --- a/core/components/minishop3/src/Controllers/Api/Manager/OrdersController.php +++ b/core/components/minishop3/src/Controllers/Api/Manager/OrdersController.php @@ -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\Grid\ManagerListFilterPolicy; use MiniShop3\Services\Order\ManagerOrderCostRecalculator; @@ -646,6 +647,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(); } @@ -850,6 +859,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()) { @@ -1801,4 +1818,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(); + } } diff --git a/core/components/minishop3/src/Controllers/Order/Order.php b/core/components/minishop3/src/Controllers/Order/Order.php index f8e8b5db..4d10d3d9 100644 --- a/core/components/minishop3/src/Controllers/Order/Order.php +++ b/core/components/minishop3/src/Controllers/Order/Order.php @@ -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; @@ -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); } /** diff --git a/core/components/minishop3/src/Services/Delivery/DeliveryService.php b/core/components/minishop3/src/Services/Delivery/DeliveryService.php index ff6bbf95..8393d59f 100644 --- a/core/components/minishop3/src/Services/Delivery/DeliveryService.php +++ b/core/components/minishop3/src/Services/Delivery/DeliveryService.php @@ -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 * diff --git a/core/components/minishop3/src/Services/Order/OrderFieldManager.php b/core/components/minishop3/src/Services/Order/OrderFieldManager.php index 563bdc67..32b06422 100644 --- a/core/components/minishop3/src/Services/Order/OrderFieldManager.php +++ b/core/components/minishop3/src/Services/Order/OrderFieldManager.php @@ -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']]); } diff --git a/core/components/minishop3/src/Services/Order/OrderFinalizeService.php b/core/components/minishop3/src/Services/Order/OrderFinalizeService.php index 2f94f1b2..bb8726e9 100644 --- a/core/components/minishop3/src/Services/Order/OrderFinalizeService.php +++ b/core/components/minishop3/src/Services/Order/OrderFinalizeService.php @@ -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, @@ -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)) { diff --git a/core/components/minishop3/src/Services/Order/OrderSubmitHandler.php b/core/components/minishop3/src/Services/Order/OrderSubmitHandler.php index 7381647d..b531ff3d 100644 --- a/core/components/minishop3/src/Services/Order/OrderSubmitHandler.php +++ b/core/components/minishop3/src/Services/Order/OrderSubmitHandler.php @@ -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']) { diff --git a/core/components/minishop3/tests/DeliveryPaymentAvailabilityTest.php b/core/components/minishop3/tests/DeliveryPaymentAvailabilityTest.php new file mode 100644 index 00000000..e9a8bf36 --- /dev/null +++ b/core/components/minishop3/tests/DeliveryPaymentAvailabilityTest.php @@ -0,0 +1,50 @@ + $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);