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%}
+
+
+
+
+ {% block content %}
+
+
+
+ {% set form = this.formView %}
+ {% form_theme form '@SyliusAdmin/shared/form_theme.html.twig' %}
+
+ {{ form_errors(form) }}
+ {{ form_start(form, {
+ attr: {
+ 'id': 'paypal-sandbox-form',
+ 'data-action': 'live#action:prevent',
+ 'data-live-action-param': 'submit',
+ 'class': 'w-100'
+ },
+ }) }}
+
+ {{ form_row(form.clientId) }}
+ {{ form_row(form.clientSecret) }}
+ {{ form_row(form.merchantId) }}
+ {{ form_end(form) }}
+
+
+
+ {% endblock %}
+
+
+
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'