diff --git a/config/app/twig_hooks/admin/payment_method/index.yaml b/config/app/twig_hooks/admin/payment_method/index.yaml index a6aa5908..2d7dabc5 100644 --- a/config/app/twig_hooks/admin/payment_method/index.yaml +++ b/config/app/twig_hooks/admin/payment_method/index.yaml @@ -4,3 +4,6 @@ sylius_twig_hooks: confirmation_modal_create_paypal: template: '@SyliusPayPalPlugin/admin/payment_method/grid/confirmation_modal_create_paypal.html.twig' priority: -100 + confirmation_modal_create_paypal_sandbox: + template: '@SyliusPayPalPlugin/admin/payment_method/grid/confirmation_modal_create_paypal_sandbox.html.twig' + priority: -200 diff --git a/config/services.xml b/config/services.xml index 92a21ed6..cf9d38c9 100644 --- a/config/services.xml +++ b/config/services.xml @@ -50,6 +50,10 @@ + + + + + + + + + + + %sylius_paypal.sandbox% @@ -315,6 +326,23 @@ + + + + + + + + + + diff --git a/config/validation/PayPalSandboxCredentials.xml b/config/validation/PayPalSandboxCredentials.xml new file mode 100644 index 00000000..83f114da --- /dev/null +++ b/config/validation/PayPalSandboxCredentials.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Creator/PayPalSandboxPaymentMethodCreator.php b/src/Creator/PayPalSandboxPaymentMethodCreator.php new file mode 100644 index 00000000..18504a92 --- /dev/null +++ b/src/Creator/PayPalSandboxPaymentMethodCreator.php @@ -0,0 +1,74 @@ +createGatewayConfig($clientId, $clientSecret, $merchantId); + $paymentMethod = $this->createPaymentMethod($gatewayConfig); + + $this->entityManager->persist($paymentMethod); + $this->entityManager->flush(); + + return $paymentMethod; + } + + private function createGatewayConfig(string $clientId, string $clientSecret, string $merchantId): GatewayConfigInterface + { + /** @var GatewayConfigInterface $gatewayConfig */ + $gatewayConfig = $this->gatewayFactory->createNew(); + $gatewayConfig->setFactoryName(SyliusPayPalExtension::PAYPAL_FACTORY_NAME); + $gatewayConfig->setGatewayName(self::GATEWAY_NAME); + + $gatewayConfig->setConfig([ + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'merchant_id' => $merchantId, + 'use_authorize' => 1, + 'sylius_merchant_id' => self::SYLIUS_SANDBOX_MERCHANT_ID, + 'reports_sftp_password' => null, + 'reports_sftp_username' => null, + 'partner_attribution_id' => self::PARTNER_ATTRIBUTION_ID, + ]); + + return $gatewayConfig; + } + + private function createPaymentMethod(GatewayConfigInterface $gatewayConfig): PaymentMethodInterface + { + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = $this->paymentMethodFactory->createNew(); + $paymentMethod->setGatewayConfig($gatewayConfig); + $paymentMethod->setCode(self::PAYMENT_METHOD_CODE); + $paymentMethod->setName(self::PAYMENT_METHOD_NAME); + $paymentMethod->setDescription(self::PAYMENT_METHOD_DESCRIPTION); + + return $paymentMethod; + } +} diff --git a/src/Creator/PayPalSandboxPaymentMethodCreatorInterface.php b/src/Creator/PayPalSandboxPaymentMethodCreatorInterface.php new file mode 100644 index 00000000..b0837315 --- /dev/null +++ b/src/Creator/PayPalSandboxPaymentMethodCreatorInterface.php @@ -0,0 +1,33 @@ +addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { - $form = $event->getForm(); - - if ($this->isSandbox) { - $form - ->add('sylius_merchant_id', HiddenType::class, ['data' => self::SANDBOX_SYLIUS_MERCHANT_ID, 'attr' => ['readonly' => true]]) - ->add('partner_attribution_id', HiddenType::class, ['data' => self::SANDBOX_ATTRIBUTION_ID, 'attr' => ['readonly' => true]]) - ->add('client_id', TextType::class, [ - 'label' => 'sylius_paypal.client_id', - 'constraints' => [new NotBlank(['groups' => 'sylius'])], - ]) - ->add('client_secret', TextType::class, [ - 'label' => 'sylius_paypal.client_secret', - 'constraints' => [new NotBlank(['groups' => 'sylius'])], - ]) - ->add('merchant_id', TextType::class, [ - 'label' => 'sylius_paypal.merchant_id', - 'constraints' => [new NotBlank(['groups' => 'sylius'])], - ]) - ; - } else { - $form - ->add('sylius_merchant_id', HiddenType::class, ['attr' => ['readonly' => true]]) - ->add('partner_attribution_id', HiddenType::class, ['attr' => ['readonly' => true]]) - ->add('client_id', TextType::class, ['label' => 'sylius_paypal.client_id', 'attr' => ['readonly' => true]]) - ->add('client_secret', TextType::class, ['label' => 'sylius_paypal.client_secret', 'attr' => ['readonly' => true]]) - ->add('merchant_id', HiddenType::class, ['attr' => ['readonly' => true]]) - ; - } - - $form - // we need to force Sylius Payum integration to postpone creating an order, it's the easiest way - ->add('use_authorize', HiddenType::class, ['data' => true, 'attr' => ['readonly' => true]]) - ->add('reports_sftp_username', TextType::class, ['label' => 'sylius_paypal.sftp_username', 'required' => false]) - ->add('reports_sftp_password', TextType::class, ['label' => 'sylius_paypal.sftp_password', 'required' => false]) - ; - }); + $builder + ->add('client_id', TextType::class, ['label' => 'sylius_paypal.client_id', 'attr' => ['readonly' => true]]) + ->add('client_secret', TextType::class, ['label' => 'sylius_paypal.client_secret', 'attr' => ['readonly' => true]]) + ->add('merchant_id', HiddenType::class, ['label' => 'sylius_paypal.client_secret', 'attr' => ['readonly' => true]]) + ->add('sylius_merchant_id', HiddenType::class, ['label' => 'sylius_paypal.client_secret', 'attr' => ['readonly' => true]]) + ->add('partner_attribution_id', HiddenType::class, ['label' => 'sylius_paypal.partner_attribution_id', 'attr' => ['readonly' => true]]) + // we need to force Sylius Payum integration to postpone creating an order, it's the easiest way + ->add('use_authorize', HiddenType::class, ['data' => true, 'attr' => ['readonly' => true]]) + ->add('reports_sftp_username', TextType::class, ['label' => 'sylius_paypal.sftp_username', 'required' => false]) + ->add('reports_sftp_password', TextType::class, ['label' => 'sylius_paypal.sftp_password', 'required' => false]) + ; $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use (&$originalData): void { $data = $event->getData(); diff --git a/src/Form/Type/PayPalSandboxCredentialsType.php b/src/Form/Type/PayPalSandboxCredentialsType.php new file mode 100644 index 00000000..829dfb21 --- /dev/null +++ b/src/Form/Type/PayPalSandboxCredentialsType.php @@ -0,0 +1,51 @@ +add('clientId', TextType::class, [ + 'label' => 'sylius_paypal.client_id', + ]) + ->add('clientSecret', TextType::class, [ + 'label' => 'sylius_paypal.client_secret', + ]) + ->add('merchantId', TextType::class, [ + 'label' => 'sylius_paypal.merchant_id', + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => PayPalSandboxCredentials::class, + 'csrf_protection' => true, + 'validation_groups' => 'sylius', + ]); + } + + public function getBlockPrefix(): string + { + return 'sylius_paypal_sandbox_credentials'; + } +} diff --git a/src/Model/PayPalSandboxCredentials.php b/src/Model/PayPalSandboxCredentials.php new file mode 100644 index 00000000..4e2a460b --- /dev/null +++ b/src/Model/PayPalSandboxCredentials.php @@ -0,0 +1,53 @@ +clientId; + } + + public function setClientId(string $clientId): void + { + $this->clientId = $clientId; + } + + public function getClientSecret(): string + { + return $this->clientSecret; + } + + public function setClientSecret(string $clientSecret): void + { + $this->clientSecret = $clientSecret; + } + + public function getMerchantId(): string + { + return $this->merchantId; + } + + public function setMerchantId(string $merchantId): void + { + $this->merchantId = $merchantId; + } +} diff --git a/src/Twig/Component/PayPalSandboxModalComponent.php b/src/Twig/Component/PayPalSandboxModalComponent.php new file mode 100644 index 00000000..c3559dd1 --- /dev/null +++ b/src/Twig/Component/PayPalSandboxModalComponent.php @@ -0,0 +1,91 @@ +submitForm(); + if (!$this->form->isSubmitted() && !$this->form->isValid()) { + return null; + } + + $credentials = $this->form->getData(); + + $clientId = $credentials->getClientId(); + $clientSecret = $credentials->getClientSecret(); + $merchantId = $credentials->getMerchantId(); + + try { + $paymentMethod = $this->sandboxPaymentMethodCreator->create($clientId, $clientSecret, $merchantId); + } catch (\Throwable $exception) { + $this->logger->error($exception->getMessage()); + + FlashBagProvider::getFlashBag($this->flashBagOrRequestStack)->add('error', 'sylius_paypal.could_not_create_paypal_payment_method'); + + return new RedirectResponse( + $this->router->generate(self::INDEX_ROUTE), + ); + } + + return new RedirectResponse( + $this->router->generate( + self::UPDATE_ROUTE, + ['id' => $paymentMethod->getId()], + ), + ); + } + + protected function instantiateForm(): FormInterface + { + return $this->formFactory->create(PayPalSandboxCredentialsType::class); + } +} diff --git a/src/Twig/PayPalExtension.php b/src/Twig/PayPalExtension.php index cfa39e0c..a9e23952 100644 --- a/src/Twig/PayPalExtension.php +++ b/src/Twig/PayPalExtension.php @@ -21,13 +21,23 @@ final class PayPalExtension extends AbstractExtension { + public function __construct(private readonly bool $sandbox) + { + } + public function getFunctions(): array { return [ new TwigFunction('sylius_is_paypal_enabled', [$this, 'isPayPalEnabled']), + new TwigFunction('sylius_is_paypal_sandbox', [$this, 'isSandbox']), ]; } + public function isSandbox(): bool + { + return $this->sandbox; + } + public function isPayPalEnabled(iterable $paymentMethods): bool { /** @var PaymentMethodInterface $paymentMethod */ diff --git a/templates/admin/payment_method/grid/action/enable_paypal.html.twig b/templates/admin/payment_method/grid/action/enable_paypal.html.twig index 0f7e2d40..0e202134 100644 --- a/templates/admin/payment_method/grid/action/enable_paypal.html.twig +++ b/templates/admin/payment_method/grid/action/enable_paypal.html.twig @@ -1,6 +1,25 @@ {% if not sylius_is_paypal_enabled(grid.data.currentPageResults) %} - +
+ + +
{% endif %} diff --git a/templates/admin/payment_method/grid/confirmation_modal_create_paypal_sandbox.html.twig b/templates/admin/payment_method/grid/confirmation_modal_create_paypal_sandbox.html.twig new file mode 100644 index 00000000..732eb5f4 --- /dev/null +++ b/templates/admin/payment_method/grid/confirmation_modal_create_paypal_sandbox.html.twig @@ -0,0 +1,5 @@ +{% component 'sylius_paypal:create_sandbox_modal' with { + modal_id: 'confirmation-modal-create-paypal-sandbox', + type: 'sylius-create-paypal-sandbox' +} %} +{% endcomponent %} diff --git a/templates/admin/shared/components/paypal_sandbox_modal.html.twig b/templates/admin/shared/components/paypal_sandbox_modal.html.twig new file mode 100644 index 00000000..fde8ddd1 --- /dev/null +++ b/templates/admin/shared/components/paypal_sandbox_modal.html.twig @@ -0,0 +1,49 @@ +{% import '@SyliusAdmin/shared/helper/button.html.twig' as button %} +{% props modal_id = null, type = null%} + +
+ +
diff --git a/tests/Unit/Creator/PayPalSandboxPaymentMethodCreatorTest.php b/tests/Unit/Creator/PayPalSandboxPaymentMethodCreatorTest.php new file mode 100644 index 00000000..1ae74cde --- /dev/null +++ b/tests/Unit/Creator/PayPalSandboxPaymentMethodCreatorTest.php @@ -0,0 +1,100 @@ +createMock(FactoryInterface::class); + /** @var FactoryInterface&MockObject $paymentMethodFactory */ + $paymentMethodFactory = $this->createMock(FactoryInterface::class); + /** @var EntityManagerInterface&MockObject $entityManager */ + $entityManager = $this->createMock(EntityManagerInterface::class); + + /** @var GatewayConfigInterface&MockObject $gatewayConfig */ + $gatewayConfig = $this->createMock(GatewayConfigInterface::class); + /** @var PaymentMethodInterface&MockObject $paymentMethod */ + $paymentMethod = $this->createMock(PaymentMethodInterface::class); + + $gatewayFactory->expects(self::once()) + ->method('createNew') + ->willReturn($gatewayConfig); + + $gatewayConfig->expects(self::once()) + ->method('setFactoryName') + ->with('sylius_paypal'); + $gatewayConfig->expects(self::once()) + ->method('setGatewayName') + ->with('sylius_paypal_sandbox'); + $gatewayConfig->expects(self::once()) + ->method('setConfig') + ->with(self::callback(function ($config) use ($clientId, $clientSecret, $merchantId) { + return $config['client_id'] === $clientId && + $config['client_secret'] === $clientSecret && + $config['merchant_id'] === $merchantId && + $config['use_authorize'] === 1 && + isset($config['sylius_merchant_id']) && + array_key_exists('reports_sftp_password', $config) && + array_key_exists('reports_sftp_username', $config) && + isset($config['partner_attribution_id']); + })); + + $paymentMethodFactory->expects(self::once()) + ->method('createNew') + ->willReturn($paymentMethod); + + $paymentMethod->expects(self::once()) + ->method('setGatewayConfig') + ->with($gatewayConfig); + $paymentMethod->expects(self::once()) + ->method('setCode') + ->with('PAYPAL'); + $paymentMethod->expects(self::once()) + ->method('setName') + ->with('PayPal'); + $paymentMethod->expects(self::once()) + ->method('setDescription') + ->with('Pay with PayPal'); + + $entityManager->expects(self::once()) + ->method('persist') + ->with($paymentMethod); + $entityManager->expects(self::once()) + ->method('flush'); + + $creator = new PayPalSandboxPaymentMethodCreator( + $gatewayFactory, + $paymentMethodFactory, + $entityManager, + ); + + $result = $creator->create($clientId, $clientSecret, $merchantId); + + self::assertSame($paymentMethod, $result); + } +} diff --git a/translations/flashes.en.yml b/translations/flashes.en.yml index e62a85e1..75cd3397 100644 --- a/translations/flashes.en.yml +++ b/translations/flashes.en.yml @@ -1,4 +1,6 @@ sylius_paypal: + could_not_create_paypal_payment_method: 'Could not create PayPal payment method. Please, try again later.' + invalid_paypal_sandbox_credentials: 'Invalid PayPal sandbox credentials. Please, check your configuration.' more_than_one_seller_not_allowed: 'You cannot onboard more than one PayPal seller!' order_cancelled: 'PayPal order has been cancelled' payment_cancelled: 'PayPal payment has been cancelled' diff --git a/translations/flashes.fr.yml b/translations/flashes.fr.yml index 7ac59814..a373ccc7 100644 --- a/translations/flashes.fr.yml +++ b/translations/flashes.fr.yml @@ -1,4 +1,6 @@ sylius_paypal: + could_not_create_paypal_payment_method: 'Impossible de créer le mode de paiement PayPal. Merci de réessayer plus tard.' + invalid_paypal_sandbox_credentials: 'Identifiants PayPal Sandbox invalides. Merci de vérifier votre configuration.' more_than_one_seller_not_allowed: 'Vous ne pouvez ajouter plus d''un vendeur PayPal !' order_cancelled: 'La commande PayPal a été annulée' payment_cancelled: 'Le paiement PayPal a été annulé' diff --git a/translations/flashes.nl.yml b/translations/flashes.nl.yml index 935b1970..0f251a05 100644 --- a/translations/flashes.nl.yml +++ b/translations/flashes.nl.yml @@ -1,4 +1,6 @@ sylius_paypal: + could_not_create_paypal_payment_method: 'PayPal betaalmethode kon niet worden aangemaakt. Probeer het later opnieuw.' + invalid_paypal_sandbox_credentials: 'Ongeldige PayPal Sandbox referenties. Controleer uw configuratie.' more_than_one_seller_not_allowed: 'U kunt niet meer dan één PayPal verkoper instellen!' order_cancelled: 'PayPal bestelling is geannuleerd' payment_cancelled: 'PayPal betaling is geannuleerd' diff --git a/translations/messages.en.yml b/translations/messages.en.yml index 113e393a..1591f140 100644 --- a/translations/messages.en.yml +++ b/translations/messages.en.yml @@ -8,6 +8,8 @@ sylius_paypal: cvv: 'CVV' different_amount: 'This is the authorized price. As you have modified the order after authorization, you will pay the amount displayed as Total' enable_paypal: 'Enable PayPal' + enable_paypal_sandbox: 'Sandbox' + enable_paypal_production: 'Production' expiration_date: 'Expiration date' invalid_form: 'Form is invalid' label: 'PayPal' diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml index ffd3bb22..4906e297 100644 --- a/translations/messages.fr.yml +++ b/translations/messages.fr.yml @@ -8,9 +8,12 @@ sylius_paypal: cvv: 'CVV' different_amount: 'Ceci est le montant autorisé. Etant donné que vous avez modifié la commande après son autorisation, vous paierez le montant indiqué comme "Total"' enable_paypal: 'Activer PayPal' + enable_paypal_sandbox: 'Sandbox' + enable_paypal_production: 'Production' expiration_date: 'Date d''expiration' invalid_form: 'Le formulaire est invalide' label: 'PayPal' + merchant_id: 'ID marchand' missing_billing_address_content: 'Merci de renseignez votre adresse' missing_billing_address_header: 'Impossible de récupérer une adresse de facturation depuis PayPal' pay_with_card: 'Payer par carte' diff --git a/translations/messages.nl.yml b/translations/messages.nl.yml index 087283dd..44fd6173 100644 --- a/translations/messages.nl.yml +++ b/translations/messages.nl.yml @@ -8,9 +8,12 @@ sylius_paypal: cvv: 'CVV' different_amount: 'Dit is de geautoriseerde prijs. Aangezien u de bestelling na autorisatie heeft gewijzigd, betaalt u het bedrag dat wordt weergegeven als Totaal' enable_paypal: 'PayPal inschakelen' + enable_paypal_sandbox: 'Sandbox' + enable_paypal_production: 'Productie' expiration_date: 'Vervaldatum' invalid_form: 'Formulier is ongeldig' label: 'PayPal' + merchant_id: 'Merchant ID' missing_billing_address_content: 'Ga alstublief terug naar de "adressering" en vul deze aan.' missing_billing_address_header: 'We konden geen facturatie adres ophalen van PayPal' pay_with_card: 'Betalen met kaart'