* @copyright 2015 Klarna AB * @license http://www.apache.org/licenses/LICENSE-2.0 Apache license v2.0 * @link http://developers.klarna.com/ */ /** * Basic implementation of the connector interface * * @category Payment * @package Klarna_Checkout * @author Rickard D. * @author Christer G. * @author David K. * @copyright 2015 Klarna AB * @license http://www.apache.org/licenses/LICENSE-2.0 Apache license v2.0 * @link http://developers.klarna.com/ */ class Klarna_Checkout_BasicConnector implements Klarna_Checkout_ConnectorInterface { /** * Klarna_Checkout_HTTP_TransportInterface Implementation * * @var Klarna_Checkout_HTTP_TransportInterface */ protected $http; /** * Digester class * * @var Klarna_Checkout_Digest */ protected $digester; /** * The domain for the request * * @var string */ protected $domain; /** * Shared Secret used to sign requests * * @var string */ private $_secret; /** * Create a new Checkout Connector * * @param Klarna_Checkout_HTTP_TransportInterface $http Transport * @param Klarna_Checkout_Digest $digester Digest Generator * @param string $secret Shared secret * @param string $domain Domain of the request */ public function __construct( Klarna_Checkout_HTTP_TransportInterface $http, Klarna_Checkout_Digest $digester, $secret, $domain = Klarna_Checkout_Connector::BASE_URL ) { $this->http = $http; $this->digester = $digester; $this->_secret = $secret; $this->domain = $domain; } /** * Create the user agent identifier to use * * @return Klarna_Checkout_UserAgent */ protected function userAgent() { return new Klarna_Checkout_UserAgent(); } /** * Applying the method on the specific resource * * @param string $method Http methods * @param Klarna_Checkout_ResourceInterface $resource resource * @param array $options Options * * @return mixed */ public function apply( $method, Klarna_Checkout_ResourceInterface $resource, array $options = null ) { switch ($method) { case 'GET': case 'POST': return $this->handle($method, $resource, $options, array()); default: throw new InvalidArgumentException( "{$method} is not a valid HTTP method" ); } } /** * Gets the underlying transport object * * @return Klarna_Checkout_HTTP_TransportInterface Transport object */ public function getTransport() { return $this->http; } /** * Set content (headers, payload) on a request * * @param Klarna_Checkout_ResourceInterface $resource Klarna Checkout Resource * @param string $method HTTP Method * @param string $payload Payload to send with the * request * @param string $url URL for request * * @return Klarna_Checkout_HTTP_Request */ protected function createRequest( Klarna_Checkout_ResourceInterface $resource, $method, $payload, $url ) { // Generate the digest string $digest = $this->digester->create($payload . $this->_secret); $request = $this->http->createRequest($url); $request->setMethod($method); // Set HTTP Headers $accept = $resource->getAccept(); $contentType = $resource->getContentType(); $request->setHeader('User-Agent', (string)$this->userAgent()); $request->setHeader('Authorization', "Klarna {$digest}"); $request->setHeader('Accept', $accept ? $accept : $contentType); if (strlen($payload) > 0) { $request->setHeader('Content-Type', $contentType); $request->setData($payload); } return $request; } /** * Get the current domain * * @return string */ public function getDomain() { return $this->domain; } /** * Get the url to use * * @param Klarna_Checkout_ResourceInterface $resource resource * @param array $options Options * * @return string Url to use for HTTP requests */ protected function getUrl( Klarna_Checkout_ResourceInterface $resource, array $options ) { if (array_key_exists('url', $options)) { return $options['url']; } return $resource->getLocation(); } /** * Get the data to use * * @param Klarna_Checkout_ResourceInterface $resource resource * @param array $options Options * * @return array data to use for HTTP requests */ protected function getData( Klarna_Checkout_ResourceInterface $resource, array $options ) { if (array_key_exists('data', $options)) { return $options['data']; } return $resource->marshal(); } /** * Throw an exception if the server responds with an error code. * * @param Klarna_Checkout_HTTP_Response $result HTTP Response object * * @throws Klarna_Checkout_ApiErrorException * @return void */ protected function verifyResponse(Klarna_Checkout_HTTP_Response $result) { // Error Status Code recieved. Throw an exception. if ($result->getStatus() >= 400 && $result->getStatus() <= 599) { $json = json_decode($result->getData(), true); $payload = ($json && is_array($json)) ? $json : array(); throw new Klarna_Checkout_ApiErrorException( "API Error", $result->getStatus(), $payload ); } } /** * Act upon the status of a response * * @param Klarna_Checkout_HTTP_Response $result response from server * @param Klarna_Checkout_ResourceInterface $resource associated resource * @param array $visited list of visited locations * * @return Klarna_Checkout_HTTP_Response * @throws Klarna_Checkout_ConnectorException */ protected function handleResponse( Klarna_Checkout_HTTP_Response $result, Klarna_Checkout_ResourceInterface $resource, array $visited = array() ) { // Check if we got an Error status code back $this->verifyResponse($result); $url = $result->getHeader('Location'); switch ($result->getStatus()) { case 301: // Update location and fallthrough $resource->setLocation($url); case 302: // Don't fallthrough for other than GET if ($result->getRequest()->getMethod() !== 'GET') { break; } case 303: // Detect eternal loops if (in_array($url, $visited)) { throw new Klarna_Checkout_ConnectorException( 'Infinite redirect loop detected.', -1 ); } $visited[] = $url; // Follow redirect return $this->handle( 'GET', $resource, array('url' => $url), $visited ); case 201: // Update Location $resource->setLocation($url); break; case 200: // Update Data on resource $json = json_decode($result->getData(), true); if ($json === null) { throw new Klarna_Checkout_ConnectorException( 'Bad format on response content.', -2 ); } $resource->parse($json); } return $result; } /** * Perform a HTTP Call on the supplied resource using the wanted method. * * @param string $method HTTP Method * @param Klarna_Checkout_ResourceInterface $resource Klarna Order * @param array $options Options * @param array $visited list of visited locations * * @throws Klarna_Checkout_Exception if 4xx or 5xx response code. * * @return Klarna_Checkout_HTTP_Response Result object containing status code * and payload */ protected function handle( $method, Klarna_Checkout_ResourceInterface $resource, array $options = null, array $visited = array() ) { if ($options === null) { $options = array(); } // Define the target URL $url = $this->getUrl($resource, $options); // Set a payload if it is a POST call. $payload = ''; if ($method === 'POST') { $payload = json_encode($this->getData($resource, $options)); } // Create a HTTP Request object $request = $this->createRequest($resource, $method, $payload, $url); // Execute the HTTP Request $result = $this->http->send($request); // Handle statuses appropriately. return $this->handleResponse($result, $resource, $visited); } }