From 5790a4eeae263c1e1c47c943b793d234d5d06570 Mon Sep 17 00:00:00 2001 From: adumont-payplug Date: Fri, 15 May 2026 14:10:19 +0200 Subject: [PATCH 1/4] IT: adding auto code review from claude on PR opening --- .github/copilot-instructions.md | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..388e61f9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,56 @@ +# Copilot Instructions + +These instructions apply to code reviews on pull requests in this repository. + +## Project Context + +This is PayPlug's official Sylius plugin. PayPlug is a Payment Service Provider (PSP). The plugin integrates PayPlug's payment processing into Sylius via multiple payment methods: standard card (PayPlug), Oney financing, Bancontact, Scalapay, Apple Pay, and American Express. + +**Stack**: PHP 8.2+, Sylius 2.0+, Symfony 6.4+, Payum, `payplug/payplug-php ^4.0` + +## Intentional Patterns — Do Not Flag as Issues + +- **`sleep(10)` in `NotifyAction`** — intentional, prevents a race condition between the IPN webhook and the user redirect. Never suggest removing it. +- **No direct SDK calls** — `PayPlugApiClient` is the only allowed entry point to the PayPlug PHP SDK. Any direct call to SDK classes bypasses this and is a bug. +- **Dual architecture** — Sylius 2.1+ uses a command/response provider model alongside legacy Payum actions. Both coexist intentionally. +- **Card saving condition** — a card is only saved when the PayPlug API response includes `metadata['customer_id']`. Absence of this guard is a bug. +- **`payment_context.cart`** — required in the PayPlug API payload for Oney and Scalapay payments. Missing it causes API rejection. +- **Distributed lock in `NotifyAction`** — Symfony Lock is used on payment ID to prevent concurrent webhook processing. Do not flag as over-engineering. + +## Code Review Dimensions + +### Security +- SQL injection, XSS, CSRF +- Authentication and authorization flaws +- Secrets or credentials committed in code +- Insecure deserialization, path traversal, SSRF +- Direct calls to PayPlug PHP SDK classes instead of going through `PayPlugApiClient` + +### Performance +- N+1 queries (especially in Sylius entity traversal) +- Unnecessary memory allocations +- Algorithmic complexity (O(n²) in hot paths) +- Missing database indexes +- Unbounded queries or loops +- Resource leaks + +### Correctness +- Edge cases: empty input, null, overflow +- Race conditions and concurrency issues +- Error handling and propagation +- Off-by-one errors, type safety +- `declare(strict_types=1)` must be present in every PHP file +- For new payment gateways (PPRO pattern): verify the full implementation checklist is covered (gateway factory, form type, resolver decorator, refund provider, templates, service definitions, translations) + +### Maintainability +- Naming clarity, single responsibility, duplication +- Test coverage: PHPUnit in `tests/PHPUnit/`, Behat in `features/` +- Documentation for non-obvious logic only — do not flag missing comments on self-explanatory code +- PHPStan level max compliance; suppressions must go in `ruleset/phpstan-baseline.neon` +- ECS coding standard based on `sylius-labs/coding-standard` + +## Output Format + +Rate each dimension: **Good** / **Needs Attention** / **Critical** + +List findings with file path and line number. Lead with Critical findings. Include positive observations alongside issues. From 3bafee647827c3690f0846cfe0c1f2504dad273b Mon Sep 17 00:00:00 2001 From: adumont-payplug Date: Thu, 28 May 2026 14:43:44 +0200 Subject: [PATCH 2/4] IT: improving copilot knowledge for review --- .github/copilot-instructions.md | 80 ++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 388e61f9..60b7a9bb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,6 +25,11 @@ This is PayPlug's official Sylius plugin. PayPlug is a Payment Service Provider - Secrets or credentials committed in code - Insecure deserialization, path traversal, SSRF - Direct calls to PayPlug PHP SDK classes instead of going through `PayPlugApiClient` +- Webhook payloads must be verified via `PayPlugApiClient::treat()` before any processing — never act on raw `php://input` directly +- Card data (PAN, CVV, raw card numbers) must never appear in logs, error messages, or stored fields +- API secret keys must never appear in logs, exception messages, or HTTP responses +- Payment amounts must be validated server-side — never trust a client-submitted amount +- `redirect_url` values must come from the PayPlug API response, never constructed from user input (open redirect risk) ### Performance - N+1 queries (especially in Sylius entity traversal) @@ -41,6 +46,11 @@ This is PayPlug's official Sylius plugin. PayPlug is a Payment Service Provider - Off-by-one errors, type safety - `declare(strict_types=1)` must be present in every PHP file - For new payment gateways (PPRO pattern): verify the full implementation checklist is covered (gateway factory, form type, resolver decorator, refund provider, templates, service definitions, translations) +- Amount unit: Payum works in the smallest currency unit (cents). Any conversion must be explicit — silently mixing units is a payment amount bug +- EUR-only enforcement must happen before the API call, not after +- `factory_name` must be stored alongside `redirect_url` in payment details — missing it allows a stale redirect from one gateway to be reused when retrying with a different one +- Refund amounts must not exceed the remaining refundable amount on the payment +- State machine transition names must match Sylius/Payum constants exactly; Oney adds a custom `oney_request_payment` transition that is easy to misspell or omit ### Maintainability - Naming clarity, single responsibility, duplication @@ -48,9 +58,75 @@ This is PayPlug's official Sylius plugin. PayPlug is a Payment Service Provider - Documentation for non-obvious logic only — do not flag missing comments on self-explanatory code - PHPStan level max compliance; suppressions must go in `ruleset/phpstan-baseline.neon` - ECS coding standard based on `sylius-labs/coding-standard` +- Translations must be complete in all three locales (`en`, `fr`, `it`) — partial translation is a regression +- New services must be registered in both `config/services.yaml` (command/response providers) and the relevant `config/services/*.xml` file (Payum factory or API client) — registering only one side causes runtime failures + +### Headless Compliance + +In a headless Sylius setup the frontend is a decoupled SPA or mobile app — it cannot follow server-side HTTP redirects. Any shop-facing response that performs a redirect instead of returning JSON breaks the headless flow. + +- Shop-facing controllers and response providers must return a `JsonResponse` containing a `redirect_url` field rather than a `RedirectResponse`. The client is responsible for performing the redirect. +- Known existing offenders (do not flag these as new issues, but flag any new code that replicates the same pattern): + - `src/OrderPay/Provider/CaptureHttpResponseProvider.php` — returns `RedirectResponse($data['redirect_url'])` + - `src/Controller/OneClickAction.php` — returns `RedirectResponse` on all exit paths +- Admin-facing controllers (`src/Action/Admin/`) are exempt — headless compliance only applies to the shop payment flow. +- If a new controller or response provider in the shop flow returns `RedirectResponse`, flag it as a headless compliance issue. ## Output Format -Rate each dimension: **Good** / **Needs Attention** / **Critical** +Structure the review comment exactly as follows: + +### 1. What's Good + +A bullet list of positive observations — things done well, non-obvious correct decisions, solid patterns. + +--- + +### 2. Summary table + +A markdown table with two columns: **Dimension** and **Rating**. One row per review dimension. Use emoji inline with the rating text: + +| Dimension | Rating | +|---|---| +| Security | ✅ Fine | +| Correctness | ⚠️ Medium (short reason) | +| Performance | ✅ Fine | +| Maintainability | ⚠️ Low (short reason) | + +Severity scale: +- ✅ **Fine** — no issues +- ⚠️ **Low / Medium** — should be fixed but not blocking +- ❌ **High / Critical** — must be fixed before merge + +--- + +### 3. Closing one-liner + +A single sentence summarising what needs to be addressed before merge (or that the PR is ready if nothing critical). + +--- + +### 4. Individual findings (one section per issue) + +Each finding follows this exact structure: + +**Heading:** `[Dimension] [emoji] [Severity]` — e.g. `Security ⚠️ Medium` + +**Subtitle (bold):** short title followed by the file path and line number as a markdown link — e.g. `**Path traversal in getPayment** (PaymentClient.php:290)` + +**Code block:** the relevant snippet from the diff showing the problem. + +**Explanation paragraph:** what the risk is and why it matters. Be concrete. + +**Fix line:** start with `Fix:` in bold, then a brief description, followed by a code block showing the suggested fix. + +Lead with Critical/High findings. Omit the findings section entirely if there are no issues. + +## Iterative Reviews + +When reviewing a new commit on a PR that already has open review threads: -List findings with file path and line number. Lead with Critical findings. Include positive observations alongside issues. +- **Resolve threads** for issues that have been addressed in the new commit — do not leave them open if the fix is present. +- **Do not re-open or re-comment** on issues that were already resolved in a previous round. +- Only open new threads for issues that are genuinely new or that remain unresolved. +- If a previous finding was partially addressed, update the thread with what still needs attention rather than opening a duplicate. From 1988f0a7e485feaed980241ddc730ddd1c7832ca Mon Sep 17 00:00:00 2001 From: adumont-payplug Date: Fri, 5 Jun 2026 10:12:49 +0200 Subject: [PATCH 3/4] Feature/pre 3224 implementing wero (#299) * PRE-3224 feat: Add Wero payment method * PRE-3224: fix the refund from back-office * PRE-3224: adding Unit tests for Wero and fixing QA url --------- Co-authored-by: Julien Hoarau --- .github/workflows/ci.yml | 8 ++-- config/services.yaml | 20 ++++++++ config/services/client.xml | 9 ++++ config/services/gateway.xml | 8 ++++ config/twig_hooks/admin.yaml | 6 +++ config/twig_hooks/shop.yaml | 3 ++ public/assets/wero/logo.svg | 41 ++++++++++++++++ ruleset/phpstan-baseline.neon | 6 +++ .../CapturePaymentRequestCommandProvider.php | 4 ++ .../NotifyPaymentRequestCommandProvider.php | 4 ++ .../StatusPaymentRequestCommandProvider.php | 4 ++ src/Creator/PayPlugPaymentDataCreator.php | 2 + .../Type/WeroGatewayConfigurationType.php | 25 ++++++++++ src/Gateway/WeroGatewayFactory.php | 14 ++++++ .../RefundPaymentGeneratedHandler.php | 2 + .../Provider/CaptureHttpResponseProvider.php | 4 ++ .../RefundPaymentProcessor.php | 2 + ...dRefundPaymentMethodsProviderDecorator.php | 15 ++++++ .../WeroPaymentMethodsResolverDecorator.php | 48 +++++++++++++++++++ src/Validator/PaymentMethodValidator.php | 9 ++++ templates/shop/integrated/index.html.twig | 6 +-- templates/shop/select_payment/_wero.html.twig | 7 +++ .../Creator/PayPlugPaymentDataCreatorTest.php | 18 +++++++ .../RefundPaymentProcessorTest.php | 18 +++++++ translations/messages.en.yml | 1 + translations/messages.fr.yml | 1 + translations/messages.it.yml | 1 + translations/validators.en.yml | 8 ++++ translations/validators.fr.yml | 8 ++++ translations/validators.it.yml | 8 ++++ 30 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 public/assets/wero/logo.svg create mode 100644 src/Gateway/Form/Type/WeroGatewayConfigurationType.php create mode 100644 src/Gateway/WeroGatewayFactory.php create mode 100644 src/Provider/WeroSupportedRefundPaymentMethodsProviderDecorator.php create mode 100644 src/Resolver/WeroPaymentMethodsResolverDecorator.php create mode 100644 templates/shop/select_payment/_wero.html.twig diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9caf7c4..acb45390 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,8 @@ name: CI 'on': pull_request: branches: - - test-develop-ci - - test-master-ci + - develop + - master paths-ignore: - README.md @@ -14,11 +14,11 @@ jobs: sylius-matrix: needs: [quality] - if: github.base_ref == 'test-develop-ci' + if: github.base_ref == 'develop' uses: payplug/template-ci/.github/workflows/sylius_phpunit.yml@main sonarcloud: - if: always() && !failure() && !cancelled() && github.base_ref == 'test-develop-ci' + if: always() && !failure() && !cancelled() && github.base_ref == 'develop' needs: sylius-matrix uses: payplug/template-ci/.github/workflows/sonarcloud.yml@main with: diff --git a/config/services.yaml b/config/services.yaml index 116676f0..7db6a5fe 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -139,6 +139,26 @@ services: - name: sylius.payment_request.provider.http_response gateway_factory: !php/const PayPlug\SyliusPayPlugPlugin\Gateway\ScalapayGatewayFactory::FACTORY_NAME + ## Wero Payplug Gateway ## + payplug_sylius_payplug_plugin.command_provider.payplug_wero: + class: Sylius\Bundle\PaymentBundle\CommandProvider\ActionsCommandProvider + arguments: + - !tagged_locator + tag: payplug_sylius_payplug_plugin.command_provider.payplug_wero + index_by: 'action' + tags: + - name: sylius.payment_request.command_provider + gateway_factory: !php/const PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory::FACTORY_NAME + payplug_sylius_payplug_plugin.provider.order_pay.http_response.payplug_wero: + class: Sylius\Bundle\PaymentBundle\Provider\ActionsHttpResponseProvider + arguments: + - !tagged_locator + tag: payplug_sylius_payplug_plugin.http_response_provider.payplug_wero + index_by: action + tags: + - name: sylius.payment_request.provider.http_response + gateway_factory: !php/const PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory::FACTORY_NAME + ## Apple Pay Payplug Gateway ## payplug_sylius_payplug_plugin.command_provider.payplug_apple_pay: diff --git a/config/services/client.xml b/config/services/client.xml index 22917bdf..3f2d3391 100644 --- a/config/services/client.xml +++ b/config/services/client.xml @@ -62,5 +62,14 @@ method="create"/> payplug_scalapay + + + + payplug_wero + diff --git a/config/services/gateway.xml b/config/services/gateway.xml index dd174517..475c0a26 100644 --- a/config/services/gateway.xml +++ b/config/services/gateway.xml @@ -55,5 +55,13 @@ + + + + PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory + + diff --git a/config/twig_hooks/admin.yaml b/config/twig_hooks/admin.yaml index ba1e2403..ebd1c960 100644 --- a/config/twig_hooks/admin.yaml +++ b/config/twig_hooks/admin.yaml @@ -33,6 +33,9 @@ sylius_twig_hooks: 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_scalapay': &scalapayGateway live_checkbox: *liveCheckbox + 'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_wero': &weroGateway + live_checkbox: *liveCheckbox + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': <<: *payplugGateway renew_oauth: &renewOAuth @@ -53,3 +56,6 @@ sylius_twig_hooks: 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_scalapay': <<: *scalapayGateway renew_oauth: *renewOAuth + 'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_wero': + <<: *weroGateway + renew_oauth: *renewOAuth diff --git a/config/twig_hooks/shop.yaml b/config/twig_hooks/shop.yaml index 5ffe2826..36252bfd 100644 --- a/config/twig_hooks/shop.yaml +++ b/config/twig_hooks/shop.yaml @@ -47,3 +47,6 @@ sylius_twig_hooks: 'sylius_shop.shared.form.select_payment.payment.choice.details#payplug_scalapay': scalapay: template: '@PayPlugSyliusPayPlugPlugin/shop/select_payment/_scalapay.html.twig' + 'sylius_shop.shared.form.select_payment.payment.choice.details#payplug_wero': + wero: + template: '@PayPlugSyliusPayPlugPlugin/shop/select_payment/_wero.html.twig' diff --git a/public/assets/wero/logo.svg b/public/assets/wero/logo.svg new file mode 100644 index 00000000..a1b9d520 --- /dev/null +++ b/public/assets/wero/logo.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruleset/phpstan-baseline.neon b/ruleset/phpstan-baseline.neon index 2ebc3b8c..abca980f 100644 --- a/ruleset/phpstan-baseline.neon +++ b/ruleset/phpstan-baseline.neon @@ -1366,6 +1366,12 @@ parameters: count: 1 path: ../src/Resolver/ScalapayPaymentMethodsResolverDecorator.php + - + message: '#^Method PayPlug\\SyliusPayPlugPlugin\\Resolver\\WeroPaymentMethodsResolverDecorator\:\:getSupportedMethods\(\) should return array\ but returns array\.$#' + identifier: return.type + count: 1 + path: ../src/Resolver/WeroPaymentMethodsResolverDecorator.php + - message: '#^Call to method apply\(\) on an unknown class SM\\StateMachine\\StateMachineInterface\.$#' identifier: class.notFound diff --git a/src/Command/Provider/CapturePaymentRequestCommandProvider.php b/src/Command/Provider/CapturePaymentRequestCommandProvider.php index 8d0be621..bb507e7e 100644 --- a/src/Command/Provider/CapturePaymentRequestCommandProvider.php +++ b/src/Command/Provider/CapturePaymentRequestCommandProvider.php @@ -35,6 +35,10 @@ 'payplug_sylius_payplug_plugin.command_provider.payplug_scalapay', ['action' => PaymentRequestInterface::ACTION_CAPTURE], )] +#[AutoconfigureTag( + 'payplug_sylius_payplug_plugin.command_provider.payplug_wero', + ['action' => PaymentRequestInterface::ACTION_CAPTURE], +)] final class CapturePaymentRequestCommandProvider implements PaymentRequestCommandProviderInterface { public function supports(PaymentRequestInterface $paymentRequest): bool diff --git a/src/Command/Provider/NotifyPaymentRequestCommandProvider.php b/src/Command/Provider/NotifyPaymentRequestCommandProvider.php index 55f16fb8..4ef91b6a 100644 --- a/src/Command/Provider/NotifyPaymentRequestCommandProvider.php +++ b/src/Command/Provider/NotifyPaymentRequestCommandProvider.php @@ -33,6 +33,10 @@ 'payplug_sylius_payplug_plugin.command_provider.payplug_scalapay', ['action' => PaymentRequestInterface::ACTION_NOTIFY], )] +#[AutoconfigureTag( + 'payplug_sylius_payplug_plugin.command_provider.payplug_wero', + ['action' => PaymentRequestInterface::ACTION_NOTIFY], +)] final class NotifyPaymentRequestCommandProvider implements PaymentRequestCommandProviderInterface { public function supports(PaymentRequestInterface $paymentRequest): bool diff --git a/src/Command/Provider/StatusPaymentRequestCommandProvider.php b/src/Command/Provider/StatusPaymentRequestCommandProvider.php index a0475641..37676e9b 100644 --- a/src/Command/Provider/StatusPaymentRequestCommandProvider.php +++ b/src/Command/Provider/StatusPaymentRequestCommandProvider.php @@ -34,6 +34,10 @@ 'payplug_sylius_payplug_plugin.command_provider.payplug_scalapay', ['action' => PaymentRequestInterface::ACTION_STATUS], )] +#[AutoconfigureTag( + 'payplug_sylius_payplug_plugin.command_provider.payplug_wero', + ['action' => PaymentRequestInterface::ACTION_STATUS], +)] final class StatusPaymentRequestCommandProvider implements PaymentRequestCommandProviderInterface { public function __construct(private RequestStack $requestStack) diff --git a/src/Creator/PayPlugPaymentDataCreator.php b/src/Creator/PayPlugPaymentDataCreator.php index 89d882c0..9b437e68 100644 --- a/src/Creator/PayPlugPaymentDataCreator.php +++ b/src/Creator/PayPlugPaymentDataCreator.php @@ -20,6 +20,7 @@ use PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\ScalapayGatewayFactory; +use PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory; use Sylius\Component\Core\Model\AddressInterface; use Sylius\Component\Core\Model\CustomerInterface; use Sylius\Component\Core\Model\OrderInterface; @@ -355,6 +356,7 @@ private function addPaymentMethodFieldToDetails(ArrayObject $details, string $ga ApplePayGatewayFactory::FACTORY_NAME => ApplePayGatewayFactory::PAYMENT_METHOD_APPLE_PAY, AmericanExpressGatewayFactory::FACTORY_NAME => AmericanExpressGatewayFactory::PAYMENT_METHOD_AMERICAN_EXPRESS, ScalapayGatewayFactory::FACTORY_NAME => ScalapayGatewayFactory::PAYMENT_METHOD_SCALAPAY, + WeroGatewayFactory::FACTORY_NAME => WeroGatewayFactory::PAYMENT_METHOD_WERO, ]; // match function is only supported by php 8. so can not use it here. foreach ($paymentMethods as $name => $method) { diff --git a/src/Gateway/Form/Type/WeroGatewayConfigurationType.php b/src/Gateway/Form/Type/WeroGatewayConfigurationType.php new file mode 100644 index 00000000..39d1a035 --- /dev/null +++ b/src/Gateway/Form/Type/WeroGatewayConfigurationType.php @@ -0,0 +1,25 @@ + 'payplug_wero', + 'label' => 'payplug_sylius_payplug_plugin.ui.wero_gateway_label', + 'priority' => 90, + ], +)] +final class WeroGatewayConfigurationType extends AbstractGatewayConfigurationType +{ + protected string $gatewayFactoryTitle = WeroGatewayFactory::FACTORY_TITLE; + + protected string $gatewayFactoryName = WeroGatewayFactory::FACTORY_NAME; + + protected string $gatewayBaseCurrencyCode = WeroGatewayFactory::BASE_CURRENCY_CODE; +} diff --git a/src/Gateway/WeroGatewayFactory.php b/src/Gateway/WeroGatewayFactory.php new file mode 100644 index 00000000..aaa3f536 --- /dev/null +++ b/src/Gateway/WeroGatewayFactory.php @@ -0,0 +1,14 @@ + PaymentRequestInterface::ACTION_CAPTURE], )] +#[AutoconfigureTag( + 'payplug_sylius_payplug_plugin.http_response_provider.payplug_wero', + ['action' => PaymentRequestInterface::ACTION_CAPTURE], +)] class CaptureHttpResponseProvider implements HttpResponseProviderInterface { public function supports(RequestConfiguration $requestConfiguration, PaymentRequestInterface $paymentRequest): bool diff --git a/src/PaymentProcessing/RefundPaymentProcessor.php b/src/PaymentProcessing/RefundPaymentProcessor.php index b6d74a0f..4f9bb13b 100644 --- a/src/PaymentProcessing/RefundPaymentProcessor.php +++ b/src/PaymentProcessing/RefundPaymentProcessor.php @@ -14,6 +14,7 @@ use PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\ScalapayGatewayFactory; +use PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Repository\RefundHistoryRepositoryInterface; use Psr\Log\LoggerInterface; use Sylius\Component\Core\Model\PaymentInterface; @@ -126,6 +127,7 @@ private function prepare(PaymentInterface $payment): void ApplePayGatewayFactory::FACTORY_NAME, AmericanExpressGatewayFactory::FACTORY_NAME, ScalapayGatewayFactory::FACTORY_NAME, + WeroGatewayFactory::FACTORY_NAME, ], true) ) { return; diff --git a/src/Provider/WeroSupportedRefundPaymentMethodsProviderDecorator.php b/src/Provider/WeroSupportedRefundPaymentMethodsProviderDecorator.php new file mode 100644 index 00000000..82870068 --- /dev/null +++ b/src/Provider/WeroSupportedRefundPaymentMethodsProviderDecorator.php @@ -0,0 +1,15 @@ +decorated->getSupportedMethods($subject); + + /** @var OrderInterface $order */ + $order = $subject->getOrder(); + $billingCountryCode = $order->getBillingAddress()?->getCountryCode(); + + return $this->supportedMethodsProvider->provide( + $supportedMethods, + WeroGatewayFactory::FACTORY_NAME, + $subject->getAmount() ?? 0, + $billingCountryCode, + ); + } + + public function supports(BasePaymentInterface $subject): bool + { + return $this->decorated->supports($subject); + } +} diff --git a/src/Validator/PaymentMethodValidator.php b/src/Validator/PaymentMethodValidator.php index 34b826e2..1ce571aa 100644 --- a/src/Validator/PaymentMethodValidator.php +++ b/src/Validator/PaymentMethodValidator.php @@ -15,6 +15,7 @@ use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsCanSavePaymentMethod; use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsOneyEnabled; use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\PayplugPermission; +use PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory; use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -48,6 +49,7 @@ public function process(PaymentMethodInterface $paymentMethod): void AmericanExpressGatewayFactory::FACTORY_NAME => $this->processAmex($paymentMethod), ApplePayGatewayFactory::FACTORY_NAME => $this->processApplePay($paymentMethod), ScalapayGatewayFactory::FACTORY_NAME => $this->processScalapay($paymentMethod), + WeroGatewayFactory::FACTORY_NAME => $this->processWero($paymentMethod), default => throw new \InvalidArgumentException('Unsupported payment method'), }; @@ -113,4 +115,11 @@ private function processScalapay(PaymentMethodInterface $paymentMethod): Constra return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); } + + private function processWero(PaymentMethodInterface $paymentMethod): ConstraintViolationListInterface + { + $constraintList = [new IsCanSavePaymentMethod()]; + + return $this->validator->validate($paymentMethod, $constraintList, self::VALIDATION_GROUPS); + } } diff --git a/templates/shop/integrated/index.html.twig b/templates/shop/integrated/index.html.twig index 61a4b1d2..6afd94b9 100644 --- a/templates/shop/integrated/index.html.twig +++ b/templates/shop/integrated/index.html.twig @@ -1,6 +1,6 @@ -{% set initRouteParam = {'paymentMethodId': paymentMethod.id} %} +{% set init_route_param = {'paymentMethodId': paymentMethod.id} %} {% if order is defined and order.getCheckoutCompletedAt is not null and order.tokenValue is not null %} - {% set initRouteParam = initRouteParam|merge({'orderToken': order.tokenValue}) %} + {% set init_route_param = init_route_param|merge({'orderToken': order.tokenValue}) %} {% endif %} @@ -14,7 +14,7 @@ payment_id: '{{ payment.details.payment_id }}', {% endif %} routes: { - init_payment: '{{ path('payplug_sylius_integrated_payment_init', initRouteParam) }}', + init_payment: '{{ path('payplug_sylius_integrated_payment_init', init_route_param) }}', }, cardholder: '{{ 'payplug_sylius_payplug_plugin.ui.integrated_payment.card_holder.title'|trans }}', pan: '{{ 'payplug_sylius_payplug_plugin.ui.integrated_payment.pan.title'|trans }}', diff --git a/templates/shop/select_payment/_wero.html.twig b/templates/shop/select_payment/_wero.html.twig new file mode 100644 index 00000000..59000c28 --- /dev/null +++ b/templates/shop/select_payment/_wero.html.twig @@ -0,0 +1,7 @@ +{% set form = hookable_metadata.context.form %} + +
diff --git a/tests/PHPUnit/Creator/PayPlugPaymentDataCreatorTest.php b/tests/PHPUnit/Creator/PayPlugPaymentDataCreatorTest.php index 144af170..4b545215 100644 --- a/tests/PHPUnit/Creator/PayPlugPaymentDataCreatorTest.php +++ b/tests/PHPUnit/Creator/PayPlugPaymentDataCreatorTest.php @@ -12,6 +12,7 @@ use PayPlug\SyliusPayPlugPlugin\Gateway\BancontactGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; +use PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sylius\Component\Core\Model\AddressInterface; @@ -439,6 +440,23 @@ public function testCreate_bancontactGateway_setsBancontactPaymentMethod(): void self::assertSame('bancontact', $details['payment_method']); } + // ------------------------------------------------------------------------- + // create() — Wero gateway (PPRO payment_method field) + // ------------------------------------------------------------------------- + + /** + * Uses the Wero gateway factory (a PPRO method routed through PayPlug). + * Verifies the payment_method field is set to the literal string 'wero'. + */ + public function testCreate_weroGateway_setsWeroPaymentMethod(): void + { + $payment = $this->buildMinimalPaymentWithGateway(WeroGatewayFactory::FACTORY_NAME); + + $details = $this->creator->create($payment); + + self::assertSame('wero', $details['payment_method']); + } + // ------------------------------------------------------------------------- // create() — phone number in billing / shipping address // ------------------------------------------------------------------------- diff --git a/tests/PHPUnit/PaymentProcessing/RefundPaymentProcessorTest.php b/tests/PHPUnit/PaymentProcessing/RefundPaymentProcessorTest.php index d4ba9812..3a2d3763 100644 --- a/tests/PHPUnit/PaymentProcessing/RefundPaymentProcessorTest.php +++ b/tests/PHPUnit/PaymentProcessing/RefundPaymentProcessorTest.php @@ -11,6 +11,7 @@ use PayPlug\SyliusPayPlugPlugin\Entity\RefundHistory; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use PayPlug\SyliusPayPlugPlugin\Gateway\ScalapayGatewayFactory; +use PayPlug\SyliusPayPlugPlugin\Gateway\WeroGatewayFactory; use PayPlug\SyliusPayPlugPlugin\PaymentProcessing\RefundPaymentProcessor; use PayPlug\SyliusPayPlugPlugin\Repository\RefundHistoryRepositoryInterface; use PHPUnit\Framework\MockObject\MockObject; @@ -100,6 +101,23 @@ public function testProcess_scalapayGateway_callsApiRefund(): void $this->processor->process($payment); } + // ------------------------------------------------------------------------- + // process() — Wero gateway → refund is processed + // ------------------------------------------------------------------------- + + /** + * Calls process() with a Wero payment; verifies refundPayment() is called, + * confirming Wero is included in the supported gateway allow-list. + */ + public function testProcess_weroGateway_callsApiRefund(): void + { + $payment = $this->buildPayment(WeroGatewayFactory::FACTORY_NAME, ['payment_id' => 'pay_wero']); + + $this->apiClient->expects(self::once())->method('refundPayment')->with('pay_wero'); + + $this->processor->process($payment); + } + // ------------------------------------------------------------------------- // process() — API exception → UpdateHandlingException // ------------------------------------------------------------------------- diff --git a/translations/messages.en.yml b/translations/messages.en.yml index 97b5424c..9ffa6acb 100644 --- a/translations/messages.en.yml +++ b/translations/messages.en.yml @@ -25,6 +25,7 @@ payplug_sylius_payplug_plugin: confirm_card_deletion: Are you sure you want to delete this card? bancontact_gateway_label: Bancontact by Payplug scalapay_gateway_label: Scalapay by PayPlug + wero_gateway_label: Wero by PayPlug apple_pay_gateway_label: Apple Pay by Payplug american_express_gateway_label: American Express by Payplug apple_pay_not_available: Apple Pay is not available on this browser or device. diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml index 42193f9b..068fba68 100644 --- a/translations/messages.fr.yml +++ b/translations/messages.fr.yml @@ -25,6 +25,7 @@ payplug_sylius_payplug_plugin: confirm_card_deletion: Êtes-vous sûr(e) de vouloir supprimer cette carte ? bancontact_gateway_label: Bancontact by Payplug scalapay_gateway_label: Scalapay by PayPlug + wero_gateway_label: Wero by PayPlug apple_pay_gateway_label: Apple Pay by Payplug american_express_gateway_label: American Express by Payplug apple_pay_not_available: Apple Pay n'est pas disponible sur ce navigateur ou appareil. diff --git a/translations/messages.it.yml b/translations/messages.it.yml index b9170f12..10459f42 100644 --- a/translations/messages.it.yml +++ b/translations/messages.it.yml @@ -25,6 +25,7 @@ payplug_sylius_payplug_plugin: confirm_card_deletion: Desideri cancellare questa carta? bancontact_gateway_label: Bancontact by Payplug scalapay_gateway_label: Scalapay by PayPlug + wero_gateway_label: Wero by PayPlug apple_pay_gateway_label: Apple Pay by Payplug american_express_gateway_label: American Express by Payplug apple_pay_not_available: Apple Pay non è disponibile su questo browser o dispositivo. diff --git a/translations/validators.en.yml b/translations/validators.en.yml index a5fc5a83..6e75e794 100644 --- a/translations/validators.en.yml +++ b/translations/validators.en.yml @@ -33,6 +33,14 @@ payplug_sylius_payplug_plugin: You don't have access to this feature yet. To activate Scalapay, please contact us at support@payplug.com and activate the LIVE mode. + payplug_wero: + can_not_save_method_with_test_key: | + The Wero 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 Wero, please contact us at support@payplug.com + and activate the LIVE mode. payplug_apple_pay: can_not_save_method_with_test_key: | The Apple Pay payment method is not available for the TEST mode. diff --git a/translations/validators.fr.yml b/translations/validators.fr.yml index 128c6ffb..2c35609a 100644 --- a/translations/validators.fr.yml +++ b/translations/validators.fr.yml @@ -32,6 +32,14 @@ payplug_sylius_payplug_plugin: Vous n'avez pas accès à cette fonctionnalité. Pour activer Scalapay, contactez-nous à support@payplug.com et activez le mode LIVE. + payplug_wero: + can_not_save_method_with_test_key: | + Le paiement par Wero 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 Wero, contactez-nous à support@payplug.com + et activez le mode LIVE. payplug_apple_pay: can_not_save_method_with_test_key: | Le paiement par Apple Pay n’est pas disponible en mode TEST. diff --git a/translations/validators.it.yml b/translations/validators.it.yml index efaf49df..23f1c2f6 100644 --- a/translations/validators.it.yml +++ b/translations/validators.it.yml @@ -32,6 +32,14 @@ payplug_sylius_payplug_plugin: Non puoi ancora accedere a questa funzionalità. Per attivare Scalapay, contattaci a support@payplug.com e attiva la modalità LIVE. + payplug_wero: + can_not_save_method_with_test_key: | + Il metodo di pagamento Wero non è disponibile in modalità TEST. + Attiva la modalità LIVE. + can_not_save_method_no_access: | + Non puoi ancora accedere a questa funzionalità. + Per attivare Wero, contattaci a support@payplug.com + e attiva la modalità LIVE. payplug_american_express: can_not_save_method_with_test_key: | Il pagamento Apple Pay non è disponibile in modalità TEST. From 86810623a85a4cc614b8975e66cbe7b81af59484 Mon Sep 17 00:00:00 2001 From: adumont-payplug Date: Fri, 5 Jun 2026 11:01:30 +0200 Subject: [PATCH 4/4] PRE-3224: update symfony compatibility versions --- .github/workflows/ci.yml | 4 +++- .github/workflows/release.yml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acb45390..b1616306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,12 @@ jobs: needs: [quality] if: github.base_ref == 'develop' uses: payplug/template-ci/.github/workflows/sylius_phpunit.yml@main + with: + symfony-versions: '["7.3"]' sonarcloud: if: always() && !failure() && !cancelled() && github.base_ref == 'develop' - needs: sylius-matrix + needs: [sylius-matrix] uses: payplug/template-ci/.github/workflows/sonarcloud.yml@main with: project-name: 'github-payplug-payplug-syliuspayplugplugin' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5030bcc7..654a383b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,8 @@ jobs: phpunit: needs: [quality] uses: payplug/template-ci/.github/workflows/sylius_phpunit.yml@main + with: + symfony-versions: '["7.3"]' github-release: needs: phpunit