• Namespace
  • Class

Namespaces

  • Klarna
    • Exceptions
    • Rest
      • Checkout
      • CustomerToken
      • HostedPaymentPage
      • InstantShopping
      • MerchantCardService
      • OrderManagement
      • Payments
      • Settlements
      • Transport
        • Exception

Classes

  • Klarna\Rest\Checkout\Order
  • Klarna\Rest\CustomerToken\Tokens
  • Klarna\Rest\HostedPaymentPage\Sessions
  • Klarna\Rest\InstantShopping\ButtonKeys
  • Klarna\Rest\InstantShopping\Orders
  • Klarna\Rest\MerchantCardService\VCCSettlements
  • Klarna\Rest\OrderManagement\Capture
  • Klarna\Rest\OrderManagement\Order
  • Klarna\Rest\OrderManagement\Refund
  • Klarna\Rest\Payments\Orders
  • Klarna\Rest\Payments\Sessions
  • Klarna\Rest\Resource
  • Klarna\Rest\Settlements\Payouts
  • Klarna\Rest\Settlements\Reports
  • Klarna\Rest\Settlements\Transactions
  • Klarna\Rest\Transport\ApiResponse
  • Klarna\Rest\Transport\Connector
  • Klarna\Rest\Transport\CURLConnector
  • Klarna\Rest\Transport\GuzzleConnector
  • Klarna\Rest\Transport\Method
  • Klarna\Rest\Transport\ResponseValidator
  • Klarna\Rest\Transport\UserAgent

Interfaces

  • Klarna\Rest\Transport\ConnectorInterface
  • Klarna\Rest\Transport\UserAgentInterface

Exceptions

  • Klarna\Exceptions\NotApplicableException
  • Klarna\Rest\Transport\Exception\ConnectorException
  1 <?php
  2 /**
  3  * Copyright 2019 Klarna AB
  4  *
  5  * Licensed under the Apache License, Version 2.0 (the "License");
  6  * you may not use this file except in compliance with the License.
  7  * You may obtain a copy of the License at
  8  *
  9  * http://www.apache.org/licenses/LICENSE-2.0
 10  *
 11  * Unless required by applicable law or agreed to in writing, software
 12  * distributed under the License is distributed on an "AS IS" BASIS,
 13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  * See the License for the specific language governing permissions and
 15  * limitations under the License.
 16  *
 17  * File containing the Connector class.
 18  */
 19 
 20 namespace Klarna\Rest\Transport;
 21 
 22 /**
 23  * Transport connector used to authenticate and make HTTP requests against the
 24  * Klarna APIs. Transport uses CURL to perform HTTP(s) calls.
 25  */
 26 class CURLConnector implements ConnectorInterface
 27 {
 28     /**
 29      * Default request type
 30      */
 31     const DEFAULT_CONTENT_TYPE = 'application/json';
 32 
 33     /**
 34      * Extra CURL request options.
 35      */
 36     protected $options = [];
 37 
 38     /**
 39      * Merchant ID.
 40      *
 41      * @var string
 42      */
 43     protected $merchantId;
 44 
 45     /**
 46      * Shared secret.
 47      *
 48      * @var string
 49      */
 50     protected $sharedSecret;
 51 
 52     /**
 53      * Base URL.
 54      *
 55      * @var string
 56      */
 57     protected $baseUrl;
 58 
 59     /**
 60      * HTTP user agent.
 61      *
 62      * @var UserAgent
 63      */
 64     protected $userAgent;
 65 
 66     public function __construct(
 67         $merchantId,
 68         $sharedSecret,
 69         $baseUrl,
 70         UserAgentInterface $userAgent = null
 71     ) {
 72         $this->merchantId = $merchantId;
 73         $this->sharedSecret = $sharedSecret;
 74         $this->baseUrl = rtrim($baseUrl, '/');
 75 
 76         if ($userAgent === null) {
 77             $userAgent = UserAgent::createDefault(['CURLConnector/' . curl_version()['version']]);
 78         }
 79         $this->userAgent = $userAgent;
 80     }
 81 
 82     /**
 83      * Sets CURL request options.
 84      *
 85      * @param options CURL options
 86      *
 87      * @return self instance
 88      */
 89     public function setOptions($options)
 90     {
 91         $this->options = $options;
 92         return $this;
 93     }
 94 
 95     /**
 96      * Sends HTTP GET request to specified path.
 97      *
 98      * @param string $path URL path.
 99      * @param array $headers HTTP request headers
100      * @return ApiResponse Processed response
101      *
102      * @throws RuntimeException if HTTP transport failed to execute a call
103      */
104     public function get($path, $headers = [])
105     {
106         return $this->request(Method::GET, $path, $headers);
107     }
108 
109     /**
110      * Sends HTTP POST request to specified path.
111      *
112      * @param string $path URL path.
113      * @param string $data Data to be sent to API server in a payload. Example: json-encoded string
114      * @param array $headers HTTP request headers
115      * @return ApiResponse Processed response
116      * @throws RuntimeException if HTTP transport failed to execute a call
117      */
118     public function post($path, $data = null, $headers = [])
119     {
120         return $this->request(Method::POST, $path, $headers, $data);
121     }
122 
123     /**
124      * Sends HTTP PUT request to specified path.
125      *
126      * @param string $path URL path.
127      * @param string $data Data to be sent to API server in a payload. Example: json-encoded string
128      * @param array $headers HTTP request headers
129      * @return ApiResponse Processed response
130      *
131      * @throws RuntimeException if HTTP transport failed to execute a call
132      */
133     public function put($path, $data = null, $headers = [])
134     {
135         return $this->request(Method::PUT, $path, $headers, $data);
136     }
137 
138     /**
139      * Sends HTTP PATCH request to specified path.
140      *
141      * @param string $path URL path.
142      * @param string $data Data to be sent to API server in a payload. Example: json-encoded string
143      * @param array $headers HTTP request headers
144      * @return ApiResponse Processed response
145      *
146      * @throws RuntimeException if HTTP transport failed to execute a call
147      */
148     public function patch($path, $data = null, $headers = [])
149     {
150         return $this->request(Method::PATCH, $path, $headers, $data);
151     }
152 
153     /**
154      * Sends HTTP DELETE request to specified path.
155      *
156      * @param string $path URL path.
157      * @param string $data Data to be sent to API server in a payload. Example: json-encoded string
158      * @param array $headers HTTP request headers
159      * @return ApiResponse Processed response
160      *
161      * @throws RuntimeException if HTTP transport failed to execute a call
162      */
163     public function delete($path, $data = null, $headers = [])
164     {
165         return $this->request(Method::DELETE, $path, $headers, $data);
166     }
167 
168     /**
169      * Performs HTTP(S) request.
170      *
171      * @param string $path URL path.
172      * @param string $data Data to be sent to API server in a payload. Example: json-encoded string
173      * @param array $headers HTTP request headers
174      * @return ApiResponse Processed response
175      *
176      * @throws RuntimeException if HTTP transport failed to execute a call
177      */
178     protected function request($method, $url, array $headers = [], $data = null)
179     {
180         $headers = array_merge([
181            'User-Agent' => (string) $this->userAgent,
182         ], $headers);
183 
184         if (isset($this->options['headers'])) {
185             $headers = array_merge($headers, $this->options['headers']);
186         }
187         array_walk($headers, function (&$v, $k) {
188             $v = $k . ': ' . $v;
189         });
190 
191         $ch = curl_init();
192 
193         if (!empty($this->merchantId)) {
194             curl_setopt($ch, CURLOPT_USERPWD, $this->merchantId . ':' . $this->sharedSecret);
195         }
196         if (!empty($this->options['ssl_cert'])) {
197             curl_setopt($ch, CURLOPT_SSLCERT, $this->options['ssl_cert']);
198             if (!empty($this->options['ssl_key'])) {
199                 curl_setopt($ch, CURLOPT_SSLKEY, $this->options['ssl_key']);
200             }
201         }
202 
203         if (!empty($this->options['timeout'])) {
204             curl_setopt($ch, CURLOPT_TIMEOUT, $this->options['timeout']);
205         }
206 
207         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
208 
209         if ($method == Method::GET) {
210             curl_setopt($ch, CURLOPT_HTTPGET, 1);
211         } elseif ($method == Method::POST) {
212             curl_setopt($ch, CURLOPT_POST, 1);
213             curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
214         } else {
215             curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
216             curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
217         }
218 
219         curl_setopt($ch, CURLOPT_URL, $this->baseUrl . $url);
220         curl_setopt($ch, CURLOPT_HEADER, 1);
221 
222         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
223         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
224         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
225 
226         if ($proxy = getenv('HTTP_PROXY')) {
227             $proxy = parse_url($proxy);
228 
229             $proxyHost = $proxy['host'];
230             $proxyPort = $proxy['port'] ? ':' . $proxy['post'] : '';
231             curl_setopt($ch, CURLOPT_PROXY, $proxyHost . $proxyPort);
232             if (!empty($proxy['user'])) {
233                 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['user'] . ':' . $proxy['pass']);
234             }
235         }
236 
237         $response = curl_exec($ch);
238         
239         $errno = curl_errno($ch);
240         $error = curl_error($ch);
241         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
242         $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
243 
244         curl_close($ch);
245 
246         // Check the TCP transport issues
247         if (!empty($errno)) {
248             throw new \RuntimeException($error, $errno);
249         }
250 
251         $rawHeaders = substr($response, 0, $header_size);
252         $body = substr($response, $header_size);
253         $headers = self::parseHeaders($rawHeaders);
254 
255         return new ApiResponse($http_code, $body, $headers);
256     }
257 
258     /**
259      * Factory method to create a connector instance.
260      *
261      * @param string             $merchantId   Merchant ID
262      * @param string             $sharedSecret Shared secret
263      * @param string             $baseUrl      Base URL for HTTP requests
264      * @param UserAgentInterface $userAgent    HTTP user agent to identify the client
265      *
266      * @return self
267      */
268     public static function create(
269         $merchantId,
270         $sharedSecret,
271         $baseUrl = self::EU_BASE_URL,
272         UserAgentInterface $userAgent = null
273     ) {
274         return new static($merchantId, $sharedSecret, $baseUrl, $userAgent);
275     }
276 
277     
278     /**
279      * Converts raw curl headers response to array.
280      *
281      * @param string $rawHeaders Headers part from the curl response
282      *
283      * @return array list of HTTP headers
284      */
285     protected static function parseHeaders($rawHeaders)
286     {
287         $headers = [];
288         foreach (explode("\r\n", $rawHeaders) as $i => $line) {
289             if (strlen($line) == 0) {
290                 continue;
291             }
292 
293             if (strpos($line, 'HTTP/') !== false) {
294                 // The line contains the HTTP response information
295                 $headers['Http'] = $line;
296                 continue;
297             }
298             list($key, $value) = explode(': ', $line);
299             $headers[ucwords($key, '-_')][] = $value;
300         }
301         return $headers;
302     }
303 }
304 
API documentation generated by ApiGen