Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refunds] Disable refund when order is free #180

Merged
merged 2 commits into from
Jan 31, 2020
Merged
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
28 changes: 28 additions & 0 deletions features/being_unable_to_refund_free_order.feature
Original file line number Diff line number Diff line change
@@ -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: Being unable 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
2 changes: 1 addition & 1 deletion features/refunding_single_order_unit.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 39 additions & 3 deletions spec/Checker/OrderRefundingAvailabilityCheckerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
45 changes: 41 additions & 4 deletions spec/Checker/OrderRefundsListAvailabilityCheckerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,79 @@ 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);
}
Expand Down
10 changes: 7 additions & 3 deletions src/Action/Admin/OrderRefundsListAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

GSadee marked this conversation as resolved.
Show resolved Hide resolved
return $this->redirectToReferer($order, 'sylius_refund.order_should_be_paid');
}

return new Response(
Expand All @@ -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()]));
}
Expand Down
8 changes: 4 additions & 4 deletions src/Checker/OrderRefundingAvailabilityChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public function __invoke(string $orderNumber): bool
$order = $this->orderRepository->findOneByNumber($orderNumber);
Assert::notNull($order);

return in_array(
$order->getPaymentState(),
[OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED]
);
return in_array($order->getPaymentState(), [
OrderPaymentStates::STATE_PAID,
OrderPaymentStates::STATE_PARTIALLY_REFUNDED,
]) && $order->getTotal() !== 0;
}
}
9 changes: 5 additions & 4 deletions src/Checker/OrderRefundsListAvailabilityChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ public function __invoke(string $orderNumber): bool
$order = $this->orderRepository->findOneByNumber($orderNumber);
Assert::notNull($order);

return in_array(
$order->getPaymentState(),
[OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED, OrderPaymentStates::STATE_REFUNDED]
);
return in_array($order->getPaymentState(), [
OrderPaymentStates::STATE_PAID,
OrderPaymentStates::STATE_PARTIALLY_REFUNDED,
OrderPaymentStates::STATE_REFUNDED,
]) && $order->getTotal() !== 0;
}
}
3 changes: 2 additions & 1 deletion src/Resources/translations/flashes.en.yml
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
44 changes: 23 additions & 21 deletions src/Resources/views/_paymentMethod.html.twig
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
{% set original_payment_method = order.payments.first().method %}
{% if order.payments.first() %}
{% set original_payment_method = order.payments.first().method %}

<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>

<div class="ui stackable grid">
<div class="two column row">
<div class="ui form column">
<div class="field">
<label for="payment-methods">{{ 'sylius.ui.payment_method'|trans }}</label>
<select id="payment-methods" name="sylius_refund_payment_method" class="ui fluid selection dropdown">
{% for payment_method in payment_methods %}
<option value="{{ payment_method.id }}" {{ (payment_method.code == original_payment_method.code) ? 'selected="selected"' : '' }}>
{{ payment_method.name }}
</option>
{% endfor %}
</select>
<small>{{ 'sylius.ui.original_payment_method'|trans }}: <strong>{{ original_payment_method }}</strong></small>
<div class="ui stackable grid">
<div class="two column row">
<div class="ui form column">
<div class="field">
<label for="payment-methods">{{ 'sylius.ui.payment_method'|trans }}</label>
<select id="payment-methods" name="sylius_refund_payment_method" class="ui fluid selection dropdown">
{% for payment_method in payment_methods %}
<option value="{{ payment_method.id }}" {{ (payment_method.code == original_payment_method.code) ? 'selected="selected"' : '' }}>
{{ payment_method.name }}
</option>
{% endfor %}
</select>
<small>{{ 'sylius.ui.original_payment_method'|trans }}: <strong>{{ original_payment_method }}</strong></small>
</div>
</div>
</div>
<div class="ui form column">
<div class="field">
<label for="sylius-refund-comment">{{ 'sylius.ui.comment'|trans }}</label>
<textarea rows="3" name="sylius_refund_comment" id="sylius-refund-comment"></textarea>
<div class="ui form column">
<div class="field">
<label for="sylius-refund-comment">{{ 'sylius.ui.comment'|trans }}</label>
<textarea rows="3" name="sylius_refund_comment" id="sylius-refund-comment"></textarea>
</div>
</div>
</div>
</div>
</div>
{% endif %}
36 changes: 36 additions & 0 deletions tests/Behat/Context/Setup/ProductContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Tests\Sylius\RefundPlugin\Behat\Context\Setup;

use Behat\Behat\Context\Context;
use Sylius\Behat\Context\Setup\ProductContext as BaseProductContext;

final class ProductContext implements Context
{
/** @var BaseProductContext */
private $baseProductContext;

public function __construct(BaseProductContext $baseProductContext)
{
$this->baseProductContext = $baseProductContext;
}

/**
* @Given the store has a free product :productName
*/
public function theStoreHasAFreeProduct(string $productName): void
{
$this->baseProductContext->storeHasAProductPricedAt($productName, 0, null);
}
}
23 changes: 21 additions & 2 deletions tests/Behat/Context/Ui/ManagingOrdersContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public function shouldBeNotifiedThatTheOrderShouldBePaid(): void
}

/**
* @Then I should not be able to see refunds button
* @Then I should not see refunds button
*/
public function shouldNotBeAbleToSeeRefundsButton(): void
public function iShouldNotSeeRefundsButton(): void
{
Assert::false($this->showPage->hasRefundsButton());
}
Expand Down Expand Up @@ -106,4 +106,23 @@ public function thisOrderSPaymentStateShouldBe(OrderInterface $order, string $or
'paymentState' => $orderPaymentState,
]));
}

/**
* @Then I should be redirected to the order :order show page
*/
public function iShouldBeRedirectedToTheOrderShowPage(OrderInterface $order): void
{
Assert::true($this->showPage->isOpen(['id' => $order->getId()]));
}

/**
* @Then I should be notified that I cannot refund a free order
*/
public function iShouldBeNotifiedThatICannotRefundAFreeOrder(): void
{
$this->notificationChecker->checkNotification(
'You cannot refund a free order',
NotificationType::failure()
);
}
}
4 changes: 4 additions & 0 deletions tests/Behat/Resources/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,9 @@
<service id="Tests\Sylius\RefundPlugin\Behat\Services\Provider\MessagesProvider">
<argument>%kernel.cache_dir%/spool</argument>
</service>

<service id="Tests\Sylius\RefundPlugin\Behat\Context\Setup\ProductContext">
<argument type="service" id="sylius.behat.context.setup.product" />
</service>
</services>
</container>
Loading