* @copyright 2019 Futurenext srl * @license https://www.zakeke.com/privacy/#general_conditions */ class ZakekeApi { const ZAKEKE_API_URL = 'https://zakeke-api-frontdoor.azurefd.net'; /** @var bool Enable debug mode */ protected $debug = false; /** * Check if the design identifier is valid by throwing an exception * * @param $designId * @throws Exception */ private static function checkDesignId($designId) { if (strpos($designId, 'Testing-') === 0) { return; } if (!preg_match('/\d\d\d-\w{22}/', $designId)) { throw new Exception($designId . ' is not a valid design identifier'); } } /** * ZakekeApi constructor. * * @param bool $debug */ public function __construct($debug) { $this->debug = $debug; } /** * Associate the guest with a customer * * @param string $guestCode Guest identifier. * @param string $customerId Customer identifier. * * @throws Exception * @return void */ public function associateGuest($guestCode, $customerId) { $data = $this->authMinimalData(); $data['visitorcode'] = $guestCode; $data['customercode'] = $customerId; $this->authToken($data); } /** * Get the minimal data required to get the authentication token * * @return array */ private function authMinimalData() { $data = array( 'api_client_id' => Configuration::get('ZAKEKE_API_CLIENT_ID'), 'api_secret_key' => Configuration::get('ZAKEKE_API_SECRET_KEY') ); return $data; } /** * Zakeke authentication token. * * @param array $data * @param array $authData * * @throws Exception * @return string */ public function authToken($data, $authData = null) { if (is_null($authData)) { $authData = $this->authMinimalData(); } $url = ZakekeApi::ZAKEKE_API_URL . '/token'; $postfields = http_build_query(array_merge($data, array( 'grant_type' => 'client_credentials' )), null, '&'); $headers = array( 'Content-Type: application/x-www-form-urlencoded', 'Accept: application/json', 'User-Agent: prestashop-zakeke/' . ZAKEKE_VERSION . '; Prestashop/' . _PS_VERSION_ . '; ' . __PS_BASE_URI__ ); $curl = curl_init(); curl_setopt_array( $curl, array( CURLOPT_URL => $url, CURLOPT_USERPWD => $authData['api_client_id'] . ':' . $authData['api_secret_key'], CURLOPT_RETURNTRANSFER => true, CURLOPT_VERBOSE => true, CURLOPT_TIMEOUT => 30, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $postfields, CURLOPT_HTTPHEADER => $headers ) ); $rawResponse = curl_exec($curl); $error = curl_error($curl); $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $this->maybeLog($url, 'POST', $data, $rawResponse, $statusCode); if ($error || !$this->isStatusCodeOk($statusCode)) { $msg = '$zakekeApi authToken: status: ' . $statusCode . ' ' . $error . ', response: ' . $rawResponse; throw new Exception($msg); } $result = json_decode($rawResponse, true); return $result['access_token']; } /** * Performs the underlying HTTP request. * * @param string $method HTTP method (GET|POST|PUT|PATCH|DELETE) * @param string $resource MailChimp API resource to be called * @param array $args array of parameters to be passed * @param string $authToken Authentication token * * @throws Exception * @return array array of decoded result */ protected function request($method, $resource, $args = array(), $authToken = null) { $url = ZakekeApi::ZAKEKE_API_URL . $resource; // attach arguments (in body or URL) $postfields = ''; if (!empty($args)) { if ($method === 'GET') { $url .= '?' . http_build_query($args); } else { $postfields = json_encode($args); } } $headers = array( 'Content-Type: application/json', 'Accept: application/json', 'User-Agent: prestashop-zakeke/' . ZAKEKE_VERSION . '; Prestashop/' . _PS_VERSION_ . '; ' . __PS_BASE_URI__ ); if ($authToken != null) { $headers[] = 'Authorization: Bearer ' . $authToken; } $curl = curl_init(); curl_setopt_array( $curl, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_VERBOSE => true, CURLOPT_TIMEOUT => 30, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_POSTFIELDS => $postfields, CURLOPT_HTTPHEADER => $headers ) ); $rawResponse = curl_exec($curl); $error = curl_error($curl); $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $this->maybeLog($resource, $method, $args, $rawResponse, $statusCode); if ($error ||!$this->isStatusCodeOk($statusCode)) { $msg = '$zakekeApi request: ' . $resource . ', status: ' . $statusCode . ' ' . $error . ', response: ' . $rawResponse; throw new Exception($msg); } $result = json_decode($rawResponse, true); return $result; } /** * Remove sensitive information from the logs * * @param string $message * @return string */ private static function sanitizeLog($message) { $zakeke_api_secret = Configuration::get('ZAKEKE_API_SECRET_KEY'); if ($zakeke_api_secret) { $message = str_replace($zakeke_api_secret, 'ZAKEKE_API_SECRET_KEY', $message); } return $message; } /** * Conditionally log Zakeke API Call * * @param string $url Zakeke url * @param string $method HTTP Method * @param array $args HTTP Request Body * @param string $response HTTP Response * @param int $statusCode HTTP status code * * @return void */ private function maybeLog($url, $method, $args, $response, $statusCode) { if (!$this->debug) { return; } PrestaShopLogger::addLog( self::sanitizeLog( 'Zakeke Webservice Call URL: ' . $method . ' ' . $url . ' METHOD: $method BODY: ' . print_r( $args, true ) . ' RESPONSE: ' . $statusCode . ' - ' . $response ) ); } /** * Check if the response was successful. * * @param int $statusCode HTTP status code * * @return bool */ protected function isStatusCodeOk($statusCode) { return $statusCode >= 200 && $statusCode < 400; } /** * Get the needed data for adding a product to the cart * * @param string $designId Zakeke design identifier * @param int $qty Quantity * * @throws Exception * @return object */ public function cartInfo($designId, $qty) { self::checkDesignId($designId); $authToken = $this->authToken(array()); $url = '/api/designdocs/' . $designId . '/cartinfo'; $data = array( 'qty' => $qty ); $json = $this->request('GET', $url, $data, $authToken); $res = new stdClass(); $res->pricing = $json['pricing']; $preview = new stdClass(); $preview->url = $json['tempPreviewUrl']; $preview->label = ''; $res->previews = array($preview); return $res; } /** * Get the needed data for adding a product to the cart * * @param string $configurationId Zakeke configuration identifier * @param int $qty Quantity * * @throws Exception * @return array */ public function configuratorCartInfo($configurationId, $qty) { self::checkDesignId($configurationId); $authToken = $this->authToken(array( 'access_type' => 'S2S' )); $url = '/v1/compositions/' . $configurationId . '/cartinfo'; $data = array( 'qty' => $qty ); return $this->request('GET', $url, $data, $authToken); } /** * Order containing Zakeke customized products placed. * * @param array $data The data of the order * * @throws Exception * @return void */ public function placeOrder($data) { $authData = array(); if (isset($data['customerID'])) { $authData['customercode'] = $data['customerID']; } elseif (isset($data['visitorID'])) { $authData['visitorcode'] = $data['visitorID']; } $authToken = $this->authToken($authData); $data['marketplaceID'] = '1'; $this->request('POST', '/api/orderdocs', $data, $authToken); } /** * Get the Zakeke design preview files * * @param string $designId Zakeke design identifier * * @throws Exception * @return array */ public function getPreviews($designId) { self::checkDesignId($designId); $authToken = $this->authToken(array()); $data = array( 'docid' => $designId ); $json = self::request( 'GET', '/api/designs/0/previewfiles', $data, $authToken ); $previews = array(); foreach ($json as $preview) { if ($preview['format'] == 'SVG') { continue; } $previewObj = new stdClass(); $previewObj->url = $preview['url']; $previewObj->label = $preview['sideName']; $previews[] = $previewObj; } return $previews; } /** * Get the Zakeke design output zip * * @param string $designId Zakeke design identifier * * @throws Exception * @return string */ public function getZakekeOutputZip($designId) { self::checkDesignId($designId); $authToken = $this->authToken(array()); $data = array( 'docid' => $designId ); $json = $this->request( 'GET', '/api/designs/0/outputfiles/zip', $data, $authToken ); return $json['url']; } public function checkCredentials($clientId, $secretKey) { try { $this->authToken(array(), array( 'api_client_id' => $clientId, 'api_secret_key' => $secretKey )); return true; } catch (Exception $e) { return false; } } }