diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53fe8f2a..0a7f06b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,11 +19,11 @@ jobs: strategy: fail-fast: false matrix: - php: ["8.0", "8.1"] + php: ["8.1", "8.2"] node: ["16.x"] mysql: ["5.7", "8.0"] symfony: ["^5.4", "^6.0"] - sylius: ["~1.12.0"] + sylius: ["~1.12.0", "1.13.x-dev"] env: APP_ENV: test DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?serverVersion=${{ matrix.mysql }}" @@ -149,10 +149,12 @@ jobs: - name: Validate composer.json + if: ${{ matrix.sylius != '1.13.x-dev' }} run: composer validate --ansi --strict - name: Run analysis + if: ${{ matrix.sylius != '1.13.x-dev' }} run: composer analyse - diff --git a/composer.json b/composer.json index 2bd8f17f..21567e79 100644 --- a/composer.json +++ b/composer.json @@ -7,10 +7,13 @@ "require": { "php": "^8.0", "doctrine/doctrine-migrations-bundle": "^3.0", + "php-http/discovery": "^1.17", "phpseclib/phpseclib": "^2.0", - "polishsymfonycommunity/symfony-mocker-container": "^1.0", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "~1.0", + "psr/http-factory-implementation": "~1.0", "sylius-labs/doctrine-migrations-extra-bundle": "^0.1.4 || ^0.2", - "sylius/sylius": "~1.12.0", + "sylius/sylius": "~1.12.0 || 1.13.x-dev", "symfony/mailer": "^5.4 || ^6.0" }, "require-dev": { @@ -27,16 +30,19 @@ "friends-of-behat/symfony-extension": "^2.1", "friends-of-behat/variadic-extension": "^1.3", "lchrusciel/api-test-case": "^5.1", + "nyholm/psr7": "^1.8", "phpspec/phpspec": "^7.0", "phpstan/phpstan": "^1.6", "phpstan/phpstan-doctrine": "1.3.37", "phpstan/phpstan-webmozart-assert": "^1.1", "phpunit/phpunit": "^8.5", + "polishsymfonycommunity/symfony-mocker-container": "^1.0", "sylius-labs/coding-standard": "^4.0", "sylius/sylius-rector": "^1.0", "symfony/browser-kit": "^5.4 || ^6.0", "symfony/debug-bundle": "^5.4 || ^6.0", "symfony/dotenv": "^5.4 || ^6.0", + "symfony/http-client": "^5.4 || ^6.0", "symfony/intl": "^5.4 || ^6.0", "symfony/web-profiler-bundle": "^5.4 || ^6.0", "symfony/webpack-encore-bundle": "^1.15" @@ -59,7 +65,8 @@ "dealerdirect/phpcodesniffer-composer-installer": false, "ocramius/package-versions": false, "symfony/flex": true, - "symfony/thanks": false + "symfony/thanks": false, + "php-http/discovery": true } }, "extra": { @@ -77,5 +84,8 @@ ] }, "prefer-stable": true, - "minimum-stability": "dev" + "minimum-stability": "dev", + "suggest": { + "php-http/guzzle6-adapter ":"Required to use this package on 32bit PHP" + } } diff --git a/spec/Api/WebhookApiSpec.php b/spec/Api/WebhookApiSpec.php index bb6165e5..25a25b76 100644 --- a/spec/Api/WebhookApiSpec.php +++ b/spec/Api/WebhookApiSpec.php @@ -4,40 +4,42 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\ClientInterface; use PhpSpec\ObjectBehavior; +use Prophecy\Argument; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; final class WebhookApiSpec extends ObjectBehavior { - function let(ClientInterface $client): void - { - $this->beConstructedWith($client, 'http://base-url.com/'); + function let( + ClientInterface $client, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + StreamInterface $stream, + RequestInterface $request + ): void { + $this->beConstructedWith($client, $requestFactory, $streamFactory, 'http://base-url.com/'); + $request->withHeader(Argument::any(), Argument::any())->willReturn($request); + $request->withBody(Argument::any())->willReturn($request); + $streamFactory->createStream(Argument::any())->willReturn($stream); } function it_registers_webhook( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, StreamInterface $body ): void { - $client->request( - 'POST', - 'http://base-url.com/v1/notifications/webhooks', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - 'json' => [ - 'url' => 'https://webhook.com', - 'event_types' => [ - ['name' => 'PAYMENT.CAPTURE.REFUNDED'], - ], - ], - ] - )->willReturn($response); + + $requestFactory->createRequest('POST','http://base-url.com/v1/notifications/webhooks') + ->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getBody()->willReturn($body); $body->getContents()->willReturn('{ "status": "CREATED" }'); @@ -46,26 +48,15 @@ function it_registers_webhook( function it_registers_webhook_without_https( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, StreamInterface $body ): void { - $client->request( - 'POST', - 'http://base-url.com/v1/notifications/webhooks', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - 'json' => [ - 'url' => 'https://webhook.com', - 'event_types' => [ - ['name' => 'PAYMENT.CAPTURE.REFUNDED'], - ], - ], - ] - )->willReturn($response); + $requestFactory->createRequest('POST','http://base-url.com/v1/notifications/webhooks') + ->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getBody()->willReturn($body); $body->getContents()->willReturn('{ "status": "CREATED" }'); diff --git a/spec/Client/PayPalClientSpec.php b/spec/Client/PayPalClientSpec.php index 64f3e1df..0a9f1dfb 100644 --- a/spec/Client/PayPalClientSpec.php +++ b/spec/Client/PayPalClientSpec.php @@ -13,11 +13,15 @@ namespace spec\Sylius\PayPalPlugin\Client; -use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use PhpSpec\ObjectBehavior; +use Prophecy\Argument; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; use Sylius\Component\Channel\Context\ChannelContextInterface; @@ -32,6 +36,10 @@ final class PayPalClientSpec extends ObjectBehavior { function let( ClientInterface $client, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + RequestInterface $request, + StreamInterface $stream, LoggerInterface $logger, UuidProviderInterface $uuidProvider, PayPalConfigurationProviderInterface $payPalConfigurationProvider, @@ -39,9 +47,14 @@ function let( ChannelInterface $channel ): void { $channelContext->getChannel()->willReturn($channel); + $streamFactory->createStream(Argument::any())->willReturn($stream); + $request->withHeader(Argument::any(), Argument::any())->willReturn($request); + $request->withBody(Argument::any())->willReturn($request); $this->beConstructedWith( $client, + $requestFactory, + $streamFactory, $logger, $uuidProvider, $payPalConfigurationProvider, @@ -58,17 +71,15 @@ function it_implements_pay_pal_client_interface(): void function it_returns_auth_token_for_given_client_data( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, StreamInterface $body ): void { - $client->request( - 'POST', - 'https://test-api.paypal.com/v1/oauth2/token', - [ - 'auth' => ['CLIENT_ID', 'CLIENT_SECRET'], - 'form_params' => ['grant_type' => 'client_credentials'], - ] - )->willReturn($response); + $requestFactory->createRequest('POST', 'https://test-api.paypal.com/v1/oauth2/token')->willReturn($request); + $request->withHeader(Argument::any(), Argument::any())->willReturn($request); + $request->withBody(Argument::any())->willReturn($request); + $client->sendRequest($request)->willReturn($response); $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"access_token": "TOKEN"}'); @@ -78,16 +89,13 @@ function it_returns_auth_token_for_given_client_data( function it_throws_an_exception_if_client_could_not_be_authorized( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response ): void { - $client->request( - 'POST', - 'https://test-api.paypal.com/v1/oauth2/token', - [ - 'auth' => ['CLIENT_ID', 'CLIENT_SECRET'], - 'form_params' => ['grant_type' => 'client_credentials'], - ] - )->willReturn($response); + $requestFactory->createRequest('POST', 'https://test-api.paypal.com/v1/oauth2/token')->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getStatusCode()->willReturn(401); $this @@ -98,6 +106,8 @@ function it_throws_an_exception_if_client_could_not_be_authorized( function it_calls_get_request_on_paypal_api( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, PayPalConfigurationProviderInterface $payPalConfigurationProvider, ChannelInterface $channel, ResponseInterface $response, @@ -105,18 +115,9 @@ function it_calls_get_request_on_paypal_api( ): void { $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'GET', - 'https://test-api.paypal.com/v2/get-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - ] - )->willReturn($response); + $requestFactory->createRequest('GET', 'https://test-api.paypal.com/v2/get-request/')->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); @@ -126,6 +127,9 @@ function it_calls_get_request_on_paypal_api( function it_logs_all_requests_if_logging_level_is_increased( ClientInterface $client, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + RequestInterface $request, LoggerInterface $logger, UuidProviderInterface $uuidProvider, PayPalConfigurationProviderInterface $payPalConfigurationProvider, @@ -136,6 +140,8 @@ function it_logs_all_requests_if_logging_level_is_increased( ): void { $this->beConstructedWith( $client, + $requestFactory, + $streamFactory, $logger, $uuidProvider, $payPalConfigurationProvider, @@ -148,18 +154,9 @@ function it_logs_all_requests_if_logging_level_is_increased( $channelContext->getChannel()->willReturn($channel); $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'GET', - 'https://test-api.paypal.com/v2/get-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - ] - )->willReturn($response); + $requestFactory->createRequest('GET', 'https://test-api.paypal.com/v2/get-request/')->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); @@ -174,6 +171,8 @@ function it_logs_all_requests_if_logging_level_is_increased( function it_logs_debug_id_from_failed_get_request( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, LoggerInterface $logger, PayPalConfigurationProviderInterface $payPalConfigurationProvider, ChannelInterface $channel, @@ -183,18 +182,8 @@ function it_logs_debug_id_from_failed_get_request( ): void { $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'GET', - 'https://test-api.paypal.com/v2/get-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - ] - )->willThrow($exception->getWrappedObject()); + $requestFactory->createRequest('GET', 'https://test-api.paypal.com/v2/get-request/')->willReturn($request); + $client->sendRequest($request)->willThrow($exception->getWrappedObject()); $exception->getResponse()->willReturn($response); $response->getBody()->willReturn($body); @@ -211,6 +200,8 @@ function it_logs_debug_id_from_failed_get_request( function it_calls_post_request_on_paypal_api( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, StreamInterface $body, UuidProviderInterface $uuidProvider, @@ -220,20 +211,9 @@ function it_calls_post_request_on_paypal_api( $uuidProvider->provide()->willReturn('REQUEST-ID'); $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'POST', - 'https://test-api.paypal.com/v2/post-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - 'PayPal-Request-Id' => 'REQUEST-ID', - ], - 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], - ] - )->willReturn($response); + $requestFactory->createRequest('POST', 'https://test-api.paypal.com/v2/post-request/')->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); @@ -246,7 +226,10 @@ function it_calls_post_request_on_paypal_api( function it_calls_post_request_on_paypal_api_with_extra_headers( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, + StreamFactoryInterface $streamFactory, StreamInterface $body, UuidProviderInterface $uuidProvider, PayPalConfigurationProviderInterface $payPalConfigurationProvider, @@ -255,21 +238,9 @@ function it_calls_post_request_on_paypal_api_with_extra_headers( $uuidProvider->provide()->willReturn('REQUEST-ID'); $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'POST', - 'https://test-api.paypal.com/v2/post-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - 'PayPal-Request-Id' => 'REQUEST-ID', - 'CUSTOM_HEADER' => 'header', - ], - 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], - ] - )->willReturn($response); + $requestFactory->createRequest('POST', 'https://test-api.paypal.com/v2/post-request/')->willReturn($request); + $client->sendRequest($request)->willReturn($response); + $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); @@ -278,10 +249,15 @@ function it_calls_post_request_on_paypal_api_with_extra_headers( ->post('v2/post-request/', 'TOKEN', ['parameter' => 'value', 'another_parameter' => 'another_value'], ['CUSTOM_HEADER' => 'header']) ->shouldReturn(['status' => 'OK', 'id' => '123123']) ; + + $request->withHeader('CUSTOM_HEADER', 'header')->shouldBeCalled(); + $streamFactory->createStream(json_encode(['parameter' => 'value', 'another_parameter' => 'another_value']))->shouldBeCalled(); } function it_logs_debug_id_from_failed_post_request( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, LoggerInterface $logger, RequestException $exception, ResponseInterface $response, @@ -293,20 +269,8 @@ function it_logs_debug_id_from_failed_post_request( $uuidProvider->provide()->willReturn('REQUEST-ID'); $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'POST', - 'https://test-api.paypal.com/v2/post-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - 'PayPal-Request-Id' => 'REQUEST-ID', - ], - 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], - ] - )->willThrow($exception->getWrappedObject()); + $requestFactory->createRequest('POST', 'https://test-api.paypal.com/v2/post-request/')->willReturn($request); + $client->sendRequest($request)->willThrow($exception->getWrappedObject()); $exception->getResponse()->willReturn($response); $response->getBody()->willReturn($body); @@ -326,26 +290,16 @@ function it_logs_debug_id_from_failed_post_request( function it_calls_patch_request_on_paypal_api( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, ResponseInterface $response, StreamInterface $body, PayPalConfigurationProviderInterface $payPalConfigurationProvider, ChannelInterface $channel ): void { $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - - $client->request( - 'PATCH', - 'https://test-api.paypal.com/v2/patch-request/123123', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], - ] - )->willReturn($response); + $requestFactory->createRequest('PATCH', 'https://test-api.paypal.com/v2/patch-request/123123')->willReturn($request); + $client->sendRequest($request)->willReturn($response); $response->getStatusCode()->willReturn(200); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); @@ -358,6 +312,8 @@ function it_calls_patch_request_on_paypal_api( function it_logs_debug_id_from_failed_patch_request( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, LoggerInterface $logger, RequestException $exception, ResponseInterface $response, @@ -367,19 +323,8 @@ function it_logs_debug_id_from_failed_patch_request( ): void { $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'PATCH', - 'https://test-api.paypal.com/v2/patch-request/123123', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], - ] - )->willThrow($exception->getWrappedObject()); + $requestFactory->createRequest('PATCH', 'https://test-api.paypal.com/v2/patch-request/123123')->willReturn($request); + $client->sendRequest($request)->willThrow($exception->getWrappedObject()); $exception->getResponse()->willReturn($response); $response->getBody()->willReturn($body); @@ -399,23 +344,15 @@ function it_logs_debug_id_from_failed_patch_request( function it_throws_exception_if_the_timeout_has_been_reached_the_specified_amount_of_time( ClientInterface $client, + RequestFactoryInterface $requestFactory, + RequestInterface $request, PayPalConfigurationProviderInterface $payPalConfigurationProvider, ChannelInterface $channel ): void { $payPalConfigurationProvider->getPartnerAttributionId($channel)->willReturn('TRACKING-ID'); - $client->request( - 'GET', - 'https://test-api.paypal.com/v2/get-request/', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', - ], - ] - )->willThrow(ConnectException::class); + $requestFactory->createRequest('GET', 'https://test-api.paypal.com/v2/get-request/')->willReturn($request); + $client->sendRequest($request)->willThrow(ConnectException::class); $this ->shouldThrow(PayPalApiTimeoutException::class) diff --git a/spec/Onboarding/Processor/BasicOnboardingProcessorSpec.php b/spec/Onboarding/Processor/BasicOnboardingProcessorSpec.php index 51da39d2..5b491b29 100644 --- a/spec/Onboarding/Processor/BasicOnboardingProcessorSpec.php +++ b/spec/Onboarding/Processor/BasicOnboardingProcessorSpec.php @@ -4,9 +4,12 @@ namespace spec\Sylius\PayPalPlugin\Onboarding\Processor; -use GuzzleHttp\ClientInterface; use Payum\Core\Model\GatewayConfigInterface; use PhpSpec\ObjectBehavior; +use Prophecy\Argument; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; use Sylius\Component\Core\Model\PaymentMethod; @@ -21,13 +24,24 @@ final class BasicOnboardingProcessorSpec extends ObjectBehavior { function let( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + RequestInterface $apiRequest, SellerWebhookRegistrarInterface $sellerWebhookRegistrar ): void { - $this->beConstructedWith($httpClient, $sellerWebhookRegistrar, 'https://paypal.facilitator.com'); + $this->beConstructedWith( + $httpClient, + $requestFactory, + $sellerWebhookRegistrar, + 'https://paypal.facilitator.com' + ); + + $apiRequest->withHeader(Argument::any(), Argument::any())->willReturn($apiRequest); } function it_processes_onboarding_for_supported_payment_method_and_request( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + RequestInterface $apiRequest, SellerWebhookRegistrarInterface $sellerWebhookRegistrar, ResponseInterface $response, StreamInterface $body, @@ -60,19 +74,11 @@ function it_processes_onboarding_for_supported_payment_method_and_request( $request->query = new ParameterBag(['onboarding_id' => 'ONBOARDING-ID']); - $httpClient - ->request( - 'GET', - 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID', - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - ] - ) - ->willReturn($response) - ; + $requestFactory->createRequest( + 'GET', + 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID' + )->willReturn($apiRequest); + $httpClient->sendRequest($apiRequest)->willReturn($response); $response->getBody()->willReturn($body); $body->getContents()->willReturn( @@ -90,6 +96,8 @@ function it_processes_onboarding_for_supported_payment_method_and_request( function it_processes_onboarding_for_supported_payment_method_with_not_granted_permissions_and_request( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + RequestInterface $apiRequest, SellerWebhookRegistrarInterface $sellerWebhookRegistrar, ResponseInterface $response, StreamInterface $body, @@ -111,19 +119,11 @@ function it_processes_onboarding_for_supported_payment_method_with_not_granted_p $request->query = new ParameterBag(['onboarding_id' => 'ONBOARDING-ID', 'permissionsGranted' => false]); - $httpClient - ->request( - 'GET', - 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID', - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - ] - ) - ->willReturn($response) - ; + $requestFactory->createRequest( + 'GET', + 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID' + )->willReturn($apiRequest); + $httpClient->sendRequest($apiRequest)->willReturn($response); $response->getBody()->willReturn($body); $body->getContents()->willReturn( @@ -153,6 +153,8 @@ function it_processes_onboarding_for_supported_payment_method_with_not_granted_p function it_processes_onboarding_for_supported_payment_method_with_not_granted_permissions_and_without_registered_webhook( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + RequestInterface $apiRequest, SellerWebhookRegistrarInterface $sellerWebhookRegistrar, ResponseInterface $response, StreamInterface $body, @@ -174,19 +176,12 @@ function it_processes_onboarding_for_supported_payment_method_with_not_granted_p $request->query = new ParameterBag(['onboarding_id' => 'ONBOARDING-ID', 'permissionsGranted' => false]); - $httpClient - ->request( - 'GET', - 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID', - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - ] - ) - ->willReturn($response) - ; + $requestFactory->createRequest( + 'GET', + 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID' + )->willReturn($apiRequest); + $httpClient->sendRequest($apiRequest)->willReturn($response); + $response->getBody()->willReturn($body); $body->getContents()->willReturn( @@ -270,6 +265,8 @@ function it_does_not_support_payment_method_that_has_client_id_is_not_set_on_req function it_throws_error_if_facilitator_data_is_not_loaded( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + RequestInterface $apiRequest, ResponseInterface $response, StreamInterface $body, GatewayConfigInterface $gatewayConfig, @@ -282,19 +279,11 @@ function it_throws_error_if_facilitator_data_is_not_loaded( $request->query = new ParameterBag(['onboarding_id' => 'ONBOARDING-ID']); - $httpClient - ->request( - 'GET', - 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID', - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - ] - ) - ->willReturn($response) - ; + $requestFactory->createRequest( + 'GET', + 'https://paypal.facilitator.com/partner-referrals/check/ONBOARDING-ID' + )->willReturn($apiRequest); + $httpClient->sendRequest($apiRequest)->willReturn($response); $response->getBody()->willReturn($body); $body->getContents()->willReturn('{"client_id":null,"client_secret":null}'); diff --git a/src/Api/WebhookApi.php b/src/Api/WebhookApi.php index 89c0be6f..e836bb40 100644 --- a/src/Api/WebhookApi.php +++ b/src/Api/WebhookApi.php @@ -4,35 +4,54 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\ClientInterface; + +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; final class WebhookApi implements WebhookApiInterface { private ClientInterface $client; + private RequestFactoryInterface $requestFactory; + + private StreamFactoryInterface $streamFactory; + private string $baseUrl; - public function __construct(ClientInterface $client, string $baseUrl) - { + public function __construct( + ClientInterface $client, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + string $baseUrl + ) { $this->client = $client; + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; $this->baseUrl = $baseUrl; } public function register(string $token, string $webhookUrl): array { - $response = $this->client->request('POST', $this->baseUrl . 'v1/notifications/webhooks', [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - 'json' => [ - 'url' => preg_replace('/^http:/i', 'https:', $webhookUrl), - 'event_types' => [ - ['name' => 'PAYMENT.CAPTURE.REFUNDED'], - ], - ], - ]); + $request = $this->requestFactory->createRequest('POST', $this->baseUrl . 'v1/notifications/webhooks') + ->withHeader('Authorization', 'Bearer ' . $token) + ->withHeader('Content-Type', 'application/json') + ->withHeader('Accept', 'application/json'); + + $request = $request->withBody( + $this->streamFactory->createStream( + json_encode( + [ + 'url' => preg_replace('/^http:/i', 'https:', $webhookUrl), + 'event_types' => [ + ['name' => 'PAYMENT.CAPTURE.REFUNDED'], + ] + ] + ) + ) + ); + + $response = $this->client->sendRequest($request); return (array) json_decode($response->getBody()->getContents(), true); } diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php index a4935bc3..61a06fe7 100644 --- a/src/Client/PayPalClient.php +++ b/src/Client/PayPalClient.php @@ -13,11 +13,14 @@ namespace Sylius\PayPalPlugin\Client; -use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Psr18Client; use Sylius\Component\Channel\Context\ChannelContextInterface; use Sylius\Component\Core\Model\ChannelInterface; use Sylius\PayPalPlugin\Exception\PayPalApiTimeoutException; @@ -27,7 +30,11 @@ final class PayPalClient implements PayPalClientInterface { - private ClientInterface $client; + private ClientInterface $client; + + private RequestFactoryInterface $requestFactory; + + private StreamFactoryInterface $streamFactory; private LoggerInterface $logger; @@ -44,7 +51,9 @@ final class PayPalClient implements PayPalClientInterface private bool $loggingLevelIncreased; public function __construct( - ClientInterface $client, + ClientInterface $client, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, LoggerInterface $logger, UuidProviderInterface $uuidProvider, PayPalConfigurationProviderInterface $payPalConfigurationProvider, @@ -54,6 +63,8 @@ public function __construct( bool $loggingLevelIncreased = false ) { $this->client = $client; + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; $this->logger = $logger; $this->uuidProvider = $uuidProvider; $this->payPalConfigurationProvider = $payPalConfigurationProvider; @@ -145,7 +156,44 @@ private function request(string $method, string $url, string $token, array $data private function doRequest(string $method, string $fullUrl, array $options): ResponseInterface { try { - $response = $this->client->request($method, $fullUrl, $options); + $request = $this->requestFactory->createRequest($method, $fullUrl); + + if (isset($options['auth'])) { + $request = $request->withHeader( + 'Authorization', + sprintf( + "Basic %s", + base64_encode(sprintf("%s:%s", $options['auth'][0], $options['auth'][1])) + ) + ); + } + + if (isset($options['form_params'])) { + $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded'); + $request = $request->withBody( + $this->streamFactory->createStream(http_build_query( + $options['form_params'], + '', + '&', + PHP_QUERY_RFC1738 + )) + ); + + } + + if (isset($options['json'])) { + $request = $request->withBody( + $this->streamFactory->createStream(json_encode($options['json'])) + ); + } + + if (isset($options['headers'])) { + foreach ($options['headers'] as $header => $headerValue) { + $request = $request->withHeader($header, $headerValue); + } + } + + $response = $this->client->sendRequest($request); } catch (ConnectException $exception) { --$this->requestTrialsLimit; if ($this->requestTrialsLimit === 0) { diff --git a/src/Listener/PayPalOrderCompletedListener.php b/src/Listener/PayPalOrderCompletedListener.php new file mode 100644 index 00000000..61f25520 --- /dev/null +++ b/src/Listener/PayPalOrderCompletedListener.php @@ -0,0 +1,37 @@ +getSubject(); + Assert::isInstanceOf($order, OrderInterface::class); + + $this->completeProcessor->completePayPalOrder($order); + } +} diff --git a/src/Listener/PayPalPaymentRefundedListener.php b/src/Listener/PayPalPaymentRefundedListener.php new file mode 100644 index 00000000..0382140d --- /dev/null +++ b/src/Listener/PayPalPaymentRefundedListener.php @@ -0,0 +1,36 @@ +getSubject(); + Assert::isInstanceOf($payment, PaymentInterface::class); + + $this->paymentRefundProcessor->refund($payment); + } +} \ No newline at end of file diff --git a/src/Onboarding/Processor/BasicOnboardingProcessor.php b/src/Onboarding/Processor/BasicOnboardingProcessor.php index 89e69335..8a1a583c 100644 --- a/src/Onboarding/Processor/BasicOnboardingProcessor.php +++ b/src/Onboarding/Processor/BasicOnboardingProcessor.php @@ -4,7 +4,8 @@ namespace Sylius\PayPalPlugin\Onboarding\Processor; -use GuzzleHttp\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Client\ClientInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\PayPalPlugin\Exception\PayPalPluginException; use Sylius\PayPalPlugin\Exception\PayPalWebhookAlreadyRegisteredException; @@ -17,16 +18,20 @@ final class BasicOnboardingProcessor implements OnboardingProcessorInterface { private ClientInterface $httpClient; + private RequestFactoryInterface $requestFactory; + private SellerWebhookRegistrarInterface $sellerWebhookRegistrar; private string $url; public function __construct( ClientInterface $httpClient, + RequestFactoryInterface $requestFactory, SellerWebhookRegistrarInterface $sellerWebhookRegistrar, string $url ) { $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; $this->sellerWebhookRegistrar = $sellerWebhookRegistrar; $this->url = $url; } @@ -43,16 +48,15 @@ public function process( Assert::notNull($gatewayConfig); $onboardingId = (string) $request->query->get('onboarding_id'); - $checkPartnerReferralsResponse = $this->httpClient->request( + $checkPartnerReferralsRequest = $this->requestFactory->createRequest( 'GET', - sprintf('%s/partner-referrals/check/%s', $this->url, $onboardingId), - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ], - ] - ); + sprintf('%s/partner-referrals/check/%s', $this->url, $onboardingId) + ) + ->withHeader('Content-Type', 'application/json') + ->withHeader('Accept', 'application/json') + ; + + $checkPartnerReferralsResponse = $this->httpClient->sendRequest($checkPartnerReferralsRequest); $response = (array) json_decode($checkPartnerReferralsResponse->getBody()->getContents(), true); diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 94ac7197..bc8c1d60 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -6,6 +6,7 @@ + diff --git a/src/Resources/config/services/api.xml b/src/Resources/config/services/api.xml index 88b94775..5a262ec6 100644 --- a/src/Resources/config/services/api.xml +++ b/src/Resources/config/services/api.xml @@ -6,11 +6,14 @@ true + - + + + @@ -86,7 +89,9 @@ id="Sylius\PayPalPlugin\Api\WebhookApiInterface" class="Sylius\PayPalPlugin\Api\WebhookApi" > - + + + %sylius.pay_pal.api_base_url% diff --git a/src/Resources/config/services/onboarding.xml b/src/Resources/config/services/onboarding.xml index 917f0971..71b22cfc 100644 --- a/src/Resources/config/services/onboarding.xml +++ b/src/Resources/config/services/onboarding.xml @@ -15,7 +15,8 @@ id="Sylius\PayPalPlugin\Onboarding\Processor\OnboardingProcessorInterface" class="Sylius\PayPalPlugin\Onboarding\Processor\BasicOnboardingProcessor" > - + + %sylius.pay_pal.facilitator_url% diff --git a/src/Resources/config/services/workflow.xml b/src/Resources/config/services/workflow.xml new file mode 100644 index 00000000..e0982f98 --- /dev/null +++ b/src/Resources/config/services/workflow.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Resources/views/bundles/SyliusShopBundle/Checkout/SelectPayment/_choice.html.twig b/src/Resources/views/bundles/SyliusShopBundle/Checkout/SelectPayment/_choice.html.twig index ece6954b..2c841237 100644 --- a/src/Resources/views/bundles/SyliusShopBundle/Checkout/SelectPayment/_choice.html.twig +++ b/src/Resources/views/bundles/SyliusShopBundle/Checkout/SelectPayment/_choice.html.twig @@ -12,7 +12,7 @@ {% endif %} {% if method.gatewayConfig.factoryName == 'sylius.pay_pal' %} - {{ render(controller('Sylius\\PayPalPlugin\\Controller\\PayPalButtonsController:renderPaymentPageButtonsAction', {'orderId': order.id})) }} + {{ render(controller('Sylius\\PayPalPlugin\\Controller\\PayPalButtonsController::renderPaymentPageButtonsAction', {'orderId': order.id})) }} {% endif %} diff --git a/tests/Application/composer.json b/tests/Application/composer.json index af8a51b2..c5ee6856 100644 --- a/tests/Application/composer.json +++ b/tests/Application/composer.json @@ -1,5 +1,8 @@ { "name": "sylius/paypal-plugin", "description": "PayPal plugin for Sylius", - "license": "MIT" + "license": "MIT", + "require": { + "nyholm/psr7": "^1.8" + } } diff --git a/tests/Application/config/bundles.php b/tests/Application/config/bundles.php index d260b314..4f4da692 100644 --- a/tests/Application/config/bundles.php +++ b/tests/Application/config/bundles.php @@ -1,6 +1,7 @@ ['all' => true], @@ -62,10 +63,15 @@ Sylius\Calendar\SyliusCalendarBundle::class => ['all' => true], ]; -if (Kernel::MINOR_VERSION < 12) { - $bundles[Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class] = ['all' => true]; -} else { +if (defined(SyliusCoreBundle::class.'::VERSION_ID') && SyliusCoreBundle::VERSION_ID >= '11200') { $bundles[League\FlysystemBundle\FlysystemBundle::class] = ['all' => true]; +} else { + $bundles[Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class] = ['all' => true]; +} + + +if ( defined(SyliusCoreBundle::class.'::VERSION_ID') && SyliusCoreBundle::VERSION_ID >= '11300') { + $bundles[Sylius\Abstraction\StateMachine\SyliusStateMachineAbstractionBundle::class] = ['all' => true]; } return $bundles; diff --git a/tests/Application/config/packages/nyholm_psr7.yaml b/tests/Application/config/packages/nyholm_psr7.yaml new file mode 100644 index 00000000..5011e61a --- /dev/null +++ b/tests/Application/config/packages/nyholm_psr7.yaml @@ -0,0 +1,11 @@ +services: + # Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories) + Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory' + + nyholm.psr7.psr17_factory: + class: Nyholm\Psr7\Factory\Psr17Factory \ No newline at end of file diff --git a/tests/Application/config/packages/test/framework.yaml b/tests/Application/config/packages/test/framework.yaml index fc1d3c13..1890c8d0 100644 --- a/tests/Application/config/packages/test/framework.yaml +++ b/tests/Application/config/packages/test/framework.yaml @@ -2,3 +2,10 @@ framework: test: ~ session: storage_factory_id: session.storage.factory.mock_file + + mailer: + dsn: 'null://null' + cache: + pools: + test.mailer_pool: + adapter: cache.adapter.filesystem \ No newline at end of file