From 597ca39ee1832bb9d5fb1d984241dbfaadb18fc7 Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:38:31 +0200 Subject: [PATCH 01/10] chore: declare homepage, authors and support links in composer.json --- composer.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/composer.json b/composer.json index af5aa53..3141162 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,18 @@ "payment-gateway" ], "license": "MIT", + "homepage": "https://github.com/CylleneDigital/SyliusAxeptaPlugin", + "authors": [ + { + "name": "Cyllene", + "homepage": "https://github.com/CylleneDigital" + } + ], + "support": { + "issues": "https://github.com/CylleneDigital/SyliusAxeptaPlugin/issues", + "source": "https://github.com/CylleneDigital/SyliusAxeptaPlugin", + "security": "https://github.com/CylleneDigital/SyliusAxeptaPlugin/security/policy" + }, "require": { "php": "^8.2", "ext-hash": "*", From 86f73db9c898262e8bec141aeae4199c0d963cf7 Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:48:01 +0200 Subject: [PATCH 02/10] refactor: define the services in PHP rather than XML Symfony 8 no longer loads the XML configuration format. Service ids, tags and arguments are unchanged; the tag attribute gateway-factory becomes gateway_factory, the key the XML loader already normalised it to. --- behat.yml.dist | 46 -------- config/services.php | 9 ++ config/services.xml | 13 --- config/services/controller.php | 27 +++++ config/services/controller.xml | 24 ---- config/services/form.php | 17 +++ config/services/form.xml | 19 ---- config/services/payment_request.php | 101 +++++++++++++++++ config/services/payment_request.xml | 104 ------------------ config/services/payum.php | 55 +++++++++ config/services/payum.xml | 59 ---------- config/services/protocol.php | 43 ++++++++ config/services/protocol.xml | 41 ------- config/services/renderer.php | 13 +++ config/services/renderer.xml | 14 --- .../CylleneDigitalSyliusAxeptaExtension.php | 6 +- tests/Behat/Resources/services.php | 72 ++++++++++++ tests/Behat/Resources/services.xml | 70 ------------ tests/Behat/Resources/suites.yml | 79 ------------- .../TestApplication/config/services_test.php | 6 +- 20 files changed, 344 insertions(+), 474 deletions(-) delete mode 100644 behat.yml.dist create mode 100644 config/services.php delete mode 100644 config/services.xml create mode 100644 config/services/controller.php delete mode 100644 config/services/controller.xml create mode 100644 config/services/form.php delete mode 100644 config/services/form.xml create mode 100644 config/services/payment_request.php delete mode 100644 config/services/payment_request.xml create mode 100644 config/services/payum.php delete mode 100644 config/services/payum.xml create mode 100644 config/services/protocol.php delete mode 100644 config/services/protocol.xml create mode 100644 config/services/renderer.php delete mode 100644 config/services/renderer.xml create mode 100644 tests/Behat/Resources/services.php delete mode 100644 tests/Behat/Resources/services.xml delete mode 100644 tests/Behat/Resources/suites.yml diff --git a/behat.yml.dist b/behat.yml.dist deleted file mode 100644 index 1dd2b92..0000000 --- a/behat.yml.dist +++ /dev/null @@ -1,46 +0,0 @@ -imports: - - vendor/sylius/sylius/src/Sylius/Behat/Resources/config/suites.yml - - tests/Behat/Resources/suites.yml - -default: - formatters: - pretty: - verbose: true - paths: false - snippets: false - - extensions: - DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~ - - FriendsOfBehat\MinkDebugExtension: - directory: etc/build - clean_start: false - screenshot: true - - Behat\MinkExtension: - files_path: "%paths.base%/vendor/sylius/sylius/src/Sylius/Behat/Resources/fixtures/" - base_url: "%env(BEHAT_BASE_URL)%" - default_session: symfony - javascript_session: chrome - sessions: - symfony: - symfony: ~ - chrome: - chrome: - api_url: http://127.0.0.1:9222 - validate_certificate: false - show_auto: false - - FriendsOfBehat\SymfonyExtension: - bootstrap: vendor/sylius/test-application/config/bootstrap.php - kernel: - class: Sylius\TestApplication\Kernel - environment: test - - FriendsOfBehat\VariadicExtension: ~ - - FriendsOfBehat\SuiteSettingsExtension: - paths: - - "features" - - SyliusLabs\SuiteTagsExtension: ~ diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..3bd582e --- /dev/null +++ b/config/services.php @@ -0,0 +1,9 @@ +import('services/*.php'); +}; diff --git a/config/services.xml b/config/services.xml deleted file mode 100644 index 3a4a0ff..0000000 --- a/config/services.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/config/services/controller.php b/config/services/controller.php new file mode 100644 index 0000000..9ade5b8 --- /dev/null +++ b/config/services/controller.php @@ -0,0 +1,27 @@ +services(); + + $services->set('cyllene_digital_sylius_axepta.provider.return_url', ReturnUrlProvider::class) + ->args([ + service('router'), + param('kernel.secret'), + ]); + + $services->set(ReturnFromPaymentPageAction::class, ReturnFromPaymentPageAction::class) + ->public() + ->args([ + service('sylius.repository.payment'), + service('cyllene_digital_sylius_axepta.provider.return_url'), + service('router'), + ]) + ->tag('controller.service_arguments'); +}; diff --git a/config/services/controller.xml b/config/services/controller.xml deleted file mode 100644 index ad8c2f1..0000000 --- a/config/services/controller.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - %kernel.secret% - - - - - - - - - - diff --git a/config/services/form.php b/config/services/form.php new file mode 100644 index 0000000..ae63062 --- /dev/null +++ b/config/services/form.php @@ -0,0 +1,17 @@ +services(); + + // The `type` attribute is an invariant: it selects this form for payment methods whose gateway + // is `axepta`, including those already in the database. + $services->set('cyllene_digital_sylius_axepta.form.type.gateway_configuration', AxeptaGatewayConfigurationType::class) + ->tag('sylius.gateway_configuration_type', ['type' => 'axepta', 'label' => 'Axepta - BNP Paribas']) + ->tag('form.type'); +}; diff --git a/config/services/form.xml b/config/services/form.xml deleted file mode 100644 index 7bbb92c..0000000 --- a/config/services/form.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/config/services/payment_request.php b/config/services/payment_request.php new file mode 100644 index 0000000..b864b1f --- /dev/null +++ b/config/services/payment_request.php @@ -0,0 +1,101 @@ +services(); + + $services->set('cyllene_digital_sylius_axepta.command_provider.payment_request', AxeptaActionsCommandProvider::class) + ->args([tagged_locator('cyllene_digital_sylius_axepta.command_provider.payment_request', indexAttribute: 'action')]) + ->tag('sylius.payment_request.command_provider', ['gateway_factory' => 'axepta']); + + $services->set('cyllene_digital_sylius_axepta.command_provider.payment_request.capture', CaptureCommandProvider::class) + ->tag('cyllene_digital_sylius_axepta.command_provider.payment_request', ['action' => 'capture']); + + $services->set('cyllene_digital_sylius_axepta.command_provider.payment_request.notify', NotifyCommandProvider::class) + ->tag('cyllene_digital_sylius_axepta.command_provider.payment_request', ['action' => 'notify']); + + $services->set('cyllene_digital_sylius_axepta.command_provider.payment_request.status', StatusCommandProvider::class) + ->tag('cyllene_digital_sylius_axepta.command_provider.payment_request', ['action' => 'status']); + + $services->set('cyllene_digital_sylius_axepta.command_handler.payment_request.capture', CaptureAxeptaPaymentRequestHandler::class) + ->args([ + service('sylius.provider.payment_request'), + service('cyllene_digital_sylius_axepta.provider.credentials'), + service('cyllene_digital_sylius_axepta.protocol.payment_page_request_builder'), + service(PaymentDescriptionProviderInterface::class), + service('sylius_abstraction.state_machine'), + service('router'), + service('cyllene_digital_sylius_axepta.provider.return_url'), + service('logger'), + ]) + ->tag('messenger.message_handler', ['bus' => 'sylius.payment_request.command_bus']) + ->tag('monolog.logger', ['channel' => '%cyllene_digital_sylius_axepta.logger_channel%']); + + $services->set('cyllene_digital_sylius_axepta.command_handler.payment_request.notify', NotifyAxeptaPaymentRequestHandler::class) + ->args([ + service('sylius.provider.payment_request'), + service('cyllene_digital_sylius_axepta.provider.credentials'), + service('cyllene_digital_sylius_axepta.protocol.notification_verifier'), + service('sylius_abstraction.state_machine'), + service('sylius_payum.resolver.payment_request.doctrine_proxy_object'), + service('logger'), + ]) + ->tag('messenger.message_handler', ['bus' => 'sylius.payment_request.command_bus']) + ->tag('monolog.logger', ['channel' => '%cyllene_digital_sylius_axepta.logger_channel%']); + + $services->set('cyllene_digital_sylius_axepta.command_handler.payment_request.status', StatusAxeptaPaymentRequestHandler::class) + ->args([ + service('sylius.provider.payment_request'), + service('sylius_abstraction.state_machine'), + ]) + ->tag('messenger.message_handler', ['bus' => 'sylius.payment_request.command_bus']); + + $services->set('cyllene_digital_sylius_axepta.provider.payment_request.http_response', AxeptaHttpResponseProvider::class) + ->args([service('cyllene_digital_sylius_axepta.renderer.auto_submit_form')]) + ->tag('sylius.payment_request.provider.http_response', ['gateway_factory' => 'axepta']); + + // Explicit priority: in the test environment Sylius registers a `DummyNotifyPaymentProvider` + // whose `supports()` answers true to everything and which returns the oldest payment in the + // database. Without a priority it captures our notifications and ties them to the wrong payment - + // silently, since the response stays 200. + $services->set('cyllene_digital_sylius_axepta.provider.payment_request.notify_payment', AxeptaNotifyPaymentProvider::class) + ->args([ + service('sylius.repository.payment'), + service('cyllene_digital_sylius_axepta.provider.credentials'), + service('cyllene_digital_sylius_axepta.protocol.notification_verifier'), + service(TransactionIdGeneratorInterface::class), + service('sylius.provider.payment_request.gateway_factory_name'), + 'axepta', + ]) + ->tag('sylius.payment_request.payment_notify_provider', ['priority' => 10]); + + // Decoration: the other gateways of the host application keep their response. + $services->set('cyllene_digital_sylius_axepta.provider.payment_request.notify_response', AxeptaNotifyResponseProvider::class) + ->decorate('sylius.provider.payment_request.notify_response') + ->args([ + service('.inner'), + service('sylius.provider.payment_request.gateway_factory_name'), + 'axepta', + ]); +}; diff --git a/config/services/payment_request.xml b/config/services/payment_request.xml deleted file mode 100644 index d0425ac..0000000 --- a/config/services/payment_request.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - axepta - - - - - - - - - axepta - - - diff --git a/config/services/payum.php b/config/services/payum.php new file mode 100644 index 0000000..a72940b --- /dev/null +++ b/config/services/payum.php @@ -0,0 +1,55 @@ +get()`: they must + // be public. A private action does not break at boot, it breaks on the first payment. + $services = $container->services()->defaults()->public(); + + $services->set('cyllene_digital_sylius_axepta.payum.gateway_factory_builder', AxeptaGatewayFactoryBuilder::class) + ->args([service('cyllene_digital_sylius_axepta.provider.credentials')]) + ->tag('payum.gateway_factory_builder', ['factory' => 'axepta']); + + $services->set('cyllene_digital_sylius_axepta.payum.action.convert_payment', ConvertPaymentAction::class) + ->tag('payum.action', ['factory' => 'axepta', 'alias' => 'cyllene_digital_sylius_axepta.convert_payment']); + + $services->set('cyllene_digital_sylius_axepta.payum.action.capture', CaptureAction::class) + ->args([ + service('payum'), + service('cyllene_digital_sylius_axepta.provider.return_url'), + service('cyllene_digital_sylius_axepta.protocol.payment_page_request_builder'), + service(PaymentDescriptionProviderInterface::class), + service('cyllene_digital_sylius_axepta.renderer.auto_submit_form'), + service('logger'), + ]) + ->tag('payum.action', ['factory' => 'axepta', 'alias' => 'cyllene_digital_sylius_axepta.capture']) + ->tag('monolog.logger', ['channel' => '%cyllene_digital_sylius_axepta.logger_channel%']); + + $services->set('cyllene_digital_sylius_axepta.payum.action.notify', NotifyAction::class) + ->args([ + service('request_stack'), + service(StateMachineInterface::class), + service('cyllene_digital_sylius_axepta.protocol.notification_verifier'), + service('logger'), + ]) + ->tag('payum.action', ['factory' => 'axepta', 'alias' => 'cyllene_digital_sylius_axepta.notify']) + ->tag('monolog.logger', ['channel' => '%cyllene_digital_sylius_axepta.logger_channel%']); + + $services->set('cyllene_digital_sylius_axepta.payum.action.status', StatusAction::class) + ->tag('payum.action', ['factory' => 'axepta', 'alias' => 'cyllene_digital_sylius_axepta.status']); + + $services->set('cyllene_digital_sylius_axepta.payum.action.resolve_next_route', ResolveNextRouteAction::class) + ->tag('payum.action', ['factory' => 'axepta', 'alias' => 'cyllene_digital_sylius_axepta.resolve_next_route']); +}; diff --git a/config/services/payum.xml b/config/services/payum.xml deleted file mode 100644 index 27b6b96..0000000 --- a/config/services/payum.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/services/protocol.php b/config/services/protocol.php new file mode 100644 index 0000000..b3c9423 --- /dev/null +++ b/config/services/protocol.php @@ -0,0 +1,43 @@ +services(); + + $services->set('cyllene_digital_sylius_axepta.crypto.blowfish_factory', BlowfishEcbFactory::class); + $services->alias(BlowfishEcbFactoryInterface::class, 'cyllene_digital_sylius_axepta.crypto.blowfish_factory'); + + $services->set('cyllene_digital_sylius_axepta.protocol.transaction_id_generator', PrefixedTransactionIdGenerator::class); + $services->alias(TransactionIdGeneratorInterface::class, 'cyllene_digital_sylius_axepta.protocol.transaction_id_generator'); + + $services->set('cyllene_digital_sylius_axepta.protocol.reference_provider', PaddedReferenceProvider::class); + $services->alias(ReferenceProviderInterface::class, 'cyllene_digital_sylius_axepta.protocol.reference_provider'); + + $services->set('cyllene_digital_sylius_axepta.protocol.payment_page_request_builder', PaymentPageRequestBuilder::class) + ->args([ + service(TransactionIdGeneratorInterface::class), + service(ReferenceProviderInterface::class), + ]); + + $services->set('cyllene_digital_sylius_axepta.protocol.notification_verifier', NotificationVerifier::class); + + $services->set('cyllene_digital_sylius_axepta.provider.credentials', CredentialsProvider::class) + ->args([ + service(BlowfishEcbFactoryInterface::class), + param('cyllene_digital_sylius_axepta.payment_page_url'), + ]); +}; diff --git a/config/services/protocol.xml b/config/services/protocol.xml deleted file mode 100644 index d9b2548..0000000 --- a/config/services/protocol.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - %cyllene_digital_sylius_axepta.payment_page_url% - - - diff --git a/config/services/renderer.php b/config/services/renderer.php new file mode 100644 index 0000000..e9f7c4e --- /dev/null +++ b/config/services/renderer.php @@ -0,0 +1,13 @@ +services() + ->set('cyllene_digital_sylius_axepta.renderer.auto_submit_form', AutoSubmitFormRenderer::class) + ->args([service('twig')]); +}; diff --git a/config/services/renderer.xml b/config/services/renderer.xml deleted file mode 100644 index 826b84c..0000000 --- a/config/services/renderer.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/src/DependencyInjection/CylleneDigitalSyliusAxeptaExtension.php b/src/DependencyInjection/CylleneDigitalSyliusAxeptaExtension.php index e1cd3de..89b8035 100644 --- a/src/DependencyInjection/CylleneDigitalSyliusAxeptaExtension.php +++ b/src/DependencyInjection/CylleneDigitalSyliusAxeptaExtension.php @@ -8,7 +8,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\Yaml\Yaml; /** @@ -29,9 +29,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('cyllene_digital_sylius_axepta.payment_page_url', $config['payment_page_url']); $container->setParameter('cyllene_digital_sylius_axepta.logger_channel', $config['logger_channel']); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../../config')); - $loader->load('services.xml'); + $loader->load('services.php'); } /** diff --git a/tests/Behat/Resources/services.php b/tests/Behat/Resources/services.php new file mode 100644 index 0000000..f98fe7c --- /dev/null +++ b/tests/Behat/Resources/services.php @@ -0,0 +1,72 @@ +services()->defaults()->public(); + + // Pages: they only add the Axepta configuration fields to the Sylius form. + $services->set('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.create', AxeptaCreatePage::class) + ->parent('sylius.behat.page.admin.crud.create') + ->args(['sylius_admin_payment_method_create']); + + $services->set('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.update', AxeptaUpdatePage::class) + ->parent('sylius.behat.page.admin.crud.update') + ->args(['sylius_admin_payment_method_update']); + + $services->set('cyllene_digital_sylius_axepta.behat.mocker.payment_page', AxeptaPaymentPageMocker::class) + ->args([service('cyllene_digital_sylius_axepta.provider.credentials')]); + + $services->set('cyllene_digital_sylius_axepta.behat.context.setup.axepta', AxeptaContext::class) + ->args([ + service('sylius.behat.shared_storage'), + service('sylius.factory.payment_method'), + service('sylius.factory.gateway_config'), + service('sylius.repository.payment_method'), + ]); + + // The Sylius context receives a hard-coded collection of factories, holding only `offline`: it + // therefore cannot build the creation URL for ours, and opens "/admin/payment-methods/new/" with + // no parameter - a 404. We redeclare it with the extended collection rather than writing our own + // creation steps. + $services->set('cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_methods', ManagingPaymentMethodsContext::class) + ->args([ + service('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.create'), + service('sylius.behat.page.admin.payment_method.index'), + service('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.update'), + service('sylius.behat.current_page_resolver'), + [ + 'offline' => 'Offline', + 'axepta' => 'Axepta - BNP Paribas', + ], + ]); + + $services->set('cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_method', ManagingAxeptaPaymentMethodContext::class) + ->args([ + service('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.create'), + service('cyllene_digital_sylius_axepta.behat.page.admin.payment_method.update'), + service('sylius.behat.current_page_resolver'), + service('sylius.behat.shared_storage'), + ]); + + $services->set('cyllene_digital_sylius_axepta.behat.context.ui.shop.payment', AxeptaPaymentContext::class) + ->args([ + service('sylius.behat.shared_storage'), + service('cyllene_digital_sylius_axepta.behat.mocker.payment_page'), + service('kernel'), + service('sylius.repository.payment_request'), + service('doctrine.orm.entity_manager'), + service('sylius.factory.payment_request'), + service('sylius.announcer.payment_request'), + ]); +}; diff --git a/tests/Behat/Resources/services.xml b/tests/Behat/Resources/services.xml deleted file mode 100644 index 34a4f68..0000000 --- a/tests/Behat/Resources/services.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - sylius_admin_payment_method_create - - - - sylius_admin_payment_method_update - - - - - - - - - - - - - - - - - - - - - Offline - Axepta - BNP Paribas - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Behat/Resources/suites.yml b/tests/Behat/Resources/suites.yml deleted file mode 100644 index 3b870a4..0000000 --- a/tests/Behat/Resources/suites.yml +++ /dev/null @@ -1,79 +0,0 @@ -default: - suites: - ui_managing_axepta_payment_method: - contexts: - - sylius.behat.context.hook.bad_gateway - - sylius.behat.context.hook.doctrine_orm - - sylius.behat.context.hook.session - - - sylius.behat.context.transform.lexical - - sylius.behat.context.transform.locale - - sylius.behat.context.transform.payment - - sylius.behat.context.transform.shared_storage - - - sylius.behat.context.setup.admin_security - - sylius.behat.context.setup.channel - - sylius.behat.context.setup.currency - - sylius.behat.context.setup.locale - - sylius.behat.context.setup.payment - - cyllene_digital_sylius_axepta.behat.context.setup.axepta - - - cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_methods - - sylius.behat.context.ui.admin.notification - - sylius.behat.context.ui.save - - cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_method - - filters: - tags: "@managing_axepta_payment_method&&@ui" - - ui_paying_with_axepta: - contexts: - - sylius.behat.context.hook.bad_gateway - - sylius.behat.context.hook.doctrine_orm - - sylius.behat.context.hook.guest_cart - - sylius.behat.context.hook.session - - - sylius.behat.context.transform.address - - sylius.behat.context.transform.channel - - sylius.behat.context.transform.country - - sylius.behat.context.transform.customer - - sylius.behat.context.transform.lexical - - sylius.behat.context.transform.locale - - sylius.behat.context.transform.order - - sylius.behat.context.transform.payment - - sylius.behat.context.transform.product - - sylius.behat.context.transform.product_variant - - sylius.behat.context.transform.shared_storage - - sylius.behat.context.transform.shipping_method - - sylius.behat.context.transform.tax_category - - sylius.behat.context.transform.user - - sylius.behat.context.transform.zone - - - sylius.behat.context.setup.cart - - sylius.behat.context.setup.channel - - sylius.behat.context.setup.checkout - - sylius.behat.context.setup.checkout.address - - sylius.behat.context.setup.currency - - sylius.behat.context.setup.geographical - - sylius.behat.context.setup.locale - - sylius.behat.context.setup.order - - sylius.behat.context.setup.payment - - sylius.behat.context.setup.product - - sylius.behat.context.setup.shipping - - sylius.behat.context.setup.shop_security - - sylius.behat.context.setup.user - - sylius.behat.context.setup.zone - - cyllene_digital_sylius_axepta.behat.context.setup.axepta - - - sylius.behat.context.ui.shop.cart - - sylius.behat.context.ui.shop.checkout - - sylius.behat.context.ui.shop.checkout.addressing - - sylius.behat.context.ui.shop.checkout.complete - - sylius.behat.context.ui.shop.checkout.order_details - - sylius.behat.context.ui.shop.checkout.payment - - sylius.behat.context.ui.shop.checkout.shipping - - sylius.behat.context.ui.shop.checkout.thank_you - - cyllene_digital_sylius_axepta.behat.context.ui.shop.payment - - filters: - tags: "@paying_with_axepta&&@ui" diff --git a/tests/TestApplication/config/services_test.php b/tests/TestApplication/config/services_test.php index a6d4814..88138be 100644 --- a/tests/TestApplication/config/services_test.php +++ b/tests/TestApplication/config/services_test.php @@ -6,7 +6,9 @@ return function (ContainerConfigurator $container) { if (str_starts_with($container->env(), 'test')) { - $container->import('../../../vendor/sylius/sylius/src/Sylius/Behat/Resources/config/services.xml'); - $container->import('@CylleneDigitalSyliusAxeptaPlugin/tests/Behat/Resources/services.xml'); + // Sylius 2.3 ships its Behat services in PHP; 2.1 and 2.2 only in XML, which Symfony 8 cannot load. + $syliusBehatServices = dirname(__DIR__, 3) . '/vendor/sylius/sylius/src/Sylius/Behat/Resources/config/services'; + $container->import(is_file($syliusBehatServices . '.php') ? $syliusBehatServices . '.php' : $syliusBehatServices . '.xml'); + $container->import('@CylleneDigitalSyliusAxeptaPlugin/tests/Behat/Resources/services.php'); } }; From 540469a83f2bf039d34143c15592e07d9cf0948e Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:48:01 +0200 Subject: [PATCH 03/10] test: move the Behat configuration to PHP and the steps to attributes Behat 4, the version Symfony 8 requires, only reads PHP configuration and no longer reads annotations. The Sylius suites are no longer imported: 2.3 ships them in PHP only, and the plugin suites only use the Sylius contexts. --- .gitattributes | 2 +- .gitignore | 2 +- behat.dist.php | 64 +++++++++++++ tests/Behat/Context/Setup/AxeptaContext.php | 11 +-- .../ManagingAxeptaPaymentMethodContext.php | 29 ++---- .../Context/Ui/Shop/AxeptaPaymentContext.php | 41 +++------ tests/Behat/Resources/suites.php | 92 +++++++++++++++++++ 7 files changed, 184 insertions(+), 57 deletions(-) create mode 100644 behat.dist.php create mode 100644 tests/Behat/Resources/suites.php diff --git a/.gitattributes b/.gitattributes index 33c243c..787d7b1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,7 +6,7 @@ /AGENTS.md export-ignore /CLAUDE.md export-ignore /Makefile export-ignore -/behat.yml.dist export-ignore +/behat.dist.php export-ignore /compose.override.dist.yml export-ignore /compose.yml export-ignore /ecs.php export-ignore diff --git a/.gitignore b/.gitignore index d584fdb..a8d5f32 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ /etc/build/* !/etc/build/.gitignore -/behat.yml +/behat.php /phpunit.xml /compose.override.yml diff --git a/behat.dist.php b/behat.dist.php new file mode 100644 index 0000000..0d9cd56 --- /dev/null +++ b/behat.dist.php @@ -0,0 +1,64 @@ + [ + 'formatters' => [ + 'pretty' => [ + 'verbose' => true, + 'paths' => false, + 'snippets' => false, + ], + ], + 'extensions' => [ + ChromeExtension::class => null, + MinkDebugExtension::class => [ + 'directory' => 'etc/build', + 'clean_start' => false, + 'screenshot' => true, + ], + MinkExtension::class => [ + 'files_path' => '%paths.base%/vendor/sylius/sylius/src/Sylius/Behat/Resources/fixtures/', + 'base_url' => '%env(BEHAT_BASE_URL)%', + 'default_session' => 'symfony', + 'javascript_session' => 'chrome', + 'sessions' => [ + 'symfony' => [ + 'symfony' => null, + ], + 'chrome' => [ + 'chrome' => [ + 'api_url' => 'http://127.0.0.1:9222', + 'validate_certificate' => false, + ], + ], + ], + 'show_auto' => false, + ], + SymfonyExtension::class => [ + 'bootstrap' => 'vendor/sylius/test-application/config/bootstrap.php', + 'kernel' => [ + 'class' => 'Sylius\TestApplication\Kernel', + 'environment' => 'test', + ], + ], + VariadicExtension::class => null, + SuiteSettingsExtension::class => [ + 'paths' => ['features'], + ], + SuiteTagsExtension::class => null, + ], + ], +]))->import('tests/Behat/Resources/suites.php'); diff --git a/tests/Behat/Context/Setup/AxeptaContext.php b/tests/Behat/Context/Setup/AxeptaContext.php index 04c4fec..e7b1517 100644 --- a/tests/Behat/Context/Setup/AxeptaContext.php +++ b/tests/Behat/Context/Setup/AxeptaContext.php @@ -5,6 +5,7 @@ namespace Tests\CylleneDigital\SyliusAxeptaPlugin\Behat\Context\Setup; use Behat\Behat\Context\Context; +use Behat\Step\Given; use CylleneDigital\SyliusAxeptaPlugin\Payum\AxeptaGatewayFactory; use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; @@ -39,18 +40,14 @@ public function __construct( ) { } - /** - * @Given the store has a payment method :name with a code :code and Axepta gateway - * @Given /^the store has a payment method "([^"]+)" with a code "([^"]+)" and Axepta gateway in (test mode)$/ - */ + #[Given('the store has a payment method :name with a code :code and Axepta gateway')] + #[Given('/^the store has a payment method "([^"]+)" with a code "([^"]+)" and Axepta gateway in (test mode)$/')] public function theStoreHasAnAxeptaPaymentMethod(string $name, string $code, ?string $testMode = null): void { $this->createPaymentMethod($name, $code, null !== $testMode, usePayum: true); } - /** - * @Given the store has a payment method :name with a code :code and Axepta gateway on the PaymentRequest path - */ + #[Given('the store has a payment method :name with a code :code and Axepta gateway on the PaymentRequest path')] public function theStoreHasAnAxeptaPaymentMethodOnThePaymentRequestPath(string $name, string $code): void { $this->createPaymentMethod($name, $code, false, usePayum: false); diff --git a/tests/Behat/Context/Ui/Admin/ManagingAxeptaPaymentMethodContext.php b/tests/Behat/Context/Ui/Admin/ManagingAxeptaPaymentMethodContext.php index a49856a..11c613c 100644 --- a/tests/Behat/Context/Ui/Admin/ManagingAxeptaPaymentMethodContext.php +++ b/tests/Behat/Context/Ui/Admin/ManagingAxeptaPaymentMethodContext.php @@ -5,6 +5,8 @@ namespace Tests\CylleneDigital\SyliusAxeptaPlugin\Behat\Context\Ui\Admin; use Behat\Behat\Context\Context; +use Behat\Step\Then; +use Behat\Step\When; use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface; use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -26,9 +28,7 @@ public function __construct( ) { } - /** - * @When I configure it with merchant id :merchantId, hmac key :hmacKey and blowfish key :blowfishKey - */ + #[When('I configure it with merchant id :merchantId, hmac key :hmacKey and blowfish key :blowfishKey')] public function iConfigureItWithCredentials(string $merchantId, string $hmacKey, string $blowfishKey): void { $page = $this->currentPage(); @@ -39,17 +39,13 @@ public function iConfigureItWithCredentials(string $merchantId, string $hmacKey, $page->specifyBlowfishKey($blowfishKey); } - /** - * @When I enable its test mode - */ + #[When('I enable its test mode')] public function iEnableItsTestMode(): void { $this->updatePage->enableTestMode(); } - /** - * @Then this payment method should be in test mode - */ + #[Then('this payment method should be in test mode')] public function thisPaymentMethodShouldBeInTestMode(): void { Assert::true( @@ -64,9 +60,8 @@ public function thisPaymentMethodShouldBeInTestMode(): void * `PasswordType` never redisplays its value: both key fields are blank on screen, and that is * expected. What matters is that saving without retyping them **does not erase them**, which is * what this step checks, by reading the stored configuration back. - * - * @Then this payment method should still have its Axepta keys */ + #[Then('this payment method should still have its Axepta keys')] public function thisPaymentMethodShouldStillHaveItsKeys(): void { Assert::true( @@ -89,25 +84,19 @@ public function thisPaymentMethodShouldStillHaveItsKeys(): void } } - /** - * @Then I should be notified that the merchant id is required - */ + #[Then('I should be notified that the merchant id is required')] public function iShouldBeNotifiedThatTheMerchantIdIsRequired(): void { $this->assertValidationMessage('merchant ID'); } - /** - * @Then I should be notified that the hmac key is required - */ + #[Then('I should be notified that the hmac key is required')] public function iShouldBeNotifiedThatTheHmacKeyIsRequired(): void { $this->assertValidationMessage('HMAC key'); } - /** - * @Then I should be notified that the blowfish key is required - */ + #[Then('I should be notified that the blowfish key is required')] public function iShouldBeNotifiedThatTheBlowfishKeyIsRequired(): void { $this->assertValidationMessage('Blowfish key'); diff --git a/tests/Behat/Context/Ui/Shop/AxeptaPaymentContext.php b/tests/Behat/Context/Ui/Shop/AxeptaPaymentContext.php index 7b8e33b..a9a7680 100644 --- a/tests/Behat/Context/Ui/Shop/AxeptaPaymentContext.php +++ b/tests/Behat/Context/Ui/Shop/AxeptaPaymentContext.php @@ -5,6 +5,9 @@ namespace Tests\CylleneDigital\SyliusAxeptaPlugin\Behat\Context\Ui\Shop; use Behat\Behat\Context\Context; +use Behat\Step\Given; +use Behat\Step\Then; +use Behat\Step\When; use CylleneDigital\SyliusAxeptaPlugin\Axepta\Protocol\AxeptaCredentials; use Doctrine\ORM\EntityManagerInterface; use Sylius\Behat\Service\SharedStorageInterface; @@ -47,9 +50,8 @@ public function __construct( * * The step is explicit rather than inferred from the checkout: what we want to exercise here is * the notification, not walking the checkout, which Sylius already covers. - * - * @Given the shop has sent the payment request to the bank */ + #[Given('the shop has sent the payment request to the bank')] public function theShopHasSentThePaymentRequestToTheBank(): void { $order = $this->order(); @@ -67,9 +69,7 @@ public function theShopHasSentThePaymentRequestToTheBank(): void $this->paymentRequestAnnouncer->dispatchPaymentRequestCommand($paymentRequest); } - /** - * @Then the payment request should target the Axepta payment page - */ + #[Then('the payment request should target the Axepta payment page')] public function thePaymentRequestShouldTargetTheAxeptaPaymentPage(): void { Assert::same($this->captureResponseData()['url'] ?? null, AxeptaCredentials::DEFAULT_PAYMENT_PAGE_URL); @@ -78,9 +78,8 @@ public function thePaymentRequestShouldTargetTheAxeptaPaymentPage(): void /** * The fields go out as such in the self-submitting form; their rendering is covered by the * integration tests. - * - * @Then it should carry a signed payload */ + #[Then('it should carry a signed payload')] public function itShouldCarryASignedPayload(): void { $fields = $this->captureResponseData()['fields'] ?? []; @@ -100,9 +99,7 @@ private function captureResponseData(): array return $this->capture()->getResponseData(); } - /** - * @When /^the bank notifies the shop that the payment (succeeded|was refused)$/ - */ + #[When('/^the bank notifies the shop that the payment (succeeded|was refused)$/')] public function theBankNotifiesTheShop(string $outcome): void { $notification = $this->mocker->notification( @@ -116,9 +113,7 @@ public function theBankNotifiesTheShop(string $outcome): void $this->sendNotification($notification); } - /** - * @When the bank sends the same notification again - */ + #[When('the bank sends the same notification again')] public function theBankSendsTheSameNotificationAgain(): void { /** @var array $notification */ @@ -127,17 +122,13 @@ public function theBankSendsTheSameNotificationAgain(): void $this->sendNotification($notification); } - /** - * @When someone sends a notification with an invalid signature - */ + #[When('someone sends a notification with an invalid signature')] public function someoneSendsAForgedNotification(): void { $this->sendNotification($this->mocker->forgedNotification($this->paymentMethod(), $this->transactionId())); } - /** - * @Then the shop should have answered the bank without an error - */ + #[Then('the shop should have answered the bank without an error')] public function theShopShouldHaveAnsweredWithoutError(): void { $status = $this->sharedStorage->get('axepta_notification_status'); @@ -148,25 +139,19 @@ public function theShopShouldHaveAnsweredWithoutError(): void )); } - /** - * @Then my order should be paid - */ + #[Then('my order should be paid')] public function myOrderShouldBePaid(): void { Assert::same($this->reloadPaymentState(), OrderPaymentStates::STATE_PAID); } - /** - * @Then my order should not be paid - */ + #[Then('my order should not be paid')] public function myOrderShouldNotBePaid(): void { Assert::notSame($this->reloadPaymentState(), OrderPaymentStates::STATE_PAID); } - /** - * @Then I should be able to pay for my order again - */ + #[Then('I should be able to pay for my order again')] public function iShouldBeAbleToPayForMyOrderAgain(): void { Assert::notSame( diff --git a/tests/Behat/Resources/suites.php b/tests/Behat/Resources/suites.php new file mode 100644 index 0000000..2145e01 --- /dev/null +++ b/tests/Behat/Resources/suites.php @@ -0,0 +1,92 @@ + [ + 'suites' => [ + 'ui_managing_axepta_payment_method' => [ + 'contexts' => [ + 'sylius.behat.context.hook.bad_gateway', + 'sylius.behat.context.hook.doctrine_orm', + 'sylius.behat.context.hook.session', + + 'sylius.behat.context.transform.lexical', + 'sylius.behat.context.transform.locale', + 'sylius.behat.context.transform.payment', + 'sylius.behat.context.transform.shared_storage', + + 'sylius.behat.context.setup.admin_security', + 'sylius.behat.context.setup.channel', + 'sylius.behat.context.setup.currency', + 'sylius.behat.context.setup.locale', + 'sylius.behat.context.setup.payment', + 'cyllene_digital_sylius_axepta.behat.context.setup.axepta', + + 'cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_methods', + 'sylius.behat.context.ui.admin.notification', + 'sylius.behat.context.ui.save', + 'cyllene_digital_sylius_axepta.behat.context.ui.admin.managing_payment_method', + ], + 'filters' => [ + 'tags' => '@managing_axepta_payment_method&&@ui', + ], + ], + 'ui_paying_with_axepta' => [ + 'contexts' => [ + 'sylius.behat.context.hook.bad_gateway', + 'sylius.behat.context.hook.doctrine_orm', + 'sylius.behat.context.hook.guest_cart', + 'sylius.behat.context.hook.session', + + 'sylius.behat.context.transform.address', + 'sylius.behat.context.transform.channel', + 'sylius.behat.context.transform.country', + 'sylius.behat.context.transform.customer', + 'sylius.behat.context.transform.lexical', + 'sylius.behat.context.transform.locale', + 'sylius.behat.context.transform.order', + 'sylius.behat.context.transform.payment', + 'sylius.behat.context.transform.product', + 'sylius.behat.context.transform.product_variant', + 'sylius.behat.context.transform.shared_storage', + 'sylius.behat.context.transform.shipping_method', + 'sylius.behat.context.transform.tax_category', + 'sylius.behat.context.transform.user', + 'sylius.behat.context.transform.zone', + + 'sylius.behat.context.setup.cart', + 'sylius.behat.context.setup.channel', + 'sylius.behat.context.setup.checkout', + 'sylius.behat.context.setup.checkout.address', + 'sylius.behat.context.setup.currency', + 'sylius.behat.context.setup.geographical', + 'sylius.behat.context.setup.locale', + 'sylius.behat.context.setup.order', + 'sylius.behat.context.setup.payment', + 'sylius.behat.context.setup.product', + 'sylius.behat.context.setup.shipping', + 'sylius.behat.context.setup.shop_security', + 'sylius.behat.context.setup.user', + 'sylius.behat.context.setup.zone', + 'cyllene_digital_sylius_axepta.behat.context.setup.axepta', + + 'sylius.behat.context.ui.shop.cart', + 'sylius.behat.context.ui.shop.checkout', + 'sylius.behat.context.ui.shop.checkout.addressing', + 'sylius.behat.context.ui.shop.checkout.complete', + 'sylius.behat.context.ui.shop.checkout.order_details', + 'sylius.behat.context.ui.shop.checkout.payment', + 'sylius.behat.context.ui.shop.checkout.shipping', + 'sylius.behat.context.ui.shop.checkout.thank_you', + 'cyllene_digital_sylius_axepta.behat.context.ui.shop.payment', + ], + 'filters' => [ + 'tags' => '@paying_with_axepta&&@ui', + ], + ], + ], + ], +]); From db9f476b0781e1556e6fa232fe4592f85582988a Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:48:01 +0200 Subject: [PATCH 04/10] feat: support Sylius 2.3 and Symfony 8 Sylius 2.3 passes a Request instead of a RequestConfiguration to the HTTP response providers: the Axepta provider types that argument as object, which satisfies both signatures since it never reads it. The abandoned friends-of-behat Mink forks give way to behat/mink and behat/mink-browserkit-driver. --- composer.json | 48 +++++++++---------- .../Provider/AxeptaHttpResponseProvider.php | 8 ++-- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 3141162..cc70cdf 100644 --- a/composer.json +++ b/composer.json @@ -30,28 +30,27 @@ "payum/core": "^1.7", "psr/log": "^3.0", "sylius/sylius": "^2.1", - "symfony/config": "^6.4 || ^7.4", - "symfony/dependency-injection": "^6.4 || ^7.4", - "symfony/form": "^6.4 || ^7.4", - "symfony/http-foundation": "^6.4 || ^7.4", - "symfony/http-kernel": "^6.4 || ^7.4", - "symfony/messenger": "^6.4 || ^7.4", - "symfony/routing": "^6.4 || ^7.4", + "symfony/config": "^6.4 || ^7.4 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.4 || ^8.0", + "symfony/form": "^6.4 || ^7.4 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.4 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/messenger": "^6.4 || ^7.4 || ^8.0", + "symfony/routing": "^6.4 || ^7.4 || ^8.0", "symfony/service-contracts": "^3.0", - "symfony/validator": "^6.4 || ^7.4", - "symfony/yaml": "^6.4 || ^7.4", + "symfony/validator": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0", "twig/twig": "^3.10" }, "require-dev": { - "behat/behat": "^3.16", + "behat/behat": "^3.16 || ^4.0", + "behat/mink": "^1.11", + "behat/mink-browserkit-driver": "^2.2", "dbrekelmans/bdi": "^1.4", - "dmore/behat-chrome-extension": "^1.4", "dmore/chrome-mink-driver": "^2.9", - "friends-of-behat/mink": "^1.11", - "friends-of-behat/mink-browserkit-driver": "^1.6", "friends-of-behat/mink-debug-extension": "^2.1", - "friends-of-behat/mink-extension": "^2.7", - "friends-of-behat/page-object-extension": "^0.3", + "friends-of-behat/mink-extension": "^2.7 || ^3.0@alpha", + "friends-of-behat/page-object-extension": "^0.3 || ^0.4", "friends-of-behat/suite-settings-extension": "^1.1", "friends-of-behat/symfony-extension": "^2.6", "friends-of-behat/variadic-extension": "^1.6", @@ -60,16 +59,17 @@ "phpstan/phpstan-doctrine": "^2.0", "phpstan/phpstan-webmozart-assert": "^2.0", "phpunit/phpunit": "^10.5", + "sylius-labs/behat-chrome-extension": "^1.5", "sylius-labs/coding-standard": "^4.4", "sylius-labs/suite-tags-extension": "~0.2", - "sylius/test-application": "^2.0.0@alpha", - "symfony/browser-kit": "^6.4 || ^7.4", - "symfony/debug-bundle": "^6.4 || ^7.4", - "symfony/dotenv": "^6.4 || ^7.4", - "symfony/http-client": "^6.4 || ^7.4", - "symfony/intl": "^6.4 || ^7.4", - "symfony/runtime": "^6.4 || ^7.4", - "symfony/web-profiler-bundle": "^6.4 || ^7.4", + "sylius/test-application": "~2.1.0@alpha || ~2.2.0@alpha || ~2.3.0@alpha", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/debug-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/http-client": "^6.4 || ^7.4 || ^8.0", + "symfony/intl": "^6.4 || ^7.4 || ^8.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.4 || ^8.0", "symfony/webpack-encore-bundle": "^2.2" }, "config": { @@ -87,7 +87,7 @@ "dev-main": "1.0.x-dev" }, "symfony": { - "require": "^7.4" + "require": "^7.4 || ^8.0" }, "public-dir": "vendor/sylius/test-application/public" }, diff --git a/src/PaymentRequest/Provider/AxeptaHttpResponseProvider.php b/src/PaymentRequest/Provider/AxeptaHttpResponseProvider.php index 3b3a3b5..9487a88 100644 --- a/src/PaymentRequest/Provider/AxeptaHttpResponseProvider.php +++ b/src/PaymentRequest/Provider/AxeptaHttpResponseProvider.php @@ -7,7 +7,6 @@ use CylleneDigital\SyliusAxeptaPlugin\Axepta\Protocol\PaymentPageRequest; use CylleneDigital\SyliusAxeptaPlugin\Renderer\AutoSubmitFormRenderer; use Sylius\Bundle\PaymentBundle\Provider\HttpResponseProviderInterface; -use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration; use Sylius\Component\Payment\Model\PaymentRequestInterface; use Symfony\Component\HttpFoundation\Response; @@ -17,6 +16,9 @@ * It is the same template as on the Payum path: the customer sees the same page whichever * mechanism the shop settled on. * + * The first argument is an `object` on purpose: Sylius 2.1 and 2.2 pass a `RequestConfiguration`, + * Sylius 2.3 a `Request`. Neither is read here, and the wider type satisfies both interfaces. + * * @experimental * * @internal @@ -27,13 +29,13 @@ public function __construct(private AutoSubmitFormRenderer $formRenderer) { } - public function supports(RequestConfiguration $requestConfiguration, PaymentRequestInterface $paymentRequest): bool + public function supports(object $request, PaymentRequestInterface $paymentRequest): bool { return PaymentRequestInterface::ACTION_CAPTURE === $paymentRequest->getAction() && [] !== $paymentRequest->getResponseData(); } - public function getResponse(RequestConfiguration $requestConfiguration, PaymentRequestInterface $paymentRequest): Response + public function getResponse(object $request, PaymentRequestInterface $paymentRequest): Response { $responseData = $paymentRequest->getResponseData(); From 5697a64b0c958c85d15caa70a21406836ce7958a Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:48:52 +0200 Subject: [PATCH 05/10] ci: cover Sylius 2.3 and Symfony 8, pin the runner and the third-party actions Before Sylius 2.3 the matrix keeps Behat 3 and Doctrine ORM below 3.7. PHPStan and ECS only run on the primary job, now Sylius 2.3 / PHP 8.4 / Symfony 7.4. --- .github/workflows/build.yaml | 59 ++++++++++++++++++++++++++++----- .github/workflows/security.yaml | 8 +++-- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c03bcde..1ab5f14 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,9 +28,15 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: tests: - runs-on: ubuntu-latest + # Pinned rather than `ubuntu-latest`: the build action is only known to work on this image. + runs-on: ubuntu-24.04 + # A stuck browser would otherwise hold the runner for the 6-hour default. + timeout-minutes: 30 name: "Sylius ${{ matrix.sylius }}, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, ${{ matrix.database }}" @@ -42,27 +48,44 @@ jobs: # dependency rather than on our code, move the line to `continue-on-error` with a # comment - do not drop it from the matrix. php: ["8.2", "8.3", "8.4", "8.5"] - symfony: ["^6.4", "^7.4"] + symfony: ["^6.4", "^7.4", "^8.0"] # The floor is 2.1 rather than 2.0 for two reasons lying with Sylius itself. Its # Behat cart context did not restore the security token before 2.1, so a scenario # logging the customer in before filling their cart cannot pass on 2.0. And 2.0 # pins Behat versions predating PHP 8.5, which Composer therefore cannot install # together. - sylius: ["~2.1.0", "~2.2.0"] + sylius: ["~2.1.0", "~2.2.0", "~2.3.0"] # The plugin creates no table and writes no query: a single database is enough on # the main matrix. The cycles saved cover 4 PHP versions. database: ["mysql:8.4"] node: ["22.x"] + # Sylius 2.3 needs PHP 8.3 and is the only one to support Symfony 8, which needs + # PHP 8.4. Symfony 6.4 is covered by Sylius 2.1 and 2.2: running it on 2.3 as well + # would cost three jobs for a combination no new project picks. + exclude: + - sylius: "~2.1.0" + symfony: "^8.0" + - sylius: "~2.2.0" + symfony: "^8.0" + - sylius: "~2.3.0" + symfony: "^6.4" + - sylius: "~2.3.0" + php: "8.2" + - symfony: "^8.0" + php: "8.3" include: + # The build action declares MariaDB with a bare version, which DBAL reads as + # MySQL: that is what the Sylius 2.3 migrations need, they skip a database + # declared as MariaDB. - php: "8.4" symfony: "^7.4" - sylius: "~2.2.0" + sylius: "~2.3.0" database: "mariadb:11.4" node: "22.x" - php: "8.4" symfony: "^7.4" - sylius: "~2.2.0" + sylius: "~2.3.0" database: "postgres:17" node: "22.x" @@ -73,9 +96,22 @@ jobs: - uses: actions/checkout@v7 + # Sylius 2.1 / 2.2 declare their Behat steps in annotations, which Behat 4 no longer reads; + # Composer would still pick Behat 4 for them. + - + name: Keep Behat 3 before Sylius 2.3 + if: matrix.sylius != '~2.3.0' + run: composer require --dev --no-update --no-interaction "behat/behat:^3.22" "friends-of-behat/mink-extension:^2.7" + + # Sylius 2.1 mappings fail ORM 3.7 validation (ShipmentUnit#shipment vs Shipment#units). + - + name: Keep Doctrine ORM below 3.7 before Sylius 2.3 + if: matrix.sylius != '~2.3.0' + run: composer require --no-update --no-interaction "doctrine/orm:^2.20 || >=3.6 <3.7" + - name: Build Sylius Test Application - uses: SyliusLabs/BuildTestAppAction@v4 + uses: SyliusLabs/BuildTestAppAction@fbdfc158a99f540fc3bbe554b46120d14444f2f4 # v4.3.0 with: # The Behat scenarios open no browser - `symfony` session, no `@javascript` # tag - but the admin pages render the Webpack Encore entries: without a @@ -86,6 +122,9 @@ jobs: database: "${{ matrix.database }}" node_version: "${{ matrix.node }}" php_version: "${{ matrix.php }}" + # The PHP-FPM worker behind `symfony server:start` segfaulted at random under OPcache + # (Behat 502, "child exited on signal 11"): Behat does not need it. + opcache_enable: "no" sylius_version: "${{ matrix.sylius }}" symfony_version: "${{ matrix.symfony }}" @@ -103,7 +142,7 @@ jobs: # most frequent class of bug. - name: Validate container - run: vendor/bin/console lint:container + run: vendor/bin/console lint:container --env=test # Sylius encrypts the gateway configuration: without this key, every test writing to # the database fails on "Cannot read keyfile", an error that does not say its name. @@ -111,12 +150,16 @@ jobs: name: Generate the payment encryption key run: vendor/bin/console sylius:payment:generate-key + # Static analysis and the coding standard do not depend on the database nor on the PHP + # version line under test, so they only run once, on the primary job. - name: Run PHPStan + if: matrix.php == '8.4' && matrix.symfony == '^7.4' && matrix.sylius == '~2.3.0' && matrix.database == 'mysql:8.4' run: vendor/bin/phpstan analyse --ansi --no-progress - name: Run ECS + if: matrix.php == '8.4' && matrix.symfony == '^7.4' && matrix.sylius == '~2.3.0' && matrix.database == 'mysql:8.4' run: vendor/bin/ecs check --ansi --no-progress-bar - @@ -148,7 +191,7 @@ jobs: # required check counts as satisfied - the rule would let through exactly what it guards # against. build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: "Build complete" needs: tests if: always() diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 9d61239..4762ca4 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -10,9 +10,13 @@ on: cron: "0 7 * * 1" # Every Monday at 7am workflow_dispatch: ~ +permissions: + contents: read + jobs: audit: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 15 name: "Composer audit" @@ -22,7 +26,7 @@ jobs: - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: "8.4" coverage: none From 151467b21a0926f3ae34bd15f61a9df177d7138e Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:55:25 +0200 Subject: [PATCH 06/10] chore: run the development stack on PHP 8.4 and Node 22, as the CI does --- compose.override.dist.yml | 4 ++-- compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compose.override.dist.yml b/compose.override.dist.yml index f8fccb6..d9e685e 100644 --- a/compose.override.dist.yml +++ b/compose.override.dist.yml @@ -1,6 +1,6 @@ services: php: - image: ghcr.io/sylius/sylius-php:8.3-fixuid-xdebug-alpine + image: ghcr.io/sylius/sylius-php:8.4-fixuid-xdebug-alpine user: ${DOCKER_USER:-1000:1000} depends_on: mysql: @@ -51,7 +51,7 @@ services: ports: - "80:80" nodejs: - image: node:${NODE_VERSION:-20}-alpine + image: node:${NODE_VERSION:-22}-alpine user: ${DOCKER_USER:-1000:1000} working_dir: /srv/sylius entrypoint: [ "/bin/sh","-c" ] diff --git a/compose.yml b/compose.yml index 067908b..67fe013 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,6 @@ services: php: - image: ghcr.io/sylius/sylius-php:8.3-alpine + image: ghcr.io/sylius/sylius-php:8.4-alpine mysql: image: mysql:8.4 platform: linux/amd64 From d6e35149efb09f255a5eda0d4f30362edfb33b6f Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:55:25 +0200 Subject: [PATCH 07/10] feat: let the payment redirect page follow the browser colour scheme --- templates/shop/payment/axepta_redirect.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/shop/payment/axepta_redirect.html.twig b/templates/shop/payment/axepta_redirect.html.twig index fad0b8d..e30974a 100644 --- a/templates/shop/payment/axepta_redirect.html.twig +++ b/templates/shop/payment/axepta_redirect.html.twig @@ -1,7 +1,7 @@ {# Transition page between the shop and the hosted payment page. - Surchargeable : `templates/bundles/CylleneDigitalSyliusAxeptaPlugin/shop/payment/axepta_redirect.html.twig`. + Overridable: `templates/bundles/CylleneDigitalSyliusAxeptaPlugin/shop/payment/axepta_redirect.html.twig`. Must stay usable without JavaScript - the `noscript` button is not decorative, it is the only way to pay for a hardened browser or a screen reader. @@ -11,6 +11,8 @@ + {# Unstyled page: without it, a dark-mode browser flashes it white before the bank's page. #} + {{ 'cyllene_digital_sylius_axepta.ui.redirecting'|trans }} From 71bf930525cbbdc01e2494d5c489057f4a7b2755 Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 17:56:01 +0200 Subject: [PATCH 08/10] docs: announce Sylius 2.3 and Symfony 8 support --- CLAUDE.md | 12 ++++++++++-- CONTRIBUTING.md | 2 +- README.md | 11 ++++++----- docs/installation.md | 5 +++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 131824e..f465583 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ docker run --rm -v "$PWD":/app -w /app/vendor/sylius/test-application \ ### A file added under `config/services/` is not seen by `cache:clear` -`config/services.xml` imports `services/**`, and the `GlobResource` keeps the list frozen. You need +`config/services.php` imports `services/*.php`, and the `GlobResource` keeps the list frozen. You need `rm -rf var/cache/*`. Symptom: the services of the new file do not exist, without the slightest error. @@ -64,7 +64,9 @@ first payment in the database appears to work. Sylius encrypts the gateway configuration (`sylius_payment.encryption.enabled` defaults to true). **Without an encryption key, every write fails on `Cannot read keyfile`.** The key lives in `vendor/sylius/test-application/config/encryption/` and therefore disappears on every -`composer install`. +`composer install` or `update` that replaces the test application. The gateway configurations +already in a database were encrypted with the lost key: the payment methods grid then answers 500 +on a decryption error. Recreate those payment methods, or reload the fixtures. ```bash APP_ENV=test vendor/bin/console doctrine:schema:update --force --complete -n @@ -86,6 +88,12 @@ APP_ENV=test vendor/bin/console sylius:payment:generate-key the matching Sylius foundation. - **`PasswordType` fields require `always_empty: false`** - otherwise saving an existing `PaymentMethod` again overwrites the keys with empty strings. +- **Configuration stays in PHP.** Symfony 8 no longer loads XML service definitions, and Behat 4 + (the one Symfony 8 requires) neither YAML configuration nor step annotations: services in + `config/services/*.php`, Behat in `behat.dist.php`, steps as `#[Given]` attributes. +- **Sylius 2.1 to 2.3 with one code base.** Where Sylius changed a signature, widen the parameter + rather than branch on the version - `AxeptaHttpResponseProvider` takes `object $request` because + 2.1 / 2.2 pass a `RequestConfiguration` and 2.3 a `Request`. - **New configuration keys must tolerate their own absence**: a gateway configuration written by an earlier version does not carry them. - The public contract - protocol classes, substitutable interfaces, hookable names, service tags, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bad7b31..9a9137a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ Two checks have to be green before a pull request can be merged: | Check | What it covers | |---|---| -| **`Build complete`** | the 18 matrix jobs, aggregated: PHP 8.2 to 8.5, Symfony 6.4 and 7.4, Sylius 2.1 and 2.2 | +| **`Build complete`** | the 23 matrix jobs, aggregated: PHP 8.2 to 8.5, Symfony 6.4, 7.4 and 8, Sylius 2.1, 2.2 and 2.3, within what each Sylius supports | | **`Composer audit`** | known vulnerabilities in the dependencies | If this is your first contribution, the workflows will not start until a maintainer approves the diff --git a/README.md b/README.md index 822a75f..804aa78 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Integration of the **Axepta BNP Paribas** payment gateway for **Sylius 2.1+**. | Component | Versions | |---|---| -| PHP | `^8.2` | -| Sylius | `^2.1` | -| Symfony | `^6.4` or `^7.4` | +| PHP | `^8.2` (Sylius 2.3: `^8.3`, Symfony 8: `^8.4`) | +| Sylius | `2.1`, `2.2`, `2.3` | +| Symfony | `^6.4`, `^7.4`, or `^8.0` with Sylius 2.3 | ## What this plugin does @@ -124,8 +124,9 @@ notification, accented description in ISO-8859-1, and twelve-character merchant nominal cycles were replayed on the code as it stands; the remaining cases come from an earlier campaign, on a slightly older revision. -**The continuous integration matrix is green** across the eighteen advertised combinations: PHP 8.2 -to 8.5, Symfony 6.4 and 7.4, Sylius 2.1 and 2.2, and MySQL 8.4, MariaDB 11.4 and PostgreSQL 17. The +**The continuous integration matrix is green** across the twenty-three advertised combinations: +PHP 8.2 to 8.5, Symfony 6.4, 7.4 and 8, Sylius 2.1, 2.2 and 2.3 - within what each Sylius version +supports - and MySQL 8.4, MariaDB 11.4 and PostgreSQL 17. The run against the real platform was carried out on **PHP 8.4 / Sylius 2.2 / Symfony 7.4** - the only combination on which a payment was actually taken. diff --git a/docs/installation.md b/docs/installation.md index f730a43..52c4db0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -4,8 +4,9 @@ | | | |---|---| -| PHP | `^8.2`, with `hash` and `mbstring` | -| Sylius | `^2.1` | +| PHP | `^8.2` (Sylius 2.3: `^8.3`), with `hash` and `mbstring` | +| Sylius | `2.1`, `2.2` or `2.3` | +| Symfony | `^6.4`, `^7.4`, or `^8.0` with Sylius 2.3 | | An Axepta contract | Merchant identifier and keys, supplied by BNP Paribas | No exotic extension: the Blowfish encryption is pure PHP, precisely so that nothing is required of From 9abf2641409f8c722aad189eb2b8c7b47f17ade2 Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 18:05:25 +0200 Subject: [PATCH 09/10] docs: align the README banner with the other Cyllene plugins --- docs/images/banner.svg | 79 +++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/docs/images/banner.svg b/docs/images/banner.svg index eec5536..8825827 100644 --- a/docs/images/banner.svg +++ b/docs/images/banner.svg @@ -1,53 +1,36 @@ - -Sylius Axepta BNP Paribas Plugin -Bandeau du plugin : paiement par carte Axepta BNP Paribas pour Sylius 2, par les chemins Payum et PaymentRequest, signe par le monogramme Cyllene - - - - - - - - - - - + + Sylius Axepta BNP Paribas + Axepta BNP Paribas card payment for Sylius, on the bank-hosted page, through the Payum and PaymentRequest paths + + + + + + + + - - - + Axepta BNP Paribas + + Card payment on the bank-hosted page + MIT · PHP 8.2+ · Sylius 2.1+ · Payum & PaymentRequest - + + -Sylius Axepta Plugin -BNP Paribas card payment, hosted by the bank + + + + + + + + + + + + - - - Payum - - PaymentRequest - - -MIT · PHP 8.2+ · Sylius 2.1+ · HMAC-SHA256 - - - - - - - - - - - - - - - + - - - - - + \ No newline at end of file From 760d17cb2ef897a9353ffe260139476ff03189b0 Mon Sep 17 00:00:00 2001 From: Julien Dehais Date: Wed, 7 Oct 2026 18:28:44 +0200 Subject: [PATCH 10/10] docs: add the 1.0 to 1.1 entry to the upgrade guide --- UPGRADE.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 845b746..2c8cd75 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -6,4 +6,9 @@ plugin. A payment plugin is upgraded in production, on a site that takes money: every compatibility break must be written here before it is released, together with the exact manoeuvre to carry out. -`v1.0.0` is the first release: there is nothing to upgrade from yet. +## From 1.0 to 1.1 + +Nothing to change in the project: 1.1 adds Sylius 2.3 and Symfony 8 support and breaks nothing. + +The plugin now defines its services in PHP, Symfony 8 no longer reading XML. Service ids, tags and +arguments are unchanged: a project that decorates or overrides them by id keeps working.