mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
[PaymentRequest] Automatically cancel all pending payment requests when the payment method gets changed (#15950)
| Q | A | |-----------------|--------------------------------------------------------------| | Branch? | payment-request <!-- see the comment below --> | | Bug fix? | no | | New feature? | yes | | BC breaks? | no | | License | MIT | <!-- - Bug fixes must be submitted against the 1.12 branch - Features and deprecations must be submitted against the 1.13 branch - Make sure that the correct base branch is set To be sure you are not breaking any Backward Compatibilities, check the documentation: https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html -->
This commit is contained in:
commit
e443ce97ba
27 changed files with 631 additions and 16 deletions
|
|
@ -0,0 +1,29 @@
|
|||
@paying_for_order
|
||||
Feature: Cancelling payment request when payment method is changed
|
||||
In order to pay with the correct payment method
|
||||
As an Administrator
|
||||
I want the customer's payment requests with the previous payment method to be cancelled
|
||||
|
||||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store has a product "PHP T-Shirt"
|
||||
And the store ships everywhere for Free
|
||||
And the store allows paying with "Cash on Delivery"
|
||||
And the store also allows paying with "Bank Transfer"
|
||||
And I am a logged in customer
|
||||
And I placed an order "#00000001"
|
||||
And I bought a single "PHP T-Shirt"
|
||||
And I addressed it to "Lucifer Morningstar", "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
|
||||
And I chose "Free" shipping method with "Cash on Delivery" payment
|
||||
And there is a "new" "authorize" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
And there is also a "processing" "status" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
And there is also a "completed" "capture" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
And there is an administrator "sylius@example.com" identified by "sylius"
|
||||
|
||||
@api @ui
|
||||
Scenario: Cancelling only not finalized payment requests when the payment method has changed
|
||||
When I browse my orders
|
||||
And I change my payment method to "Bank Transfer"
|
||||
Then the administrator should see the payment request with action "authorize" for "Cash on Delivery" payment method and state "cancelled"
|
||||
And the administrator should see the payment request with action "status" for "Cash on Delivery" payment method and state "cancelled"
|
||||
And the administrator should see the payment request with action "capture" for "Cash on Delivery" payment method and state "completed"
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
@paying_for_order
|
||||
Feature: Cancelling payment request when payment method is changed
|
||||
In order to pay with the correct payment method
|
||||
As a Customer
|
||||
I want my payment requests with the previous payment method to be cancelled
|
||||
|
||||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store has a product "PHP T-Shirt"
|
||||
And the store ships everywhere for Free
|
||||
And the store allows paying with "Cash on Delivery"
|
||||
And the store also allows paying with "Bank Transfer"
|
||||
And I am a logged in customer
|
||||
And I placed an order "#00000001"
|
||||
And I bought a single "PHP T-Shirt"
|
||||
And I addressed it to "Lucifer Morningstar", "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address
|
||||
And I chose "Free" shipping method with "Cash on Delivery" payment
|
||||
And there is a "new" "authorize" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
And there is also a "processing" "status" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
And there is also a "completed" "capture" payment request for order "#00000001" using the "Cash on Delivery" payment method
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Cancelling only not finalized payment requests when the payment method has changed
|
||||
When I view the summary of my order "#00000001"
|
||||
And I change payment method to "Bank Transfer" after checkout
|
||||
Then my payment request with action "authorize" for payment method "Cash on Delivery" should have state "cancelled"
|
||||
And my payment request with action "status" for payment method "Cash on Delivery" should have state "cancelled"
|
||||
And my payment request with action "capture" for payment method "Cash on Delivery" should have state "completed"
|
||||
|
|
@ -15,17 +15,16 @@ namespace Sylius\Behat\Context\Api\Admin;
|
|||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\RequestFactoryInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\Converter\IriConverterInterface;
|
||||
use Sylius\Component\Core\Formatter\StringInflector;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Behat\Service\SharedSecurityServiceInterface;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Payment\PaymentTransitions;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Request as HTTPRequest;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final readonly class ManagingPaymentRequestsContext implements Context
|
||||
|
|
@ -35,6 +34,9 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
private ResponseCheckerInterface $responseChecker,
|
||||
private IriConverterInterface $iriConverter,
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private RequestFactoryInterface $requestFactory,
|
||||
private SharedSecurityServiceInterface $sharedSecurityService,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
$this->client->subResourceIndex(
|
||||
Resources::PAYMENTS,
|
||||
Resources::PAYMENT_REQUESTS,
|
||||
(string) $order->getLastPayment()->getId()
|
||||
(string) $order->getLastPayment()->getId(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -122,10 +124,12 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
*/
|
||||
public function itsMethodShouldBe(PaymentMethodInterface $paymentMethod): void
|
||||
{
|
||||
Assert::true($this->responseChecker->hasValue(
|
||||
$this->client->getLastResponse(),
|
||||
'method',
|
||||
$this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin'))
|
||||
Assert::true(
|
||||
$this->responseChecker->hasValue(
|
||||
$this->client->getLastResponse(),
|
||||
'method',
|
||||
$this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -153,4 +157,32 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
Assert::isEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'responseData'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the administrator should see the payment request with action :action for :paymentMethod payment method and state :state
|
||||
*/
|
||||
public function administratorShouldSeeThePaymentRequestWithActionAndState(string $action, PaymentMethodInterface $paymentMethod, string $state): void
|
||||
{
|
||||
$adminUser = $this->sharedStorage->get('administrator');
|
||||
|
||||
/** @var OrderInterface $order */
|
||||
$this->sharedSecurityService->performActionAsAdminUser($adminUser, function () {
|
||||
$order = $this->sharedStorage->get('order');
|
||||
|
||||
$request = $this->requestFactory->custom('/api/v2/admin/payments/' . $order->getLastPayment()->getId() . '/payment-requests', HTTPRequest::METHOD_GET, [], $this->client->getToken());
|
||||
$this->client->executeCustomRequest($request);
|
||||
});
|
||||
|
||||
Assert::true(
|
||||
$this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'action', $action),
|
||||
sprintf('Payment request should have action %s', $action),
|
||||
);
|
||||
Assert::true(
|
||||
$this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'method', $this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin')),
|
||||
sprintf('Payment request should have payment method %s', $paymentMethod->getCode()),
|
||||
);
|
||||
Assert::true(
|
||||
$this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'state', $state),
|
||||
sprintf('Payment request should have state %s', $state),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ final readonly class ManagingPaymentsContext implements Context
|
|||
$this->client->subResourceIndex(
|
||||
Resources::PAYMENTS,
|
||||
Resources::PAYMENT_REQUESTS,
|
||||
(string) $this->responseChecker->getCollection($response)[0]['id']
|
||||
(string) $this->responseChecker->getCollection($response)[0]['id'],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ final readonly class OrderContext implements Context
|
|||
public function iViewTheSummaryOfMyOrder(OrderInterface $order): void
|
||||
{
|
||||
$this->shopClient->show(Resources::ORDERS, $order->getTokenValue());
|
||||
|
||||
$this->sharedStorage->set('cart_token', $order->getTokenValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,19 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Api\Shop;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\Request;
|
||||
use Sylius\Behat\Client\RequestFactoryInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request as HTTPRequest;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final readonly class PaymentRequestContext implements Context
|
||||
{
|
||||
|
|
@ -22,6 +32,7 @@ final readonly class PaymentRequestContext implements Context
|
|||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private RequestFactoryInterface $requestFactory,
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -81,4 +92,31 @@ final readonly class PaymentRequestContext implements Context
|
|||
|
||||
$this->client->executeCustomRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then my payment request with action :action for payment method :paymentMethod should have state :state
|
||||
*/
|
||||
public function myPaymentRequestShouldBeCancelled(string $action, PaymentMethodInterface $paymentMethod, string $state): void
|
||||
{
|
||||
$request = $this->getRequestForPaymentRequestWithAction($action);
|
||||
Assert::notNull($request, sprintf('Payment request with action %s not found', $action));
|
||||
|
||||
$this->client->executeCustomRequest($request);
|
||||
$response = $this->client->getLastResponse();
|
||||
|
||||
Assert::same($this->responseChecker->getValue($response, 'action'), $action, sprintf('Payment request should have action %s', $action));
|
||||
Assert::true(str_contains($this->responseChecker->getValue($response, 'method'), $paymentMethod->getCode()), sprintf('Payment request should have payment method %s', $paymentMethod->getCode()));
|
||||
Assert::same($this->responseChecker->getValue($response, 'state'), $state, sprintf('Payment request should have state %s', $state));
|
||||
}
|
||||
|
||||
private function getRequestForPaymentRequestWithAction(string $action): ?Request
|
||||
{
|
||||
$orderToken = $this->sharedStorage->get('cart_token');
|
||||
$order = $this->client->show(Resources::ORDERS, $orderToken);
|
||||
$payments = $this->responseChecker->getValue($order, 'payments');
|
||||
$paymentId = end($payments)['id'];
|
||||
$paymentRequest = $this->paymentRequestRepository->findOneBy(['payment' => $paymentId, 'action' => $action]);
|
||||
|
||||
return $paymentRequest ? $this->requestFactory->custom('/api/v2/shop/payment-requests/' . $paymentRequest->getHash(), HttpRequest::METHOD_GET, [], $this->client->getToken()) : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,15 +14,23 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Setup;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Sylius\Bundle\ApiBundle\Command\Payment\AddPaymentRequest;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Payment\Factory\PaymentRequestFactoryInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\PaymentRequestTransitions;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
final readonly class PaymentRequestContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private MessageBusInterface $commandBus,
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private PaymentRequestFactoryInterface $paymentRequestFactory,
|
||||
private StateMachineInterface $stateMachine,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +40,7 @@ final readonly class PaymentRequestContext implements Context
|
|||
public function thePaymentRequestActionHasBeenExecutedForOrderWithThePaymentMethod(
|
||||
string $action,
|
||||
OrderInterface $order,
|
||||
PaymentMethodInterface $paymentMethod
|
||||
PaymentMethodInterface $paymentMethod,
|
||||
): void {
|
||||
$addPaymentRequest = new AddPaymentRequest(
|
||||
paymentId: $order->getLastPayment()->getId(),
|
||||
|
|
@ -42,4 +50,37 @@ final readonly class PaymentRequestContext implements Context
|
|||
|
||||
$this->commandBus->dispatch($addPaymentRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given there is (also) a :state :action payment request for order :order using the :paymentMethod payment method
|
||||
*/
|
||||
public function thePaymentRequestActionHasBeenExecutedForOrderWithThePaymentMethodAndState(
|
||||
string $state = null,
|
||||
string $action,
|
||||
OrderInterface $order,
|
||||
PaymentMethodInterface $paymentMethod,
|
||||
): void {
|
||||
$paymentRequest = $this->paymentRequestFactory->create($order->getLastPayment(), $paymentMethod);
|
||||
|
||||
if ($state !== PaymentRequestInterface::STATE_NEW) {
|
||||
$this->stateMachine->apply(
|
||||
$paymentRequest,
|
||||
PaymentRequestTransitions::GRAPH,
|
||||
$this->getTransitionForState($state),
|
||||
);
|
||||
}
|
||||
|
||||
$paymentRequest->setAction($action);
|
||||
|
||||
$this->paymentRequestRepository->add($paymentRequest);
|
||||
}
|
||||
|
||||
private function getTransitionForState(string $state): string
|
||||
{
|
||||
return match ($state) {
|
||||
'completed' => PaymentRequestTransitions::TRANSITION_COMPLETE,
|
||||
'processing' => PaymentRequestTransitions::TRANSITION_PROCESS,
|
||||
default => throw new \InvalidArgumentException(sprintf('Invalid state "%s" provided.', $state)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ namespace Sylius\Behat\Context\Ui\Admin;
|
|||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Page\Admin\Payment\PaymentRequest\IndexPageInterface;
|
||||
use Sylius\Behat\Page\Admin\Payment\PaymentRequest\ShowPageInterface;
|
||||
use Sylius\Behat\Service\SharedSecurityServiceInterface;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
|
|
@ -27,13 +29,15 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
private IndexPageInterface $indexPage,
|
||||
private ShowPageInterface $showPage,
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
private SharedSecurityServiceInterface $sharedSecurityService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I browse payment requests of an order :order
|
||||
*/
|
||||
public function iBrowseOrdersOfACustomer(OrderInterface $order): void
|
||||
public function iBrowsePaymentRequestsOfACustomer(OrderInterface $order): void
|
||||
{
|
||||
$this->indexPage->open(['paymentId' => $order->getLastPayment()->getId()]);
|
||||
}
|
||||
|
|
@ -48,7 +52,7 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
|
||||
$this->showPage->open([
|
||||
'hash' => $paymentRequest->getHash(),
|
||||
'paymentId' => $payment->getId()
|
||||
'paymentId' => $payment->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -126,4 +130,22 @@ final readonly class ManagingPaymentRequestsContext implements Context
|
|||
{
|
||||
Assert::same($this->showPage->getFieldText('response_data'), json_encode([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the administrator should see the payment request with action :action for :method payment method and state :state
|
||||
*/
|
||||
public function administratorShouldSeeThePaymentRequestWithActionAndState(string $action, string $paymentMethod, string $state): void
|
||||
{
|
||||
$adminUser = $this->sharedStorage->get('administrator');
|
||||
|
||||
$this->sharedSecurityService->performActionAsAdminUser($adminUser, function () {
|
||||
$this->iBrowsePaymentRequestsOfACustomer($this->sharedStorage->get('order'));
|
||||
});
|
||||
|
||||
Assert::true($this->indexPage->isSingleResourceOnPage([
|
||||
'action' => $action,
|
||||
'state' => $state,
|
||||
'method' => $paymentMethod,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,6 +309,9 @@
|
|||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="api_platform.symfony.iri_converter" />
|
||||
<argument type="service" id="sylius.repository.payment_request" />
|
||||
<argument type="service" id="sylius.behat.request_factory" />
|
||||
<argument type="service" id="sylius.behat.api.shared_security" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@
|
|||
<argument type="service" id="sylius.behat.api_platform_client.shop" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.request_factory" />
|
||||
<argument type="service" id="sylius.repository.payment_request" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.shop.payment" class="Sylius\Behat\Context\Api\Shop\PaymentContext">
|
||||
|
|
|
|||
|
|
@ -322,6 +322,9 @@
|
|||
|
||||
<service id="sylius.behat.context.setup.payment_request" class="Sylius\Behat\Context\Setup\PaymentRequestContext">
|
||||
<argument type="service" id="sylius.command_bus" />
|
||||
<argument type="service" id="sylius.repository.payment_request" />
|
||||
<argument type="service" id="sylius.factory.payment_request" />
|
||||
<argument type="service" id="sylius_abstraction.state_machine" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -618,6 +618,8 @@
|
|||
<argument type="service" id="sylius.behat.page.admin.payment.payment_request.index" />
|
||||
<argument type="service" id="sylius.behat.page.admin.payment.payment_request.show"/>
|
||||
<argument type="service" id="sylius.repository.payment_request"/>
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
<argument type="service" id="sylius.behat.shared_security" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ default:
|
|||
- sylius.behat.context.transform.user
|
||||
- sylius.behat.context.transform.zone
|
||||
|
||||
- sylius.behat.context.setup.admin_user
|
||||
- sylius.behat.context.setup.cart
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.currency
|
||||
|
|
@ -34,6 +35,7 @@ default:
|
|||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.order
|
||||
- sylius.behat.context.setup.payment
|
||||
- sylius.behat.context.setup.payment_request
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.shipping
|
||||
- sylius.behat.context.setup.shop_api_security
|
||||
|
|
@ -41,10 +43,12 @@ default:
|
|||
- sylius.behat.context.setup.user
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.api.admin.managing_payment_requests
|
||||
- sylius.behat.context.api.shop.cart
|
||||
- sylius.behat.context.api.shop.checkout
|
||||
- sylius.behat.context.api.shop.checkout.complete
|
||||
- sylius.behat.context.api.shop.checkout.order_details
|
||||
- sylius.behat.context.api.shop.customer
|
||||
- sylius.behat.context.api.shop.order
|
||||
- sylius.behat.context.api.shop.payment_request
|
||||
- sylius.behat.context.api.shop.response
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ default:
|
|||
- sylius.behat.context.transform.user
|
||||
- sylius.behat.context.transform.zone
|
||||
|
||||
- sylius.behat.context.setup.admin_user
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.currency
|
||||
- sylius.behat.context.setup.geographical
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.order
|
||||
- sylius.behat.context.setup.payment
|
||||
- sylius.behat.context.setup.payment_request
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.shipping
|
||||
- sylius.behat.context.setup.shop_security
|
||||
|
|
@ -37,6 +39,7 @@ default:
|
|||
- sylius.behat.context.setup.user
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.ui.admin.managing_payment_requests
|
||||
- sylius.behat.context.ui.shop.account
|
||||
- sylius.behat.context.ui.shop.cart
|
||||
- sylius.behat.context.ui.shop.checkout
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\PaymentBundle\Canceller;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Sylius\Component\Payment\Canceller\PaymentRequestCancellerInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\PaymentRequestTransitions;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
|
||||
final class PaymentRequestCanceller implements PaymentRequestCancellerInterface
|
||||
{
|
||||
/**
|
||||
* @param PaymentRequestRepositoryInterface<PaymentRequestInterface> $paymentRequestRepository
|
||||
* @param array<string> $paymentRequestStatesToBeCancelled
|
||||
*/
|
||||
public function __construct(
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private StateMachineInterface $stateMachine,
|
||||
private ObjectManager $objectManager,
|
||||
private array $paymentRequestStatesToBeCancelled,
|
||||
) {
|
||||
}
|
||||
|
||||
public function cancelPaymentRequests(mixed $paymentId, string $paymentMethodCode): void
|
||||
{
|
||||
$paymentRequests = $this->paymentRequestRepository->findByPaymentIdAndStates($paymentId, $this->paymentRequestStatesToBeCancelled);
|
||||
|
||||
foreach ($paymentRequests as $paymentRequest) {
|
||||
if ($paymentRequest->getMethod()->getCode() !== $paymentMethodCode) {
|
||||
$this->stateMachine->apply(
|
||||
$paymentRequest,
|
||||
PaymentRequestTransitions::GRAPH,
|
||||
PaymentRequestTransitions::TRANSITION_CANCEL,
|
||||
);
|
||||
|
||||
$this->objectManager->persist($paymentRequest);
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,14 @@ final class Configuration implements ConfigurationInterface
|
|||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('payment_request')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->arrayNode('states_that_should_be_cancelled_when_payment_request_has_changed')
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end()
|
||||
->arrayNode('gateways')
|
||||
->useAttributeAsKey('name')
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ final class SyliusPaymentExtension extends AbstractResourceExtension
|
|||
|
||||
$container->setParameter('sylius.payment_gateways', $config['gateways']);
|
||||
$container->setParameter('sylius.gateway_config.validation_groups', $config['gateway_config']['validation_groups']);
|
||||
$container->setParameter('sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed', $config['payment_request']['states_that_should_be_cancelled_when_payment_request_has_changed']);
|
||||
|
||||
$this->registerAutoconfiguration($container);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,4 +73,21 @@ class PaymentRequestRepository extends EntityRepository implements PaymentReques
|
|||
|
||||
return count($paymentRequests) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $states
|
||||
*
|
||||
* @return array<PaymentRequestInterface>
|
||||
*/
|
||||
public function findByPaymentIdAndStates(mixed $paymentId, array $states): array
|
||||
{
|
||||
return $this->createQueryBuilder('o')
|
||||
->where('o.payment = :paymentId')
|
||||
->andWhere('o.state IN (:states)')
|
||||
->setParameter('paymentId', $paymentId)
|
||||
->setParameter('states', $states)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\PaymentBundle\EventListener;
|
||||
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Sylius\Component\Payment\Canceller\PaymentRequestCancellerInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentInterface;
|
||||
|
||||
final class PaymentMethodChangeEventListener
|
||||
{
|
||||
public function __construct(private PaymentRequestCancellerInterface $paymentRequestCanceller)
|
||||
{
|
||||
}
|
||||
|
||||
public function postUpdate(PostUpdateEventArgs $args): void
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
|
||||
if (!$entity instanceof PaymentInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var array<string, array<int, mixed>> $changeSet */
|
||||
$changeSet = $args->getObjectManager()->getUnitOfWork()->getEntityChangeSet($entity);
|
||||
|
||||
if (array_key_exists('method', $changeSet)) {
|
||||
[$oldMethod, $newMethod] = $changeSet['method'];
|
||||
|
||||
if ($oldMethod !== $newMethod) {
|
||||
$this->paymentRequestCanceller->cancelPaymentRequests($entity->getId(), $newMethod->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,3 +10,9 @@ jms_serializer:
|
|||
sylius-payment:
|
||||
namespace_prefix: "Sylius\\Component\\Payment"
|
||||
path: "@SyliusPaymentBundle/Resources/config/serializer"
|
||||
|
||||
sylius_payment:
|
||||
payment_request:
|
||||
states_that_should_be_cancelled_when_payment_request_has_changed:
|
||||
- !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_PROCESSING
|
||||
- !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_NEW
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<container
|
||||
xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
|
||||
>
|
||||
<services>
|
||||
<service id="sylius.canceller.payment_request" class="Sylius\Bundle\PaymentBundle\Canceller\PaymentRequestCanceller">
|
||||
<argument type="service" id="sylius.repository.payment_request" />
|
||||
<argument type="service" id="sylius_abstraction.state_machine" />
|
||||
<argument type="service" id="doctrine.orm.entity_manager" />
|
||||
<argument>%sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed%</argument>
|
||||
</service>
|
||||
<service id="Sylius\Component\Payment\Canceller\PaymentRequestCancellerInterface" alias="sylius.canceller.payment_request" />
|
||||
</services>
|
||||
</container>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<container
|
||||
xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
|
||||
>
|
||||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="sylius.listener.payment_method_change" class="Sylius\Bundle\PaymentBundle\EventListener\PaymentMethodChangeEventListener">
|
||||
<argument type="service" id="sylius.canceller.payment_request" />
|
||||
<tag name="doctrine.event_listener" event="postUpdate" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
@ -19,6 +19,7 @@ use Sylius\Bundle\PaymentBundle\Attribute\AsPaymentMethodsResolver;
|
|||
use Sylius\Bundle\PaymentBundle\DependencyInjection\SyliusPaymentExtension;
|
||||
use Sylius\Bundle\PaymentBundle\Tests\Stub\GatewayConfigurationTypeStub;
|
||||
use Sylius\Bundle\PaymentBundle\Tests\Stub\PaymentMethodsResolverStub;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
|
||||
final class SyliusPaymentExtensionTest extends AbstractExtensionTestCase
|
||||
|
|
@ -62,6 +63,7 @@ final class SyliusPaymentExtensionTest extends AbstractExtensionTestCase
|
|||
['type' => 'test', 'label' => 'Test', 'priority' => 15],
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_loads_gateway_config_validation_groups_parameter_value_properly(): void
|
||||
{
|
||||
|
|
@ -80,6 +82,24 @@ final class SyliusPaymentExtensionTest extends AbstractExtensionTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_loads_parameter_with_payment_request_states_that_should_be_cancelled_when_payment_method_is_changed(): void
|
||||
{
|
||||
$this->load([
|
||||
'payment_request' => [
|
||||
'states_that_should_be_cancelled_when_payment_request_has_changed' => [
|
||||
PaymentRequestInterface::STATE_NEW,
|
||||
PaymentRequestInterface::STATE_PROCESSING,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertContainerBuilderHasParameter(
|
||||
'sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed',
|
||||
[PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING],
|
||||
);
|
||||
}
|
||||
|
||||
protected function getContainerExtensions(): array
|
||||
{
|
||||
return [new SyliusPaymentExtension()];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\PaymentBundle\Tests\EventListener;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\PaymentBundle\EventListener\PaymentMethodChangeEventListener;
|
||||
use Sylius\Component\Payment\Canceller\PaymentRequestCancellerInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentMethodInterface;
|
||||
|
||||
final class PaymentMethodChangeEventListenerTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_updates_payment_request_when_the_payment_has_changed_payment_method(): void
|
||||
{
|
||||
$paymentRequestCanceller = $this->createMock(PaymentRequestCancellerInterface::class);
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$oldMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
$newMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
|
||||
$payment->expects($this->once())
|
||||
->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$newMethod->expects($this->once())
|
||||
->method('getCode')
|
||||
->willReturn('newMethodCode');
|
||||
|
||||
$unitOfWork->expects($this->once())
|
||||
->method('getEntityChangeSet')
|
||||
->with($payment)
|
||||
->willReturn(['method' => [$oldMethod, $newMethod]]);
|
||||
|
||||
$entityManager->expects($this->once())
|
||||
->method('getUnitOfWork')
|
||||
->willReturn($unitOfWork);
|
||||
|
||||
$args = new PostUpdateEventArgs($payment, $entityManager);
|
||||
|
||||
$paymentRequestCanceller->expects($this->once())
|
||||
->method('cancelPaymentRequests')
|
||||
->with(1, 'newMethodCode');
|
||||
|
||||
$listener = new PaymentMethodChangeEventListener($paymentRequestCanceller);
|
||||
$listener->postUpdate($args);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_update_if_entity_is_not_payment(): void
|
||||
{
|
||||
$paymentRequestCanceller = $this->createMock(PaymentRequestCancellerInterface::class);
|
||||
$nonPaymentEntity = new \stdClass();
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
|
||||
$args = new PostUpdateEventArgs($nonPaymentEntity, $entityManager);
|
||||
|
||||
$paymentRequestCanceller->expects($this->never())
|
||||
->method('cancelPaymentRequests');
|
||||
|
||||
$listener = new PaymentMethodChangeEventListener($paymentRequestCanceller);
|
||||
$listener->postUpdate($args);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_update_if_payment_method_did_not_change(): void
|
||||
{
|
||||
$paymentRequestCanceller = $this->createMock(PaymentRequestCancellerInterface::class);
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$oldMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
$newMethod = $oldMethod;
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
|
||||
$unitOfWork->expects($this->once())
|
||||
->method('getEntityChangeSet')
|
||||
->with($payment)
|
||||
->willReturn(['method' => [$oldMethod, $newMethod]]);
|
||||
|
||||
$entityManager->expects($this->once())
|
||||
->method('getUnitOfWork')
|
||||
->willReturn($unitOfWork);
|
||||
|
||||
$args = new PostUpdateEventArgs($payment, $entityManager);
|
||||
|
||||
$paymentRequestCanceller->expects($this->never())
|
||||
->method('cancelPaymentRequests');
|
||||
|
||||
$listener = new PaymentMethodChangeEventListener($paymentRequestCanceller);
|
||||
$listener->postUpdate($args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\PaymentBundle\Canceller;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
|
||||
final class PaymentRequestCancellerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(PaymentRequestRepositoryInterface $paymentRequestRepository, StateMachineInterface $stateMachine, ObjectManager $objectManager): void
|
||||
{
|
||||
$this->beConstructedWith($paymentRequestRepository, $stateMachine, $objectManager, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING]);
|
||||
}
|
||||
|
||||
function it_cancels_payment_requests_if_the_payment_method_code_is_different(
|
||||
PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
PaymentRequestInterface $paymentRequest1,
|
||||
PaymentRequestInterface $paymentRequest2,
|
||||
PaymentMethodInterface $paymentMethod1,
|
||||
PaymentMethodInterface $paymentMethod2,
|
||||
StateMachineInterface $stateMachine,
|
||||
ObjectManager $objectManager,
|
||||
): void {
|
||||
$paymentRequestRepository->findByPaymentIdAndStates(1, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING])
|
||||
->willReturn([$paymentRequest1, $paymentRequest2]);
|
||||
|
||||
$paymentRequest1->getMethod()->willReturn($paymentMethod1);
|
||||
$paymentMethod1->getCode()->willReturn('payment_method_with_different_code');
|
||||
$paymentRequest2->getMethod()->willReturn($paymentMethod2);
|
||||
$paymentMethod2->getCode()->willReturn('payment_method_code');
|
||||
|
||||
$stateMachine->apply($paymentRequest1, 'sylius_payment_request', 'cancel')->shouldBeCalled();
|
||||
$stateMachine->apply($paymentRequest2, 'sylius_payment_request', 'cancel')->shouldNotBeCalled();
|
||||
$objectManager->persist($paymentRequest1)->shouldBeCalled();
|
||||
$objectManager->persist($paymentRequest2)->shouldNotBeCalled();
|
||||
$objectManager->flush()->shouldBeCalledOnce();
|
||||
|
||||
$this->cancelPaymentRequests(1, 'payment_method_code');
|
||||
}
|
||||
|
||||
function it_does_not_cancel_payment_requests_if_none_found(
|
||||
PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
StateMachineInterface $stateMachine,
|
||||
): void {
|
||||
$paymentRequestRepository->findByPaymentIdAndStates(1, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING])
|
||||
->willReturn([]);
|
||||
|
||||
$stateMachine->apply(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$this->cancelPaymentRequests(1, 'payment_method_code');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Component\Payment\Canceller;
|
||||
|
||||
interface PaymentRequestCancellerInterface
|
||||
{
|
||||
public function cancelPaymentRequests(mixed $paymentId, string $paymentMethodCode): void;
|
||||
}
|
||||
|
|
@ -29,4 +29,11 @@ interface PaymentRequestRepositoryInterface extends RepositoryInterface
|
|||
public function createQueryBuilderForPayment(string $paymentId): QueryBuilder;
|
||||
|
||||
public function duplicateExists(PaymentRequestInterface $paymentRequest): bool;
|
||||
|
||||
/**
|
||||
* @param array<string> $states
|
||||
*
|
||||
* @return array<PaymentRequestInterface>
|
||||
*/
|
||||
public function findByPaymentIdAndStates(mixed $paymentId, array $states): array;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue