From 5961189f8abcbead463eef97cb9c323c6062c6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 09:25:30 +0200 Subject: [PATCH 01/18] PPSYL-163 - Replace secretKey by live checkbox --- config/twig_hooks/admin.yaml | 11 ++++++++++- .../Form/Type/AbstractGatewayConfigurationType.php | 13 +++++++++++-- .../payment_method/form/live_checkbox.html.twig | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 templates/admin/payment_method/form/live_checkbox.html.twig diff --git a/config/twig_hooks/admin.yaml b/config/twig_hooks/admin.yaml index f73c2bb8..082c77aa 100644 --- a/config/twig_hooks/admin.yaml +++ b/config/twig_hooks/admin.yaml @@ -4,6 +4,10 @@ sylius_twig_hooks: secret_key: &secretKey template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/secret_key.html.twig' priority: 0 + enabled: false + live_checkbox: &liveCheckbox + template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/live_checkbox.html.twig' + priority: 0 one_click: template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/one_click.html.twig' priority: 0 @@ -15,18 +19,23 @@ sylius_twig_hooks: priority: 0 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_oney': &oneyGateway secret_key: *secretKey + live_checkbox: *liveCheckbox fees_for: template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/fees_for.html.twig' priority: 0 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_bancontact': &bancontactGateway secret_key: *secretKey + live_checkbox: *liveCheckbox 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_apple_pay': &applePayGateway secret_key: *secretKey + live_checkbox: *liveCheckbox + 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_american_express': &amexGateway secret_key: *secretKey + live_checkbox: *liveCheckbox 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': *payplugGateway 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_oney': *oneyGateway 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_bancontact': *bancontactGateway 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_apple_pay': *applePayGateway - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express': *amexGateway \ No newline at end of file + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express': *amexGateway diff --git a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php index 1f8418e9..98729c7a 100644 --- a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php @@ -13,7 +13,9 @@ use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; @@ -47,7 +49,14 @@ public function __construct( public function buildForm(FormBuilderInterface $builder, array $options): void { $builder - ->add('secretKey', PasswordType::class, [ + ->add('live', CheckboxType::class, [ + 'block_name' => 'payplug_checkbox', + 'label' => 'payplug_sylius_payplug_plugin.ui.live', + 'help' => 'payplug_sylius_payplug_plugin.ui.live_help', + 'help_html' => true, + 'required' => false, + ]) + ->add('secretKey', TextType::class, [ 'label' => 'payplug_sylius_payplug_plugin.ui.secret_key', 'validation_groups' => self::VALIDATION_GROUPS, 'constraints' => [ diff --git a/templates/admin/payment_method/form/live_checkbox.html.twig b/templates/admin/payment_method/form/live_checkbox.html.twig new file mode 100644 index 00000000..82c955e9 --- /dev/null +++ b/templates/admin/payment_method/form/live_checkbox.html.twig @@ -0,0 +1,5 @@ +{% set form = hookable_metadata.context.form.gatewayConfig.config.live %} + +
+ {{ form_row(form) }} +
From 70839bb016db3f3f22cfe77acac4b11e01383185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 09:34:15 +0200 Subject: [PATCH 02/18] PPSYL-163 - Remove secretKey from form --- config/twig_hooks/admin.yaml | 8 -------- .../Type/AbstractGatewayConfigurationType.php | 16 ++-------------- .../payment_method/form/secret_key.html.twig | 5 ----- translations/messages.en.yml | 2 -- translations/messages.fr.yml | 2 -- translations/messages.it.yml | 2 -- 6 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 templates/admin/payment_method/form/secret_key.html.twig diff --git a/config/twig_hooks/admin.yaml b/config/twig_hooks/admin.yaml index 082c77aa..2480a8fd 100644 --- a/config/twig_hooks/admin.yaml +++ b/config/twig_hooks/admin.yaml @@ -1,10 +1,6 @@ sylius_twig_hooks: hooks: 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug': &payplugGateway - secret_key: &secretKey - template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/secret_key.html.twig' - priority: 0 - enabled: false live_checkbox: &liveCheckbox template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/live_checkbox.html.twig' priority: 0 @@ -18,20 +14,16 @@ sylius_twig_hooks: template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/deferred_capture.html.twig' priority: 0 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_oney': &oneyGateway - secret_key: *secretKey live_checkbox: *liveCheckbox fees_for: template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/fees_for.html.twig' priority: 0 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_bancontact': &bancontactGateway - secret_key: *secretKey live_checkbox: *liveCheckbox 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_apple_pay': &applePayGateway - secret_key: *secretKey live_checkbox: *liveCheckbox 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_american_express': &amexGateway - secret_key: *secretKey live_checkbox: *liveCheckbox 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': *payplugGateway diff --git a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php index 98729c7a..481073de 100644 --- a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php @@ -14,15 +14,12 @@ use Sylius\Component\Resource\Repository\RepositoryInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Contracts\Translation\TranslatorInterface; class AbstractGatewayConfigurationType extends AbstractType @@ -53,16 +50,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.ui.live', 'help' => 'payplug_sylius_payplug_plugin.ui.live_help', - 'help_html' => true, - 'required' => false, - ]) - ->add('secretKey', TextType::class, [ - 'label' => 'payplug_sylius_payplug_plugin.ui.secret_key', - 'validation_groups' => self::VALIDATION_GROUPS, - 'constraints' => [ - new NotBlank([ - 'message' => 'payplug_sylius_payplug_plugin.secret_key.not_blank', - ]), + 'constraints' => [ // TODO: handle these check later new IsPayPlugSecretKeyValid(), new IsCanSavePaymentMethod([ 'noTestKeyMessage' => $this->noTestKeyMessage, @@ -70,8 +58,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]), new IsOneyEnabled(), ], - 'help' => $this->translator->trans('payplug_sylius_payplug_plugin.ui.retrieve_secret_key_in_api_configuration_portal'), 'help_html' => true, + 'required' => false, ]) ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void { $this->checkCreationRequirements( diff --git a/templates/admin/payment_method/form/secret_key.html.twig b/templates/admin/payment_method/form/secret_key.html.twig deleted file mode 100644 index 2bf05d7d..00000000 --- a/templates/admin/payment_method/form/secret_key.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% set form = hookable_metadata.context.form.gatewayConfig.config.secretKey %} - -
- {{ form_row(form) }} -
\ No newline at end of file diff --git a/translations/messages.en.yml b/translations/messages.en.yml index 79820a27..28c713ee 100644 --- a/translations/messages.en.yml +++ b/translations/messages.en.yml @@ -29,10 +29,8 @@ payplug_sylius_payplug_plugin: error: billing.postcode: The postal code %postalCode% entered for the billing address is invalid shipping.postcode: The postal code %postalCode% entered for the delivery address is invalid - secret_key: Secret key payplug_gateway_label: Payplug payment_refund_locally: The payment refund was made locally only. - retrieve_secret_key_in_api_configuration_portal: Find your LIVE and TEST Secret Keys in your Payplug portal test_mode: TEST Mode payplug_id: | Payplug ID: %paymentId% diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml index 90bd5b09..128aff85 100644 --- a/translations/messages.fr.yml +++ b/translations/messages.fr.yml @@ -29,10 +29,8 @@ payplug_sylius_payplug_plugin: error: billing.postcode: Le code postal %postalCode% renseigné pour l'adresse de facturation est invalide shipping.postcode: Le code postal %postalCode% renseigné pour l'adresse de livraison est invalide - secret_key: Clé secrète payplug_gateway_label: Payplug payment_refund_locally: Le remboursement du paiement a été effectué uniquement localement. - retrieve_secret_key_in_api_configuration_portal: Retrouvez vos Secret Key LIVE et TEST dans votre portail Payplug test_mode: Mode TEST payplug_id: | ID Payplug : %paymentId% diff --git a/translations/messages.it.yml b/translations/messages.it.yml index 7e4b3ee6..9c50acf9 100644 --- a/translations/messages.it.yml +++ b/translations/messages.it.yml @@ -29,10 +29,8 @@ payplug_sylius_payplug_plugin: error: billing.postcode: Il codice postale %postalCode% inserito per l’indirizzo di fatturazione non è valido shipping.postcode: Il codice postale %postalCode% inserito per l’indirizzo di consegna non è valido - secret_key: ID segreto payplug_gateway_label: Payplug payment_refund_locally: Il rimborso del pagamento è stato effettuato solo a livello locale (interno). - retrieve_secret_key_in_api_configuration_portal: Trova il tuo ID segreto LIVE e TEST nel tuo portale Payplug test_mode: Modalità TEST payplug_id: | ID Payplug : %paymentId% From 5222bda753e57f4ac55d6ed31d30634ac47e27a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 12:04:38 +0200 Subject: [PATCH 03/18] PPSYL-163 - Handle Oauth redirection and creation of clients --- .../Auth/UnifiedAuthenticationController.php | 103 ++++++++++++++++++ .../PostSavePaymentMethodEventListener.php | 49 +++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/Action/Admin/Auth/UnifiedAuthenticationController.php create mode 100644 src/EventListener/PostSavePaymentMethodEventListener.php diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php new file mode 100644 index 00000000..ea73f742 --- /dev/null +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -0,0 +1,103 @@ + $paymentMethodRepository + */ + public function __construct( + private RouterInterface $router, + private RepositoryInterface $paymentMethodRepository, + private EntityManagerInterface $entityManager, + ) { + } + + #[Route('/setup-redirection', name: 'payplug_sylius_admin_auth_setup_redirection')] + public function setupRedirection(Request $request): void + { + $clientId = $request->query->get('client_id'); + $companyId = $request->query->get('company_id'); + + $request->getSession()->set('payplug_client_id', $clientId); + $request->getSession()->set('payplug_company_id', $companyId); + + $challenge = bin2hex(openssl_random_pseudo_bytes(50)); + $request->getSession()->set('payplug_oauth_challenge', $challenge); + + $callBackUrl = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', [], RouterInterface::ABSOLUTE_URL); + + // This method will redirect the user to PayPlug's oauth page via header('Location')' + Authentication::initiateOAuth($clientId, $callBackUrl, $challenge); + exit; + } + + #[Route('/oauth-callback', name: 'payplug_sylius_admin_auth_oauth_callback')] + public function oauthCallback(Request $request): Response + { + $code = $request->query->get('code'); + $clientId = $request->getSession()->get('payplug_client_id'); + $challenge = $request->getSession()->get('payplug_oauth_challenge'); + $callback = $this->generateUrl('payplug_sylius_admin_auth_oauth_callback', [], UrlGeneratorInterface::ABSOLUTE_URL); + + $jwt = Authentication::generateJWTOneShot($code, $callback, $clientId, $challenge); + if ([] === $jwt || $jwt['httpStatus'] !== 200) { + throw new BadRequestHttpException('Error while generating JWT'); + } + $paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id'); + if (null === $paymentMethodId) { + throw new BadRequestHttpException('No payment method id found in session'); + } + $paymentMethod = $this->paymentMethodRepository->find($paymentMethodId); + if (null === $paymentMethod) { + throw new \LogicException('No payment method found'); + } + $gatewayConfig = $paymentMethod->getGatewayConfig(); + if (null === $gatewayConfig) { + throw new \LogicException('No gateway config found'); + } + + $companyId = $request->getSession()->get('payplug_company_id'); + Payplug::init(['secretKey' => $jwt['httpResponse']['access_token']]); + $clientName = 'Sylius - ' . $paymentMethod->getName(); + $testClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'test'); + $liveClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'live'); + + $config = $gatewayConfig->getConfig(); + $config['live_client'] = $liveClientDataResult['httpResponse']; + $config['test_client'] = $testClientDataResult['httpResponse']; + $gatewayConfig->setConfig($config); + + $this->entityManager->flush(); + $this->cleanSession($request); + + $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.ui.admin.auth.oauth_callback.success'); + return new RedirectResponse($this->router->generate('sylius_admin_payment_method_index'));; + } + + private function cleanSession(Request $request): void + { + $session = $request->getSession(); + $session->remove('payplug_client_id'); + $session->remove('payplug_company_id'); + $session->remove('payplug_oauth_challenge'); + $session->remove('payplug_sylius_oauth_payment_method_id'); + } +} diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php new file mode 100644 index 00000000..98a10422 --- /dev/null +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -0,0 +1,49 @@ +getSubject(); + if (!$paymentMethod instanceof PaymentMethodInterface) { + return; + } + + // TODO: check if the paymentMethod is one belong to payplug + + $request = $this->requestStack->getCurrentRequest(); + if (null === $request) { + // Should never happen + return; + } + + $request->getSession()->set('payplug_sylius_oauth_payment_method_id', $paymentMethod->getId()); + $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); + $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); + + /** @var string $payplugRedirectUrl */ + $payplugRedirectUrl = Authentication::getRegisterUrl($setupRedirection, $oauthCallback); + + $event->setResponse(new RedirectResponse($payplugRedirectUrl)); + } +} From 147b3e2aae3df358173002cdc181f82cd1994114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 15:14:53 +0200 Subject: [PATCH 04/18] PPSYL-163 - Retrieve access_token and use it in PayplugApiClient --- src/ApiClient/PayPlugApiClientFactory.php | 42 +++++++++++++++---- .../PayPlugApiClientFactoryInterface.php | 2 +- src/Controller/IpnAction.php | 3 +- src/Controller/OneClickAction.php | 6 ++- .../RefundPaymentProcessor.php | 6 +-- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/ApiClient/PayPlugApiClientFactory.php b/src/ApiClient/PayPlugApiClientFactory.php index b25872a5..19a14b37 100644 --- a/src/ApiClient/PayPlugApiClientFactory.php +++ b/src/ApiClient/PayPlugApiClientFactory.php @@ -4,9 +4,13 @@ namespace PayPlug\SyliusPayPlugPlugin\ApiClient; +use Payplug\Authentication; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Component\Payment\Model\PaymentMethodInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; +use Symfony\Config\SyliusPayment\GatewayConfigConfig; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; final class PayPlugApiClientFactory implements PayPlugApiClientFactoryInterface { @@ -16,18 +20,17 @@ public function __construct( ) { } - public function create(string $factoryName, ?string $key = null): PayPlugApiClientInterface + public function create(string $factoryName): PayPlugApiClientInterface { - if (null === $key) { - /** @var \Payum\Core\Model\GatewayConfig|null $gatewayConfig */ - $gatewayConfig = $this->gatewayConfigRepository->findOneBy(['factoryName' => $factoryName]); + /** @var GatewayConfigInterface|null $gatewayConfig */ + $gatewayConfig = $this->gatewayConfigRepository->findOneBy(['factoryName' => $factoryName]); - if (null === $gatewayConfig) { - throw new \LogicException('Not yet gateway created for ' . $factoryName); - } - $key = $gatewayConfig->getConfig()['secretKey']; + if (null === $gatewayConfig) { + throw new \LogicException('Not yet gateway created for ' . $factoryName); } + $key = $this->getTokenForGatewayConfig($gatewayConfig); + return new PayPlugApiClient($key, $factoryName, $this->cache); } @@ -35,9 +38,30 @@ public function createForPaymentMethod(PaymentMethodInterface $paymentMethod): P { $gatewayConfig = $paymentMethod->getGatewayConfig() ?? throw new \LogicException('Gateway config not found'); - $key = $gatewayConfig->getConfig()['secretKey']; + $key = $this->getTokenForGatewayConfig($gatewayConfig); $factoryName = $gatewayConfig->getFactoryName(); return new PayPlugApiClient($key, $factoryName, $this->cache); } + + private function getTokenForGatewayConfig(GatewayConfigInterface $gatewayConfig): string + { + $config = $gatewayConfig->getConfig(); + $clientConfig = $config['live_client']; + if (true !== $config['live']) { // The live mode is not enabled, use client config for test mode + $clientConfig = $config['test_client']; + } + $cacheKey = sprintf('payplug_%s_api_key_%s', $gatewayConfig->getFactoryName(), $config['live'] === true ? 'live' : 'test'); + + return $this->cache->get($cacheKey, function (ItemInterface $item) use ($clientConfig) { + $response = Authentication::generateJWT($clientConfig['client_id'], $clientConfig['client_secret']); + if ([] === $response) { + throw new \LogicException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); + } + + $item->expiresAfter($response['httpResponse']['expires_in']); + /** @var string */ + return $response['httpResponse']['access_token']; + }); + } } diff --git a/src/ApiClient/PayPlugApiClientFactoryInterface.php b/src/ApiClient/PayPlugApiClientFactoryInterface.php index 6031219c..12b96f2a 100644 --- a/src/ApiClient/PayPlugApiClientFactoryInterface.php +++ b/src/ApiClient/PayPlugApiClientFactoryInterface.php @@ -8,7 +8,7 @@ interface PayPlugApiClientFactoryInterface { - public function create(string $factoryName, ?string $key = null): PayPlugApiClientInterface; + public function create(string $factoryName): PayPlugApiClientInterface; public function createForPaymentMethod(PaymentMethodInterface $paymentMethod): PayPlugApiClientInterface; } diff --git a/src/Controller/IpnAction.php b/src/Controller/IpnAction.php index 25d67368..c4f86f6b 100644 --- a/src/Controller/IpnAction.php +++ b/src/Controller/IpnAction.php @@ -80,8 +80,7 @@ public function __invoke(Request $request): JsonResponse return new JsonResponse(null, Response::HTTP_UNAUTHORIZED); } - $this->payPlugApiClient = $this->apiClientFactory->create($factoryName, $gatewayConfig['secretKey']); - $this->payPlugApiClient->initialise($gatewayConfig['secretKey']); + $this->payPlugApiClient = $this->apiClientFactory->create($factoryName); try { $resource = $this->payPlugApiClient->treat($input); diff --git a/src/Controller/OneClickAction.php b/src/Controller/OneClickAction.php index 307a0527..6ce2764a 100644 --- a/src/Controller/OneClickAction.php +++ b/src/Controller/OneClickAction.php @@ -22,6 +22,9 @@ use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\Routing\Attribute\Route; +/** + * TODO : !!! Check if this controller is still needed + */ #[AsController] class OneClickAction extends AbstractController implements GatewayAwareInterface, ApiAwareInterface { @@ -56,8 +59,7 @@ public function __invoke(Request $request): Response [], ); - $secretKey = $paymentGateway->getConfig()['secretKey']; - $this->payPlugApiClient = $this->payPlugApiClientFactory->create(PayPlugGatewayFactory::FACTORY_NAME, $secretKey); + $this->payPlugApiClient = $this->payPlugApiClientFactory->createForPaymentMethod($paymentMethod); $resource = $this->payPlugApiClient->retrieve((string) $payment->getDetails()['payment_id']); //if is_paid is true, you can consider the payment as being fully paid, diff --git a/src/PaymentProcessing/RefundPaymentProcessor.php b/src/PaymentProcessing/RefundPaymentProcessor.php index 3525e7fb..f544efde 100644 --- a/src/PaymentProcessing/RefundPaymentProcessor.php +++ b/src/PaymentProcessing/RefundPaymentProcessor.php @@ -138,10 +138,6 @@ private function prepare(PaymentInterface $payment): void $this->logger->info('[PayPlug] Start refund payment', ['payment_id' => $details['payment_id']]); - $gatewayConfig = $paymentMethod->getGatewayConfig()->getConfig(); - - $this->payPlugApiClient = $this->apiClientFactory->create($factoryName, $gatewayConfig['secretKey']); - - $this->payPlugApiClient->initialise($gatewayConfig['secretKey']); + $this->payPlugApiClient = $this->apiClientFactory->createForPaymentMethod($paymentMethod); } } From 1b347d2a4e03125a39628f7c3204e40269f560ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 15:33:50 +0200 Subject: [PATCH 05/18] PPSYL-163 - Remove usage of initialize method from PayplugApiClient --- src/ApiClient/PayPlugApiClient.php | 13 ------------- src/ApiClient/PayPlugApiClientInterface.php | 5 ----- src/Resolver/PaymentStateResolver.php | 9 ++++----- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/ApiClient/PayPlugApiClient.php b/src/ApiClient/PayPlugApiClient.php index ebc50236..8da541f6 100644 --- a/src/ApiClient/PayPlugApiClient.php +++ b/src/ApiClient/PayPlugApiClient.php @@ -53,19 +53,6 @@ public function __construct(string $secretKey, ?string $factoryName = null, ?Cac ); } - /** - * @deprecated use DI instead to get a pre-configured client - */ - public function initialise(string $secretKey): void - { - Payplug::setSecretKey($secretKey); - HttpClient::addDefaultUserAgentProduct( - 'PayPlug-Sylius', - PayPlugSyliusPayPlugPlugin::VERSION, - 'Sylius/' . SyliusCoreBundle::VERSION, - ); - } - public function getAccount(bool $refresh = false): array { $cacheKey = 'payplug_account_' . substr($this->configuration->getToken(), 8); diff --git a/src/ApiClient/PayPlugApiClientInterface.php b/src/ApiClient/PayPlugApiClientInterface.php index aff3041e..20699a25 100644 --- a/src/ApiClient/PayPlugApiClientInterface.php +++ b/src/ApiClient/PayPlugApiClientInterface.php @@ -35,11 +35,6 @@ interface PayPlugApiClientInterface public const REFUNDED = 'refunded'; - /** - * @deprecated - */ - public function initialise(string $secretKey): void; - public function getConfiguration(): Payplug; public function getAccount(bool $refresh = false): array; diff --git a/src/Resolver/PaymentStateResolver.php b/src/Resolver/PaymentStateResolver.php index 53e21586..bf8ee0ef 100644 --- a/src/Resolver/PaymentStateResolver.php +++ b/src/Resolver/PaymentStateResolver.php @@ -7,6 +7,7 @@ use Doctrine\ORM\EntityManagerInterface; use Payplug\Resource\Payment; use Payplug\Resource\PaymentAuthorization; +use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientFactory; use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientInterface; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use Payum\Core\Model\GatewayConfigInterface; @@ -20,8 +21,7 @@ final class PaymentStateResolver implements PaymentStateResolverInterface { public function __construct( private StateMachineInterface $stateMachine, - #[Autowire('@payplug_sylius_payplug_plugin.api_client.payplug')] - private PayPlugApiClientInterface $payPlugApiClient, + private PayPlugApiClientFactory $payPlugApiClientFactory, private EntityManagerInterface $paymentEntityManager, ) { } @@ -43,9 +43,8 @@ public function resolve(PaymentInterface $payment): void return; } - $gatewayConfig = $paymentMethod->getGatewayConfig()->getConfig(); - $this->payPlugApiClient->initialise($gatewayConfig['secretKey']); - $payment = $this->payPlugApiClient->retrieve((string) $details['payment_id']); + $payplugApiClient = $this->payPlugApiClientFactory->createForPaymentMethod($paymentMethod); + $payment = $payplugApiClient->retrieve((string) $details['payment_id']); switch (true) { case $payment->is_paid: From 91ba46c0fab650f1538f2c7f73aa4558d96ffd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 16:29:39 +0200 Subject: [PATCH 06/18] PPSYL-163 - Allow renew oauth when updating payment method --- config/twig_hooks/admin.yaml | 22 ++++++++++++---- .../PostSavePaymentMethodEventListener.php | 26 ++++++++++++++++++- .../Type/AbstractGatewayConfigurationType.php | 7 +++++ .../payment_method/form/renew_oauth.html.twig | 5 ++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 templates/admin/payment_method/form/renew_oauth.html.twig diff --git a/config/twig_hooks/admin.yaml b/config/twig_hooks/admin.yaml index 2480a8fd..0b2b9fed 100644 --- a/config/twig_hooks/admin.yaml +++ b/config/twig_hooks/admin.yaml @@ -26,8 +26,20 @@ sylius_twig_hooks: 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_american_express': &amexGateway live_checkbox: *liveCheckbox - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': *payplugGateway - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_oney': *oneyGateway - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_bancontact': *bancontactGateway - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_apple_pay': *applePayGateway - 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express': *amexGateway + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': + <<: *payplugGateway + renew_oauth: &renewOAuth + template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/renew_oauth.html.twig' + priority: -5 + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_oney': + <<: *oneyGateway + renew_oauth: *renewOAuth + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_bancontact': + <<: *bancontactGateway + renew_oauth: *renewOAuth + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_apple_pay': + <<: *applePayGateway + renew_oauth: *renewOAuth + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express': + <<: *amexGateway + renew_oauth: *renewOAuth diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index 98a10422..6277346c 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -14,6 +14,7 @@ use function Symfony\Component\Translation\t; #[AsEventListener(event: 'sylius.payment_method.post_create', method: 'onCreate')] +#[AsEventListener(event: 'sylius.payment_method.post_update', method: 'onUpdate')] final class PostSavePaymentMethodEventListener { public function __construct( @@ -31,12 +32,35 @@ public function onCreate(ResourceControllerEvent $event): void // TODO: check if the paymentMethod is one belong to payplug + $this->startOAuth($paymentMethod, $event); + } + + public function onUpdate(ResourceControllerEvent $event): void + { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { - // Should never happen return; } + $isRenewal = $request->request->all('sylius_admin_payment_method')['gatewayConfig']['config']['renew_oauth'] ?? false; + $isRenewal = \filter_var($isRenewal, \FILTER_VALIDATE_BOOLEAN); + if (true !== $isRenewal) { + return; + } + $paymentMethod = $event->getSubject(); + if (!$paymentMethod instanceof PaymentMethodInterface) { + return; + } + + $this->startOAuth($paymentMethod, $event); + } + private function startOAuth(PaymentMethodInterface $paymentMethod, ResourceControllerEvent $event): void + { + $request = $this->requestStack->getCurrentRequest(); + if (null === $request) { + // Should never happen + return; + } $request->getSession()->set('payplug_sylius_oauth_payment_method_id', $paymentMethod->getId()); $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); diff --git a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php index 481073de..2f9617a7 100644 --- a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php @@ -61,6 +61,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help_html' => true, 'required' => false, ]) + ->add('renew_oauth', CheckboxType::class, [ + 'label' => 'payplug_sylius_payplug_plugin.ui.renew_oauth', + 'help' => 'payplug_sylius_payplug_plugin.ui.renew_oauth_help', + 'help_html' => true, + 'mapped' => false, + 'required' => false, + ]) ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void { $this->checkCreationRequirements( $this->gatewayFactoryTitle, diff --git a/templates/admin/payment_method/form/renew_oauth.html.twig b/templates/admin/payment_method/form/renew_oauth.html.twig new file mode 100644 index 00000000..ee30d8f9 --- /dev/null +++ b/templates/admin/payment_method/form/renew_oauth.html.twig @@ -0,0 +1,5 @@ +{% set form = hookable_metadata.context.form.gatewayConfig.config.renew_oauth %} + +
+ {{ form_row(form, {attr: {name:"renew_oauth"}}) }} +
From eee9f67fe588d2a427fcd1e152b62385328bcc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 29 Aug 2025 18:17:22 +0200 Subject: [PATCH 07/18] PPSYL-163 - Delegate constraint check after submit --- .../Auth/UnifiedAuthenticationController.php | 10 +- .../PostSavePaymentMethodEventListener.php | 12 ++- ...yPlugGatewayConfigurationTypeExtension.php | 12 +-- .../Type/AbstractGatewayConfigurationType.php | 13 --- ...mericanExpressGatewayConfigurationType.php | 4 - .../Type/ApplePayGatewayConfigurationType.php | 4 - .../BancontactGatewayConfigurationType.php | 4 - .../Constraints/IsCanSavePaymentMethod.php | 5 +- .../IsCanSavePaymentMethodValidator.php | 15 +-- .../Constraints/IsPayPlugSecretKeyValid.php | 21 ---- .../IsPayPlugSecretKeyValidator.php | 46 --------- .../PayplugPermissionValidator.php | 18 +--- src/Validator/PaymentMethodValidator.php | 99 +++++++++++++++++++ translations/validators.en.yml | 26 ++--- translations/validators.fr.yml | 28 +++--- translations/validators.it.yml | 24 ++--- 16 files changed, 173 insertions(+), 168 deletions(-) delete mode 100644 src/Gateway/Validator/Constraints/IsPayPlugSecretKeyValid.php delete mode 100644 src/Gateway/Validator/Constraints/IsPayPlugSecretKeyValidator.php create mode 100644 src/Validator/PaymentMethodValidator.php diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php index ea73f742..f22ce3f9 100644 --- a/src/Action/Admin/Auth/UnifiedAuthenticationController.php +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -7,11 +7,14 @@ use Doctrine\ORM\EntityManagerInterface; use Payplug\Authentication; use Payplug\Payplug; +use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator; +use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Resource\Doctrine\Persistence\RepositoryInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -27,6 +30,7 @@ public function __construct( private RouterInterface $router, private RepositoryInterface $paymentMethodRepository, private EntityManagerInterface $entityManager, + private PaymentMethodValidator $paymentMethodValidator, ) { } @@ -89,7 +93,11 @@ public function oauthCallback(Request $request): Response $this->cleanSession($request); $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.ui.admin.auth.oauth_callback.success'); - return new RedirectResponse($this->router->generate('sylius_admin_payment_method_index'));; + + // Ensure that the payment method is well configured + $this->paymentMethodValidator->process($paymentMethod); + + return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethod->getId()])); } private function cleanSession(Request $request): void diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index 6277346c..1cce4d7e 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -5,6 +5,7 @@ namespace PayPlug\SyliusPayPlugPlugin\EventListener; use Payplug\Authentication; +use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator; use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; @@ -20,6 +21,7 @@ final class PostSavePaymentMethodEventListener public function __construct( private RequestStack $requestStack, private RouterInterface $router, + private PaymentMethodValidator $paymentMethodValidator, ) { } @@ -37,6 +39,10 @@ public function onCreate(ResourceControllerEvent $event): void public function onUpdate(ResourceControllerEvent $event): void { + $paymentMethod = $event->getSubject(); + if (!$paymentMethod instanceof PaymentMethodInterface) { + return; + } $request = $this->requestStack->getCurrentRequest(); if (null === $request) { return; @@ -44,10 +50,8 @@ public function onUpdate(ResourceControllerEvent $event): void $isRenewal = $request->request->all('sylius_admin_payment_method')['gatewayConfig']['config']['renew_oauth'] ?? false; $isRenewal = \filter_var($isRenewal, \FILTER_VALIDATE_BOOLEAN); if (true !== $isRenewal) { - return; - } - $paymentMethod = $event->getSubject(); - if (!$paymentMethod instanceof PaymentMethodInterface) { + $this->paymentMethodValidator->process($paymentMethod); + return; } diff --git a/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php b/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php index 3853b5b1..a6c47a2a 100644 --- a/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php +++ b/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php @@ -4,24 +4,17 @@ namespace PayPlug\SyliusPayPlugPlugin\Gateway\Form\Extension; -use PayPlug\SyliusPayPlugPlugin\Const\Permission; use PayPlug\SyliusPayPlugPlugin\Gateway\Form\Type\AbstractGatewayConfigurationType; use PayPlug\SyliusPayPlugPlugin\Gateway\Form\Type\PayPlugGatewayConfigurationType; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; -use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\PayplugPermission; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; -use Symfony\Contracts\Translation\TranslatorInterface; final class PayPlugGatewayConfigurationTypeExtension extends AbstractTypeExtension { - public function __construct(private TranslatorInterface $translator) - { - } - /** * @inheritdoc */ @@ -32,8 +25,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.form.one_click_enable', 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, - 'constraints' => [new PayplugPermission(Permission::CAN_SAVE_CARD)], - 'help' => $this->translator->trans('payplug_sylius_payplug_plugin.form.one_click_help'), + 'help' => 'payplug_sylius_payplug_plugin.form.one_click_help', 'help_html' => true, 'required' => false, ]) @@ -41,14 +33,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.form.integrated_payment_enable', 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, - 'constraints' => [new PayplugPermission(Permission::CAN_USE_INTEGRATED_PAYMENTS)], 'required' => false, ]) ->add(PayPlugGatewayFactory::DEFERRED_CAPTURE, CheckboxType::class, [ 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.form.deferred_capture_enable', 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, - 'constraints' => [new PayplugPermission(Permission::CAN_CREATE_DEFERRED_PAYMENT)], 'help' => 'payplug_sylius_payplug_plugin.form.deferred_capture_help', 'help_html' => true, 'required' => false, diff --git a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php index 2f9617a7..da325b82 100644 --- a/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/AbstractGatewayConfigurationType.php @@ -6,9 +6,6 @@ use Doctrine\Common\Collections\Collection; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; -use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsCanSavePaymentMethod; -use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsOneyEnabled; -use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsPayPlugSecretKeyValid; use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; @@ -26,8 +23,6 @@ class AbstractGatewayConfigurationType extends AbstractType { public const VALIDATION_GROUPS = ['Default', 'sylius']; - protected string $noTestKeyMessage = ''; - protected string $noAccessMessage = ''; protected string $gatewayFactoryTitle = ''; protected string $gatewayFactoryName = ''; @@ -50,14 +45,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.ui.live', 'help' => 'payplug_sylius_payplug_plugin.ui.live_help', - 'constraints' => [ // TODO: handle these check later - new IsPayPlugSecretKeyValid(), - new IsCanSavePaymentMethod([ - 'noTestKeyMessage' => $this->noTestKeyMessage, - 'noAccessMessage' => $this->noAccessMessage, - ]), - new IsOneyEnabled(), - ], 'help_html' => true, 'required' => false, ]) diff --git a/src/Gateway/Form/Type/AmericanExpressGatewayConfigurationType.php b/src/Gateway/Form/Type/AmericanExpressGatewayConfigurationType.php index a8b974ef..4387942b 100644 --- a/src/Gateway/Form/Type/AmericanExpressGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/AmericanExpressGatewayConfigurationType.php @@ -17,10 +17,6 @@ )] final class AmericanExpressGatewayConfigurationType extends AbstractGatewayConfigurationType { - protected string $noTestKeyMessage = 'payplug_sylius_payplug_plugin.american_express.can_not_save_method_with_test_key'; - - protected string $noAccessMessage = 'payplug_sylius_payplug_plugin.american_express.can_not_save_method_no_access'; - protected string $gatewayFactoryTitle = AmericanExpressGatewayFactory::FACTORY_TITLE; protected string $gatewayFactoryName = AmericanExpressGatewayFactory::FACTORY_NAME; diff --git a/src/Gateway/Form/Type/ApplePayGatewayConfigurationType.php b/src/Gateway/Form/Type/ApplePayGatewayConfigurationType.php index 6a29e2e3..1f3da3ec 100644 --- a/src/Gateway/Form/Type/ApplePayGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/ApplePayGatewayConfigurationType.php @@ -17,10 +17,6 @@ )] final class ApplePayGatewayConfigurationType extends AbstractGatewayConfigurationType { - protected string $noTestKeyMessage = 'payplug_sylius_payplug_plugin.apple_pay.can_not_save_method_with_test_key'; - - protected string $noAccessMessage = 'payplug_sylius_payplug_plugin.apple_pay.can_not_save_method_no_access'; - protected string $gatewayFactoryTitle = ApplePayGatewayFactory::FACTORY_TITLE; protected string $gatewayFactoryName = ApplePayGatewayFactory::FACTORY_NAME; diff --git a/src/Gateway/Form/Type/BancontactGatewayConfigurationType.php b/src/Gateway/Form/Type/BancontactGatewayConfigurationType.php index 4831cf07..25eac35b 100644 --- a/src/Gateway/Form/Type/BancontactGatewayConfigurationType.php +++ b/src/Gateway/Form/Type/BancontactGatewayConfigurationType.php @@ -17,10 +17,6 @@ )] final class BancontactGatewayConfigurationType extends AbstractGatewayConfigurationType { - protected string $noTestKeyMessage = 'payplug_sylius_payplug_plugin.bancontact.can_not_save_method_with_test_key'; - - protected string $noAccessMessage = 'payplug_sylius_payplug_plugin.bancontact.can_not_save_method_no_access'; - protected string $gatewayFactoryTitle = BancontactGatewayFactory::FACTORY_TITLE; protected string $gatewayFactoryName = BancontactGatewayFactory::FACTORY_NAME; diff --git a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethod.php b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethod.php index f568d98f..7bd79203 100644 --- a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethod.php +++ b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethod.php @@ -11,9 +11,8 @@ */ final class IsCanSavePaymentMethod extends Constraint { - public string $noTestKeyMessage = ''; - - public string $noAccessMessage = ''; + public string $noTestKeyMessage = 'payplug_sylius_payplug_plugin.%s.can_not_save_method_with_test_key'; + public string $noAccessMessage = 'payplug_sylius_payplug_plugin.%s.can_not_save_method_no_access'; public function validatedBy(): string { diff --git a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php index 0f591c8b..f2b6fc24 100644 --- a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php +++ b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php @@ -9,6 +9,7 @@ use PayPlug\SyliusPayPlugPlugin\Checker\CanSavePayplugPaymentMethodChecker; use PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; +use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -30,13 +31,14 @@ public function validate($value, Constraint $constraint): void if (!$constraint instanceof IsCanSavePaymentMethod) { throw new UnexpectedTypeException($constraint, IsCanSavePaymentMethod::class); } - if (null === $value || '' === $value) { + + if (!$value instanceof PaymentMethodInterface) { return; } - $factoryName = $this->context->getRoot()->getData()->getGatewayConfig()->getFactoryName(); - $channels = $this->context->getRoot()->getData()->getChannels(); - Assert::string($value); + $factoryName = $value->getGatewayConfig()?->getFactoryName(); + $channels = $value->getChannels(); + Assert::stringNotEmpty($factoryName); if (in_array($factoryName, self::GATEWAYS_SKIP, true)) { @@ -44,16 +46,15 @@ public function validate($value, Constraint $constraint): void } $checker = new CanSavePayplugPaymentMethodChecker($this->apiClientFactory->create($factoryName, $value)); - try { if (!$checker->isLive()) { - $this->context->buildViolation($constraint->noTestKeyMessage)->addViolation(); + $this->context->buildViolation(sprintf($constraint->noTestKeyMessage, $factoryName))->addViolation(); return; } if (!$checker->isEnabled($factoryName, $channels)) { - $this->context->buildViolation($constraint->noAccessMessage)->addViolation(); + $this->context->buildViolation(sprintf($constraint->noAccessMessage, $factoryName))->addViolation(); } return; diff --git a/src/Gateway/Validator/Constraints/IsPayPlugSecretKeyValid.php b/src/Gateway/Validator/Constraints/IsPayPlugSecretKeyValid.php deleted file mode 100644 index 0b2719c6..00000000 --- a/src/Gateway/Validator/Constraints/IsPayPlugSecretKeyValid.php +++ /dev/null @@ -1,21 +0,0 @@ - $value]); - \Payplug\Authentication::getPermissions(); - $apiClient = $this->apiClientFactory->create(PayPlugGatewayFactory::FACTORY_NAME, $value); - $apiClient->getAccount(true); - } catch (UnauthorizedException) { - $this->context->buildViolation($constraint->message) - ->addViolation(); - } - } -} diff --git a/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php b/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php index 1caae2b9..d59fae7b 100644 --- a/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php +++ b/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php @@ -6,11 +6,10 @@ use Payplug\Exception\UnauthorizedException; use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientFactory; -use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; +use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; -use Symfony\Component\Validator\Exception\UnexpectedValueException; final class PayplugPermissionValidator extends ConstraintValidator { @@ -24,22 +23,13 @@ public function validate(mixed $value, Constraint $constraint): void throw new UnexpectedTypeException($constraint, PayplugPermission::class); } - if (null === $value || '' === $value) { + if (!$value instanceof PaymentMethodInterface) { return; } - - if (!is_bool($value)) { - throw new UnexpectedValueException($value, 'boolean'); - } - - if (false === $value) { - return; - } - - $secretKey = $this->context->getRoot()->getData()->getGatewayConfig()->getConfig()['secretKey']; + $paymentMethod = $value; try { - $client = $this->apiClientFactory->create(PayPlugGatewayFactory::FACTORY_NAME, $secretKey); + $client = $this->apiClientFactory->createForPaymentMethod($paymentMethod); $accountPermissions = $client->getPermissions(); if (false === $accountPermissions[$constraint->permission]) { diff --git a/src/Validator/PaymentMethodValidator.php b/src/Validator/PaymentMethodValidator.php new file mode 100644 index 00000000..8bce925f --- /dev/null +++ b/src/Validator/PaymentMethodValidator.php @@ -0,0 +1,99 @@ +getGatewayConfig()) { + return; + } + + $errors = match ($paymentMethod->getGatewayConfig()->getFactoryName()) { + PayPlugGatewayFactory::FACTORY_NAME => $this->processPayplug($paymentMethod), + OneyGatewayFactory::FACTORY_NAME => $this->processOney($paymentMethod), + BancontactGatewayFactory::FACTORY_NAME => $this->processBancontact($paymentMethod), + AmericanExpressGatewayFactory::FACTORY_NAME => $this->processAmex($paymentMethod), + ApplePayGatewayFactory::FACTORY_NAME => $this->processApplePay($paymentMethod), + default => throw new \InvalidArgumentException("Unsupported payment method"), + }; + + foreach ($errors as $error) { + $this->requestStack->getSession()->getFlashBag()->add('error', $error->getMessage()); + } + if (0 !== count($errors)) { + $paymentMethod->disable(); + } + $this->entityManager->flush(); + } + + private function processPayplug(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $config = $paymentMethod->getGatewayConfig()?->getConfig() ?? []; + $constraintList = [new IsCanSavePaymentMethod()]; + + if (true === $config[PayPlugGatewayFactory::ONE_CLICK]) { + $constraintList[] = new PayplugPermission(Permission::CAN_SAVE_CARD); + } + if (true === $config[PayPlugGatewayFactory::DEFERRED_CAPTURE]) { + $constraintList[] = new PayplugPermission(Permission::CAN_CREATE_DEFERRED_PAYMENT); + } + if (true === $config[PayPlugGatewayFactory::INTEGRATED_PAYMENT]) { + $constraintList[] = new PayplugPermission(Permission::CAN_USE_INTEGRATED_PAYMENTS); + } + + return $this->validator->validate($paymentMethod, $constraintList); + } + + private function processOney(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $constraintList = [new IsOneyEnabled()]; + return $this->validator->validate($paymentMethod, $constraintList); + } + + private function processBancontact(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $constraintList = [new IsCanSavePaymentMethod()]; + return $this->validator->validate($paymentMethod, $constraintList); + + } + + private function processAmex(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $constraintList = [new IsCanSavePaymentMethod()]; + return $this->validator->validate($paymentMethod, $constraintList); + } + + private function processApplePay(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $constraintList = [new IsCanSavePaymentMethod()]; + return $this->validator->validate($paymentMethod, $constraintList); + } +} diff --git a/translations/validators.en.yml b/translations/validators.en.yml index 78facf6b..8ca73234 100644 --- a/translations/validators.en.yml +++ b/translations/validators.en.yml @@ -16,28 +16,30 @@ payplug_sylius_payplug_plugin: can_not_save_cards: | You do not have access to this feature. For more information, please contact us at: support@payplug.com - bancontact: + payplug_bancontact: can_not_save_method_with_test_key: | - The Bancontact payment method is not available for the TEST mode. - Please activate the LIVE mode by adding the corresponding secret key. + The Bancontact payment method is not available for the TEST mode. + Please activate the LIVE mode. can_not_save_method_no_access: | - You don't have access to this feature yet. - To activate Bancontact, please fill in - this form + You don't have access to this feature yet. + To activate Bancontact, please fill in + this form and activate the LIVE mode. - apple_pay: + payplug_apple_pay: can_not_save_method_with_test_key: | The Apple Pay payment method is not available for the TEST mode. + Please activate the LIVE mode. can_not_save_method_no_access: | - You don't have access to this feature yet. - To activate Apple Pay, please fill in + You don't have access to this feature yet. + To activate Apple Pay, please fill in this form and activate the LIVE mode. - american_express: + payplug_american_express: can_not_save_method_with_test_key: | The American Express payment method is not available for the TEST mode. + Please activate the LIVE mode. can_not_save_method_no_access: | - You don't have access to this feature yet. - To activate American Express, please fill in + You don't have access to this feature yet. + To activate American Express, please fill in this form and activate the LIVE mode. diff --git a/translations/validators.fr.yml b/translations/validators.fr.yml index e2ab2a5c..4da2b61b 100644 --- a/translations/validators.fr.yml +++ b/translations/validators.fr.yml @@ -15,28 +15,30 @@ payplug_sylius_payplug_plugin: can_not_save_cards: | Vous n'avez pas accès à cette fonctionnalité. Pour plus d'informations, veuillez nous contacter à : support@payplug.com - bancontact: - can_not_save_method_with_test_key: | - Le paiement par Bancontact n’est pas disponible en mode TEST. - Veuillez activer le mode LIVE en saisissant la clé secrète correspondante. + payplug_bancontact: + can_not_save_method_with_test_key: | + Le paiement par Bancontact n’est pas disponible en mode TEST. + Veuillez activer le mode LIVE. can_not_save_method_no_access: | - Vous n'avez pas accès à cette fonctionnalité. - Pour activer Bancontact, rendez-vous sur - ce formulaire + Vous n'avez pas accès à cette fonctionnalité. + Pour activer Bancontact, rendez-vous sur + ce formulaire et activez le mode LIVE. - apple_pay: + payplug_apple_pay: can_not_save_method_with_test_key: | Le paiement par Apple Pay n’est pas disponible en mode TEST. + Veuillez activer le mode LIVE. can_not_save_method_no_access: | - Vous n'avez pas accès à cette fonctionnalité. - Pour activer Apple Pay, rendez-vous sur + Vous n'avez pas accès à cette fonctionnalité. + Pour activer Apple Pay, rendez-vous sur ce formulaire et activez le mode LIVE. - american_express: + payplug_american_express: can_not_save_method_with_test_key: | Le paiement par American Express n’est pas disponible en mode TEST. + Veuillez activer le mode LIVE. can_not_save_method_no_access: | - Vous n'avez pas accès à cette fonctionnalité. - Pour activer American Express, rendez-vous sur + Vous n'avez pas accès à cette fonctionnalité. + Pour activer American Express, rendez-vous sur ce formulaire et activez le mode LIVE. diff --git a/translations/validators.it.yml b/translations/validators.it.yml index 1abcd3f6..fdd6fe52 100644 --- a/translations/validators.it.yml +++ b/translations/validators.it.yml @@ -15,28 +15,30 @@ payplug_sylius_payplug_plugin: can_not_save_cards: | Non hai accesso a questa funzionalità. Per ulteriori informazioni, contattaci al seguente indirizzo e-mail: support@payplug.com - bancontact: + payplug_bancontact: can_not_save_method_with_test_key: | - Il metodo di pagamento Bancontact non è disponibile in modalità TEST. - Attiva la modalità LIVE utilizzando la chiave segreta corrispondente. + Il metodo di pagamento Bancontact non è disponibile in modalità TEST. + Attiva la modalità LIVE. can_not_save_method_no_access: | - Non puoi ancora accedere a questa funzionalità. - Per attivare Bancontact, compila - questo modulo + Non puoi ancora accedere a questa funzionalità. + Per attivare Bancontact, compila + questo modulo e attiva la modalità LIVE. - apple_pay: + payplug_american_express: can_not_save_method_with_test_key: | Il pagamento Apple Pay non è disponibile in modalità TEST. + Attiva la modalità LIVE. can_not_save_method_no_access: | - Non puoi ancora accedere a questa funzionalità. - Per attivare Apple Pay, compila + Non puoi ancora accedere a questa funzionalità. + Per attivare Apple Pay, compila questo modulo e attiva la modalità LIVE. american_express: can_not_save_method_with_test_key: | Il pagamento American Express non è disponibile in modalità TEST. + Attiva la modalità LIVE. can_not_save_method_no_access: | - Non puoi ancora accedere a questa funzionalità. - Per attivare American Express, compila + Non puoi ancora accedere a questa funzionalità. + Per attivare American Express, compila questo modulo e attiva la modalità LIVE. From 40837b07f45d3e1e4bb541916dd14ddddf8ec46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 09:21:03 +0200 Subject: [PATCH 08/18] PPSYL-163 - Add custom flashes error message to display html --- config/twig_hooks/admin.yaml | 4 ++++ src/Validator/PaymentMethodValidator.php | 18 +++++++------- templates/admin/shared/flashes.html.twig | 30 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 templates/admin/shared/flashes.html.twig diff --git a/config/twig_hooks/admin.yaml b/config/twig_hooks/admin.yaml index 0b2b9fed..931ecf69 100644 --- a/config/twig_hooks/admin.yaml +++ b/config/twig_hooks/admin.yaml @@ -1,5 +1,9 @@ sylius_twig_hooks: hooks: + sylius_admin.payment_method.update.content: + payplug_flashes: + template: '@PayPlugSyliusPayPlugPlugin/admin/shared/flashes.html.twig' + priority: 350 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug': &payplugGateway live_checkbox: &liveCheckbox template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/live_checkbox.html.twig' diff --git a/src/Validator/PaymentMethodValidator.php b/src/Validator/PaymentMethodValidator.php index 8bce925f..bb88cf29 100644 --- a/src/Validator/PaymentMethodValidator.php +++ b/src/Validator/PaymentMethodValidator.php @@ -21,8 +21,10 @@ * Validate if the payment method is well configured. * If not, it adds errors in flash-bag AND disable the payment method. */ -class PaymentMethodValidator +final class PaymentMethodValidator { + private const VALIDATION_GROUPS = ['Default', 'sylius']; + public function __construct( private RequestStack $requestStack, private ValidatorInterface $validator, @@ -46,10 +48,11 @@ public function process(PaymentMethodInterface $paymentMethod): void }; foreach ($errors as $error) { - $this->requestStack->getSession()->getFlashBag()->add('error', $error->getMessage()); + $this->requestStack->getSession()->getFlashBag()->add('payplug_error', $error->getMessage()); } if (0 !== count($errors)) { $paymentMethod->disable(); + $this->requestStack->getSession()->getFlashBag()->add('payplug_error', 'payplug_sylius_payplug_plugin.admin.payment_method_disabled'); } $this->entityManager->flush(); } @@ -69,31 +72,30 @@ private function processPayplug(PaymentMethodInterface $paymentMethod): Constrai $constraintList[] = new PayplugPermission(Permission::CAN_USE_INTEGRATED_PAYMENTS); } - return $this->validator->validate($paymentMethod, $constraintList); + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } private function processOney(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface { $constraintList = [new IsOneyEnabled()]; - return $this->validator->validate($paymentMethod, $constraintList); + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } private function processBancontact(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface { $constraintList = [new IsCanSavePaymentMethod()]; - return $this->validator->validate($paymentMethod, $constraintList); - + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } private function processAmex(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface { $constraintList = [new IsCanSavePaymentMethod()]; - return $this->validator->validate($paymentMethod, $constraintList); + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } private function processApplePay(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface { $constraintList = [new IsCanSavePaymentMethod()]; - return $this->validator->validate($paymentMethod, $constraintList); + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } } diff --git a/templates/admin/shared/flashes.html.twig b/templates/admin/shared/flashes.html.twig new file mode 100644 index 00000000..9e656426 --- /dev/null +++ b/templates/admin/shared/flashes.html.twig @@ -0,0 +1,30 @@ +{% set session = _context.app.session|default(null) %} + +
+{% for flash in session.flashbag.get('payplug_error') %} + +{% endfor %} +
From 6d19c1b825bddf07d4dc252f4ff82980167b4047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 09:22:50 +0200 Subject: [PATCH 09/18] PPSYL-163 - Add translations --- src/Action/Admin/Auth/UnifiedAuthenticationController.php | 4 +--- translations/flashes.en.yml | 4 ++++ translations/flashes.fr.yml | 3 +++ translations/flashes.it.yml | 3 +++ translations/messages.en.yml | 7 +++++++ translations/messages.fr.yml | 7 +++++++ translations/messages.it.yml | 7 +++++++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php index f22ce3f9..9eb457ce 100644 --- a/src/Action/Admin/Auth/UnifiedAuthenticationController.php +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -8,13 +8,11 @@ use Payplug\Authentication; use Payplug\Payplug; use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator; -use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Resource\Doctrine\Persistence\RepositoryInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -92,7 +90,7 @@ public function oauthCallback(Request $request): Response $this->entityManager->flush(); $this->cleanSession($request); - $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.ui.admin.auth.oauth_callback.success'); + $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.admin.oauth_callback_success'); // Ensure that the payment method is well configured $this->paymentMethodValidator->process($paymentMethod); diff --git a/translations/flashes.en.yml b/translations/flashes.en.yml index 68820d60..636d36a7 100644 --- a/translations/flashes.en.yml +++ b/translations/flashes.en.yml @@ -5,3 +5,7 @@ payplug_sylius_payplug_plugin: transaction_failed_1click: The transaction was not completed and your card was not charged. warning: payment_success_no_card_saved: The payment was successful but we were unable to save your credit card details. + admin: + oauth_callback_success: The OAuth connection was established successfully. + payment_method_disabled: ⚠️ The payment method has been disabled because some validation criteria are not met. + diff --git a/translations/flashes.fr.yml b/translations/flashes.fr.yml index 6449312e..cdb5062d 100644 --- a/translations/flashes.fr.yml +++ b/translations/flashes.fr.yml @@ -5,3 +5,6 @@ payplug_sylius_payplug_plugin: transaction_failed_1click: La transaction a échoué, votre carte de paiement ne sera pas débitée. warning: payment_success_no_card_saved: Le paiement a été effectué avec succès, mais nous n'avons pas pu enregistrer votre carte bancaire. + admin: + oauth_callback_success: La connexion OAuth a été établie avec succès. + payment_method_disabled: ⚠️ La méthode de paiement a été désactivée car certains critères de validation ne sont pas remplis. diff --git a/translations/flashes.it.yml b/translations/flashes.it.yml index 7a9ac5db..885d8c4c 100644 --- a/translations/flashes.it.yml +++ b/translations/flashes.it.yml @@ -5,3 +5,6 @@ payplug_sylius_payplug_plugin: transaction_failed_1click: La transazione non è stata conclusa e non è stato effettuato alcun addebito sulla tua carta. warning: payment_success_no_card_saved: Il pagamento è andato a buon fine ma non è stato possibile salvare la tua carta di credito. + admin: + oauth_callback_success: La connessione OAuth è stata stabilita con successo. + payment_method_disabled: ⚠️ Il metodo di pagamento è stato disattivato perché alcuni criteri di validazione non sono soddisfatti. diff --git a/translations/messages.en.yml b/translations/messages.en.yml index 28c713ee..53f4ec16 100644 --- a/translations/messages.en.yml +++ b/translations/messages.en.yml @@ -107,6 +107,13 @@ payplug_sylius_payplug_plugin: deferred_capture: process_order_info: | You will be charged when your order is processed. + live: LIVE Mode + live_help: | + When this option is enabled, LIVE mode is used.
+ If disabled, TEST mode will be applied + renew_oauth: Force OAuth reconnection + renew_oauth_help: | + If this option is checked, a new authentication flow will be started when clicking the "Update" button. form: oney_error: Some missing information is required to pay using Oney by Payplug complete_info: diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml index 128aff85..c024c679 100644 --- a/translations/messages.fr.yml +++ b/translations/messages.fr.yml @@ -127,6 +127,13 @@ payplug_sylius_payplug_plugin: process_order_info: | Vous serez prélevé(é) lors du traitement de votre commande. + live: Mode LIVE + live_help: | + Lorsque cette option est activée, le mode LIVE est utilisé.
+ Si elle est désactivée, le mode TEST sera appliqué + renew_oauth: Forcer la reconnexion OAuth + renew_oauth_help: | + Si cette option est cochée, un nouveau flux d’authentification sera lancé lors du clic sur le bouton "Mise à jour". form: oney_error: Il y a des informations manquantes pour pouvoir payer en utilisant Oney by Payplug complete_info: diff --git a/translations/messages.it.yml b/translations/messages.it.yml index 9c50acf9..b703af6a 100644 --- a/translations/messages.it.yml +++ b/translations/messages.it.yml @@ -107,6 +107,13 @@ payplug_sylius_payplug_plugin: deferred_capture: process_order_info: | L'addebito avverrà al momento dell'elaborazione dell'ordine. + live: Modalità LIVE + live_help: | + Quando questa opzione è attivata, viene utilizzata la modalità LIVE.
+ Se disattivata, verrà applicata la modalità TEST + renew_oauth: Forza la riconnessione OAuth + renew_oauth_help: | + Se questa opzione è selezionata, un nuovo flusso di autenticazione verrà avviato quando si fa clic sul pulsante "Aggiorna". form: oney_error: Mancano alcune informazioni per poter pagare con “Oney by Payplug” complete_info: From 7c7020fa30333c1362aabc7793dbda58be82de22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 14:28:47 +0200 Subject: [PATCH 10/18] PPSYL-163 - Bump payplug sdk --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b98d2ab3..079ccdb9 100755 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": "^8.2", "ext-json": "*", "giggsey/libphonenumber-for-php": "^8.12", - "payplug/payplug-php": "^3.1", + "payplug/payplug-php": "^4.0", "php-http/message-factory": "^1.1", "sylius/refund-plugin": "^2.0", "sylius/sylius": "^2.0", From bb74f87cee722dcaf7d31fce37a5ef5a7cf9370b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 15:26:32 +0200 Subject: [PATCH 11/18] PPSYL-163 - Improve error catching and log on dedicated channel --- config/services.yaml | 4 +- .../Auth/UnifiedAuthenticationController.php | 139 +++++++++++------- .../PayPlugSyliusPayPlugExtension.php | 10 ++ .../PostSavePaymentMethodEventListener.php | 29 +++- src/Handler/PaymentNotificationHandler.php | 2 - .../RefundPaymentProcessor.php | 2 - .../OneySimulationDataProvider.php | 1 - translations/flashes.en.yml | 2 +- translations/flashes.fr.yml | 1 + translations/flashes.it.yml | 1 + 10 files changed, 124 insertions(+), 67 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index 3a290b5f..a58fc643 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -7,6 +7,8 @@ services: PayPlug\SyliusPayPlugPlugin\: resource: '../src/*' exclude: '../src/{ApiClient,DependencyInjection,Entity,Exception,Model,Repository,PayPlugSyliusPayPlugPlugin.php}' + bind: + Psr\Log\LoggerInterface: '@monolog.logger.payplug' PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepositoryInterface: class: PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepository @@ -73,7 +75,7 @@ services: tags: - name: sylius.payment_request.provider.http_response gateway_factory: !php/const PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory::FACTORY_NAME - + ## Bancontact Payplug Gateway ## payplug_sylius_payplug_plugin.command_provider.payplug_bancontact: diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php index 9eb457ce..69e0b888 100644 --- a/src/Action/Admin/Auth/UnifiedAuthenticationController.php +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -8,6 +8,7 @@ use Payplug\Authentication; use Payplug\Payplug; use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator; +use Psr\Log\LoggerInterface; use Sylius\Resource\Doctrine\Persistence\RepositoryInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -18,6 +19,12 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; +/** + * This controller is used to authenticate the user with PayPlug + * + * The OAuth process start when creating a new payment method or updated it. + * @see PayPlug\SyliusPayPlugPlugin\EventListener\PostSavePaymentMethodEventListener + */ #[Route('/payplug/auth')] final class UnifiedAuthenticationController extends AbstractController { @@ -29,73 +36,101 @@ public function __construct( private RepositoryInterface $paymentMethodRepository, private EntityManagerInterface $entityManager, private PaymentMethodValidator $paymentMethodValidator, + private LoggerInterface $logger, ) { } #[Route('/setup-redirection', name: 'payplug_sylius_admin_auth_setup_redirection')] - public function setupRedirection(Request $request): void + public function setupRedirection(Request $request): Response { - $clientId = $request->query->get('client_id'); - $companyId = $request->query->get('company_id'); - - $request->getSession()->set('payplug_client_id', $clientId); - $request->getSession()->set('payplug_company_id', $companyId); - - $challenge = bin2hex(openssl_random_pseudo_bytes(50)); - $request->getSession()->set('payplug_oauth_challenge', $challenge); - - $callBackUrl = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', [], RouterInterface::ABSOLUTE_URL); - - // This method will redirect the user to PayPlug's oauth page via header('Location')' - Authentication::initiateOAuth($clientId, $callBackUrl, $challenge); - exit; + try { + $clientId = $request->query->get('client_id'); + $companyId = $request->query->get('company_id'); + + $request->getSession()->set('payplug_client_id', $clientId); + $request->getSession()->set('payplug_company_id', $companyId); + + $challenge = bin2hex(openssl_random_pseudo_bytes(50)); + $request->getSession()->set('payplug_oauth_challenge', $challenge); + + $callBackUrl = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', [], RouterInterface::ABSOLUTE_URL); + + // This method will redirect the user to PayPlug's oauth page via header('Location')' + Authentication::initiateOAuth($clientId, $callBackUrl, $challenge); + // Fetch the header Location the Sdk put and redirect the user to it + $headers = \headers_list(); + foreach ($headers as $header) { + if (str_starts_with($header, 'Location:')) { + return new RedirectResponse(substr($header, 9)); + } + } + } catch (\Throwable $e) { + $this->logger->critical('Error while perform Payplug OAuth Setup redirection', ['message' => $e->getMessage(), 'exception' => $e]); + return $this->handleOAuthError($request); + } } #[Route('/oauth-callback', name: 'payplug_sylius_admin_auth_oauth_callback')] public function oauthCallback(Request $request): Response { - $code = $request->query->get('code'); - $clientId = $request->getSession()->get('payplug_client_id'); - $challenge = $request->getSession()->get('payplug_oauth_challenge'); - $callback = $this->generateUrl('payplug_sylius_admin_auth_oauth_callback', [], UrlGeneratorInterface::ABSOLUTE_URL); - - $jwt = Authentication::generateJWTOneShot($code, $callback, $clientId, $challenge); - if ([] === $jwt || $jwt['httpStatus'] !== 200) { - throw new BadRequestHttpException('Error while generating JWT'); + try { + $code = $request->query->get('code'); + $clientId = $request->getSession()->get('payplug_client_id'); + $challenge = $request->getSession()->get('payplug_oauth_challenge'); + $callback = $this->generateUrl('payplug_sylius_admin_auth_oauth_callback', [], UrlGeneratorInterface::ABSOLUTE_URL); + + $jwt = Authentication::generateJWTOneShot($code, $callback, $clientId, $challenge); + if ([] === $jwt || $jwt['httpStatus'] !== 200) { + throw new BadRequestHttpException('Error while generating JWT'); + } + $paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id'); + if (null === $paymentMethodId) { + throw new BadRequestHttpException('No payment method id found in session'); + } + $paymentMethod = $this->paymentMethodRepository->find($paymentMethodId); + if (null === $paymentMethod) { + throw new \LogicException('No payment method found'); + } + $gatewayConfig = $paymentMethod->getGatewayConfig(); + if (null === $gatewayConfig) { + throw new \LogicException('No gateway config found'); + } + + $companyId = $request->getSession()->get('payplug_company_id'); + Payplug::init(['secretKey' => $jwt['httpResponse']['access_token']]); + $clientName = 'Sylius - ' . $paymentMethod->getName(); + $testClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'test'); + $liveClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'live'); + + $config = $gatewayConfig->getConfig(); + $config['live_client'] = $liveClientDataResult['httpResponse']; + $config['test_client'] = $testClientDataResult['httpResponse']; + $gatewayConfig->setConfig($config); + + $this->entityManager->flush(); + $this->cleanSession($request); + + $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.admin.oauth_callback_success'); + + // Ensure that the payment method is well configured + $this->paymentMethodValidator->process($paymentMethod); + + return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethod->getId()])); + } catch (\Throwable $e) { + $this->logger->critical('Error while perform Payplug OAuth callback', ['message' => $e->getMessage(), 'exception' => $e]); + return $this->handleOAuthError($request); } + } + + private function handleOAuthError(Request $request): RedirectResponse + { + $request->getSession()->getFlashBag()->add('error', 'payplug_sylius_payplug_plugin.admin.oauth_setup_error'); $paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id'); if (null === $paymentMethodId) { - throw new BadRequestHttpException('No payment method id found in session'); + return new RedirectResponse($this->router->generate('sylius_admin_payment_method_index')); } - $paymentMethod = $this->paymentMethodRepository->find($paymentMethodId); - if (null === $paymentMethod) { - throw new \LogicException('No payment method found'); - } - $gatewayConfig = $paymentMethod->getGatewayConfig(); - if (null === $gatewayConfig) { - throw new \LogicException('No gateway config found'); - } - - $companyId = $request->getSession()->get('payplug_company_id'); - Payplug::init(['secretKey' => $jwt['httpResponse']['access_token']]); - $clientName = 'Sylius - ' . $paymentMethod->getName(); - $testClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'test'); - $liveClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'live'); - - $config = $gatewayConfig->getConfig(); - $config['live_client'] = $liveClientDataResult['httpResponse']; - $config['test_client'] = $testClientDataResult['httpResponse']; - $gatewayConfig->setConfig($config); - - $this->entityManager->flush(); - $this->cleanSession($request); - - $request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.admin.oauth_callback_success'); - - // Ensure that the payment method is well configured - $this->paymentMethodValidator->process($paymentMethod); - return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethod->getId()])); + return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethodId])); } private function cleanSession(Request $request): void diff --git a/src/DependencyInjection/PayPlugSyliusPayPlugExtension.php b/src/DependencyInjection/PayPlugSyliusPayPlugExtension.php index 1154226a..d22ef359 100644 --- a/src/DependencyInjection/PayPlugSyliusPayPlugExtension.php +++ b/src/DependencyInjection/PayPlugSyliusPayPlugExtension.php @@ -32,6 +32,7 @@ public function prepend(ContainerBuilder $container): void { $this->prependTwigExtension($container); $this->prependDoctrineMigrations($container); + $this->prependMonologExtension($container); } private function prependTwigExtension(ContainerBuilder $container): void @@ -48,6 +49,15 @@ private function prependTwigExtension(ContainerBuilder $container): void ]); } + public function prependMonologExtension(ContainerBuilder $container): void + { + if ($container->hasExtension('monolog')) { + $container->prependExtensionConfig('monolog', [ + 'channels' => ['payplug'], + ]); + } + } + protected function getMigrationsNamespace(): string { return 'PayPlug\SyliusPayPlugPlugin\Migrations'; diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index 1cce4d7e..de09a409 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -6,6 +6,7 @@ use Payplug\Authentication; use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator; +use Psr\Log\LoggerInterface; use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; @@ -22,6 +23,7 @@ public function __construct( private RequestStack $requestStack, private RouterInterface $router, private PaymentMethodValidator $paymentMethodValidator, + private LoggerInterface $logger, ) { } @@ -32,7 +34,11 @@ public function onCreate(ResourceControllerEvent $event): void return; } - // TODO: check if the paymentMethod is one belong to payplug + $gateway = $paymentMethod->getGatewayConfig(); + if (null === $gateway || !\str_contains('payplug', $gateway->getFactoryName())) { + // A new payment method has been created but that is not a payplug one, do nothing + return; + } $this->startOAuth($paymentMethod, $event); } @@ -47,9 +53,11 @@ public function onUpdate(ResourceControllerEvent $event): void if (null === $request) { return; } + $isRenewal = $request->request->all('sylius_admin_payment_method')['gatewayConfig']['config']['renew_oauth'] ?? false; $isRenewal = \filter_var($isRenewal, \FILTER_VALIDATE_BOOLEAN); if (true !== $isRenewal) { + // No need to renew the oauth token, let's validate the payment method with already existing config $this->paymentMethodValidator->process($paymentMethod); return; @@ -65,13 +73,18 @@ private function startOAuth(PaymentMethodInterface $paymentMethod, ResourceContr // Should never happen return; } - $request->getSession()->set('payplug_sylius_oauth_payment_method_id', $paymentMethod->getId()); - $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); - $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); + try { + $request->getSession()->set('payplug_sylius_oauth_payment_method_id', $paymentMethod->getId()); + $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); + $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); - /** @var string $payplugRedirectUrl */ - $payplugRedirectUrl = Authentication::getRegisterUrl($setupRedirection, $oauthCallback); - - $event->setResponse(new RedirectResponse($payplugRedirectUrl)); + throw new \LogicException('No location header found'); + /** @var string $payplugRedirectUrl */ + $payplugRedirectUrl = Authentication::getRegisterUrl($setupRedirection, $oauthCallback); + $event->setResponse(new RedirectResponse($payplugRedirectUrl)); + } catch (\Throwable $e) { + $this->logger->critical('Error while starting Payplug OAuth process', ['message' => $e->getMessage(), 'exception' => $e]); + $request->getSession()->getFlashBag()->add('error', 'payplug_sylius_payplug_plugin.admin.oauth_setup_error'); + } } } diff --git a/src/Handler/PaymentNotificationHandler.php b/src/Handler/PaymentNotificationHandler.php index edb322ba..afcc8468 100644 --- a/src/Handler/PaymentNotificationHandler.php +++ b/src/Handler/PaymentNotificationHandler.php @@ -18,14 +18,12 @@ use Sylius\Component\Core\Repository\CustomerRepositoryInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; -use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Lock\LockFactory; class PaymentNotificationHandler { public function __construct( - #[Autowire('@monolog.logger.payum')] private LoggerInterface $logger, private RepositoryInterface $payplugCardRepository, private FactoryInterface $payplugCardFactory, diff --git a/src/PaymentProcessing/RefundPaymentProcessor.php b/src/PaymentProcessing/RefundPaymentProcessor.php index f544efde..db250fb2 100644 --- a/src/PaymentProcessing/RefundPaymentProcessor.php +++ b/src/PaymentProcessing/RefundPaymentProcessor.php @@ -22,7 +22,6 @@ use Sylius\Component\Resource\Repository\RepositoryInterface; use Sylius\RefundPlugin\Entity\RefundPayment; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; -use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Workflow\Attribute\AsCompletedListener; use Symfony\Component\Workflow\Event\CompletedEvent; @@ -35,7 +34,6 @@ final class RefundPaymentProcessor implements PaymentProcessorInterface public function __construct( private RequestStack $requestStack, - #[Autowire('@monolog.logger.payum')] private LoggerInterface $logger, private TranslatorInterface $translator, private RepositoryInterface $refundPaymentRepository, diff --git a/src/Provider/OneySimulation/OneySimulationDataProvider.php b/src/Provider/OneySimulation/OneySimulationDataProvider.php index cb94dc22..bab83133 100644 --- a/src/Provider/OneySimulation/OneySimulationDataProvider.php +++ b/src/Provider/OneySimulation/OneySimulationDataProvider.php @@ -16,7 +16,6 @@ final class OneySimulationDataProvider implements OneySimulationDataProviderInte public function __construct( #[Autowire('@payplug_sylius_payplug_plugin.api_client.oney')] private PayPlugApiClientInterface $oneyClient, - #[Autowire('@monolog.logger.payum')] private LoggerInterface $logger, private OneySupportedPaymentChoiceProvider $oneySupportedPaymentChoiceProvider, ) { diff --git a/translations/flashes.en.yml b/translations/flashes.en.yml index 636d36a7..1faaa11a 100644 --- a/translations/flashes.en.yml +++ b/translations/flashes.en.yml @@ -8,4 +8,4 @@ payplug_sylius_payplug_plugin: admin: oauth_callback_success: The OAuth connection was established successfully. payment_method_disabled: ⚠️ The payment method has been disabled because some validation criteria are not met. - + oauth_setup_error: An error occurred while setting up the OAuth connection. Please try again. diff --git a/translations/flashes.fr.yml b/translations/flashes.fr.yml index cdb5062d..b05b5110 100644 --- a/translations/flashes.fr.yml +++ b/translations/flashes.fr.yml @@ -8,3 +8,4 @@ payplug_sylius_payplug_plugin: admin: oauth_callback_success: La connexion OAuth a été établie avec succès. payment_method_disabled: ⚠️ La méthode de paiement a été désactivée car certains critères de validation ne sont pas remplis. + oauth_setup_error: Une erreur s'est produite lors de la configuration de la connexion OAuth. Veuillez réessayer. diff --git a/translations/flashes.it.yml b/translations/flashes.it.yml index 885d8c4c..10cd5113 100644 --- a/translations/flashes.it.yml +++ b/translations/flashes.it.yml @@ -8,3 +8,4 @@ payplug_sylius_payplug_plugin: admin: oauth_callback_success: La connessione OAuth è stata stabilita con successo. payment_method_disabled: ⚠️ Il metodo di pagamento è stato disattivato perché alcuni criteri di validazione non sono soddisfatti. + oauth_setup_error: Si è verificato un errore durante la configurazione della connessione OAuth. Per favore riprova. From 45279d004533cbd7e8140a44a446bbd85331e68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 15:29:56 +0200 Subject: [PATCH 12/18] PPSYL-163 - Fix Oney validation --- .../PostSavePaymentMethodEventListener.php | 1 - .../DisplayOneyGatewayFormEventSubscriber.php | 59 ------------------- .../Constraints/IsOneyEnabledValidator.php | 14 +---- 3 files changed, 3 insertions(+), 71 deletions(-) delete mode 100644 src/EventSubscriber/DisplayOneyGatewayFormEventSubscriber.php diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index de09a409..449a6767 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -78,7 +78,6 @@ private function startOAuth(PaymentMethodInterface $paymentMethod, ResourceContr $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); - throw new \LogicException('No location header found'); /** @var string $payplugRedirectUrl */ $payplugRedirectUrl = Authentication::getRegisterUrl($setupRedirection, $oauthCallback); $event->setResponse(new RedirectResponse($payplugRedirectUrl)); diff --git a/src/EventSubscriber/DisplayOneyGatewayFormEventSubscriber.php b/src/EventSubscriber/DisplayOneyGatewayFormEventSubscriber.php deleted file mode 100644 index fd9bce68..00000000 --- a/src/EventSubscriber/DisplayOneyGatewayFormEventSubscriber.php +++ /dev/null @@ -1,59 +0,0 @@ - 'handle', - ]; - } - - public function handle(KernelEvent $event): void - { - if ('sylius_admin_payment_method_update' !== $event->getRequest()->attributes->get('_route')) { - return; - } - - /** @var \Sylius\Component\Core\Model\PaymentMethod|null $subject */ - $subject = $this->paymentMethodRepository->find($event->getRequest()->attributes->get('id')); - if (null === $subject) { - return; - } - - if ( - false === $subject->isEnabled() || - null === $subject->getGatewayConfig() || - OneyGatewayFactory::FACTORY_NAME !== $subject->getGatewayConfig()->getFactoryName() - ) { - return; - } - - if ($this->oneyChecker->isEnabled()) { - // Oney still enabled, do nothing - return; - } - - $this->requestStack->getSession()->getFlashBag()->add('error', 'payplug_sylius_payplug_plugin.error.oney_not_enabled'); - $subject->disable(); - $this->paymentMethodRepository->add($subject); - } -} diff --git a/src/Gateway/Validator/Constraints/IsOneyEnabledValidator.php b/src/Gateway/Validator/Constraints/IsOneyEnabledValidator.php index 8df285d1..fe5c8afb 100644 --- a/src/Gateway/Validator/Constraints/IsOneyEnabledValidator.php +++ b/src/Gateway/Validator/Constraints/IsOneyEnabledValidator.php @@ -31,18 +31,11 @@ public function validate($value, Constraint $constraint): void if (null === $value || '' === $value) { return; } - if (!is_string($value)) { - throw new UnexpectedValueException($value, 'string'); - } - // phpstan checks - $form = $this->context->getRoot(); - Assert::isInstanceOf($form, Form::class); - - $paymentMethod = $form->getData(); - if (!$paymentMethod instanceof PaymentMethodInterface) { + if (!$value instanceof PaymentMethodInterface) { return; } + $paymentMethod = $value; $gatewayConfig = $paymentMethod->getGatewayConfig(); if (!$gatewayConfig instanceof GatewayConfigInterface) { return; @@ -56,8 +49,7 @@ public function validate($value, Constraint $constraint): void } try { - $checker = new OneyChecker($this->apiClientFactory->create($factoryName, $value)); - + $checker = new OneyChecker($this->apiClientFactory->createForPaymentMethod($paymentMethod)); if (false === $checker->isEnabled()) { $this->context->buildViolation($constraint->message) ->addViolation(); From 4d940b5e0cea30654b0c6058572aaf0eb2acec2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Fri, 5 Sep 2025 17:47:55 +0200 Subject: [PATCH 13/18] PPSYL-163 - Fix prevent start oauth on non payplug payment --- src/EventListener/PostSavePaymentMethodEventListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index 449a6767..438b4907 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -35,7 +35,7 @@ public function onCreate(ResourceControllerEvent $event): void } $gateway = $paymentMethod->getGatewayConfig(); - if (null === $gateway || !\str_contains('payplug', $gateway->getFactoryName())) { + if (null === $gateway || !\str_contains($gateway->getFactoryName(), 'payplug')) { // A new payment method has been created but that is not a payplug one, do nothing return; } From de6365366a4bc17a78f1e9d11ae818147f72ed50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Mon, 8 Sep 2025 17:38:19 +0200 Subject: [PATCH 14/18] PPSYL-163 - Do not validate when paymentmethod is not payplug on update --- src/EventListener/PostSavePaymentMethodEventListener.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index 438b4907..e6a380bb 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -49,6 +49,12 @@ public function onUpdate(ResourceControllerEvent $event): void if (!$paymentMethod instanceof PaymentMethodInterface) { return; } + $gateway = $paymentMethod->getGatewayConfig(); + if (null === $gateway || !\str_contains($gateway->getFactoryName(), 'payplug')) { + // A new payment method has been created but that is not a payplug one, do nothing + return; + } + $request = $this->requestStack->getCurrentRequest(); if (null === $request) { return; From ee8e9d3fb802a549496c21ff5f5846bf487c3dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Tue, 9 Sep 2025 09:14:39 +0200 Subject: [PATCH 15/18] PPSYL-163 - Fix retrieving cards --- src/ApiClient/PayPlugApiClientInterface.php | 2 ++ src/Entity/Traits/CustomerTrait.php | 13 ++++--------- src/Entity/Traits/PaymentMethodTrait.php | 13 ++++--------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/ApiClient/PayPlugApiClientInterface.php b/src/ApiClient/PayPlugApiClientInterface.php index 20699a25..efa53f85 100644 --- a/src/ApiClient/PayPlugApiClientInterface.php +++ b/src/ApiClient/PayPlugApiClientInterface.php @@ -15,8 +15,10 @@ interface PayPlugApiClientInterface public const INTEGRATED_PAYMENT_INTEGRATION = 'INTEGRATED_PAYMENT'; + /** @deprecated */ public const LIVE_KEY_PREFIX = 'sk_live'; + /** @deprecated */ public const TEST_KEY_PREFIX = 'sk_test'; public const STATUS_CREATED = 'created'; diff --git a/src/Entity/Traits/CustomerTrait.php b/src/Entity/Traits/CustomerTrait.php index 9a7cc57c..7d3ae3dd 100644 --- a/src/Entity/Traits/CustomerTrait.php +++ b/src/Entity/Traits/CustomerTrait.php @@ -26,15 +26,10 @@ trait CustomerTrait public function getCards(): Collection { return $this->cards->filter(function (Card $card): bool { - $secretKeyPrefix = \substr($card->getPaymentMethod()->getGatewayConfig()->getConfig()['secretKey'], 0, 7); - if ( - ($card->isLive() && PayPlugApiClientInterface::LIVE_KEY_PREFIX === $secretKeyPrefix) || - (!$card->isLive() && PayPlugApiClientInterface::TEST_KEY_PREFIX === $secretKeyPrefix) - ) { - return true; - } - - return false; + $isLivePaymentMethod = $card->getPaymentMethod()->getGatewayConfig()?->getConfig()['live'] ?? false; + + return ($card->isLive() && true === $isLivePaymentMethod) || + (!$card->isLive() && false === $isLivePaymentMethod); }); } diff --git a/src/Entity/Traits/PaymentMethodTrait.php b/src/Entity/Traits/PaymentMethodTrait.php index 2ba4c6e1..a5a6c862 100644 --- a/src/Entity/Traits/PaymentMethodTrait.php +++ b/src/Entity/Traits/PaymentMethodTrait.php @@ -21,15 +21,10 @@ trait PaymentMethodTrait public function getCards(): Collection { return $this->cards->filter(function (Card $card): bool { - $secretKeyPrefix = \substr($card->getPaymentMethod()->getGatewayConfig()->getConfig()['secretKey'], 0, 7); - if ( - ($card->isLive() && PayPlugApiClientInterface::LIVE_KEY_PREFIX === $secretKeyPrefix) || - (!$card->isLive() && PayPlugApiClientInterface::TEST_KEY_PREFIX === $secretKeyPrefix) - ) { - return true; - } - - return false; + $isLivePaymentMethod = $card->getPaymentMethod()->getGatewayConfig()?->getConfig()['live'] ?? false; + + return ($card->isLive() && true === $isLivePaymentMethod) || + (!$card->isLive() && false === $isLivePaymentMethod); }); } From 541463fcf1ca87baa0b2f357b2e53bae3449cbbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Tue, 9 Sep 2025 11:07:29 +0200 Subject: [PATCH 16/18] PPSYL-163 - PHPStan getSession flashbag - Ignore error --- ruleset/phpstan-baseline.neon | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ruleset/phpstan-baseline.neon b/ruleset/phpstan-baseline.neon index 8563556e..2ed4e7ab 100644 --- a/ruleset/phpstan-baseline.neon +++ b/ruleset/phpstan-baseline.neon @@ -462,11 +462,27 @@ parameters: count: 1 path: ../src/Controller/OrderController.php + # Session Specific - message: '#^Cannot call method add\(\) on mixed\.$#' identifier: method.nonObject count: 4 path: ../src/Controller/OrderController.php + - + message: '#^Cannot call method add\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: ../src/Action/Admin/Auth/UnifiedAuthenticationController.php + - + message: '#^Cannot call method add\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: ../src/Validator/PaymentMethodValidator.php + - + message: '#^Cannot call method add\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: ../src/EventListener/PostSavePaymentMethodEventListener.php - message: '#^Cannot call method apply\(\) on mixed\.$#' From 58a10132957206183c3cd57f09f91a9266957e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Tue, 9 Sep 2025 11:08:44 +0200 Subject: [PATCH 17/18] PPSYL-163 - PHPStan Clean issues --- .../Auth/UnifiedAuthenticationController.php | 7 +++-- src/ApiClient/PayPlugApiClientFactory.php | 23 ++++++++++---- .../PostSavePaymentMethodEventListener.php | 30 ++++++++++++++----- .../IsCanSavePaymentMethodValidator.php | 2 +- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/Action/Admin/Auth/UnifiedAuthenticationController.php b/src/Action/Admin/Auth/UnifiedAuthenticationController.php index 69e0b888..86d0f65a 100644 --- a/src/Action/Admin/Auth/UnifiedAuthenticationController.php +++ b/src/Action/Admin/Auth/UnifiedAuthenticationController.php @@ -64,6 +64,7 @@ public function setupRedirection(Request $request): Response return new RedirectResponse(substr($header, 9)); } } + throw new \LogicException('No location header found'); } catch (\Throwable $e) { $this->logger->critical('Error while perform Payplug OAuth Setup redirection', ['message' => $e->getMessage(), 'exception' => $e]); return $this->handleOAuthError($request); @@ -74,13 +75,15 @@ public function setupRedirection(Request $request): Response public function oauthCallback(Request $request): Response { try { - $code = $request->query->get('code'); + $code = $request->query->getString('code'); + /** @var string $clientId */ $clientId = $request->getSession()->get('payplug_client_id'); + /** @var string $challenge */ $challenge = $request->getSession()->get('payplug_oauth_challenge'); $callback = $this->generateUrl('payplug_sylius_admin_auth_oauth_callback', [], UrlGeneratorInterface::ABSOLUTE_URL); $jwt = Authentication::generateJWTOneShot($code, $callback, $clientId, $challenge); - if ([] === $jwt || $jwt['httpStatus'] !== 200) { + if ([] === $jwt || $jwt['httpStatus'] !== 200 || !\is_array($jwt['httpResponse'])) { throw new BadRequestHttpException('Error while generating JWT'); } $paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id'); diff --git a/src/ApiClient/PayPlugApiClientFactory.php b/src/ApiClient/PayPlugApiClientFactory.php index 19a14b37..67c09ca7 100644 --- a/src/ApiClient/PayPlugApiClientFactory.php +++ b/src/ApiClient/PayPlugApiClientFactory.php @@ -51,17 +51,30 @@ private function getTokenForGatewayConfig(GatewayConfigInterface $gatewayConfig) if (true !== $config['live']) { // The live mode is not enabled, use client config for test mode $clientConfig = $config['test_client']; } + if (!\is_array($clientConfig)) { + throw new \LogicException('No client config found for ' . $gatewayConfig->getFactoryName() . '. Please renew your credentials in the PayPlug plugin configuration.'); + } + $cacheKey = sprintf('payplug_%s_api_key_%s', $gatewayConfig->getFactoryName(), $config['live'] === true ? 'live' : 'test'); + /** @var array $clientConfig */ return $this->cache->get($cacheKey, function (ItemInterface $item) use ($clientConfig) { - $response = Authentication::generateJWT($clientConfig['client_id'], $clientConfig['client_secret']); - if ([] === $response) { + $response = Authentication::generateJWT($clientConfig['client_id'] ?? '', $clientConfig['client_secret'] ?? ''); + if ([] === $response || !is_array($response['httpResponse'])) { + throw new \LogicException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); + } + + $accessToken = $response['httpResponse']['access_token']; + if (!is_string($accessToken)) { throw new \LogicException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.'); } + $expiresIn = $response['httpResponse']['expires_in']; + if (!is_int($expiresIn)) { + $expiresIn = 200; + } - $item->expiresAfter($response['httpResponse']['expires_in']); - /** @var string */ - return $response['httpResponse']['access_token']; + $item->expiresAfter($expiresIn); + return $accessToken; }); } } diff --git a/src/EventListener/PostSavePaymentMethodEventListener.php b/src/EventListener/PostSavePaymentMethodEventListener.php index e6a380bb..59657705 100644 --- a/src/EventListener/PostSavePaymentMethodEventListener.php +++ b/src/EventListener/PostSavePaymentMethodEventListener.php @@ -34,9 +34,7 @@ public function onCreate(ResourceControllerEvent $event): void return; } - $gateway = $paymentMethod->getGatewayConfig(); - if (null === $gateway || !\str_contains($gateway->getFactoryName(), 'payplug')) { - // A new payment method has been created but that is not a payplug one, do nothing + if (false === $this->isPayplugPaymentMethod($paymentMethod)) { return; } @@ -49,9 +47,8 @@ public function onUpdate(ResourceControllerEvent $event): void if (!$paymentMethod instanceof PaymentMethodInterface) { return; } - $gateway = $paymentMethod->getGatewayConfig(); - if (null === $gateway || !\str_contains($gateway->getFactoryName(), 'payplug')) { - // A new payment method has been created but that is not a payplug one, do nothing + + if (false === $this->isPayplugPaymentMethod($paymentMethod)) { return; } @@ -60,7 +57,7 @@ public function onUpdate(ResourceControllerEvent $event): void return; } - $isRenewal = $request->request->all('sylius_admin_payment_method')['gatewayConfig']['config']['renew_oauth'] ?? false; + $isRenewal = $request->request->all('sylius_admin_payment_method')['gatewayConfig']['config']['renew_oauth'] ?? false; // @phpstan-ignore-line $isRenewal = \filter_var($isRenewal, \FILTER_VALIDATE_BOOLEAN); if (true !== $isRenewal) { // No need to renew the oauth token, let's validate the payment method with already existing config @@ -84,7 +81,10 @@ private function startOAuth(PaymentMethodInterface $paymentMethod, ResourceContr $setupRedirection = $this->router->generate('payplug_sylius_admin_auth_setup_redirection', referenceType: RouterInterface::ABSOLUTE_URL); $oauthCallback = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', referenceType: RouterInterface::ABSOLUTE_URL); - /** @var string $payplugRedirectUrl */ + /** + * @var string $payplugRedirectUrl + * @phpstan-ignore-next-line -- Error of return type in Payplug SDK + */ $payplugRedirectUrl = Authentication::getRegisterUrl($setupRedirection, $oauthCallback); $event->setResponse(new RedirectResponse($payplugRedirectUrl)); } catch (\Throwable $e) { @@ -92,4 +92,18 @@ private function startOAuth(PaymentMethodInterface $paymentMethod, ResourceContr $request->getSession()->getFlashBag()->add('error', 'payplug_sylius_payplug_plugin.admin.oauth_setup_error'); } } + + private function isPayplugPaymentMethod(PaymentMethodInterface $paymentMethod): bool + { + $gateway = $paymentMethod->getGatewayConfig(); + if (null === $gateway) { + return false; + } + $factoryName = $gateway->getFactoryName(); + if (null === $factoryName) { + return false; + } + + return \str_contains($factoryName, 'payplug'); + } } diff --git a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php index f2b6fc24..ba287239 100644 --- a/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php +++ b/src/Gateway/Validator/Constraints/IsCanSavePaymentMethodValidator.php @@ -45,7 +45,7 @@ public function validate($value, Constraint $constraint): void return; } - $checker = new CanSavePayplugPaymentMethodChecker($this->apiClientFactory->create($factoryName, $value)); + $checker = new CanSavePayplugPaymentMethodChecker($this->apiClientFactory->createForPaymentMethod($value)); try { if (!$checker->isLive()) { $this->context->buildViolation(sprintf($constraint->noTestKeyMessage, $factoryName))->addViolation(); From 99cc1458ea6736ab11aa6b2e05a71f889f57bbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Tue, 9 Sep 2025 16:39:42 +0200 Subject: [PATCH 18/18] PPSYL-163 - Add Changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ RELEASE.md | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..7be32a5b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - Unreleased + +### Added +- Support for Sylius v2.0+ +- PHP 8.2+ compatibility +- Use Payment Request API from Sylius +- New Unified Authentication System (OAuth2) + +> [!IMPORTANT] +> Merchants will need to contact support to switch to the new authentication method. + +### Changed +- Plugin structure has been changed to follow the new Symfony bundle structure +- Front assets have been migrated to use Stimulus + +### Removed +- Drop Payum support +- Drop Sylius 1.x support +- Drop usage of Secret key - Use OAuth2 instead + +Please refer to [github releases](https://github.com/payplug/SyliusPayPlugPlugin/releases) for historical release information. + +--- + +For migration guides and upgrade instructions, see [UPGRADE.md](UPGRADE.md). +For contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/RELEASE.md b/RELEASE.md index 7f73a0af..6cc12383 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,6 @@ # Release Process Upon releasing a new version there are checks and updates to be made: -* Update plugin's version inside `src/PayPlugSyliusPayPlugPlugin.php` and `src/Resources/dev/package.json` +* Update plugin's version inside `src/PayPlugSyliusPayPlugPlugin.php` +* Ensure that the `CHANGELOG.md` is up to date with the changes made +* Ensure that the date of the release version is updated in `CHANGELOG.md`