From bef4abe5ab051fbfcf9468f0dae909eb2ce26a33 Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Tue, 12 Mar 2019 15:01:47 +0100 Subject: [PATCH 01/14] Add support of HTTP PUT method. --- src/Klarna/Rest/Resource.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Klarna/Rest/Resource.php b/src/Klarna/Rest/Resource.php index 183f147..0b00d3f 100644 --- a/src/Klarna/Rest/Resource.php +++ b/src/Klarna/Rest/Resource.php @@ -228,6 +228,23 @@ protected function patch($url, array $data) return $this->request('PATCH', $url, ['Content-Type' => 'application/json'], json_encode($data)); } + /** + * Sends a HTTP PUT request to the specified url. + * + * @param string $url Request destination + * @param array $data Data to be JSON encoded + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \LogicException When Guzzle cannot populate the response + * + * @return ResponseValidator + */ + protected function put($url, array $data) + { + return $this->request('PUT', $url, ['Content-Type' => 'application/json'], json_encode($data)); + } + /** * Sends a HTTP POST request to the specified url. * From 1bfc18b0189a2c60a0ffdf402d8ab57ac9ef2ecd Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Tue, 12 Mar 2019 15:02:02 +0100 Subject: [PATCH 02/14] Add support of Instant Shopping API. --- .../Rest/InstantShopping/ButtonKeys.php | 129 ++++++++++++++++++ src/Klarna/Rest/InstantShopping/Orders.php | 121 ++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 src/Klarna/Rest/InstantShopping/ButtonKeys.php create mode 100644 src/Klarna/Rest/InstantShopping/Orders.php diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php new file mode 100644 index 0000000..199768f --- /dev/null +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -0,0 +1,129 @@ +setLocation(self::$path . "/{$key}"); + $this[static::ID_FIELD] = $key; + } + } + + /** + * Creates a button key based on setup options. + * + * @param array $data Creation data + * + * @see https://developers.klarna.com/api/#instant-shopping-api-create-a-button-key-based-on-setup-options + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException If the API replies with an unexpected response + * @throws \LogicException When Guzzle cannot populate the response + * + * @return array Button properties + */ + public function create(array $data) + { + $data = $this->post(self::$path, $data) + ->status('201') + ->contentType('application/json') + ->getJson(); + + return $data; + } + + /** + * Updates the setup options for a specific button key. + * + * @param array $data Update data + * + * @see https://developers.klarna.com/api/#instant-shopping-api-update-the-setup-options-for-a-specific-button-key + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException If the API replies with an unexpected response + * @throws \LogicException When Guzzle cannot populate the response + * + * @return array Button properties + */ + public function update(array $data) + { + $data = $this->put($this->getLocation(), $data) + ->status('200') + ->contentType('application/json') + ->getJson(); + + return $data; + } + + /** + * See the setup options for a specific button key. + * + * @see https://developers.klarna.com/api/#instant-shopping-api-see-the-setup-options-for-a-specific-button-key + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException If key was not specified when creating a resource + * @throws \RuntimeException On an unexpected API response + * @throws \RuntimeException If the response content type is not JSON + * @throws \InvalidArgumentException If the JSON cannot be parsed + * @throws \LogicException When Guzzle cannot populate the response + * + * @return self + */ + public function retrieve() + { + if (empty($this[static::ID_FIELD])) { + throw new \RuntimeException(static::ID_FIELD . ' property is not defined'); + } + + return $this->fetch(); + } +} diff --git a/src/Klarna/Rest/InstantShopping/Orders.php b/src/Klarna/Rest/InstantShopping/Orders.php new file mode 100644 index 0000000..1b0ebc9 --- /dev/null +++ b/src/Klarna/Rest/InstantShopping/Orders.php @@ -0,0 +1,121 @@ +setLocation(self::$path . "/{$authorizationToken}"); + $this[static::ID_FIELD] = $authorizationToken; + } + + /** + * Retrieves an authorized order based on the authorization token. + * + * @see https://developers.klarna.com/api/#instant-shopping-api-retrieves-an-authorized-order-based-on-the-authorization-token + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException On an unexpected API response + * @throws \RuntimeException If the response content type is not JSON + * @throws \InvalidArgumentException If the JSON cannot be parsed + * @throws \LogicException When Guzzle cannot populate the response + * + * @return self + */ + public function retrieve() + { + return $this->fetch(); + } + + /** + * Declines an authorized order identified by the authorization token. + * + * @param array $data Decline data + * + * @see https://developers.klarna.com/api/#instant-shopping-api-declines-an-authorized-order-identified-by-the-authorization-token + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException If the location header is missing + * @throws \RuntimeException If the API replies with an unexpected response + * @throws \LogicException When Guzzle cannot populate the response + * + * @return self + */ + public function decline(array $data = null) + { + $this->delete($this->getLocation(), $data) + ->status('204'); + + return $this; + } + + /** + * Approves the authorized order and places an order identified by the authorization token. + * + * @see https://developers.klarna.com/api/#instant-shopping-api-approve-the-authorized-order-and-place-an-order-identified-by-the-authorization-token + * + * @param array $data Order data + * + * @throws ConnectorException When the API replies with an error response + * @throws RequestException When an error is encountered + * @throws \RuntimeException On an unexpected API response + * @throws \RuntimeException If the response content type is not JSON + * @throws \InvalidArgumentException If the JSON cannot be parsed + * @throws \LogicException When Guzzle cannot populate the response + * + * @return self + */ + public function approve(array $data) + { + $this->post($this->getLocation() . '/orders', $data) + ->status('200'); + + return $this; + } +} From c4265f6faeff475fcee306874ea3e5e7f339a6ed Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Tue, 12 Mar 2019 17:19:32 +0100 Subject: [PATCH 03/14] InstantShopping: Cover API with unit tests. --- .../Rest/InstantShopping/ButtonKeys.php | 23 +-- src/Klarna/Rest/InstantShopping/Orders.php | 9 +- .../InstantShopping/ButtonKeysTest.php | 165 ++++++++++++++++++ .../Component/InstantShopping/OrdersTest.php | 130 ++++++++++++++ 4 files changed, 311 insertions(+), 16 deletions(-) create mode 100644 tests/Component/InstantShopping/ButtonKeysTest.php create mode 100644 tests/Component/InstantShopping/OrdersTest.php diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php index 199768f..1fe35f4 100644 --- a/src/Klarna/Rest/InstantShopping/ButtonKeys.php +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -32,7 +32,7 @@ class ButtonKeys extends Resource /** * {@inheritDoc} */ - const ID_FIELD = 'key'; + const ID_FIELD = 'button_key'; /** * {@inheritDoc} @@ -43,15 +43,16 @@ class ButtonKeys extends Resource * Constructs a ButtonKey instance. * * @param Connector $connector HTTP transport connector + * @param string $buttonKey Button identifier * @param string $key Button key based on setup options */ - public function __construct(Connector $connector, $key = null) + public function __construct(Connector $connector, $buttonKey = null) { parent::__construct($connector); - if ($sessionId !== null) { - $this->setLocation(self::$path . "/{$key}"); - $this[static::ID_FIELD] = $key; + if ($buttonKey !== null) { + $this->setLocation(self::$path . "/{$buttonKey}"); + $this[static::ID_FIELD] = $buttonKey; } } @@ -73,10 +74,12 @@ public function create(array $data) { $data = $this->post(self::$path, $data) ->status('201') - ->contentType('application/json') - ->getJson(); + ->contentType('application/json'); - return $data; + $url = $data->getLocation(); + $this->setLocation($url); + + return $data->getJson(); } /** @@ -95,12 +98,10 @@ public function create(array $data) */ public function update(array $data) { - $data = $this->put($this->getLocation(), $data) + return $this->put($this->getLocation(), $data) ->status('200') ->contentType('application/json') ->getJson(); - - return $data; } /** diff --git a/src/Klarna/Rest/InstantShopping/Orders.php b/src/Klarna/Rest/InstantShopping/Orders.php index 1b0ebc9..464c9f8 100644 --- a/src/Klarna/Rest/InstantShopping/Orders.php +++ b/src/Klarna/Rest/InstantShopping/Orders.php @@ -109,13 +109,12 @@ public function decline(array $data = null) * @throws \InvalidArgumentException If the JSON cannot be parsed * @throws \LogicException When Guzzle cannot populate the response * - * @return self + * @return array approving status */ public function approve(array $data) { - $this->post($this->getLocation() . '/orders', $data) - ->status('200'); - - return $this; + return $this->post($this->getLocation() . '/orders', $data) + ->status('200') + ->getJson(); } } diff --git a/tests/Component/InstantShopping/ButtonKeysTest.php b/tests/Component/InstantShopping/ButtonKeysTest.php new file mode 100644 index 0000000..13cd3e1 --- /dev/null +++ b/tests/Component/InstantShopping/ButtonKeysTest.php @@ -0,0 +1,165 @@ +mock->append( + new Response( + 201, + [ + 'Content-Type' => 'application/json', + 'Location' => 'https://example.com/some-url', + ], + $json + ) + ); + + $button = new ButtonKeys($this->connector); + + $data = $button->create([ + 'data' => 'sent in' + ]); + + $this->assertEquals('123-key', $data['button_key']); + $this->assertTrue($data['disabled']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('POST', $request->getMethod()); + $this->assertEquals('/instantshopping/v1/buttons', $request->getUri()->getPath()); + + $this->assertAuthorization($request); + } + + /** + * Make sure that the request sent is correct when updating. + * + * @return void + */ + public function testUpdate() + { + $json = <<mock->append( + new Response( + 200, + ['Content-Type' => 'application/json'], + $json + ) + ); + + $button = new ButtonKeys($this->connector, 'button-id-123456'); + $data = $button->update(['data' => 'sent in']); + + $this->assertEquals('123-key', $data['button_key']); + $this->assertTrue($data['disabled']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals( + '/instantshopping/v1/buttons/button-id-123456', + $request->getUri()->getPath() + ); + $this->assertEquals('{"data":"sent in"}', strval($request->getBody())); + + + $this->assertAuthorization($request); + } + + /** + * Make sure that the request sent is correct when retrieving. + * + * @return void + */ + public function testRetrieve() + { + $json = <<mock->append( + new Response( + 200, + ['Content-Type' => 'application/json'], + $json + ) + ); + + $button = new ButtonKeys($this->connector, 'button-id-123456'); + $button->retrieve(); + + $this->assertEquals('123-key', $button['button_key']); + $this->assertEquals('123-key', $button->getId()); + $this->assertTrue($button['disabled']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals( + '/instantshopping/v1/buttons/button-id-123456', + $request->getUri()->getPath() + ); + + $this->assertAuthorization($request); + } + + /** + * @expectedException RuntimeException + */ + public function testRetrieveException() + { + $button = new ButtonKeys($this->connector); + $button->retrieve(); + } +} diff --git a/tests/Component/InstantShopping/OrdersTest.php b/tests/Component/InstantShopping/OrdersTest.php new file mode 100644 index 0000000..da7aa40 --- /dev/null +++ b/tests/Component/InstantShopping/OrdersTest.php @@ -0,0 +1,130 @@ +mock->append( + new Response( + 200, + ['Content-Type' => 'application/json'], + $json + ) + ); + + $order = new Orders($this->connector, 'auth-token-123456'); + $this->assertEquals('auth-token-123456', $order->getId()); + + $order->retrieve(); + + $this->assertEquals('f3392f8b-6116-4073-ab96-e330819e2c07', $order['order_id']); + $this->assertEquals(50000, $order['order_amount']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/instantshopping/v1/authorizations/auth-token-123456', $request->getUri()->getPath()); + + $this->assertAuthorization($request); + } + + /** + * Make sure that the request sent is correct when declining an order. + * + * @return void + */ + public function testDeclines() + { + $this->mock->append(new Response(204)); + + $order = new Orders($this->connector, 'auth-token-123456'); + $order->decline(['data' => 'sent in']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals( + '/instantshopping/v1/authorizations/auth-token-123456', + $request->getUri()->getPath() + ); + $this->assertEquals('{"data":"sent in"}', strval($request->getBody())); + + + $this->assertAuthorization($request); + } + + /** + * Make sure that the request sent and retrieved data is correct. + * + * @return void + */ + public function testApprove() + { + $json = <<mock->append( + new Response( + 200, + ['Content-Type' => 'application/json'], + $json + ) + ); + + $order = new Orders($this->connector, 'auth-token-123456'); + $data = $order->approve([ + 'data' => 'sent in' + ]); + + $this->assertEquals('45aa52f387871e3a210645d4', $data['order_id']); + $this->assertEquals('REJECTED', $data['fraud_status']); + + $request = $this->mock->getLastRequest(); + $this->assertEquals('POST', $request->getMethod()); + $this->assertEquals('/instantshopping/v1/authorizations/auth-token-123456/orders', $request->getUri()->getPath()); + $this->assertEquals('{"data":"sent in"}', strval($request->getBody())); + + $this->assertAuthorization($request); + } +} From e484934fe54e0992d65d0f7c09f20915e3a2e28d Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 14:54:59 +0100 Subject: [PATCH 04/14] Connector: Avoid throwing exceptions if API serivce did not return proper error. --- .../Exception/ConnectorException.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Klarna/Rest/Transport/Exception/ConnectorException.php b/src/Klarna/Rest/Transport/Exception/ConnectorException.php index 1a7e05a..2ed3ad1 100644 --- a/src/Klarna/Rest/Transport/Exception/ConnectorException.php +++ b/src/Klarna/Rest/Transport/Exception/ConnectorException.php @@ -58,6 +58,8 @@ public function __construct( array $data, RequestException $prev ) { + $data = self::setDefaultData($data); + $messages = implode(', ', $data['error_messages']); $serviceVersion = isset($data['service_version']) ? $data['service_version'] : ''; $message = "{$data['error_code']}: {$messages} (#{$data['correlation_id']})"; @@ -120,4 +122,21 @@ public function getResponse() { return $this->getPrevious()->getResponse(); } + + private static function setDefaultData($data) + { + print_r($data); + $default = [ + 'error_code' => 'UNDEFINED', + 'error_messages' => [], + 'correlation_id' => 'UNDEFINED', + ]; + + foreach ($default as $k => $v) { + if (!isset($data[$k])) { + $data[$k] = $v; + } + } + return $data; + } } From a7d9ae06b602addb7f23e3c9d935c6942446d5bc Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 14:56:11 +0100 Subject: [PATCH 05/14] Add InstantShopping Button key examples. --- .../ButtonKeys/create_button_key.php | 110 ++++++++++++++++++ .../ButtonKeys/see_button_key_options.php | 56 +++++++++ .../ButtonKeys/update_button_key.php | 80 +++++++++++++ .../Rest/InstantShopping/ButtonKeys.php | 5 + 4 files changed, 251 insertions(+) create mode 100644 docs/examples/InstantShoppingAPI/ButtonKeys/create_button_key.php create mode 100644 docs/examples/InstantShoppingAPI/ButtonKeys/see_button_key_options.php create mode 100644 docs/examples/InstantShoppingAPI/ButtonKeys/update_button_key.php diff --git a/docs/examples/InstantShoppingAPI/ButtonKeys/create_button_key.php b/docs/examples/InstantShoppingAPI/ButtonKeys/create_button_key.php new file mode 100644 index 0000000..3108d77 --- /dev/null +++ b/docs/examples/InstantShoppingAPI/ButtonKeys/create_button_key.php @@ -0,0 +1,110 @@ + 'John Doe', + 'merchant_urls' => [ + 'place_order' => 'https://example.com/place-callback', + 'push' => 'https://example.com/push-callback', + 'confirmation' => 'https://example.com/confirmation-callback', + 'terms' => 'https://example.com/terms-callback', + 'notification' => 'https://example.com/notification-callback', + 'update' => 'https://example.com/update-callback', + ], + 'purchase_currency' => 'EUR', + 'purchase_country' => 'DE', + 'billing_countries' => ["UK", "DE", "SE"], + 'shipping_countries' => ["UK", "DE", "SE"], + 'locale' => 'en-US', + 'order_amount' => 50000, + 'order_tax_amount' => 0, + 'order_lines' => [ + [ + 'name' => 'Red T-Shirt', + 'type' => 'physical', + 'reference' => '19-402-USA', + 'quantity' => 5, + 'quantity_unit' => 'pcs', + 'tax_rate' => 0, + 'total_amount' => 50000, + 'total_discount_amount' => 0, + 'total_tax_amount' => 0, + 'unit_price' => 10000, + 'product_url' => 'https://www.estore.com/products/f2a8d7e34', + 'image_url' => 'https://www.exampleobjects.com/logo.png', + 'product_identifiers' => + [ + 'category_path' => 'Electronics Store > Computers & Tablets > Desktops', + 'global_trade_item_number' => '735858293167', + 'manufacturer_part_number' => 'BOXNUC5CPYH', + 'brand' => 'Intel', + ], + ], + ], + 'shipping_options' => [ + [ + 'id' => 'my-shipping-id', + 'name' => 'Pickup Store', + 'description' => 'My custom description', + 'promo' => 'string', + 'price' => 10, + 'tax_amount' => 0, + 'tax_rate' => 0, + 'preselected' => true, + 'shipping_method' => 'PICKUPSTORE', + ], + ], + ]; + $button = $buttonsApi->create($data); + + echo 'Button has been successfully created' . PHP_EOL; + print_r($button); + +} catch (Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . "\n"; +} diff --git a/docs/examples/InstantShoppingAPI/ButtonKeys/see_button_key_options.php b/docs/examples/InstantShoppingAPI/ButtonKeys/see_button_key_options.php new file mode 100644 index 0000000..eebe5cf --- /dev/null +++ b/docs/examples/InstantShoppingAPI/ButtonKeys/see_button_key_options.php @@ -0,0 +1,56 @@ +retrieve(); + + print_r($button->getArrayCopy()); + +} catch (Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . "\n"; +} diff --git a/docs/examples/InstantShoppingAPI/ButtonKeys/update_button_key.php b/docs/examples/InstantShoppingAPI/ButtonKeys/update_button_key.php new file mode 100644 index 0000000..53dc7e0 --- /dev/null +++ b/docs/examples/InstantShoppingAPI/ButtonKeys/update_button_key.php @@ -0,0 +1,80 @@ + 'New name', + 'merchant_urls' => [ + 'place_order' => 'https://example.com/place-callback', + 'push' => 'https://example.com/push-callback', + 'confirmation' => 'https://example.com/confirmation-callback', + 'terms' => 'https://example.com/terms-callback', + 'notification' => 'https://example.com/notification-callback', + 'update' => 'https://example.com/update-callback', + ], + 'shipping_options' => [ + [ + 'id' => 'my-new-shipping-id', + 'name' => 'Priority delivery', + 'description' => '', + 'price' => 300, + 'tax_amount' => 0, + 'tax_rate' => 0, + 'preselected' => false, + 'shipping_method' => 'PRIME_DELIVERY', + ], + ], + ]; + $button = $buttonsApi->update($data); + + echo 'Button has been successfully updated' . PHP_EOL; + print_r($button); + +} catch (Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . "\n"; +} diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php index 1fe35f4..2a0499a 100644 --- a/src/Klarna/Rest/InstantShopping/ButtonKeys.php +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -92,12 +92,17 @@ public function create(array $data) * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered * @throws \RuntimeException If the API replies with an unexpected response + * @throws \RuntimeException If key was not specified when creating a resource * @throws \LogicException When Guzzle cannot populate the response * * @return array Button properties */ public function update(array $data) { + if (empty($this[static::ID_FIELD])) { + throw new \RuntimeException(static::ID_FIELD . ' property is not defined'); + } + return $this->put($this->getLocation(), $data) ->status('200') ->contentType('application/json') From 91d71bf5b5e62c7828098387ee0f271c4ad04a6b Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 16:08:13 +0100 Subject: [PATCH 06/14] InstantShopping: Add Orders examples. --- .../Orders/approve_order.php | 121 ++++++++++++++++++ .../Orders/decline_order.php | 61 +++++++++ .../Orders/retrieve_order.php | 55 ++++++++ 3 files changed, 237 insertions(+) create mode 100644 docs/examples/InstantShoppingAPI/Orders/approve_order.php create mode 100644 docs/examples/InstantShoppingAPI/Orders/decline_order.php create mode 100644 docs/examples/InstantShoppingAPI/Orders/retrieve_order.php diff --git a/docs/examples/InstantShoppingAPI/Orders/approve_order.php b/docs/examples/InstantShoppingAPI/Orders/approve_order.php new file mode 100644 index 0000000..ce06937 --- /dev/null +++ b/docs/examples/InstantShoppingAPI/Orders/approve_order.php @@ -0,0 +1,121 @@ + https://github.com/klarna/kco_rest_php/#api-credentials + * + * Make sure that your credentials belong to the right endpoint. If you have credentials for the US Playground, + * such credentials will not work for the EU Playground and you will get 401 Unauthorized exception. + */ +$merchantId = getenv('USERNAME') ?: 'K123456_abcd12345'; +$sharedSecret = getenv('PASSWORD') ?: 'sharedSecret'; +$authToken = getenv('AUTH_TOKEN') ?: 'authorization_token'; + +/* +EU_BASE_URL = 'https://api.klarna.com' +EU_TEST_BASE_URL = 'https://api.playground.klarna.com' +NA_BASE_URL = 'https://api-na.klarna.com' +NA_TEST_BASE_URL = 'https://api-na.playground.klarna.com' +*/ +$apiEndpoint = Klarna\Rest\Transport\ConnectorInterface::EU_TEST_BASE_URL; + +$connector = Klarna\Rest\Transport\Connector::create( + $merchantId, + $sharedSecret, + $apiEndpoint +); + +$order = [ + "order_id" => "f3392f8b-6116-4073-ab96-e330819e2c07", + "purchase_country" => "gb", + "purchase_currency" => "gbp", + "locale" => "en-gb", + "order_amount" => 10000, + "order_tax_amount" => 2000, + "billing_address" => [ + "given_name" => "Jane", + "family_name"=> "Doe", + "email"=> "jane-doe@example.com", + "title"=> "Ms", + "street_address"=> "Lombard St 10", + "street_address2"=> "Apt 214", + "postal_code"=> "90210", + "city"=> "Beverly Hills", + "region"=> "CA", + "phone"=> "333444555", + "country"=> "US" + ], + "order_lines" => [ + [ + "type" => "physical", + "reference" => "123050", + "name" => "Tomatoes", + "quantity" => 10, + "quantity_unit" => "kg", + "unit_price" => 600, + "tax_rate" => 2500, + "total_amount" => 6000, + "total_tax_amount" => 1200 + ], + [ + "type" => "physical", + "reference" => "543670", + "name" => "Bananas", + "quantity" => 1, + "quantity_unit" => "bag", + "unit_price" => 5000, + "tax_rate" => 2500, + "total_amount" => 4000, + "total_discount_amount" => 1000, + "total_tax_amount" => 800 + ] + ], + "merchant_urls" => [ + "terms" => "http://www.merchant.com/toc", + "checkout" => "http://www.merchant.com/checkout?klarna_order_id={checkout.order.id}", + "confirmation" => "http://www.merchant.com/thank-you?klarna_order_id={checkout.order.id}", + "push" => "http://www.merchant.com/create_order?klarna_order_id={checkout.order.id}" + ], + "customer" => [ + "date_of_birth" => "1995-10-20", + "title" => "Mr", + "gender" => "male", + "last_four_ssn" => "0512", + "national_identification_number" => "3108971100", + "type" => "person", + "vat_id" => "string", + "organization_registration_id" => "556737-0431", + "organization_entity_type" => "LIMITED_COMPANY" + ] +]; + +try { + $orderApi = new Klarna\Rest\InstantShopping\Orders($connector, $authToken); + $status = $orderApi->approve($order); + + echo 'The order has been approved' . PHP_EOL; + print_r($status); + +} catch (Exception $e) { + echo 'Caught exception => ' . $e->getMessage() . "\n"; +} diff --git a/docs/examples/InstantShoppingAPI/Orders/decline_order.php b/docs/examples/InstantShoppingAPI/Orders/decline_order.php new file mode 100644 index 0000000..8062d28 --- /dev/null +++ b/docs/examples/InstantShoppingAPI/Orders/decline_order.php @@ -0,0 +1,61 @@ + "https://example.com/rejected.html", + "deny_code" => "other", + "deny_message" => "You are not permitted to purchase this product", + ]; + $orderApi->decline($data); + echo 'The order has been declined'; + +} catch (Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . "\n"; +} diff --git a/docs/examples/InstantShoppingAPI/Orders/retrieve_order.php b/docs/examples/InstantShoppingAPI/Orders/retrieve_order.php new file mode 100644 index 0000000..046c1af --- /dev/null +++ b/docs/examples/InstantShoppingAPI/Orders/retrieve_order.php @@ -0,0 +1,55 @@ +retrieve(); + + print_r($order->getArrayCopy()); +} catch (Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . "\n"; +} From 7c6e09b679221b851c2e4059c145ab73085e5255 Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 16:09:55 +0100 Subject: [PATCH 07/14] Remove debugging. --- src/Klarna/Rest/Transport/Exception/ConnectorException.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Klarna/Rest/Transport/Exception/ConnectorException.php b/src/Klarna/Rest/Transport/Exception/ConnectorException.php index 2ed3ad1..4e55f5d 100644 --- a/src/Klarna/Rest/Transport/Exception/ConnectorException.php +++ b/src/Klarna/Rest/Transport/Exception/ConnectorException.php @@ -125,7 +125,6 @@ public function getResponse() private static function setDefaultData($data) { - print_r($data); $default = [ 'error_code' => 'UNDEFINED', 'error_messages' => [], From a1b0f487de555253d3411709b0499ef4ec189f9a Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 16:11:57 +0100 Subject: [PATCH 08/14] Refactoring: ConnectorException: Change variable names. --- .../Rest/Transport/Exception/ConnectorException.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Klarna/Rest/Transport/Exception/ConnectorException.php b/src/Klarna/Rest/Transport/Exception/ConnectorException.php index 4e55f5d..75482d5 100644 --- a/src/Klarna/Rest/Transport/Exception/ConnectorException.php +++ b/src/Klarna/Rest/Transport/Exception/ConnectorException.php @@ -125,15 +125,15 @@ public function getResponse() private static function setDefaultData($data) { - $default = [ + $defaults = [ 'error_code' => 'UNDEFINED', 'error_messages' => [], 'correlation_id' => 'UNDEFINED', ]; - foreach ($default as $k => $v) { - if (!isset($data[$k])) { - $data[$k] = $v; + foreach ($defaults as $field => $default) { + if (!isset($data[$field])) { + $data[$field] = $default; } } return $data; From 74633bd557a2fad15e9660d55486233489ba5748 Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Thu, 14 Mar 2019 16:39:07 +0100 Subject: [PATCH 09/14] Align with PSR-2 standard. --- src/Klarna/Rest/InstantShopping/Orders.php | 6 ------ tests/Component/InstantShopping/OrdersTest.php | 10 ++++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Klarna/Rest/InstantShopping/Orders.php b/src/Klarna/Rest/InstantShopping/Orders.php index 464c9f8..ca5f4fa 100644 --- a/src/Klarna/Rest/InstantShopping/Orders.php +++ b/src/Klarna/Rest/InstantShopping/Orders.php @@ -55,8 +55,6 @@ public function __construct(Connector $connector, $authorizationToken) /** * Retrieves an authorized order based on the authorization token. - * - * @see https://developers.klarna.com/api/#instant-shopping-api-retrieves-an-authorized-order-based-on-the-authorization-token * * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered @@ -77,8 +75,6 @@ public function retrieve() * * @param array $data Decline data * - * @see https://developers.klarna.com/api/#instant-shopping-api-declines-an-authorized-order-identified-by-the-authorization-token - * * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered * @throws \RuntimeException If the location header is missing @@ -98,8 +94,6 @@ public function decline(array $data = null) /** * Approves the authorized order and places an order identified by the authorization token. * - * @see https://developers.klarna.com/api/#instant-shopping-api-approve-the-authorized-order-and-place-an-order-identified-by-the-authorization-token - * * @param array $data Order data * * @throws ConnectorException When the API replies with an error response diff --git a/tests/Component/InstantShopping/OrdersTest.php b/tests/Component/InstantShopping/OrdersTest.php index da7aa40..0e272aa 100644 --- a/tests/Component/InstantShopping/OrdersTest.php +++ b/tests/Component/InstantShopping/OrdersTest.php @@ -61,7 +61,10 @@ public function testRetrive() $request = $this->mock->getLastRequest(); $this->assertEquals('GET', $request->getMethod()); - $this->assertEquals('/instantshopping/v1/authorizations/auth-token-123456', $request->getUri()->getPath()); + $this->assertEquals( + '/instantshopping/v1/authorizations/auth-token-123456', + $request->getUri()->getPath() + ); $this->assertAuthorization($request); } @@ -122,7 +125,10 @@ public function testApprove() $request = $this->mock->getLastRequest(); $this->assertEquals('POST', $request->getMethod()); - $this->assertEquals('/instantshopping/v1/authorizations/auth-token-123456/orders', $request->getUri()->getPath()); + $this->assertEquals( + '/instantshopping/v1/authorizations/auth-token-123456/orders', + $request->getUri()->getPath() + ); $this->assertEquals('{"data":"sent in"}', strval($request->getBody())); $this->assertAuthorization($request); From f1313f1e86fafb0eb90b782d9c0b463125f393b3 Mon Sep 17 00:00:00 2001 From: Georghios Skettos Date: Fri, 15 Mar 2019 10:55:35 +0100 Subject: [PATCH 10/14] Add editorconfig to adhere to PSR-1, PSR-2 standards --- .editorconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..86909dd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ + + +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# PSR-1 / PSR-2 Coding Style, https://www.php-fig.org/psr/psr-2/ +[*.php] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + From 22a9c7e870fc1eea8f2516a7606691cfff977dee Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Fri, 15 Mar 2019 13:57:09 +0100 Subject: [PATCH 11/14] Update the copyright year. --- src/Klarna/Rest/InstantShopping/ButtonKeys.php | 2 +- src/Klarna/Rest/InstantShopping/Orders.php | 2 +- tests/Component/InstantShopping/ButtonKeysTest.php | 2 +- tests/Component/InstantShopping/OrdersTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php index 2a0499a..7c80d45 100644 --- a/src/Klarna/Rest/InstantShopping/ButtonKeys.php +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -1,6 +1,6 @@ Date: Fri, 15 Mar 2019 14:01:08 +0100 Subject: [PATCH 12/14] Refactoring: Change the variable name to avoid overriding the incoming data. --- src/Klarna/Rest/Checkout/Order.php | 10 +++++----- src/Klarna/Rest/HostedPaymentPage/Sessions.php | 4 ++-- src/Klarna/Rest/InstantShopping/ButtonKeys.php | 6 +++--- .../Rest/MerchantCardService/VCCSettlements.php | 12 ++++++------ src/Klarna/Rest/Payments/Orders.php | 4 ++-- src/Klarna/Rest/Payments/Sessions.php | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Klarna/Rest/Checkout/Order.php b/src/Klarna/Rest/Checkout/Order.php index 7b455c6..6df4466 100644 --- a/src/Klarna/Rest/Checkout/Order.php +++ b/src/Klarna/Rest/Checkout/Order.php @@ -76,12 +76,12 @@ public function __construct(Connector $connector, $orderId = null) */ public function create(array $data) { - $data = $this->post(self::$path, $data) + $response = $this->post(self::$path, $data) ->status('201') ->contentType('application/json'); - $this->exchangeArray($data->getJson()); - $this->setLocation($data->getLocation()); + $this->exchangeArray($response->getJson()); + $this->setLocation($response->getLocation()); return $this; } @@ -102,12 +102,12 @@ public function create(array $data) */ public function update(array $data) { - $data = $this->post($this->getLocation(), $data) + $response = $this->post($this->getLocation(), $data) ->status('200') ->contentType('application/json') ->getJson(); - $this->exchangeArray($data); + $this->exchangeArray($response); return $this; } diff --git a/src/Klarna/Rest/HostedPaymentPage/Sessions.php b/src/Klarna/Rest/HostedPaymentPage/Sessions.php index a219ec8..637d433 100644 --- a/src/Klarna/Rest/HostedPaymentPage/Sessions.php +++ b/src/Klarna/Rest/HostedPaymentPage/Sessions.php @@ -73,12 +73,12 @@ public function __construct(Connector $connector, $sessionId = null) */ public function create(array $data) { - $data = $this->post(self::$path, $data) + $response = $this->post(self::$path, $data) ->status('201') ->contentType('application/json') ->getJson(); - return $data; + return $response; } /** diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php index 7c80d45..0c16c6f 100644 --- a/src/Klarna/Rest/InstantShopping/ButtonKeys.php +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -72,14 +72,14 @@ public function __construct(Connector $connector, $buttonKey = null) */ public function create(array $data) { - $data = $this->post(self::$path, $data) + $response = $this->post(self::$path, $data) ->status('201') ->contentType('application/json'); - $url = $data->getLocation(); + $url = $response->getLocation(); $this->setLocation($url); - return $data->getJson(); + return $response->getJson(); } /** diff --git a/src/Klarna/Rest/MerchantCardService/VCCSettlements.php b/src/Klarna/Rest/MerchantCardService/VCCSettlements.php index fd0f948..6ca8248 100644 --- a/src/Klarna/Rest/MerchantCardService/VCCSettlements.php +++ b/src/Klarna/Rest/MerchantCardService/VCCSettlements.php @@ -78,12 +78,12 @@ public function fetch() */ public function create(array $data) { - $data = $this->post(self::$path, $data) + $response = $this->post(self::$path, $data) ->status('201') ->contentType('application/json') ->getJson(); - return $data; + return $response; } /** @@ -104,7 +104,7 @@ public function create(array $data) */ public function retrieveSettlement($settlementId, $keyId) { - $data = $this->request( + $response = $this->request( 'GET', self::$path . "/$settlementId", ['KeyId' => $keyId] @@ -112,7 +112,7 @@ public function retrieveSettlement($settlementId, $keyId) ->contentType('application/json') ->getJson(); - return $data; + return $response; } /** @@ -133,7 +133,7 @@ public function retrieveSettlement($settlementId, $keyId) */ public function retrieveOrderSettlement($orderId, $keyId) { - $data = $this->request( + $response = $this->request( 'GET', self::$path . "/order/$orderId", ['KeyId' => $keyId] @@ -141,6 +141,6 @@ public function retrieveOrderSettlement($orderId, $keyId) ->contentType('application/json') ->getJson(); - return $data; + return $response; } } diff --git a/src/Klarna/Rest/Payments/Orders.php b/src/Klarna/Rest/Payments/Orders.php index 83bbb94..55d7521 100644 --- a/src/Klarna/Rest/Payments/Orders.php +++ b/src/Klarna/Rest/Payments/Orders.php @@ -128,11 +128,11 @@ public function cancelAuthorization() */ public function generateToken(array $data) { - $data = $this->post($this->getLocation() . '/customer-token', $data) + $response = $this->post($this->getLocation() . '/customer-token', $data) ->status('200') ->contentType('application/json') ->getJson(); - return $data; + return $response; } } diff --git a/src/Klarna/Rest/Payments/Sessions.php b/src/Klarna/Rest/Payments/Sessions.php index 672ca81..f03e73a 100644 --- a/src/Klarna/Rest/Payments/Sessions.php +++ b/src/Klarna/Rest/Payments/Sessions.php @@ -74,11 +74,11 @@ public function __construct(Connector $connector, $sessionId = null) */ public function create(array $data) { - $data = $this->post(self::$path, $data) + $response = $this->post(self::$path, $data) ->status('200') ->contentType('application/json'); - $this->exchangeArray($data->getJson()); + $this->exchangeArray($response->getJson()); // Payments API does not send Location header after creating a new session. // Use workaround to set new location. From 73855af24ec1fc768bbe6260de327928c2109cd0 Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Fri, 15 Mar 2019 14:03:28 +0100 Subject: [PATCH 13/14] Apply PSR-2 coding style standarts and remove extra indentation. --- src/Klarna/Rest/InstantShopping/ButtonKeys.php | 4 ++-- src/Klarna/Rest/InstantShopping/Orders.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Klarna/Rest/InstantShopping/ButtonKeys.php b/src/Klarna/Rest/InstantShopping/ButtonKeys.php index 0c16c6f..9a353b8 100644 --- a/src/Klarna/Rest/InstantShopping/ButtonKeys.php +++ b/src/Klarna/Rest/InstantShopping/ButtonKeys.php @@ -92,7 +92,7 @@ public function create(array $data) * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered * @throws \RuntimeException If the API replies with an unexpected response - * @throws \RuntimeException If key was not specified when creating a resource + * @throws \RuntimeException If key was not specified when creating a resource * @throws \LogicException When Guzzle cannot populate the response * * @return array Button properties @@ -111,7 +111,7 @@ public function update(array $data) /** * See the setup options for a specific button key. - * + * * @see https://developers.klarna.com/api/#instant-shopping-api-see-the-setup-options-for-a-specific-button-key * * @throws ConnectorException When the API replies with an error response diff --git a/src/Klarna/Rest/InstantShopping/Orders.php b/src/Klarna/Rest/InstantShopping/Orders.php index 24b7335..13718ea 100644 --- a/src/Klarna/Rest/InstantShopping/Orders.php +++ b/src/Klarna/Rest/InstantShopping/Orders.php @@ -74,7 +74,7 @@ public function retrieve() * Declines an authorized order identified by the authorization token. * * @param array $data Decline data - * + * * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered * @throws \RuntimeException If the location header is missing From e509e76d5fd500acd4169a720d5defdbc2eb7b50 Mon Sep 17 00:00:00 2001 From: Alexander Zinovev Date: Fri, 15 Mar 2019 14:24:49 +0100 Subject: [PATCH 14/14] InstantShopping: Add codingStandardsIgnore tag to be able to populate the link to docs. --- src/Klarna/Rest/InstantShopping/Orders.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Klarna/Rest/InstantShopping/Orders.php b/src/Klarna/Rest/InstantShopping/Orders.php index 13718ea..436c013 100644 --- a/src/Klarna/Rest/InstantShopping/Orders.php +++ b/src/Klarna/Rest/InstantShopping/Orders.php @@ -56,6 +56,10 @@ public function __construct(Connector $connector, $authorizationToken) /** * Retrieves an authorized order based on the authorization token. * + * @codingStandardsIgnoreStart + * @see https://developers.klarna.com/api/#instant-shopping-api-retrieves-an-authorized-order-based-on-the-authorization-token + * @codingStandardsIgnoreEnd + * * @throws ConnectorException When the API replies with an error response * @throws RequestException When an error is encountered * @throws \RuntimeException On an unexpected API response @@ -73,6 +77,10 @@ public function retrieve() /** * Declines an authorized order identified by the authorization token. * + * @codingStandardsIgnoreStart + * @see https://developers.klarna.com/api/#instant-shopping-api-declines-an-authorized-order-identified-by-the-authorization-token + * @codingStandardsIgnoreEnd + * * @param array $data Decline data * * @throws ConnectorException When the API replies with an error response @@ -94,6 +102,10 @@ public function decline(array $data = null) /** * Approves the authorized order and places an order identified by the authorization token. * + * @codingStandardsIgnoreStart + * @see https://developers.klarna.com/api/#instant-shopping-api-approve-the-authorized-order-and-place-an-order-identified-by-the-authorization-token + * @codingStandardsIgnoreEnd + * * @param array $data Order data * * @throws ConnectorException When the API replies with an error response