From f60c4c0629848c52eb60ec1ab54486702c6e06bb Mon Sep 17 00:00:00 2001 From: Mateusz Zalewski Date: Tue, 25 Aug 2020 13:40:34 +0200 Subject: [PATCH 1/5] Extract PayPalClient that logs debug_id from failed requests --- spec/Client/PayPalClientSpec.php | 157 ++++++++++++++++++++++++++ src/Client/PayPalClient.php | 82 ++++++++++++++ src/Client/PayPalClientInterface.php | 12 ++ src/Resources/config/services/api.xml | 9 ++ 4 files changed, 260 insertions(+) create mode 100644 spec/Client/PayPalClientSpec.php create mode 100644 src/Client/PayPalClient.php create mode 100644 src/Client/PayPalClientInterface.php diff --git a/spec/Client/PayPalClientSpec.php b/spec/Client/PayPalClientSpec.php new file mode 100644 index 00000000..1df97b4f --- /dev/null +++ b/spec/Client/PayPalClientSpec.php @@ -0,0 +1,157 @@ +beConstructedWith($client, $logger, 'https://test-api.paypal.com/'); + } + + function it_implements_pay_pal_client_interface(): void + { + $this->shouldImplement(PayPalClientInterface::class); + } + + function it_calls_get_request_on_paypal_api( + ClientInterface $client, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + ] + )->willReturn($response); + $response->getStatusCode()->willReturn(200); + $response->getBody()->willReturn($body); + $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); + + $this->get('v2/get-request/', 'TOKEN')->shouldReturn(['status' => 'OK', 'id' => '123123']); + } + + function it_logs_debug_id_from_failed_get_request( + ClientInterface $client, + LoggerInterface $logger, + RequestException $exception, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + ] + )->willThrow($exception->getWrappedObject()); + + $exception->getResponse()->willReturn($response); + $response->getBody()->willReturn($body); + $response->getStatusCode()->willReturn(400); + $body->getContents()->willReturn('{"status": "FAILED", "debug_id": "123123"}'); + + $logger + ->error('GET request to "https://test-api.paypal.com/v2/get-request/" failed with debug ID 123123') + ->shouldBeCalled() + ; + + $this->get('v2/get-request/', 'TOKEN')->shouldReturn(['status' => 'FAILED', 'debug_id' => '123123']); + } + + function it_calls_post_request_on_paypal_api( + ClientInterface $client, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], + ] + )->willReturn($response); + $response->getStatusCode()->willReturn(200); + $response->getBody()->willReturn($body); + $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); + + $this + ->post('v2/post-request/', 'TOKEN', ['parameter' => 'value', 'another_parameter' => 'another_value']) + ->shouldReturn(['status' => 'OK', 'id' => '123123']) + ; + } + + function it_logs_debug_id_from_failed_post_request( + ClientInterface $client, + LoggerInterface $logger, + RequestException $exception, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], + ] + )->willThrow($exception->getWrappedObject()); + + $exception->getResponse()->willReturn($response); + $response->getBody()->willReturn($body); + $response->getStatusCode()->willReturn(400); + $body->getContents()->willReturn('{"status": "FAILED", "debug_id": "123123"}'); + + $logger + ->error('POST request to "https://test-api.paypal.com/v2/post-request/" failed with debug ID 123123') + ->shouldBeCalled() + ; + + $this + ->post('v2/post-request/', 'TOKEN', ['parameter' => 'value', 'another_parameter' => 'another_value']) + ->shouldReturn(['status' => 'FAILED', 'debug_id' => '123123']) + ; + } +} diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php new file mode 100644 index 00000000..74f4bc72 --- /dev/null +++ b/src/Client/PayPalClient.php @@ -0,0 +1,82 @@ +client = $client; + $this->logger = $logger; + $this->baseUrl = $baseUrl; + } + + public function get(string $url, string $token): array + { + return $this->request('GET', $url, $token); + } + + public function post(string $url, string $token, array $data): array + { + return $this->request('POST', $url, $token, $data); + } + + private function request(string $method, string $url, string $token, array $data = null): array + { + $options = [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $token, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + ], + ]; + + if ($data !== null) { + $options['json'] = $data; + } + + $fullUrl = $this->baseUrl.$url; + + try { + $response = $this->client->request($method, $fullUrl, $options); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + } + + $content = (array) json_decode($response->getBody()->getContents(), true); + + if ($response->getStatusCode() !== 200 && isset($content['debug_id'])) { + $this + ->logger + ->error(sprintf('%s request to "%s" failed with debug ID %s', $method, $fullUrl, $content['debug_id'])) + ; + } + + return $content; + } +} diff --git a/src/Client/PayPalClientInterface.php b/src/Client/PayPalClientInterface.php new file mode 100644 index 00000000..202bbe97 --- /dev/null +++ b/src/Client/PayPalClientInterface.php @@ -0,0 +1,12 @@ + + + + + %env(resolve:PAYPAL_API_BASE_URL)% + + Date: Tue, 25 Aug 2020 15:40:11 +0200 Subject: [PATCH 2/5] Use new PayPal client in already implemented API services --- spec/Api/CompleteOrderApiSpec.php | 29 +++++--------- spec/Api/CreateOrderApiSpec.php | 57 +++++++++++---------------- spec/Api/OrderDetailsApiSpec.php | 35 +++++----------- spec/Api/RefundPaymentApiSpec.php | 42 +++++--------------- src/Api/CompleteOrderApi.php | 29 ++------------ src/Api/CreateOrderApi.php | 30 ++------------ src/Api/OrderDetailsApi.php | 28 ++----------- src/Api/RefundPaymentApi.php | 30 ++------------ src/Client/PayPalClient.php | 2 +- src/Client/PayPalClientInterface.php | 2 +- src/Resources/config/services/api.xml | 16 ++------ 11 files changed, 73 insertions(+), 227 deletions(-) diff --git a/spec/Api/CompleteOrderApiSpec.php b/spec/Api/CompleteOrderApiSpec.php index f5fd6610..406a7299 100644 --- a/spec/Api/CompleteOrderApiSpec.php +++ b/spec/Api/CompleteOrderApiSpec.php @@ -20,12 +20,13 @@ use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\PayPalPlugin\Api\CompleteOrderApiInterface; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class CompleteOrderApiSpec extends ObjectBehavior { - function let(Client $client): void + function let(PayPalClientInterface $client): void { - $this->beConstructedWith($client, 'https://api.test-paypal.com/', 'PARTNER_ATTRIBUTION_ID'); + $this->beConstructedWith($client); } function it_implements_complete_order_api_interface(): void @@ -34,30 +35,18 @@ function it_implements_complete_order_api_interface(): void } function it_completes_pay_pal_order_with_given_id( - Client $client, + PayPalClientInterface $client, PaymentInterface $payment, - OrderInterface $order, - ResponseInterface $response, - StreamInterface $body + OrderInterface $order ): void { $payment->getOrder()->willReturn($order); $payment->getAmount()->willReturn(10000); $order->getCurrencyCode()->willReturn('PLN'); - $client->request( - 'POST', - 'https://api.test-paypal.com/v2/checkout/orders/123123/capture', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Prefer' => 'return=representation', - 'PayPal-Partner-Attribution-Id' => 'PARTNER_ATTRIBUTION_ID', - 'Content-Type' => 'application/json', - ], - ] - )->willReturn($response); - $response->getBody()->willReturn($body); - $body->getContents()->willReturn('{"status": "COMPLETED", "id": 123}'); + $client + ->post('v2/checkout/orders/123123/capture', 'TOKEN') + ->willReturn(['status' => 'COMPLETED', 'id' => 123]) + ; $this->complete('TOKEN', '123123')->shouldReturn(['status' => 'COMPLETED', 'id' => 123]); } diff --git a/spec/Api/CreateOrderApiSpec.php b/spec/Api/CreateOrderApiSpec.php index 3c2b1304..ee4915dc 100644 --- a/spec/Api/CreateOrderApiSpec.php +++ b/spec/Api/CreateOrderApiSpec.php @@ -24,12 +24,13 @@ use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\PayPalPlugin\Api\CreateOrderApiInterface; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class CreateOrderApiSpec extends ObjectBehavior { - function let(Client $client): void + function let(PayPalClientInterface $client): void { - $this->beConstructedWith($client, 'https://api.test-paypal.com/', 'PARTNER_ATTRIBUTION_ID'); + $this->beConstructedWith($client); } function it_implements_create_order_api_interface(): void @@ -38,11 +39,9 @@ function it_implements_create_order_api_interface(): void } function it_creates_pay_pal_order_based_on_given_payment( - Client $client, + PayPalClientInterface $client, PaymentInterface $payment, OrderInterface $order, - ResponseInterface $response, - StreamInterface $body, PaymentMethodInterface $paymentMethod, GatewayConfigInterface $gatewayConfig ): void { @@ -58,30 +57,25 @@ function it_creates_pay_pal_order_based_on_given_payment( ['merchant_id' => 'merchant-id', 'sylius_merchant_id' => 'sylius-merchant-id'] ); - $client->request( - 'POST', - 'https://api.test-paypal.com/v2/checkout/orders', + $client->post( + 'v2/checkout/orders', + 'TOKEN', Argument::that(function (array $data): bool { return - $data['headers']['Authorization'] === 'Bearer TOKEN' && - $data['json']['intent'] === 'CAPTURE' && - $data['json']['purchase_units'][0]['amount']['value'] === 100 && - $data['json']['purchase_units'][0]['amount']['currency_code'] === 'PLN' + $data['intent'] === 'CAPTURE' && + $data['purchase_units'][0]['amount']['value'] === 100 && + $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' ; }) - )->willReturn($response); - $response->getBody()->willReturn($body); - $body->getContents()->willReturn('{"status": "CREATED", "id": 123}'); + )->willReturn(['status' => 'CREATED', 'id' => 123]); $this->create('TOKEN', $payment)->shouldReturn(['status' => 'CREATED', 'id' => 123]); } function it_creates_pay_pal_order_with_shipping_address_based_on_given_payment( - Client $client, + PayPalClientInterface $client, PaymentInterface $payment, OrderInterface $order, - ResponseInterface $response, - StreamInterface $body, PaymentMethodInterface $paymentMethod, GatewayConfigInterface $gatewayConfig, AddressInterface $shippingAddress @@ -104,25 +98,22 @@ function it_creates_pay_pal_order_with_shipping_address_based_on_given_payment( ['merchant_id' => 'merchant-id', 'sylius_merchant_id' => 'sylius-merchant-id'] ); - $client->request( - 'POST', - 'https://api.test-paypal.com/v2/checkout/orders', + $client->post( + 'v2/checkout/orders', + 'TOKEN', Argument::that(function (array $data): bool { return - $data['headers']['Authorization'] === 'Bearer TOKEN' && - $data['json']['intent'] === 'CAPTURE' && - $data['json']['purchase_units'][0]['amount']['value'] === 100 && - $data['json']['purchase_units'][0]['amount']['currency_code'] === 'PLN' && - $data['json']['purchase_units'][0]['shipping']['name']['full_name'] === 'Gandalf The Grey' && - $data['json']['purchase_units'][0]['shipping']['address']['address_line_1'] === 'Hobbit St. 123' && - $data['json']['purchase_units'][0]['shipping']['address']['admin_area_2'] === 'Minas Tirith' && - $data['json']['purchase_units'][0]['shipping']['address']['postal_code'] === '000' && - $data['json']['purchase_units'][0]['shipping']['address']['country_code'] === 'US' + $data['intent'] === 'CAPTURE' && + $data['purchase_units'][0]['amount']['value'] === 100 && + $data['purchase_units'][0]['amount']['currency_code'] === 'PLN' && + $data['purchase_units'][0]['shipping']['name']['full_name'] === 'Gandalf The Grey' && + $data['purchase_units'][0]['shipping']['address']['address_line_1'] === 'Hobbit St. 123' && + $data['purchase_units'][0]['shipping']['address']['admin_area_2'] === 'Minas Tirith' && + $data['purchase_units'][0]['shipping']['address']['postal_code'] === '000' && + $data['purchase_units'][0]['shipping']['address']['country_code'] === 'US' ; }) - )->willReturn($response); - $response->getBody()->willReturn($body); - $body->getContents()->willReturn('{"status": "CREATED", "id": 123}'); + )->willReturn(['status' => 'CREATED', 'id' => 123]); $this->create('TOKEN', $payment)->shouldReturn(['status' => 'CREATED', 'id' => 123]); } diff --git a/spec/Api/OrderDetailsApiSpec.php b/spec/Api/OrderDetailsApiSpec.php index be3d2f8f..61cba851 100644 --- a/spec/Api/OrderDetailsApiSpec.php +++ b/spec/Api/OrderDetailsApiSpec.php @@ -13,18 +13,15 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use PhpSpec\ObjectBehavior; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; -use Sylius\PayPalPlugin\Api\AuthorizeClientApiInterface; use Sylius\PayPalPlugin\Api\OrderDetailsApiInterface; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class OrderDetailsApiSpec extends ObjectBehavior { - function let(Client $client): void + function let(PayPalClientInterface $client): void { - $this->beConstructedWith($client, 'https://api.test-paypal.com/', 'PARTNER_ATTRIBUTION_ID'); + $this->beConstructedWith($client); } function it_implements_pay_pal_order_details_provider_interface(): void @@ -32,26 +29,12 @@ function it_implements_pay_pal_order_details_provider_interface(): void $this->shouldImplement(OrderDetailsApiInterface::class); } - function it_provides_details_about_pay_pal_order( - Client $client, - AuthorizeClientApiInterface $authorizeClientApi, - ResponseInterface $detailsResponse, - StreamInterface $detailsBody - ): void { - $client->request( - 'GET', - 'https://api.test-paypal.com/v2/checkout/orders/123123', - [ - 'headers' => [ - 'Authorization' => 'Bearer TOKEN', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'PARTNER_ATTRIBUTION_ID', - ], - ] - )->willReturn($detailsResponse); - $detailsResponse->getBody()->willReturn($detailsBody); - $detailsBody->getContents()->willReturn('{"total": 1111}'); + function it_provides_details_about_pay_pal_order(PayPalClientInterface $client): void + { + $client + ->get('v2/checkout/orders/123123', 'TOKEN') + ->willReturn(['total' => 1111]) + ; $this->get('TOKEN', '123123')->shouldReturn(['total' => 1111]); } diff --git a/spec/Api/RefundPaymentApiSpec.php b/spec/Api/RefundPaymentApiSpec.php index 1b2ac4d3..de1aa58a 100644 --- a/spec/Api/RefundPaymentApiSpec.php +++ b/spec/Api/RefundPaymentApiSpec.php @@ -13,20 +13,15 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; -use Sylius\Component\Core\Model\OrderInterface; -use Sylius\Component\Core\Model\PaymentInterface; use Sylius\PayPalPlugin\Api\RefundPaymentApiInterface; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class RefundPaymentApiSpec extends ObjectBehavior { - function let(Client $client): void + function let(PayPalClientInterface $client): void { - $this->beConstructedWith($client, 'https://api.test-paypal.com/', 'PARTNER_ATTRIBUTION_ID'); + $this->beConstructedWith($client); } function it_implements_refund_order_api_interface(): void @@ -34,31 +29,12 @@ function it_implements_refund_order_api_interface(): void $this->shouldImplement(RefundPaymentApiInterface::class); } - function it_refunds_pay_pal_payment_with_given_id( - Client $client, - PaymentInterface $payment, - OrderInterface $order, - ResponseInterface $response, - StreamInterface $body - ): void { - $payment->getOrder()->willReturn($order); - $payment->getAmount()->willReturn(10000); - $order->getCurrencyCode()->willReturn('PLN'); - - $client->request( - 'POST', - 'https://api.test-paypal.com/v2/payments/captures/123123/refund', - Argument::that(function (array $options): bool { - return - $options['headers']['Authorization'] === 'Bearer TOKEN' && - $options['headers']['PayPal-Partner-Attribution-Id'] === 'PARTNER_ATTRIBUTION_ID' && - $options['headers']['Content-Type'] === 'application/json' && - is_string($options['headers']['PayPal-Request-Id']) - ; - }) - )->willReturn($response); - $response->getBody()->willReturn($body); - $body->getContents()->willReturn('{"status": "COMPLETED", "id": "123123"}'); + function it_refunds_pay_pal_payment_with_given_id(PayPalClientInterface $client): void + { + $client + ->post('v2/payments/captures/123123/refund', 'TOKEN') + ->willReturn(['status' => 'COMPLETED', 'id' => '123123']) + ; $this->refund('TOKEN', '123123')->shouldReturn(['status' => 'COMPLETED', 'id' => '123123']); } diff --git a/src/Api/CompleteOrderApi.php b/src/Api/CompleteOrderApi.php index a9124134..f563c430 100644 --- a/src/Api/CompleteOrderApi.php +++ b/src/Api/CompleteOrderApi.php @@ -13,41 +13,20 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class CompleteOrderApi implements CompleteOrderApiInterface { - /** @var Client */ + /** @var PayPalClientInterface */ private $client; - /** @var string */ - private $baseUrl; - - /** @var string */ - private $partnerAttributionId; - - public function __construct(Client $client, string $baseUrl, string $partnerAttributionId) + public function __construct(PayPalClientInterface $client) { $this->client = $client; - $this->baseUrl = $baseUrl; - $this->partnerAttributionId = $partnerAttributionId; } public function complete(string $token, string $orderId): array { - $response = $this->client->request( - 'POST', - sprintf('%sv2/checkout/orders/%s/capture', $this->baseUrl, $orderId), - [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'Prefer' => 'return=representation', - 'PayPal-Partner-Attribution-Id' => $this->partnerAttributionId, - 'Content-Type' => 'application/json', - ], - ] - ); - - return (array) json_decode($response->getBody()->getContents(), true); + return $this->client->post(sprintf('v2/checkout/orders/%s/capture', $orderId), $token); } } diff --git a/src/Api/CreateOrderApi.php b/src/Api/CreateOrderApi.php index 316aad3b..72fba126 100644 --- a/src/Api/CreateOrderApi.php +++ b/src/Api/CreateOrderApi.php @@ -13,29 +13,21 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; use Webmozart\Assert\Assert; final class CreateOrderApi implements CreateOrderApiInterface { - /** @var Client */ + /** @var PayPalClientInterface */ private $client; - /** @var string */ - private $baseUrl; - - /** @var string */ - private $partnerAttributionId; - - public function __construct(Client $client, string $baseUrl, string $partnerAttributionId) + public function __construct(PayPalClientInterface $client) { $this->client = $client; - $this->baseUrl = $baseUrl; - $this->partnerAttributionId = $partnerAttributionId; } public function create(string $token, PaymentInterface $payment): array @@ -93,20 +85,6 @@ public function create(string $token, PaymentInterface $payment): array ]; } - $response = $this->client->request( - 'POST', - $this->baseUrl . 'v2/checkout/orders', - [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => $this->partnerAttributionId, - ], - 'json' => $data, - ] - ); - - return (array) json_decode($response->getBody()->getContents(), true); + return $this->client->post('v2/checkout/orders', $token, $data); } } diff --git a/src/Api/OrderDetailsApi.php b/src/Api/OrderDetailsApi.php index 23a1b029..61ca617f 100644 --- a/src/Api/OrderDetailsApi.php +++ b/src/Api/OrderDetailsApi.php @@ -4,40 +4,20 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class OrderDetailsApi implements OrderDetailsApiInterface { - /** @var Client */ + /** @var PayPalClientInterface */ private $client; - /** @var string */ - private $baseUrl; - - /** @var string */ - private $partnerAttributionId; - - public function __construct(Client $client, string $baseUrl, string $partnerAttributionId) + public function __construct(PayPalClientInterface $client) { $this->client = $client; - $this->baseUrl = $baseUrl; - $this->partnerAttributionId = $partnerAttributionId; } public function get(string $token, string $orderId): array { - $response = $this->client->request( - 'GET', - sprintf('%sv2/checkout/orders/%s', $this->baseUrl, $orderId), [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => $this->partnerAttributionId, - ], - ] - ); - - return (array) json_decode($response->getBody()->getContents(), true); + return $this->client->get(sprintf('v2/checkout/orders/%s', $orderId), $token); } } diff --git a/src/Api/RefundPaymentApi.php b/src/Api/RefundPaymentApi.php index 018b8dbb..149c9fe1 100644 --- a/src/Api/RefundPaymentApi.php +++ b/src/Api/RefundPaymentApi.php @@ -13,42 +13,20 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; -use Ramsey\Uuid\Uuid; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class RefundPaymentApi implements RefundPaymentApiInterface { - /** @var Client */ + /** @var PayPalClientInterface */ private $client; - /** @var string */ - private $baseUrl; - - /** @var string */ - private $partnerAttributionId; - - public function __construct(Client $client, string $baseUrl, string $partnerAttributionId) + public function __construct(PayPalClientInterface $client) { $this->client = $client; - $this->baseUrl = $baseUrl; - $this->partnerAttributionId = $partnerAttributionId; } public function refund(string $token, string $paymentId): array { - $response = $this->client->request( - 'POST', - sprintf('%sv2/payments/captures/%s/refund', $this->baseUrl, $paymentId), - [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'PayPal-Partner-Attribution-Id' => $this->partnerAttributionId, - 'Content-Type' => 'application/json', - 'PayPal-Request-Id' => Uuid::uuid4()->toString(), - ], - ] - ); - - return (array) json_decode($response->getBody()->getContents(), true); + return $this->client->post(sprintf('v2/payments/captures/%s/refund', $paymentId), $token); } } diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php index 74f4bc72..88293744 100644 --- a/src/Client/PayPalClient.php +++ b/src/Client/PayPalClient.php @@ -40,7 +40,7 @@ public function get(string $url, string $token): array return $this->request('GET', $url, $token); } - public function post(string $url, string $token, array $data): array + public function post(string $url, string $token, array $data = null): array { return $this->request('POST', $url, $token, $data); } diff --git a/src/Client/PayPalClientInterface.php b/src/Client/PayPalClientInterface.php index 202bbe97..c1dc5469 100644 --- a/src/Client/PayPalClientInterface.php +++ b/src/Client/PayPalClientInterface.php @@ -8,5 +8,5 @@ interface PayPalClientInterface { public function get(string $url, string $token): array; - public function post(string $url, string $token, array $data): array; + public function post(string $url, string $token, array $data = null): array; } diff --git a/src/Resources/config/services/api.xml b/src/Resources/config/services/api.xml index ea99612f..71b97942 100644 --- a/src/Resources/config/services/api.xml +++ b/src/Resources/config/services/api.xml @@ -23,27 +23,21 @@ id="Sylius\PayPalPlugin\Api\CompleteOrderApiInterface" class="Sylius\PayPalPlugin\Api\CompleteOrderApi" > - - %env(resolve:PAYPAL_API_BASE_URL)% - %env(resolve:PAYPAL_TRACKING_ID)% + - - %env(resolve:PAYPAL_API_BASE_URL)% - %env(resolve:PAYPAL_TRACKING_ID)%) + - - %env(resolve:PAYPAL_API_BASE_URL)% - %env(resolve:PAYPAL_TRACKING_ID)% + - - %env(resolve:PAYPAL_API_BASE_URL)% - %env(resolve:PAYPAL_TRACKING_ID)% + From 22189f0de339217d88127873e670025d49a83dcf Mon Sep 17 00:00:00 2001 From: Mateusz Zalewski Date: Tue, 25 Aug 2020 16:16:17 +0200 Subject: [PATCH 3/5] Handle PATCH requests in the new PayPal client --- spec/Api/UpdateOrderApiSpec.php | 59 ++++++------------------- spec/Client/PayPalClientSpec.php | 65 ++++++++++++++++++++++++++++ src/Api/UpdateOrderApi.php | 41 +++++------------- src/Api/UpdateOrderApiInterface.php | 3 -- src/Client/PayPalClient.php | 10 ++++- src/Client/PayPalClientInterface.php | 2 + 6 files changed, 99 insertions(+), 81 deletions(-) diff --git a/spec/Api/UpdateOrderApiSpec.php b/spec/Api/UpdateOrderApiSpec.php index 066ced67..48e2b977 100644 --- a/spec/Api/UpdateOrderApiSpec.php +++ b/spec/Api/UpdateOrderApiSpec.php @@ -13,18 +13,16 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Psr\Http\Message\ResponseInterface; use Sylius\PayPalPlugin\Api\UpdateOrderApiInterface; -use Sylius\PayPalPlugin\Exception\PayPalOrderUpdateException; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class UpdateOrderApiSpec extends ObjectBehavior { - function let(Client $client): void + function let(PayPalClientInterface $client): void { - $this->beConstructedWith($client, 'https://api.test-paypal.com/', 'PARTNER-ATTRIBUTION-ID'); + $this->beConstructedWith($client); } function it_implements_update_order_api_interface(): void @@ -32,52 +30,21 @@ function it_implements_update_order_api_interface(): void $this->shouldImplement(UpdateOrderApiInterface::class); } - function it_updates_pay_pal_order_with_given_new_total( - Client $client, - ResponseInterface $response - ): void { - $client->request( - 'PATCH', - 'https://api.test-paypal.com/v2/checkout/orders/ORDER-ID', + function it_updates_pay_pal_order_with_given_new_total(PayPalClientInterface $client): void + { + $client->patch( + 'v2/checkout/orders/ORDER-ID', + 'TOKEN', Argument::that(function (array $data): bool { return - $data['headers']['Authorization'] === 'Bearer TOKEN' && - $data['headers']['PayPal-Partner-Attribution-Id'] === 'PARTNER-ATTRIBUTION-ID' && - $data['json'][0]['op'] === 'replace' && - $data['json'][0]['path'] === '/purchase_units/@reference_id==\'default\'/amount' && - $data['json'][0]['value']['value'] === '11.22' && - $data['json'][0]['value']['currency_code'] === 'USD' + $data[0]['op'] === 'replace' && + $data[0]['path'] === '/purchase_units/@reference_id==\'default\'/amount' && + $data[0]['value']['value'] === '11.22' && + $data[0]['value']['currency_code'] === 'USD' ; }) - )->willReturn($response); - $response->getStatusCode()->willReturn(204); + )->shouldBeCalled(); $this->update('TOKEN', 'ORDER-ID', '11.22', 'USD'); } - - function it_throws_an_exception_if_update_is_not_successful( - Client $client, - ResponseInterface $response - ): void { - $client->request( - 'PATCH', - 'https://api.test-paypal.com/v2/checkout/orders/ORDER-ID', - Argument::that(function (array $data): bool { - return - $data['headers']['Authorization'] === 'Bearer TOKEN' && - $data['headers']['PayPal-Partner-Attribution-Id'] === 'PARTNER-ATTRIBUTION-ID' && - $data['json'][0]['op'] === 'replace' && - $data['json'][0]['path'] === '/purchase_units/@reference_id==\'default\'/amount' && - $data['json'][0]['value']['value'] === '11.22' && - $data['json'][0]['value']['currency_code'] === 'USD' - ; - }) - )->willReturn($response); - $response->getStatusCode()->willReturn(500); - - $this - ->shouldThrow(PayPalOrderUpdateException::class) - ->during('update', ['TOKEN', 'ORDER-ID', '11.22', 'USD']) - ; - } } diff --git a/spec/Client/PayPalClientSpec.php b/spec/Client/PayPalClientSpec.php index 1df97b4f..142afe41 100644 --- a/spec/Client/PayPalClientSpec.php +++ b/spec/Client/PayPalClientSpec.php @@ -154,4 +154,69 @@ function it_logs_debug_id_from_failed_post_request( ->shouldReturn(['status' => 'FAILED', 'debug_id' => '123123']) ; } + + function it_calls_patch_request_on_paypal_api( + ClientInterface $client, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], + ] + )->willReturn($response); + $response->getStatusCode()->willReturn(200); + $response->getBody()->willReturn($body); + $body->getContents()->willReturn('{"status": "OK", "id": "123123"}'); + + $this + ->patch('v2/patch-request/123123', 'TOKEN', ['parameter' => 'value', 'another_parameter' => 'another_value']) + ->shouldReturn(['status' => 'OK', 'id' => '123123']) + ; + } + + function it_logs_debug_id_from_failed_patch_request( + ClientInterface $client, + LoggerInterface $logger, + RequestException $exception, + ResponseInterface $response, + StreamInterface $body + ): void { + $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' => 'sylius-ppcp4p-bn-code', + ], + 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], + ] + )->willThrow($exception->getWrappedObject()); + + $exception->getResponse()->willReturn($response); + $response->getBody()->willReturn($body); + $response->getStatusCode()->willReturn(400); + $body->getContents()->willReturn('{"status": "FAILED", "debug_id": "123123"}'); + + $logger + ->error('PATCH request to "https://test-api.paypal.com/v2/patch-request/123123" failed with debug ID 123123') + ->shouldBeCalled() + ; + + $this + ->patch('v2/patch-request/123123', 'TOKEN', ['parameter' => 'value', 'another_parameter' => 'another_value']) + ->shouldReturn(['status' => 'FAILED', 'debug_id' => '123123']) + ; + } } diff --git a/src/Api/UpdateOrderApi.php b/src/Api/UpdateOrderApi.php index e38aab2f..970a582f 100644 --- a/src/Api/UpdateOrderApi.php +++ b/src/Api/UpdateOrderApi.php @@ -13,51 +13,30 @@ namespace Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; -use Sylius\PayPalPlugin\Exception\PayPalOrderUpdateException; +use Sylius\PayPalPlugin\Client\PayPalClientInterface; final class UpdateOrderApi implements UpdateOrderApiInterface { - /** @var Client */ + /** @var PayPalClientInterface */ private $client; - /** @var string */ - private $baseUrl; - - /** @var string */ - private $partnerAttributionId; - - public function __construct(Client $client, string $baseUrl, string $partnerAttributionId) + public function __construct(PayPalClientInterface $client) { $this->client = $client; - $this->baseUrl = $baseUrl; - $this->partnerAttributionId = $partnerAttributionId; } public function update(string $token, string $orderId, string $newTotal, string $newCurrencyCode): void { - $response = $this->client->request( - 'PATCH', - sprintf('%sv2/checkout/orders/%s', $this->baseUrl, $orderId), + $this->client->patch( + sprintf('v2/checkout/orders/%s', $orderId), + $token, [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => $this->partnerAttributionId, - ], - 'json' => [ - [ - 'op' => 'replace', - 'path' => '/purchase_units/@reference_id==\'default\'/amount', - 'value' => ['value' => $newTotal, 'currency_code' => $newCurrencyCode], - ], + [ + 'op' => 'replace', + 'path' => '/purchase_units/@reference_id==\'default\'/amount', + 'value' => ['value' => $newTotal, 'currency_code' => $newCurrencyCode], ], ] ); - - if ($response->getStatusCode() !== 204) { - throw new PayPalOrderUpdateException(); - } } } diff --git a/src/Api/UpdateOrderApiInterface.php b/src/Api/UpdateOrderApiInterface.php index 7c9eb0f2..53b065e5 100644 --- a/src/Api/UpdateOrderApiInterface.php +++ b/src/Api/UpdateOrderApiInterface.php @@ -8,8 +8,5 @@ interface UpdateOrderApiInterface { - /** - * @throws PayPalOrderUpdateException - */ public function update(string $token, string $orderId, string $newTotal, string $newCurrencyCode): void; } diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php index 88293744..1aaad3d2 100644 --- a/src/Client/PayPalClient.php +++ b/src/Client/PayPalClient.php @@ -45,6 +45,11 @@ public function post(string $url, string $token, array $data = null): array return $this->request('POST', $url, $token, $data); } + public function patch(string $url, string $token, array $data = null): array + { + return $this->request('PATCH', $url, $token, $data); + } + private function request(string $method, string $url, string $token, array $data = null): array { $options = [ @@ -70,7 +75,10 @@ private function request(string $method, string $url, string $token, array $data $content = (array) json_decode($response->getBody()->getContents(), true); - if ($response->getStatusCode() !== 200 && isset($content['debug_id'])) { + if ( + (!in_array($response->getStatusCode(), [200, 204])) && + isset($content['debug_id']) + ) { $this ->logger ->error(sprintf('%s request to "%s" failed with debug ID %s', $method, $fullUrl, $content['debug_id'])) diff --git a/src/Client/PayPalClientInterface.php b/src/Client/PayPalClientInterface.php index c1dc5469..ebf62304 100644 --- a/src/Client/PayPalClientInterface.php +++ b/src/Client/PayPalClientInterface.php @@ -9,4 +9,6 @@ interface PayPalClientInterface public function get(string $url, string $token): array; public function post(string $url, string $token, array $data = null): array; + + public function patch(string $url, string $token, array $data = null): array; } From 6b95950033221f2e0ef6d339f6dc67bc33f74f6b Mon Sep 17 00:00:00 2001 From: Mateusz Zalewski Date: Tue, 25 Aug 2020 16:18:32 +0200 Subject: [PATCH 4/5] CS fixes --- spec/Api/CompleteOrderApiSpec.php | 3 --- spec/Api/CreateOrderApiSpec.php | 3 --- src/Api/UpdateOrderApiInterface.php | 2 -- src/Client/PayPalClient.php | 7 +++++-- src/Resources/config/services/api.xml | 4 +--- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/spec/Api/CompleteOrderApiSpec.php b/spec/Api/CompleteOrderApiSpec.php index 406a7299..aa686f88 100644 --- a/spec/Api/CompleteOrderApiSpec.php +++ b/spec/Api/CompleteOrderApiSpec.php @@ -13,10 +13,7 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use PhpSpec\ObjectBehavior; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\PayPalPlugin\Api\CompleteOrderApiInterface; diff --git a/spec/Api/CreateOrderApiSpec.php b/spec/Api/CreateOrderApiSpec.php index ee4915dc..d68bd8cf 100644 --- a/spec/Api/CreateOrderApiSpec.php +++ b/spec/Api/CreateOrderApiSpec.php @@ -13,12 +13,9 @@ namespace spec\Sylius\PayPalPlugin\Api; -use GuzzleHttp\Client; use Payum\Core\Model\GatewayConfigInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; use Sylius\Component\Core\Model\AddressInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; diff --git a/src/Api/UpdateOrderApiInterface.php b/src/Api/UpdateOrderApiInterface.php index 53b065e5..9669dbb6 100644 --- a/src/Api/UpdateOrderApiInterface.php +++ b/src/Api/UpdateOrderApiInterface.php @@ -4,8 +4,6 @@ namespace Sylius\PayPalPlugin\Api; -use Sylius\PayPalPlugin\Exception\PayPalOrderUpdateException; - interface UpdateOrderApiInterface { public function update(string $token, string $orderId, string $newTotal, string $newCurrencyCode): void; diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php index 1aaad3d2..b7c1c9fb 100644 --- a/src/Client/PayPalClient.php +++ b/src/Client/PayPalClient.php @@ -15,6 +15,7 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; +use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; final class PayPalClient implements PayPalClientInterface @@ -65,11 +66,13 @@ private function request(string $method, string $url, string $token, array $data $options['json'] = $data; } - $fullUrl = $this->baseUrl.$url; + $fullUrl = $this->baseUrl . $url; try { + /** @var ResponseInterface $response */ $response = $this->client->request($method, $fullUrl, $options); } catch (RequestException $exception) { + /** @var ResponseInterface $response */ $response = $exception->getResponse(); } @@ -81,7 +84,7 @@ private function request(string $method, string $url, string $token, array $data ) { $this ->logger - ->error(sprintf('%s request to "%s" failed with debug ID %s', $method, $fullUrl, $content['debug_id'])) + ->error(sprintf('%s request to "%s" failed with debug ID %s', $method, $fullUrl, (string) $content['debug_id'])) ; } diff --git a/src/Resources/config/services/api.xml b/src/Resources/config/services/api.xml index 71b97942..7fa841d0 100644 --- a/src/Resources/config/services/api.xml +++ b/src/Resources/config/services/api.xml @@ -44,9 +44,7 @@ id="Sylius\PayPalPlugin\Api\UpdateOrderApiInterface" class="Sylius\PayPalPlugin\Api\UpdateOrderApi" > - - %env(resolve:PAYPAL_API_BASE_URL)% - %env(resolve:PAYPAL_TRACKING_ID)% + Date: Mon, 31 Aug 2020 11:23:46 +0200 Subject: [PATCH 5/5] Use tracking id from env variable --- spec/Client/PayPalClientSpec.php | 14 +++++++------- src/Client/PayPalClient.php | 8 ++++++-- src/Resources/config/services/api.xml | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/spec/Client/PayPalClientSpec.php b/spec/Client/PayPalClientSpec.php index 142afe41..b4d0ee8b 100644 --- a/spec/Client/PayPalClientSpec.php +++ b/spec/Client/PayPalClientSpec.php @@ -25,7 +25,7 @@ final class PayPalClientSpec extends ObjectBehavior { function let(ClientInterface $client, LoggerInterface $logger): void { - $this->beConstructedWith($client, $logger, 'https://test-api.paypal.com/'); + $this->beConstructedWith($client, $logger, 'https://test-api.paypal.com/', 'TRACKING-ID'); } function it_implements_pay_pal_client_interface(): void @@ -46,7 +46,7 @@ function it_calls_get_request_on_paypal_api( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], ] )->willReturn($response); @@ -72,7 +72,7 @@ function it_logs_debug_id_from_failed_get_request( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], ] )->willThrow($exception->getWrappedObject()); @@ -103,7 +103,7 @@ function it_calls_post_request_on_paypal_api( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], ] @@ -133,7 +133,7 @@ function it_logs_debug_id_from_failed_post_request( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], ] @@ -168,7 +168,7 @@ function it_calls_patch_request_on_paypal_api( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], ] @@ -198,7 +198,7 @@ function it_logs_debug_id_from_failed_patch_request( 'Authorization' => 'Bearer TOKEN', 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => 'TRACKING-ID', ], 'json' => ['parameter' => 'value', 'another_parameter' => 'another_value'], ] diff --git a/src/Client/PayPalClient.php b/src/Client/PayPalClient.php index b7c1c9fb..2e9da1cb 100644 --- a/src/Client/PayPalClient.php +++ b/src/Client/PayPalClient.php @@ -29,11 +29,15 @@ final class PayPalClient implements PayPalClientInterface /** @var string */ private $baseUrl; - public function __construct(ClientInterface $client, LoggerInterface $logger, string $baseUrl) + /** @var string */ + private $trackingId; + + public function __construct(ClientInterface $client, LoggerInterface $logger, string $baseUrl, string $trackingId) { $this->client = $client; $this->logger = $logger; $this->baseUrl = $baseUrl; + $this->trackingId = $trackingId; } public function get(string $url, string $token): array @@ -58,7 +62,7 @@ private function request(string $method, string $url, string $token, array $data 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', 'Accept' => 'application/json', - 'PayPal-Partner-Attribution-Id' => 'sylius-ppcp4p-bn-code', + 'PayPal-Partner-Attribution-Id' => $this->trackingId, ], ]; diff --git a/src/Resources/config/services/api.xml b/src/Resources/config/services/api.xml index 7fa841d0..328df55e 100644 --- a/src/Resources/config/services/api.xml +++ b/src/Resources/config/services/api.xml @@ -9,6 +9,7 @@ %env(resolve:PAYPAL_API_BASE_URL)% + %env(resolve:PAYPAL_TRACKING_ID)%