diff --git a/config/services.xml b/config/services.xml index cf9d38c9..742f048f 100644 --- a/config/services.xml +++ b/config/services.xml @@ -39,10 +39,6 @@ - - - - %sylius_paypal.sandbox% @@ -68,9 +64,6 @@ - - - %sylius_paypal.sandbox% @@ -370,5 +363,13 @@ %sylius_paypal.supported_locales% + + + + + diff --git a/config/validation/PaymentMethod.xml b/config/validation/PaymentMethod.xml new file mode 100644 index 00000000..b4102997 --- /dev/null +++ b/config/validation/PaymentMethod.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/features/managing_multiple_paypal_payment_methods.feature b/features/managing_multiple_paypal_payment_methods.feature new file mode 100644 index 00000000..f334a2de --- /dev/null +++ b/features/managing_multiple_paypal_payment_methods.feature @@ -0,0 +1,30 @@ +@managing_payment_methods +Feature: Managing multiple PayPal payment methods + In order to switch between PayPal configurations + As an Administrator + I want to have multiple PayPal methods but only one enabled at a time + + Background: + Given the store operates on a single channel in "United States" + And I am logged in as an administrator + + @ui + Scenario: Cannot create and enable a new PayPal method when another is already enabled + Given the store allows paying with "PayPal Sandbox" with "PayPal" factory name + When I create a new PayPal payment method "PayPal Production" and try to save it as enabled + Then I should see a validation error that only one PayPal method can be enabled + And the PayPal payment method "PayPal Production" should not exist + + @ui + Scenario: Cannot enable an existing PayPal method when another is already enabled + Given the store allows paying with "PayPal Sandbox" with "PayPal" factory name + And the store has a disabled "PayPal Production" payment method with "PayPal" gateway factory + When I try to enable the PayPal payment method "PayPal Production" + Then I should see a validation error that only one PayPal method can be enabled + And the PayPal payment method "PayPal Production" should still be disabled + + @ui + Scenario: Can create and enable a new PayPal method when no other is enabled + Given the store has a disabled "PayPal Sandbox" payment method with "PayPal" gateway factory + When I create a new PayPal payment method "PayPal Production" and save it as enabled + Then the new PayPal payment method should be in the list and enabled diff --git a/features/trying_to_onboard_more_than_one_pay_pal_seller.feature b/features/trying_to_onboard_more_than_one_pay_pal_seller.feature deleted file mode 100644 index a7fe86a2..00000000 --- a/features/trying_to_onboard_more_than_one_pay_pal_seller.feature +++ /dev/null @@ -1,20 +0,0 @@ -@managing_payment_methods -Feature: Trying to onboard more than one PayPal seller - In order to handle PayPal integration properly - As an Administrator - I want to be prevented from onboarding more than one PayPal seller - - Background: - Given the store operates on a single channel in "United States" - And the store allows paying with "PayPal" with "PayPal" factory name - And I am logged in as an administrator - - @ui - Scenario: Trying to onboard second PayPal seller - When I try to create a new payment method with "PayPal" gateway factory - Then I should be notified that I cannot onboard more than one PayPal seller - - @ui - Scenario: Being able to create different payment methods - When I want to create a new offline payment method - Then I should not be notified that I cannot onboard more than one PayPal seller diff --git a/src/Form/Extension/PaymentMethodTypeExtension.php b/src/Form/Extension/PaymentMethodTypeExtension.php deleted file mode 100644 index 65ce647f..00000000 --- a/src/Form/Extension/PaymentMethodTypeExtension.php +++ /dev/null @@ -1,51 +0,0 @@ -addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { - /** @var PaymentMethodInterface $data */ - $data = $event->getData(); - $form = $event->getForm(); - - /** @var GatewayConfigInterface $gatewayConfig */ - $gatewayConfig = $data->getGatewayConfig(); - if ($gatewayConfig->getFactoryName() === SyliusPayPalExtension::PAYPAL_FACTORY_NAME) { - $form->add('enabled', HiddenType::class, [ - 'required' => false, - 'label' => 'sylius.form.payment_method.enabled', - 'data' => $data->isEnabled(), - ]); - } - }); - } - - public static function getExtendedTypes(): iterable - { - return [PaymentMethodType::class]; - } -} diff --git a/src/Listener/PayPalPaymentMethodListener.php b/src/Listener/PayPalPaymentMethodListener.php index 061ebc69..3dbe9389 100644 --- a/src/Listener/PayPalPaymentMethodListener.php +++ b/src/Listener/PayPalPaymentMethodListener.php @@ -16,29 +16,21 @@ use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension; -use Sylius\PayPalPlugin\Exception\PayPalPaymentMethodNotFoundException; use Sylius\PayPalPlugin\Onboarding\Initiator\OnboardingInitiatorInterface; -use Sylius\PayPalPlugin\Provider\FlashBagProvider; -use Sylius\PayPalPlugin\Provider\PayPalPaymentMethodProviderInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Webmozart\Assert\Assert; final readonly class PayPalPaymentMethodListener { public function __construct( private OnboardingInitiatorInterface $onboardingInitiator, - private UrlGeneratorInterface $urlGenerator, - private RequestStack $flashBagOrRequestStack, - private PayPalPaymentMethodProviderInterface $payPalPaymentMethodProvider, private bool $isSandbox = false, ) { } public function initializeCreate(ResourceControllerEvent $event): void { - /** @var object $paymentMethod */ + /** @var PaymentMethodInterface|mixed $paymentMethod */ $paymentMethod = $event->getSubject(); Assert::isInstanceOf($paymentMethod, PaymentMethodInterface::class); @@ -46,16 +38,6 @@ public function initializeCreate(ResourceControllerEvent $event): void return; } - if ($this->isTherePayPalPaymentMethod()) { - FlashBagProvider::getFlashBag($this->flashBagOrRequestStack) - ->add('error', 'sylius_paypal.more_than_one_seller_not_allowed') - ; - - $event->setResponse(new RedirectResponse($this->urlGenerator->generate('sylius_admin_payment_method_index'))); - - return; - } - if ($this->isSandbox || !$this->onboardingInitiator->supports($paymentMethod)) { return; } @@ -69,15 +51,4 @@ private function isNewPaymentMethodPayPal(PaymentMethodInterface $paymentMethod) return $gatewayConfig->getFactoryName() === SyliusPayPalExtension::PAYPAL_FACTORY_NAME; } - - private function isTherePayPalPaymentMethod(): bool - { - try { - $this->payPalPaymentMethodProvider->provide(); - } catch (PayPalPaymentMethodNotFoundException $exception) { - return false; - } - - return true; - } } diff --git a/src/Provider/PayPalPaymentMethodProvider.php b/src/Provider/PayPalPaymentMethodProvider.php index 01a1d301..3ca6f94f 100644 --- a/src/Provider/PayPalPaymentMethodProvider.php +++ b/src/Provider/PayPalPaymentMethodProvider.php @@ -28,7 +28,7 @@ public function __construct(private PaymentMethodRepositoryInterface $paymentMet public function provide(): PaymentMethodInterface { - $paymentMethods = $this->paymentMethodRepository->findAll(); + $paymentMethods = $this->paymentMethodRepository->findBy(['enabled' => true]); /** @var PaymentMethodInterface $paymentMethod */ foreach ($paymentMethods as $paymentMethod) { diff --git a/src/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethod.php b/src/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethod.php new file mode 100644 index 00000000..9e6ddcaf --- /dev/null +++ b/src/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethod.php @@ -0,0 +1,31 @@ + $paymentMethodRepository */ + public function __construct( + private readonly PaymentMethodRepositoryInterface $paymentMethodRepository, + ) { + } + + public function validate(mixed $value, Constraint $constraint): void + { + if (!$constraint instanceof OnlyOneEnabledPayPalPaymentMethod) { + throw new UnexpectedTypeException($constraint, OnlyOneEnabledPayPalPaymentMethod::class); + } + + if (!$value instanceof PaymentMethodInterface) { + throw new UnexpectedValueException($value, PaymentMethodInterface::class); + } + + if (!$this->isPayPalMethod($value) || !$value->isEnabled()) { + return; + } + + $allMethods = $this->paymentMethodRepository->findBy(['enabled' => true]); + foreach ($allMethods as $method) { + if ($method->getId() === $value->getId()) { + continue; + } + if (!$this->isPayPalMethod($method)) { + continue; + } + + $this->context + ->buildViolation($constraint->message) + ->atPath('enabled') + ->addViolation() + ; + + return; + } + } + + private function isPayPalMethod(PaymentMethodInterface $paymentMethod): bool + { + $gatewayConfig = $paymentMethod->getGatewayConfig(); + + return $gatewayConfig?->getFactoryName() === SyliusPayPalExtension::PAYPAL_FACTORY_NAME; + } +} diff --git a/tests/Behat/Context/Admin/ManagingPaymentMethodsContext.php b/tests/Behat/Context/Admin/ManagingPaymentMethodsContext.php index caae1349..bfe86d2f 100644 --- a/tests/Behat/Context/Admin/ManagingPaymentMethodsContext.php +++ b/tests/Behat/Context/Admin/ManagingPaymentMethodsContext.php @@ -15,10 +15,13 @@ use Behat\Behat\Context\Context; use Behat\Mink\Exception\ElementNotFoundException; -use Sylius\Behat\Exception\NotificationExpectationMismatchException; use Sylius\Behat\NotificationType; +use Sylius\Behat\Page\Admin\Crud\IndexPageInterface; use Sylius\Behat\Page\Admin\PaymentMethod\CreatePageInterface; +use Sylius\Behat\Page\Admin\PaymentMethod\UpdatePageInterface; use Sylius\Behat\Service\NotificationCheckerInterface; +use Sylius\Behat\Service\SharedStorageInterface; +use Sylius\Component\Payment\Repository\PaymentMethodRepositoryInterface; use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension; use Tests\Sylius\PayPalPlugin\Behat\Element\DownloadPayPalReportElementInterface; use Webmozart\Assert\Assert; @@ -29,6 +32,10 @@ public function __construct( private DownloadPayPalReportElementInterface $downloadPayPalReportElement, private NotificationCheckerInterface $notificationChecker, private CreatePageInterface $createPage, + private UpdatePageInterface $updatePage, + private IndexPageInterface $indexPage, + private PaymentMethodRepositoryInterface $paymentMethodRepository, + private SharedStorageInterface $sharedStorage, ) { } @@ -49,11 +56,52 @@ public function yesterdayReportCsvFileShouldBeSuccessfullyDownloaded(): void } /** - * @When I try to create a new payment method with "PayPal" gateway factory + * @When I create a new PayPal payment method :name and try to save it as enabled */ - public function iTryToCreateANewPaymentMethodWithGatewayFactory(): void + public function iCreateANewPayPalPaymentMethodAndTryToSaveItAsEnabled(string $name): void { - $this->createPage->tryToOpen(['factory' => SyliusPayPalExtension::PAYPAL_FACTORY_NAME]); + $code = $this->normalizeCode($name); + + $this->createPage->open(['factory' => SyliusPayPalExtension::PAYPAL_FACTORY_NAME]); + $this->createPage->nameIt($name, 'en_US'); + $this->createPage->specifyCode($code); + $this->createPage->create(); + + $this->sharedStorage->set('payment_method_name', $name); + $this->sharedStorage->set('payment_method_code', $code); + } + + /** + * @When I try to enable the PayPal payment method :name + */ + public function iTryToEnableThePayPalPaymentMethod(string $name): void + { + $code = $this->normalizeCode($name); + $paymentMethod = $this->paymentMethodRepository->findOneBy(['code' => $code]); + Assert::notNull($paymentMethod, sprintf('Payment method "%s" not found', $name)); + + $this->updatePage->open(['id' => $paymentMethod->getId()]); + $this->updatePage->enable(); + $this->updatePage->saveChanges(); + + $this->sharedStorage->set('payment_method_name', $name); + $this->sharedStorage->set('payment_method_code', $code); + } + + /** + * @When I create a new PayPal payment method :name and save it as enabled + */ + public function iCreateANewPayPalPaymentMethodAndSaveItAsEnabled(string $name): void + { + $code = $this->normalizeCode($name); + + $this->createPage->open(['factory' => SyliusPayPalExtension::PAYPAL_FACTORY_NAME]); + $this->createPage->nameIt($name, 'en_US'); + $this->createPage->specifyCode($code); + $this->createPage->create(); + + $this->sharedStorage->set('payment_method_name', $name); + $this->sharedStorage->set('payment_method_code', $code); } /** @@ -68,19 +116,79 @@ public function iShouldBeNotifiedThatICannotOnboardMoreThanOnePayPalSeller(): vo } /** - * @Then I should not be notified that I cannot onboard more than one PayPal seller + * @Then I should see a validation error that only one PayPal method can be enabled */ - public function iShouldNotBeNotifiedThatICannotOnboardMoreThanOnePayPalSeller(): void + public function iShouldSeeAValidationErrorThatOnlyOnePayPalMethodCanBeEnabled(): void { try { - $this->notificationChecker->checkNotification( - 'You cannot onboard more than one PayPal seller!', - NotificationType::failure(), - ); - } catch (NotificationExpectationMismatchException|ElementNotFoundException $exception) { - return; + $message = $this->updatePage->getValidationMessage('enabled'); + } catch (ElementNotFoundException) { + $message = $this->createPage->getValidationMessage('enabled'); } - throw new \DomainException('Step should fail'); + Assert::contains( + $message, + 'Only one PayPal payment method can be enabled at a time', + ); + } + + /** + * @Then the PayPal payment method :name should not exist + */ + public function thePayPalPaymentMethodShouldNotExist(string $name): void + { + $this->indexPage->open(); + + Assert::false( + $this->indexPage->isSingleResourceOnPage(['name' => $name]), + sprintf('Payment method "%s" should not exist', $name), + ); + } + + /** + * @Then the PayPal payment method :name should still be disabled + */ + public function thePayPalPaymentMethodShouldStillBeDisabled(string $name): void + { + $paymentMethod = $this->paymentMethodRepository->findOneBy(['code' => $this->normalizeCode($name)]); + Assert::notNull($paymentMethod, sprintf('Payment method "%s" not found', $name)); + + $this->updatePage->open(['id' => $paymentMethod->getId()]); + + Assert::false( + $this->updatePage->isPaymentMethodEnabled(), + sprintf('Payment method "%s" should be disabled', $name), + ); + } + + /** + * @Then the new PayPal payment method should be in the list and enabled + */ + public function theNewPayPalPaymentMethodShouldBeInTheListAndEnabled(): void + { + $name = $this->sharedStorage->get('payment_method_name'); + $code = $this->sharedStorage->get('payment_method_code'); + + $this->indexPage->open(); + + Assert::true( + $this->indexPage->isSingleResourceOnPage(['name' => $name]), + sprintf('Payment method "%s" should exist in the list', $name), + ); + + $paymentMethod = $this->paymentMethodRepository->findOneBy(['code' => $code]); + Assert::notNull($paymentMethod, sprintf('Payment method "%s" not found', $name)); + + $this->updatePage->open(['id' => $paymentMethod->getId()]); + + Assert::true( + $this->updatePage->isPaymentMethodEnabled(), + sprintf('Payment method "%s" should be enabled', $name), + ); + } + + private function normalizeCode(string $name): string + { + return 'PM_' . str_replace(' ', '_', strtoupper($name)); } } diff --git a/tests/Behat/Context/Setup/PaymentPayPalContext.php b/tests/Behat/Context/Setup/PaymentPayPalContext.php index 1b72c584..9b4f2c0a 100644 --- a/tests/Behat/Context/Setup/PaymentPayPalContext.php +++ b/tests/Behat/Context/Setup/PaymentPayPalContext.php @@ -39,9 +39,17 @@ public function __construct( * @Given /^the store allows paying with "([^"]*)" with "([^"]*)" factory name at position (\d+)$/ * @Given /^the store allows paying with "([^"]*)" with "([^"]*)" factory name$/ */ - public function theStoreAllowsPayingWithWithFactoryNameAtPosition(string $paymentMethodName, string $gatewayFactory, ?int $position = 0) + public function theStoreAllowsPayingWithWithFactoryNameAtPosition(string $paymentMethodName, string $gatewayFactory, ?int $position = 0): void { - $this->createPaymentMethod($paymentMethodName, 'PM_' . $paymentMethodName, $gatewayFactory, 'Payment method', $position); + $this->createPaymentMethod($paymentMethodName, 'PM_' . str_replace(' ', '_', strtoupper($paymentMethodName)), $gatewayFactory, 'Payment method', $position, true); + } + + /** + * @Given /^the store has a disabled "([^"]*)" payment method with "([^"]*)" gateway factory$/ + */ + public function theStoreHasADisabledPaymentMethodWithGatewayFactory(string $paymentMethodName, string $gatewayFactory): void + { + $this->createPaymentMethod($paymentMethodName, 'PM_' . str_replace(' ', '_', strtoupper($paymentMethodName)), $gatewayFactory, 'Payment method', 0, false); } /** @@ -58,6 +66,7 @@ private function createPaymentMethod( string $gatewayFactory, string $description, int $position, + bool $enabled = true, ): void { $gatewayFactory = $this->findGatewayNameByTranslation($gatewayFactory, $this->gatewayFactories); @@ -68,7 +77,7 @@ private function createPaymentMethod( 'description' => $description, 'gatewayName' => $gatewayFactory, 'gatewayFactory' => $gatewayFactory, - 'enabled' => true, + 'enabled' => $enabled, 'channels' => ($this->sharedStorage->has('channel')) ? [$this->sharedStorage->get('channel')] : [], ]); diff --git a/tests/Behat/Resources/services.xml b/tests/Behat/Resources/services.xml index 036e71e5..8b0c98ac 100644 --- a/tests/Behat/Resources/services.xml +++ b/tests/Behat/Resources/services.xml @@ -24,6 +24,10 @@ + + + + diff --git a/tests/DependencyInjection/SyliusPayPalExtensionTest.php b/tests/DependencyInjection/SyliusPayPalExtensionTest.php index 29ded92f..e2590075 100644 --- a/tests/DependencyInjection/SyliusPayPalExtensionTest.php +++ b/tests/DependencyInjection/SyliusPayPalExtensionTest.php @@ -85,54 +85,6 @@ public static function sandboxModeParametersProvider(): iterable 'sylius_paypal.facilitator_url', 'https://paypal.sylius.com', ]; - - yield 'production mode sftp host' => [ - false, - 'sylius.pay_pal.reports_sftp_host', - 'reports.paypal.com', - ]; - - yield 'sandbox mode sftp host' => [ - true, - 'sylius.pay_pal.reports_sftp_host', - 'reports.sandbox.paypal.com', - ]; - - yield 'production mode aliased facilitator url' => [ - false, - 'sylius.pay_pal.facilitator_url', - 'https://prod.paypal.sylius.com', - ]; - - yield 'sandbox mode aliased facilitator url' => [ - true, - 'sylius.pay_pal.facilitator_url', - 'https://paypal.sylius.com', - ]; - - yield 'production mode aliased api base url' => [ - false, - 'sylius.pay_pal.api_base_url', - 'https://api.paypal.com/', - ]; - - yield 'sandbox mode aliased api base url' => [ - true, - 'sylius.pay_pal.api_base_url', - 'https://api.sandbox.paypal.com/', - ]; - - yield 'production mode aliased sftp host' => [ - false, - 'sylius.pay_pal.reports_sftp_host', - 'reports.paypal.com', - ]; - - yield 'sandbox mode aliased sftp host' => [ - true, - 'sylius.pay_pal.reports_sftp_host', - 'reports.sandbox.paypal.com', - ]; } /** diff --git a/tests/Unit/Listener/PayPalPaymentMethodListenerTest.php b/tests/Unit/Listener/PayPalPaymentMethodListenerTest.php index fb4e1936..b856a859 100644 --- a/tests/Unit/Listener/PayPalPaymentMethodListenerTest.php +++ b/tests/Unit/Listener/PayPalPaymentMethodListenerTest.php @@ -19,41 +19,23 @@ use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Component\Payment\Model\GatewayConfigInterface; -use Sylius\PayPalPlugin\Exception\PayPalPaymentMethodNotFoundException; use Sylius\PayPalPlugin\Listener\PayPalPaymentMethodListener; use Sylius\PayPalPlugin\Onboarding\Initiator\OnboardingInitiatorInterface; -use Sylius\PayPalPlugin\Provider\PayPalPaymentMethodProviderInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; final class PayPalPaymentMethodListenerTest extends TestCase { private OnboardingInitiatorInterface&MockObject $onboardingInitiator; - private UrlGeneratorInterface&MockObject $urlGenerator; - - private RequestStack&MockObject $requestStack; - - private PayPalPaymentMethodProviderInterface&MockObject $payPalPaymentMethodProvider; - private PayPalPaymentMethodListener $payPalPaymentMethodListener; protected function setUp(): void { parent::setUp(); $this->onboardingInitiator = $this->createMock(OnboardingInitiatorInterface::class); - $this->urlGenerator = $this->createMock(UrlGeneratorInterface::class); - $this->requestStack = $this->createMock(RequestStack::class); - $this->payPalPaymentMethodProvider = $this->createMock(PayPalPaymentMethodProviderInterface::class); $this->payPalPaymentMethodListener = new PayPalPaymentMethodListener( $this->onboardingInitiator, - $this->urlGenerator, - $this->requestStack, - $this->payPalPaymentMethodProvider, ); } @@ -79,11 +61,6 @@ public function it_initiates_onboarding_when_creating_a_supported_payment_method ->method('getFactoryName') ->willReturn('sylius_paypal'); - $this->payPalPaymentMethodProvider - ->expects(self::once()) - ->method('provide') - ->willThrowException(new PayPalPaymentMethodNotFoundException()); - $this->onboardingInitiator ->expects(self::once()) ->method('supports') @@ -122,24 +99,17 @@ public function it_throws_an_exception_if_subject_is_not_a_payment_method(): voi } #[Test] - public function it_redirects_with_error_if_the_paypal_payment_method_already_exists(): void + public function it_does_nothing_when_creating_an_unsupported_payment_method(): void { $event = $this->createMock(ResourceControllerEvent::class); $paymentMethod = $this->createMock(PaymentMethodInterface::class); $gatewayConfig = $this->createMock(GatewayConfigInterface::class); - $session = $this->createMock(SessionInterface::class); - $flashBag = $this->createMock(FlashBagInterface::class); $event ->expects(self::once()) ->method('getSubject') ->willReturn($paymentMethod); - $this->payPalPaymentMethodProvider - ->expects(self::once()) - ->method('provide') - ->willReturn($paymentMethod); - $paymentMethod ->expects(self::once()) ->method('getGatewayConfig') @@ -150,44 +120,21 @@ public function it_redirects_with_error_if_the_paypal_payment_method_already_exi ->method('getFactoryName') ->willReturn('sylius_paypal'); - $flashBag - ->expects(self::once()) - ->method('add') - ->with('error', 'sylius_paypal.more_than_one_seller_not_allowed'); - - $session - ->expects(self::once()) - ->method('getBag') - ->with('flashes') - ->willReturn($flashBag); - - $this->requestStack - ->expects(self::once()) - ->method('getSession') - ->willReturn($session); - - $this->urlGenerator + $this->onboardingInitiator ->expects(self::once()) - ->method('generate') - ->with('sylius_admin_payment_method_index') - ->willReturn('http://redirect-url.com'); + ->method('supports') + ->with($paymentMethod) + ->willReturn(false); $event - ->expects(self::once()) - ->method('setResponse') - ->with($this->callback(function (RedirectResponse $response): bool { - return $response->getTargetUrl() === 'http://redirect-url.com'; - })); - - $this->onboardingInitiator ->expects($this->never()) - ->method('initiate'); + ->method('setResponse'); $this->payPalPaymentMethodListener->initializeCreate($event); } #[Test] - public function it_does_nothing_when_creating_an_unsupported_payment_method(): void + public function it_does_nothing_if_payment_method_is_not_paypal(): void { $event = $this->createMock(ResourceControllerEvent::class); $paymentMethod = $this->createMock(PaymentMethodInterface::class); @@ -206,18 +153,7 @@ public function it_does_nothing_when_creating_an_unsupported_payment_method(): v $gatewayConfig ->expects(self::once()) ->method('getFactoryName') - ->willReturn('sylius_paypal'); - - $this->payPalPaymentMethodProvider - ->expects(self::once()) - ->method('provide') - ->willThrowException(new PayPalPaymentMethodNotFoundException()); - - $this->onboardingInitiator - ->expects(self::once()) - ->method('supports') - ->with($paymentMethod) - ->willReturn(false); + ->willReturn('offline'); $event ->expects($this->never()) @@ -227,8 +163,13 @@ public function it_does_nothing_when_creating_an_unsupported_payment_method(): v } #[Test] - public function it_does_nothing_if_payment_method_is_not_paypal(): void + public function it_does_nothing_when_in_sandbox_mode(): void { + $sandboxListener = new PayPalPaymentMethodListener( + $this->onboardingInitiator, + true, // isSandbox = true + ); + $event = $this->createMock(ResourceControllerEvent::class); $paymentMethod = $this->createMock(PaymentMethodInterface::class); $gatewayConfig = $this->createMock(GatewayConfigInterface::class); @@ -246,12 +187,20 @@ public function it_does_nothing_if_payment_method_is_not_paypal(): void $gatewayConfig ->expects(self::once()) ->method('getFactoryName') - ->willReturn('offline'); + ->willReturn('sylius_paypal'); + + $this->onboardingInitiator + ->expects($this->never()) + ->method('supports'); + + $this->onboardingInitiator + ->expects($this->never()) + ->method('initiate'); $event ->expects($this->never()) ->method('setResponse'); - $this->payPalPaymentMethodListener->initializeCreate($event); + $sandboxListener->initializeCreate($event); } } diff --git a/tests/Unit/Provider/PayPalPaymentMethodProviderTest.php b/tests/Unit/Provider/PayPalPaymentMethodProviderTest.php new file mode 100644 index 00000000..a53c51e2 --- /dev/null +++ b/tests/Unit/Provider/PayPalPaymentMethodProviderTest.php @@ -0,0 +1,185 @@ +&MockObject */ + private PaymentMethodRepositoryInterface&MockObject $paymentMethodRepository; + + private PayPalPaymentMethodProvider $payPalPaymentMethodProvider; + + protected function setUp(): void + { + parent::setUp(); + + $this->paymentMethodRepository = $this->createMock(PaymentMethodRepositoryInterface::class); + $this->payPalPaymentMethodProvider = new PayPalPaymentMethodProvider($this->paymentMethodRepository); + } + + #[Test] + public function it_provides_pay_pal_payment_method(): void + { + $payPalPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $payPalGatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $this->paymentMethodRepository + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$payPalPaymentMethod]) + ; + + $payPalPaymentMethod + ->method('getGatewayConfig') + ->willReturn($payPalGatewayConfig) + ; + + $payPalGatewayConfig + ->method('getFactoryName') + ->willReturn('sylius_paypal') + ; + + $result = $this->payPalPaymentMethodProvider->provide(); + + self::assertSame($payPalPaymentMethod, $result); + } + + #[Test] + public function it_provides_first_pay_pal_payment_method_when_multiple_exist(): void + { + $firstPayPalPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $secondPayPalPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $firstPayPalGatewayConfig = $this->createMock(GatewayConfigInterface::class); + $secondPayPalGatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $this->paymentMethodRepository + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$firstPayPalPaymentMethod, $secondPayPalPaymentMethod]) + ; + + $firstPayPalPaymentMethod + ->method('getGatewayConfig') + ->willReturn($firstPayPalGatewayConfig) + ; + + $firstPayPalGatewayConfig + ->method('getFactoryName') + ->willReturn('sylius_paypal') + ; + + $secondPayPalPaymentMethod + ->method('getGatewayConfig') + ->willReturn($secondPayPalGatewayConfig) + ; + + $secondPayPalGatewayConfig + ->method('getFactoryName') + ->willReturn('sylius_paypal') + ; + + $result = $this->payPalPaymentMethodProvider->provide(); + + self::assertSame($firstPayPalPaymentMethod, $result); + } + + #[Test] + public function it_provides_pay_pal_payment_method_when_other_payment_methods_exist(): void + { + $otherPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $payPalPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $otherGatewayConfig = $this->createMock(GatewayConfigInterface::class); + $payPalGatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $this->paymentMethodRepository + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$otherPaymentMethod, $payPalPaymentMethod]) + ; + + $otherPaymentMethod + ->method('getGatewayConfig') + ->willReturn($otherGatewayConfig) + ; + + $otherGatewayConfig + ->method('getFactoryName') + ->willReturn('other') + ; + + $payPalPaymentMethod + ->method('getGatewayConfig') + ->willReturn($payPalGatewayConfig) + ; + + $payPalGatewayConfig + ->method('getFactoryName') + ->willReturn('sylius_paypal') + ; + + $result = $this->payPalPaymentMethodProvider->provide(); + + self::assertSame($payPalPaymentMethod, $result); + } + + #[Test] + public function it_throws_exception_when_no_enabled_payment_methods_exist(): void + { + $this->paymentMethodRepository + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([]) + ; + + $this->expectException(PayPalPaymentMethodNotFoundException::class); + + $this->payPalPaymentMethodProvider->provide(); + } + + #[Test] + public function it_throws_exception_when_no_pay_pal_payment_method_exists(): void + { + $otherPaymentMethod = $this->createMock(PaymentMethodInterface::class); + $otherGatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $this->paymentMethodRepository + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$otherPaymentMethod]) + ; + + $otherPaymentMethod + ->method('getGatewayConfig') + ->willReturn($otherGatewayConfig) + ; + + $otherGatewayConfig + ->method('getFactoryName') + ->willReturn('other') + ; + + $this->expectException(PayPalPaymentMethodNotFoundException::class); + + $this->payPalPaymentMethodProvider->provide(); + } +} diff --git a/tests/Unit/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethodValidatorTest.php b/tests/Unit/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethodValidatorTest.php new file mode 100644 index 00000000..e7b73732 --- /dev/null +++ b/tests/Unit/Validator/Constraints/OnlyOneEnabledPayPalPaymentMethodValidatorTest.php @@ -0,0 +1,237 @@ +paymentMethodRepository = $this->createMock(PaymentMethodRepositoryInterface::class); + $this->context = $this->createMock(ExecutionContextInterface::class); + + $this->validator = new OnlyOneEnabledPayPalPaymentMethodValidator( + $this->paymentMethodRepository, + ); + $this->validator->initialize($this->context); + } + + #[Test] + public function it_throws_exception_for_wrong_constraint(): void + { + $wrongConstraint = $this->createMock(Constraint::class); + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + + $this->expectException(UnexpectedTypeException::class); + + $this->validator->validate($paymentMethod, $wrongConstraint); + } + + #[Test] + public function it_throws_exception_for_wrong_value(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + + $this->expectException(UnexpectedValueException::class); + + $this->validator->validate(new \stdClass(), $constraint); + } + + #[Test] + public function it_does_nothing_for_non_paypal_methods(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + $gatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $paymentMethod + ->expects(self::once()) + ->method('getGatewayConfig') + ->willReturn($gatewayConfig) + ; + + $gatewayConfig + ->expects(self::once()) + ->method('getFactoryName') + ->willReturn('offline') + ; + + $this->context + ->expects(self::never()) + ->method('buildViolation') + ; + + $this->validator->validate($paymentMethod, $constraint); + } + + #[Test] + public function it_does_nothing_when_paypal_method_is_disabled(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + $gatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $paymentMethod + ->expects(self::once()) + ->method('getGatewayConfig') + ->willReturn($gatewayConfig) + ; + + $gatewayConfig + ->expects(self::once()) + ->method('getFactoryName') + ->willReturn('sylius_paypal') + ; + + $paymentMethod + ->expects(self::once()) + ->method('isEnabled') + ->willReturn(false) + ; + + $this->context + ->expects(self::never()) + ->method('buildViolation') + ; + + $this->validator->validate($paymentMethod, $constraint); + } + + #[Test] + public function it_passes_when_no_other_paypal_method_is_enabled(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + $paymentMethod = $this->createEnabledPayPalPaymentMethod(1); + $offlinePaymentMethod = $this->createOfflinePaymentMethod(2); + + $this->paymentMethodRepository + ->expects(self::once()) + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$paymentMethod, $offlinePaymentMethod]) + ; + + $this->context + ->expects(self::never()) + ->method('buildViolation') + ; + + $this->validator->validate($paymentMethod, $constraint); + } + + #[Test] + public function it_adds_violation_when_another_paypal_method_is_enabled(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + $paymentMethod = $this->createEnabledPayPalPaymentMethod(1); + $otherPayPalMethod = $this->createEnabledPayPalPaymentMethod(2); + + $this->paymentMethodRepository + ->expects(self::once()) + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$paymentMethod, $otherPayPalMethod]) + ; + + $violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class); + + $this->context + ->expects(self::once()) + ->method('buildViolation') + ->with($constraint->message) + ->willReturn($violationBuilder) + ; + + $violationBuilder + ->expects(self::once()) + ->method('atPath') + ->with('enabled') + ->willReturn($violationBuilder) + ; + + $violationBuilder + ->expects(self::once()) + ->method('addViolation') + ; + + $this->validator->validate($paymentMethod, $constraint); + } + + #[Test] + public function it_skips_the_same_payment_method_when_checking(): void + { + $constraint = new OnlyOneEnabledPayPalPaymentMethod(); + $paymentMethod = $this->createEnabledPayPalPaymentMethod(1, true); + + $this->paymentMethodRepository + ->expects(self::once()) + ->method('findBy') + ->with(['enabled' => true]) + ->willReturn([$paymentMethod]) + ; + + $this->context + ->expects(self::never()) + ->method('buildViolation'); + + $this->validator->validate($paymentMethod, $constraint); + } + + private function createEnabledPayPalPaymentMethod(int $id): PaymentMethodInterface&MockObject + { + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + $gatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $paymentMethod->method('getId')->willReturn($id); + $paymentMethod->method('isEnabled')->willReturn(true); + $paymentMethod->method('getGatewayConfig')->willReturn($gatewayConfig); + $gatewayConfig->method('getFactoryName')->willReturn('sylius_paypal'); + + return $paymentMethod; + } + + private function createOfflinePaymentMethod(int $id): PaymentMethodInterface&MockObject + { + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + $gatewayConfig = $this->createMock(GatewayConfigInterface::class); + + $paymentMethod->method('getId')->willReturn($id); + $paymentMethod->method('isEnabled')->willReturn(true); + $paymentMethod->method('getGatewayConfig')->willReturn($gatewayConfig); + $gatewayConfig->method('getFactoryName')->willReturn('offline'); + + return $paymentMethod; + } +} diff --git a/translations/validators.de.yml b/translations/validators.de.yml new file mode 100644 index 00000000..f49c1903 --- /dev/null +++ b/translations/validators.de.yml @@ -0,0 +1,2 @@ +sylius_paypal: + only_one_paypal_enabled: 'Es kann nur eine PayPal-Zahlungsmethode gleichzeitig aktiviert sein. Bitte deaktivieren Sie zuerst die andere PayPal-Methode.' diff --git a/translations/validators.en.yml b/translations/validators.en.yml new file mode 100644 index 00000000..cc1948c6 --- /dev/null +++ b/translations/validators.en.yml @@ -0,0 +1,2 @@ +sylius_paypal: + only_one_paypal_enabled: 'Only one PayPal payment method can be enabled at a time. Please disable the other PayPal method first.' diff --git a/translations/validators.fr.yml b/translations/validators.fr.yml new file mode 100644 index 00000000..8630c8af --- /dev/null +++ b/translations/validators.fr.yml @@ -0,0 +1,2 @@ +sylius_paypal: + only_one_paypal_enabled: "Une seule methode de paiement PayPal peut etre activee a la fois. Veuillez d'abord desactiver l'autre methode PayPal." diff --git a/translations/validators.nl.yml b/translations/validators.nl.yml new file mode 100644 index 00000000..3b564420 --- /dev/null +++ b/translations/validators.nl.yml @@ -0,0 +1,2 @@ +sylius_paypal: + only_one_paypal_enabled: 'Er kan slechts een PayPal-betaalmethode tegelijk actief zijn. Schakel eerst de andere PayPal-methode uit.'