mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-05 20:57:12 +00:00
[UPMERGE] 2.1 -> 2.2 (#19041)
This PR has been generated automatically. For more details see [upmerge_pr.yaml](/Sylius/Sylius/blob/2.3/.github/workflows/upmerge_pr.yaml). **Remember!** The upmerge should always be merged with using `Merge pull request` button. In case of conflicts, please resolve them manually with usign the following commands: ``` git fetch upstream gh pr checkout <this-pr-number> git merge upstream/2.2 -m "Resolve conflicts between 2.1 and 2.2" ``` If you use other name for the upstream remote, please replace `upstream` with the name of your remote pointing to the `Sylius/Sylius` repository. Once the conflicts are resolved, please run `git merge --continue` and push the changes to this PR.
This commit is contained in:
commit
9315d9e1f4
30 changed files with 1928 additions and 40 deletions
|
|
@ -1,3 +1,26 @@
|
|||
# UPGRADE FROM `2.1.14` TO `2.1.15`
|
||||
|
||||
## Security
|
||||
|
||||
### Payment request ownership enforcement
|
||||
|
||||
`GET /api/v2/shop/payment-requests/{hash}`, `PUT /api/v2/shop/payment-requests/{hash}` and `POST /api/v2/shop/orders/{tokenValue}/payment-requests` now enforce ownership:
|
||||
|
||||
- An unauthenticated request receives `404 Not Found`, unless the underlying order is a guest order. An order qualifies as a guest order when it has no customer, when its customer has no associated user account, or when it was placed through the guest checkout flow (`Order::isCreatedByGuest()` returns `true`).
|
||||
- An authenticated shop user can only access payment requests belonging to their own orders; requests for another customer's payment request also receive `404 Not Found`.
|
||||
|
||||
Previously these operations could be performed by any authenticated user or even an anonymous user who knew the payment request hash (or, for the creation endpoint, the order token value), which constituted a broken object-level authorization (IDOR) vulnerability.
|
||||
|
||||
**No action is required** unless your integration assumed that these endpoints were publicly accessible or shared across customers.
|
||||
|
||||
### `Sylius\Bundle\ApiBundle\StateProvider\Shop\Payment\PaymentRequest\ItemProvider` constructor
|
||||
|
||||
A new `UserContextInterface` argument is appended to the constructor. Omitting it is deprecated and will be required in Sylius 3.0; without it the ownership check is skipped and the provider falls back to its previous behavior.
|
||||
|
||||
### `Sylius\Bundle\ApiBundle\CommandHandler\Payment\AddPaymentRequestHandler` constructor
|
||||
|
||||
A new, nullable `UserContextInterface` argument is appended to the constructor. When it is not provided, the ownership check is skipped and the handler falls back to its previous behavior.
|
||||
|
||||
# UPGRADE FROM `2.1.10` TO `2.1.11`
|
||||
|
||||
### Constructor signature changes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
@stale_live_component
|
||||
Feature: Stale cart LiveComponent cannot mutate a completed order
|
||||
In order to protect completed orders from data corruption
|
||||
As a Developer
|
||||
I want the cart LiveComponent to be safe against stale browser state
|
||||
|
||||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store has a product "Sylius T-Shirt" priced at "$19.99"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Clearing cart from a stale page does not delete a completed order
|
||||
Given I added product "Sylius T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
And I note the current order id for later assertions
|
||||
When the order is completed in the background
|
||||
And I clear my cart from the stale page without reloading
|
||||
Then the completed order should still exist in the database
|
||||
And the order checkout state should be "completed"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Removing an item from a stale page does not mutate a completed order
|
||||
Given I added product "Sylius T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
And I note the current order id for later assertions
|
||||
When the order is completed in the background
|
||||
And I remove first item from cart from the stale page without reloading
|
||||
Then the completed order should still exist in the database
|
||||
And the order should still have 1 item
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Increasing item quantity on a stale page does not mutate a completed order
|
||||
Given I added product "Sylius T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
And I note the current order id for later assertions
|
||||
When the order is completed in the background
|
||||
And I increase the quantity of the first cart item to 2 on the stale page without reloading
|
||||
Then the completed order should still exist in the database
|
||||
And the order item quantity should still be 1
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<?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\Ui\Shop;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Mink\Mink;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\OrderCheckoutStates;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Sylius\Component\Order\Model\OrderInterface as BaseOrderInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final readonly class StaleCartLiveComponentContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private Mink $mink,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
private OrderRepositoryInterface $orderRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I note the current order id for later assertions
|
||||
*/
|
||||
public function iNoteTheCurrentOrderId(): void
|
||||
{
|
||||
$page = $this->mink->getSession()->getPage();
|
||||
$component = $page->find('css', '[data-live-name-value="sylius_shop:cart:form"]');
|
||||
Assert::notNull($component, 'Cart LiveComponent root element not found on page.');
|
||||
|
||||
$rawProps = $component->getAttribute('data-live-props-value');
|
||||
Assert::notNull($rawProps, 'data-live-props-value attribute missing on cart form LiveComponent.');
|
||||
|
||||
$props = json_decode($rawProps, true);
|
||||
$orderId = $props['resource'] ?? null;
|
||||
Assert::notNull($orderId, 'Order ID not found in dehydrated LiveComponent props.');
|
||||
|
||||
$this->sharedStorage->set('stale_order_id', (int) $orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the order is completed in the background
|
||||
*/
|
||||
public function theOrderIsCompletedInTheBackground(): void
|
||||
{
|
||||
$orderId = $this->sharedStorage->get('stale_order_id');
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
Assert::notNull($order, sprintf('Order #%d not found.', $orderId));
|
||||
|
||||
$order->setCheckoutState(OrderCheckoutStates::STATE_COMPLETED);
|
||||
$order->setState(BaseOrderInterface::STATE_NEW);
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I clear my cart from the stale page without reloading
|
||||
*/
|
||||
public function iClearMyCartFromTheStalePage(): void
|
||||
{
|
||||
$page = $this->mink->getSession()->getPage();
|
||||
|
||||
$button = $page->find('css', '[data-test-clear-cart]')
|
||||
?? $page->find('css', '[data-live-action-param="clearCart"]');
|
||||
|
||||
Assert::notNull($button, 'Clear cart button not found on the stale page.');
|
||||
|
||||
$button->click();
|
||||
|
||||
$this->waitForLiveComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I remove first item from cart from the stale page without reloading
|
||||
*/
|
||||
public function iRemoveFirstItemFromCartFromTheStalePageWithoutReloading(): void
|
||||
{
|
||||
$page = $this->mink->getSession()->getPage();
|
||||
|
||||
$button = $page->find('css', '[data-test-remove-cart-item]')
|
||||
?? $page->find('css', '[data-live-action-param="removeItem"]');
|
||||
|
||||
Assert::notNull($button, 'Remove item button not found on the stale page.');
|
||||
|
||||
$button->click();
|
||||
|
||||
$this->waitForLiveComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the completed order should still exist in the database
|
||||
*/
|
||||
public function theCompletedOrderShouldStillExistInTheDatabase(): void
|
||||
{
|
||||
$orderId = $this->sharedStorage->get('stale_order_id');
|
||||
|
||||
// Clear the identity map to bypass any cached entity and force a real SELECT.
|
||||
$this->entityManager->clear();
|
||||
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
|
||||
Assert::notNull(
|
||||
$order,
|
||||
sprintf('Order #%d was deleted. The stale LiveComponent action must not remove a completed order.', $orderId),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the order checkout state should be :expectedState
|
||||
*/
|
||||
public function theOrderCheckoutStateShouldBe(string $expectedState): void
|
||||
{
|
||||
$orderId = $this->sharedStorage->get('stale_order_id');
|
||||
$this->entityManager->clear();
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
Assert::notNull($order);
|
||||
|
||||
Assert::same(
|
||||
$order->getCheckoutState(),
|
||||
$expectedState,
|
||||
sprintf('Checkout state: expected "%s" but got "%s".', $expectedState, $order->getCheckoutState()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I increase the quantity of the first cart item to :quantity on the stale page without reloading
|
||||
*/
|
||||
public function iIncreaseTheQuantityOfTheFirstCartItemOnTheStalePage(int $quantity): void
|
||||
{
|
||||
$page = $this->mink->getSession()->getPage();
|
||||
|
||||
$input = $page->find('css', '[data-test-cart-item-quantity]');
|
||||
Assert::notNull($input, 'Cart item quantity input not found on the stale page.');
|
||||
|
||||
$input->setValue((string) $quantity);
|
||||
|
||||
$this->waitForLiveComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the order item quantity should still be :quantity
|
||||
*/
|
||||
public function theOrderItemQuantityShouldStillBe(int $quantity): void
|
||||
{
|
||||
$orderId = $this->sharedStorage->get('stale_order_id');
|
||||
$this->entityManager->clear();
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
Assert::notNull($order);
|
||||
|
||||
$firstItem = $order->getItems()->first();
|
||||
Assert::notFalse($firstItem, 'The order has no items.');
|
||||
|
||||
Assert::same(
|
||||
$firstItem->getQuantity(),
|
||||
$quantity,
|
||||
sprintf('Expected item quantity %d but got %d.', $quantity, $firstItem->getQuantity()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the order should still have :count item(s)
|
||||
*/
|
||||
public function theOrderShouldStillHaveItems(int $count): void
|
||||
{
|
||||
$orderId = $this->sharedStorage->get('stale_order_id');
|
||||
$this->entityManager->clear();
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
Assert::notNull($order);
|
||||
|
||||
Assert::count(
|
||||
$order->getItems(),
|
||||
$count,
|
||||
sprintf('Expected %d item(s) but found %d.', $count, $order->getItems()->count()),
|
||||
);
|
||||
}
|
||||
|
||||
private function waitForLiveComponent(int $timeoutMs = 5000): void
|
||||
{
|
||||
$this->mink->getSession()->wait(2000, "document.querySelector('[data-live-loading]') !== null");
|
||||
$this->mink->getSession()->wait($timeoutMs, "document.querySelector('[data-live-loading]') === null");
|
||||
}
|
||||
}
|
||||
|
|
@ -505,6 +505,13 @@
|
|||
<argument type="service" id="sylius.behat.element.browser" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.ui.shop.stale_cart_live_component" class="Sylius\Behat\Context\Ui\Shop\StaleCartLiveComponentContext">
|
||||
<argument type="service" id="behat.mink" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
<argument type="service" id="sylius.repository.order" />
|
||||
<argument type="service" id="doctrine.orm.entity_manager" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.ui.shop.contact" class="Sylius\Behat\Context\Ui\Shop\ContactContext">
|
||||
<argument type="service" id="sylius.behat.page.shop.contact" />
|
||||
<argument type="service" id="sylius.behat.notification_checker.shop" />
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ imports:
|
|||
- ui/admin/security.yml
|
||||
- ui/cart/accessing_cart.yaml
|
||||
- ui/cart/shopping_cart.yml
|
||||
- ui/cart/stale_cart_live_component.yml
|
||||
- ui/channel/channels.yml
|
||||
- ui/channel/managing_channels.yml
|
||||
- ui/channel/products_accessibility_in_multiple_channels.yml
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
default:
|
||||
suites:
|
||||
ui_stale_cart_live_component:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.bad_gateway
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
- sylius.behat.context.hook.guest_cart
|
||||
- sylius.behat.context.hook.session
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.cart
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.currency
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.ui.shop.cart
|
||||
- sylius.behat.context.ui.shop.product
|
||||
- sylius.behat.context.ui.shop.stale_cart_live_component
|
||||
|
||||
filters:
|
||||
tags: "@stale_live_component&&@ui"
|
||||
|
|
@ -19,6 +19,7 @@ use Sylius\Component\Core\Model\PaymentMethodInterface;
|
|||
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentInterface;
|
||||
use Sylius\Component\Payment\Resolver\PaymentMethodsResolverInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final readonly class PaymentMethodChanger implements PaymentMethodChangerInterface
|
||||
|
|
@ -26,6 +27,7 @@ final readonly class PaymentMethodChanger implements PaymentMethodChangerInterfa
|
|||
public function __construct(
|
||||
private PaymentRepositoryInterface $paymentRepository,
|
||||
private PaymentMethodRepositoryInterface $paymentMethodRepository,
|
||||
private ?PaymentMethodsResolverInterface $paymentMethodsResolver = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +45,13 @@ final readonly class PaymentMethodChanger implements PaymentMethodChangerInterfa
|
|||
$payment = $this->paymentRepository->findOneByOrderId($paymentId, $order->getId());
|
||||
Assert::notNull($payment, 'Can not find payment with given identifier.');
|
||||
|
||||
if (
|
||||
$this->paymentMethodsResolver !== null &&
|
||||
!in_array($paymentMethod, $this->paymentMethodsResolver->getSupportedMethods($payment), true)
|
||||
) {
|
||||
throw new PaymentMethodCannotBeChangedException();
|
||||
}
|
||||
|
||||
if ($order->getState() === OrderInterface::STATE_NEW) {
|
||||
Assert::same(
|
||||
$payment->getState(),
|
||||
|
|
|
|||
|
|
@ -14,18 +14,23 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\ApiBundle\CommandHandler\Payment;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Payment\AddPaymentRequest;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentMethodNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentNotFoundException;
|
||||
use Sylius\Bundle\PaymentBundle\Provider\DefaultActionProviderInterface;
|
||||
use Sylius\Bundle\PaymentBundle\Provider\DefaultPayloadProviderInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
|
||||
use Sylius\Component\Payment\Factory\PaymentRequestFactoryInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/** @experimental */
|
||||
#[AsMessageHandler]
|
||||
|
|
@ -44,6 +49,7 @@ final class AddPaymentRequestHandler
|
|||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private DefaultActionProviderInterface $defaultActionProvider,
|
||||
private DefaultPayloadProviderInterface $defaultPayloadProvider,
|
||||
private ?UserContextInterface $userContext = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +70,10 @@ final class AddPaymentRequestHandler
|
|||
throw new PaymentNotFoundException();
|
||||
}
|
||||
|
||||
if ($this->userContext !== null && !$this->isAccessibleByCurrentUser($payment, $this->userContext->getUser())) {
|
||||
throw new PaymentNotFoundException();
|
||||
}
|
||||
|
||||
/** @var PaymentMethodInterface|null $paymentMethod */
|
||||
$paymentMethod = $this->paymentMethodRepository->findOneBy(['code' => $addPaymentRequest->paymentMethodCode]);
|
||||
if (null === $paymentMethod) {
|
||||
|
|
@ -76,4 +86,29 @@ final class AddPaymentRequestHandler
|
|||
|
||||
return $paymentRequest;
|
||||
}
|
||||
|
||||
private function isAccessibleByCurrentUser(PaymentInterface $payment, ?UserInterface $user): bool
|
||||
{
|
||||
$order = $payment->getOrder();
|
||||
if (!$order instanceof OrderInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user instanceof ShopUserInterface) {
|
||||
$customer = $user->getCustomer();
|
||||
|
||||
return $customer instanceof CustomerInterface && $order->getCustomer() === $customer;
|
||||
}
|
||||
|
||||
return $this->isGuestOrder($order);
|
||||
}
|
||||
|
||||
private function isGuestOrder(OrderInterface $order): bool
|
||||
{
|
||||
$customer = $order->getCustomer();
|
||||
|
||||
return null === $customer ||
|
||||
null === $customer->getUser() ||
|
||||
$order->isCreatedByGuest();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<?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\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
|
||||
final readonly class ShopUserBasedExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SectionProviderInterface $sectionProvider,
|
||||
private UserContextInterface $userContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $context
|
||||
*/
|
||||
public function applyToCollection(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
$this->filterByCustomer($queryBuilder, $queryNameGenerator, $resourceClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $identifiers
|
||||
* @param array<array-key, mixed> $context
|
||||
*/
|
||||
public function applyToItem(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
array $identifiers,
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
$this->filterByCustomer($queryBuilder, $queryNameGenerator, $resourceClass);
|
||||
}
|
||||
|
||||
private function filterByCustomer(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
): void {
|
||||
if (!is_a($resourceClass, PaymentRequestInterface::class, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->sectionProvider->getSection() instanceof ShopApiSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->userContext->getUser();
|
||||
if (!$user instanceof ShopUserInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
$paymentJoinAlias = $queryNameGenerator->generateJoinAlias('payment');
|
||||
$orderJoinAlias = $queryNameGenerator->generateJoinAlias('order');
|
||||
$customerParameterName = $queryNameGenerator->generateParameterName('customer');
|
||||
|
||||
$queryBuilder
|
||||
->innerJoin(sprintf('%s.payment', $rootAlias), $paymentJoinAlias)
|
||||
->innerJoin(sprintf('%s.order', $paymentJoinAlias), $orderJoinAlias)
|
||||
->andWhere($queryBuilder->expr()->eq(sprintf('%s.customer', $orderJoinAlias), sprintf(':%s', $customerParameterName)))
|
||||
->setParameter($customerParameterName, $user->getCustomer())
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?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\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
|
||||
final readonly class VisitorBasedExtension implements QueryItemExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SectionProviderInterface $sectionProvider,
|
||||
private UserContextInterface $userContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $identifiers
|
||||
* @param array<array-key, mixed> $context
|
||||
*/
|
||||
public function applyToItem(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
array $identifiers,
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
if (!is_a($resourceClass, PaymentRequestInterface::class, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->sectionProvider->getSection() instanceof ShopApiSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $this->userContext->getUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
$paymentJoinAlias = $queryNameGenerator->generateJoinAlias('payment');
|
||||
$orderJoinAlias = $queryNameGenerator->generateJoinAlias('order');
|
||||
$customerJoinAlias = $queryNameGenerator->generateJoinAlias('customer');
|
||||
$userJoinAlias = $queryNameGenerator->generateJoinAlias('user');
|
||||
$createdByGuestParameterName = $queryNameGenerator->generateParameterName('createdByGuest');
|
||||
|
||||
$queryBuilder
|
||||
->innerJoin(sprintf('%s.payment', $rootAlias), $paymentJoinAlias)
|
||||
->innerJoin(sprintf('%s.order', $paymentJoinAlias), $orderJoinAlias)
|
||||
->leftJoin(sprintf('%s.customer', $orderJoinAlias), $customerJoinAlias)
|
||||
->leftJoin(sprintf('%s.user', $customerJoinAlias), $userJoinAlias)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->isNull($userJoinAlias),
|
||||
$queryBuilder->expr()->isNull(sprintf('%s.customer', $orderJoinAlias)),
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->isNotNull($userJoinAlias),
|
||||
$queryBuilder->expr()->eq(sprintf('%s.createdByGuest', $orderJoinAlias), sprintf(':%s', $createdByGuestParameterName)),
|
||||
),
|
||||
),
|
||||
)
|
||||
->setParameter($createdByGuestParameterName, true)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
<service id="sylius_api.changer.payment_method" class="Sylius\Bundle\ApiBundle\Changer\PaymentMethodChanger">
|
||||
<argument type="service" id="sylius.repository.payment" />
|
||||
<argument type="service" id="sylius.repository.payment_method" />
|
||||
<argument type="service" id="sylius.resolver.payment_methods" />
|
||||
</service>
|
||||
<service id="Sylius\Bundle\ApiBundle\Changer\PaymentMethodChangerInterface" alias="sylius_api.changer.payment_method" />
|
||||
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@
|
|||
<argument type="service" id="sylius.repository.payment_request" />
|
||||
<argument type="service" id="sylius.provider.payment_request.default_action" />
|
||||
<argument type="service" id="sylius.provider.payment_request.default_payload" />
|
||||
<argument type="service" id="sylius_api.context.user.token_based" />
|
||||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
</service>
|
||||
|
||||
|
|
|
|||
|
|
@ -280,5 +280,24 @@
|
|||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.item" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
id="sylius_api.doctrine.orm.query_extension.shop.payment_request.shop_user_based"
|
||||
class="Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest\ShopUserBasedExtension"
|
||||
>
|
||||
<argument type="service" id="sylius.section_resolver.uri_based" />
|
||||
<argument type="service" id="sylius_api.context.user.token_based" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.item" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
id="sylius_api.doctrine.orm.query_extension.shop.payment_request.visitor_based"
|
||||
class="Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest\VisitorBasedExtension"
|
||||
>
|
||||
<argument type="service" id="sylius.section_resolver.uri_based" />
|
||||
<argument type="service" id="sylius_api.context.user.token_based" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.item" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
<argument type="service" id="sylius.section_resolver.uri_based" />
|
||||
<argument type="service" id="sylius.repository.payment_request"/>
|
||||
<argument type="service" id="sylius.checker.finalized_payment_request"/>
|
||||
<argument type="service" id="sylius_api.context.user.token_based" />
|
||||
<tag name="api_platform.state_provider" priority="10"/>
|
||||
</service>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,5 +21,10 @@
|
|||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\ChosenPaymentMethodEligibility">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@ namespace Sylius\Bundle\ApiBundle\StateProvider\Shop\Payment\PaymentRequest;
|
|||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Bundle\PaymentBundle\Checker\FinalizedPaymentRequestCheckerInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
@ -35,6 +40,7 @@ final readonly class ItemProvider implements ProviderInterface
|
|||
private SectionProviderInterface $sectionProvider,
|
||||
private PaymentRequestRepositoryInterface $paymentRequestRepository,
|
||||
private FinalizedPaymentRequestCheckerInterface $finalizedPaymentRequestChecker,
|
||||
private ?UserContextInterface $userContext = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -45,14 +51,62 @@ final readonly class ItemProvider implements ProviderInterface
|
|||
Assert::isInstanceOf($this->sectionProvider->getSection(), ShopApiSection::class);
|
||||
|
||||
$paymentRequest = $this->paymentRequestRepository->find($uriVariables['hash']);
|
||||
if (null === $paymentRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
$paymentRequest === null ||
|
||||
$this->finalizedPaymentRequestChecker->isFinal($paymentRequest)
|
||||
) {
|
||||
if ($this->userContext !== null && !$this->isAccessibleByCurrentUser($paymentRequest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->finalizedPaymentRequestChecker->isFinal($paymentRequest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $paymentRequest;
|
||||
}
|
||||
|
||||
private function isAccessibleByCurrentUser(PaymentRequestInterface $paymentRequest): bool
|
||||
{
|
||||
$user = $this->userContext->getUser();
|
||||
|
||||
if ($user instanceof ShopUserInterface) {
|
||||
$customer = $user->getCustomer();
|
||||
|
||||
return $customer instanceof CustomerInterface && $this->isOwnedByCustomer($paymentRequest, $customer);
|
||||
}
|
||||
|
||||
return $this->isGuestOrder($paymentRequest);
|
||||
}
|
||||
|
||||
private function isOwnedByCustomer(PaymentRequestInterface $paymentRequest, CustomerInterface $customer): bool
|
||||
{
|
||||
$payment = $paymentRequest->getPayment();
|
||||
if (!$payment instanceof PaymentInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = $payment->getOrder();
|
||||
|
||||
return $order instanceof OrderInterface && $order->getCustomer() === $customer;
|
||||
}
|
||||
|
||||
private function isGuestOrder(PaymentRequestInterface $paymentRequest): bool
|
||||
{
|
||||
$payment = $paymentRequest->getPayment();
|
||||
if (!$payment instanceof PaymentInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = $payment->getOrder();
|
||||
if (!$order instanceof OrderInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$customer = $order->getCustomer();
|
||||
|
||||
return null === $customer
|
||||
|| null === $customer->getUser()
|
||||
|| $order->isCreatedByGuest();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Account\ChangePaymentMethod;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\ChoosePaymentMethod;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
|
|
@ -38,7 +39,7 @@ final class ChosenPaymentMethodEligibilityValidator extends ConstraintValidator
|
|||
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
Assert::isInstanceOf($value, ChoosePaymentMethod::class);
|
||||
Assert::isInstanceOfAny($value, [ChoosePaymentMethod::class, ChangePaymentMethod::class]);
|
||||
|
||||
/** @var ChosenPaymentMethodEligibility $constraint */
|
||||
Assert::isInstanceOf($constraint, ChosenPaymentMethodEligibility::class);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Sylius\Component\Core\Model\PaymentInterface;
|
|||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
|
||||
use Sylius\Component\Payment\Resolver\PaymentMethodsResolverInterface;
|
||||
|
||||
final class PaymentMethodChangerTest extends TestCase
|
||||
{
|
||||
|
|
@ -29,6 +30,8 @@ final class PaymentMethodChangerTest extends TestCase
|
|||
|
||||
private MockObject&PaymentMethodRepositoryInterface $paymentMethodRepository;
|
||||
|
||||
private MockObject&PaymentMethodsResolverInterface $paymentMethodsResolver;
|
||||
|
||||
private PaymentMethodChanger $paymentMethodChanger;
|
||||
|
||||
private MockObject&OrderInterface $order;
|
||||
|
|
@ -42,9 +45,11 @@ final class PaymentMethodChangerTest extends TestCase
|
|||
parent::setUp();
|
||||
$this->paymentRepository = $this->createMock(PaymentRepositoryInterface::class);
|
||||
$this->paymentMethodRepository = $this->createMock(PaymentMethodRepositoryInterface::class);
|
||||
$this->paymentMethodsResolver = $this->createMock(PaymentMethodsResolverInterface::class);
|
||||
$this->paymentMethodChanger = new PaymentMethodChanger(
|
||||
$this->paymentRepository,
|
||||
$this->paymentMethodRepository,
|
||||
$this->paymentMethodsResolver,
|
||||
);
|
||||
$this->order = $this->createMock(OrderInterface::class);
|
||||
$this->paymentMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
|
|
@ -70,6 +75,12 @@ final class PaymentMethodChangerTest extends TestCase
|
|||
->with('123', '444')
|
||||
->willReturn($this->payment);
|
||||
|
||||
$this->paymentMethodsResolver
|
||||
->expects(self::once())
|
||||
->method('getSupportedMethods')
|
||||
->with($this->payment)
|
||||
->willReturn([$this->paymentMethod]);
|
||||
|
||||
$this->order
|
||||
->expects(self::once())
|
||||
->method('getState')
|
||||
|
|
@ -147,6 +158,12 @@ final class PaymentMethodChangerTest extends TestCase
|
|||
->with('123', '444')
|
||||
->willReturn($this->payment);
|
||||
|
||||
$this->paymentMethodsResolver
|
||||
->expects(self::once())
|
||||
->method('getSupportedMethods')
|
||||
->with($this->payment)
|
||||
->willReturn([$this->paymentMethod]);
|
||||
|
||||
$this->order->expects(self::once())->method('getState')->willReturn(OrderInterface::STATE_NEW);
|
||||
|
||||
$this->payment->expects(self::once())->method('getState')->willReturn(PaymentInterface::STATE_NEW);
|
||||
|
|
|
|||
|
|
@ -18,12 +18,16 @@ use PHPUnit\Framework\MockObject\MockObject;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Command\Payment\AddPaymentRequest;
|
||||
use Sylius\Bundle\ApiBundle\CommandHandler\Payment\AddPaymentRequestHandler;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentMethodNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentNotFoundException;
|
||||
use Sylius\Bundle\PaymentBundle\Provider\DefaultActionProviderInterface;
|
||||
use Sylius\Bundle\PaymentBundle\Provider\DefaultPayloadProviderInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
|
||||
use Sylius\Component\Payment\Factory\PaymentRequestFactoryInterface;
|
||||
|
|
@ -44,6 +48,8 @@ final class AddPaymentRequestHandlerTest extends TestCase
|
|||
|
||||
private DefaultPayloadProviderInterface&MockObject $defaultPayloadProvider;
|
||||
|
||||
private MockObject&UserContextInterface $userContext;
|
||||
|
||||
private AddPaymentRequestHandler $addPaymentRequestHandler;
|
||||
|
||||
protected function setUp(): void
|
||||
|
|
@ -54,6 +60,7 @@ final class AddPaymentRequestHandlerTest extends TestCase
|
|||
$this->paymentRequestRepository = $this->createMock(PaymentRequestRepositoryInterface::class);
|
||||
$this->defaultActionProvider = $this->createMock(DefaultActionProviderInterface::class);
|
||||
$this->defaultPayloadProvider = $this->createMock(DefaultPayloadProviderInterface::class);
|
||||
$this->userContext = $this->createMock(UserContextInterface::class);
|
||||
|
||||
$this->addPaymentRequestHandler = new AddPaymentRequestHandler(
|
||||
$this->paymentMethodRepository,
|
||||
|
|
@ -62,6 +69,7 @@ final class AddPaymentRequestHandlerTest extends TestCase
|
|||
$this->paymentRequestRepository,
|
||||
$this->defaultActionProvider,
|
||||
$this->defaultPayloadProvider,
|
||||
$this->userContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -75,27 +83,110 @@ final class AddPaymentRequestHandlerTest extends TestCase
|
|||
$this->addPaymentRequestHandler->__invoke(new AddPaymentRequest('token', 1, 'bank_transfer'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_throws_an_exception_if_the_order_is_not_owned_by_the_logged_in_shop_user(): void
|
||||
{
|
||||
self::expectException(PaymentNotFoundException::class);
|
||||
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$shopUser = $this->createMock(ShopUserInterface::class);
|
||||
$orderCustomer = $this->createMock(CustomerInterface::class);
|
||||
$userCustomer = $this->createMock(CustomerInterface::class);
|
||||
|
||||
$this->paymentRepository->method('findOneByOrderToken')->with(1, 'token')->willReturn($payment);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
$order->method('getCustomer')->willReturn($orderCustomer);
|
||||
$this->userContext->method('getUser')->willReturn($shopUser);
|
||||
$shopUser->method('getCustomer')->willReturn($userCustomer);
|
||||
|
||||
$this->paymentMethodRepository->expects($this->never())->method('findOneBy');
|
||||
|
||||
$this->addPaymentRequestHandler->__invoke(new AddPaymentRequest('token', 1, 'bank_transfer'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_throws_an_exception_if_an_anonymous_user_creates_a_payment_request_for_a_customer_order(): void
|
||||
{
|
||||
self::expectException(PaymentNotFoundException::class);
|
||||
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$shopUser = $this->createMock(ShopUserInterface::class);
|
||||
|
||||
$this->paymentRepository->method('findOneByOrderToken')->with(1, 'token')->willReturn($payment);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
$order->method('getCustomer')->willReturn($customer);
|
||||
$customer->method('getUser')->willReturn($shopUser);
|
||||
$order->method('isCreatedByGuest')->willReturn(false);
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
|
||||
$this->paymentMethodRepository->expects($this->never())->method('findOneBy');
|
||||
|
||||
$this->addPaymentRequestHandler->__invoke(new AddPaymentRequest('token', 1, 'bank_transfer'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_throws_an_exception_if_there_is_no_payment_method_for_given_code(): void
|
||||
{
|
||||
self::expectException(PaymentMethodNotFoundException::class);
|
||||
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
|
||||
$this->paymentRepository->method('findOneByOrderToken')->with(1, 'token')->willReturn($payment);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
$order->method('getCustomer')->willReturn(null);
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
$this->paymentMethodRepository->method('findOneBy')->with(['code' => 'bank_transfer'])->willReturn(null);
|
||||
|
||||
$this->addPaymentRequestHandler->__invoke(new AddPaymentRequest('token', 1, 'bank_transfer'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_creates_a_payment_request(): void
|
||||
public function it_creates_a_payment_request_for_a_guest_order_requested_by_an_anonymous_user(): void
|
||||
{
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
|
||||
$this->paymentRepository->method('findOneByOrderToken')->with(1, 'token')->willReturn($payment);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
$order->method('getCustomer')->willReturn(null);
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
$this->paymentMethodRepository->method('findOneBy')->with(['code' => 'bank_transfer'])->willReturn($paymentMethod);
|
||||
$this->defaultActionProvider->method('getAction')->with($paymentRequest)->willReturn('authorize');
|
||||
$this->defaultPayloadProvider->method('getPayload')->with($paymentRequest)->willReturn(['foo' => 'bar']);
|
||||
|
||||
$this->paymentRequestFactory->method('create')->with($payment, $paymentMethod)->willReturn($paymentRequest);
|
||||
$paymentRequest->expects($this->once())->method('setAction')->with('authorize');
|
||||
$paymentRequest->expects($this->once())->method('setPayload')->with(['foo' => 'bar']);
|
||||
|
||||
self::assertSame(
|
||||
$paymentRequest,
|
||||
$this->addPaymentRequestHandler->__invoke(
|
||||
new AddPaymentRequest('token', 1, 'bank_transfer'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_creates_a_payment_request_for_an_order_owned_by_the_logged_in_shop_user(): void
|
||||
{
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$shopUser = $this->createMock(ShopUserInterface::class);
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
|
||||
$this->paymentRepository->method('findOneByOrderToken')->with(1, 'token')->willReturn($payment);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
$order->method('getCustomer')->willReturn($customer);
|
||||
$this->userContext->method('getUser')->willReturn($shopUser);
|
||||
$shopUser->method('getCustomer')->willReturn($customer);
|
||||
$this->paymentMethodRepository->method('findOneBy')->with(['code' => 'bank_transfer'])->willReturn($paymentMethod);
|
||||
$this->defaultActionProvider->method('getAction')->with($paymentRequest)->willReturn('authorize');
|
||||
$this->defaultPayloadProvider->method('getPayload')->with($paymentRequest)->willReturn(['foo' => 'bar']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,246 @@
|
|||
<?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 Tests\Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Expr\Comparison;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest\ShopUserBasedExtension;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\AdminApiSection;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Resource\Model\ResourceInterface;
|
||||
|
||||
final class ShopUserBasedExtensionTest extends TestCase
|
||||
{
|
||||
private ShopUserBasedExtension $extension;
|
||||
|
||||
private MockObject&SectionProviderInterface $sectionProvider;
|
||||
|
||||
private MockObject&UserContextInterface $userContext;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->sectionProvider = $this->createMock(SectionProviderInterface::class);
|
||||
$this->userContext = $this->createMock(UserContextInterface::class);
|
||||
$this->extension = new ShopUserBasedExtension($this->sectionProvider, $this->userContext);
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_collection_for_unsupported_resource(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToCollection($queryBuilder, $nameGenerator, ResourceInterface::class, new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_collection_for_admin_api_section(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$section = $this->createMock(AdminApiSection::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn($section);
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToCollection($queryBuilder, $nameGenerator, PaymentRequestInterface::class, new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_collection_for_anonymous_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToCollection($queryBuilder, $nameGenerator, PaymentRequestInterface::class, new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_collection_for_non_shop_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$user = $this->createMock(AdminUserInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn($user);
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToCollection($queryBuilder, $nameGenerator, PaymentRequestInterface::class, new Get());
|
||||
}
|
||||
|
||||
public function test_applies_conditions_to_collection(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$user = $this->createMock(ShopUserInterface::class);
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$expr = $this->createMock(Expr::class);
|
||||
$exprEq = $this->createMock(Comparison::class);
|
||||
|
||||
$user->method('getCustomer')->willReturn($customer);
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn($user);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('getRootAliases')->willReturn(['o']);
|
||||
|
||||
$nameGenerator->expects($this->exactly(2))
|
||||
->method('generateJoinAlias')
|
||||
->willReturnCallback(fn (string $alias) => $alias);
|
||||
$nameGenerator->expects($this->once())
|
||||
->method('generateParameterName')
|
||||
->with('customer')
|
||||
->willReturn('customer');
|
||||
|
||||
$queryBuilder->expects($this->exactly(2))
|
||||
->method('innerJoin')
|
||||
->willReturnCallback(function (string $join, string $alias) use ($queryBuilder): QueryBuilder {
|
||||
static $calls = 0;
|
||||
++$calls;
|
||||
if (1 === $calls) {
|
||||
self::assertSame('o.payment', $join);
|
||||
self::assertSame('payment', $alias);
|
||||
} else {
|
||||
self::assertSame('payment.order', $join);
|
||||
self::assertSame('order', $alias);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
});
|
||||
|
||||
$queryBuilder->expects($this->once())->method('expr')->willReturn($expr);
|
||||
$expr->expects($this->once())->method('eq')->with('order.customer', ':customer')->willReturn($exprEq);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('andWhere')->with($exprEq)->willReturn($queryBuilder);
|
||||
$queryBuilder->expects($this->once())->method('setParameter')->with('customer', $customer)->willReturn($queryBuilder);
|
||||
|
||||
$this->extension->applyToCollection($queryBuilder, $nameGenerator, PaymentRequestInterface::class, new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_item_for_unsupported_resource(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, ResourceInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_item_for_admin_api_section(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$section = $this->createMock(AdminApiSection::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn($section);
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_item_for_anonymous_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_to_item_for_non_shop_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$user = $this->createMock(AdminUserInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn($user);
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
$queryBuilder->expects($this->never())->method('andWhere');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_applies_conditions_to_item(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$user = $this->createMock(ShopUserInterface::class);
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$expr = $this->createMock(Expr::class);
|
||||
$exprEq = $this->createMock(Comparison::class);
|
||||
|
||||
$user->method('getCustomer')->willReturn($customer);
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn($user);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('getRootAliases')->willReturn(['o']);
|
||||
|
||||
$nameGenerator->expects($this->exactly(2))
|
||||
->method('generateJoinAlias')
|
||||
->willReturnCallback(fn (string $alias) => $alias);
|
||||
$nameGenerator->expects($this->once())
|
||||
->method('generateParameterName')
|
||||
->with('customer')
|
||||
->willReturn('customer');
|
||||
|
||||
$queryBuilder->expects($this->exactly(2))
|
||||
->method('innerJoin')
|
||||
->willReturnCallback(function (string $join, string $alias) use ($queryBuilder): QueryBuilder {
|
||||
static $calls = 0;
|
||||
++$calls;
|
||||
if (1 === $calls) {
|
||||
self::assertSame('o.payment', $join);
|
||||
self::assertSame('payment', $alias);
|
||||
} else {
|
||||
self::assertSame('payment.order', $join);
|
||||
self::assertSame('order', $alias);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
});
|
||||
|
||||
$queryBuilder->expects($this->once())->method('expr')->willReturn($expr);
|
||||
$expr->expects($this->once())->method('eq')->with('order.customer', ':customer')->willReturn($exprEq);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('andWhere')->with($exprEq)->willReturn($queryBuilder);
|
||||
$queryBuilder->expects($this->once())->method('setParameter')->with('customer', $customer)->willReturn($queryBuilder);
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<?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 Tests\Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Expr\Andx;
|
||||
use Doctrine\ORM\Query\Expr\Comparison;
|
||||
use Doctrine\ORM\Query\Expr\Orx;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\PaymentRequest\VisitorBasedExtension;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\AdminApiSection;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Resource\Model\ResourceInterface;
|
||||
|
||||
final class VisitorBasedExtensionTest extends TestCase
|
||||
{
|
||||
private VisitorBasedExtension $extension;
|
||||
|
||||
private MockObject&SectionProviderInterface $sectionProvider;
|
||||
|
||||
private MockObject&UserContextInterface $userContext;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->sectionProvider = $this->createMock(SectionProviderInterface::class);
|
||||
$this->userContext = $this->createMock(UserContextInterface::class);
|
||||
$this->extension = new VisitorBasedExtension($this->sectionProvider, $this->userContext);
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_for_unsupported_resource(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, ResourceInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_for_admin_section(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new AdminApiSection());
|
||||
$this->userContext->expects($this->never())->method('getUser');
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_does_not_apply_conditions_for_authenticated_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn($this->createMock(ShopUserInterface::class));
|
||||
$queryBuilder->expects($this->never())->method('getRootAliases');
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
public function test_applies_guest_order_filter_for_anonymous_user(): void
|
||||
{
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$nameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$expr = $this->createMock(Expr::class);
|
||||
$orX = $this->createMock(Orx::class);
|
||||
$andX = $this->createMock(Andx::class);
|
||||
$comparison = $this->createMock(Comparison::class);
|
||||
|
||||
$this->sectionProvider->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->method('getUser')->willReturn(null);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('getRootAliases')->willReturn(['o']);
|
||||
|
||||
$nameGenerator->expects($this->exactly(4))
|
||||
->method('generateJoinAlias')
|
||||
->willReturnCallback(fn (string $alias) => $alias);
|
||||
|
||||
$nameGenerator->expects($this->once())
|
||||
->method('generateParameterName')
|
||||
->with('createdByGuest')
|
||||
->willReturn('createdByGuest');
|
||||
|
||||
$queryBuilder->expects($this->exactly(2))
|
||||
->method('innerJoin')
|
||||
->willReturnCallback(function (string $join, string $alias) use ($queryBuilder): QueryBuilder {
|
||||
static $calls = 0;
|
||||
++$calls;
|
||||
if (1 === $calls) {
|
||||
self::assertSame('o.payment', $join);
|
||||
self::assertSame('payment', $alias);
|
||||
} else {
|
||||
self::assertSame('payment.order', $join);
|
||||
self::assertSame('order', $alias);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
});
|
||||
|
||||
$queryBuilder->expects($this->exactly(2))
|
||||
->method('leftJoin')
|
||||
->willReturnCallback(function (string $join, string $alias) use ($queryBuilder): QueryBuilder {
|
||||
static $calls = 0;
|
||||
++$calls;
|
||||
if (1 === $calls) {
|
||||
self::assertSame('order.customer', $join);
|
||||
self::assertSame('customer', $alias);
|
||||
} else {
|
||||
self::assertSame('customer.user', $join);
|
||||
self::assertSame('user', $alias);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
});
|
||||
|
||||
$queryBuilder->expects($this->exactly(6))->method('expr')->willReturn($expr);
|
||||
|
||||
$expr->expects($this->exactly(2))
|
||||
->method('isNull')
|
||||
->willReturnCallback(fn (string $arg) => $arg . ' IS NULL');
|
||||
|
||||
$expr->expects($this->once())
|
||||
->method('isNotNull')
|
||||
->with('user')
|
||||
->willReturn('user IS NOT NULL');
|
||||
|
||||
$expr->expects($this->once())
|
||||
->method('eq')
|
||||
->with('order.createdByGuest', ':createdByGuest')
|
||||
->willReturn($comparison);
|
||||
|
||||
$expr->expects($this->once())
|
||||
->method('andX')
|
||||
->with('user IS NOT NULL', $comparison)
|
||||
->willReturn($andX);
|
||||
|
||||
$expr->expects($this->once())
|
||||
->method('orX')
|
||||
->with('user IS NULL', 'order.customer IS NULL', $andX)
|
||||
->willReturn($orX);
|
||||
|
||||
$queryBuilder->expects($this->once())->method('andWhere')->with($orX)->willReturn($queryBuilder);
|
||||
$queryBuilder->expects($this->once())->method('setParameter')->with('createdByGuest', true)->willReturn($queryBuilder);
|
||||
|
||||
$this->extension->applyToItem($queryBuilder, $nameGenerator, PaymentRequestInterface::class, [], new Get());
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,16 @@ use ApiPlatform\Metadata\Put;
|
|||
use ApiPlatform\State\ProviderInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\AdminApiSection;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\ApiBundle\StateProvider\Shop\Payment\PaymentRequest\ItemProvider;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Bundle\PaymentBundle\Checker\FinalizedPaymentRequestCheckerInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Component\Payment\Repository\PaymentRequestRepositoryInterface;
|
||||
|
||||
|
|
@ -30,6 +35,8 @@ final class ItemProviderTest extends TestCase
|
|||
{
|
||||
private MockObject&SectionProviderInterface $sectionProvider;
|
||||
|
||||
private MockObject&UserContextInterface $userContext;
|
||||
|
||||
private MockObject&PaymentRequestRepositoryInterface $paymentRequestRepository;
|
||||
|
||||
private FinalizedPaymentRequestCheckerInterface&MockObject $finalizedPaymentRequestChecker;
|
||||
|
|
@ -39,10 +46,17 @@ final class ItemProviderTest extends TestCase
|
|||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->sectionProvider = $this->createMock(SectionProviderInterface::class);
|
||||
$this->userContext = $this->createMock(UserContextInterface::class);
|
||||
$this->paymentRequestRepository = $this->createMock(PaymentRequestRepositoryInterface::class);
|
||||
$this->finalizedPaymentRequestChecker = $this->createMock(FinalizedPaymentRequestCheckerInterface::class);
|
||||
$this->itemProvider = new ItemProvider($this->sectionProvider, $this->paymentRequestRepository, $this->finalizedPaymentRequestChecker);
|
||||
$this->itemProvider = new ItemProvider(
|
||||
$this->sectionProvider,
|
||||
$this->paymentRequestRepository,
|
||||
$this->finalizedPaymentRequestChecker,
|
||||
$this->userContext,
|
||||
);
|
||||
}
|
||||
|
||||
public function testAStateProvider(): void
|
||||
|
|
@ -50,63 +64,240 @@ final class ItemProviderTest extends TestCase
|
|||
self::assertInstanceOf(ProviderInterface::class, $this->itemProvider);
|
||||
}
|
||||
|
||||
public function testThrowsAnExceptionIfOperationClassIsNotPayment(): void
|
||||
public function testThrowsAnExceptionIfOperationClassIsNotPaymentRequest(): void
|
||||
{
|
||||
/** @var Operation|MockObject $operationMock */
|
||||
$operationMock = $this->createMock(Operation::class);
|
||||
$operationMock->expects(self::once())->method('getClass')->willReturn(\stdClass::class);
|
||||
/** @var Operation&MockObject $operation */
|
||||
$operation = $this->createMock(Operation::class);
|
||||
$operation->expects(self::once())->method('getClass')->willReturn(\stdClass::class);
|
||||
|
||||
self::expectException(\InvalidArgumentException::class);
|
||||
$this->itemProvider->provide($operationMock);
|
||||
|
||||
$this->itemProvider->provide($operation);
|
||||
}
|
||||
|
||||
public function testThrowsAnExceptionIfOperationIsNotPut(): void
|
||||
{
|
||||
/** @var Operation|MockObject $operationMock */
|
||||
$operationMock = $this->createMock(Operation::class);
|
||||
$operationMock->expects(self::once())->method('getClass')->willReturn(PaymentRequestInterface::class);
|
||||
/** @var Operation&MockObject $operation */
|
||||
$operation = $this->createMock(Operation::class);
|
||||
$operation->expects(self::once())->method('getClass')->willReturn(PaymentRequestInterface::class);
|
||||
|
||||
self::expectException(\InvalidArgumentException::class);
|
||||
$this->itemProvider->provide($operationMock);
|
||||
|
||||
$this->itemProvider->provide($operation);
|
||||
}
|
||||
|
||||
public function testThrowsAnExceptionIfSectionIsNotShopApiSection(): void
|
||||
{
|
||||
$operation = new Put(class: PaymentRequestInterface::class, name: 'put');
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new AdminApiSection());
|
||||
|
||||
self::expectException(\InvalidArgumentException::class);
|
||||
$this->itemProvider->provide($operation, [], []);
|
||||
|
||||
$this->itemProvider->provide($this->putOperation(), [], []);
|
||||
}
|
||||
|
||||
public function testReturnsNothingIfPaymentRequestIsNotFound(): void
|
||||
{
|
||||
$hash = 'hash';
|
||||
$operation = new Put(class: PaymentRequestInterface::class, name: 'put');
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with($hash)->willReturn(null);
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn(null);
|
||||
$this->userContext->expects(self::never())->method('getUser');
|
||||
$this->finalizedPaymentRequestChecker->expects(self::never())->method('isFinal');
|
||||
$this->assertNull($this->itemProvider->provide($operation, ['hash' => $hash], []));
|
||||
|
||||
self::assertNull($this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsNothingIfPaymentRequestIsInFinalState(): void
|
||||
public function testReturnsNothingIfShopUserHasNoCustomer(): void
|
||||
{
|
||||
/** @var PaymentRequestInterface|MockObject $paymentRequestMock */
|
||||
$paymentRequestMock = $this->createMock(PaymentRequestInterface::class);
|
||||
$hash = 'hash';
|
||||
$operation = new Put(class: PaymentRequestInterface::class, name: 'put');
|
||||
/** @var ShopUserInterface&MockObject $user */
|
||||
$user = $this->createMock(ShopUserInterface::class);
|
||||
$user->expects(self::once())->method('getCustomer')->willReturn(null);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with($hash)->willReturn($paymentRequestMock);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequestMock)->willReturn(true);
|
||||
$this->assertNull($this->itemProvider->provide($operation, ['hash' => $hash], []));
|
||||
$this->userContext->expects(self::once())->method('getUser')->willReturn($user);
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn(
|
||||
$this->createMock(PaymentRequestInterface::class),
|
||||
);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::never())->method('isFinal');
|
||||
|
||||
self::assertNull($this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsPaymentRequestByHash(): void
|
||||
public function testReturnsNothingIfPaymentRequestIsNotOwnedByTheCustomer(): void
|
||||
{
|
||||
/** @var PaymentRequestInterface|MockObject $paymentRequestMock */
|
||||
$paymentRequestMock = $this->createMock(PaymentRequestInterface::class);
|
||||
$hash = 'hash';
|
||||
$operation = new Put(class: PaymentRequestInterface::class, name: 'put');
|
||||
$customer = $this->stubAuthenticatedCustomer();
|
||||
|
||||
/** @var OrderInterface&MockObject $order */
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$order->method('getCustomer')->willReturn($this->createMock(CustomerInterface::class));
|
||||
|
||||
/** @var PaymentInterface&MockObject $payment */
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
$paymentRequest->method('getPayment')->willReturn($payment);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with($hash)->willReturn($paymentRequestMock);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequestMock)->willReturn(false);
|
||||
self::assertSame($paymentRequestMock, $this->itemProvider->provide($operation, ['hash' => $hash], []));
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::never())->method('isFinal');
|
||||
|
||||
self::assertNull($this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
self::assertNotSame($customer, $order->getCustomer());
|
||||
}
|
||||
|
||||
public function testReturnsNothingIfPaymentRequestIsOwnedByTheCustomerButInFinalState(): void
|
||||
{
|
||||
$paymentRequest = $this->createOwnedPaymentRequest($this->stubAuthenticatedCustomer());
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequest)->willReturn(true);
|
||||
|
||||
self::assertNull($this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsThePaymentRequestWhenOwnedByTheCustomerAndNotFinal(): void
|
||||
{
|
||||
$paymentRequest = $this->createOwnedPaymentRequest($this->stubAuthenticatedCustomer());
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequest)->willReturn(false);
|
||||
|
||||
self::assertSame($paymentRequest, $this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testWithoutUserContextSkipsOwnershipCheck(): void
|
||||
{
|
||||
$itemProvider = new ItemProvider(
|
||||
$this->sectionProvider,
|
||||
$this->paymentRequestRepository,
|
||||
$this->finalizedPaymentRequestChecker,
|
||||
);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->userContext->expects(self::never())->method('getUser');
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequest)->willReturn(false);
|
||||
|
||||
self::assertSame($paymentRequest, $itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsThePaymentRequestForAnonymousUserWhenOrderHasNoCustomer(): void
|
||||
{
|
||||
$this->userContext->expects(self::once())->method('getUser')->willReturn(null);
|
||||
|
||||
/** @var OrderInterface&MockObject $order */
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$order->method('getCustomer')->willReturn(null);
|
||||
|
||||
/** @var PaymentInterface&MockObject $payment */
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
$paymentRequest->method('getPayment')->willReturn($payment);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequest)->willReturn(false);
|
||||
|
||||
self::assertSame($paymentRequest, $this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsThePaymentRequestForAnonymousUserWhenOrderWasCreatedByGuest(): void
|
||||
{
|
||||
$this->userContext->expects(self::once())->method('getUser')->willReturn(null);
|
||||
|
||||
/** @var CustomerInterface&MockObject $customer */
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$customer->method('getUser')->willReturn($this->createMock(ShopUserInterface::class));
|
||||
|
||||
/** @var OrderInterface&MockObject $order */
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$order->method('getCustomer')->willReturn($customer);
|
||||
$order->method('isCreatedByGuest')->willReturn(true);
|
||||
|
||||
/** @var PaymentInterface&MockObject $payment */
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
$paymentRequest->method('getPayment')->willReturn($payment);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::once())->method('isFinal')->with($paymentRequest)->willReturn(false);
|
||||
|
||||
self::assertSame($paymentRequest, $this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
public function testReturnsNothingForAnonymousUserWhenOrderHasRegisteredCustomerAndNotCreatedByGuest(): void
|
||||
{
|
||||
$this->userContext->expects(self::once())->method('getUser')->willReturn(null);
|
||||
|
||||
/** @var CustomerInterface&MockObject $customer */
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
$customer->method('getUser')->willReturn($this->createMock(ShopUserInterface::class));
|
||||
|
||||
/** @var OrderInterface&MockObject $order */
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$order->method('getCustomer')->willReturn($customer);
|
||||
$order->method('isCreatedByGuest')->willReturn(false);
|
||||
|
||||
/** @var PaymentInterface&MockObject $payment */
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
$paymentRequest->method('getPayment')->willReturn($payment);
|
||||
|
||||
$this->sectionProvider->expects(self::once())->method('getSection')->willReturn(new ShopApiSection());
|
||||
$this->paymentRequestRepository->expects(self::once())->method('find')->with('hash')->willReturn($paymentRequest);
|
||||
$this->finalizedPaymentRequestChecker->expects(self::never())->method('isFinal');
|
||||
|
||||
self::assertNull($this->itemProvider->provide($this->putOperation(), ['hash' => 'hash'], []));
|
||||
}
|
||||
|
||||
private function putOperation(): Put
|
||||
{
|
||||
return new Put(class: PaymentRequestInterface::class, name: 'put');
|
||||
}
|
||||
|
||||
private function stubAuthenticatedCustomer(): CustomerInterface&MockObject
|
||||
{
|
||||
/** @var CustomerInterface&MockObject $customer */
|
||||
$customer = $this->createMock(CustomerInterface::class);
|
||||
|
||||
/** @var ShopUserInterface&MockObject $user */
|
||||
$user = $this->createMock(ShopUserInterface::class);
|
||||
$user->method('getCustomer')->willReturn($customer);
|
||||
|
||||
$this->userContext->expects(self::once())->method('getUser')->willReturn($user);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
private function createOwnedPaymentRequest(CustomerInterface $customer): MockObject&PaymentRequestInterface
|
||||
{
|
||||
/** @var OrderInterface&MockObject $order */
|
||||
$order = $this->createMock(OrderInterface::class);
|
||||
$order->method('getCustomer')->willReturn($customer);
|
||||
|
||||
/** @var PaymentInterface&MockObject $payment */
|
||||
$payment = $this->createMock(PaymentInterface::class);
|
||||
$payment->method('getOrder')->willReturn($order);
|
||||
|
||||
/** @var PaymentRequestInterface&MockObject $paymentRequest */
|
||||
$paymentRequest = $this->createMock(PaymentRequestInterface::class);
|
||||
$paymentRequest->method('getPayment')->willReturn($payment);
|
||||
|
||||
return $paymentRequest;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Tests\Sylius\Bundle\ApiBundle\Validator\Constraints;
|
|||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Command\Account\ChangePaymentMethod;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\ChoosePaymentMethod;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\ChosenPaymentMethodEligibility;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\ChosenPaymentMethodEligibilityValidator;
|
||||
|
|
@ -100,6 +101,28 @@ final class ChosenPaymentMethodEligibilityValidatorTest extends TestCase
|
|||
$this->chosenPaymentMethodEligibilityValidator->validate($command, new ChosenPaymentMethodEligibility());
|
||||
}
|
||||
|
||||
public function testAddsViolationIfChangePaymentMethodDoesNotMatchSupportedMethods(): void
|
||||
{
|
||||
/** @var PaymentMethodInterface|MockObject $outOfChannelPaymentMethodMock */
|
||||
$outOfChannelPaymentMethodMock = $this->createMock(PaymentMethodInterface::class);
|
||||
/** @var PaymentMethodInterface|MockObject $supportedPaymentMethodMock */
|
||||
$supportedPaymentMethodMock = $this->createMock(PaymentMethodInterface::class);
|
||||
/** @var PaymentInterface|MockObject $paymentMock */
|
||||
$paymentMock = $this->createMock(PaymentInterface::class);
|
||||
$command = new ChangePaymentMethod(
|
||||
orderTokenValue: 'ORDER_TOKEN',
|
||||
paymentId: 123,
|
||||
paymentMethodCode: 'PAYMENT_METHOD_CODE',
|
||||
);
|
||||
$this->paymentMethodRepository->expects(self::once())->method('findOneBy')->with(['code' => 'PAYMENT_METHOD_CODE'])->willReturn($outOfChannelPaymentMethodMock);
|
||||
$outOfChannelPaymentMethodMock->expects(self::once())->method('getName')->willReturn('offline');
|
||||
$this->paymentRepository->expects(self::once())->method('find')->with('123')->willReturn($paymentMock);
|
||||
$this->paymentMethodsResolver->expects(self::once())->method('getSupportedMethods')->with($paymentMock)->willReturn([$supportedPaymentMethodMock]);
|
||||
$this->executionContext->expects(self::once())->method('addViolation')->with('sylius.payment_method.not_available', ['%name%' => 'offline'])
|
||||
;
|
||||
$this->chosenPaymentMethodEligibilityValidator->validate($command, new ChosenPaymentMethodEligibility());
|
||||
}
|
||||
|
||||
public function testAddsViolationIfPaymentDoesNotExist(): void
|
||||
{
|
||||
/** @var PaymentMethodInterface|MockObject $paymentMethodMock */
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ use Doctrine\Persistence\ObjectManager;
|
|||
use Sylius\Bundle\UiBundle\Twig\Component\ResourceFormComponentTrait;
|
||||
use Sylius\Bundle\UiBundle\Twig\Component\TemplatePropTrait;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\OrderCheckoutStates;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Sylius\Component\Order\SyliusCartEvents;
|
||||
use Sylius\Resource\Model\ResourceInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
|
|
@ -56,10 +58,28 @@ class FormComponent
|
|||
$this->initialize($orderRepository, $formFactory, $resourceClass, $formClass);
|
||||
}
|
||||
|
||||
public function hydrateResource(mixed $value): ?ResourceInterface
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $this->createResource();
|
||||
}
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->repository->find($value);
|
||||
|
||||
if (!$order instanceof OrderInterface
|
||||
|| $order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED
|
||||
) {
|
||||
return $this->createResource();
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
#[PreReRender(priority: -100)]
|
||||
public function saveCart(): void
|
||||
{
|
||||
if ($this->shouldSaveCart) {
|
||||
if ($this->shouldSaveCart && $this->resource?->getId() !== null) {
|
||||
$form = $this->getForm();
|
||||
if ($form->isValid()) {
|
||||
$this->eventDispatcher->dispatch(new GenericEvent($form->getData()), SyliusCartEvents::CART_CHANGE);
|
||||
|
|
@ -72,6 +92,10 @@ class FormComponent
|
|||
#[LiveAction]
|
||||
public function removeItem(#[LiveArg] int $index): void
|
||||
{
|
||||
if ($this->resource?->getId() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->formValues['items'];
|
||||
unset($data[$index]);
|
||||
$this->formValues['items'] = array_values($data);
|
||||
|
|
@ -91,6 +115,10 @@ class FormComponent
|
|||
#[LiveAction]
|
||||
public function clearCart(): void
|
||||
{
|
||||
if ($this->resource?->getId() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->formValues['items'] = [];
|
||||
$this->eventDispatcher->dispatch(new GenericEvent($this->resource), SyliusCartEvents::CART_CLEAR);
|
||||
$this->manager->remove($this->resource);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
Sylius\Component\Payment\Model\PaymentMethodTranslation:
|
||||
payment_method_not_in_channel_translation:
|
||||
name: 'Out of channel'
|
||||
locale: 'en_US'
|
||||
description: '<paragraph(2)>'
|
||||
translatable: '@payment_method_not_in_channel'
|
||||
|
||||
Sylius\Component\Core\Model\PaymentMethod:
|
||||
payment_method_not_in_channel:
|
||||
code: 'PAYMENT_METHOD_NOT_IN_CHANNEL'
|
||||
enabled: true
|
||||
gatewayConfig: '@gateway_offline'
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
- '@payment_method_not_in_channel_translation'
|
||||
channels: ['@channel_mobile']
|
||||
|
|
@ -15,4 +15,5 @@ Sylius\Component\Core\Model\Order:
|
|||
paymentState: "awaiting_payment"
|
||||
shippingState: "ready"
|
||||
tokenValue: "token"
|
||||
customer: "@customer_oliver"
|
||||
__calls:
|
||||
- setCustomerWithAuthorization: ["@customer_oliver"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
Sylius\Component\Core\Model\Payment:
|
||||
payment:
|
||||
method: "@payment_method_cash_on_delivery"
|
||||
order: "@order"
|
||||
state: "processing"
|
||||
amount: 1000
|
||||
currencyCode: "USD"
|
||||
|
||||
Sylius\Component\Core\Model\Order:
|
||||
order:
|
||||
channel: "@channel_web"
|
||||
currencyCode: "USD"
|
||||
localeCode: "en_US"
|
||||
state: "new"
|
||||
paymentState: "awaiting_payment"
|
||||
shippingState: "ready"
|
||||
tokenValue: "token"
|
||||
customer: "@customer_oliver"
|
||||
|
|
@ -292,4 +292,40 @@ final class PaymentMethodTest extends JsonApiTestCase
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_to_choose_payment_method_that_is_not_assigned_to_channel(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'channel/channel.yaml',
|
||||
'cart.yaml',
|
||||
'country.yaml',
|
||||
'shipping_method.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_method/out_of_channel_payment_method.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentMethodInterface $outOfChannelPaymentMethod */
|
||||
$outOfChannelPaymentMethod = $fixtures['payment_method_not_in_channel'];
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$cart = $this->updateCartWithAddress($tokenValue);
|
||||
$cart = $this->dispatchShippingMethodChooseCommand($tokenValue, 'DHL', $cart->getShipments()->first()->getId());
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payments/%s', $tokenValue, $cart->getLastPayment()->getId()),
|
||||
server: $this->headerBuilder()->withMergePatchJsonContentType()->withJsonLdAccept()->build(),
|
||||
content: json_encode([
|
||||
'paymentMethod' => $outOfChannelPaymentMethod->getCode(),
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseViolations(
|
||||
[
|
||||
['propertyPath' => '', 'message' => 'The payment method Out of channel is not available for this order. Please choose another one.'],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,6 +467,40 @@ final class OrdersTest extends JsonApiTestCase
|
|||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_to_change_payment_method_to_one_not_assigned_to_channel(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'cart.yaml',
|
||||
'country.yaml',
|
||||
'customer.yaml',
|
||||
'shipping_method.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_method/out_of_channel_payment_method.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentMethodInterface $outOfChannelPaymentMethod */
|
||||
$outOfChannelPaymentMethod = $fixtures['payment_method_not_in_channel'];
|
||||
$order = $this->placeOrder('token', 'oliver@doe.com');
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/account/orders/token/payments/%s', $order->getPayments()->first()->getId()),
|
||||
server: $this->headerBuilder()->withMergePatchJsonContentType()->withJsonLdAccept()->withShopUserAuthorization('oliver@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'paymentMethod' => $outOfChannelPaymentMethod->getCode(),
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseViolations(
|
||||
[
|
||||
['propertyPath' => '', 'message' => 'The payment method Out of channel is not available for this order. Please choose another one.'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_gets_payment_configuration_of_order_created_by_a_user_authenticated_as_a_user(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Sylius\Tests\Api\Shop;
|
|||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Payment\Model\PaymentRequestInterface;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Sylius\Tests\Api\Utils\OrderPlacerTrait;
|
||||
|
|
@ -253,6 +254,418 @@ final class PaymentRequestsTest extends JsonApiTestCase
|
|||
$this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_an_anonymous_customer_to_get_a_payment_request_owned_by_a_customer(): void
|
||||
{
|
||||
$paymentRequest = $this->loadPaymentRequestOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_customer_to_get_a_payment_request_owned_by_another_customer(): void
|
||||
{
|
||||
$paymentRequest = $this->loadPaymentRequestOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withShopUserAuthorization('dave@doe.com')->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_an_anonymous_customer_to_update_a_payment_request_owned_by_a_customer(): void
|
||||
{
|
||||
$paymentRequest = $this->loadPaymentRequestOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'payload' => [
|
||||
'target_path' => 'https://attacker.tld/target-path',
|
||||
'after_path' => 'https://attacker.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_customer_to_update_a_payment_request_owned_by_another_customer(): void
|
||||
{
|
||||
$paymentRequest = $this->loadPaymentRequestOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('dave@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'payload' => [
|
||||
'target_path' => 'https://attacker.tld/target-path',
|
||||
'after_path' => 'https://attacker.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_shop_user_to_get_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$this->setUpDefaultGetHeaders();
|
||||
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withShopUserAuthorization('oliver@doe.com')->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_shop_user_to_update_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/target-path',
|
||||
'after_path' => 'https://myshop.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_get_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$this->setUpDefaultGetHeaders();
|
||||
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_update_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$this->setUpDefaultGetHeaders();
|
||||
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'channel/channel.yaml',
|
||||
'gateway_config_payment_request.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/new-target-path',
|
||||
'after_path' => 'https://myshop.tld/new-after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_get_a_payment_request_for_an_order_created_as_guest_by_a_registered_customer(): void
|
||||
{
|
||||
$this->setUpDefaultGetHeaders();
|
||||
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order_with_customer_created_as_guest.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_update_a_payment_request_for_an_order_created_as_guest_by_a_registered_customer(): void
|
||||
{
|
||||
$this->setUpDefaultGetHeaders();
|
||||
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'gateway_config_payment_request.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order_with_customer_created_as_guest.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/new-target-path',
|
||||
'after_path' => 'https://myshop.tld/new-after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_an_anonymous_customer_to_create_a_payment_request_for_an_order_owned_by_a_customer(): void
|
||||
{
|
||||
$payment = $this->loadOrderOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://attacker.tld/target-path',
|
||||
'after_path' => 'https://attacker.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_customer_to_create_a_payment_request_for_an_order_owned_by_another_customer(): void
|
||||
{
|
||||
$payment = $this->loadOrderOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('dave@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://attacker.tld/target-path',
|
||||
'after_path' => 'https://attacker.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_allow_a_shop_user_to_create_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$payment = $this->loadGuestOrder();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/target-path',
|
||||
'after_path' => 'https://myshop.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_a_customer_to_create_a_payment_request_for_their_own_order(): void
|
||||
{
|
||||
$payment = $this->loadOrderOwnedByOliver();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/target-path',
|
||||
'after_path' => 'https://myshop.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_create_a_payment_request_for_a_guest_order(): void
|
||||
{
|
||||
$payment = $this->loadGuestOrder();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/target-path',
|
||||
'after_path' => 'https://myshop.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_allows_an_anonymous_user_to_create_a_payment_request_for_an_order_created_as_guest_by_a_registered_customer(): void
|
||||
{
|
||||
$payment = $this->loadOrderCreatedAsGuestByRegisteredCustomer();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/payment-requests', $payment->getOrder()->getTokenValue()),
|
||||
server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->build(),
|
||||
content: json_encode([
|
||||
'paymentId' => $payment->getId(),
|
||||
'paymentMethodCode' => $payment->getMethod()->getCode(),
|
||||
'payload' => [
|
||||
'target_path' => 'https://myshop.tld/target-path',
|
||||
'after_path' => 'https://myshop.tld/after-path',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
private function loadOrderOwnedByOliver(): PaymentInterface
|
||||
{
|
||||
return $this->loadOrderPayment('payment_request/order_with_customer.yaml');
|
||||
}
|
||||
|
||||
private function loadGuestOrder(): PaymentInterface
|
||||
{
|
||||
return $this->loadOrderPayment('payment_request/order.yaml');
|
||||
}
|
||||
|
||||
private function loadOrderCreatedAsGuestByRegisteredCustomer(): PaymentInterface
|
||||
{
|
||||
return $this->loadOrderPayment('payment_request/order_with_customer_created_as_guest.yaml');
|
||||
}
|
||||
|
||||
private function loadOrderPayment(string $orderFixture): PaymentInterface
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
$orderFixture,
|
||||
]);
|
||||
|
||||
/** @var PaymentInterface $payment */
|
||||
$payment = $fixtures['payment'];
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
private function loadPaymentRequestOwnedByOliver(): PaymentRequestInterface
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/shop_user.yaml',
|
||||
'channel/channel.yaml',
|
||||
'payment_method.yaml',
|
||||
'payment_request/payment_request.yaml',
|
||||
'payment_request/order_with_customer.yaml',
|
||||
]);
|
||||
|
||||
/** @var PaymentRequestInterface $paymentRequest */
|
||||
$paymentRequest = $fixtures['payment_request_capture'];
|
||||
|
||||
return $paymentRequest;
|
||||
}
|
||||
|
||||
public static function createPaymentRequestProvider(): iterable
|
||||
{
|
||||
yield 'Payment request' => [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue