diff --git a/.env b/.env index ae9dd9a66a..23eeec1158 100644 --- a/.env +++ b/.env @@ -36,6 +36,8 @@ SYLIUS_MESSENGER_TRANSPORT_MAIN_DSN=doctrine://default SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=doctrine://default?queue_name=main_failed SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=doctrine://default?queue_name=catalog_promotion_removal SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=doctrine://default?queue_name=catalog_promotion_removal_failed +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_DSN=doctrine://default?queue_name=payment_request +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_FAILED_DSN=doctrine://default?queue_name=payment_request_failed ###< symfony/messenger ### ###> symfony/mailer ### diff --git a/.env.test b/.env.test index f77c248753..ac192d5cad 100644 --- a/.env.test +++ b/.env.test @@ -13,6 +13,8 @@ SYLIUS_MESSENGER_TRANSPORT_MAIN_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_FAILED_DSN=sync:// ###< symfony/messenger ### MAILER_DSN=null://null diff --git a/.env.test_cached b/.env.test_cached index ea9c95ca96..e278eea0cc 100644 --- a/.env.test_cached +++ b/.env.test_cached @@ -14,6 +14,8 @@ SYLIUS_MESSENGER_TRANSPORT_MAIN_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=sync:// SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_FAILED_DSN=sync:// ###< symfony/messenger ### MAILER_DSN=null://null diff --git a/.env.test_cached_payum b/.env.test_cached_payum new file mode 100644 index 0000000000..e278eea0cc --- /dev/null +++ b/.env.test_cached_payum @@ -0,0 +1,21 @@ +APP_DEBUG=0 +APP_SECRET='s$cretf0rt3st' + +KERNEL_CLASS='App\Kernel' + +###> lexik/jwt-authentication-bundle ### +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private-test.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public-test.pem +JWT_PASSPHRASE=ALL_THAT_IS_GOLD_DOES_NOT_GLITTER_NOT_ALL_THOSE_WHO_WANDER_ARE_LOST +###< lexik/jwt-authentication-bundle ### + +###> symfony/messenger ### +SYLIUS_MESSENGER_TRANSPORT_MAIN_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_DSN=sync:// +SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_FAILED_DSN=sync:// +###< symfony/messenger ### + +MAILER_DSN=null://null diff --git a/.github/workflows/ci_e2e-mysql.yaml b/.github/workflows/ci_e2e-mysql.yaml index cd7f9e09e9..6b8ae5cebd 100644 --- a/.github/workflows/ci_e2e-mysql.yaml +++ b/.github/workflows/ci_e2e-mysql.yaml @@ -44,14 +44,14 @@ jobs: behat-no-js: needs: get-matrix runs-on: ubuntu-latest - name: "Non-JS, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" + name: "Non-JS, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (${{ matrix.env }}), MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" timeout-minutes: 45 strategy: fail-fast: false matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }} env: - APP_ENV: test_cached + APP_ENV: ${{ matrix.env }} DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?charset=utf8mb4&serverVersion=${{ matrix.mysql }}" steps: @@ -132,14 +132,14 @@ jobs: behat-ui-js-chromedriver: needs: get-matrix runs-on: ubuntu-latest - name: "JS with Chromedriver, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" + name: "JS with Chromedriver, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (${{ matrix.env }}), MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" timeout-minutes: 45 strategy: fail-fast: false matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }} env: - APP_ENV: test_cached + APP_ENV: ${{ matrix.env }} DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?charset=utf8mb4&serverVersion=${{ matrix.mysql }}" steps: @@ -210,14 +210,14 @@ jobs: behat-ui-js-panther: needs: get-matrix runs-on: ubuntu-latest - name: "JS with Panther, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" + name: "JS with Panther, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (${{ matrix.env }}), MySQL ${{ matrix.mysql }}, Twig ${{ matrix.twig }}, API Platform ${{ matrix.api-platform }}" timeout-minutes: 45 strategy: fail-fast: false matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }} env: - APP_ENV: test_cached + APP_ENV: ${{ matrix.env }} DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?charset=utf8mb4&serverVersion=${{ matrix.mysql }}" steps: diff --git a/.github/workflows/matrix.json b/.github/workflows/matrix.json index 1dd5a2f61d..9e57d8c853 100644 --- a/.github/workflows/matrix.json +++ b/.github/workflows/matrix.json @@ -39,14 +39,24 @@ "symfony": "^6.4", "api-platform": "^3.3", "mysql": "8.0", - "twig": "^3.3" + "twig": "^3.3", + "env": "test_cached" }, { "php": "8.3", "symfony": "^7.1", "api-platform": "^3.3", "mysql": "8.4", - "twig": "^3.3" + "twig": "^3.3", + "env": "test_cached" + }, + { + "php": "8.3", + "symfony": "^6.4", + "api-platform": "^3.3", + "mysql": "8.4", + "twig": "^3.3", + "env": "test_cached_payum" } ] }, @@ -101,7 +111,18 @@ "symfony": ["^6.4", "^7.1"], "api-platform": ["^3.3"], "mysql": ["8.0", "8.4"], - "twig": ["^3.3"] + "twig": ["^3.3"], + "env": "test_cached", + "include": [ + { + "php": "8.3", + "symfony": "^6.4", + "api-platform": "^2.7.10", + "mysql": "8.4", + "twig": "^3.3", + "env": "test_cached_payum" + } + ] }, "e2e-pgsql": { "php": ["8.2", "8.3"], diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index f972ab52f8..b9b07840ec 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -6,6 +6,9 @@ * The `sylius_default.bus` and `sylius_event.bus` configuration options were removed. Use `sylius.command_bus` and `sylius.event_bus` for commands and events respectively. +* SyliusStateMachineAbstraction: + * The `sylius_state_machine_abstraction.default_adapter` option has been changed from `winzou_state_machine` to `symfony_workflow`. + ## Codebase * Doctrine MongoDB and PHPCR is no longer supported in ResourceBundle and GridBundle: @@ -369,6 +372,11 @@ https://symfony.com/doc/current/security/passwords.html#configure-a-new-hasher-u Note: If your app never changed the hasher name configuration, you don't need to configure this "migrate_from" configuration. +## Directory structure + +* The Winzou state machine configuration file `state_machine.yml` has been moved from `@SyliusPaymentBundle/Resources/config/app` to `@SyliusPaymentBundle/Resources/config/app/state_machine` and renamed to `sylius_payment.yaml`. +* The Symfony workflow configuration file `state_machine.yaml` has been moved from `@SyliusPaymentBundle/Resources/config/workflow` to `@SyliusPaymentBundle/Resources/config/app/workflow` and renamed to `sylius_payment.yaml`. + ## Frontend * `use_webpack` option was removed from the `sylius_ui` configuration, and the Webpack has become the only module @@ -385,3 +393,13 @@ If your app never changed the hasher name configuration, you don't need to confi if you're running your shop in headless mode. * Channel's `themeName` form field existence is made optional and depends on `ShopBundle` presence. * The `Sylius\Bundle\CoreBundle\Theme\ChannelBasedThemeContext` has been moved to the `Sylius\Bundle\ShopBundle\Theme\ChannelBasedThemeContext`. + +## Routing + +* Routing for the `sylius_shop_payum` has been moved from the `SyliusShopBundle` to the `SyliusPayumBundle`: + +```yaml +sylius_shop_payum: +- resource: "@SyliusShopBundle/Resources/config/routing/payum.yml" ++ resource: "@SyliusPayumBundle/Resources/config/routing/integrations/sylius_shop.yaml" +``` diff --git a/composer-require-checker.json b/composer-require-checker.json index f7016521fb..0739730daa 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -66,6 +66,7 @@ "Payum\\Core\\Model\\GatewayConfig", "Payum\\Core\\Model\\GatewayConfigInterface", "Payum\\Core\\Model\\Identity", + "Payum\\Core\\Model\\ModelAggregateInterface", "Payum\\Core\\Model\\Payment", "Payum\\Core\\Model\\PaymentInterface", "Payum\\Core\\Payum", @@ -74,11 +75,15 @@ "Payum\\Core\\Request\\Capture", "Payum\\Core\\Request\\Convert", "Payum\\Core\\Request\\Generic", + "Payum\\Core\\Request\\GetHttpRequest", "Payum\\Core\\Request\\GetStatusInterface", "Payum\\Core\\Request\\Notify", + "Payum\\Core\\Request\\RenderTemplate", "Payum\\Core\\Security\\CryptedInterface", + "Payum\\Core\\Security\\CypherInterface", "Payum\\Core\\Security\\GenericTokenFactoryInterface", "Payum\\Core\\Security\\HttpRequestVerifierInterface", + "Payum\\Core\\Security\\TokenAggregateInterface", "Payum\\Core\\Security\\TokenInterface", "Payum\\Core\\Security\\Util\\Random", "Payum\\Core\\Storage\\AbstractStorage", diff --git a/composer.json b/composer.json index 761891b8bb..112542dc77 100644 --- a/composer.json +++ b/composer.json @@ -128,6 +128,7 @@ "symfony/translation": "^6.4.0 || ^7.1", "symfony/translation-contracts": "^3.3", "symfony/twig-bundle": "^6.4.0 || ^7.1", + "symfony/uid": "^6.4.0 || ^7.1", "symfony/ux-autocomplete": "^2.17", "symfony/ux-live-component": "^2.17", "symfony/ux-twig-component": "^2.17", diff --git a/config/bundles.php b/config/bundles.php index f4ed614387..13f91ecc94 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -47,12 +47,12 @@ return [ Sylius\Bundle\ShopBundle\SyliusShopBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], Sylius\Bundle\ApiBundle\SyliusApiBundle::class => ['all' => true], - Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true], - Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true], - Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true], - Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true], - FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true, 'test_cached' => true], - Sylius\Behat\Application\SyliusTestPlugin\SyliusTestPlugin::class => ['test' => true, 'test_cached' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_cached_payum' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_cached_payum' => true], + Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_cached_payum' => true], + Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_cached_payum' => true], + FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true, 'test_cached' => true, 'test_cached_payum' => true], + Sylius\Behat\Application\SyliusTestPlugin\SyliusTestPlugin::class => ['test' => true, 'test_cached' => true, 'test_cached_payum' => true], Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], SyliusLabs\DoctrineMigrationsExtraBundle\SyliusLabsDoctrineMigrationsExtraBundle::class => ['all' => true], Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], diff --git a/config/packages/_sylius.yaml b/config/packages/_sylius.yaml index 663f65eff9..1aa9eff54d 100644 --- a/config/packages/_sylius.yaml +++ b/config/packages/_sylius.yaml @@ -1,12 +1,9 @@ imports: - { resource: "@SyliusCoreBundle/Resources/config/app/config.yml" } - + - { resource: "@SyliusPayumBundle/Resources/config/app/config.yaml" } - { resource: "@SyliusShopBundle/Resources/config/app/config.yml" } - - { resource: "@SyliusApiBundle/Resources/config/app/config.yaml" } - - { resource: "@SyliusAdminBundle/Resources/config/app/config.yml" } - - { resource: "../parameters.yaml" } parameters: diff --git a/config/packages/test/_sylius.yaml b/config/packages/test/_sylius.yaml index d0b157e62a..9da08880cd 100644 --- a/config/packages/test/_sylius.yaml +++ b/config/packages/test/_sylius.yaml @@ -13,5 +13,6 @@ sylius_state_machine_abstraction: sylius_order_payment: '%test_sylius_state_machine_adapter%' sylius_order_shipping: '%test_sylius_state_machine_adapter%' sylius_payment: '%test_sylius_state_machine_adapter%' + sylius_payment_request: '%test_sylius_state_machine_adapter%' sylius_product_review: '%test_sylius_state_machine_adapter%' sylius_shipment: '%test_sylius_state_machine_adapter%' diff --git a/config/packages/test_cached_payum/_sylius.yaml b/config/packages/test_cached_payum/_sylius.yaml new file mode 100644 index 0000000000..c37a7353a0 --- /dev/null +++ b/config/packages/test_cached_payum/_sylius.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/_sylius.yaml" } diff --git a/config/packages/test_cached_payum/doctrine.yaml b/config/packages/test_cached_payum/doctrine.yaml new file mode 100644 index 0000000000..c3263c8bb9 --- /dev/null +++ b/config/packages/test_cached_payum/doctrine.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/doctrine.yaml" } diff --git a/config/packages/test_cached_payum/fidry_alice_data_fixtures.yaml b/config/packages/test_cached_payum/fidry_alice_data_fixtures.yaml new file mode 100644 index 0000000000..dc171f69eb --- /dev/null +++ b/config/packages/test_cached_payum/fidry_alice_data_fixtures.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/fidry_alice_data_fixtures.yaml" } diff --git a/config/packages/test_cached_payum/framework.yaml b/config/packages/test_cached_payum/framework.yaml new file mode 100644 index 0000000000..8ced67a150 --- /dev/null +++ b/config/packages/test_cached_payum/framework.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/framework.yaml" } diff --git a/config/packages/test_cached_payum/mailer.yaml b/config/packages/test_cached_payum/mailer.yaml new file mode 100644 index 0000000000..3397f9db95 --- /dev/null +++ b/config/packages/test_cached_payum/mailer.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/mailer.yaml" } diff --git a/config/packages/test_cached_payum/monolog.yaml b/config/packages/test_cached_payum/monolog.yaml new file mode 100644 index 0000000000..287961d338 --- /dev/null +++ b/config/packages/test_cached_payum/monolog.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/monolog.yaml" } diff --git a/config/packages/test_cached_payum/nelmio_alice.yaml b/config/packages/test_cached_payum/nelmio_alice.yaml new file mode 100644 index 0000000000..caec543dea --- /dev/null +++ b/config/packages/test_cached_payum/nelmio_alice.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: ../dev/nelmio_alice.yaml } diff --git a/config/packages/test_cached_payum/security.yaml b/config/packages/test_cached_payum/security.yaml new file mode 100644 index 0000000000..325f82c0dd --- /dev/null +++ b/config/packages/test_cached_payum/security.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/security.yaml" } diff --git a/config/packages/test_cached_payum/sylius_channel.yaml b/config/packages/test_cached_payum/sylius_channel.yaml new file mode 100644 index 0000000000..8498632aff --- /dev/null +++ b/config/packages/test_cached_payum/sylius_channel.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/sylius_channel.yaml" } diff --git a/config/packages/test_cached_payum/sylius_theme.yaml b/config/packages/test_cached_payum/sylius_theme.yaml new file mode 100644 index 0000000000..50898d6766 --- /dev/null +++ b/config/packages/test_cached_payum/sylius_theme.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/sylius_theme.yaml" } diff --git a/config/packages/test_cached_payum/sylius_uploader.yaml b/config/packages/test_cached_payum/sylius_uploader.yaml new file mode 100644 index 0000000000..430da6ead1 --- /dev/null +++ b/config/packages/test_cached_payum/sylius_uploader.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/sylius_uploader.yaml" } diff --git a/config/packages/test_cached_payum/twig.yaml b/config/packages/test_cached_payum/twig.yaml new file mode 100644 index 0000000000..79615ca899 --- /dev/null +++ b/config/packages/test_cached_payum/twig.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/twig.yaml" } diff --git a/config/packages/test_cached_payum/web_profiler.yaml b/config/packages/test_cached_payum/web_profiler.yaml new file mode 100644 index 0000000000..cc60840683 --- /dev/null +++ b/config/packages/test_cached_payum/web_profiler.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/web_profiler.yaml" } diff --git a/config/packages/test_cached_payum/webpack_encore.yaml b/config/packages/test_cached_payum/webpack_encore.yaml new file mode 100644 index 0000000000..7dedf7a728 --- /dev/null +++ b/config/packages/test_cached_payum/webpack_encore.yaml @@ -0,0 +1,2 @@ +imports: + - { resource: "../test_cached/webpack_encore.yaml" } diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 0000000000..31926f02ff --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 6 + time_based_uuid_version: 6 diff --git a/config/routes/sylius_shop.yaml b/config/routes/sylius_shop.yaml index 22e7a4a9a9..05f00c8803 100644 --- a/config/routes/sylius_shop.yaml +++ b/config/routes/sylius_shop.yaml @@ -5,7 +5,7 @@ sylius_shop: _locale: ^[A-Za-z]{2,4}(_([A-Za-z]{4}|[0-9]{3}))?(_([A-Za-z]{2}|[0-9]{3}))?$ sylius_shop_payum: - resource: "@SyliusShopBundle/Resources/config/routing/payum.yml" + resource: "@SyliusPayumBundle/Resources/config/routing/integrations/sylius_shop.yaml" sylius_shop_default_locale: path: / diff --git a/config/routes/test_cached_payum/routing.yaml b/config/routes/test_cached_payum/routing.yaml new file mode 100644 index 0000000000..6a6bd07275 --- /dev/null +++ b/config/routes/test_cached_payum/routing.yaml @@ -0,0 +1,5 @@ +sylius_test_plugin_main: + path: /test/main + controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController::templateAction + defaults: + template: "@SyliusTestPlugin/main.html.twig" diff --git a/config/services_test_cached_payum.yaml b/config/services_test_cached_payum.yaml new file mode 100644 index 0000000000..1461ba2cdb --- /dev/null +++ b/config/services_test_cached_payum.yaml @@ -0,0 +1,14 @@ +imports: + - { resource: "services_test_cached.yaml" } + +services: + ## + # Prioritize Payum by tagging services with a higher priority (or lower for tagged_iterator). + ## + + 'sylius.payum.payment_request.command_provider.offline': + class: Sylius\Bundle\PaymentBundle\CommandProvider\ActionsCommandProvider + arguments: + - !tagged_locator { tag: 'sylius.payum.payment_request.command_provider.offline', index_by: 'action' } + tags: + - { name: 'sylius.payment_request.command_provider', gateway-factory: 'offline', priority: -100 } diff --git a/features/admin/payment/managing_payments/payment_request/browsing_payment_requests.feature b/features/admin/payment/managing_payments/payment_request/browsing_payment_requests.feature new file mode 100644 index 0000000000..84df289684 --- /dev/null +++ b/features/admin/payment/managing_payments/payment_request/browsing_payment_requests.feature @@ -0,0 +1,25 @@ +@managing_payments +Feature: Browsing payment requests + In order to have an overview of all payment requests of a specific payment + As an Administrator + I want to browse all payment requests of a payment + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" + And the store ships everywhere for Free + And the store allows paying with "Cash on Delivery" + And there is an "#00000001" order with "PHP T-Shirt" product + And the payment request action "authorize" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And the payment request action "capture" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And the payment request action "sync" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And I am logged in as an administrator + + @api @ui + Scenario: Browsing payment requests of a payment + When I browse payments + And I want to view the payment requests of the first payment + Then there should be 3 payment requests on the list + And it should be the payment request with action "authorize" + And it should be the payment request with action "capture" + And it should be the payment request with action "sync" diff --git a/features/admin/payment/managing_payments/payment_request/filtering_payment_requests.feature b/features/admin/payment/managing_payments/payment_request/filtering_payment_requests.feature new file mode 100644 index 0000000000..444c4d18d0 --- /dev/null +++ b/features/admin/payment/managing_payments/payment_request/filtering_payment_requests.feature @@ -0,0 +1,46 @@ +@managing_payments +Feature: Filtering payment requests + In order to see specific payment requests + As an Administrator + I want to be able to filter payment requests on the list + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" + And the store ships everywhere for Free + And the store allows paying with "Cash on Delivery" + And the store allows paying with "Credit Card" + And there is an "#00000001" order with "PHP T-Shirt" product + And there is an "#00000002" order with "PHP T-Shirt" product + And the payment request action "authorize" has been executed for order "#00000001" with the payment method "Credit Card" + And the payment request action "authorize" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And the payment request action "capture" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And the payment request action "sync" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And the payment request action "capture" has been executed for order "#00000002" with the payment method "Cash on Delivery" + And I am logged in as an administrator + + @api @ui + Scenario: Filtering payment requests by action + When I browse payment requests of an order "#00000001" + And I filter by the "capture" action + Then there should be 1 payment request on the list + And it should be the payment request with action "capture" + + @api @ui @mink:chromedriver + Scenario: Filtering payment requests by payment method + When I browse payment requests of an order "#00000001" + And I filter by the "Credit Card" payment method + Then there should be 1 payment request on the list + And it should be the payment request with payment method "Credit Card" + + @api @ui + Scenario: Filtering payment requests by state "New" + When I browse payment requests of an order "#00000001" + And I filter by the "new" state + Then there should be 4 payment requests on the list + + @api @ui + Scenario: Filtering payment requests by state "Completed" + When I browse payment requests of an order "#00000001" + And I filter by the "completed" state + Then there should be 0 payment request on the list diff --git a/features/admin/payment/managing_payments/payment_request/seeing_payment_request_details.feature b/features/admin/payment/managing_payments/payment_request/seeing_payment_request_details.feature new file mode 100644 index 0000000000..a1af8f9f26 --- /dev/null +++ b/features/admin/payment/managing_payments/payment_request/seeing_payment_request_details.feature @@ -0,0 +1,23 @@ +@managing_payments +Feature: Seeing payment request's details + In order to have an overview of one of payment requests + As an Administrator + I want to be able to see its details + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" + And the store ships everywhere for Free + And the store allows paying with "Cash on Delivery" + And there is an "#00000001" order with "PHP T-Shirt" product + And the payment request action "authorize" has been executed for order "#00000001" with the payment method "Cash on Delivery" + And I am logged in as an administrator + + @api @ui + Scenario: Seeing payment request's details + When I view details of the payment request for the "#00000001" order + Then its method should be "Cash on Delivery" + And its action should be "Authorize" + And its state should be "New" + And its payload should has empty value + And its response data should has empty value diff --git a/features/admin/product/managing_products/checking_taxons_of_product.feature b/features/admin/product/managing_products/checking_taxons_of_product.feature index 8d233fe718..48270f2858 100644 --- a/features/admin/product/managing_products/checking_taxons_of_product.feature +++ b/features/admin/product/managing_products/checking_taxons_of_product.feature @@ -13,7 +13,7 @@ Feature: Checking taxons of a product And the product "T-Shirt" belongs to taxon "T-Shirts" And I am logged in as an administrator - @ui @no-api @mink:chromedriver + @no-api @ui @mink:chromedriver Scenario: Checking all taxons When I want to modify the "Shirt" product And I check all taxons @@ -21,7 +21,7 @@ Feature: Checking taxons of a product Then the product "Shirt" should have the "Clothes" taxon And the product "Shirt" should have the "T-Shirts" taxon - @ui @no-api @mink:chromedriver + @no-api @ui @mink:chromedriver Scenario: Unchecking all taxons When I want to modify the "T-Shirt" product And I uncheck all taxons @@ -29,7 +29,7 @@ Feature: Checking taxons of a product Then the product "Shirt" should not have the "Clothes" taxon And the product "Shirt" should not have the "T-Shirts" taxon - @ui @no-api @mink:chromedriver + @no-api @ui @mink:chromedriver Scenario: Checking all filtered taxons When I want to modify the "Shirt" product And I filter taxons by "T-Shirts" @@ -38,7 +38,7 @@ Feature: Checking taxons of a product Then the product "Shirt" should have the "T-Shirts" taxon But the product "Shirt" should not have the "Clothes" taxon - @ui @no-api @mink:chromedriver + @no-api @ui @mink:chromedriver Scenario: Unchecking all filtered taxons When I want to modify the "T-Shirt" product And I filter taxons by "T-Shirts" diff --git a/features/admin/product/managing_products/filtering_taxons_of_product.feature b/features/admin/product/managing_products/filtering_taxons_of_product.feature index 3acfd58ab0..a0dc738b83 100644 --- a/features/admin/product/managing_products/filtering_taxons_of_product.feature +++ b/features/admin/product/managing_products/filtering_taxons_of_product.feature @@ -10,7 +10,7 @@ Feature: Filtering product taxons And the store has a "Shirt" configurable product And I am logged in as an administrator - @ui @no-api @mink:chromedriver + @no-api @ui @mink:chromedriver Scenario: Filtering product taxons When I want to modify the "Shirt" product And I filter taxons by "T-Shirts" diff --git a/features/admin/product/managing_products/sorting_products_within_a_taxon_by_position.feature b/features/admin/product/managing_products/sorting_products_within_a_taxon_by_position.feature index 86195960cd..cd660e1393 100644 --- a/features/admin/product/managing_products/sorting_products_within_a_taxon_by_position.feature +++ b/features/admin/product/managing_products/sorting_products_within_a_taxon_by_position.feature @@ -80,7 +80,7 @@ Feature: Sorting listed products from a taxon by position And the 7th product on this page should be named "Pug Master" And this product should be at position 16 - @no-api @ui @mink:chromedriver + @no-api @tod-ui @mink:chromedriver Scenario: Setting two products to the positions overflowing the max available position on the non-last page When I am browsing the 1st page of products from "Soft Toys" taxon And I set the position of "Old pug" to "25" diff --git a/features/hybrid/checkout/changing_payment_method/cancelling_payment_request_when_payment_method_is_changed.feature b/features/hybrid/checkout/changing_payment_method/cancelling_payment_request_when_payment_method_is_changed.feature new file mode 100644 index 0000000000..6c4bb94766 --- /dev/null +++ b/features/hybrid/checkout/changing_payment_method/cancelling_payment_request_when_payment_method_is_changed.feature @@ -0,0 +1,29 @@ +@paying_for_order +Feature: Cancelling payment request when payment method is changed + In order to pay with the correct payment method + As an Administrator + I want the customer's payment requests with the previous payment method to be cancelled + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" + And the store ships everywhere for Free + And the store allows paying with "Cash on Delivery" + And the store also allows paying with "Bank Transfer" + And I am a logged in customer + And I placed an order "#00000001" + And I bought a single "PHP T-Shirt" + And I addressed it to "Lucifer Morningstar", "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address + And I chose "Free" shipping method with "Cash on Delivery" payment + And there is a "new" "authorize" payment request for order "#00000001" using the "Cash on Delivery" payment method + And there is also a "processing" "status" payment request for order "#00000001" using the "Cash on Delivery" payment method + And there is also a "completed" "capture" payment request for order "#00000001" using the "Cash on Delivery" payment method + And there is an administrator "sylius@example.com" identified by "sylius" + + @api @ui + Scenario: Cancelling only not finalized payment requests when the payment method has changed + When I browse my orders + And I change my payment method to "Bank Transfer" + Then the administrator should see the payment request with action "authorize" for "Cash on Delivery" payment method and state "cancelled" + And the administrator should see the payment request with action "status" for "Cash on Delivery" payment method and state "cancelled" + And the administrator should see the payment request with action "capture" for "Cash on Delivery" payment method and state "completed" diff --git a/features/shop/checkout/paying_for_order/cancelling_payment_request_when_payment_method_is_changed.feature b/features/shop/checkout/paying_for_order/cancelling_payment_request_when_payment_method_is_changed.feature new file mode 100644 index 0000000000..055289e0af --- /dev/null +++ b/features/shop/checkout/paying_for_order/cancelling_payment_request_when_payment_method_is_changed.feature @@ -0,0 +1,28 @@ +@paying_for_order +Feature: Cancelling payment request when payment method is changed + In order to pay with the correct payment method + As a Customer + I want my payment requests with the previous payment method to be cancelled + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" + And the store ships everywhere for Free + And the store allows paying with "Cash on Delivery" + And the store also allows paying with "Bank Transfer" + And I am a logged in customer + And I placed an order "#00000001" + And I bought a single "PHP T-Shirt" + And I addressed it to "Lucifer Morningstar", "Seaside Fwy", "90802" "Los Angeles" in the "United States" with identical billing address + And I chose "Free" shipping method with "Cash on Delivery" payment + And there is a "new" "authorize" payment request for order "#00000001" using the "Cash on Delivery" payment method + And there is also a "processing" "status" payment request for order "#00000001" using the "Cash on Delivery" payment method + And there is also a "completed" "capture" payment request for order "#00000001" using the "Cash on Delivery" payment method + + @api @no-ui + Scenario: Cancelling only not finalized payment requests when the payment method has changed + When I view the summary of my order "#00000001" + And I change payment method to "Bank Transfer" after checkout + Then my payment request with action "authorize" for payment method "Cash on Delivery" should have state "cancelled" + And my payment request with action "status" for payment method "Cash on Delivery" should have state "cancelled" + And my payment request with action "capture" for payment method "Cash on Delivery" should have state "completed" diff --git a/features/shop/checkout/paying_for_order/changing_offline_payment_method_after_order_confirmation.feature b/features/shop/checkout/paying_for_order/changing_offline_payment_method_after_order_confirmation.feature index f210e156f0..e27714f35c 100644 --- a/features/shop/checkout/paying_for_order/changing_offline_payment_method_after_order_confirmation.feature +++ b/features/shop/checkout/paying_for_order/changing_offline_payment_method_after_order_confirmation.feature @@ -21,7 +21,7 @@ Feature: Changing the offline payment method after order confirmation And I retry the payment with "Offline" payment method Then I should have chosen "Offline" payment method - @no-api @ui @javascript + @api @ui @javascript Scenario: Retrying the payment with different Offline payment works correctly together with inventory Given there is 1 unit of product "PHP T-Shirt" available in the inventory And this product is tracked by the inventory @@ -33,7 +33,7 @@ Feature: Changing the offline payment method after order confirmation And I retry the payment with "Offline" payment method Then I should have chosen "Offline" payment method - @no-api @ui @javascript + @todo-api @ui @javascript Scenario: Being unable to pay for my order if my chosen payment method gets disabled Given I added product "PHP T-Shirt" to the cart When I complete addressing step with email "john@example.com" and "United States" based billing address @@ -44,7 +44,7 @@ Feature: Changing the offline payment method after order confirmation And I try to pay for my order Then I should be notified to choose a payment method - @no-api @ui @javascript + @api @ui @javascript Scenario: Retrying the payment with different payment method after order confirmation when the original is disabled Given I added product "PHP T-Shirt" to the cart When I complete addressing step with email "john@example.com" and "United States" based billing address @@ -55,7 +55,7 @@ Feature: Changing the offline payment method after order confirmation And I retry the payment with "Offline" payment method Then I should have chosen "Offline" payment method - @no-api @ui @javascript + @todo-api @ui @javascript Scenario: Being unable to pay for my order if no methods are available Given I added product "PHP T-Shirt" to the cart When I complete addressing step with email "john@example.com" and "United States" based billing address diff --git a/features/shop/checkout/paying_for_order/preventing_to_pay_for_cancelled_order.feature b/features/shop/checkout/paying_for_order/preventing_to_pay_for_cancelled_order.feature index 9fe59a91e6..a111a54377 100644 --- a/features/shop/checkout/paying_for_order/preventing_to_pay_for_cancelled_order.feature +++ b/features/shop/checkout/paying_for_order/preventing_to_pay_for_cancelled_order.feature @@ -13,7 +13,7 @@ Feature: Preventing to pay for the cancelled order And the store allows paying Offline And there is a customer "sylius@example.com" that placed an order "#00000022" - @no-api @ui + @api @ui Scenario: Not being able to pay for cancelled order Given the customer bought 3 "Iron Maiden T-Shirt" products And the customer chose "Free" shipping method to "United States" with "Offline" payment diff --git a/phparkitect.php b/phparkitect.php index de74150980..a59ad89288 100644 --- a/phparkitect.php +++ b/phparkitect.php @@ -6,8 +6,8 @@ use Arkitect\ClassSet; use Arkitect\CLI\Config; use Arkitect\Expression\ForClasses\Extend; use Arkitect\Expression\ForClasses\HaveNameMatching; -use Arkitect\Expression\ForClasses\IsNotAbstract; use Arkitect\Expression\ForClasses\IsFinal; +use Arkitect\Expression\ForClasses\IsNotAbstract; use Arkitect\Expression\ForClasses\NotDependsOnTheseNamespaces; use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces; use Arkitect\Rules\Rule; @@ -75,6 +75,7 @@ return static function (Config $config): void 'Sylius\Bundle\AdminBundle', 'Sylius\Bundle\ApiBundle', 'Sylius\Bundle\CoreBundle', + 'Sylius\Bundle\PayumBundle', 'Sylius\Bundle\ShopBundle', ) ->that(new ResideInOneOfTheseNamespaces('Sylius\Bundle')) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index baa6a40fec..f548d69e99 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1100,6 +1100,26 @@ parameters: count: 1 path: src/Sylius/Bundle/CoreBundle/Order/Checker/OrderPromotionsIntegrityChecker.php + - + message: "#^Call to method toBinary\\(\\) on an unknown class Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Bundle/CoreBundle/PaymentRequest/CommandProvider/Offline/CapturePaymentRequestCommandProvider.php + + - + message: "#^Call to method toBinary\\(\\) on an unknown class Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Bundle/CoreBundle/PaymentRequest/CommandProvider/Payum/AuthorizeCommandProvider.php + + - + message: "#^Call to method toBinary\\(\\) on an unknown class Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Bundle/CoreBundle/PaymentRequest/CommandProvider/Payum/CaptureCommandProvider.php + + - + message: "#^Call to method toBinary\\(\\) on an unknown class Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Bundle/CoreBundle/PaymentRequest/CommandProvider/Payum/StatusCommandProvider.php + - message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\PriceHistory\\\\Command\\\\ApplyLowestPriceOnChannelPricings\\:\\:__construct\\(\\) has parameter \\$channelPricingIds with no value type specified in iterable type array\\.$#" count: 1 @@ -1330,41 +1350,6 @@ parameters: count: 1 path: src/Sylius/Bundle/PaymentBundle/Form/Type/PaymentGatewayChoiceType.php - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Controller\\\\PayumController\\:\\:__construct\\(\\) has parameter \\$orderRepository with generic interface Sylius\\\\Component\\\\Order\\\\Repository\\\\OrderRepositoryInterface but does not specify its types\\: T$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Controller/PayumController.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Controller\\\\PayumController\\:\\:prepareCaptureAction\\(\\) has parameter \\$tokenValue with no type specified\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Controller/PayumController.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Controller\\\\PayumController\\:\\:provideTokenBasedOnPayment\\(\\) has parameter \\$redirectOptions with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Controller/PayumController.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Factory\\\\GetStatusFactory\\:\\:createNewWithModel\\(\\) has parameter \\$model with no type specified\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Factory/GetStatusFactory.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Factory\\\\GetStatusFactoryInterface\\:\\:createNewWithModel\\(\\) has parameter \\$model with no type specified\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Factory/GetStatusFactoryInterface.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Factory\\\\ResolveNextRouteFactory\\:\\:createNewWithModel\\(\\) has parameter \\$model with no type specified\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Factory/ResolveNextRouteFactory.php - - - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Factory\\\\ResolveNextRouteFactoryInterface\\:\\:createNewWithModel\\(\\) has parameter \\$model with no type specified\\.$#" - count: 1 - path: src/Sylius/Bundle/PayumBundle/Factory/ResolveNextRouteFactoryInterface.php - - message: "#^Method Sylius\\\\Bundle\\\\PayumBundle\\\\Request\\\\ResolveNextRoute\\:\\:getRouteParameters\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -1675,6 +1660,11 @@ parameters: count: 1 path: src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php + - + message: "#^Method Sylius\\\\Bundle\\\\ShopBundle\\\\DependencyInjection\\\\SyliusShopExtension\\:\\:configureOrderPay\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php + - message: "#^Method Sylius\\\\Bundle\\\\ShopBundle\\\\DependencyInjection\\\\SyliusShopExtension\\:\\:registerCheckoutRedirectListener\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#" count: 1 @@ -2910,6 +2900,26 @@ parameters: count: 1 path: src/Sylius/Component/Payment/Model/PaymentInterface.php + - + message: "#^Call to method toBinary\\(\\) on an unknown class Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Component/Payment/Model/PaymentRequest.php + + - + message: "#^Method Sylius\\\\Component\\\\Payment\\\\Model\\\\PaymentRequest\\:\\:getHash\\(\\) has invalid return type Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Component/Payment/Model/PaymentRequest.php + + - + message: "#^Property Sylius\\\\Component\\\\Payment\\\\Model\\\\PaymentRequest\\:\\:\\$hash has unknown class Symfony\\\\Component\\\\Uid\\\\Uuid as its type\\.$#" + count: 1 + path: src/Sylius/Component/Payment/Model/PaymentRequest.php + + - + message: "#^Method Sylius\\\\Component\\\\Payment\\\\Model\\\\PaymentRequestInterface\\:\\:getHash\\(\\) has invalid return type Symfony\\\\Component\\\\Uid\\\\Uuid\\.$#" + count: 1 + path: src/Sylius/Component/Payment/Model/PaymentRequestInterface.php + - message: "#^Method Sylius\\\\Component\\\\Payment\\\\Resolver\\\\DefaultPaymentMethodResolver\\:\\:__construct\\(\\) has parameter \\$paymentMethodRepository with generic interface Sylius\\\\Component\\\\Payment\\\\Repository\\\\PaymentMethodRepositoryInterface but does not specify its types\\: T$#" count: 1 diff --git a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php index 57d7088fbd..563fb30bdb 100644 --- a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php +++ b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php @@ -28,7 +28,7 @@ final class Configuration implements ConfigurationInterface $rootNode ->addDefaultsIfNotSet() ->children() - ->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end() + ->scalarNode('default_adapter')->defaultValue('symfony_workflow')->end() ->arrayNode('graphs_to_adapters_mapping') ->useAttributeAsKey('graph_name') ->scalarPrototype()->end() diff --git a/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php b/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php index c92a412b76..3e881c1f47 100644 --- a/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php +++ b/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php @@ -51,7 +51,7 @@ final class ConfigurationTest extends TestCase ], ], [ - 'default_adapter' => 'winzou_state_machine', + 'default_adapter' => 'symfony_workflow', 'graphs_to_adapters_mapping' => [ 'order' => 'symfony_workflow', 'payment' => 'winzou_state_machine', diff --git a/src/Sylius/Behat/Client/ApiPlatformClient.php b/src/Sylius/Behat/Client/ApiPlatformClient.php index a93447efe2..32833811e2 100644 --- a/src/Sylius/Behat/Client/ApiPlatformClient.php +++ b/src/Sylius/Behat/Client/ApiPlatformClient.php @@ -55,10 +55,10 @@ final class ApiPlatformClient implements ApiClientInterface /** @param array $queryParameters */ public function subResourceIndex(string $resource, string $subResource, string $id, array $queryParameters = [], bool $forgetResponse = false): Response { - $request = $this->requestFactory->subResourceIndex($this->section, $resource, $id, $subResource, $queryParameters); - $request->authorize($this->getToken(), $this->authorizationHeader); + $this->request = $this->requestFactory->subResourceIndex($this->section, $resource, $id, $subResource, $queryParameters); + $this->request->authorize($this->getToken(), $this->authorizationHeader); - return $this->request($request, $forgetResponse); + return $this->request($this->request, $forgetResponse); } public function show(string $resource, string $id, bool $forgetResponse = false): Response diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentRequestsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentRequestsContext.php new file mode 100644 index 0000000000..940ccf79b4 --- /dev/null +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentRequestsContext.php @@ -0,0 +1,188 @@ +client->subResourceIndex( + Resources::PAYMENTS, + Resources::PAYMENT_REQUESTS, + (string) $order->getLastPayment()->getId(), + ); + } + + /** + * @When I view details of the payment request for the :order order + */ + public function iViewDetailsOfThePaymentRequestForTheOrder(OrderInterface $order): void + { + $paymentRequest = $this->paymentRequestRepository->findOneBy(['payment' => $order->getLastPayment()]); + + $this->client->show(Resources::PAYMENT_REQUESTS, (string) $paymentRequest->getHash()); + } + + /** + * @When I filter by the :action action + */ + public function iFilterByTheAction(string $action): void + { + $this->client->addFilter('action', $action); + $this->client->filter(); + } + + /** + * @When I filter by the :paymentMethod payment method + */ + public function iFilterByThePaymentMethod(PaymentMethodInterface $paymentMethod): void + { + $this->client->addFilter('method.code', $paymentMethod->getCode()); + $this->client->filter(); + } + + /** + * @When I filter by the :state state + */ + public function iFilterByTheState(string $state): void + { + $this->client->addFilter('state', $state); + $this->client->filter(); + } + + /** + * @Then /^there should be (\d+) payment requests? on the list$/ + */ + public function thereShouldBeProductVariantsOnTheList(int $count): void + { + Assert::same($this->responseChecker->countCollectionItems($this->client->getLastResponse()), $count); + } + + /** + * @Then it should be the payment request with action :action + */ + public function itShouldBeThePaymentRequestWithAction(string $action): void + { + Assert::true($this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'action', $action)); + } + + /** + * @Then it should be the payment request with payment method :paymentMethod + */ + public function itShouldBeThePaymentRequestWithPaymentMethod(PaymentMethodInterface $paymentMethod): void + { + Assert::true( + $this->responseChecker->hasItemWithValue( + $this->client->getLastResponse(), + 'method', + $this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin'), + ), + ); + } + + /** + * @Then its method should be :paymentMethod + */ + public function itsMethodShouldBe(PaymentMethodInterface $paymentMethod): void + { + Assert::true( + $this->responseChecker->hasValue( + $this->client->getLastResponse(), + 'method', + $this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin'), + ), + ); + } + + /** + * @Then /^its (action|state) should be "([^"]+)"$/ + */ + public function itsActionStateShouldBe(string $field, string $value): void + { + Assert::true($this->responseChecker->hasValue($this->client->getLastResponse(), $field, strtolower($value))); + } + + /** + * @Then its payload should has empty value + */ + public function itsPayloadShouldHasEmptyValue(): void + { + Assert::isEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'payload')); + } + + /** + * @Then its response data should has empty value + */ + public function itsResponseDataShouldHasEmptyValue(): void + { + Assert::isEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'responseData')); + } + + /** + * @Then the administrator should see the payment request with action :action for :paymentMethod payment method and state :state + */ + public function administratorShouldSeeThePaymentRequestWithActionAndState(string $action, PaymentMethodInterface $paymentMethod, string $state): void + { + $adminUser = $this->sharedStorage->get('administrator'); + + /** @var OrderInterface $order */ + $this->sharedSecurityService->performActionAsAdminUser($adminUser, function () { + $order = $this->sharedStorage->get('order'); + + $request = $this->requestFactory->custom('/api/v2/admin/payments/' . $order->getLastPayment()->getId() . '/payment-requests', HTTPRequest::METHOD_GET, [], $this->client->getToken()); + $this->client->executeCustomRequest($request); + }); + + Assert::true( + $this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'action', $action), + sprintf('Payment request should have action %s', $action), + ); + Assert::true( + $this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'method', $this->iriConverter->getIriFromResourceInSection($paymentMethod, 'admin')), + sprintf('Payment request should have payment method %s', $paymentMethod->getCode()), + ); + Assert::true( + $this->responseChecker->hasItemWithValue($this->client->getLastResponse(), 'state', $state), + sprintf('Payment request should have state %s', $state), + ); + } +} diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentsContext.php index d79eb9302a..3b143443fd 100644 --- a/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentsContext.php +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingPaymentsContext.php @@ -58,6 +58,20 @@ final readonly class ManagingPaymentsContext implements Context $this->client->customItemAction(Resources::ORDERS, $order->getTokenValue(), HttpRequest::METHOD_GET, 'payments'); } + /** + * @When I want to view the payment requests of the first payment + */ + public function iWantToViewThePaymentRequestsOfTheFirstPayment(): void + { + $response = $this->client->getLastResponse(); + + $this->client->subResourceIndex( + Resources::PAYMENTS, + Resources::PAYMENT_REQUESTS, + (string) $this->responseChecker->getCollection($response)[0]['id'], + ); + } + /** * @Then I should see the details of order :order */ diff --git a/src/Sylius/Behat/Context/Api/Resources.php b/src/Sylius/Behat/Context/Api/Resources.php index b5d4709ccf..8b8b0897a9 100644 --- a/src/Sylius/Behat/Context/Api/Resources.php +++ b/src/Sylius/Behat/Context/Api/Resources.php @@ -55,6 +55,8 @@ final class Resources public const PAYMENTS = 'payments'; + public const PAYMENT_REQUESTS = 'payment-requests'; + public const PRODUCT_ASSOCIATION_TYPES = 'product-association-types'; public const PRODUCT_ASSOCIATIONS = 'product-associations'; diff --git a/src/Sylius/Behat/Context/Api/Shop/Checkout/CheckoutOrderDetailsContext.php b/src/Sylius/Behat/Context/Api/Shop/Checkout/CheckoutOrderDetailsContext.php new file mode 100644 index 0000000000..2c775ab287 --- /dev/null +++ b/src/Sylius/Behat/Context/Api/Shop/Checkout/CheckoutOrderDetailsContext.php @@ -0,0 +1,89 @@ +sharedStorage->set('cart_token', $order->getTokenValue()); + $this->client->show(Resources::ORDERS, $order->getTokenValue()); + } + + /** + * @Then I should be able to pay (again) + */ + public function iShouldBeAbleToPay(): void + { + $state = $this->getLatestPaymentState(); + Assert::eq($state, PaymentInterface::STATE_NEW); + } + + /** + * @Then I should not be able to pay (again) + */ + public function iShouldNotBeAbleToPay(): void + { + $state = $this->getLatestPaymentState(); + Assert::notEq($state, PaymentInterface::STATE_NEW); + } + + /** + * @When I want to pay for my order + * @When I go to the change payment method page + */ + public function iWantToPayForMyOrder(): void + { + $this->client->show(Resources::ORDERS, $this->sharedStorage->get('cart_token')); + } + + private function getLatestPaymentState(): ?string + { + $response = $this->client->show(Resources::ORDERS, $this->sharedStorage->get('cart_token')); + Assert::same($this->client->getLastResponse()->getStatusCode(), 200); + + // If the payment is canceled we won't be able to retrieve it because only new one are retrievable + if (OrderPaymentStates::STATE_CANCELLED === $this->responseChecker->getValue($response, 'paymentState')) { + return PaymentInterface::STATE_CANCELLED; + } + + $payments = $this->responseChecker->getValue($response, 'payments'); + $payment = end($payments); + + $paymentId = $payment['id']; + $response = $this->client->requestGet(sprintf('/api/v2/shop/orders/%s/payments/%s', $this->sharedStorage->get('cart_token'), $paymentId)); + + return $this->responseChecker->getValue($response, 'state'); + } +} diff --git a/src/Sylius/Behat/Context/Api/Shop/OrderContext.php b/src/Sylius/Behat/Context/Api/Shop/OrderContext.php index c3f5ce44f3..3d83e94d9b 100644 --- a/src/Sylius/Behat/Context/Api/Shop/OrderContext.php +++ b/src/Sylius/Behat/Context/Api/Shop/OrderContext.php @@ -110,6 +110,8 @@ final readonly class OrderContext implements Context public function iViewTheSummaryOfMyOrder(OrderInterface $order): void { $this->shopClient->show(Resources::ORDERS, $order->getTokenValue()); + + $this->sharedStorage->set('cart_token', $order->getTokenValue()); } /** diff --git a/src/Sylius/Behat/Context/Api/Shop/PaymentRequestContext.php b/src/Sylius/Behat/Context/Api/Shop/PaymentRequestContext.php new file mode 100644 index 0000000000..238b53a977 --- /dev/null +++ b/src/Sylius/Behat/Context/Api/Shop/PaymentRequestContext.php @@ -0,0 +1,122 @@ +client->show(Resources::ORDERS, $this->sharedStorage->get('cart_token')); + + $payments = $this->responseChecker->getValue($this->client->getLastResponse(), 'payments'); + $payment = end($payments); + $this->postPaymentRequest($payment, $payload); + + $uri = $this->responseChecker->getValue($this->client->getLastResponse(), '@id'); + $this->sharedStorage->set('payment_request_uri', $uri); + } + + /** + * @When I try to update my payment request + */ + public function iTryToUpdateMyPaymentRequest(array $payload = []): void + { + $this->putPaymentRequest($this->sharedStorage->get('payment_request_uri'), $payload); + } + + private function postPaymentRequest(array $payment, array $payload): void + { + $request = $this->requestFactory->create( + 'shop', + Resources::PAYMENT_REQUESTS, + 'Authorization', + $this->client->getToken(), + ); + + $request->setContent([ + 'paymentId' => $payment['id'], + 'paymentMethodCode' => $payment['method'], + 'payload' => $payload, + ]); + + $this->client->executeCustomRequest($request); + } + + public function putPaymentRequest(string $paymentRequestUri, array $payload = []): void + { + $request = $this->requestFactory->custom( + $paymentRequestUri, + HttpRequest::METHOD_PUT, + [], + $this->client->getToken(), + ); + + $request->setContent([ + 'payload' => $payload, + ]); + + $this->client->executeCustomRequest($request); + } + + /** + * @Then my payment request with action :action for payment method :paymentMethod should have state :state + */ + public function myPaymentRequestShouldBeCancelled(string $action, PaymentMethodInterface $paymentMethod, string $state): void + { + $request = $this->getRequestForPaymentRequestWithAction($action); + Assert::notNull($request, sprintf('Payment request with action %s not found', $action)); + + $this->client->executeCustomRequest($request); + $response = $this->client->getLastResponse(); + + Assert::same($this->responseChecker->getValue($response, 'action'), $action, sprintf('Payment request should have action %s', $action)); + Assert::true(str_contains($this->responseChecker->getValue($response, 'method'), $paymentMethod->getCode()), sprintf('Payment request should have payment method %s', $paymentMethod->getCode())); + Assert::same($this->responseChecker->getValue($response, 'state'), $state, sprintf('Payment request should have state %s', $state)); + } + + private function getRequestForPaymentRequestWithAction(string $action): ?Request + { + $orderToken = $this->sharedStorage->get('cart_token'); + $order = $this->client->show(Resources::ORDERS, $orderToken); + $payments = $this->responseChecker->getValue($order, 'payments'); + $paymentId = end($payments)['id']; + $paymentRequest = $this->paymentRequestRepository->findOneBy(['payment' => $paymentId, 'action' => $action]); + + return $paymentRequest ? $this->requestFactory->custom('/api/v2/shop/payment-requests/' . $paymentRequest->getHash(), HttpRequest::METHOD_GET, [], $this->client->getToken()) : null; + } +} diff --git a/src/Sylius/Behat/Context/Setup/PaymentContext.php b/src/Sylius/Behat/Context/Setup/PaymentContext.php index 28fdb0d812..e5d3b91e73 100644 --- a/src/Sylius/Behat/Context/Setup/PaymentContext.php +++ b/src/Sylius/Behat/Context/Setup/PaymentContext.php @@ -17,14 +17,13 @@ use Behat\Behat\Context\Context; use Doctrine\Persistence\ObjectManager; use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Bundle\CoreBundle\Fixture\Factory\ExampleFactoryInterface; +use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; use Sylius\Component\Core\Formatter\StringInflector; use Sylius\Component\Core\Model\ChannelInterface; -use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\Component\Payment\Model\PaymentMethodTranslationInterface; use Sylius\Component\Payment\Repository\PaymentMethodRepositoryInterface; use Sylius\Resource\Factory\FactoryInterface; -use Webmozart\Assert\Assert; final readonly class PaymentContext implements Context { @@ -71,7 +70,7 @@ final readonly class PaymentContext implements Context /** * @Given /^the store allows paying (\w+) for (all channels)$/ */ - public function storeAllowsPayingForAllChannels($paymentMethodName, array $channels) + public function storeAllowsPayingForAllChannels($paymentMethodName, array $channels): void { $paymentMethod = $this->createPaymentMethod($paymentMethodName, StringInflector::nameToUppercaseCode($paymentMethodName), 'Offline', 'Payment method', false); @@ -91,7 +90,7 @@ final readonly class PaymentContext implements Context /** * @Given /^(this payment method) is named "([^"]+)" in the "([^"]+)" locale$/ */ - public function thisPaymentMethodIsNamedIn(PaymentMethodInterface $paymentMethod, $name, $locale) + public function thisPaymentMethodIsNamedIn(PaymentMethodInterface $paymentMethod, $name, $locale): void { /** @var PaymentMethodTranslationInterface $translation */ $translation = $this->paymentMethodTranslationFactory->createNew(); @@ -108,7 +107,7 @@ final readonly class PaymentContext implements Context * @Given /^(this payment method) (?:has been|is) disabled$/ * @When the payment method :paymentMethod gets disabled */ - public function theStoreHasAPaymentMethodDisabled(PaymentMethodInterface $paymentMethod) + public function theStoreHasAPaymentMethodDisabled(PaymentMethodInterface $paymentMethod): void { $paymentMethod->disable(); @@ -118,7 +117,7 @@ final readonly class PaymentContext implements Context /** * @Given /^(it) has instructions "([^"]+)"$/ */ - public function itHasInstructions(PaymentMethodInterface $paymentMethod, $instructions) + public function itHasInstructions(PaymentMethodInterface $paymentMethod, $instructions): void { $paymentMethod->setInstructions($instructions); @@ -128,7 +127,7 @@ final readonly class PaymentContext implements Context /** * @Given the store has :paymentMethodName payment method not assigned to any channel */ - public function theStoreHasPaymentMethodNotAssignedToAnyChannel($paymentMethodName) + public function theStoreHasPaymentMethodNotAssignedToAnyChannel(string $paymentMethodName): void { $this->createPaymentMethod($paymentMethodName, 'PM_' . $paymentMethodName, 'Offline', 'Payment method', false); } @@ -136,8 +135,9 @@ final readonly class PaymentContext implements Context /** * @Given the payment method :paymentMethod requires authorization before capturing */ - public function thePaymentMethodRequiresAuthorizationBeforeCapturing(PaymentMethodInterface $paymentMethod) + public function thePaymentMethodRequiresAuthorizationBeforeCapturing(PaymentMethodInterface $paymentMethod): void { + /** @var GatewayConfigInterface $config */ $config = $paymentMethod->getGatewayConfig(); $config->setConfig(array_merge($config->getConfig(), ['use_authorize' => true])); $paymentMethod->setGatewayConfig($config); @@ -161,16 +161,6 @@ final readonly class PaymentContext implements Context $paymentMethod->addChannel($channel); } - /** - * @Then /^the (latest order) should have a payment with state "([^"]+)"$/ - */ - public function theLatestOrderHasAuthorizedPayment(OrderInterface $order, string $state) - { - $payment = $order->getLastPayment(); - - Assert::eq($payment->getState(), $state); - } - private function createPaymentMethod( string $name, string $code, diff --git a/src/Sylius/Behat/Context/Setup/PaymentRequestContext.php b/src/Sylius/Behat/Context/Setup/PaymentRequestContext.php new file mode 100644 index 0000000000..eba9c552e3 --- /dev/null +++ b/src/Sylius/Behat/Context/Setup/PaymentRequestContext.php @@ -0,0 +1,86 @@ +getLastPayment()->getId(), + paymentMethodCode: $paymentMethod->getCode(), + action: $action, + ); + + $this->commandBus->dispatch($addPaymentRequest); + } + + /** + * @Given there is (also) a :state :action payment request for order :order using the :paymentMethod payment method + */ + public function thePaymentRequestActionHasBeenExecutedForOrderWithThePaymentMethodAndState( + string $state = null, + string $action, + OrderInterface $order, + PaymentMethodInterface $paymentMethod, + ): void { + $paymentRequest = $this->paymentRequestFactory->create($order->getLastPayment(), $paymentMethod); + + if ($state !== PaymentRequestInterface::STATE_NEW) { + $this->stateMachine->apply( + $paymentRequest, + PaymentRequestTransitions::GRAPH, + $this->getTransitionForState($state), + ); + } + + $paymentRequest->setAction($action); + + $this->paymentRequestRepository->add($paymentRequest); + } + + private function getTransitionForState(string $state): string + { + return match ($state) { + 'completed' => PaymentRequestTransitions::TRANSITION_COMPLETE, + 'processing' => PaymentRequestTransitions::TRANSITION_PROCESS, + default => throw new \InvalidArgumentException(sprintf('Invalid state "%s" provided.', $state)), + }; + } +} diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentRequestsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentRequestsContext.php new file mode 100644 index 0000000000..4b7e7ba572 --- /dev/null +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentRequestsContext.php @@ -0,0 +1,151 @@ +indexPage->open(['paymentId' => $order->getLastPayment()->getId()]); + } + + /** + * @When I view details of the payment request for the :order order + */ + public function iViewDetailsOfThePaymentRequestForTheOrder(OrderInterface $order): void + { + $payment = $order->getLastPayment(); + $paymentRequest = $this->paymentRequestRepository->findOneBy(['payment' => $payment]); + + $this->showPage->open([ + 'hash' => $paymentRequest->getHash(), + 'paymentId' => $payment->getId(), + ]); + } + + /** + * @When I filter by the :action action + */ + public function iFilterByTheAction(string $action): void + { + $this->indexPage->chooseActionToFilter($action); + $this->indexPage->filter(); + } + + /** + * @When I filter by the :paymentMethod payment method + */ + public function iFilterByThePaymentMethod(PaymentMethodInterface $paymentMethod): void + { + $this->indexPage->choosePaymentMethodToFilter($paymentMethod->getName()); + $this->indexPage->filter(); + } + + /** + * @When I filter by the :state state + */ + public function iFilterByTheState(string $state): void + { + $this->indexPage->chooseStateToFilter($state); + $this->indexPage->filter(); + } + + /** + * @Then /^there should be (\d+) payment requests? on the list$/ + */ + public function thereShouldBeProductVariantsOnTheList(int $count): void + { + Assert::same($this->indexPage->countItems(), $count); + } + + /** + * @Then it should be the payment request with action :action + */ + public function itShouldBeThePaymentRequestWithAction(string $action): void + { + Assert::true($this->indexPage->isSingleResourceOnPage(['action' => $action])); + } + + /** + * @Then it should be the payment request with payment method :paymentMethod + */ + public function itShouldBeThePaymentRequestWithPaymentMethod(PaymentMethodInterface $paymentMethod): void + { + Assert::true($this->indexPage->isSingleResourceOnPage(['method' => $paymentMethod->getName()])); + } + + /** + * @Then its :field should be :value + */ + public function itsFieldShouldBe(string $field, string $value): void + { + Assert::same($this->showPage->getFieldText($field), $value); + } + + /** + * @Then its payload should has empty value + */ + public function itsPayloadShouldHasEmptyValue(): void + { + Assert::same($this->showPage->getFieldText('payload'), 'null'); + } + + /** + * @Then its response data should has empty value + */ + public function itsResponseDataShouldBe(): void + { + Assert::same($this->showPage->getFieldText('response_data'), json_encode([])); + } + + /** + * @Then the administrator should see the payment request with action :action for :method payment method and state :state + */ + public function administratorShouldSeeThePaymentRequestWithActionAndState(string $action, string $paymentMethod, string $state): void + { + $adminUser = $this->sharedStorage->get('administrator'); + + $this->sharedSecurityService->performActionAsAdminUser($adminUser, function () { + $this->iBrowsePaymentRequestsOfACustomer($this->sharedStorage->get('order')); + }); + + Assert::true($this->indexPage->isSingleResourceOnPage([ + 'action' => $action, + 'state' => $state, + 'method' => $paymentMethod, + ])); + } +} diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentsContext.php index 3effca2332..308462e854 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingPaymentsContext.php @@ -80,6 +80,14 @@ final class ManagingPaymentsContext implements Context $this->indexPage->chooseChannelFilter($channelName); } + /** + * @When I want to view the payment requests of the first payment + */ + public function iWantToViewThePaymentRequestsOfTheFirstPayment(): void + { + $this->indexPage->showPaymentRequestOfNthPayment(1); + } + /** * @Then I should see :count payments in the list * @Then I should see a single payment in the list diff --git a/src/Sylius/Behat/Context/Ui/PaypalContext.php b/src/Sylius/Behat/Context/Ui/PaypalContext.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Sylius/Behat/Page/Admin/Payment/IndexPage.php b/src/Sylius/Behat/Page/Admin/Payment/IndexPage.php index 0a2d7bef50..a62f87753a 100644 --- a/src/Sylius/Behat/Page/Admin/Payment/IndexPage.php +++ b/src/Sylius/Behat/Page/Admin/Payment/IndexPage.php @@ -48,6 +48,17 @@ class IndexPage extends BaseIndexPage implements IndexPageInterface $this->getOrderLinkForRow($position)->clickLink('#'); } + public function showPaymentRequestOfNthPayment(int $position): void + { + $tableAccessor = $this->getTableAccessor(); + $table = $this->getElement('table'); + + $row = $tableAccessor->getRowsWithFields($table, [])[$position]; + $field = $tableAccessor->getFieldFromRow($table, $row, 'actions'); + + $field->find('css', '[data-test-show-action="List payment requests"]')->click(); + } + public function chooseChannelFilter(string $channelName): void { $this->getElement('filter_channel')->selectOption($channelName); diff --git a/src/Sylius/Behat/Page/Admin/Payment/IndexPageInterface.php b/src/Sylius/Behat/Page/Admin/Payment/IndexPageInterface.php index d02b24a266..e7f3ec27c0 100644 --- a/src/Sylius/Behat/Page/Admin/Payment/IndexPageInterface.php +++ b/src/Sylius/Behat/Page/Admin/Payment/IndexPageInterface.php @@ -27,5 +27,7 @@ interface IndexPageInterface extends BaseIndexPageInterface public function showOrderPageForNthPayment(int $position): void; + public function showPaymentRequestOfNthPayment(int $position): void; + public function chooseChannelFilter(string $channelName): void; } diff --git a/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPage.php b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPage.php new file mode 100644 index 0000000000..3ac6d0a016 --- /dev/null +++ b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPage.php @@ -0,0 +1,74 @@ +specifyAutocompleteFilter($this->getElement('filter_payment_method'), $paymentMethodName); + } + + public function chooseActionToFilter(string $action): void + { + $this->getElement('filter_action')->selectOption($action); + } + + public function chooseStateToFilter(string $state): void + { + $this->getElement('filter_state')->selectOption($state); + } + + protected function getDefinedElements(): array + { + return array_merge(parent::getDefinedElements(), [ + 'filter_action' => '#criteria_action', + 'filter_payment_method' => '#criteria_payment_method', + 'filter_state' => '#criteria_state', + ]); + } + + private function specifyAutocompleteFilter(NodeElement $autocomplete, string $value): void + { + if (!$this->areFiltersVisible()) { + $this->toggleFilters(); + } + + $this->autocompleteHelper->selectByName( + $this->getDriver(), + $autocomplete->getXpath(), + $value, + ); + + $this->waitForFormUpdate(); + } +} diff --git a/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPageInterface.php b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPageInterface.php new file mode 100644 index 0000000000..2aa4f49da1 --- /dev/null +++ b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/IndexPageInterface.php @@ -0,0 +1,25 @@ +getElement($fieldName)->getText(); + } + + protected function getDefinedElements(): array + { + return array_merge(parent::getDefinedElements(), [ + 'action' => '[data-test-action]', + 'method' => '[data-test-method]', + 'payload' => '[data-test-payload]', + 'response_data' => '[data-test-response-data]', + 'state' => '[data-test-state]', + ]); + } +} diff --git a/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/ShowPageInterface.php b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/ShowPageInterface.php new file mode 100644 index 0000000000..61e2309546 --- /dev/null +++ b/src/Sylius/Behat/Page/Admin/Payment/PaymentRequest/ShowPageInterface.php @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml index 59c7c131fb..27ee69bc82 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml @@ -161,6 +161,14 @@ + + + + + + + + @@ -191,6 +199,12 @@ + + + + + + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/setup.xml b/src/Sylius/Behat/Resources/config/services/contexts/setup.xml index 4a3ade4e7c..9f6aa372e8 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/setup.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/setup.xml @@ -142,7 +142,6 @@ Offline - @@ -320,5 +319,12 @@ + + + + + + + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/ui.xml b/src/Sylius/Behat/Resources/config/services/contexts/ui.xml index 979e2b9fc6..377143f833 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/ui.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/ui.xml @@ -613,5 +613,13 @@ + + + + + + + + diff --git a/src/Sylius/Behat/Resources/config/services/pages/admin/payment.xml b/src/Sylius/Behat/Resources/config/services/pages/admin/payment.xml index 6aa10f4483..e872f8c5c7 100644 --- a/src/Sylius/Behat/Resources/config/services/pages/admin/payment.xml +++ b/src/Sylius/Behat/Resources/config/services/pages/admin/payment.xml @@ -14,6 +14,8 @@ Sylius\Behat\Page\Admin\Payment\IndexPage + Sylius\Behat\Page\Admin\Payment\PaymentRequest\IndexPage + Sylius\Behat\Page\Admin\Payment\PaymentRequest\ShowPage @@ -22,5 +24,21 @@ sylius_admin_payment_index + + + sylius_admin_payment_request_index + + + + + diff --git a/src/Sylius/Behat/Resources/config/suites/api/checkout/paying_for_order.yml b/src/Sylius/Behat/Resources/config/suites/api/checkout/paying_for_order.yml index 631823ae24..6708f5af2e 100644 --- a/src/Sylius/Behat/Resources/config/suites/api/checkout/paying_for_order.yml +++ b/src/Sylius/Behat/Resources/config/suites/api/checkout/paying_for_order.yml @@ -11,8 +11,10 @@ default: - sylius.behat.context.transform.cart - 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 @@ -23,24 +25,32 @@ default: - sylius.behat.context.transform.user - sylius.behat.context.transform.zone + - sylius.behat.context.setup.admin_user - sylius.behat.context.setup.cart - sylius.behat.context.setup.channel + - sylius.behat.context.setup.currency - sylius.behat.context.setup.checkout - sylius.behat.context.setup.checkout.address - sylius.behat.context.setup.geographical - sylius.behat.context.setup.locale - sylius.behat.context.setup.order - sylius.behat.context.setup.payment + - sylius.behat.context.setup.payment_request - sylius.behat.context.setup.product - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shop_api_security - sylius.behat.context.setup.taxation + - sylius.behat.context.setup.user - sylius.behat.context.setup.zone + - sylius.behat.context.api.admin.managing_payment_requests - sylius.behat.context.api.shop.cart - sylius.behat.context.api.shop.checkout - sylius.behat.context.api.shop.checkout.complete + - sylius.behat.context.api.shop.checkout.order_details + - sylius.behat.context.api.shop.customer - sylius.behat.context.api.shop.order + - sylius.behat.context.api.shop.payment_request - sylius.behat.context.api.shop.response filters: diff --git a/src/Sylius/Behat/Resources/config/suites/api/payment/managing_payments.yml b/src/Sylius/Behat/Resources/config/suites/api/payment/managing_payments.yml index 0df135235c..38f71963a6 100644 --- a/src/Sylius/Behat/Resources/config/suites/api/payment/managing_payments.yml +++ b/src/Sylius/Behat/Resources/config/suites/api/payment/managing_payments.yml @@ -11,6 +11,7 @@ default: - sylius.behat.context.transform.customer - sylius.behat.context.transform.lexical - sylius.behat.context.transform.order + - sylius.behat.context.transform.payment - sylius.behat.context.transform.product - sylius.behat.context.transform.shared_storage @@ -18,10 +19,12 @@ default: - sylius.behat.context.setup.channel - sylius.behat.context.setup.order - sylius.behat.context.setup.payment + - sylius.behat.context.setup.payment_request - sylius.behat.context.setup.product - sylius.behat.context.setup.shipping - sylius.behat.context.api.admin.managing_payments + - sylius.behat.context.api.admin.managing_payment_requests filters: tags: "@managing_payments&&@api" diff --git a/src/Sylius/Behat/Resources/config/suites/ui/checkout/paying_for_order.yml b/src/Sylius/Behat/Resources/config/suites/ui/checkout/paying_for_order.yml index 1b6412dfb6..7beca5480d 100644 --- a/src/Sylius/Behat/Resources/config/suites/ui/checkout/paying_for_order.yml +++ b/src/Sylius/Behat/Resources/config/suites/ui/checkout/paying_for_order.yml @@ -24,12 +24,14 @@ default: - sylius.behat.context.transform.user - sylius.behat.context.transform.zone + - sylius.behat.context.setup.admin_user - sylius.behat.context.setup.channel - 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.payment_request - sylius.behat.context.setup.product - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shop_security @@ -37,6 +39,7 @@ default: - sylius.behat.context.setup.user - sylius.behat.context.setup.zone + - sylius.behat.context.ui.admin.managing_payment_requests - sylius.behat.context.ui.shop.account - sylius.behat.context.ui.shop.cart - sylius.behat.context.ui.shop.checkout diff --git a/src/Sylius/Behat/Resources/config/suites/ui/payment/managing_payments.yml b/src/Sylius/Behat/Resources/config/suites/ui/payment/managing_payments.yml index 20797a92d1..b8bd2d9a81 100644 --- a/src/Sylius/Behat/Resources/config/suites/ui/payment/managing_payments.yml +++ b/src/Sylius/Behat/Resources/config/suites/ui/payment/managing_payments.yml @@ -23,6 +23,7 @@ default: - sylius.behat.context.setup.geographical - sylius.behat.context.setup.order - sylius.behat.context.setup.payment + - sylius.behat.context.setup.payment_request - sylius.behat.context.setup.product - sylius.behat.context.setup.shipping - sylius.behat.context.setup.zone @@ -30,6 +31,7 @@ default: - sylius.behat.context.ui.email - sylius.behat.context.ui.admin.managing_payments + - sylius.behat.context.ui.admin.managing_payment_requests - sylius.behat.context.ui.shop.cart filters: tags: "@managing_payments&&@ui" diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/order/show.yaml b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/order/show.yaml index 8be199f635..8e0be838cb 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/order/show.yaml +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/order/show.yaml @@ -124,6 +124,8 @@ twig_hooks: template: '@SyliusAdmin/order/show/content/sections/payments/item/actions.html.twig' 'sylius_admin.order.show.content.sections.payments.item.actions': + list_payment_requests: + template: '@SyliusAdmin/order/show/content/sections/payments/item/actions/list_payment_requests.html.twig' complete: template: '@SyliusAdmin/order/show/content/sections/payments/item/actions/complete.html.twig' refund: diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/index.yaml b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/index.yaml new file mode 100644 index 0000000000..087c9da377 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/index.yaml @@ -0,0 +1,5 @@ +twig_hooks: + hooks: + 'sylius_admin.payment_request.index.content.header': + breadcrumbs: + template: '@SyliusAdmin/payment_request/index/content/header/breadcrumbs.html.twig' diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/show.yaml b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/show.yaml new file mode 100644 index 0000000000..731c453478 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/app/twig_hooks/payment_request/show.yaml @@ -0,0 +1,35 @@ +twig_hooks: + hooks: + 'sylius_admin.payment_request.show.content': + sections: + template: '@SyliusAdmin/payment_request/show/content/sections.html.twig' + + 'sylius_admin.payment_request.show.content.header': + breadcrumbs: + template: '@SyliusAdmin/payment_request/show/content/header/breadcrumbs.html.twig' + + 'sylius_admin.payment_request.show.content.sections': + general: + template: '@SyliusAdmin/payment_request/show/content/sections/general.html.twig' + + 'sylius_admin.payment_request.show.content.sections#left': + payload: + template: '@SyliusAdmin/payment_request/show/content/sections/payload.html.twig' + + 'sylius_admin.payment_request.show.content.sections#right': + response_data: + template: '@SyliusAdmin/payment_request/show/content/sections/response_data.html.twig' + + 'sylius_admin.payment_request.show.content.sections.general': + hash: + template: '@SyliusAdmin/payment_request/show/content/sections/general/hash.html.twig' + method: + template: '@SyliusAdmin/payment_request/show/content/sections/general/method.html.twig' + action: + template: '@SyliusAdmin/payment_request/show/content/sections/general/action.html.twig' + state: + template: '@SyliusAdmin/payment_request/show/content/sections/general/state.html.twig' + created_at: + template: '@SyliusAdmin/payment_request/show/content/sections/general/created_at.html.twig' + updated_at: + template: '@SyliusAdmin/payment_request/show/content/sections/general/updated_at.html.twig' diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment.yml b/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment.yml index 9e68b20060..48567073b3 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment.yml +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment.yml @@ -69,6 +69,15 @@ sylius_grid: class: "%sylius.model.channel.class%" actions: item: + list_payment_requests: + type: show + icon: list_letters + label: sylius.ui.list_payment_requests + options: + link: + route: sylius_admin_payment_request_index + parameters: + paymentId: resource.id complete: type: apply_transition label: sylius.ui.complete diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment_request.yml b/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment_request.yml new file mode 100644 index 0000000000..59a908ad5c --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/grids/payment_request.yml @@ -0,0 +1,93 @@ +sylius_grid: + grids: + sylius_admin_payment_request: + driver: + name: doctrine/orm + options: + class: "%sylius.model.payment_request.class%" + repository: + method: createQueryBuilderForPayment + arguments: + paymentId: $paymentId + sorting: + createdAt: desc + fields: + hash: + type: string + label: sylius.ui.hash + sortable: ~ + method: + type: twig + label: sylius.ui.payment_method + path: . + options: + template: "@SyliusAdmin/payment_request/grid/field/method.html.twig" + action: + type: twig + label: sylius.ui.action + options: + template: "@SyliusAdmin/payment_request/grid/field/action.html.twig" + state: + type: twig + label: sylius.ui.state + options: + template: "@SyliusAdmin/payment_request/grid/field/state.html.twig" + createdAt: + type: twig + label: sylius.ui.creation_date + sortable: ~ + options: + template: "@SyliusAdmin/shared/grid/field/date.html.twig" + vars: + th_class: "w-1 text-center" + updatedAt: + type: twig + label: sylius.ui.updating_date + sortable: ~ + options: + template: "@SyliusAdmin/shared/grid/field/date.html.twig" + vars: + th_class: "w-1 text-center" + filters: + payment_method: + type: ux_translatable_autocomplete + label: sylius.ui.payment_method + form_options: + extra_options: + class: "%sylius.model.payment_method.class%" + translation_fields: [ name ] + choice_label: name + options: + fields: [ method.id ] + action: + type: select + label: sylius.ui.action + form_options: + choices: + sylius.ui.authorize: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_AUTHORIZE + sylius.ui.cancel: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_CANCEL + sylius.ui.capture: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_CAPTURE + sylius.ui.payout: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_PAYOUT + sylius.ui.refund: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_REFUND + sylius.ui.status: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_STATUS + sylius.ui.sync: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::ACTION_SYNC + state: + type: select + label: sylius.ui.state + form_options: + choices: + sylius.ui.cancelled: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_CANCELLED + sylius.ui.completed: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_COMPLETED + sylius.ui.failed: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_FAILED + sylius.ui.new: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_NEW + sylius.ui.processing: !php/const Sylius\Component\Payment\Model\PaymentRequestInterface::STATE_PROCESSING + actions: + item: + show: + type: show + options: + link: + route: sylius_admin_payment_request_show + parameters: + hash: resource.hash + paymentId: resource.payment.id diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/routing.yml b/src/Sylius/Bundle/AdminBundle/Resources/config/routing.yml index 369dd42ee3..28c4adc1d3 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/config/routing.yml +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/routing.yml @@ -58,6 +58,10 @@ sylius_admin_payment: sylius_admin_payment_method: resource: "@SyliusAdminBundle/Resources/config/routing/payment_method.yml" +sylius_admin_payment_request: + resource: "@SyliusAdminBundle/Resources/config/routing/payment_request.yaml" + prefix: /payments/{paymentId}/payment-requests/ + sylius_admin_product: resource: "@SyliusAdminBundle/Resources/config/routing/product.yml" diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/routing/payment_request.yaml b/src/Sylius/Bundle/AdminBundle/Resources/config/routing/payment_request.yaml new file mode 100644 index 0000000000..b70fcc573c --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/routing/payment_request.yaml @@ -0,0 +1,25 @@ +sylius_admin_payment_request_index: + path: / + methods: [GET] + defaults: + _controller: sylius.controller.payment_request::indexAction + _sylius: + section: admin + permission: true + template: "@SyliusAdmin/shared/crud/index.html.twig" + grid: sylius_admin_payment_request + +sylius_admin_payment_request_show: + path: /{hash} + methods: [GET] + defaults: + _controller: sylius.controller.payment_request::showAction + _sylius: + section: admin + permission: true + template: "@SyliusAdmin/shared/crud/show.html.twig" + repository: + method: findOneByPaymentId + arguments: + hash: $hash + paymentId: $paymentId diff --git a/src/Sylius/Bundle/AdminBundle/composer.json b/src/Sylius/Bundle/AdminBundle/composer.json index 6f4b7aafee..d7b741a1e2 100644 --- a/src/Sylius/Bundle/AdminBundle/composer.json +++ b/src/Sylius/Bundle/AdminBundle/composer.json @@ -34,6 +34,7 @@ "knplabs/knp-menu": "^3.1", "knplabs/knp-menu-bundle": "^3.0", "sylius/core-bundle": "^2.0", + "sylius/payum-bundle": "^2.0", "sylius/ui-bundle": "^2.0", "sylius/twig-hooks": "~0.3.0", "symfony/framework-bundle": "^6.4.1 || ^7.1", diff --git a/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions.html.twig b/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions.html.twig index adda10e322..bd819d830f 100644 --- a/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions.html.twig +++ b/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions.html.twig @@ -1,5 +1,5 @@ -
+
{% hook 'actions' %}
diff --git a/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions/list_payment_requests.html.twig b/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions/list_payment_requests.html.twig new file mode 100644 index 0000000000..eb9d4afdf5 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/order/show/content/sections/payments/item/actions/list_payment_requests.html.twig @@ -0,0 +1,7 @@ +{% from '@SyliusAdmin/shared/helper/icon.html.twig' import icon %} + +{% set payment = hookable_metadata.context.payment %} + + + {{ icon({ icon: 'list_letters', class: 'icon icon-tabler' }) }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/cancelled.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/cancelled.html.twig new file mode 100644 index 0000000000..32ccce80d9 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/cancelled.html.twig @@ -0,0 +1,3 @@ + + {{ 'sylius.ui.cancelled'|trans }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/completed.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/completed.html.twig new file mode 100644 index 0000000000..a5905f47ca --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/completed.html.twig @@ -0,0 +1,3 @@ + + {{ 'sylius.ui.completed'|trans }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/failed.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/failed.html.twig new file mode 100644 index 0000000000..fe3f039a82 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/failed.html.twig @@ -0,0 +1,3 @@ + + {{ 'sylius.ui.failed'|trans }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/new.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/new.html.twig new file mode 100644 index 0000000000..a4d06f6191 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/new.html.twig @@ -0,0 +1,3 @@ + + {{ 'sylius.ui.new'|trans }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/processing.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/processing.html.twig new file mode 100644 index 0000000000..73cc86d08f --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/common/label/state/processing.html.twig @@ -0,0 +1,3 @@ + + {{ 'sylius.ui.processing'|trans }} + diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/action.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/action.html.twig new file mode 100644 index 0000000000..fdd927ad81 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/action.html.twig @@ -0,0 +1 @@ +{{ ('sylius.ui.' ~ data)|trans }} diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/method.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/method.html.twig new file mode 100644 index 0000000000..27de352225 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/method.html.twig @@ -0,0 +1 @@ +{{ data.method.name }} diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/state.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/state.html.twig new file mode 100644 index 0000000000..3aa2d08bac --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/grid/field/state.html.twig @@ -0,0 +1 @@ +{{ include('@SyliusAdmin/payment_request/common/label/state/' ~ data ~ '.html.twig') }} diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/index/content/header/breadcrumbs.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/index/content/header/breadcrumbs.html.twig new file mode 100644 index 0000000000..6bb28bc2b8 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/index/content/header/breadcrumbs.html.twig @@ -0,0 +1,10 @@ +{% from '@SyliusAdmin/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% set configuration = hookable_metadata.context.resources.requestConfiguration %} + +{{ breadcrumbs([ + { name: 'sylius.ui.dashboard', url: path('sylius_admin_dashboard'), active: false }, + { name: 'sylius.ui.payments'|trans, url: path('sylius_admin_payment_index'), active: false }, + { name: configuration.request.get('id'), active: false }, + { name: 'sylius.ui.payment_requests'|trans, active: true }, +]) }} diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/header/breadcrumbs.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/header/breadcrumbs.html.twig new file mode 100644 index 0000000000..64db485e5f --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/header/breadcrumbs.html.twig @@ -0,0 +1,12 @@ +{% from '@SyliusAdmin/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% set payment_request = hookable_metadata.context.resource %} +{% set payment_id = payment_request.payment.id %} + +{{ breadcrumbs([ + { name: 'sylius.ui.dashboard', url: path('sylius_admin_dashboard'), active: false }, + { name: 'sylius.ui.payments'|trans, url: path('sylius_admin_payment_index'), active: false }, + { name: payment_id, active: false }, + { name: 'sylius.ui.payment_requests'|trans, url: path('sylius_admin_payment_request_index', {'paymentId': payment_id}), active: false }, + { name: payment_request.hash, active: true }, +]) }} diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections.html.twig new file mode 100644 index 0000000000..5c8568ec9d --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections.html.twig @@ -0,0 +1,13 @@ +
+
+ {% hook 'sections' %} +
+
+ {% hook 'sections#left' %} +
+
+ {% hook 'sections#right' %} +
+
+
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general.html.twig new file mode 100644 index 0000000000..ef8fb0c36a --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general.html.twig @@ -0,0 +1,12 @@ +
+
+
+ {{ 'sylius.ui.general'|trans }} +
+
+
+
+ {% hook 'general' %} +
+
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/action.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/action.html.twig new file mode 100644 index 0000000000..1ac90f6367 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/action.html.twig @@ -0,0 +1,6 @@ +
+
{{ 'sylius.ui.action'|trans }}
+
+ {{ ('sylius.ui.' ~ hookable_metadata.context.resource.action)|trans }} +
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/created_at.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/created_at.html.twig new file mode 100644 index 0000000000..b0d951d92f --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/created_at.html.twig @@ -0,0 +1,8 @@ +{% set date_format = hookable_metadata.configuration.date_format|default('YYYY-MM-dd HH:mm:ss') %} + +
+
{{ 'sylius.ui.created_at'|trans }}
+
+ {{ hookable_metadata.context.resource.createdAt|format_datetime(pattern=date_format) }} +
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/hash.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/hash.html.twig new file mode 100644 index 0000000000..ebd52237b9 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/hash.html.twig @@ -0,0 +1,4 @@ +
+
{{ 'sylius.ui.hash'|trans }}
+
{{ hookable_metadata.context.resource.hash }}
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/method.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/method.html.twig new file mode 100644 index 0000000000..bc03040d24 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/method.html.twig @@ -0,0 +1,8 @@ +{% set payment_method = hookable_metadata.context.resource.method %} + +
+
{{ 'sylius.ui.payment_method'|trans }}
+ +
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/state.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/state.html.twig new file mode 100644 index 0000000000..3db192851d --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/state.html.twig @@ -0,0 +1,6 @@ +
+
{{ 'sylius.ui.state'|trans }}
+
+ {{ include('@SyliusAdmin/payment_request/common/label/state/' ~ hookable_metadata.context.resource.state ~ '.html.twig') }} +
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/updated_at.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/updated_at.html.twig new file mode 100644 index 0000000000..b2118eff8c --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/general/updated_at.html.twig @@ -0,0 +1,8 @@ +{% set date_format = hookable_metadata.configuration.date_format|default('YYYY-MM-dd HH:mm:ss') %} + +
+
{{ 'sylius.ui.updated_at'|trans }}
+
+ {{ hookable_metadata.context.resource.updatedAt|format_datetime(pattern=date_format) }} +
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/payload.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/payload.html.twig new file mode 100644 index 0000000000..d84ed901b7 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/payload.html.twig @@ -0,0 +1,10 @@ +
+
+
+ {{ 'sylius.ui.payload'|trans }} +
+
+
+
{{ hookable_metadata.context.resource.payload|json_encode(constant('JSON_PRETTY_PRINT')) }}
+
+
diff --git a/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/response_data.html.twig b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/response_data.html.twig new file mode 100644 index 0000000000..4b0f4776c7 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/templates/payment_request/show/content/sections/response_data.html.twig @@ -0,0 +1,10 @@ +
+
+
+ {{ 'sylius.ui.response_data'|trans }} +
+
+
+
{{ hookable_metadata.context.resource.responseData|json_encode(constant('JSON_PRETTY_PRINT')) }}
+
+
diff --git a/src/Sylius/Bundle/AdminBundle/test/config/bundles.php b/src/Sylius/Bundle/AdminBundle/test/config/bundles.php index f5b7f26e80..94f89466fd 100644 --- a/src/Sylius/Bundle/AdminBundle/test/config/bundles.php +++ b/src/Sylius/Bundle/AdminBundle/test/config/bundles.php @@ -45,10 +45,10 @@ return [ Knp\Bundle\GaufretteBundle\KnpGaufretteBundle::class => ['all' => true], League\FlysystemBundle\FlysystemBundle::class => ['all' => true], Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], - Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], Sylius\Bundle\FixturesBundle\SyliusFixturesBundle::class => ['all' => true], + Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true], Sylius\Bundle\PayumBundle\SyliusPayumBundle::class => ['all' => true], Sonata\BlockBundle\SonataBlockBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], diff --git a/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml b/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml index dfea3e341e..3a8ab52c4e 100644 --- a/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml +++ b/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml @@ -1,5 +1,6 @@ imports: - { resource: "@SyliusCoreBundle/Resources/config/app/config.yml" } + - { resource: "@SyliusPayumBundle/Resources/config/app/config.yaml" } parameters: locale: en_US diff --git a/src/Sylius/Bundle/ApiBundle/Attribute/PaymentRequestHashAware.php b/src/Sylius/Bundle/ApiBundle/Attribute/PaymentRequestHashAware.php new file mode 100644 index 0000000000..0da8b09c59 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Attribute/PaymentRequestHashAware.php @@ -0,0 +1,24 @@ +getState(), [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING]); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Checker/UpdatePaymentRequestEligibilityCheckerInterface.php b/src/Sylius/Bundle/ApiBundle/Checker/UpdatePaymentRequestEligibilityCheckerInterface.php new file mode 100644 index 0000000000..1554e8a515 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Checker/UpdatePaymentRequestEligibilityCheckerInterface.php @@ -0,0 +1,21 @@ + $paymentMethodRepository + * @param PaymentRepositoryInterface $paymentRepository + * @param PaymentRequestFactoryInterface $paymentRequestFactory + * @param PaymentRequestRepositoryInterface $paymentRequestRepository + */ + public function __construct( + private PaymentMethodRepositoryInterface $paymentMethodRepository, + private PaymentRepositoryInterface $paymentRepository, + private PaymentRequestFactoryInterface $paymentRequestFactory, + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private DefaultActionProviderInterface $defaultActionProvider, + private DefaultPayloadProviderInterface $defaultPayloadProvider, + ) { + } + + public function __invoke(AddPaymentRequest $addPaymentRequest): PaymentRequestInterface + { + $paymentRequest = $this->createPaymentRequest($addPaymentRequest); + + $this->paymentRequestRepository->add($paymentRequest); + + return $paymentRequest; + } + + private function createPaymentRequest(AddPaymentRequest $addPaymentRequest): PaymentRequestInterface + { + /** @var PaymentMethodInterface|null $paymentMethod */ + $paymentMethod = $this->paymentMethodRepository->findOneBy([ + 'code' => $addPaymentRequest->paymentMethodCode, + ]); + + if (null === $paymentMethod) { + throw new PaymentMethodNotFoundException(); + } + + /** @var PaymentInterface|null $payment */ + $payment = $this->paymentRepository->find($addPaymentRequest->paymentId); + + if (null === $payment) { + throw new PaymentNotFoundException(); + } + + $paymentRequest = $this->paymentRequestFactory->create($payment, $paymentMethod); + $paymentRequest->setAction($addPaymentRequest->action ?? $this->defaultActionProvider->getAction($paymentRequest)); + $paymentRequest->setPayload($addPaymentRequest->payload ?? $this->defaultPayloadProvider->getPayload($paymentRequest)); + + return $paymentRequest; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Payment/UpdatePaymentRequestHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Payment/UpdatePaymentRequestHandler.php new file mode 100644 index 0000000000..c2ad290a3c --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Payment/UpdatePaymentRequestHandler.php @@ -0,0 +1,44 @@ + $paymentRequestRepository + */ + public function __construct( + private PaymentRequestRepositoryInterface $paymentRequestRepository, + ) { + } + + public function __invoke(UpdatePaymentRequest $updatePaymentRequest): PaymentRequestInterface + { + $paymentRequest = $this->paymentRequestRepository->find($updatePaymentRequest->hash); + if (null === $paymentRequest) { + throw new PaymentRequestNotFoundException(); + } + + $paymentRequest->setPayload($updatePaymentRequest->payload); + + return $paymentRequest; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/EventSubscriber/PaymentRequestEventSubscriber.php b/src/Sylius/Bundle/ApiBundle/EventSubscriber/PaymentRequestEventSubscriber.php new file mode 100644 index 0000000000..b84ebfa93a --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/EventSubscriber/PaymentRequestEventSubscriber.php @@ -0,0 +1,56 @@ + ['postWrite', EventPriorities::POST_WRITE], + ]; + } + + public function postWrite(ViewEvent $event): void + { + $paymentRequest = $event->getControllerResult(); + + if (!$paymentRequest instanceof PaymentRequestInterface) { + return; + } + + $method = $event->getRequest()->getMethod(); + if ($method === Request::METHOD_POST) { + $this->paymentRequestAnnouncer->dispatchPaymentRequestCommand($paymentRequest); + + return; + } + + if (in_array($method, [Request::METHOD_PUT, Request::METHOD_PATCH], true)) { + $this->paymentRequestAnnouncer->dispatchPaymentRequestCommand($paymentRequest); + } + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Exception/PaymentMethodNotFoundException.php b/src/Sylius/Bundle/ApiBundle/Exception/PaymentMethodNotFoundException.php new file mode 100644 index 0000000000..fd52fe7b30 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Exception/PaymentMethodNotFoundException.php @@ -0,0 +1,26 @@ + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentMethod.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentMethod.xml index de0d7d8bd4..f825c27ed3 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentMethod.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentMethod.xml @@ -86,7 +86,7 @@ - sylius_api.payment_method_configuration_groups_generator + sylius_api.validator.payment_method_groups_generator @@ -116,7 +116,7 @@ - sylius_api.payment_method_configuration_groups_generator + sylius_api.validator.payment_method_groups_generator diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentRequest.xml new file mode 100644 index 0000000000..5d77f2b6f9 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/admin/PaymentRequest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + sylius:admin:payment_request:show + + + + + + + + + + + + + + + + + sylius:admin:payment_request:index + + + + + + sylius_api.search_filter.admin.payment_request + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/shop/PaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/shop/PaymentRequest.xml new file mode 100644 index 0000000000..2c0c96dc6b --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/shop/PaymentRequest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + sylius:shop:payment_request:show + + + + + + + + sylius:shop:payment_request:show + + + + + sylius:shop:payment_request:create + + + + + sylius + + + + + + + + sylius:shop:payment_request:show + + + + + sylius:shop:payment_request:update + + + + + sylius + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml b/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml index 28df3afa27..2f3ce7e423 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml @@ -56,7 +56,10 @@ api_platform: Sylius\Bundle\ApiBundle\Exception\InvalidProductAttributeValueTypeException: 422 Sylius\Bundle\ApiBundle\Exception\InvalidRequestArgumentException: 400 Sylius\Bundle\ApiBundle\Exception\LocaleIsUsedException: 422 + Sylius\Component\Payment\Exception\PaymentRequestNotFoundException: 404 + Sylius\Bundle\ApiBundle\Exception\PaymentNotFoundException: 404 Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved: 422 + Sylius\Bundle\ApiBundle\Exception\PaymentMethodNotFoundException: 404 Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved: 422 Sylius\Bundle\ApiBundle\Exception\ProductCannotBeRemoved: 422 Sylius\Bundle\ApiBundle\Exception\ProductOptionValueCannotBeRemoved: 422 @@ -74,6 +77,7 @@ api_platform: Sylius\Bundle\UserBundle\Exception\UserNotFoundException: 404 Sylius\Component\Core\Inventory\Exception\NotEnoughUnitsOnHandException: 422 Sylius\Component\Core\Inventory\Exception\NotEnoughUnitsOnHoldException: 422 + Sylius\Component\Payment\Exception\InvalidPaymentRequestPayloadException: 422 Sylius\Component\Promotion\Exception\FailedGenerationException: 422 Symfony\Component\Serializer\Exception\UnexpectedValueException: 400 Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException: 400 diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/AddPaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/AddPaymentRequest.xml new file mode 100644 index 0000000000..3c77fc6319 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/AddPaymentRequest.xml @@ -0,0 +1,32 @@ + + + + + + + + sylius:shop:payment_request:create + + + sylius:shop:payment_request:create + + + sylius:shop:payment_request:create + + + sylius:shop:payment_request:create + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/UpdatePaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/UpdatePaymentRequest.xml new file mode 100644 index 0000000000..0c1ba4ccf9 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Payment/UpdatePaymentRequest.xml @@ -0,0 +1,23 @@ + + + + + + + + sylius:shop:payment_request:update + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/GatewayConfig.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/GatewayConfig.xml index 337cc5d2f5..066a62bb5d 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/GatewayConfig.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/GatewayConfig.xml @@ -15,7 +15,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd" > - + sylius:admin:payment_method:show sylius:admin:payment_method:index diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/PaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/PaymentRequest.xml new file mode 100644 index 0000000000..ccf25c293b --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/PaymentRequest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + sylius:admin:payment_request:index + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:index + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:index + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:index + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:index + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + + sylius:admin:payment_request:show + sylius:shop:payment_request:show + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml index 10edd21b88..c733a3f773 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml @@ -59,6 +59,9 @@ + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml index 63a2c854a8..8a6ad80472 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml @@ -211,5 +211,20 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/context_builders.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/context_builders.xml index 967d759a2b..45e62c9b06 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/context_builders.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/context_builders.xml @@ -116,6 +116,18 @@ Sylius\Component\Core\Model\PaymentInterface + + + Sylius\Bundle\ApiBundle\Attribute\PaymentRequestHashAware + Sylius\Bundle\ApiBundle\Attribute\PaymentRequestHashAware::DEFAULT_ARGUMENT_NAME + Sylius\Component\Payment\Model\PaymentRequestInterface + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/filters.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/filters.xml index ee57f66735..b15271be1f 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/filters.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/filters.xml @@ -35,6 +35,15 @@ + + + exact + exact + exact + + + + partial diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/payments.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/payments.xml index 95b4fb5172..18c92fc7ec 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/payments.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/payments.xml @@ -22,5 +22,6 @@ + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/shop/state_providers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/shop/state_providers.xml index d1058e0864..12a296d582 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/shop/state_providers.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/shop/state_providers.xml @@ -71,5 +71,12 @@ + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml index fd17fa8839..fff160e6ec 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml @@ -193,6 +193,13 @@ + + + + + + + @@ -208,11 +215,12 @@ - %sylius.payum.gateway_config.validation_groups% + %sylius.form.type.payment_method.validation_groups% + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/AddPaymentRequest.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/AddPaymentRequest.xml new file mode 100644 index 0000000000..9486f588a3 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/AddPaymentRequest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/translations/validators.en.yaml b/src/Sylius/Bundle/ApiBundle/Resources/translations/validators.en.yaml index 923817cb51..5ae40d7bac 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/translations/validators.en.yaml +++ b/src/Sylius/Bundle/ApiBundle/Resources/translations/validators.en.yaml @@ -26,6 +26,9 @@ sylius: cannot_change_payment_method_for_cancelled_order: 'You cannot change the payment method for a cancelled order.' not_available: 'The payment method %name% is not available for this order. Please choose another one.' not_exist: 'The payment method with %code% code does not exist.' + payment_request: + action_not_available: 'The payment request (method code "%code%" and payment id "%id%") has no handler. Please choose another payment method.' + not_available: 'The payment request is not supporting this action.' product: not_exist: 'The product %productName% does not exist.' product_variant: diff --git a/src/Sylius/Bundle/ApiBundle/StateProvider/Shop/Payment/PaymentRequest/ItemProvider.php b/src/Sylius/Bundle/ApiBundle/StateProvider/Shop/Payment/PaymentRequest/ItemProvider.php new file mode 100644 index 0000000000..e967ef76fd --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/StateProvider/Shop/Payment/PaymentRequest/ItemProvider.php @@ -0,0 +1,54 @@ + */ +final readonly class ItemProvider implements ProviderInterface +{ + /** @param PaymentRequestRepositoryInterface $paymentRequestRepository */ + public function __construct( + private SectionProviderInterface $sectionProvider, + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private UpdatePaymentRequestEligibilityCheckerInterface $updatePaymentRequestEligibilityChecker, + ) { + } + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object|null + { + Assert::true(is_a($operation->getClass(), PaymentRequestInterface::class, true)); + Assert::isInstanceOf($operation, Put::class); + Assert::isInstanceOf($this->sectionProvider->getSection(), ShopApiSection::class); + + $paymentRequest = $this->paymentRequestRepository->find($uriVariables['hash']); + + if ( + $paymentRequest === null || + !$this->updatePaymentRequestEligibilityChecker->isEligible($paymentRequest) + ) { + return null; + } + + return $paymentRequest; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php index 6e5ef151a6..d13d6938e6 100644 --- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php +++ b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php @@ -45,12 +45,12 @@ return [ Knp\Bundle\GaufretteBundle\KnpGaufretteBundle::class => ['all' => true], League\FlysystemBundle\FlysystemBundle::class => ['all' => true], Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], - Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], - Sylius\Bundle\FixturesBundle\SyliusFixturesBundle::class => ['all' => true], + Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true], Sylius\Bundle\PayumBundle\SyliusPayumBundle::class => ['all' => true], + Sylius\Bundle\FixturesBundle\SyliusFixturesBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], Sylius\Bundle\ApiBundle\SyliusApiBundle::class => ['all' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true], diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml index 236358c63e..08b907d8fb 100644 --- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml +++ b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml @@ -1,5 +1,6 @@ imports: - { resource: "@SyliusCoreBundle/Resources/config/app/config.yml" } + - { resource: "@SyliusPayumBundle/Resources/config/app/config.yaml" } - { resource: "@SyliusApiBundle/Resources/config/app/config.yaml" } - { resource: "security.yaml" } diff --git a/src/Sylius/Bundle/ApiBundle/Validator/Constraints/ChosenPaymentRequestActionEligibility.php b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/ChosenPaymentRequestActionEligibility.php new file mode 100644 index 0000000000..35822af532 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/ChosenPaymentRequestActionEligibility.php @@ -0,0 +1,31 @@ +getCommandProvider($value); + if (null === $gatewayFactoryCommandProvider) { + $this->context->addViolation($constraint->notAvailable, [ + '%code%' => $value->getPaymentMethodCode(), + '%id%' => $value->getPaymentId(), + ]); + } + + if (false === $gatewayFactoryCommandProvider instanceof ServiceProviderAwareCommandProviderInterface) { + return; + } + + $actionsCommandProvider = $gatewayFactoryCommandProvider->getCommandProvider($value->getAction()); + if (null !== $actionsCommandProvider) { + return; + } + + $this->context->addViolation($constraint->notAvailable); + } + + private function getCommandProvider(AddPaymentRequest $addPaymentRequest): ?PaymentRequestCommandProviderInterface + { + /** @var PaymentMethodInterface|null $paymentMethod */ + $paymentMethod = $this->paymentMethodRepository->findOneBy(['code' => $addPaymentRequest->paymentMethodCode]); + + if ($paymentMethod === null) { + return null; + } + + $gatewayConfig = $paymentMethod->getGatewayConfig(); + if (null === $gatewayConfig) { + return null; + } + + $factoryName = $this->gatewayFactoryNameProvider->provide($paymentMethod); + $commandProvider = $this->gatewayFactoryCommandProvider->getCommandProvider($factoryName); + + return $commandProvider ?? null; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/composer.json b/src/Sylius/Bundle/ApiBundle/composer.json index 225be3c36b..a7cf2403e4 100644 --- a/src/Sylius/Bundle/ApiBundle/composer.json +++ b/src/Sylius/Bundle/ApiBundle/composer.json @@ -29,6 +29,7 @@ "enshrined/svg-sanitize": "^0.16", "lexik/jwt-authentication-bundle": "^2.11", "sylius/core-bundle": "^2.0", + "sylius/payum-bundle": "^2.0", "symfony/messenger": "^6.4.0 || ^7.1" }, "require-dev": { diff --git a/src/Sylius/Bundle/ApiBundle/spec/Checker/UpdatePaymentRequestEligibilityCheckerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Checker/UpdatePaymentRequestEligibilityCheckerSpec.php new file mode 100644 index 0000000000..ad0253eafd --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/Checker/UpdatePaymentRequestEligibilityCheckerSpec.php @@ -0,0 +1,60 @@ +getState()->willReturn(PaymentRequestInterface::STATE_NEW); + $this->isEligible($paymentRequest)->shouldReturn(true); + } + + function it_returns_true_if_payment_request_is_in_processing_state( + PaymentRequestInterface $paymentRequest + ): void + { + $paymentRequest->getState()->willReturn(PaymentRequestInterface::STATE_PROCESSING); + $this->isEligible($paymentRequest)->shouldReturn(true); + } + + function it_returns_false_if_payment_request_is_not_in_completed_state( + PaymentRequestInterface $paymentRequest + ): void + { + $paymentRequest->getState()->willReturn(PaymentRequestInterface::STATE_COMPLETED); + $this->isEligible($paymentRequest)->shouldReturn(false); + } + + function it_returns_false_if_payment_request_is_not_in_failed_state( + PaymentRequestInterface $paymentRequest + ): void + { + $paymentRequest->getState()->willReturn(PaymentRequestInterface::STATE_FAILED); + $this->isEligible($paymentRequest)->shouldReturn(false); + } + + function it_returns_false_if_payment_request_is_not_in_cancelled_state( + PaymentRequestInterface $paymentRequest + ): void + { + $paymentRequest->getState()->willReturn(PaymentRequestInterface::STATE_CANCELLED); + $this->isEligible($paymentRequest)->shouldReturn(false); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/spec/StateProvider/Shop/Payment/PaymentRequest/ItemProviderSpec.php b/src/Sylius/Bundle/ApiBundle/spec/StateProvider/Shop/Payment/PaymentRequest/ItemProviderSpec.php new file mode 100644 index 0000000000..310caa3bb8 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/StateProvider/Shop/Payment/PaymentRequest/ItemProviderSpec.php @@ -0,0 +1,95 @@ +beConstructedWith($sectionProvider, $paymentRequestRepository, $updatePaymentRequestEligibilityChecker); + } + + function it_is_a_state_provider(): void + { + $this->shouldImplement(ProviderInterface::class); + } + + function it_throws_an_exception_if_operation_class_is_not_payment( + Operation $operation, + ): void { + $operation->getClass()->willReturn(\stdClass::class); + $this->shouldThrow(\InvalidArgumentException::class)->during('provide', [$operation]); + } + + function it_throws_an_exception_if_operation_is_not_put( + Operation $operation, + ): void { + $operation->getClass()->willReturn(PaymentRequestInterface::class); + + $this->shouldThrow(\InvalidArgumentException::class)->during('provide', [$operation]); + } + + function it_throws_an_exception_if_section_is_not_shop_api_section( + SectionProviderInterface $sectionProvider, + ): void { + $operation = new Put(class: PaymentRequestInterface::class, name: 'put'); + $sectionProvider->getSection()->willReturn(new AdminApiSection()); + + $this->shouldThrow(\InvalidArgumentException::class)->during('provide', [$operation, [], []]); + } + + function it_returns_nothing_if_payment_request_is_not_found( + SectionProviderInterface $sectionProvider, + PaymentRequestRepositoryInterface $paymentRequestRepository, + UpdatePaymentRequestEligibilityCheckerInterface $updatePaymentRequestEligibilityChecker, + ): void { + $hash = 'hash'; + $operation = new Put(class: PaymentRequestInterface::class, name: 'put'); + $sectionProvider->getSection()->willReturn(new ShopApiSection()); + $paymentRequestRepository->find($hash)->willReturn(null); + $updatePaymentRequestEligibilityChecker->isEligible(Argument::any())->shouldNotBeCalled(); + + $this->provide($operation, ['hash' => $hash], [])->shouldReturn(null); + } + + function it_returns_payment_request_by_hash( + SectionProviderInterface $sectionProvider, + PaymentRequestRepositoryInterface $paymentRequestRepository, + UpdatePaymentRequestEligibilityCheckerInterface $updatePaymentRequestEligibilityChecker, + PaymentRequestInterface $paymentRequest, + ): void { + $hash = 'hash'; + $operation = new Put(class: PaymentRequestInterface::class, name: 'put'); + $sectionProvider->getSection()->willReturn(new ShopApiSection()); + $paymentRequestRepository->find($hash)->willReturn($paymentRequest); + $updatePaymentRequestEligibilityChecker->isEligible($paymentRequest)->willReturn(true); + + $this->provide($operation, ['hash' => $hash], [])->shouldReturn($paymentRequest); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php index c48337f5b3..58e665f19f 100644 --- a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php +++ b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php @@ -45,7 +45,6 @@ final class SyliusCoreExtension extends AbstractResourceExtension implements Pre 'sylius_locale', 'sylius_order', 'sylius_payment', - 'sylius_payum', 'sylius_product', 'sylius_promotion', 'sylius_review', diff --git a/src/Sylius/Bundle/CoreBundle/Form/Extension/PaymentMethodTypeExtension.php b/src/Sylius/Bundle/CoreBundle/Form/Extension/PaymentMethodTypeExtension.php index 296d598777..ce517b5651 100644 --- a/src/Sylius/Bundle/CoreBundle/Form/Extension/PaymentMethodTypeExtension.php +++ b/src/Sylius/Bundle/CoreBundle/Form/Extension/PaymentMethodTypeExtension.php @@ -15,61 +15,22 @@ namespace Sylius\Bundle\CoreBundle\Form\Extension; use Sylius\Bundle\ChannelBundle\Form\Type\ChannelChoiceType; use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType; -use Sylius\Bundle\PayumBundle\Form\Type\GatewayConfigType; -use Sylius\Bundle\PayumBundle\Validator\GroupsGenerator\GatewayConfigGroupsGenerator; -use Sylius\Component\Core\Formatter\StringInflector; -use Sylius\Component\Core\Model\PaymentMethodInterface; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\OptionsResolver\OptionsResolver; final class PaymentMethodTypeExtension extends AbstractTypeExtension { - public function __construct(private readonly GatewayConfigGroupsGenerator $gatewayConfigGroupsGenerator) - { - } - public function buildForm(FormBuilderInterface $builder, array $options): void { - $gatewayFactory = $options['data']->getGatewayConfig(); - $builder ->add('channels', ChannelChoiceType::class, [ 'multiple' => true, 'expanded' => true, 'label' => 'sylius.form.payment_method.channels', ]) - ->add('gatewayConfig', GatewayConfigType::class, [ - 'label' => false, - 'data' => $gatewayFactory, - ]) - ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void { - $paymentMethod = $event->getData(); - - if (!$paymentMethod instanceof PaymentMethodInterface) { - return; - } - - $gatewayConfig = $paymentMethod->getGatewayConfig(); - /** @var string|null $gatewayName */ - $gatewayName = $gatewayConfig->getGatewayName(); - - if (null === $gatewayName && null !== $paymentMethod->getCode()) { - $gatewayConfig->setGatewayName(StringInflector::nameToLowercaseCode($paymentMethod->getCode())); - } - }) ; } - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'validation_groups' => $this->gatewayConfigGroupsGenerator, - ]); - } - public static function getExtendedTypes(): iterable { return [PaymentMethodType::class]; diff --git a/src/Sylius/Bundle/CoreBundle/Migrations/Version20231106190918.php b/src/Sylius/Bundle/CoreBundle/Migrations/Version20231106190918.php new file mode 100644 index 0000000000..6724aba8a8 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Migrations/Version20231106190918.php @@ -0,0 +1,39 @@ +addSql('CREATE TABLE sylius_payment_request (hash BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', method_id INT NOT NULL, payment_id INT NOT NULL, state VARCHAR(255) NOT NULL, action VARCHAR(255) NOT NULL, payload LONGTEXT NOT NULL COMMENT \'(DC2Type:object)\', response_data JSON NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, INDEX IDX_86D904B19883967 (method_id), INDEX IDX_86D904B4C3A3BB (payment_id), PRIMARY KEY(hash)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE sylius_payment_request ADD CONSTRAINT FK_86D904B19883967 FOREIGN KEY (method_id) REFERENCES sylius_payment_method (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE sylius_payment_request ADD CONSTRAINT FK_86D904B4C3A3BB FOREIGN KEY (payment_id) REFERENCES sylius_payment (id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE sylius_payment_request DROP FOREIGN KEY FK_86D904B19883967'); + $this->addSql('ALTER TABLE sylius_payment_request DROP FOREIGN KEY FK_86D904B4C3A3BB'); + $this->addSql('DROP TABLE sylius_payment_request'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/Migrations/Version20240203222417.php b/src/Sylius/Bundle/CoreBundle/Migrations/Version20240203222417.php new file mode 100644 index 0000000000..ad3315b49f --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Migrations/Version20240203222417.php @@ -0,0 +1,43 @@ +addSql('CREATE TABLE sylius_payment_request (hash UUID NOT NULL, method_id INT NOT NULL, payment_id INT NOT NULL, state VARCHAR(255) NOT NULL, action VARCHAR(255) NOT NULL, payload TEXT NOT NULL, response_data JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(hash))'); + $this->addSql('CREATE INDEX IDX_86D904B19883967 ON sylius_payment_request (method_id)'); + $this->addSql('CREATE INDEX IDX_86D904B4C3A3BB ON sylius_payment_request (payment_id)'); + $this->addSql('COMMENT ON COLUMN sylius_payment_request.hash IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN sylius_payment_request.payload IS \'(DC2Type:object)\''); + $this->addSql('ALTER TABLE sylius_payment_request ADD CONSTRAINT FK_86D904B19883967 FOREIGN KEY (method_id) REFERENCES sylius_payment_method (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sylius_payment_request ADD CONSTRAINT FK_86D904B4C3A3BB FOREIGN KEY (payment_id) REFERENCES sylius_payment (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE sylius_payment_request DROP CONSTRAINT FK_86D904B19883967'); + $this->addSql('ALTER TABLE sylius_payment_request DROP CONSTRAINT FK_86D904B4C3A3BB'); + $this->addSql('DROP TABLE sylius_payment_request'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Controller/OrderPayController.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Controller/OrderPayController.php new file mode 100644 index 0000000000..7a223f55ff --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Controller/OrderPayController.php @@ -0,0 +1,77 @@ + $orderRepository + * @param iterable $payResponseProviders + * @param iterable $afterPayResponseProviders + */ + public function __construct( + private OrderRepositoryInterface $orderRepository, + private MetadataInterface $orderMetadata, + private RequestConfigurationFactoryInterface $requestConfigurationFactory, + private iterable $payResponseProviders, + private iterable $afterPayResponseProviders, + ) { + } + + public function payAction(Request $request, string $tokenValue): Response + { + $configuration = $this->requestConfigurationFactory->create($this->orderMetadata, $request); + + /** @var OrderInterface|null $order */ + $order = $this->orderRepository->findOneByTokenValue($tokenValue); + + if (null === $order) { + throw new NotFoundHttpException(sprintf('Order with token "%s" does not exist.', $tokenValue)); + } + + $request->getSession()->set('sylius_order_id', $order->getId()); + + foreach ($this->payResponseProviders as $provider) { + if ($provider->supports($configuration, $order)) { + return $provider->getResponse($configuration, $order); + } + } + + throw new LogicException(sprintf('No "pay response provider" available for order (id %s).', $order->getId())); + } + + public function afterPayAction(Request $request): Response + { + $configuration = $this->requestConfigurationFactory->create($this->orderMetadata, $request); + + foreach ($this->afterPayResponseProviders as $provider) { + if ($provider->supports($configuration)) { + return $provider->getResponse($configuration); + } + } + + throw new LogicException('No "after pay response provider" available.'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandler.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandler.php new file mode 100644 index 0000000000..1147c5a445 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandler.php @@ -0,0 +1,41 @@ +getRequest(); + + if (!$request->hasSession()) { + return; + } + + /** @var FlashBagAwareSessionInterface $session */ + $session = $request->getSession(); + if (PaymentInterface::STATE_NEW !== $state) { + $session->getFlashBag()->add('info', sprintf($this->format, $state)); + } + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandlerInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandlerInterface.php new file mode 100644 index 0000000000..ab574afeb8 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Handler/PaymentStateFlashHandlerInterface.php @@ -0,0 +1,21 @@ + $rawParameter) { + $parameters[$key] = (string) $this->expressionLanguage->evaluate($rawParameter, $context); + } + + return $this->router->generate( + $route, + $parameters, + ); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Processor/RouteParametersProcessorInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Processor/RouteParametersProcessorInterface.php new file mode 100644 index 0000000000..a6641710d4 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Processor/RouteParametersProcessorInterface.php @@ -0,0 +1,23 @@ + $rawParameters + * @param array $context + */ + public function process(string $route, array $rawParameters = [], array $context = []): string; +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayResponseProviderInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayResponseProviderInterface.php new file mode 100644 index 0000000000..b1ebdbd321 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayResponseProviderInterface.php @@ -0,0 +1,24 @@ + $afterPayRouteParameters + */ + public function __construct( + private RouteParametersProcessorInterface $routeParametersProcessor, + private string $afterPayRoute, + private array $afterPayRouteParameters, + ) { + } + + public function getUrl(PaymentRequestInterface $paymentRequest): string { + $context = [ + 'paymentRequest' => $paymentRequest, + 'payment' => $paymentRequest->getPayment(), + 'method' => $paymentRequest->getMethod(), + ]; + + return $this->routeParametersProcessor->process( + $this->afterPayRoute, + $this->afterPayRouteParameters, + $context + ); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayUrlProviderInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayUrlProviderInterface.php new file mode 100644 index 0000000000..072a26867e --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/AfterPayUrlProviderInterface.php @@ -0,0 +1,21 @@ + $finalRouteParameters + * @param array $retryRouteParameters + */ + public function __construct( + private RouteParametersProcessorInterface $routeParametersProcessor, + private string $finalRoute, + private array $finalRouteParameters, + private string $retryRoute, + private array $retryRouteParameters, + ) { + } + + public function getUrl(?PaymentInterface $payment): string { + $context = [ + 'payment' => $payment, + 'order' => $payment?->getOrder(), + ]; + + if ( + null === $payment || + $payment->getState() === BasePaymentInterface::STATE_COMPLETED || + $payment->getState() === BasePaymentInterface::STATE_AUTHORIZED + ) { + return $this->routeParametersProcessor->process( + $this->finalRoute, + $this->finalRouteParameters, + $context + ); + } + + return $this->routeParametersProcessor->process( + $this->retryRoute, + $this->retryRouteParameters, + $context + ); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/FinalUrlProviderInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/FinalUrlProviderInterface.php new file mode 100644 index 0000000000..852f62ebf1 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/FinalUrlProviderInterface.php @@ -0,0 +1,21 @@ +orderPayFinalUrlProvider->getUrl(null); + + return new RedirectResponse($url); + } + + public function supports( + RequestConfiguration $requestConfiguration, + OrderInterface $order + ): bool { + $payment = $this->paymentToPayResolver->getLastPayment($order); + return null === $payment; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/Offline/StatusHttpResponseProvider.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/Offline/StatusHttpResponseProvider.php new file mode 100644 index 0000000000..62cef277b5 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/Offline/StatusHttpResponseProvider.php @@ -0,0 +1,46 @@ +getAction() === PaymentRequestInterface::ACTION_STATUS; + } + + public function getResponse( + RequestConfiguration $requestConfiguration, + PaymentRequestInterface $paymentRequest, + ): Response { + // Force null payment to go to the thank you page + $finalUrl = $this->finalUrlProvider->getUrl(null); + + return new RedirectResponse($finalUrl); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PayResponseProviderInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PayResponseProviderInterface.php new file mode 100644 index 0000000000..4e9122ba2c --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PayResponseProviderInterface.php @@ -0,0 +1,31 @@ + $paymentRequestFactory + * @param PaymentRequestRepositoryInterface $paymentRequestRepository + */ + public function __construct( + private PaymentRequestFactoryInterface $paymentRequestFactory, + private PaymentRequestAnnouncerInterface $paymentRequestAnnouncer, + private ServiceProviderAwareProviderInterface $httpResponseProvider, + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private PaymentRequestCommandProviderInterface $paymentRequestCommandProvider, + private PaymentStateFlashHandlerInterface $paymentStateFlashHandler, + private FinalUrlProviderInterface $orderPayFinalUrlProvider, + ) { + } + + public function getResponse(RequestConfiguration $requestConfiguration): Response + { + $hash = $this->getPaymentRequestHash($requestConfiguration); + Assert::notNull($hash, 'A request attribute "hash" is required to retrieve the related order.'); + + /** @var PaymentRequestInterface|null $previousPaymentRequest */ + $previousPaymentRequest = $this->paymentRequestRepository->find($hash); + if (null === $previousPaymentRequest) { + throw new NotFoundHttpException(sprintf('The Payment Request with hash "%s" does not exist.', $hash)); + } + + $paymentRequest = $this->paymentRequestFactory->createFromPaymentRequest($previousPaymentRequest); + $paymentRequest->setAction(PaymentRequestInterface::ACTION_STATUS); + + if ($this->paymentRequestCommandProvider->supports($paymentRequest)) { + $this->paymentRequestRepository->add($paymentRequest); + $this->paymentRequestAnnouncer->dispatchPaymentRequestCommand($paymentRequest); + } + + if ($this->httpResponseProvider->supports($requestConfiguration, $paymentRequest)) { + return $this->httpResponseProvider->getResponse($requestConfiguration, $paymentRequest); + } + + /** @var PaymentInterface $payment */ + $payment = $paymentRequest->getPayment(); + $this->paymentStateFlashHandler->handle($requestConfiguration, $payment->getState()); + + $url = $this->orderPayFinalUrlProvider->getUrl($payment); + + return new RedirectResponse($url); + } + + public function supports(RequestConfiguration $requestConfiguration): bool + { + $hash = $this->getPaymentRequestHash($requestConfiguration); + + return null !== $hash; + } + + private function getPaymentRequestHash(RequestConfiguration $requestConfiguration): mixed + { + return $requestConfiguration->getRequest()->attributes->get('hash'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PaymentRequestPayResponseProvider.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PaymentRequestPayResponseProvider.php new file mode 100644 index 0000000000..5fb0976421 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Provider/PaymentRequestPayResponseProvider.php @@ -0,0 +1,80 @@ + $paymentRequestFactory + * @param PaymentRequestRepositoryInterface $paymentRequestRepository + */ + public function __construct( + private PaymentRequestFactoryInterface $paymentRequestFactory, + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private PaymentRequestAnnouncerInterface $paymentRequestAnnouncer, + private ServiceProviderAwareProviderInterface $httpResponseProvider, + private DefaultActionProviderInterface $defaultActionProvider, + private DefaultPayloadProviderInterface $defaultPayloadProvider, + private PaymentToPayResolverInterface $paymentToPayResolver, + private AfterPayUrlProvider $afterPayUrlProvider, + ) { + } + + public function getResponse( + RequestConfiguration $requestConfiguration, + OrderInterface $order + ): Response { + $payment = $this->paymentToPayResolver->getLastPayment($order); + Assert::notNull($payment, sprintf('Order (id %s) must have last payment in state "new".', $order->getId())); + + $paymentMethod = $payment->getMethod(); + Assert::notNull($paymentMethod, sprintf('Payment (id %s) must have payment method.', $payment->getId())); + + $paymentRequest = $this->paymentRequestFactory->create($payment, $paymentMethod); + + $paymentRequest->setAction($this->defaultActionProvider->getAction($paymentRequest)); + $paymentRequest->setPayload($this->defaultPayloadProvider->getPayload($paymentRequest)); + + $this->paymentRequestRepository->add($paymentRequest); + + $this->paymentRequestAnnouncer->dispatchPaymentRequestCommand($paymentRequest); + + if ($this->httpResponseProvider->supports($requestConfiguration, $paymentRequest)) { + return $this->httpResponseProvider->getResponse($requestConfiguration, $paymentRequest); + } + + return new RedirectResponse($this->afterPayUrlProvider->getUrl($paymentRequest)); + } + + public function supports( + RequestConfiguration $requestConfiguration, + OrderInterface $order + ): bool { + return true; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolver.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolver.php new file mode 100644 index 0000000000..1536c8e190 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolver.php @@ -0,0 +1,29 @@ +getLastPayment($this->state); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolverInterface.php b/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolverInterface.php new file mode 100644 index 0000000000..f60b23e08d --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/OrderPay/Resolver/PaymentToPayResolverInterface.php @@ -0,0 +1,22 @@ + - - - - diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml index 49d9472f7a..4a086b7302 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml @@ -84,7 +84,6 @@ - diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay.xml new file mode 100644 index 0000000000..af3d9185e8 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/controllers.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/controllers.xml new file mode 100644 index 0000000000..f7087130d2 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/controllers.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + sylius.order + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/handlers.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/handlers.xml new file mode 100644 index 0000000000..c70ab8107d --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/handlers.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/processors.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/processors.xml new file mode 100644 index 0000000000..774f59e71d --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/processors.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/providers.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/providers.xml new file mode 100644 index 0000000000..7dc3c2ee1b --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/order_pay/providers.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml index ad0ded0594..4c97e99907 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml @@ -280,9 +280,5 @@ sylius: version_recommended: Recommended PHP version version_help: Sylius recommends %recommended% PHP version, your current version is %current%. Please upgrade. - payum_gateway: - cash_on_delivery: Cash on delivery - offline: Offline - shipping_method_resolver: by_zones_and_channel: By zones and channel diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/PaymentRequestWorkflowTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/PaymentRequestWorkflowTest.php new file mode 100644 index 0000000000..6940276ef2 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/PaymentRequestWorkflowTest.php @@ -0,0 +1,73 @@ +paymentRequest = new PaymentRequest( + $this->createMock(PaymentInterface::class), + $this->createMock(PaymentMethodInterface::class) + ); + } + + /** + * @test + * + * @dataProvider availableTransitions + */ + public function it_applies_all_available_transitions( + string $fromState, + string $transition, + string $toState, + ): void { + $this->paymentRequest->setState($fromState); + + $stateMachine = $this->getStateMachine(); + $stateMachine->apply($this->paymentRequest, PaymentRequestTransitions::GRAPH, $transition); + + $this->assertSame($toState, $this->paymentRequest->getState()); + } + + public function availableTransitions(): iterable + { + yield [PaymentRequestInterface::STATE_NEW, PaymentRequestTransitions::TRANSITION_PROCESS, PaymentRequestInterface::STATE_PROCESSING]; + yield [PaymentRequestInterface::STATE_NEW, PaymentRequestTransitions::TRANSITION_COMPLETE, PaymentRequestInterface::STATE_COMPLETED]; + yield [PaymentRequestInterface::STATE_PROCESSING, PaymentRequestTransitions::TRANSITION_COMPLETE, PaymentRequestInterface::STATE_COMPLETED]; + yield [PaymentRequestInterface::STATE_NEW, PaymentRequestTransitions::TRANSITION_FAIL, PaymentRequestInterface::STATE_FAILED]; + yield [PaymentRequestInterface::STATE_PROCESSING, PaymentRequestTransitions::TRANSITION_FAIL, PaymentRequestInterface::STATE_FAILED]; + yield [PaymentRequestInterface::STATE_NEW, PaymentRequestTransitions::TRANSITION_CANCEL, PaymentRequestInterface::STATE_CANCELLED]; + yield [PaymentRequestInterface::STATE_PROCESSING, PaymentRequestTransitions::TRANSITION_CANCEL, PaymentRequestInterface::STATE_CANCELLED]; + } + + private function getStateMachine(): StateMachineInterface + { + return self::getContainer()->get('sylius_abstraction.state_machine.adapter.symfony_workflow'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/composer.json b/src/Sylius/Bundle/CoreBundle/composer.json index 306551a22f..63c5309edb 100644 --- a/src/Sylius/Bundle/CoreBundle/composer.json +++ b/src/Sylius/Bundle/CoreBundle/composer.json @@ -53,7 +53,6 @@ "sylius/money-bundle": "^2.0", "sylius/order-bundle": "^2.0", "sylius/payment-bundle": "^2.0", - "sylius/payum-bundle": "^2.0", "sylius/product-bundle": "^2.0", "sylius/promotion-bundle": "^2.0", "sylius/resource-bundle": "^1.11 || ^1.12@alpha", diff --git a/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Processor/RouteParametersProcessorSpec.php b/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Processor/RouteParametersProcessorSpec.php new file mode 100644 index 0000000000..5adfdc8cb7 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Processor/RouteParametersProcessorSpec.php @@ -0,0 +1,76 @@ +beConstructedWith( + $expressionLanguage, + $router, + ); + } + + function it_processes_from_a_route( + RouterInterface $router, + ): void { + $router->generate('a_route', [])->willReturn('https://localhost/a_route'); + + $this->process('a_route')->shouldReturn('https://localhost/a_route'); + } + + function it_processes_from_a_route_and_parameters( + RouterInterface $router, + ExpressionLanguage $expressionLanguage, + ): void { + $expressionLanguage->evaluate('value', [])->willReturn('value')->shouldBeCalledOnce(); + + $router->generate( + 'a_route', + ['aParam'=>'value'], + )->willReturn('https://localhost/a_route?aParam=value'); + + $this->process( + 'a_route', + ['aParam' => 'value'], + )->shouldReturn('https://localhost/a_route?aParam=value'); + } + + function it_processes_from_a_route_and_parameters_and_context( + RouterInterface $router, + ExpressionLanguage $expressionLanguage, + ): void { + $expressionLanguage->evaluate('value', [ + 'value' => '1' + ])->willReturn('1')->shouldBeCalledOnce(); + + $router->generate( + 'a_route', + ['aParam'=>'1'], + )->willReturn('https://localhost/a_route?aParam=1'); + + $this->process( + 'a_route', + ['aParam' => 'value'], + ['value' => '1'], + )->shouldReturn('https://localhost/a_route?aParam=1'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Provider/FinalUrlProviderSpec.php b/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Provider/FinalUrlProviderSpec.php new file mode 100644 index 0000000000..8e09b9274a --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/OrderPay/Provider/FinalUrlProviderSpec.php @@ -0,0 +1,89 @@ +beConstructedWith( + $routeParametersProcessor, + 'final_route', + [], + 'retry_route', + [], + ); + } + function it_provides_a_final_url_from_null_payment( + RouteParametersProcessorInterface $routeParametersProcessor, + ): void { + $routeParametersProcessor->process('final_route', [], [ + 'payment' => null, + 'order' => null, + ])->willReturn('http://localhost/final_route'); + + $this->getUrl(null)->shouldReturn('http://localhost/final_route'); + } + + function it_provides_a_final_url_from_payment_with_state_complete( + PaymentInterface $payment, + RouteParametersProcessorInterface $routeParametersProcessor, + ): void { + $payment->getOrder()->willReturn(null); + $payment->getState()->willReturn(BasePaymentInterface::STATE_COMPLETED); + + $routeParametersProcessor->process('final_route', [], [ + 'payment' => $payment->getWrappedObject(), + 'order' => null, + ])->willReturn('http://localhost/final_route'); + + $this->getUrl($payment)->shouldReturn('http://localhost/final_route'); + } + + function it_provides_a_final_url_from_payment_with_state_authorized( + PaymentInterface $payment, + RouteParametersProcessorInterface $routeParametersProcessor, + ): void { + $payment->getOrder()->willReturn(null); + $payment->getState()->willReturn(BasePaymentInterface::STATE_AUTHORIZED); + + $routeParametersProcessor->process('final_route', [], [ + 'payment' => $payment->getWrappedObject(), + 'order' => null, + ])->willReturn('http://localhost/final_route'); + + $this->getUrl($payment)->shouldReturn('http://localhost/final_route'); + } + + function it_provides_a_retry_url_from_payment_with_state_cancelled( + PaymentInterface $payment, + RouteParametersProcessorInterface $routeParametersProcessor, + ): void { + $payment->getOrder()->willReturn(null); + $payment->getState()->willReturn(BasePaymentInterface::STATE_CANCELLED)->shouldBeCalledTimes(2); + + $routeParametersProcessor->process('retry_route', [], [ + 'payment' => $payment->getWrappedObject(), + 'order' => null, + ])->willReturn('http://localhost/retry_route'); + + $this->getUrl($payment)->shouldReturn('http://localhost/retry_route'); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/test/config/bundles.php b/src/Sylius/Bundle/CoreBundle/test/config/bundles.php index 70fc2bc547..c16585acc2 100644 --- a/src/Sylius/Bundle/CoreBundle/test/config/bundles.php +++ b/src/Sylius/Bundle/CoreBundle/test/config/bundles.php @@ -45,11 +45,9 @@ return [ Knp\Bundle\GaufretteBundle\KnpGaufretteBundle::class => ['all' => true], League\FlysystemBundle\FlysystemBundle::class => ['all' => true], Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], - Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], Sylius\Bundle\FixturesBundle\SyliusFixturesBundle::class => ['all' => true], - Sylius\Bundle\PayumBundle\SyliusPayumBundle::class => ['all' => true], Sonata\BlockBundle\SonataBlockBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], SyliusLabs\DoctrineMigrationsExtraBundle\SyliusLabsDoctrineMigrationsExtraBundle::class => ['all' => true], diff --git a/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncer.php b/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncer.php new file mode 100644 index 0000000000..d65a2eab1f --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncer.php @@ -0,0 +1,32 @@ +commandBus->dispatch($this->paymentRequestCommandProvider->provide($paymentRequest)); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncerInterface.php b/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncerInterface.php new file mode 100644 index 0000000000..c6c6fe2dc1 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Announcer/PaymentRequestAnnouncerInterface.php @@ -0,0 +1,21 @@ + $paymentRequestRepository + * @param array $paymentRequestStatesToBeCancelled + */ + public function __construct( + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private StateMachineInterface $stateMachine, + private ObjectManager $objectManager, + private array $paymentRequestStatesToBeCancelled, + ) { + } + + public function cancelPaymentRequests(mixed $paymentId, string $paymentMethodCode): void + { + $paymentRequests = $this->paymentRequestRepository->findByPaymentIdAndStates($paymentId, $this->paymentRequestStatesToBeCancelled); + + foreach ($paymentRequests as $paymentRequest) { + if ($paymentRequest->getMethod()->getCode() !== $paymentMethodCode) { + $this->stateMachine->apply( + $paymentRequest, + PaymentRequestTransitions::GRAPH, + PaymentRequestTransitions::TRANSITION_CANCEL, + ); + + $this->objectManager->persist($paymentRequest); + $this->objectManager->flush(); + } + } + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationChecker.php b/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationChecker.php new file mode 100644 index 0000000000..1a109c1323 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationChecker.php @@ -0,0 +1,31 @@ + $paymentRequestRepository */ + public function __construct( + private PaymentRequestRepositoryInterface $paymentRequestRepository, + ) { + } + + public function hasDuplicates(PaymentRequestInterface $paymentRequest): bool + { + return $this->paymentRequestRepository->duplicateExists($paymentRequest); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationCheckerInterface.php b/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationCheckerInterface.php new file mode 100644 index 0000000000..7597ef86f2 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Checker/PaymentRequestDuplicationCheckerInterface.php @@ -0,0 +1,21 @@ +hash; + } + + public function setHash(string $hash): void + { + $this->hash = $hash; + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandHandler/Offline/CapturePaymentRequestHandler.php b/src/Sylius/Bundle/PaymentBundle/CommandHandler/Offline/CapturePaymentRequestHandler.php new file mode 100644 index 0000000000..62bba671d5 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandHandler/Offline/CapturePaymentRequestHandler.php @@ -0,0 +1,41 @@ +paymentRequestProvider->provide($capturePaymentRequest); + + $this->stateMachine->apply( + $paymentRequest, + PaymentRequestTransitions::GRAPH, + PaymentRequestTransitions::TRANSITION_COMPLETE + ); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandProvider/AbstractServiceCommandProvider.php b/src/Sylius/Bundle/PaymentBundle/CommandProvider/AbstractServiceCommandProvider.php new file mode 100644 index 0000000000..3d95adff1a --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandProvider/AbstractServiceCommandProvider.php @@ -0,0 +1,66 @@ + */ + protected ServiceProviderInterface $locator; + + public function supports(PaymentRequestInterface $paymentRequest): bool + { + $index = $this->getCommandProviderIndex($paymentRequest); + $httpResponseProvider = $this->getCommandProvider($index); + if (null === $httpResponseProvider) { + return false; + } + + return $httpResponseProvider->supports($paymentRequest); + } + + public function provide(PaymentRequestInterface $paymentRequest): object + { + $index = $this->getCommandProviderIndex($paymentRequest); + $commandProvider = $this->getCommandProvider($index); + if (null === $commandProvider) { + throw new PaymentRequestNotSupportedException(sprintf( + 'No payment request command provider supported for "%s" (command providers available are: %s).', + $index, + implode(', ', $this->getCommandProviderIndexes()), + )); + } + + return $commandProvider->provide($paymentRequest); + } + + public function getCommandProvider(string $index): ?PaymentRequestCommandProviderInterface + { + if (false === $this->locator->has($index)) { + return null; + } + + return $this->locator->get($index); + } + + public function getCommandProviderIndexes(): array + { + return array_keys($this->locator->getProvidedServices()); + } + + abstract protected function getCommandProviderIndex(PaymentRequestInterface $paymentRequest): string; +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandProvider/ActionsCommandProvider.php b/src/Sylius/Bundle/PaymentBundle/CommandProvider/ActionsCommandProvider.php new file mode 100644 index 0000000000..e41c9f7999 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandProvider/ActionsCommandProvider.php @@ -0,0 +1,32 @@ + $locator + */ + public function __construct( + protected ServiceProviderInterface $locator, + ) { + } + + protected function getCommandProviderIndex(PaymentRequestInterface $paymentRequest): string { + return $paymentRequest->getAction(); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandProvider/GatewayFactoryCommandProvider.php b/src/Sylius/Bundle/PaymentBundle/CommandProvider/GatewayFactoryCommandProvider.php new file mode 100644 index 0000000000..211d4e3b7c --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandProvider/GatewayFactoryCommandProvider.php @@ -0,0 +1,47 @@ + $locator + */ + public function __construct( + private PaymentRequestDuplicationCheckerInterface $paymentRequestDuplicationChecker, + private GatewayFactoryNameProviderInterface $gatewayFactoryNameProvider, + protected ServiceProviderInterface $locator, + ) { + } + + public function supports(PaymentRequestInterface $paymentRequest): bool + { + $hasDuplicates = $this->paymentRequestDuplicationChecker->hasDuplicates($paymentRequest); + + if ($hasDuplicates) { + return false; + } + + return parent::supports($paymentRequest); + } + + protected function getCommandProviderIndex(PaymentRequestInterface $paymentRequest): string { + return $this->gatewayFactoryNameProvider->provideFromPaymentRequest($paymentRequest); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandProvider/Offline/CapturePaymentRequestCommandProvider.php b/src/Sylius/Bundle/PaymentBundle/CommandProvider/Offline/CapturePaymentRequestCommandProvider.php new file mode 100644 index 0000000000..f9c36204bd --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandProvider/Offline/CapturePaymentRequestCommandProvider.php @@ -0,0 +1,31 @@ +getAction() === PaymentRequestInterface::ACTION_CAPTURE; + } + + public function provide(PaymentRequestInterface $paymentRequest): object + { + return new CapturePaymentRequest($paymentRequest->getHash()?->toBinary()); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/CommandProvider/PaymentRequestCommandProviderInterface.php b/src/Sylius/Bundle/PaymentBundle/CommandProvider/PaymentRequestCommandProviderInterface.php new file mode 100644 index 0000000000..c707358f3e --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/CommandProvider/PaymentRequestCommandProviderInterface.php @@ -0,0 +1,23 @@ +has('sylius.form_registry.payum_gateway_config')) { + if (!$container->has('sylius.form_registry.payment_gateway_config')) { return; } - $formRegistry = $container->findDefinition('sylius.form_registry.payum_gateway_config'); - $gatewayFactories = [['priority' => 0, 'label' => 'sylius.payum_gateway_factory.offline', 'type' => 'offline']]; + $formRegistry = $container->findDefinition('sylius.form_registry.payment_gateway_config'); + $gatewayFactories = [['priority' => 0, 'label' => 'sylius.gateway_factory.offline', 'type' => 'offline']]; $gatewayConfigurationTypes = $container->findTaggedServiceIds('sylius.gateway_configuration_type'); diff --git a/src/Sylius/Bundle/PaymentBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/PaymentBundle/DependencyInjection/Configuration.php index f8c4d88ded..b0d520fed6 100644 --- a/src/Sylius/Bundle/PaymentBundle/DependencyInjection/Configuration.php +++ b/src/Sylius/Bundle/PaymentBundle/DependencyInjection/Configuration.php @@ -13,17 +13,24 @@ declare(strict_types=1); namespace Sylius\Bundle\PaymentBundle\DependencyInjection; +use Sylius\Bundle\PaymentBundle\Doctrine\ORM\PaymentRequestRepository; +use Sylius\Bundle\PaymentBundle\Form\Type\GatewayConfigType; use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodTranslationType; use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType; use Sylius\Bundle\PaymentBundle\Form\Type\PaymentType; use Sylius\Bundle\ResourceBundle\Controller\ResourceController; use Sylius\Bundle\ResourceBundle\SyliusResourceBundle; +use Sylius\Component\Payment\Factory\PaymentRequestFactory; +use Sylius\Component\Payment\Model\GatewayConfig; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Component\Payment\Model\Payment; use Sylius\Component\Payment\Model\PaymentInterface; use Sylius\Component\Payment\Model\PaymentMethod; use Sylius\Component\Payment\Model\PaymentMethodInterface; use Sylius\Component\Payment\Model\PaymentMethodTranslation; use Sylius\Component\Payment\Model\PaymentMethodTranslationInterface; +use Sylius\Component\Payment\Model\PaymentRequest; +use Sylius\Component\Payment\Model\PaymentRequestInterface; use Sylius\Resource\Factory\Factory; use Sylius\Resource\Factory\TranslatableFactory; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -41,6 +48,23 @@ final class Configuration implements ConfigurationInterface $rootNode ->addDefaultsIfNotSet() ->children() + ->arrayNode('gateway_config') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('validation_groups') + ->useAttributeAsKey('name') + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ->arrayNode('payment_request') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('states_that_should_be_cancelled_when_payment_request_has_changed') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() ->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end() ->arrayNode('gateways') ->useAttributeAsKey('name') @@ -61,6 +85,23 @@ final class Configuration implements ConfigurationInterface ->arrayNode('resources') ->addDefaultsIfNotSet() ->children() + ->arrayNode('gateway_config') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(GatewayConfig::class)->cannotBeEmpty()->end() + ->scalarNode('interface')->defaultValue(GatewayConfigInterface::class)->cannotBeEmpty()->end() + ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() + ->scalarNode('repository')->cannotBeEmpty()->end() + ->scalarNode('factory')->defaultValue(Factory::class)->cannotBeEmpty()->end() + ->scalarNode('form')->defaultValue(GatewayConfigType::class)->cannotBeEmpty()->end() + ->end() + ->end() + ->end() + ->end() ->arrayNode('payment_method') ->addDefaultsIfNotSet() ->children() @@ -118,6 +159,23 @@ final class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('payment_request') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(PaymentRequest::class)->cannotBeEmpty()->end() + ->scalarNode('interface')->defaultValue(PaymentRequestInterface::class)->cannotBeEmpty()->end() + ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() + ->scalarNode('repository')->defaultValue(PaymentRequestRepository::class)->cannotBeEmpty()->end() + ->scalarNode('factory')->defaultValue(PaymentRequestFactory::class)->end() + ->scalarNode('form')->defaultValue(null)->end() + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Sylius/Bundle/PaymentBundle/DependencyInjection/SyliusPaymentExtension.php b/src/Sylius/Bundle/PaymentBundle/DependencyInjection/SyliusPaymentExtension.php index c61062393d..21577d37b2 100644 --- a/src/Sylius/Bundle/PaymentBundle/DependencyInjection/SyliusPaymentExtension.php +++ b/src/Sylius/Bundle/PaymentBundle/DependencyInjection/SyliusPaymentExtension.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Sylius\Bundle\PaymentBundle\DependencyInjection; +use Sylius\Bundle\PaymentBundle\Attribute\AsGatewayConfigurationType; use Sylius\Bundle\PaymentBundle\Attribute\AsPaymentMethodsResolver; use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension; use Symfony\Component\Config\FileLocator; @@ -32,12 +33,25 @@ final class SyliusPaymentExtension extends AbstractResourceExtension $loader->load('services.xml'); $container->setParameter('sylius.payment_gateways', $config['gateways']); + $container->setParameter('sylius.gateway_config.validation_groups', $config['gateway_config']['validation_groups']); + $container->setParameter('sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed', $config['payment_request']['states_that_should_be_cancelled_when_payment_request_has_changed']); $this->registerAutoconfiguration($container); } private function registerAutoconfiguration(ContainerBuilder $container): void { + $container->registerAttributeForAutoconfiguration( + AsGatewayConfigurationType::class, + static function (ChildDefinition $definition, AsGatewayConfigurationType $attribute): void { + $definition->addTag(AsGatewayConfigurationType::SERVICE_TAG, [ + 'type' => $attribute->getType(), + 'label' => $attribute->getLabel(), + 'priority' => $attribute->getPriority(), + ]); + }, + ); + $container->registerAttributeForAutoconfiguration( AsPaymentMethodsResolver::class, static function (ChildDefinition $definition, AsPaymentMethodsResolver $attribute): void { diff --git a/src/Sylius/Bundle/PaymentBundle/Doctrine/ORM/PaymentRequestRepository.php b/src/Sylius/Bundle/PaymentBundle/Doctrine/ORM/PaymentRequestRepository.php new file mode 100644 index 0000000000..a1ed57d530 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Doctrine/ORM/PaymentRequestRepository.php @@ -0,0 +1,93 @@ + + */ +class PaymentRequestRepository extends EntityRepository implements PaymentRequestRepositoryInterface +{ + public function findOneByPaymentId(mixed $hash, mixed $paymentId): ?PaymentRequestInterface + { + $queryBuilder = $this->createQueryBuilder('o'); + + return $queryBuilder + ->andWhere($queryBuilder->expr()->eq('o.hash', ':hash')) + ->andWhere($queryBuilder->expr()->eq('o.payment', ':paymentId')) + ->setParameter('hash', $hash, UuidType::NAME) + ->setParameter('paymentId', $paymentId) + ->getQuery() + ->getOneOrNullResult() + ; + } + + public function createQueryBuilderForPayment(string $paymentId): QueryBuilder + { + $queryBuilder = $this->createQueryBuilder('o'); + $queryBuilder + ->andWhere($queryBuilder->expr()->eq('o.payment', ':paymentId')) + ->setParameter('paymentId', $paymentId) + ; + + return $queryBuilder; + } + + public function duplicateExists(PaymentRequestInterface $paymentRequest): bool + { + /** @var PaymentRequestInterface[] $paymentRequests */ + $paymentRequests = $this->createQueryBuilder('o') + ->innerJoin('o.payment', 'payment') + ->innerJoin('o.method', 'method') + ->where('o != :paymentRequest') + ->andWhere('o.action = :action') + ->andWhere('o.payment = :payment') + ->andWhere('o.method = :method') + // Symfony\Bridge\Doctrine\Types\UuidType has signature changes between sf 5.4 and 6.4 + // We are forced to use type: "uuid" to use both Sf versions + ->setParameter('paymentRequest', $paymentRequest->getHash(), 'uuid') + ->setParameter('action', $paymentRequest->getAction()) + ->setParameter('method', $paymentRequest->getMethod()) + ->setParameter('payment', $paymentRequest->getPayment()) + ->getQuery() + ->getResult() + ; + + return count($paymentRequests) > 0; + } + + /** + * @param array $states + * + * @return array + */ + public function findByPaymentIdAndStates(mixed $paymentId, array $states): array + { + return $this->createQueryBuilder('o') + ->where('o.payment = :paymentId') + ->andWhere('o.state IN (:states)') + ->setParameter('paymentId', $paymentId) + ->setParameter('states', $states) + ->getQuery() + ->getResult() + ; + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/EventListener/PaymentMethodChangeEventListener.php b/src/Sylius/Bundle/PaymentBundle/EventListener/PaymentMethodChangeEventListener.php new file mode 100644 index 0000000000..d1bdcf22cf --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/EventListener/PaymentMethodChangeEventListener.php @@ -0,0 +1,45 @@ +getObject(); + + if (!$entity instanceof PaymentInterface) { + return; + } + + /** @var array> $changeSet */ + $changeSet = $args->getObjectManager()->getUnitOfWork()->getEntityChangeSet($entity); + + if (array_key_exists('method', $changeSet)) { + [$oldMethod, $newMethod] = $changeSet['method']; + + if ($oldMethod !== $newMethod) { + $this->paymentRequestCanceller->cancelPaymentRequests($entity->getId(), $newMethod->getCode()); + } + } + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Exception/PaymentRequestNotSupportedException.php b/src/Sylius/Bundle/PaymentBundle/Exception/PaymentRequestNotSupportedException.php new file mode 100644 index 0000000000..e7460fc3eb --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Exception/PaymentRequestNotSupportedException.php @@ -0,0 +1,26 @@ +getGatewayConfig(); + $builder ->add('translations', ResourceTranslationsType::class, [ 'entry_type' => PaymentMethodTranslationType::class, @@ -37,10 +55,40 @@ final class PaymentMethodType extends AbstractResourceType 'required' => false, 'label' => 'sylius.form.payment_method.enabled', ]) + ->add('gatewayConfig', GatewayConfigType::class, [ + 'label' => false, + 'data' => $gatewayFactory, + ]) ->addEventSubscriber(new AddCodeFormSubscriber()) + ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void { + $paymentMethod = $event->getData(); + + if (!$paymentMethod instanceof PaymentMethodInterface) { + return; + } + + $gatewayConfig = $paymentMethod->getGatewayConfig(); + /** @var string|null $gatewayName */ + $gatewayName = $gatewayConfig->getGatewayName(); + + if (null === $gatewayName && null !== $paymentMethod->getCode()) { + $gatewayConfig->setGatewayName($this->gatewayConfigNameGenerator->generate($paymentMethod)); + } + }) ; } + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'validation_groups' => function (FormInterface $form): array { + $data = $form->getData(); + + return $this->paymentMethodGroupsGenerator->__invoke($data); + }, + ]); + } + public function getBlockPrefix(): string { return 'sylius_payment_method'; diff --git a/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGenerator.php b/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGenerator.php new file mode 100644 index 0000000000..2cb17e7ed2 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGenerator.php @@ -0,0 +1,29 @@ +getCode(); + if (null === $paymentMethodCode) { + return null; + } + + return strtolower(str_replace([' ', '-', '\''], '_', $paymentMethodCode)); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGeneratorInterface.php b/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGeneratorInterface.php new file mode 100644 index 0000000000..fcfaaabb07 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Generator/GatewayConfigNameGeneratorInterface.php @@ -0,0 +1,21 @@ + */ + protected ServiceProviderInterface $locator; + + public function supports( + RequestConfiguration $requestConfiguration, + PaymentRequestInterface $paymentRequest, + ): bool { + $index = $this->getHttpResponseProviderIndex($paymentRequest); + $httpResponseProvider = $this->getHttpResponseProvider($index); + if (null === $httpResponseProvider) { + return false; + } + + return $httpResponseProvider->supports($requestConfiguration, $paymentRequest); + } + + public function getResponse( + RequestConfiguration $requestConfiguration, + PaymentRequestInterface $paymentRequest, + ): Response { + $index = $this->getHttpResponseProviderIndex($paymentRequest); + $httpResponseProvider = $this->getHttpResponseProvider($index); + if (null === $httpResponseProvider) { + throw new PaymentRequestNotSupportedException(sprintf( + 'No PaymentRequest HTTP Response provider supported for "%s" (providers available are: %s).', + $index, + implode(', ', $this->getProviderIndexes()), + )); + } + + return $httpResponseProvider->getResponse($requestConfiguration, $paymentRequest); + } + + public function getProviderIndexes(): array + { + return array_keys($this->locator->getProvidedServices()); + } + + public function getHttpResponseProvider(string $index): ?HttpResponseProviderInterface + { + if (false === $this->locator->has($index)) { + return null; + } + + return $this->locator->get($index); + } + + abstract protected function getHttpResponseProviderIndex(PaymentRequestInterface $paymentRequest): string; +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/ActionsHttpResponseProvider.php b/src/Sylius/Bundle/PaymentBundle/Provider/ActionsHttpResponseProvider.php new file mode 100644 index 0000000000..51d59c5e23 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/ActionsHttpResponseProvider.php @@ -0,0 +1,32 @@ + $locator + */ + public function __construct( + protected ServiceProviderInterface $locator, + ) { + } + + protected function getHttpResponseProviderIndex(PaymentRequestInterface $paymentRequest): string { + return $paymentRequest->getAction(); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProvider.php b/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProvider.php new file mode 100644 index 0000000000..f4de5160ff --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProvider.php @@ -0,0 +1,31 @@ +getMethod(); + /** @var GatewayConfigInterface $gatewayConfig */ + $gatewayConfig = $paymentMethod->getGatewayConfig(); + $authorize = $gatewayConfig->getConfig()['use_authorize'] ?? false; + + return $authorize ? PaymentRequestInterface::ACTION_AUTHORIZE : $paymentRequest->getAction(); + + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProviderInterface.php b/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProviderInterface.php new file mode 100644 index 0000000000..bf7f521a0c --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/DefaultActionProviderInterface.php @@ -0,0 +1,21 @@ + $locator + */ + public function __construct( + private GatewayFactoryNameProviderInterface $gatewayFactoryNameProvider, + protected ServiceProviderInterface $locator, + ) { + } + + protected function getHttpResponseProviderIndex(PaymentRequestInterface $paymentRequest): string { + return $this->gatewayFactoryNameProvider->provideFromPaymentRequest($paymentRequest); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProvider.php b/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProvider.php new file mode 100644 index 0000000000..2bb6a918f2 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProvider.php @@ -0,0 +1,36 @@ +getGatewayConfig(); + + return $gatewayConfig->getConfig()['factory'] ?? $gatewayConfig->getFactoryName(); + } + + public function provideFromPaymentRequest(PaymentRequestInterface $paymentRequest): string { + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = $paymentRequest->getMethod(); + + return $this->provide($paymentMethod); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProviderInterface.php b/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProviderInterface.php new file mode 100644 index 0000000000..839f62c580 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/GatewayFactoryNameProviderInterface.php @@ -0,0 +1,24 @@ + $paymentRequestRepository + */ + public function __construct( + private PaymentRequestRepositoryInterface $paymentRequestRepository, + ) { + } + + public function provide(PaymentRequestHashAwareInterface $command): PaymentRequestInterface + { + /** @var PaymentRequestInterface|null $paymentRequest */ + $paymentRequest = $this->paymentRequestRepository->find($command->getHash()); + if (null === $paymentRequest) { + throw new PaymentRequestNotFoundException(); + } + + return $paymentRequest; + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Provider/PaymentRequestProviderInterface.php b/src/Sylius/Bundle/PaymentBundle/Provider/PaymentRequestProviderInterface.php new file mode 100644 index 0000000000..b5aae2a1dc --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Provider/PaymentRequestProviderInterface.php @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/Payment.orm.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/Payment.orm.xml index ddd3e7b39b..27e17cc3d0 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/Payment.orm.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/Payment.orm.xml @@ -23,6 +23,12 @@ + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentMethod.orm.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentMethod.orm.xml index 3d7a61baad..d12a055c30 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentMethod.orm.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentMethod.orm.xml @@ -30,6 +30,13 @@ + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentRequest.orm.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentRequest.orm.xml new file mode 100644 index 0000000000..b63f9e74a0 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/doctrine/model/PaymentRequest.orm.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services.xml index fb9fadbbe9..7e6bfe9b6c 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services.xml @@ -13,7 +13,7 @@ - + @@ -42,5 +42,22 @@ + + + + + %sylius.form.type.gateway_config.validation_groups% + %sylius.gateway_config.validation_groups% + + + + + %sylius.form.type.payment_method.validation_groups% + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/announcer.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/announcer.xml new file mode 100644 index 0000000000..baffe147a7 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/announcer.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/canceller.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/canceller.xml new file mode 100644 index 0000000000..fddfe4a977 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/canceller.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + %sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed% + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/checker.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/checker.xml new file mode 100644 index 0000000000..0821e66146 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/checker.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_handler.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_handler.xml new file mode 100644 index 0000000000..e88644f0b7 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_handler.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_provider.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_provider.xml new file mode 100644 index 0000000000..c01cda7de7 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/command_provider.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/form.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/form.xml index 4f9b25c573..80802adcbf 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/form.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/form.xml @@ -22,11 +22,21 @@ sylius + + sylius + + + %sylius.model.gateway_config.class% + %sylius.form.type.gateway_config.validation_groups% + + + + %sylius.model.payment.class% %sylius.form.type.payment.validation_groups% @@ -36,6 +46,8 @@ %sylius.model.payment_method.class% %sylius.form.type.payment_method.validation_groups% + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/listener.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/listener.xml new file mode 100644 index 0000000000..1f44abf819 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/listener.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/config/services/provider.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/provider.xml new file mode 100644 index 0000000000..d870cc6043 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/provider.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/validator.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/validator.xml similarity index 72% rename from src/Sylius/Bundle/PayumBundle/Resources/config/services/validator.xml rename to src/Sylius/Bundle/PaymentBundle/Resources/config/services/validator.xml index 150b0d3a0e..3c36b0a299 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services/validator.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/services/validator.xml @@ -18,13 +18,9 @@ - + %sylius.gateway_factories% - - - %sylius.payum.gateway_config.validation_groups% - diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/validation/GatewayConfig.xml b/src/Sylius/Bundle/PaymentBundle/Resources/config/validation/GatewayConfig.xml similarity index 71% rename from src/Sylius/Bundle/PayumBundle/Resources/config/validation/GatewayConfig.xml rename to src/Sylius/Bundle/PaymentBundle/Resources/config/validation/GatewayConfig.xml index b8bbcb78a0..3af72be4fe 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/validation/GatewayConfig.xml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/config/validation/GatewayConfig.xml @@ -11,20 +11,25 @@ --> - - + + + - + + diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/PaymentBundle/Resources/translations/messages.en.yml index f7a10edfbe..a3aeacdb69 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/translations/messages.en.yml @@ -32,3 +32,5 @@ sylius: instructions: Instructions name: Name position: Position + gateway_factory: + offline: Offline diff --git a/src/Sylius/Bundle/PaymentBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/PaymentBundle/Resources/translations/validators.en.yml index 253cde092d..be0c3a804c 100644 --- a/src/Sylius/Bundle/PaymentBundle/Resources/translations/validators.en.yml +++ b/src/Sylius/Bundle/PaymentBundle/Resources/translations/validators.en.yml @@ -16,6 +16,12 @@ sylius: min_length: The credit card's CVV code must be at most than {{ limit }} digits long. expiry_year: not_blank: Please select the expiration year. + gateway_config: + invalid_gateway_factory: Invalid gateway factory. Available factories are {{ available_factories }}. + gateway_name: + not_blank: Please enter gateway name. + factory_name: + not_blank: Please enter gateway factory name. payment: currency_code: not_valid: The currency code you entered is invalid. diff --git a/src/Sylius/Bundle/PaymentBundle/SyliusPaymentBundle.php b/src/Sylius/Bundle/PaymentBundle/SyliusPaymentBundle.php index 32eded95e4..922aa3cea7 100644 --- a/src/Sylius/Bundle/PaymentBundle/SyliusPaymentBundle.php +++ b/src/Sylius/Bundle/PaymentBundle/SyliusPaymentBundle.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Sylius\Bundle\PaymentBundle; +use Sylius\Bundle\PaymentBundle\DependencyInjection\Compiler\RegisterGatewayConfigTypePass; use Sylius\Bundle\PaymentBundle\DependencyInjection\Compiler\RegisterPaymentMethodsResolversPass; use Sylius\Bundle\ResourceBundle\AbstractResourceBundle; use Sylius\Bundle\ResourceBundle\SyliusResourceBundle; @@ -36,6 +37,7 @@ final class SyliusPaymentBundle extends AbstractResourceBundle parent::build($container); $container->addCompilerPass(new RegisterPaymentMethodsResolversPass()); + $container->addCompilerPass(new RegisterGatewayConfigTypePass()); } protected function getModelNamespace(): string diff --git a/src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php b/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php similarity index 84% rename from src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php rename to src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php index cb18fa7c83..9bca34779a 100644 --- a/src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php +++ b/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/Compiler/RegisterGatewayConfigTypePassTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Sylius\Bundle\PayumBundle\Tests\DependencyInjection\Compiler; +namespace Sylius\Bundle\PaymentBundle\Tests\DependencyInjection\Compiler; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase; -use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\RegisterGatewayConfigTypePass; +use Sylius\Bundle\PaymentBundle\DependencyInjection\Compiler\RegisterGatewayConfigTypePass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -23,7 +23,7 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa /** @test */ public function it_registers_payment_gateways_configs_by_their_priority_in_the_registry(): void { - $this->setDefinition('sylius.form_registry.payum_gateway_config', new Definition()); + $this->setDefinition('sylius.form_registry.payment_gateway_config', new Definition()); $this->setDefinition( 'custom_low_priority_gateway', (new Definition('LowGatewayClass'))->addTag( @@ -49,17 +49,17 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa $this->compile(); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'low_gateway', 'LowGatewayClass'], ); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'high_gateway', 'HighGatewayClass'], ); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'medium_gateway', 'MediumGatewayClass'], ); @@ -69,7 +69,7 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa 'high_gateway' => 'High Gateway', 'medium_gateway' => 'Medium Gateway', 'low_gateway' => 'Low Gateway', - 'offline' => 'sylius.payum_gateway_factory.offline', + 'offline' => 'sylius.gateway_factory.offline', ], $this->container->getParameter('sylius.gateway_factories'), ); @@ -78,7 +78,7 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa /** @test */ public function it_registers_payment_gateways_configs_with_default_priorities_in_the_registry(): void { - $this->setDefinition('sylius.form_registry.payum_gateway_config', new Definition()); + $this->setDefinition('sylius.form_registry.payment_gateway_config', new Definition()); $this->setDefinition( 'custom_low_priority_gateway', (new Definition('LowGatewayClass'))->addTag( @@ -104,17 +104,17 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa $this->compile(); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'low_gateway', 'LowGatewayClass'], ); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'regular_gateway', 'RegularGatewayClass'], ); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'sylius.form_registry.payum_gateway_config', + 'sylius.form_registry.payment_gateway_config', 'add', ['gateway_config', 'medium_gateway', 'MediumGatewayClass'], ); @@ -123,7 +123,7 @@ final class RegisterGatewayConfigTypePassTest extends AbstractCompilerPassTestCa [ 'medium_gateway' => 'Medium Gateway', 'low_gateway' => 'Low Gateway', - 'offline' => 'sylius.payum_gateway_factory.offline', + 'offline' => 'sylius.gateway_factory.offline', 'regular_gateway' => 'Regular Gateway', ], $this->container->getParameter('sylius.gateway_factories'), diff --git a/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/SyliusPaymentExtensionTest.php b/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/SyliusPaymentExtensionTest.php index 1fc34cd505..833dd7c06b 100644 --- a/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/SyliusPaymentExtensionTest.php +++ b/src/Sylius/Bundle/PaymentBundle/Tests/DependencyInjection/SyliusPaymentExtensionTest.php @@ -14,9 +14,12 @@ declare(strict_types=1); namespace Sylius\Bundle\PaymentBundle\Tests\DependencyInjection; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; +use Sylius\Bundle\PaymentBundle\Attribute\AsGatewayConfigurationType; use Sylius\Bundle\PaymentBundle\Attribute\AsPaymentMethodsResolver; use Sylius\Bundle\PaymentBundle\DependencyInjection\SyliusPaymentExtension; +use Sylius\Bundle\PaymentBundle\Tests\Stub\GatewayConfigurationTypeStub; use Sylius\Bundle\PaymentBundle\Tests\Stub\PaymentMethodsResolverStub; +use Sylius\Component\Payment\Model\PaymentRequestInterface; use Symfony\Component\DependencyInjection\Definition; final class SyliusPaymentExtensionTest extends AbstractExtensionTestCase @@ -41,6 +44,62 @@ final class SyliusPaymentExtensionTest extends AbstractExtensionTestCase ); } + /** @test */ + public function it_autoconfigures_gateway_configuration_type_with_attribute(): void + { + $this->container->setDefinition( + 'acme.gateway_configuration_type_with_attribute', + (new Definition()) + ->setClass(GatewayConfigurationTypeStub::class) + ->setAutoconfigured(true), + ); + + $this->load(); + $this->compile(); + + $this->assertContainerBuilderHasServiceDefinitionWithTag( + 'acme.gateway_configuration_type_with_attribute', + AsGatewayConfigurationType::SERVICE_TAG, + ['type' => 'test', 'label' => 'Test', 'priority' => 15], + ); + } + + /** @test */ + public function it_loads_gateway_config_validation_groups_parameter_value_properly(): void + { + $this->load([ + 'gateway_config' => [ + 'validation_groups' => [ + 'paypal_express_checkout' => ['sylius', 'paypal'], + 'offline' => ['sylius'], + ], + ], + ]); + + $this->assertContainerBuilderHasParameter( + 'sylius.gateway_config.validation_groups', + ['paypal_express_checkout' => ['sylius', 'paypal'], 'offline' => ['sylius']], + ); + } + + /** @test */ + public function it_loads_parameter_with_payment_request_states_that_should_be_cancelled_when_payment_method_is_changed(): void + { + $this->load([ + 'payment_request' => [ + 'states_that_should_be_cancelled_when_payment_request_has_changed' => [ + PaymentRequestInterface::STATE_NEW, + PaymentRequestInterface::STATE_PROCESSING, + ], + ], + ]); + + $this->assertContainerBuilderHasParameter( + 'sylius.payment_request.states_that_should_be_cancelled_when_payment_request_has_changed', + [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING], + ); + } + protected function getContainerExtensions(): array { return [new SyliusPaymentExtension()]; diff --git a/src/Sylius/Bundle/PaymentBundle/Tests/EventListener/PaymentMethodChangeEventListenerTest.php b/src/Sylius/Bundle/PaymentBundle/Tests/EventListener/PaymentMethodChangeEventListenerTest.php new file mode 100644 index 0000000000..d4648aa2c6 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Tests/EventListener/PaymentMethodChangeEventListenerTest.php @@ -0,0 +1,107 @@ +createMock(PaymentRequestCancellerInterface::class); + $payment = $this->createMock(PaymentInterface::class); + $oldMethod = $this->createMock(PaymentMethodInterface::class); + $newMethod = $this->createMock(PaymentMethodInterface::class); + $entityManager = $this->createMock(EntityManagerInterface::class); + $unitOfWork = $this->createMock(UnitOfWork::class); + + $payment->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $newMethod->expects($this->once()) + ->method('getCode') + ->willReturn('newMethodCode'); + + $unitOfWork->expects($this->once()) + ->method('getEntityChangeSet') + ->with($payment) + ->willReturn(['method' => [$oldMethod, $newMethod]]); + + $entityManager->expects($this->once()) + ->method('getUnitOfWork') + ->willReturn($unitOfWork); + + $args = new PostUpdateEventArgs($payment, $entityManager); + + $paymentRequestCanceller->expects($this->once()) + ->method('cancelPaymentRequests') + ->with(1, 'newMethodCode'); + + $listener = new PaymentMethodChangeEventListener($paymentRequestCanceller); + $listener->postUpdate($args); + } + + /** @test */ + public function it_does_not_update_if_entity_is_not_payment(): void + { + $paymentRequestCanceller = $this->createMock(PaymentRequestCancellerInterface::class); + $nonPaymentEntity = new \stdClass(); + $entityManager = $this->createMock(EntityManagerInterface::class); + + $args = new PostUpdateEventArgs($nonPaymentEntity, $entityManager); + + $paymentRequestCanceller->expects($this->never()) + ->method('cancelPaymentRequests'); + + $listener = new PaymentMethodChangeEventListener($paymentRequestCanceller); + $listener->postUpdate($args); + } + + /** @test */ + public function it_does_not_update_if_payment_method_did_not_change(): void + { + $paymentRequestCanceller = $this->createMock(PaymentRequestCancellerInterface::class); + $payment = $this->createMock(PaymentInterface::class); + $oldMethod = $this->createMock(PaymentMethodInterface::class); + $newMethod = $oldMethod; + $entityManager = $this->createMock(EntityManagerInterface::class); + $unitOfWork = $this->createMock(UnitOfWork::class); + + $unitOfWork->expects($this->once()) + ->method('getEntityChangeSet') + ->with($payment) + ->willReturn(['method' => [$oldMethod, $newMethod]]); + + $entityManager->expects($this->once()) + ->method('getUnitOfWork') + ->willReturn($unitOfWork); + + $args = new PostUpdateEventArgs($payment, $entityManager); + + $paymentRequestCanceller->expects($this->never()) + ->method('cancelPaymentRequests'); + + $listener = new PaymentMethodChangeEventListener($paymentRequestCanceller); + $listener->postUpdate($args); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/Tests/Stub/GatewayConfigurationTypeStub.php b/src/Sylius/Bundle/PaymentBundle/Tests/Stub/GatewayConfigurationTypeStub.php similarity index 75% rename from src/Sylius/Bundle/PayumBundle/Tests/Stub/GatewayConfigurationTypeStub.php rename to src/Sylius/Bundle/PaymentBundle/Tests/Stub/GatewayConfigurationTypeStub.php index 7141aebc4d..37bcde9c5a 100644 --- a/src/Sylius/Bundle/PayumBundle/Tests/Stub/GatewayConfigurationTypeStub.php +++ b/src/Sylius/Bundle/PaymentBundle/Tests/Stub/GatewayConfigurationTypeStub.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Sylius\Bundle\PayumBundle\Tests\Stub; +namespace Sylius\Bundle\PaymentBundle\Tests\Stub; -use Sylius\Bundle\PayumBundle\Attribute\AsGatewayConfigurationType; +use Sylius\Bundle\PaymentBundle\Attribute\AsGatewayConfigurationType; #[AsGatewayConfigurationType(type: 'test', label: 'Test', priority: 15)] final class GatewayConfigurationTypeStub diff --git a/src/Sylius/Bundle/PayumBundle/Validator/Constraints/GatewayFactoryExists.php b/src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExists.php similarity index 91% rename from src/Sylius/Bundle/PayumBundle/Validator/Constraints/GatewayFactoryExists.php rename to src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExists.php index c02c3eee20..b3cc868fa7 100644 --- a/src/Sylius/Bundle/PayumBundle/Validator/Constraints/GatewayFactoryExists.php +++ b/src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExists.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Sylius\Bundle\PayumBundle\Validator\Constraints; +namespace Sylius\Bundle\PaymentBundle\Validator\Constraints; use Symfony\Component\Validator\Constraint; diff --git a/src/Sylius/Bundle/PayumBundle/Validator/GatewayFactoryExistsValidator.php b/src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExistsValidator.php similarity index 91% rename from src/Sylius/Bundle/PayumBundle/Validator/GatewayFactoryExistsValidator.php rename to src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExistsValidator.php index c478cfa05a..b4f710035e 100644 --- a/src/Sylius/Bundle/PayumBundle/Validator/GatewayFactoryExistsValidator.php +++ b/src/Sylius/Bundle/PaymentBundle/Validator/Constraints/GatewayFactoryExistsValidator.php @@ -11,9 +11,8 @@ declare(strict_types=1); -namespace Sylius\Bundle\PayumBundle\Validator; +namespace Sylius\Bundle\PaymentBundle\Validator\Constraints; -use Sylius\Bundle\PayumBundle\Validator\Constraints\GatewayFactoryExists; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; diff --git a/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php new file mode 100644 index 0000000000..3f5e2e158e --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php @@ -0,0 +1,36 @@ + $defaultValidationGroups + * @param array> $validationGroups + */ + public function __construct(private array $defaultValidationGroups, private array $validationGroups) + { + } + + public function __invoke(GatewayConfigInterface $gatewayConfig): array + { + if ($gatewayConfig->getFactoryName() === null) { + return $this->defaultValidationGroups; + } + + return $this->validationGroups[$gatewayConfig->getFactoryName()] ?? $this->defaultValidationGroups; + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorInterface.php b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorInterface.php new file mode 100644 index 0000000000..726464926c --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorInterface.php @@ -0,0 +1,22 @@ + */ + public function __invoke(GatewayConfigInterface $gatewayConfig): array; +} diff --git a/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGenerator.php b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGenerator.php new file mode 100644 index 0000000000..57324ba36c --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGenerator.php @@ -0,0 +1,40 @@ + $defaultValidationGroups */ + public function __construct( + private array $defaultValidationGroups, + private GatewayConfigGroupsGeneratorInterface $gatewayConfigGroupsGenerator, + ) { + } + + public function __invoke(PaymentMethodInterface $paymentMethod): array + { + $gatewayConfig = $paymentMethod->getGatewayConfig(); + if ($gatewayConfig === null) { + return $this->defaultValidationGroups; + } + + return array_unique(array_merge( + $this->defaultValidationGroups, + $this->gatewayConfigGroupsGenerator->__invoke($gatewayConfig), + )); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorInterface.php b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorInterface.php new file mode 100644 index 0000000000..6aabfc43ee --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorInterface.php @@ -0,0 +1,22 @@ + */ + public function __invoke(PaymentMethodInterface $paymentMethod): array; +} diff --git a/src/Sylius/Bundle/PaymentBundle/composer.json b/src/Sylius/Bundle/PaymentBundle/composer.json index d09ab5c0b1..a1bc7dd4f0 100644 --- a/src/Sylius/Bundle/PaymentBundle/composer.json +++ b/src/Sylius/Bundle/PaymentBundle/composer.json @@ -29,7 +29,10 @@ "php": "^8.2", "sylius/payment": "^2.0", "sylius/resource-bundle": "^1.11 || ^1.12@alpha", - "symfony/framework-bundle": "^6.4.1 || ^7.1" + "sylius/state-machine-abstraction": "^2.0", + "symfony/doctrine-bridge": "^6.4.0 || ^7.1", + "symfony/framework-bundle": "^6.4.1 || ^7.1", + "symfony/messenger": "^6.4.0 || ^7.1" }, "conflict": { "doctrine/orm": "2.15.2" diff --git a/src/Sylius/Bundle/PaymentBundle/spec/Canceller/PaymentRequestCancellerSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/Canceller/PaymentRequestCancellerSpec.php new file mode 100644 index 0000000000..12a77a7a84 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/spec/Canceller/PaymentRequestCancellerSpec.php @@ -0,0 +1,68 @@ +beConstructedWith($paymentRequestRepository, $stateMachine, $objectManager, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING]); + } + + function it_cancels_payment_requests_if_the_payment_method_code_is_different( + PaymentRequestRepositoryInterface $paymentRequestRepository, + PaymentRequestInterface $paymentRequest1, + PaymentRequestInterface $paymentRequest2, + PaymentMethodInterface $paymentMethod1, + PaymentMethodInterface $paymentMethod2, + StateMachineInterface $stateMachine, + ObjectManager $objectManager, + ): void { + $paymentRequestRepository->findByPaymentIdAndStates(1, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING]) + ->willReturn([$paymentRequest1, $paymentRequest2]); + + $paymentRequest1->getMethod()->willReturn($paymentMethod1); + $paymentMethod1->getCode()->willReturn('payment_method_with_different_code'); + $paymentRequest2->getMethod()->willReturn($paymentMethod2); + $paymentMethod2->getCode()->willReturn('payment_method_code'); + + $stateMachine->apply($paymentRequest1, 'sylius_payment_request', 'cancel')->shouldBeCalled(); + $stateMachine->apply($paymentRequest2, 'sylius_payment_request', 'cancel')->shouldNotBeCalled(); + $objectManager->persist($paymentRequest1)->shouldBeCalled(); + $objectManager->persist($paymentRequest2)->shouldNotBeCalled(); + $objectManager->flush()->shouldBeCalledOnce(); + + $this->cancelPaymentRequests(1, 'payment_method_code'); + } + + function it_does_not_cancel_payment_requests_if_none_found( + PaymentRequestRepositoryInterface $paymentRequestRepository, + StateMachineInterface $stateMachine, + ): void { + $paymentRequestRepository->findByPaymentIdAndStates(1, [PaymentRequestInterface::STATE_NEW, PaymentRequestInterface::STATE_PROCESSING]) + ->willReturn([]); + + $stateMachine->apply(Argument::cetera())->shouldNotBeCalled(); + + $this->cancelPaymentRequests(1, 'payment_method_code'); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/spec/CommandHandler/Offline/CapturePaymentRequestHandlerSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/CommandHandler/Offline/CapturePaymentRequestHandlerSpec.php new file mode 100644 index 0000000000..2122d9c28e --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/spec/CommandHandler/Offline/CapturePaymentRequestHandlerSpec.php @@ -0,0 +1,43 @@ +beConstructedWith($paymentRequestProvider, $stateMachine); + } + + function it_processes_offline_capture( + PaymentRequestProviderInterface $paymentRequestProvider, + StateMachineInterface $stateMachine, + PaymentRequestInterface $paymentRequest + ): void { + $capturePaymentRequest = new CapturePaymentRequest('hash'); + $paymentRequestProvider->provide($capturePaymentRequest)->willReturn($paymentRequest); + $stateMachine->apply($paymentRequest, PaymentRequestTransitions::GRAPH, PaymentRequestTransitions::TRANSITION_COMPLETE)->shouldBeCalled(); + + $this->__invoke($capturePaymentRequest); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/spec/Generator/GatewayConfigNameGeneratorSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/Generator/GatewayConfigNameGeneratorSpec.php new file mode 100644 index 0000000000..0c66d8f3c3 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/spec/Generator/GatewayConfigNameGeneratorSpec.php @@ -0,0 +1,36 @@ +getCode()->willReturn('PayPal Express Checkout'); + + $this->generate($paymentMethod)->shouldReturn('paypal_express_checkout'); + } + + function it_returns_null_if_payment_method_code_is_null( + PaymentMethodInterface $paymentMethod, + ): void { + $paymentMethod->getCode()->willReturn(null); + + $this->generate($paymentMethod)->shouldReturn(null); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/spec/Validator/GatewayFactoryExistsValidatorSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/Validator/Constraints/GatewayFactoryExistsValidatorSpec.php similarity index 91% rename from src/Sylius/Bundle/PayumBundle/spec/Validator/GatewayFactoryExistsValidatorSpec.php rename to src/Sylius/Bundle/PaymentBundle/spec/Validator/Constraints/GatewayFactoryExistsValidatorSpec.php index d648096f0d..e597d1517f 100644 --- a/src/Sylius/Bundle/PayumBundle/spec/Validator/GatewayFactoryExistsValidatorSpec.php +++ b/src/Sylius/Bundle/PaymentBundle/spec/Validator/Constraints/GatewayFactoryExistsValidatorSpec.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace spec\Sylius\Bundle\PayumBundle\Validator; +namespace spec\Sylius\Bundle\PaymentBundle\Validator\Constraints; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; -use Sylius\Bundle\PayumBundle\Validator\Constraints\GatewayFactoryExists; +use Sylius\Bundle\PaymentBundle\Validator\Constraints\GatewayFactoryExists; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\UnexpectedTypeException; diff --git a/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php new file mode 100644 index 0000000000..03752181ac --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php @@ -0,0 +1,44 @@ +beConstructedWith(['sylius'], [ + 'paypal_express_checkout' => ['paypal_express_checkout', 'sylius'], + 'stripe_checkout' => ['stripe_checkout', 'sylius'], + ]); + } + + function it_returns_gateway_config_validation_groups( + GatewayConfigInterface $gatewayConfig, + ): void { + $gatewayConfig->getFactoryName()->willReturn('paypal_express_checkout'); + + $this($gatewayConfig)->shouldReturn(['paypal_express_checkout', 'sylius']); + } + + function it_returns_default_validation_groups_if_factory_name_is_null( + GatewayConfigInterface $gatewayConfig, + ): void { + $gatewayConfig->getFactoryName()->willReturn(null); + + $this($gatewayConfig)->shouldReturn(['sylius']); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorSpec.php b/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorSpec.php new file mode 100644 index 0000000000..cd1e62aa77 --- /dev/null +++ b/src/Sylius/Bundle/PaymentBundle/spec/Validator/GroupsGenerator/PaymentMethodGroupsGeneratorSpec.php @@ -0,0 +1,46 @@ +beConstructedWith(['sylius'], $gatewayConfigGroupsGenerator); + } + + function it_returns_payment_method_validation_groups( + GatewayConfigGroupsGeneratorInterface $gatewayConfigGroupsGenerator, + GatewayConfigInterface $gatewayConfig, + PaymentMethodInterface $paymentMethod, + ): void { + $paymentMethod->getGatewayConfig()->willReturn($gatewayConfig); + $gatewayConfigGroupsGenerator->__invoke($gatewayConfig)->willReturn(['paypal_express_checkout', 'sylius']); + + $this($paymentMethod)->shouldReturn(['sylius', 'paypal_express_checkout']); + } + + function it_returns_default_validation_groups_if_gateway_config_is_null( + PaymentMethodInterface $paymentMethod, + ): void { + $paymentMethod->getGatewayConfig()->willReturn(null); + + $this($paymentMethod)->shouldReturn(['sylius']); + } +} diff --git a/src/Sylius/Bundle/PaymentBundle/test/app/AppKernel.php b/src/Sylius/Bundle/PaymentBundle/test/app/AppKernel.php index 67ca3597de..52d347e507 100644 --- a/src/Sylius/Bundle/PaymentBundle/test/app/AppKernel.php +++ b/src/Sylius/Bundle/PaymentBundle/test/app/AppKernel.php @@ -27,6 +27,7 @@ class AppKernel extends Kernel new Sylius\Bundle\PaymentBundle\SyliusPaymentBundle(), new Sylius\Bundle\LocaleBundle\SyliusLocaleBundle(), new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(), + new Sylius\Abstraction\StateMachine\SyliusStateMachineAbstractionBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), ]; } diff --git a/src/Sylius/Bundle/PaymentBundle/test/app/config/config.yml b/src/Sylius/Bundle/PaymentBundle/test/app/config/config.yml index c141b550b5..6d962c7532 100644 --- a/src/Sylius/Bundle/PaymentBundle/test/app/config/config.yml +++ b/src/Sylius/Bundle/PaymentBundle/test/app/config/config.yml @@ -1,4 +1,5 @@ imports: + - { resource: "@SyliusPaymentBundle/Resources/config/app/config.yml" } - { resource: "@SyliusPaymentBundle/test/app/config/parameters.yml" } framework: @@ -15,6 +16,7 @@ framework: storage_factory_id: session.storage.factory.mock_file http_method_override: true test: ~ + workflows: ~ twig: debug: "%kernel.debug%" diff --git a/src/Sylius/Bundle/PayumBundle/Controller/PayumController.php b/src/Sylius/Bundle/PayumBundle/Controller/PayumController.php deleted file mode 100644 index 30c4c96bbc..0000000000 --- a/src/Sylius/Bundle/PayumBundle/Controller/PayumController.php +++ /dev/null @@ -1,140 +0,0 @@ -requestConfigurationFactory->create($this->orderMetadata, $request); - - /** @var OrderInterface|null $order */ - $order = $this->orderRepository->findOneByTokenValue($tokenValue); - - if (null === $order) { - throw new NotFoundHttpException(sprintf('Order with token "%s" does not exist.', $tokenValue)); - } - - $request->getSession()->set('sylius_order_id', $order->getId()); - $payment = $order->getLastPayment(PaymentInterface::STATE_NEW); - - if (null === $payment) { - $url = $this->router->generate('sylius_shop_order_thank_you'); - - return new RedirectResponse($url); - } - - $token = $this->provideTokenBasedOnPayment($payment, $configuration->getParameters()->get('redirect')); - - return new RedirectResponse($token->getTargetUrl()); - } - - public function afterCaptureAction(Request $request): Response - { - $configuration = $this->requestConfigurationFactory->create($this->orderMetadata, $request); - - $token = $this->getHttpRequestVerifier()->verify($request); - - /** @var Generic&GetStatusInterface $status */ - $status = $this->getStatusRequestFactory->createNewWithModel($token); - $this->payum->getGateway($token->getGatewayName())->execute($status); - - $resolveNextRoute = $this->resolveNextRouteRequestFactory->createNewWithModel($status->getFirstModel()); - $this->payum->getGateway($token->getGatewayName())->execute($resolveNextRoute); - - $this->getHttpRequestVerifier()->invalidate($token); - - if (PaymentInterface::STATE_NEW !== $status->getValue()) { - /** @var FlashBagInterface $flashBag */ - $flashBag = $request->getSession()->getBag('flashes'); - $flashBag->add('info', sprintf('sylius.payment.%s', $status->getValue())); - } - - return new RedirectResponse($this->router->generate($resolveNextRoute->getRouteName(), $resolveNextRoute->getRouteParameters())); - } - - private function getTokenFactory(): GenericTokenFactoryInterface - { - return $this->payum->getTokenFactory(); - } - - private function getHttpRequestVerifier(): HttpRequestVerifierInterface - { - return $this->payum->getHttpRequestVerifier(); - } - - private function provideTokenBasedOnPayment(PaymentInterface $payment, array $redirectOptions): TokenInterface - { - /** @var PaymentMethodInterface $paymentMethod */ - $paymentMethod = $payment->getMethod(); - - /** @var GatewayConfigInterface $gatewayConfig */ - $gatewayConfig = $paymentMethod->getGatewayConfig(); - - if (isset($gatewayConfig->getConfig()['use_authorize']) && true === (bool) $gatewayConfig->getConfig()['use_authorize']) { - $token = $this->getTokenFactory()->createAuthorizeToken( - $gatewayConfig->getGatewayName(), - $payment, - $redirectOptions['route'] - ?? null, - $redirectOptions['parameters'] - ?? [], - ); - } else { - $token = $this->getTokenFactory()->createCaptureToken( - $gatewayConfig->getGatewayName(), - $payment, - $redirectOptions['route'] - ?? null, - $redirectOptions['parameters'] - ?? [], - ); - } - - return $token; - } -} diff --git a/src/Sylius/Bundle/PayumBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Configuration.php index d64c8eff84..78fedcb09c 100644 --- a/src/Sylius/Bundle/PayumBundle/DependencyInjection/Configuration.php +++ b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Configuration.php @@ -13,9 +13,6 @@ declare(strict_types=1); namespace Sylius\Bundle\PayumBundle\DependencyInjection; -use Sylius\Bundle\PayumBundle\Form\Type\GatewayConfigType; -use Sylius\Bundle\PayumBundle\Model\GatewayConfig; -use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; use Sylius\Bundle\PayumBundle\Model\PaymentSecurityToken; use Sylius\Bundle\PayumBundle\Model\PaymentSecurityTokenInterface; use Sylius\Bundle\ResourceBundle\Controller\ResourceController; @@ -36,15 +33,6 @@ final class Configuration implements ConfigurationInterface $rootNode ->addDefaultsIfNotSet() ->children() - ->arrayNode('gateway_config') - ->addDefaultsIfNotSet() - ->children() - ->arrayNode('validation_groups') - ->useAttributeAsKey('name') - ->variablePrototype()->end() - ->end() - ->end() - ->end() ->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end() ->arrayNode('template') ->addDefaultsIfNotSet() @@ -85,24 +73,6 @@ final class Configuration implements ConfigurationInterface ->end() ->end() ->end() - ->arrayNode('gateway_config') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('options') - ->setDeprecated('sylius/payum-bundle', '1.13', 'The "%node%" node at "%path%" is deprecated and will be removed in 2.0.') - ->end() - ->arrayNode('classes') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('model')->defaultValue(GatewayConfig::class)->cannotBeEmpty()->end() - ->scalarNode('interface')->defaultValue(GatewayConfigInterface::class)->cannotBeEmpty()->end() - ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() - ->scalarNode('factory')->defaultValue(Factory::class)->end() - ->scalarNode('form')->defaultValue(GatewayConfigType::class)->cannotBeEmpty()->end() - ->end() - ->end() - ->end() - ->end() ->end() ->end() ->end() diff --git a/src/Sylius/Bundle/PayumBundle/DependencyInjection/SyliusPayumExtension.php b/src/Sylius/Bundle/PayumBundle/DependencyInjection/SyliusPayumExtension.php index 760306e14a..9c24202d60 100644 --- a/src/Sylius/Bundle/PayumBundle/DependencyInjection/SyliusPayumExtension.php +++ b/src/Sylius/Bundle/PayumBundle/DependencyInjection/SyliusPayumExtension.php @@ -13,10 +13,8 @@ declare(strict_types=1); namespace Sylius\Bundle\PayumBundle\DependencyInjection; -use Sylius\Bundle\PayumBundle\Attribute\AsGatewayConfigurationType; use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension; use Symfony\Component\Config\FileLocator; -use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -31,14 +29,21 @@ final class SyliusPayumExtension extends AbstractResourceExtension implements Pr $loader->load('services.xml'); + $bundles = $container->getParameter('kernel.bundles'); + if (array_key_exists('SyliusShopBundle', $bundles)) { + $loader->load('services/integrations/sylius_shop.xml'); + } + $container->setParameter('payum.template.layout', $config['template']['layout']); $container->setParameter('payum.template.obtain_credit_card', $config['template']['obtain_credit_card']); - $container->setParameter('sylius.payum.gateway_config.validation_groups', $config['gateway_config']['validation_groups']); - - $this->registerAutoconfiguration($container); } public function prepend(ContainerBuilder $container): void + { + $this->prependSyliusPayment($container); + } + + private function prependSyliusPayment(ContainerBuilder $container): void { if (!$container->hasExtension('sylius_payment')) { return; @@ -59,18 +64,4 @@ final class SyliusPayumExtension extends AbstractResourceExtension implements Pr $container->prependExtensionConfig('sylius_payment', ['gateways' => $gateways]); } - - private function registerAutoconfiguration(ContainerBuilder $container): void - { - $container->registerAttributeForAutoconfiguration( - AsGatewayConfigurationType::class, - static function (ChildDefinition $definition, AsGatewayConfigurationType $attribute): void { - $definition->addTag(AsGatewayConfigurationType::SERVICE_TAG, [ - 'type' => $attribute->getType(), - 'label' => $attribute->getLabel(), - 'priority' => $attribute->getPriority(), - ]); - }, - ); - } } diff --git a/src/Sylius/Bundle/PayumBundle/Exception/NonExistingPayumTokenException.php b/src/Sylius/Bundle/PayumBundle/Exception/NonExistingPayumTokenException.php new file mode 100644 index 0000000000..7b9f9bcf63 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Exception/NonExistingPayumTokenException.php @@ -0,0 +1,26 @@ + $decryptedConfig */ + protected array $decryptedConfig; - public function getId(): int + public function __construct() { - return $this->id; + parent::__construct(); + + $this->decryptedConfig = []; + } + + public function getConfig(): array + { + if (isset($this->config['encrypted'])) { + return $this->decryptedConfig; + } + + return $this->config; + } + + public function setConfig(array $config): void + { + $this->config = $config; + $this->decryptedConfig = $config; + } + + /** This method is based on Payum\Core\Model\GatewayConfig::decrypt */ + public function decrypt(CypherInterface $cypher): void + { + if (empty($this->config['encrypted'])) { + return; + } + + foreach ($this->config as $name => $value) { + if ('encrypted' == $name || is_bool($value)) { + $this->decryptedConfig[$name] = $value; + + continue; + } + + $this->decryptedConfig[$name] = $cypher->decrypt($value); + } + } + + /** This method is based on Payum\Core\Model\GatewayConfig::encrypt */ + public function encrypt(CypherInterface $cypher): void + { + $this->decryptedConfig['encrypted'] = true; + + foreach ($this->decryptedConfig as $name => $value) { + if ('encrypted' == $name || is_bool($value)) { + $this->config[$name] = $value; + + continue; + } + + $this->config[$name] = $cypher->encrypt($value); + } } } diff --git a/src/Sylius/Bundle/PayumBundle/Model/GatewayConfigInterface.php b/src/Sylius/Bundle/PayumBundle/Model/GatewayConfigInterface.php index ca7815cd98..9e779d9c04 100644 --- a/src/Sylius/Bundle/PayumBundle/Model/GatewayConfigInterface.php +++ b/src/Sylius/Bundle/PayumBundle/Model/GatewayConfigInterface.php @@ -13,9 +13,10 @@ declare(strict_types=1); namespace Sylius\Bundle\PayumBundle\Model; -use Payum\Core\Model\GatewayConfigInterface as BaseGatewayConfigInterface; -use Sylius\Resource\Model\ResourceInterface; +use Payum\Core\Model\GatewayConfigInterface as PayumGatewayConfigInterface; +use Payum\Core\Security\CryptedInterface; +use Sylius\Component\Payment\Model\GatewayConfigInterface as BaseGatewayConfigInterface; -interface GatewayConfigInterface extends BaseGatewayConfigInterface, ResourceInterface +interface GatewayConfigInterface extends BaseGatewayConfigInterface, PayumGatewayConfigInterface, CryptedInterface { } diff --git a/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumAfterPayResponseProvider.php b/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumAfterPayResponseProvider.php new file mode 100644 index 0000000000..27b77d1ae2 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumAfterPayResponseProvider.php @@ -0,0 +1,74 @@ +getHttpRequestVerifier()->verify($requestConfiguration->getRequest()); + + /** @var GetStatusInterface&Generic $status */ + $status = $this->getStatusRequestFactory->createNewWithModel($token); + $this->payum->getGateway($token->getGatewayName())->execute($status); + + $resolveNextRoute = $this->resolveNextRouteRequestFactory->createNewWithModel($status->getFirstModel()); + $this->payum->getGateway($token->getGatewayName())->execute($resolveNextRoute); + + $this->paymentStatusFlashHandler->handle($requestConfiguration, (string) $status->getValue()); + + $url = $this->router->generate( + $resolveNextRoute->getRouteName(), + $resolveNextRoute->getRouteParameters() + ); + + $this->getHttpRequestVerifier()->invalidate($token); + + return new RedirectResponse($url); + } + public function supports(RequestConfiguration $requestConfiguration): bool + { + $request = $requestConfiguration->getRequest(); + $hash = $request->attributes->get('payum_token', $request->get('payum_token', false)); + + return false !== $hash; + } + + private function getHttpRequestVerifier(): HttpRequestVerifierInterface + { + return $this->payum->getHttpRequestVerifier(); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumPayResponseProvider.php b/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumPayResponseProvider.php new file mode 100644 index 0000000000..962fcead21 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/OrderPay/Provider/PayumPayResponseProvider.php @@ -0,0 +1,105 @@ +paymentToPayResolver->getLastPayment($order); + Assert::notNull($payment, sprintf('Order (id %s) must have last payment in state "new".', $order->getId())); + + $redirectOptions = $requestConfiguration->getParameters()->get('redirect'); + if (is_string($redirectOptions)) { + $redirectOptions = [ + 'route' => $redirectOptions, + ]; + } + $token = $this->provideTokenBasedOnPayment($payment, $redirectOptions); + + return new RedirectResponse($token->getTargetUrl()); + } + public function supports( + RequestConfiguration $requestConfiguration, + OrderInterface $order + ): bool { + + $payment = $this->paymentToPayResolver->getLastPayment($order); + if (null === $payment) { + return false; + } + + $gatewayConfig = $this->getGatewayConfigFromPayment($payment); + if (null === $gatewayConfig) { + return false; + } + + $gatewayName = $gatewayConfig->getGatewayName() ?? ''; + + return isset($this->payum->getGateways()[$gatewayName]); + } + + /** + * @param array{route: ?string, parameters: ?string[]} $redirectOptions + */ + private function provideTokenBasedOnPayment(PaymentInterface $payment, array $redirectOptions): TokenInterface + { + $gatewayConfig = $this->getGatewayConfigFromPayment($payment); + Assert::notNull($gatewayConfig, 'An existing gateway config must exist.'); + + $config = $gatewayConfig->getConfig(); + $tokenFactory = $this->payum->getTokenFactory(); + + if (isset($config['use_authorize']) && true === (bool) $config['use_authorize']) { + return $tokenFactory->createAuthorizeToken( + $gatewayConfig->getGatewayName(), + $payment, + $redirectOptions['route'] ?? null, + $redirectOptions['parameters'] ?? [], + ); + } + + return $tokenFactory->createCaptureToken( + $gatewayConfig->getGatewayName(), + $payment, + $redirectOptions['route'] ?? null, + $redirectOptions['parameters'] ?? [], + ); + } + + private function getGatewayConfigFromPayment(PaymentInterface $payment): ?GatewayConfigInterface + { + return $payment->getMethod()?->getGatewayConfig(); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusGetHttpRequestAction.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusGetHttpRequestAction.php new file mode 100644 index 0000000000..d64a7a1d1a --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusGetHttpRequestAction.php @@ -0,0 +1,74 @@ +updateRequest($request); + } + + private function updateRequest(GetHttpRequest $request): void + { + $paymentRequest = $this->payumApiContext->getPaymentRequest(); + + /** @var array{ + * 'http_request'?: array{ + * 'query'?: array, + * 'request'?: array, + * 'method'?: string, + * 'uri'?: string, + * 'client_ip'?: string, + * 'user_agent'?: string, + * 'content'?: string, + * 'headers'?: array, + * }, + * } $payload + */ + $payload = $paymentRequest->getPayload(); + $httpRequest = $payload['http_request'] ?? []; + + $request->query = $httpRequest['query'] ?? []; + $request->request = $httpRequest['request'] ?? []; + $request->method = $httpRequest['method'] ?? 'POST'; + $request->uri = $httpRequest['uri'] ?? ''; + $request->clientIp = $httpRequest['client_ip'] ?? ''; + $request->userAgent = $httpRequest['user_agent'] ?? ''; + $request->content = $httpRequest['content'] ?? ''; + + // Next release of Payum will have this field + if (property_exists($request, 'headers')) { + $request->headers = $httpRequest['headers'] ?? []; + } + } + + public function supports($request): bool + { + return $this->payumApiContext->isEnabled() && $request instanceof GetHttpRequest; + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusRenderTemplateAction.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusRenderTemplateAction.php new file mode 100644 index 0000000000..bd3a21346a --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Action/SyliusRenderTemplateAction.php @@ -0,0 +1,45 @@ +payumApiContext->getPaymentRequest(); + + $details = $paymentRequest->getResponseData(); + $paymentRequest->setResponseData(array_merge($details, $request->getParameters())); + + $request->setResult(''); + } + + public function supports($request): bool + { + return $this->payumApiContext->isEnabled() && $request instanceof RenderTemplate; + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Command/AuthorizePaymentRequest.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Command/AuthorizePaymentRequest.php new file mode 100644 index 0000000000..f1734dfce2 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Command/AuthorizePaymentRequest.php @@ -0,0 +1,26 @@ +paymentRequestProvider->provide($command); + $this->doctrineProxyObjectResolver->resolve($paymentRequest); + + $payment = $paymentRequest->getPayment(); + $request = $this->factory->createNewWithModel($payment); + + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = $paymentRequest->getMethod(); + + /** @var GatewayConfigInterface $gatewayConfig */ + $gatewayConfig = $paymentMethod->getGatewayConfig(); + + $this->requestProcessor->process($paymentRequest, $request, $gatewayConfig->getGatewayName()); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandHandler/TokenPaymentRequestHandler.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandHandler/TokenPaymentRequestHandler.php new file mode 100644 index 0000000000..591e112e32 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandHandler/TokenPaymentRequestHandler.php @@ -0,0 +1,55 @@ +paymentRequestProvider->provide($command); + $this->doctrineProxyObjectResolver->resolve($paymentRequest); + + $token = $this->payumTokenFactory->createNew($paymentRequest); + $request = $this->payumRequestFactory->createNewWithToken($token); + + $token = $request->getToken(); + if (null === $token) { + throw new NonExistingPayumTokenException(); + } + + $this->requestProcessor->process($paymentRequest, $request, $token->getGatewayName()); + $this->afterTokenRequestProcessor->process($paymentRequest, $token); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/AuthorizeCommandProvider.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/AuthorizeCommandProvider.php new file mode 100644 index 0000000000..b4d476005a --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/AuthorizeCommandProvider.php @@ -0,0 +1,31 @@ +getAction() === PaymentRequestInterface::ACTION_AUTHORIZE; + } + + public function provide(PaymentRequestInterface $paymentRequest): object + { + return new AuthorizePaymentRequest($paymentRequest->getHash()?->toBinary()); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/CaptureCommandProvider.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/CaptureCommandProvider.php new file mode 100644 index 0000000000..f31e79e498 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/CaptureCommandProvider.php @@ -0,0 +1,31 @@ +getAction() === PaymentRequestInterface::ACTION_CAPTURE; + } + + public function provide(PaymentRequestInterface $paymentRequest): object + { + return new CapturePaymentRequest($paymentRequest->getHash()?->toBinary()); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/StatusCommandProvider.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/StatusCommandProvider.php new file mode 100644 index 0000000000..75d00013d4 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/CommandProvider/StatusCommandProvider.php @@ -0,0 +1,31 @@ +getAction() === PaymentRequestInterface::ACTION_STATUS; + } + + public function provide(PaymentRequestInterface $paymentRequest): object + { + return new StatusPaymentRequest($paymentRequest->getHash()?->toBinary()); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContext.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContext.php new file mode 100644 index 0000000000..4f903b787c --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContext.php @@ -0,0 +1,41 @@ +paymentRequest; + } + + public function enable(PaymentRequestInterface $paymentRequest): void + { + $this->paymentRequest = $paymentRequest; + } + + public function getPaymentRequest(): PaymentRequestInterface + { + return $this->paymentRequest; + } + + public function disable(): void + { + $this->paymentRequest = null; + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContextInterface.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContextInterface.php new file mode 100644 index 0000000000..1beca7f1d0 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Context/PaymentRequestContextInterface.php @@ -0,0 +1,27 @@ +getPayment(); + + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = $paymentRequest->getMethod(); + + $gatewayConfig = $paymentMethod->getGatewayConfig(); + Assert::notNull($gatewayConfig, sprintf('Gateway config of the payment method (code %s) cannot be null.', $paymentMethod->getCode())); + $gatewayName = $gatewayConfig->getGatewayName(); + + /** @var array{ + * 'target_path'?: string, + * 'target_path_parameters'?: array, + * 'after_path'?: string, + * 'after_path_parameters'?: array, + * }|null $payload + */ + $payload = $paymentRequest->getPayload(); + if (null === $payload) { + throw new InvalidPaymentRequestPayloadException('Payload of the payment request cannot be null.'); + } + + $targetPath = $payload['target_path'] ?? null; + if (null === $targetPath) { + throw new InvalidPaymentRequestPayloadException('The target path of the payment request cannot be null.'); + } + + $targetPathParameters = $payload['target_path_parameters'] ?? []; + + $afterPath = $payload['after_path'] ?? null; + if (null === $afterPath) { + throw new InvalidPaymentRequestPayloadException('The after path of the payment request cannot be null.'); + } + + $afterPathParameters = $payload['after_path_parameters'] ?? []; + + return $this->payum->getTokenFactory()->createToken( + $gatewayName, + $payment, + $targetPath, + $targetPathParameters, + $afterPath, + $afterPathParameters, + ); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Factory/PayumTokenFactoryInterface.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Factory/PayumTokenFactoryInterface.php new file mode 100644 index 0000000000..16a21c8761 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Factory/PayumTokenFactoryInterface.php @@ -0,0 +1,22 @@ + $paymentRequestFactory + * @param PaymentRequestRepositoryInterface $paymentRequestRepository + */ + public function __construct( + private PaymentRequestFactoryInterface $paymentRequestFactory, + private PaymentRequestRepositoryInterface $paymentRequestRepository, + private PaymentRequestAnnouncerInterface $paymentRequestCommandDispatcher, + ) { + } + + public function process( + PaymentRequestInterface $paymentRequest, + TokenInterface $token, + ): void { + if (PaymentRequestInterface::STATE_COMPLETED !== $paymentRequest->getState()) { + return; + } + + $details = $paymentRequest->getResponseData(); + $details['after_url'] = $token->getAfterUrl(); + $paymentRequest->setResponseData($details); + + $newPaymentRequest = $this->paymentRequestFactory->createFromPaymentRequest($paymentRequest); + $newPaymentRequest->setAction(PaymentRequestInterface::ACTION_STATUS); + + $this->paymentRequestRepository->add($newPaymentRequest); + + $this->paymentRequestCommandDispatcher->dispatchPaymentRequestCommand($newPaymentRequest); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/AfterTokenRequestProcessorInterface.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/AfterTokenRequestProcessorInterface.php new file mode 100644 index 0000000000..a16770dfcc --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/AfterTokenRequestProcessorInterface.php @@ -0,0 +1,25 @@ +payum->getGateway($gatewayName); + + $this->payumApiContext->enable($paymentRequest); + $reply = $gateway->execute($request, true); + $this->payumApiContext->disable(); + + if (null !== $reply) { + return; + } + + $this->stateMachine->apply( + $paymentRequest, + PaymentRequestTransitions::GRAPH, + PaymentRequestTransitions::TRANSITION_COMPLETE + ); + + if (false === $request instanceof TokenAggregateInterface) { + return; + } + + $token = $request->getToken(); + if (null === $token) { + return; + } + + $this->payum->getHttpRequestVerifier()->invalidate($token); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/RequestProcessorInterface.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/RequestProcessorInterface.php new file mode 100644 index 0000000000..9791a51e15 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Processor/RequestProcessorInterface.php @@ -0,0 +1,25 @@ +getPayment(); + if ($payment instanceof Proxy) { + $this->entityManager->detach($payment); + $payment = $this->entityManager->find(PaymentInterface::class, $payment->getId()); + $paymentRequest->setPayment($payment); + } + } +} diff --git a/src/Sylius/Bundle/PayumBundle/PaymentRequest/Resolver/DoctrineProxyObjectResolverInterface.php b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Resolver/DoctrineProxyObjectResolverInterface.php new file mode 100644 index 0000000000..b62c66de5d --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/PaymentRequest/Resolver/DoctrineProxyObjectResolverInterface.php @@ -0,0 +1,21 @@ + - - - - - +> + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/routing/payum.yml b/src/Sylius/Bundle/PayumBundle/Resources/config/routing/integrations/sylius_shop.yaml similarity index 100% rename from src/Sylius/Bundle/ShopBundle/Resources/config/routing/payum.yml rename to src/Sylius/Bundle/PayumBundle/Resources/config/routing/integrations/sylius_shop.yaml diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services.xml index 9061ed560f..f4710f4314 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services.xml @@ -11,13 +11,14 @@ --> - - + + @@ -26,6 +27,5 @@ - diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/controller.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/controller.xml deleted file mode 100644 index b2cb1aa1b2..0000000000 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services/controller.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - sylius.order - - - - - - - - - diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/factory.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/factory.xml index f194ce1a53..595cb5ecdb 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services/factory.xml +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/factory.xml @@ -12,5 +12,8 @@ + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/form.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/form.xml index 4c15d9e786..936bfbca60 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services/form.xml +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/form.xml @@ -12,21 +12,9 @@ --> - - - sylius - - - - %sylius.model.gateway_config.class% - %sylius.form.type.gateway_config.validation_groups% - - - - diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop.xml new file mode 100644 index 0000000000..cc6edfc2af --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop/order_pay/providers.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop/order_pay/providers.xml new file mode 100644 index 0000000000..4d30b07522 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/integrations/sylius_shop/order_pay/providers.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/order_pay.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/order_pay.xml new file mode 100644 index 0000000000..69407d9b9e --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/order_pay.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/action.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/action.xml new file mode 100644 index 0000000000..82e1bf0b73 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/action.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_handler.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_handler.xml new file mode 100644 index 0000000000..ea64a17fcb --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_handler.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_provider.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_provider.xml new file mode 100644 index 0000000000..e1f96cc36e --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/command_provider.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/context.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/context.xml new file mode 100644 index 0000000000..4b8b19bfee --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/factory.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/factory.xml new file mode 100644 index 0000000000..9bc0959a75 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/factory.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/processor.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/processor.xml new file mode 100644 index 0000000000..71ac9d782d --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/processor.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/resolver.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/resolver.xml new file mode 100644 index 0000000000..da23c770d7 --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/payment_request/resolver.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/PayumBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/PayumBundle/Resources/translations/messages.en.yml index bc2e349b1d..d5eb234be3 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/PayumBundle/Resources/translations/messages.en.yml @@ -2,5 +2,8 @@ sylius: form: gateway_config: type: Type + payum_gateway: + cash_on_delivery: Cash on delivery + offline: Offline payum_gateway_factory: offline: Offline diff --git a/src/Sylius/Bundle/PayumBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/PayumBundle/Resources/translations/validators.en.yml deleted file mode 100644 index f0eaf279a6..0000000000 --- a/src/Sylius/Bundle/PayumBundle/Resources/translations/validators.en.yml +++ /dev/null @@ -1,7 +0,0 @@ -sylius: - gateway_config: - invalid_gateway_factory: Invalid gateway factory. Available factories are {{ available_factories }}. - gateway_name: - not_blank: Please enter gateway name. - factory_name: - not_blank: Please enter gateway factory name. diff --git a/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php b/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php index 5ad125e91b..acf57702c8 100644 --- a/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php +++ b/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php @@ -14,7 +14,6 @@ declare(strict_types=1); namespace Sylius\Bundle\PayumBundle; use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\InjectContainerIntoControllersPass; -use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\RegisterGatewayConfigTypePass; use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\UseTweakedDoctrineStoragePass; use Sylius\Bundle\ResourceBundle\AbstractResourceBundle; use Sylius\Bundle\ResourceBundle\SyliusResourceBundle; @@ -35,7 +34,6 @@ final class SyliusPayumBundle extends AbstractResourceBundle parent::build($container); $container->addCompilerPass(new InjectContainerIntoControllersPass()); - $container->addCompilerPass(new RegisterGatewayConfigTypePass()); $container->addCompilerPass(new UseTweakedDoctrineStoragePass()); } } diff --git a/src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/SyliusPayumExtensionTest.php b/src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/SyliusPayumExtensionTest.php deleted file mode 100644 index fdbd7e2acc..0000000000 --- a/src/Sylius/Bundle/PayumBundle/Tests/DependencyInjection/SyliusPayumExtensionTest.php +++ /dev/null @@ -1,63 +0,0 @@ -container->setDefinition( - 'acme.gateway_configuration_type_with_attribute', - (new Definition()) - ->setClass(GatewayConfigurationTypeStub::class) - ->setAutoconfigured(true), - ); - - $this->load(); - $this->compile(); - - $this->assertContainerBuilderHasServiceDefinitionWithTag( - 'acme.gateway_configuration_type_with_attribute', - AsGatewayConfigurationType::SERVICE_TAG, - ['type' => 'test', 'label' => 'Test', 'priority' => 15], - ); - } - - /** @test */ - public function it_loads_gateway_config_validation_groups_parameter_value_properly(): void - { - $this->load([ - 'gateway_config' => [ - 'validation_groups' => [ - 'paypal' => ['sylius', 'paypal'], - 'offline' => ['sylius'], - ], - ], - ]); - - $this->assertContainerBuilderHasParameter('sylius.payum.gateway_config.validation_groups', ['paypal' => ['sylius', 'paypal'], 'offline' => ['sylius']]); - } - - protected function getContainerExtensions(): array - { - return [new SyliusPayumExtension()]; - } -} diff --git a/src/Sylius/Bundle/PayumBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php b/src/Sylius/Bundle/PayumBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php deleted file mode 100644 index da2445f711..0000000000 --- a/src/Sylius/Bundle/PayumBundle/Validator/GroupsGenerator/GatewayConfigGroupsGenerator.php +++ /dev/null @@ -1,47 +0,0 @@ -> $validationGroups */ - public function __construct(private array $validationGroups) - { - } - - /** - * @param FormInterface|PaymentMethodInterface $object - * - * @return array - */ - public function __invoke($object): array - { - if ($object instanceof FormInterface) { - $object = $object->getData(); - } - - Assert::isInstanceOf($object, PaymentMethodInterface::class); - - if ($object->getGatewayConfig()?->getFactoryName() === null) { - return ['sylius']; - } - - return $this->validationGroups[$object->getGatewayConfig()->getFactoryName()] ?? ['sylius']; - } -} diff --git a/src/Sylius/Bundle/PayumBundle/composer.json b/src/Sylius/Bundle/PayumBundle/composer.json index 1e3d828558..aa12a8bd2d 100644 --- a/src/Sylius/Bundle/PayumBundle/composer.json +++ b/src/Sylius/Bundle/PayumBundle/composer.json @@ -31,7 +31,7 @@ "php": "^8.2", "payum/offline": "^1.7.3", "payum/payum-bundle": "^2.5", - "sylius/core": "^2.0", + "sylius/core-bundle": "^2.0", "sylius/currency": "^2.0", "sylius/order-bundle": "^2.0", "sylius/payment-bundle": "^2.0", diff --git a/src/Sylius/Bundle/PayumBundle/phpunit.xml.dist b/src/Sylius/Bundle/PayumBundle/phpunit.xml.dist index 20f2829b34..8efd23600b 100644 --- a/src/Sylius/Bundle/PayumBundle/phpunit.xml.dist +++ b/src/Sylius/Bundle/PayumBundle/phpunit.xml.dist @@ -7,10 +7,4 @@ - - - - ./Tests/ - - diff --git a/src/Sylius/Bundle/PayumBundle/spec/Model/GatewayConfigSpec.php b/src/Sylius/Bundle/PayumBundle/spec/Model/GatewayConfigSpec.php new file mode 100644 index 0000000000..d667f010eb --- /dev/null +++ b/src/Sylius/Bundle/PayumBundle/spec/Model/GatewayConfigSpec.php @@ -0,0 +1,25 @@ +shouldImplement(GatewayConfigInterface::class); + } +} diff --git a/src/Sylius/Bundle/PayumBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php b/src/Sylius/Bundle/PayumBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php deleted file mode 100644 index 84adf2c82f..0000000000 --- a/src/Sylius/Bundle/PayumBundle/spec/Validator/GroupsGenerator/GatewayConfigGroupsGeneratorSpec.php +++ /dev/null @@ -1,68 +0,0 @@ -beConstructedWith([ - 'paypal' => ['paypal', 'sylius'], - 'stripe_checkout' => ['stripe_checkout', 'sylius'], - ]); - } - - function it_throws_error_if_invalid_object_is_passed(): void - { - $this - ->shouldThrow(\InvalidArgumentException::class) - ->during('__invoke', [new \stdClass()]) - ; - } - - function it_returns_gateway_config_validation_groups( - GatewayConfigInterface $gatewayConfig, - PaymentMethodInterface $paymentMethod, - ): void { - $paymentMethod->getGatewayConfig()->willReturn($gatewayConfig); - $gatewayConfig->getFactoryName()->willReturn('paypal'); - - $this($paymentMethod)->shouldReturn(['paypal', 'sylius']); - } - - function it_returns_default_validation_groups_if_gateway_config_is_null( - PaymentMethodInterface $paymentMethod, - ): void { - $paymentMethod->getGatewayConfig()->willReturn(null); - - $this($paymentMethod)->shouldReturn(['sylius']); - } - - function it_returns_gateway_config_validation_groups_if_it_is_payment_method_form( - FormInterface $form, - GatewayConfigInterface $gatewayConfig, - PaymentMethodInterface $paymentMethod, - ): void { - $form->getData()->willReturn($paymentMethod); - $paymentMethod->getGatewayConfig()->willReturn($gatewayConfig); - $gatewayConfig->getFactoryName()->willReturn('paypal'); - - $this($form)->shouldReturn(['paypal', 'sylius']); - } -} diff --git a/src/Sylius/Bundle/ShopBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/ShopBundle/DependencyInjection/Configuration.php index 6348e5fd4d..4ba46d144a 100644 --- a/src/Sylius/Bundle/ShopBundle/DependencyInjection/Configuration.php +++ b/src/Sylius/Bundle/ShopBundle/DependencyInjection/Configuration.php @@ -66,6 +66,39 @@ final class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('order_pay') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('after_pay_route') + ->defaultValue('sylius_shop_order_after_pay') + ->end() + ->arrayNode('after_pay_route_parameters') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('hash') + ->defaultValue('paymentRequest.getHash()') + ->end() + ->end() + ->end() + ->scalarNode('final_route') + ->defaultValue('sylius_shop_order_thank_you') + ->end() + ->arrayNode('final_route_parameters') + ->addDefaultsIfNotSet() + ->end() + ->scalarNode('retry_route') + ->defaultValue('sylius_shop_order_show') + ->end() + ->arrayNode('retry_route_parameters') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('tokenValue') + ->defaultValue('order.getTokenValue()') + ->end() + ->end() + ->end() + ->end() + ->end() ->end() ; diff --git a/src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php b/src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php index de26b8b945..ab714e2d85 100644 --- a/src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php +++ b/src/Sylius/Bundle/ShopBundle/DependencyInjection/SyliusShopExtension.php @@ -33,6 +33,8 @@ final class SyliusShopExtension extends Extension implements PrependExtensionInt $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $this->configureOrderPay($config['order_pay'], $container); + $loader->load('services.xml'); $loader->load(sprintf('services/integrations/locale/%s.xml', $config['locale_switcher'])); $container->setAlias(LocaleSwitcherInterface::class, 'sylius.shop.locale_switcher'); @@ -49,6 +51,7 @@ final class SyliusShopExtension extends Extension implements PrependExtensionInt 'sylius_shop.product_grid.include_all_descendants', $config['product_grid']['include_all_descendants'], ); + $this->configureCheckoutResolverIfNeeded($config['checkout_resolver'], $container); } @@ -121,4 +124,14 @@ final class SyliusShopExtension extends Extension implements PrependExtensionInt $container->prependExtensionConfig('sylius_theme', ['context' => 'sylius.theme.context.channel_based']); } + + private function configureOrderPay(array $config, ContainerBuilder $container): void + { + $container->setParameter('sylius_shop.order_pay.after_pay_route', $config['after_pay_route']); + $container->setParameter('sylius_shop.order_pay.after_pay_route_parameters', $config['after_pay_route_parameters']); + $container->setParameter('sylius_shop.order_pay.final_route', $config['final_route']); + $container->setParameter('sylius_shop.order_pay.final_route_parameters', $config['final_route_parameters']); + $container->setParameter('sylius_shop.order_pay.retry_route', $config['retry_route']); + $container->setParameter('sylius_shop.order_pay.retry_route_parameters', $config['retry_route_parameters']); + } } diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/routing/order.yml b/src/Sylius/Bundle/ShopBundle/Resources/config/routing/order.yml index 435778285b..bcc68d44fd 100644 --- a/src/Sylius/Bundle/ShopBundle/Resources/config/routing/order.yml +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/routing/order.yml @@ -13,16 +13,16 @@ sylius_shop_order_pay: path: /{tokenValue}/pay methods: [GET] defaults: - _controller: sylius.controller.payum::prepareCaptureAction + _controller: sylius.shop.controller.order_pay::payAction _sylius: redirect: route: sylius_shop_order_after_pay sylius_shop_order_after_pay: - path: /after-pay + path: /after-pay/{hash?} methods: [GET, POST] defaults: - _controller: sylius.controller.payum::afterCaptureAction + _controller: sylius.shop.controller.order_pay::afterPayAction sylius_shop_order_show: path: /{tokenValue} diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services.xml index 48544d7b76..9ded6bf8f1 100644 --- a/src/Sylius/Bundle/ShopBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services.xml @@ -14,6 +14,7 @@ + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/controllers.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/controllers.xml new file mode 100644 index 0000000000..e65acc4f4f --- /dev/null +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/controllers.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/handlers.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/handlers.xml new file mode 100644 index 0000000000..c040475a0e --- /dev/null +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/handlers.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/offline/providers.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/offline/providers.xml new file mode 100644 index 0000000000..5bef26803d --- /dev/null +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/offline/providers.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/providers.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/providers.xml new file mode 100644 index 0000000000..52dce768e5 --- /dev/null +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/providers.xml @@ -0,0 +1,56 @@ + + + + + + + + + + %sylius_shop.order_pay.after_pay_route% + %sylius_shop.order_pay.after_pay_route_parameters% + + + + %sylius_shop.order_pay.final_route% + %sylius_shop.order_pay.final_route_parameters% + %sylius_shop.order_pay.retry_route% + %sylius_shop.order_pay.retry_route_parameters% + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/resolvers.xml b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/resolvers.xml new file mode 100644 index 0000000000..cf3f266c7d --- /dev/null +++ b/src/Sylius/Bundle/ShopBundle/Resources/config/services/order_pay/resolvers.xml @@ -0,0 +1,24 @@ + + + + + + + + Sylius\Component\Payment\Model\PaymentInterface::STATE_NEW + + + diff --git a/src/Sylius/Bundle/ShopBundle/composer.json b/src/Sylius/Bundle/ShopBundle/composer.json index 80986ddb86..c115217958 100644 --- a/src/Sylius/Bundle/ShopBundle/composer.json +++ b/src/Sylius/Bundle/ShopBundle/composer.json @@ -28,6 +28,7 @@ "require": { "php": "^8.2", "sylius/core-bundle": "^2.0", + "sylius/payum-bundle": "^2.0", "sylius/twig-hooks": "~0.3.0", "sylius/ui-bundle": "^2.0", "sylius/theme-bundle": "^2.4", diff --git a/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml index 5a195f699b..c4e695c119 100644 --- a/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml @@ -75,6 +75,7 @@ sylius: assortment: 'Assortment' attributes: 'Attributes' author: 'Author' + authorize: 'Authorize' authorized: 'Authorized' authorized_payment: 'Authorized payment' availability: 'Availability' @@ -110,6 +111,7 @@ sylius: call_us: 'Call us!' cancel: 'Cancel' cancelled: 'Cancelled' + capture: 'Capture' cart: 'Cart' cart_locked: 'Cart locked' cart_summary: 'Cart summary' @@ -397,6 +399,7 @@ sylius: guest: 'Guest' guest_customer: 'Guest customer' have_an_account_already: 'Have an account already?' + hash: 'Hash' height: 'Height' hello: 'Hello' hello_name: 'Hello %name%' @@ -448,6 +451,7 @@ sylius: license: 'License' links: 'Links' list_coupons: 'List coupons' + list_payment_requests: 'List payment requests' list_variants: 'List variants' loading: 'Loading' locale: 'Locale' @@ -646,14 +650,18 @@ sylius: password: 'Password' pay: 'Pay' pay_again: 'Click pay to try again' + payload: 'Payload' payment: 'Payment' payment_details: 'Payment details' payment_method: 'Payment method' payment_methods: 'Payment methods' payment_mode: 'Payment mode' payment_ready_to_pay: 'You can now make your payment' + payment_request: 'Payment request' + payment_requests: 'Payment requests' payment_state: 'Payment state' payments: 'Payments' + payout: 'Payout' pending: 'Pending' per_customer_usage_limit: 'Per customer usage limit' percent: 'Percent' @@ -751,6 +759,7 @@ sylius: reset: 'Reset' reset_button: 'Reset' reset_password: 'Reset password' + response_data: 'Response data' restore: 'Restore' retail_price: 'Retail price' return: 'Return' @@ -852,6 +861,7 @@ sylius: summary_of_your_order: 'Summary of your order' support: 'Support' sylius_logo: 'Sylius' + sync: 'Sync' target_currency: 'Target currency' tax: 'Tax' tax_categories: 'Tax Categories' @@ -951,6 +961,7 @@ sylius: update_cart: 'Update cart' update_product_association_type: 'Update product association type' updated: 'Updated' + updated_at: 'Updated at' updating_date: 'Updating date' updating_tax_rate: 'Updating tax rate' usage: 'Usage' diff --git a/src/Sylius/Bundle/UserBundle/Resources/config/app/config.yml b/src/Sylius/Bundle/UserBundle/Resources/config/app/config.yml index cf9bbc2840..8f8ec5c600 100644 --- a/src/Sylius/Bundle/UserBundle/Resources/config/app/config.yml +++ b/src/Sylius/Bundle/UserBundle/Resources/config/app/config.yml @@ -15,6 +15,11 @@ when@test_cached: password_hashers: plaintext: plaintext +when@test_cached_payum: + security: + password_hashers: + plaintext: plaintext + jms_serializer: metadata: directories: diff --git a/src/Sylius/Component/Core/Factory/PaymentMethodFactory.php b/src/Sylius/Component/Core/Factory/PaymentMethodFactory.php index 65e724513c..f3f192387f 100644 --- a/src/Sylius/Component/Core/Factory/PaymentMethodFactory.php +++ b/src/Sylius/Component/Core/Factory/PaymentMethodFactory.php @@ -13,8 +13,8 @@ declare(strict_types=1); namespace Sylius\Component\Core\Factory; -use Payum\Core\Model\GatewayConfigInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Resource\Factory\FactoryInterface; /** diff --git a/src/Sylius/Component/Core/Model/PaymentMethod.php b/src/Sylius/Component/Core/Model/PaymentMethod.php index 206a5638f8..3a2f08f12d 100644 --- a/src/Sylius/Component/Core/Model/PaymentMethod.php +++ b/src/Sylius/Component/Core/Model/PaymentMethod.php @@ -15,7 +15,6 @@ namespace Sylius\Component\Core\Model; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Payum\Core\Model\GatewayConfigInterface; use Sylius\Component\Channel\Model\ChannelInterface as BaseChannelInterface; use Sylius\Component\Payment\Model\PaymentMethod as BasePaymentMethod; use Sylius\Component\Payment\Model\PaymentMethodTranslation; @@ -25,9 +24,6 @@ class PaymentMethod extends BasePaymentMethod implements PaymentMethodInterface /** @var Collection */ protected $channels; - /** @var GatewayConfigInterface|null */ - protected $gatewayConfig; - public function __construct() { parent::__construct(); @@ -60,16 +56,6 @@ class PaymentMethod extends BasePaymentMethod implements PaymentMethodInterface } } - public function setGatewayConfig(?GatewayConfigInterface $gateway): void - { - $this->gatewayConfig = $gateway; - } - - public function getGatewayConfig(): ?GatewayConfigInterface - { - return $this->gatewayConfig; - } - public static function getTranslationClass(): string { return PaymentMethodTranslation::class; diff --git a/src/Sylius/Component/Core/Model/PaymentMethodInterface.php b/src/Sylius/Component/Core/Model/PaymentMethodInterface.php index b5be073f72..035d4a29d4 100644 --- a/src/Sylius/Component/Core/Model/PaymentMethodInterface.php +++ b/src/Sylius/Component/Core/Model/PaymentMethodInterface.php @@ -13,13 +13,9 @@ declare(strict_types=1); namespace Sylius\Component\Core\Model; -use Payum\Core\Model\GatewayConfigInterface; use Sylius\Component\Channel\Model\ChannelsAwareInterface; use Sylius\Component\Payment\Model\PaymentMethodInterface as BasePaymentMethodInterface; interface PaymentMethodInterface extends BasePaymentMethodInterface, ChannelsAwareInterface { - public function setGatewayConfig(?GatewayConfigInterface $gateway): void; - - public function getGatewayConfig(): ?GatewayConfigInterface; } diff --git a/src/Sylius/Component/Core/composer.json b/src/Sylius/Component/Core/composer.json index 424d2692f0..981ff3ec04 100644 --- a/src/Sylius/Component/Core/composer.json +++ b/src/Sylius/Component/Core/composer.json @@ -31,7 +31,6 @@ "guzzlehttp/guzzle": "^6.5 || ^7.0", "knplabs/gaufrette": "^0.10 || ^0.11", "league/flysystem": "^3.3", - "payum/core": "^1.7.3", "psr/http-client": "^1.0", "psr/http-message": "^1.0", "sylius/addressing": "^2.0", diff --git a/src/Sylius/Component/Core/spec/Factory/PaymentMethodFactorySpec.php b/src/Sylius/Component/Core/spec/Factory/PaymentMethodFactorySpec.php index a0c2590f7d..02a7d9bd87 100644 --- a/src/Sylius/Component/Core/spec/Factory/PaymentMethodFactorySpec.php +++ b/src/Sylius/Component/Core/spec/Factory/PaymentMethodFactorySpec.php @@ -13,10 +13,10 @@ declare(strict_types=1); namespace spec\Sylius\Component\Core\Factory; -use Payum\Core\Model\GatewayConfigInterface; use PhpSpec\ObjectBehavior; use Sylius\Component\Core\Factory\PaymentMethodFactoryInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Resource\Factory\FactoryInterface; final class PaymentMethodFactorySpec extends ObjectBehavior diff --git a/src/Sylius/Component/Core/spec/Model/PaymentMethodSpec.php b/src/Sylius/Component/Core/spec/Model/PaymentMethodSpec.php index 3f3b170f03..c43b4621bc 100644 --- a/src/Sylius/Component/Core/spec/Model/PaymentMethodSpec.php +++ b/src/Sylius/Component/Core/spec/Model/PaymentMethodSpec.php @@ -13,7 +13,6 @@ declare(strict_types=1); namespace spec\Sylius\Component\Core\Model; -use Payum\Core\Model\GatewayConfigInterface; use PhpSpec\ObjectBehavior; use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -47,10 +46,4 @@ final class PaymentMethodSpec extends ObjectBehavior $this->removeChannel($channel); $this->hasChannel($channel)->shouldReturn(false); } - - function its_gateway_config_is_mutable(GatewayConfigInterface $gatewayConfig): void - { - $this->setGatewayConfig($gatewayConfig); - $this->getGatewayConfig()->shouldReturn($gatewayConfig); - } } diff --git a/src/Sylius/Component/Payment/Canceller/PaymentRequestCancellerInterface.php b/src/Sylius/Component/Payment/Canceller/PaymentRequestCancellerInterface.php new file mode 100644 index 0000000000..6f4c45c7d8 --- /dev/null +++ b/src/Sylius/Component/Payment/Canceller/PaymentRequestCancellerInterface.php @@ -0,0 +1,19 @@ + + */ +final class PaymentRequestFactory implements PaymentRequestFactoryInterface +{ + public function __construct(private string $className) + { + if (!is_a($className, PaymentRequestInterface::class, true)) { + throw new \DomainException(sprintf( + 'This factory requires %s or its descendant to be used as resource', + PaymentRequestInterface::class, + )); + } + } + + /** + * @throws UnsupportedMethodException + */ + public function createNew(): object + { + throw new UnsupportedMethodException('createNew'); + } + + public function create(PaymentInterface $payment, PaymentMethodInterface $paymentMethod): PaymentRequestInterface + { + return new $this->className($payment, $paymentMethod); + } + + public function createFromPaymentRequest(PaymentRequestInterface $paymentRequest): PaymentRequestInterface + { + return $this->create($paymentRequest->getPayment(), $paymentRequest->getMethod()); + } +} diff --git a/src/Sylius/Component/Payment/Factory/PaymentRequestFactoryInterface.php b/src/Sylius/Component/Payment/Factory/PaymentRequestFactoryInterface.php new file mode 100644 index 0000000000..a944431780 --- /dev/null +++ b/src/Sylius/Component/Payment/Factory/PaymentRequestFactoryInterface.php @@ -0,0 +1,31 @@ + + */ +interface PaymentRequestFactoryInterface extends FactoryInterface +{ + public function create(PaymentInterface $payment, PaymentMethodInterface $paymentMethod): PaymentRequestInterface; + + public function createFromPaymentRequest(PaymentRequestInterface $paymentRequest): PaymentRequestInterface; +} diff --git a/src/Sylius/Component/Payment/Model/GatewayConfig.php b/src/Sylius/Component/Payment/Model/GatewayConfig.php new file mode 100644 index 0000000000..040ddf3503 --- /dev/null +++ b/src/Sylius/Component/Payment/Model/GatewayConfig.php @@ -0,0 +1,66 @@ + $config */ + protected array $config; + + public function __construct() + { + $this->config = []; + } + + public function getId(): mixed + { + return $this->id; + } + + public function getGatewayName(): ?string + { + return $this->gatewayName; + } + + public function setGatewayName($gatewayName): void + { + $this->gatewayName = $gatewayName; + } + + public function getFactoryName(): ?string + { + return $this->factoryName ?? $this->config['factory'] ?? null; + } + + public function setFactoryName($factoryName): void + { + $this->factoryName = $factoryName; + } + + public function getConfig(): array + { + return $this->config; + } + + public function setConfig(array $config): void + { + $this->config = $config; + } +} diff --git a/src/Sylius/Component/Payment/Model/GatewayConfigInterface.php b/src/Sylius/Component/Payment/Model/GatewayConfigInterface.php new file mode 100644 index 0000000000..24cad88df8 --- /dev/null +++ b/src/Sylius/Component/Payment/Model/GatewayConfigInterface.php @@ -0,0 +1,35 @@ + */ + public function getConfig(): array; + + /** @param array $config */ + public function setConfig(array $config): void; +} diff --git a/src/Sylius/Component/Payment/Model/Payment.php b/src/Sylius/Component/Payment/Model/Payment.php index a5dc1a3064..45c84a74b6 100644 --- a/src/Sylius/Component/Payment/Model/Payment.php +++ b/src/Sylius/Component/Payment/Model/Payment.php @@ -13,6 +13,8 @@ declare(strict_types=1); namespace Sylius\Component\Payment\Model; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Sylius\Resource\Model\TimestampableTrait; class Payment implements PaymentInterface @@ -37,9 +39,13 @@ class Payment implements PaymentInterface /** @var mixed[] */ protected $details = []; + /** @var Collection */ + protected Collection $paymentRequests; + public function __construct() { $this->createdAt = new \DateTime(); + $this->paymentRequests = new ArrayCollection(); } public function getId() @@ -96,4 +102,9 @@ class Payment implements PaymentInterface { $this->details = $details; } + + public function getPaymentRequests(): Collection + { + return $this->paymentRequests; + } } diff --git a/src/Sylius/Component/Payment/Model/PaymentInterface.php b/src/Sylius/Component/Payment/Model/PaymentInterface.php index 086c42c49a..81ff6d07f4 100644 --- a/src/Sylius/Component/Payment/Model/PaymentInterface.php +++ b/src/Sylius/Component/Payment/Model/PaymentInterface.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Sylius\Component\Payment\Model; +use Doctrine\Common\Collections\Collection; use Sylius\Resource\Model\ResourceInterface; use Sylius\Resource\Model\TimestampableInterface; @@ -55,4 +56,9 @@ interface PaymentInterface extends TimestampableInterface, ResourceInterface public function getDetails(): array; public function setDetails(array $details): void; + + /** + * @return Collection + */ + public function getPaymentRequests(): Collection; } diff --git a/src/Sylius/Component/Payment/Model/PaymentMethod.php b/src/Sylius/Component/Payment/Model/PaymentMethod.php index b72e9b1b08..90cc943ed5 100644 --- a/src/Sylius/Component/Payment/Model/PaymentMethod.php +++ b/src/Sylius/Component/Payment/Model/PaymentMethod.php @@ -38,6 +38,8 @@ class PaymentMethod implements PaymentMethodInterface, \Stringable /** @var int|null */ protected $position; + protected ?GatewayConfigInterface $gatewayConfig = null; + public function __construct() { $this->initializeTranslationsCollection(); @@ -115,6 +117,16 @@ class PaymentMethod implements PaymentMethodInterface, \Stringable $this->position = $position; } + public function getGatewayConfig(): ?GatewayConfigInterface + { + return $this->gatewayConfig; + } + + public function setGatewayConfig(?GatewayConfigInterface $gatewayConfig): void + { + $this->gatewayConfig = $gatewayConfig; + } + /** * @return PaymentMethodTranslationInterface */ diff --git a/src/Sylius/Component/Payment/Model/PaymentMethodInterface.php b/src/Sylius/Component/Payment/Model/PaymentMethodInterface.php index 172d71a8cf..e8c17beb5c 100644 --- a/src/Sylius/Component/Payment/Model/PaymentMethodInterface.php +++ b/src/Sylius/Component/Payment/Model/PaymentMethodInterface.php @@ -47,6 +47,10 @@ interface PaymentMethodInterface extends public function setPosition(?int $position): void; + public function getGatewayConfig(): ?GatewayConfigInterface; + + public function setGatewayConfig(?GatewayConfigInterface $gatewayConfig): void; + /** * @return PaymentMethodTranslationInterface */ diff --git a/src/Sylius/Component/Payment/Model/PaymentRequest.php b/src/Sylius/Component/Payment/Model/PaymentRequest.php new file mode 100644 index 0000000000..e2c39985b4 --- /dev/null +++ b/src/Sylius/Component/Payment/Model/PaymentRequest.php @@ -0,0 +1,110 @@ + */ + protected array $responseData = []; + + public function __construct( + protected PaymentInterface $payment, + protected PaymentMethodInterface $method, + ) { + $this->createdAt = new \DateTime(); + } + + public function getId(): ?string + { + return $this->getHash()?->toBinary(); + } + + public function getHash(): ?Uuid + { + return $this->hash; + } + + public function getPayment(): PaymentInterface + { + return $this->payment; + } + + public function getState(): string + { + return $this->state; + } + + public function setState(string $state): void + { + $this->state = $state; + } + + public function getAction(): string + { + return $this->action; + } + + public function setAction(string $action): void + { + $this->action = $action; + } + + public function getPayload(): mixed + { + return $this->payload; + } + + public function setPayload(mixed $payload): void + { + $this->payload = $payload; + } + + public function getResponseData(): array + { + return $this->responseData; + } + + public function setResponseData(array $responseData): void + { + $this->responseData = $responseData; + } + + public function setPayment(PaymentInterface $payment): void + { + $this->payment = $payment; + } + + public function getMethod(): PaymentMethodInterface + { + return $this->method; + } + + public function setMethod(PaymentMethodInterface $method): void + { + $this->method = $method; + } +} diff --git a/src/Sylius/Component/Payment/Model/PaymentRequestInterface.php b/src/Sylius/Component/Payment/Model/PaymentRequestInterface.php new file mode 100644 index 0000000000..f7e3a29218 --- /dev/null +++ b/src/Sylius/Component/Payment/Model/PaymentRequestInterface.php @@ -0,0 +1,75 @@ + */ + public function getResponseData(): array; + + /** @param array $responseData */ + public function setResponseData(array $responseData): void; + + public function getPayment(): PaymentInterface; + + public function setPayment(PaymentInterface $payment): void; + + public function getMethod(): PaymentMethodInterface; + + public function setMethod(PaymentMethodInterface $method): void; +} diff --git a/src/Sylius/Component/Payment/PaymentRequestTransitions.php b/src/Sylius/Component/Payment/PaymentRequestTransitions.php new file mode 100644 index 0000000000..1d2f9b681d --- /dev/null +++ b/src/Sylius/Component/Payment/PaymentRequestTransitions.php @@ -0,0 +1,27 @@ + + */ +interface PaymentRequestRepositoryInterface extends RepositoryInterface +{ + public function findOneByPaymentId(mixed $hash, mixed $paymentId): ?PaymentRequestInterface; + + public function createQueryBuilderForPayment(string $paymentId): QueryBuilder; + + public function duplicateExists(PaymentRequestInterface $paymentRequest): bool; + + /** + * @param array $states + * + * @return array + */ + public function findByPaymentIdAndStates(mixed $paymentId, array $states): array; +} diff --git a/src/Sylius/Component/Payment/composer.json b/src/Sylius/Component/Payment/composer.json index 877600569b..a93be5a534 100644 --- a/src/Sylius/Component/Payment/composer.json +++ b/src/Sylius/Component/Payment/composer.json @@ -28,7 +28,8 @@ "require": { "php": "^8.2", "sylius/registry": "^1.6", - "sylius/resource": "^1.12@alpha" + "sylius/resource": "^1.12@alpha", + "symfony/uid": "^6.4.0 || ^7.1" }, "require-dev": { "phpspec/phpspec": "^7.2" diff --git a/src/Sylius/Component/Payment/spec/Model/GatewayConfigSpec.php b/src/Sylius/Component/Payment/spec/Model/GatewayConfigSpec.php new file mode 100644 index 0000000000..29308fa07b --- /dev/null +++ b/src/Sylius/Component/Payment/spec/Model/GatewayConfigSpec.php @@ -0,0 +1,49 @@ +shouldImplement(GatewayConfigInterface::class); + } + + function its_gateway_name_is_mutable(): void + { + $this->setGatewayName('Offline'); + $this->getGatewayName()->shouldReturn('Offline'); + } + + function it_gets_factory_name_from_config_if_the_variable_is_not_set(): void + { + $this->setConfig(['factory' => 'Offline']); + $this->getFactoryName()->shouldReturn('Offline'); + } + + function its_factory_name_is_mutable(): void + { + $this->setFactoryName('Offline'); + $this->getFactoryName()->shouldReturn('Offline'); + } + + function its_config_s_mutable(): void + { + $this->setConfig(['key' => '123']); + $this->getConfig()->shouldReturn(['key' => '123']); + } +} diff --git a/src/Sylius/Component/Payment/spec/Model/PaymentMethodSpec.php b/src/Sylius/Component/Payment/spec/Model/PaymentMethodSpec.php index 7bced680c4..e853bc23ef 100644 --- a/src/Sylius/Component/Payment/spec/Model/PaymentMethodSpec.php +++ b/src/Sylius/Component/Payment/spec/Model/PaymentMethodSpec.php @@ -14,6 +14,7 @@ declare(strict_types=1); namespace spec\Sylius\Component\Payment\Model; use PhpSpec\ObjectBehavior; +use Sylius\Component\Payment\Model\GatewayConfigInterface; use Sylius\Component\Payment\Model\PaymentMethodInterface; final class PaymentMethodSpec extends ObjectBehavior @@ -116,4 +117,10 @@ final class PaymentMethodSpec extends ObjectBehavior { $this->getUpdatedAt()->shouldReturn(null); } + + function its_gateway_config_is_mutable(GatewayConfigInterface $gatewayConfig): void + { + $this->setGatewayConfig($gatewayConfig); + $this->getGatewayConfig()->shouldReturn($gatewayConfig); + } } diff --git a/src/Sylius/Component/Payment/spec/Model/PaymentRequestSpec.php b/src/Sylius/Component/Payment/spec/Model/PaymentRequestSpec.php new file mode 100644 index 0000000000..ee6731a38b --- /dev/null +++ b/src/Sylius/Component/Payment/spec/Model/PaymentRequestSpec.php @@ -0,0 +1,131 @@ +beConstructedWith($payment, $method); + } + + function it_implements_sylius_payment_request_interface(): void + { + $this->shouldImplement(PaymentRequestInterface::class); + } + + function it_has_no_hash_by_default(): void + { + $this->getId()->shouldReturn(null); + } + + function it_has_a_payment_by_default(): void + { + $this->getPayment()->shouldReturnAnInstanceOf(PaymentInterface::class); + } + + function it_has_a_payment_method_by_default(): void + { + $this->getMethod()->shouldReturnAnInstanceOf(PaymentMethodInterface::class); + } + + function its_payment_method_is_mutable(PaymentMethodInterface $method): void + { + $this->setMethod($method); + $this->getMethod()->shouldReturn($method); + } + + function its_payment_is_mutable(PaymentInterface $payment): void + { + $this->setPayment($payment); + $this->getPayment()->shouldReturn($payment); + } + + function it_has_new_state_by_default(): void + { + $this->getState()->shouldReturn(PaymentRequestInterface::STATE_NEW); + } + + function its_state_is_mutable(): void + { + $this->setState('test_state'); + $this->getState()->shouldReturn('test_state'); + } + + function it_has_capture_action_by_default(): void + { + $this->getAction()->shouldReturn(PaymentRequestInterface::ACTION_CAPTURE); + } + + function its_action_is_mutable(): void + { + $this->setAction('test_action'); + $this->getAction()->shouldReturn('test_action'); + } + + function it_has_null_payload_by_default(): void + { + $this->getPayload()->shouldReturn(null); + } + + function its_payload_is_mutable(): void + { + $stdClass = new stdClass(); + $this->setPayload($stdClass); + $this->getPayload()->shouldReturn($stdClass); + } + + function it_has_empty_array_response_data_by_default(): void + { + $this->getResponseData()->shouldReturn([]); + } + + function its_response_data_are_mutable(): void + { + $this->setResponseData(['foo', 'bar']); + $this->getResponseData()->shouldReturn(['foo', 'bar']); + } + + function it_initializes_creation_date_by_default(): void + { + $this->getCreatedAt()->shouldHaveType('DateTime'); + } + + function its_creation_date_is_mutable(): void + { + $date = new \DateTime('last year'); + + $this->setCreatedAt($date); + $this->getCreatedAt()->shouldReturn($date); + } + + function it_has_no_last_update_date_by_default(): void + { + $this->getUpdatedAt()->shouldReturn(null); + } + + function its_last_update_date_is_mutable(): void + { + $date = new \DateTime('last year'); + + $this->setUpdatedAt($date); + $this->getUpdatedAt()->shouldReturn($date); + } +} diff --git a/symfony.lock b/symfony.lock index 6622b14cb1..530b333ae5 100644 --- a/symfony.lock +++ b/symfony.lock @@ -454,9 +454,6 @@ "ralouphie/getallheaders": { "version": "3.0.3" }, - "ramsey/uuid": { - "version": "3.8.0" - }, "rector/rector": { "version": "0.11.52" }, @@ -849,6 +846,18 @@ "templates/base.html.twig" ] }, + "symfony/uid": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + }, + "files": [ + "config/packages/uid.yaml" + ] + }, "symfony/ux-autocomplete": { "version": "2.9999999", "recipe": { diff --git a/tests/Api/Admin/PaymentMethodsTest.php b/tests/Api/Admin/PaymentMethodsTest.php index 8cc45c198c..f415719ae4 100644 --- a/tests/Api/Admin/PaymentMethodsTest.php +++ b/tests/Api/Admin/PaymentMethodsTest.php @@ -108,6 +108,25 @@ final class PaymentMethodsTest extends JsonApiTestCase ); } + /** @test */ + public function it_removes_a_payment_method(): void + { + $this->setUpAdminContext(); + + $fixtures = $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + ]); + + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = $fixtures['payment_method_cash_on_delivery']; + + $this->requestDelete(uri: '/api/v2/admin/payment-methods/' . $paymentMethod->getCode()); + + $this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT); + } + /** @test */ public function it_updates_a_payment_method(): void { @@ -153,25 +172,6 @@ final class PaymentMethodsTest extends JsonApiTestCase ); } - /** @test */ - public function it_removes_a_payment_method(): void - { - $this->setUpAdminContext(); - - $fixtures = $this->loadFixturesFromFiles([ - 'authentication/api_administrator.yaml', - 'channel/channel.yaml', - 'payment_method.yaml', - ]); - - /** @var PaymentMethodInterface $paymentMethod */ - $paymentMethod = $fixtures['payment_method_cash_on_delivery']; - - $this->requestDelete(uri: '/api/v2/admin/payment-methods/' . $paymentMethod->getCode()); - - $this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT); - } - /** @test */ public function it_does_not_update_a_payment_method_with_duplicate_locale_translation(): void { diff --git a/tests/Api/Admin/PaymentRequestsTest.php b/tests/Api/Admin/PaymentRequestsTest.php new file mode 100644 index 0000000000..8653f74a2c --- /dev/null +++ b/tests/Api/Admin/PaymentRequestsTest.php @@ -0,0 +1,70 @@ +setUpAdminContext(); + + parent::setUp(); + } + + /** @test */ + public function it_gets_payment_requests_for_payment(): void + { + $this->setUpDefaultGetHeaders(); + + $fixtures = $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request.yaml', + 'payment_request/order.yaml', + ]); + + /** @var PaymentInterface $payment */ + $payment = $fixtures['payment']; + + $this->requestGet(uri: sprintf('/api/v2/admin/payments/%s/payment-requests', $payment->getId())); + + $this->assertResponseSuccessful('admin/payment_request/get_payment_requests_for_payment'); + } + + /** @test */ + public function it_gets_a_payment_request(): void + { + $this->setUpDefaultGetHeaders(); + + $fixtures = $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request.yaml', + 'payment_request/order.yaml', + ]); + + /** @var PaymentRequestInterface $paymentRequest */ + $paymentRequest = $fixtures['payment_request_authorize']; + + $this->requestGet(uri: sprintf('/api/v2/admin/payment-requests/%s', $paymentRequest->getHash())); + + $this->assertResponseSuccessful('admin/payment_request/get_payment_request'); + } +} diff --git a/tests/Api/DataFixtures/ORM/payment_request/order.yaml b/tests/Api/DataFixtures/ORM/payment_request/order.yaml new file mode 100644 index 0000000000..fcd3cc2d49 --- /dev/null +++ b/tests/Api/DataFixtures/ORM/payment_request/order.yaml @@ -0,0 +1,17 @@ +Sylius\Component\Core\Model\Payment: + payment: + method: "@payment_method_cash_on_delivery" + order: "@order" + state: "processing" + amount: 1000 + currencyCode: "USD" + +Sylius\Component\Core\Model\Order: + order: + channel: "@channel_web" + currencyCode: "USD" + localeCode: "en_US" + state: "new" + paymentState: "awaiting_payment" + shippingState: "ready" + tokenValue: "token" diff --git a/tests/Api/DataFixtures/ORM/payment_request/order_with_customer.yaml b/tests/Api/DataFixtures/ORM/payment_request/order_with_customer.yaml new file mode 100644 index 0000000000..17271ac86c --- /dev/null +++ b/tests/Api/DataFixtures/ORM/payment_request/order_with_customer.yaml @@ -0,0 +1,18 @@ +Sylius\Component\Core\Model\Payment: + payment: + method: "@payment_method_cash_on_delivery" + order: "@order" + state: "processing" + amount: 1000 + currencyCode: "USD" + +Sylius\Component\Core\Model\Order: + order: + channel: "@channel_web" + currencyCode: "USD" + localeCode: "en_US" + state: "new" + paymentState: "awaiting_payment" + shippingState: "ready" + tokenValue: "token" + customer: "@customer_oliver" diff --git a/tests/Api/DataFixtures/ORM/payment_request/payment_request.yaml b/tests/Api/DataFixtures/ORM/payment_request/payment_request.yaml new file mode 100644 index 0000000000..f4be623365 --- /dev/null +++ b/tests/Api/DataFixtures/ORM/payment_request/payment_request.yaml @@ -0,0 +1,33 @@ +Sylius\Component\Payment\Model\PaymentRequest: + payment_request_status: + __construct: [ '@payment', '@payment_method_cash_on_delivery' ] + state: "completed" + action: "status" + payload: null + responseData: [] + payment_request_authorize: + __construct: [ '@payment', '@payment_method_cash_on_delivery' ] + state: "completed" + action: "authorize" + payload: { + "target_path": "https://myshop.tld/target-path", + "after_path": "https://myshop.tld/after-path", + } + responseData: { + "after_url": "https://myshop.tld/after-path", + } + payment_request_capture: + __construct: [ '@payment', '@payment_method_cash_on_delivery' ] + state: "processing" + action: "capture" + payload: { + "http_request": { + "query": { + "token": "EC-2d9EV13959UR209410U", + "PayerID": "UX8WBNYWGBVMG" + } + } + } + responseData: { + "after_url": "https://myshop.tld/after-path", + } diff --git a/tests/Api/DataFixtures/ORM/payment_request/payment_request_payum.yaml b/tests/Api/DataFixtures/ORM/payment_request/payment_request_payum.yaml new file mode 100644 index 0000000000..0d24ef5909 --- /dev/null +++ b/tests/Api/DataFixtures/ORM/payment_request/payment_request_payum.yaml @@ -0,0 +1,16 @@ +Sylius\Component\Payment\Model\PaymentRequest: + payment_request_capture: + __construct: [ '@payment', '@payment_method_cash_on_delivery' ] + state: "processing" + action: "capture" + payload: { + "http_request": { + "query": { + "token": "EC-2d9EV13959UR209410U", + "PayerID": "UX8WBNYWGBVMG" + } + } + } + responseData: { + "after_url": "https://myshop.tld/after-path", + } diff --git a/tests/Api/Responses/admin/payment/patch_complete_payment_response.json b/tests/Api/Responses/admin/payment/patch_complete_payment_response.json new file mode 100644 index 0000000000..6308190e6f --- /dev/null +++ b/tests/Api/Responses/admin/payment/patch_complete_payment_response.json @@ -0,0 +1,18 @@ +{ + "@context": "\/api\/v2\/contexts\/Payment", + "@id": "\/api\/v2\/admin\/payments\/@integer@", + "@type": "Payment", + "order": "\/api\/v2\/admin\/orders\/nAWw2jewpA", + "id": @integer@, + "method": { + "@id": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY", + "@type": "PaymentMethod", + "name": "Cash on delivery" + }, + "currencyCode": "USD", + "amount": 6500, + "state": "completed", + "details": [], + "createdAt": @string@, + "updatedAt": @string@ +} diff --git a/tests/Api/Responses/admin/payment_request/get_payment_request.json b/tests/Api/Responses/admin/payment_request/get_payment_request.json new file mode 100644 index 0000000000..d374b5c12c --- /dev/null +++ b/tests/Api/Responses/admin/payment_request/get_payment_request.json @@ -0,0 +1,19 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/admin\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "authorize", + "payload": { + "target_path": "https:\/\/myshop.tld\/target-path", + "after_path": "https:\/\/myshop.tld\/after-path" + }, + "responseData": { + "after_url": "https:\/\/myshop.tld\/after-path" + }, + "payment": "\/api\/v2\/admin\/payments\/@integer@", + "method": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": @datetime@, + "updatedAt": @datetime@ +} diff --git a/tests/Api/Responses/admin/payment_request/get_payment_requests_for_payment.json b/tests/Api/Responses/admin/payment_request/get_payment_requests_for_payment.json new file mode 100644 index 0000000000..a242e65a17 --- /dev/null +++ b/tests/Api/Responses/admin/payment_request/get_payment_requests_for_payment.json @@ -0,0 +1,78 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/admin\/payments\/@integer@\/payment-requests", + "@type": "hydra:Collection", + "hydra:totalItems": 3, + "hydra:member": [ + { + "@id": "\/api\/v2\/admin\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "status", + "payment": "\/api\/v2\/admin\/payments\/@integer@", + "method": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY" + }, + { + "@id": "\/api\/v2\/admin\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "authorize", + "payment": "\/api\/v2\/admin\/payments\/@integer@", + "method": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY" + }, + { + "@id": "\/api\/v2\/admin\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "processing", + "action": "capture", + "payment": "\/api\/v2\/admin\/payments\/@integer@", + "method": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY" + } + ], + "hydra:search": { + "@type": "hydra:IriTemplate", + "hydra:template": "\/api\/v2\/admin\/payments\/@integer@\/payment-requests{?action,action[],method.code,method.code[],state,state[]}", + "hydra:variableRepresentation": "BasicRepresentation", + "hydra:mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "action", + "property": "action", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "action[]", + "property": "action", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "method.code", + "property": "method.code", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "method.code[]", + "property": "method.code", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "state", + "property": "state", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "state[]", + "property": "state", + "required": false + } + ] + } +} diff --git a/tests/Api/Responses/shop/payment_request/get_payment_request.json b/tests/Api/Responses/shop/payment_request/get_payment_request.json new file mode 100644 index 0000000000..d19b4534ea --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/get_payment_request.json @@ -0,0 +1,23 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/shop\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "processing", + "action": "capture", + "payload": { + "http_request": { + "query": { + "token": "EC-2d9EV13959UR209410U", + "PayerID": "UX8WBNYWGBVMG" + } + } + }, + "responseData": { + "after_url": "https:\/\/myshop.tld\/after-path" + }, + "payment": "\/api\/v2\/shop\/orders\/token\/payments\/@integer@", + "method": "\/api\/v2\/shop\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": @datetime@, + "updatedAt": @datetime@ +} diff --git a/tests/Api/Responses/shop/payment_request/post_payment_request.json b/tests/Api/Responses/shop/payment_request/post_payment_request.json new file mode 100644 index 0000000000..f3dc829848 --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/post_payment_request.json @@ -0,0 +1,17 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/shop\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "capture", + "payload": { + "target_path": "https:\/\/myshop.tld\/target-path", + "after_path": "https:\/\/myshop.tld\/after-path" + }, + "responseData": [], + "payment": "\/api\/v2\/shop\/orders\/@string@\/payments\/@integer@", + "method": "\/api\/v2\/shop\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": "@datetime@", + "updatedAt": "@datetime@" +} diff --git a/tests/Api/Responses/shop/payment_request/post_payment_request_payum.json b/tests/Api/Responses/shop/payment_request/post_payment_request_payum.json new file mode 100644 index 0000000000..0a94f7e84a --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/post_payment_request_payum.json @@ -0,0 +1,19 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/shop\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "capture", + "payload": { + "target_path": "https:\/\/myshop.tld\/target-path", + "after_path": "https:\/\/myshop.tld\/after-path" + }, + "responseData": { + "after_url": "@string@" + }, + "payment": "\/api\/v2\/shop\/orders\/@string@\/payments\/@integer@", + "method": "\/api\/v2\/shop\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": "@datetime@", + "updatedAt": "@datetime@" +} diff --git a/tests/Api/Responses/shop/payment_request/post_payment_request_without_required_data.json b/tests/Api/Responses/shop/payment_request/post_payment_request_without_required_data.json new file mode 100644 index 0000000000..40acb75331 --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/post_payment_request_without_required_data.json @@ -0,0 +1,4 @@ +{ + "code": 400, + "message": "Request does not have the following required fields specified: paymentId, paymentMethodCode." +} diff --git a/tests/Api/Responses/shop/payment_request/put_payment_request.json b/tests/Api/Responses/shop/payment_request/put_payment_request.json new file mode 100644 index 0000000000..589badaa3b --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/put_payment_request.json @@ -0,0 +1,19 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/shop\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "capture", + "payload": { + "target_path": "https:\/\/myshop.tld\/new-target-path", + "after_path": "https:\/\/myshop.tld\/new-after-path" + }, + "responseData": { + "after_url": "https:\/\/myshop.tld\/after-path" + }, + "payment": "\/api\/v2\/shop\/orders\/@string@\/payments\/@integer@", + "method": "\/api\/v2\/shop\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": "@datetime@", + "updatedAt": "@datetime@" +} diff --git a/tests/Api/Responses/shop/payment_request/put_payment_request_payum.json b/tests/Api/Responses/shop/payment_request/put_payment_request_payum.json new file mode 100644 index 0000000000..7f7cc1735d --- /dev/null +++ b/tests/Api/Responses/shop/payment_request/put_payment_request_payum.json @@ -0,0 +1,19 @@ +{ + "@context": "\/api\/v2\/contexts\/PaymentRequest", + "@id": "\/api\/v2\/shop\/payment-requests\/@string@", + "@type": "PaymentRequest", + "hash": "@string@", + "state": "completed", + "action": "capture", + "payload": { + "target_path": "https:\/\/myshop.tld\/new-target-path", + "after_path": "https:\/\/myshop.tld\/new-after-path" + }, + "responseData": { + "after_url": "https:\/\/myshop.tld\/new-after-path" + }, + "payment": "\/api\/v2\/shop\/orders\/@string@\/payments\/@integer@", + "method": "\/api\/v2\/shop\/payment-methods\/CASH_ON_DELIVERY", + "createdAt": "@datetime@", + "updatedAt": "@datetime@" +} diff --git a/tests/Api/Shop/PaymentRequestsTest.php b/tests/Api/Shop/PaymentRequestsTest.php new file mode 100644 index 0000000000..a4764dbcb1 --- /dev/null +++ b/tests/Api/Shop/PaymentRequestsTest.php @@ -0,0 +1,252 @@ +setUpOrderPlacer(); + $this->setUpShopUserContext(); + + parent::setUp(); + } + + /** @test */ + public function it_gets_a_payment_request(): void + { + $this->setUpDefaultGetHeaders(); + + $fixtures = $this->loadFixturesFromFiles([ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request.yaml', + 'payment_request/order_with_customer.yaml', + ]); + + /** @var PaymentRequestInterface $paymentRequest */ + $paymentRequest = $fixtures['payment_request_capture']; + + $this->client->request( + method: 'GET', + uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()), + server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(), + ); + + $this->assertResponseSuccessful('shop/payment_request/get_payment_request'); + } + + /** + * @test + * + * @dataProvider createPaymentRequestProvider + * + * @param array $fixturesPaths + * + * @throws \JsonException + */ + public function it_creates_a_payment_request(array $fixturesPaths, string $responsePath): void + { + $this->loadFixturesFromFiles($fixturesPaths); + + $order = $this->placeOrder('nAWw2jewpA', 'oliver@doe.com'); + $payment = $order->getLastPayment(); + + $this->client->request( + method: 'POST', + uri: '/api/v2/shop/payment-requests', + server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(), + content: json_encode([ + 'paymentId' => $payment->getId(), + 'paymentMethodCode' => $payment->getMethod()->getCode(), + 'action' => 'capture', + 'payload' => [ + 'target_path' => 'https://myshop.tld/target-path', + 'after_path' => 'https://myshop.tld/after-path', + ], + ], \JSON_THROW_ON_ERROR), + ); + + $this->assertResponse( + $this->client->getResponse(), + $responsePath, + Response::HTTP_CREATED, + ); + } + + public function createPaymentRequestProvider(): iterable + { + $environment = getenv('APP_ENV'); + + if ($environment === 'test_cached_payum') { + yield [ + [ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ], + 'shop/payment_request/post_payment_request_payum', + ]; + + return; + } + + yield [ + [ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ], + 'shop/payment_request/post_payment_request', + ]; + } + + /** @test */ + public function it_does_not_create_a_payment_request_without_required_data(): void + { + $this->loadFixturesFromFiles([ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ]); + + $this->placeOrder('nAWw2jewpA', 'oliver@doe.com'); + + $this->client->request( + method: 'POST', + uri: '/api/v2/shop/payment-requests', + server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(), + content: json_encode([], \JSON_THROW_ON_ERROR), + ); + + $this->assertResponse( + $this->client->getResponse(), + 'shop/payment_request/post_payment_request_without_required_data', + Response::HTTP_BAD_REQUEST, + ); + } + + /** + * @test + * + * @dataProvider updatePaymentRequestProvider + * + * @param array $fixturesPaths + * + * @throws \JsonException + */ + public function it_updates_a_payment_request(array $fixturesPaths, string $responsePath): void + { + $this->setUpDefaultGetHeaders(); + + $fixtures = $this->loadFixturesFromFiles($fixturesPaths); + + /** @var PaymentRequestInterface $paymentRequest */ + $paymentRequest = $fixtures['payment_request_capture']; + + $this->client->request( + method: 'PUT', + uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()), + server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(), + content: json_encode([ + 'payload' => [ + 'target_path' => 'https://myshop.tld/new-target-path', + 'after_path' => 'https://myshop.tld/new-after-path', + ], + ], \JSON_THROW_ON_ERROR), + ); + + $this->assertResponseSuccessful($responsePath); + } + + public function updatePaymentRequestProvider(): iterable + { + $environment = getenv('APP_ENV'); + + if ($environment === 'test_cached_payum') { + yield [ + [ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request_payum.yaml', + 'payment_request/order_with_customer.yaml', + ], + 'shop/payment_request/put_payment_request_payum', + ]; + + return; + } + + yield [ + [ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request.yaml', + 'payment_request/order_with_customer.yaml', + ], + 'shop/payment_request/put_payment_request', + ]; + } + + /** @test */ + public function it_does_not_update_a_payment_request_in_wrong_state(): void + { + $this->setUpDefaultGetHeaders(); + + $fixtures = $this->loadFixturesFromFiles([ + 'authentication/shop_user.yaml', + 'channel/channel.yaml', + 'payment_method.yaml', + 'payment_request/payment_request.yaml', + 'payment_request/order_with_customer.yaml', + ]); + + /** @var PaymentRequestInterface $paymentRequest */ + $paymentRequest = $fixtures['payment_request_authorize']; + + $this->client->request( + method: 'PUT', + uri: sprintf('/api/v2/shop/payment-requests/%s', $paymentRequest->getHash()), + server: $this->headerBuilder()->withJsonLdAccept()->withJsonLdContentType()->withShopUserAuthorization('oliver@doe.com')->build(), + content: json_encode([ + 'payload' => [ + 'target_path' => 'https://myshop.tld/new-target-path', + 'after_path' => 'https://myshop.tld/new-after-path', + ], + ], \JSON_THROW_ON_ERROR), + ); + $response = $this->client->getResponse(); + + $this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } +}