1 <?php
2 /**
3 * Copyright 2015 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 Klarna_Checkout_BasicConnector class
18 *
19 * PHP version 5.3
20 *
21 * @category Payment
22 * @package Klarna_Checkout
23 * @author Klarna <support@klarna.com>
24 * @copyright 2015 Klarna AB
25 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache license v2.0
26 * @link http://developers.klarna.com/
27 */
28
29 /**
30 * Basic implementation of the connector interface
31 *
32 * @category Payment
33 * @package Klarna_Checkout
34 * @author Rickard D. <rickard.dybeck@klarna.com>
35 * @author Christer G. <christer.gustavsson@klarna.com>
36 * @author David K. <david.keijser@klarna.com>
37 * @copyright 2015 Klarna AB
38 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache license v2.0
39 * @link http://developers.klarna.com/
40 */
41 class Klarna_Checkout_BasicConnector implements Klarna_Checkout_ConnectorInterface
42 {
43
44 /**
45 * Klarna_Checkout_HTTP_TransportInterface Implementation
46 *
47 * @var Klarna_Checkout_HTTP_TransportInterface
48 */
49 protected $http;
50
51 /**
52 * Digester class
53 *
54 * @var Klarna_Checkout_Digest
55 */
56 protected $digester;
57
58 /**
59 * The domain for the request
60 *
61 * @var string
62 */
63 protected $domain;
64
65 /**
66 * Shared Secret used to sign requests
67 *
68 * @var string
69 */
70 private $_secret;
71
72 /**
73 * Create a new Checkout Connector
74 *
75 * @param Klarna_Checkout_HTTP_TransportInterface $http Transport
76 * @param Klarna_Checkout_Digest $digester Digest Generator
77 * @param string $secret Shared secret
78 * @param string $domain Domain of the request
79 */
80 public function __construct(
81 Klarna_Checkout_HTTP_TransportInterface $http,
82 Klarna_Checkout_Digest $digester,
83 $secret,
84 $domain = Klarna_Checkout_Connector::BASE_URL
85 ) {
86 $this->http = $http;
87 $this->digester = $digester;
88 $this->_secret = $secret;
89 $this->domain = $domain;
90 }
91
92 /**
93 * Create the user agent identifier to use
94 *
95 * @return Klarna_Checkout_UserAgent
96 */
97 protected function userAgent()
98 {
99 return new Klarna_Checkout_UserAgent();
100 }
101
102 /**
103 * Applying the method on the specific resource
104 *
105 * @param string $method Http methods
106 * @param Klarna_Checkout_ResourceInterface $resource resource
107 * @param array $options Options
108 *
109 * @return mixed
110 */
111 public function apply(
112 $method,
113 Klarna_Checkout_ResourceInterface $resource,
114 array $options = null
115 ) {
116 switch ($method) {
117 case 'GET':
118 case 'POST':
119 return $this->handle($method, $resource, $options, array());
120 default:
121 throw new InvalidArgumentException(
122 "{$method} is not a valid HTTP method"
123 );
124 }
125 }
126
127 /**
128 * Gets the underlying transport object
129 *
130 * @return Klarna_Checkout_HTTP_TransportInterface Transport object
131 */
132 public function getTransport()
133 {
134 return $this->http;
135 }
136
137 /**
138 * Set content (headers, payload) on a request
139 *
140 * @param Klarna_Checkout_ResourceInterface $resource Klarna Checkout Resource
141 * @param string $method HTTP Method
142 * @param string $payload Payload to send with the
143 * request
144 * @param string $url URL for request
145 *
146 * @return Klarna_Checkout_HTTP_Request
147 */
148 protected function createRequest(
149 Klarna_Checkout_ResourceInterface $resource,
150 $method,
151 $payload,
152 $url
153 ) {
154 // Generate the digest string
155 $digest = $this->digester->create($payload . $this->_secret);
156
157 $request = $this->http->createRequest($url);
158
159 $request->setMethod($method);
160
161 // Set HTTP Headers
162 $accept = $resource->getAccept();
163 $contentType = $resource->getContentType();
164 $request->setHeader('User-Agent', (string)$this->userAgent());
165 $request->setHeader('Authorization', "Klarna {$digest}");
166 $request->setHeader('Accept', $accept ? $accept : $contentType);
167 if (strlen($payload) > 0) {
168 $request->setHeader('Content-Type', $contentType);
169 $request->setData($payload);
170 }
171
172 return $request;
173 }
174
175 /**
176 * Get the current domain
177 *
178 * @return string
179 */
180 public function getDomain()
181 {
182 return $this->domain;
183 }
184
185 /**
186 * Get the url to use
187 *
188 * @param Klarna_Checkout_ResourceInterface $resource resource
189 * @param array $options Options
190 *
191 * @return string Url to use for HTTP requests
192 */
193 protected function getUrl(
194 Klarna_Checkout_ResourceInterface $resource, array $options
195 ) {
196 if (array_key_exists('url', $options)) {
197 return $options['url'];
198 }
199
200 return $resource->getLocation();
201 }
202
203 /**
204 * Get the data to use
205 *
206 * @param Klarna_Checkout_ResourceInterface $resource resource
207 * @param array $options Options
208 *
209 * @return array data to use for HTTP requests
210 */
211 protected function getData(
212 Klarna_Checkout_ResourceInterface $resource, array $options
213 ) {
214 if (array_key_exists('data', $options)) {
215 return $options['data'];
216 }
217
218 return $resource->marshal();
219 }
220
221 /**
222 * Throw an exception if the server responds with an error code.
223 *
224 * @param Klarna_Checkout_HTTP_Response $result HTTP Response object
225 *
226 * @throws Klarna_Checkout_ApiErrorException
227 * @return void
228 */
229 protected function verifyResponse(Klarna_Checkout_HTTP_Response $result)
230 {
231 // Error Status Code recieved. Throw an exception.
232 if ($result->getStatus() >= 400 && $result->getStatus() <= 599) {
233
234 $json = json_decode($result->getData(), true);
235 $payload = ($json && is_array($json)) ? $json : array();
236
237 throw new Klarna_Checkout_ApiErrorException(
238 "API Error", $result->getStatus(), $payload
239 );
240 }
241 }
242
243 /**
244 * Act upon the status of a response
245 *
246 * @param Klarna_Checkout_HTTP_Response $result response from server
247 * @param Klarna_Checkout_ResourceInterface $resource associated resource
248 * @param array $visited list of visited locations
249 *
250 * @return Klarna_Checkout_HTTP_Response
251 * @throws Klarna_Checkout_ConnectorException
252 */
253 protected function handleResponse(
254 Klarna_Checkout_HTTP_Response $result,
255 Klarna_Checkout_ResourceInterface $resource,
256 array $visited = array()
257 ) {
258 // Check if we got an Error status code back
259 $this->verifyResponse($result);
260
261 $url = $result->getHeader('Location');
262 switch ($result->getStatus()) {
263 case 301:
264 // Update location and fallthrough
265 $resource->setLocation($url);
266 case 302:
267 // Don't fallthrough for other than GET
268 if ($result->getRequest()->getMethod() !== 'GET') {
269 break;
270 }
271 case 303:
272 // Detect eternal loops
273 if (in_array($url, $visited)) {
274 throw new Klarna_Checkout_ConnectorException(
275 'Infinite redirect loop detected.',
276 -1
277 );
278 }
279 $visited[] = $url;
280 // Follow redirect
281 return $this->handle(
282 'GET',
283 $resource,
284 array('url' => $url),
285 $visited
286 );
287 case 201:
288 // Update Location
289 $resource->setLocation($url);
290 break;
291 case 200:
292 // Update Data on resource
293 $json = json_decode($result->getData(), true);
294 if ($json === null) {
295 throw new Klarna_Checkout_ConnectorException(
296 'Bad format on response content.',
297 -2
298 );
299 }
300 $resource->parse($json);
301 }
302
303 return $result;
304 }
305
306 /**
307 * Perform a HTTP Call on the supplied resource using the wanted method.
308 *
309 * @param string $method HTTP Method
310 * @param Klarna_Checkout_ResourceInterface $resource Klarna Order
311 * @param array $options Options
312 * @param array $visited list of visited locations
313 *
314 * @throws Klarna_Checkout_Exception if 4xx or 5xx response code.
315 *
316 * @return Klarna_Checkout_HTTP_Response Result object containing status code
317 * and payload
318 */
319 protected function handle(
320 $method,
321 Klarna_Checkout_ResourceInterface $resource,
322 array $options = null,
323 array $visited = array()
324 ) {
325 if ($options === null) {
326 $options = array();
327 }
328
329 // Define the target URL
330 $url = $this->getUrl($resource, $options);
331
332 // Set a payload if it is a POST call.
333 $payload = '';
334 if ($method === 'POST') {
335 $payload = json_encode($this->getData($resource, $options));
336 }
337
338 // Create a HTTP Request object
339 $request = $this->createRequest($resource, $method, $payload, $url);
340
341 // Execute the HTTP Request
342 $result = $this->http->send($request);
343
344 // Handle statuses appropriately.
345 return $this->handleResponse($result, $resource, $visited);
346 }
347 }
348