diff --git a/features/being_unable_to_refund_free_order.feature b/features/being_unable_to_refund_free_order.feature new file mode 100644 index 000000000..ea820721f --- /dev/null +++ b/features/being_unable_to_refund_free_order.feature @@ -0,0 +1,28 @@ +@refunds +Feature: Being unable to refund a free order + In order to give back money to the customer only if it is possible + As an Administrator + I want not to be able to generate a refund for a free order + + Background: + Given the store operates on a single green channel in "United States" + And the store has a free product "Witcher Sword" + And the store ships everywhere for free for all channels + And the store allows paying with "Space money" + And there is a customer "Lucifer Morningstar" that placed an order "#0000001" + And the customer bought 3 "Witcher Sword" products + And the customer "Lucifer Morningstar" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address + And the customer chose "Free" shipping method + And this order is already paid + And I am logged in as an administrator + + @ui + Scenario: Being unable to refund a free order + When I view the summary of the order "#0000001" + Then I should not see refunds button + + @ui + Scenario: Not being able to open refund page when order is free + When I try to refund some units of order "#0000001" + Then I should be redirected to the order "#0000001" show page + And I should be notified that I cannot refund a free order diff --git a/features/preventing_from_refund_free_order.feature b/features/preventing_from_refund_free_order.feature deleted file mode 100644 index fc928b4dd..000000000 --- a/features/preventing_from_refund_free_order.feature +++ /dev/null @@ -1,26 +0,0 @@ -@refunds -Feature: Preventing from refund a free order - In order to give back money to the customer only if it is possible - As an Administrator - I want to not be able to generate refund for free order - - Background: Given the store operates on a single green channel in "United States" - And the store has a free product "Witcher Sword" - And the store allows shipping with "Galaxy Post" - And the store ships everywhere for free for all channels - And the store allows paying with "Space money" - And there is a customer "geralt@netflix.com" that placed an order "#0000001" - And the customer bought 3 "Witcher Sword" products - And the customer chose "Free" shipping method to "United States" with "Space money" payment - And this order is already paid - And I am logged in as an administrator - - @ui - Scenario: Not being able to refund free order - When I am viewing the summary of the order "#0000001" - Then I should not be able to see refunds button - - Scenario: Not being able to open refund page when order is free - When I want to refund some units of order "#0000001" - Then I should be redirected to the order "#0000001" show page - And I should be notified that I cannot refund free order diff --git a/features/refunding_single_order_unit.feature b/features/refunding_single_order_unit.feature index 6e3f3cf90..383660f06 100644 --- a/features/refunding_single_order_unit.feature +++ b/features/refunding_single_order_unit.feature @@ -54,7 +54,7 @@ Feature: Refunding a single order unit @ui Scenario: Not being able to see refunds button When I view the summary of the order "#00000022" - Then I should not be able to see refunds button + Then I should not see refunds button @ui Scenario: Being able to choose only offline payment methods diff --git a/spec/Checker/OrderRefundingAvailabilityCheckerSpec.php b/spec/Checker/OrderRefundingAvailabilityCheckerSpec.php index 8ebc48d7c..d429f2aa7 100644 --- a/spec/Checker/OrderRefundingAvailabilityCheckerSpec.php +++ b/spec/Checker/OrderRefundingAvailabilityCheckerSpec.php @@ -22,32 +22,68 @@ function it_implements_order_refunding_availability_checker_interface(): void $this->shouldImplement(OrderRefundingAvailabilityCheckerInterface::class); } - function it_returns_true_if_order_is_paid( + function it_returns_true_if_order_is_paid_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); + $order->getTotal()->willReturn(100); $this('00000007')->shouldReturn(true); } - function it_returns_true_if_order_is_partialy_refunded( + function it_returns_true_if_order_is_partially_refunded_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PARTIALLY_REFUNDED); + $order->getTotal()->willReturn(100); $this('00000007')->shouldReturn(true); } - function it_returns_false_if_order_is_in_other_state( + function it_returns_false_if_order_is_in_other_state_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT); + $order->getTotal()->willReturn(100); + + $this('00000007')->shouldReturn(false); + } + + function it_returns_false_if_order_is_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); + $order->getTotal()->willReturn(0); + + $this('00000007')->shouldReturn(false); + } + + function it_returns_false_if_order_is_partially_refunded_and_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PARTIALLY_REFUNDED); + $order->getTotal()->willReturn(0); + + $this('00000007')->shouldReturn(false); + } + + function it_returns_false_if_order_is_in_other_state_and_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT); + $order->getTotal()->willReturn(0); $this('00000007')->shouldReturn(false); } diff --git a/spec/Checker/OrderRefundsListAvailabilityCheckerSpec.php b/spec/Checker/OrderRefundsListAvailabilityCheckerSpec.php index 8fe2ea633..04349d211 100644 --- a/spec/Checker/OrderRefundsListAvailabilityCheckerSpec.php +++ b/spec/Checker/OrderRefundsListAvailabilityCheckerSpec.php @@ -22,42 +22,80 @@ function it_implements_order_refunding_availability_checker_interface(): void $this->shouldImplement(OrderRefundingAvailabilityCheckerInterface::class); } - function it_returns_true_if_order_is_paid( + function it_returns_true_if_order_is_paid_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); + $order->getTotal()->willReturn(100); $this('00000007')->shouldReturn(true); } - function it_returns_true_if_order_is_refunded( + function it_returns_true_if_order_is_refunded_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_REFUNDED); + $order->getTotal()->willReturn(100); $this('00000007')->shouldReturn(true); } - function it_returns_true_if_order_is_partialy_refunded( + function it_returns_true_if_order_is_partially_refunded_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PARTIALLY_REFUNDED); + $order->getTotal()->willReturn(100); $this('00000007')->shouldReturn(true); } - function it_returns_false_if_order_is_in_other_state( + function it_returns_false_if_order_is_in_other_state_and_not_free( OrderRepositoryInterface $orderRepository, OrderInterface $order ): void { $orderRepository->findOneByNumber('00000007')->willReturn($order); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT); + $order->getTotal()->willReturn(100); + + $this('00000007')->shouldReturn(false); + } + + + function it_returns_false_if_order_is_paid_and_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); + $order->getTotal()->willReturn(0); + + $this('00000007')->shouldReturn(false); + } + + function it_returns_false_if_order_is_refunded_and_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_REFUNDED); + $order->getTotal()->willReturn(0); + + $this('00000007')->shouldReturn(false); + } + + function it_returns_false_if_order_is_in_other_state_and_free( + OrderRepositoryInterface $orderRepository, + OrderInterface $order + ): void { + $orderRepository->findOneByNumber('00000007')->willReturn($order); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_AWAITING_PAYMENT); + $order->getTotal()->willReturn(0); $this('00000007')->shouldReturn(false); } diff --git a/src/Action/Admin/OrderRefundsListAction.php b/src/Action/Admin/OrderRefundsListAction.php index 9a8fe73c6..cc59f9f10 100644 --- a/src/Action/Admin/OrderRefundsListAction.php +++ b/src/Action/Admin/OrderRefundsListAction.php @@ -57,7 +57,11 @@ public function __invoke(Request $request): Response $order = $this->orderRepository->findOneByNumber($request->attributes->get('orderNumber')); if (!$this->orderRefundsListAvailabilityChecker->__invoke($request->attributes->get('orderNumber'))) { - return $this->redirectToReferer($order); + if ($order->getTotal() === 0) { + return $this->redirectToReferer($order, 'sylius_refund.free_order_should_not_be_refund'); + } + + return $this->redirectToReferer($order, 'sylius_refund.order_should_be_paid'); } return new Response( @@ -68,9 +72,9 @@ public function __invoke(Request $request): Response ); } - private function redirectToReferer(OrderInterface $order): Response + private function redirectToReferer(OrderInterface $order, string $message): Response { - $this->session->getFlashBag()->add('error', 'sylius_refund.order_should_be_paid'); + $this->session->getFlashBag()->add('error', $message); return new RedirectResponse($this->router->generate('sylius_admin_order_show', ['id' => $order->getId()])); } diff --git a/src/Checker/OrderRefundingAvailabilityChecker.php b/src/Checker/OrderRefundingAvailabilityChecker.php index d5e34f62a..393ed15ae 100644 --- a/src/Checker/OrderRefundingAvailabilityChecker.php +++ b/src/Checker/OrderRefundingAvailabilityChecker.php @@ -27,7 +27,8 @@ public function __invoke(string $orderNumber): bool return in_array( $order->getPaymentState(), - [OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED] - ); + [OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED]) && + $order->getTotal() !== 0 + ; } } diff --git a/src/Checker/OrderRefundsListAvailabilityChecker.php b/src/Checker/OrderRefundsListAvailabilityChecker.php index 6198d96f4..e52eb6865 100644 --- a/src/Checker/OrderRefundsListAvailabilityChecker.php +++ b/src/Checker/OrderRefundsListAvailabilityChecker.php @@ -27,7 +27,8 @@ public function __invoke(string $orderNumber): bool return in_array( $order->getPaymentState(), - [OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED, OrderPaymentStates::STATE_REFUNDED] - ); + [OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED, OrderPaymentStates::STATE_REFUNDED]) && + $order->getTotal() !== 0 + ; } } diff --git a/src/Resources/translations/flashes.en.yml b/src/Resources/translations/flashes.en.yml index 97bfa9fbf..4512e6e5c 100644 --- a/src/Resources/translations/flashes.en.yml +++ b/src/Resources/translations/flashes.en.yml @@ -1,5 +1,7 @@ sylius_refund: at_least_one_unit_should_be_selected_to_refund: 'At least one unit should be selected to refund' + error_occurred: 'Unexpected error occurred' + free_order_should_not_be_refund: 'You cannot refund a free order' order_should_be_paid: 'Order should be paid for the units to could be refunded' refund_amount_must_be_greater: 'Refund amount must be greater than 0' refund_amount_must_be_less: 'You cannot refund more money than the refunded unit total' @@ -8,4 +10,3 @@ sylius_refund: resend_credit_memo_success: 'Selected credit memo has been successfully resent' unit_refund_exceeded: 'You cannot refund more money than the order unit total' units_successfully_refunded: 'Selected order units have been successfully refunded' - error_occurred: 'Unexpected error occurred' diff --git a/src/Resources/views/_paymentMethod.html.twig b/src/Resources/views/_paymentMethod.html.twig index 069f7aff0..f601eb8da 100644 --- a/src/Resources/views/_paymentMethod.html.twig +++ b/src/Resources/views/_paymentMethod.html.twig @@ -1,27 +1,29 @@ -{% set original_payment_method = order.payments.first().method %} +{% if order.payments.first() %} + {% set original_payment_method = order.payments.first().method %} -
+ -