diff --git a/apiDocs/eComZone Dropshipping - API documentation.pdf b/apiDocs/eComZone Dropshipping - API documentation.pdf new file mode 100644 index 0000000..4b88063 Binary files /dev/null and b/apiDocs/eComZone Dropshipping - API documentation.pdf differ diff --git a/apiDocs/readme.md b/apiDocs/readme.md new file mode 100644 index 0000000..72d2e5a --- /dev/null +++ b/apiDocs/readme.md @@ -0,0 +1,133 @@ +## + +prestashop integration with ecom-zone-api + +eComZone Dropshipping - API +documentation + +1. Introduction + Generate API token on platform page: https://dropship.ecomzone.eu/api-key. + To generate API token, your account must be confirmed by our dropshipping manager. + If you need help, please contact nemanja@ecomzone.eu or via WhatsApp + +38670290400 + API token use in request, either as bearer-token or as token parameter. +2. Endpoints + Use different endpoints based on the action you want to take. + Catalog endpoint - Use, when you want to see a full product catalog. + Product endpoint - Use, when you want to see all data/information about a specific + product. + Ordering endpoint - Use, when you want to send orders to the platform. + Order endpoint - Use, when you want to see information about a specific order. + Rate-limit = 120 requests per minute per IP address + 2.1 CATALOG ENDPOINT + GET https://dropship.ecomzone.eu/api/catalog + On this endpoint, we have pagination. By default, it will show 1000 products in + response. + By adding the url parameter per_page, you can set the number of products per page in + response. + per_page = integer (optional, min = 10, max = 1000) + Example:https://dropship.ecomzone.eu/api/catalog?per_page=10 + If you set per_page < 10, the system will automatically switch per_page = 10. + 2.1.1. CURL + 2.1.2 RESPONSE + http status-code + Response body as json-encoded array. Each array member is data for one + product. + Paginated: + Pagination response data: + +- "current_page": 1, +- "next_page_url": "null", +- "path": "https://dropship.ecomzone.eu/api/catalog", +- "per_page": "1000", +- "prev_page_url": null, +- "to": 561, +- "total": 561 + Product_id is part of full_sku, which determines variation of the product(color, size). If + there is only 1 product_id, it means that product has only 1 variation, but when creating + an order, you still need to send full_sku(main_sku + product_id). + 2.2 PRODUCT ENDPOINT + GET https://dropship.ecomzone.eu/api/product/{main_sku} + {main_sku} = product sku + Example, in case of Kitchen food processor {2246-911} + https://dropship.ecomzone.eu/api/product/2246-911 + 2.2.1 CURL + 2.2.2 RESPONSE + http-code + response-body json-encoded array: +- status: ok or error +- message: error message in case of status = error +- data as json-econded array with detail data about product, product-variations, + stocks + 2.3 ORDERING ENDPOINT + POST https://dropship.ecomzone.eu/api/ordering + request: orders as json-encoded array + Each array member represents data for specific order with parameters: +- order_index (string or integer) (optional): For indexing specific order in response +- ext_id ( string max 20 char ) (optional): Internal order_id, which is visible on platform +- shop_name (string max 20 char) (optional): Your custom shop_name, which will be + visible on shipping labels as sender of package +- payment['method'](string 2 char 'cod' or 'pp') (required):Cash On Delivery or Prepaid +- payment['customer_price'] (number integer or decimal) ( required if payment-method = + 'cod'): Amount which is collected from end customer + //customer_price value needs to be sent in local currency +- customer_data['full_name] (string max 200) (required): Full name and surname of + customer +- customer_data['email'] (string max 100) (required): Email address of customer +- customer_data['phone_number'] (string max 45) (required): Phone number of + customer +- customer_data['country'] (string 2 char)(required): Alpha-2 customer country-code +- customer_data['address'] (string max 1000) (required): Customer address +- customer_data['city] (string max 100) (required): Customer city name +- customer_data['post_code'](string max 45)(required): Postal code +- customer_data['comment'] (STRING MAX 1000)(optional): Comment/note for courier + items: array with data about products in order +- items['full_sku'](string) (required): Full-sku of products +- items['quantity']( number integer )(required): Quantity of single product in order + 2.3.1 CURL + 2.3.2 RESPONSE + http status-code (200 - if all orders from request has been successfully imported; + 202 - if 1 or more orders from request is not imported) + response body is in form, json-encoded array: +- status: ok(200), accepted(202) or error(422) +- message: +- data['received] : number of orders in request +- data['imported]: number of successfully imported orders +- index: json-encoded array with status of each order from request(if + order_index is set in request) + index['order_index']['status]: ok or error + index['order_index]['order_id']: If indexed order was successfully imported + (status = ok), here we return order_id, under which order was imported on + platform + index['order_index]['note]: If order was not imported (status = error), here + we return an error message explaining why it was not imported ( which + data is incorrect or missing). + Use “comment”: “test” or “TEST” when sending test orders, so that they don’t + get confirmed automatically in our system. + 2.4 ORDER ENDPOINT + GET https://dropship.ecomzone.eu/api/order/{order_id} + {order_id} = ID of order from eComZone platform + If the requested order_id does not belong to the user, the response will be + 401-unauthorized. + 2.4.1 CURL + 2.4.2 RESPONSE + http-code + response-body contains data about order in form json-encoded array: +- status: ok or error +- data as json-encoded array: +- currency +- order_status: title of current status +- order_created_time: time in format Y-m-d H:i:sa +- last_order_status_changed: time of last status changed, in format Y-m-d H:i:s +- payment_method: cod or pp +- customer_price: value that is collected from customers (cod orders) +- returned_bonus: value of return bonus (if exists) +- order_tracking_number: tracking number +- order_tracking_url: tracking url +- order_details['items_cost']: total cost of products in order +- order_details['tax']: total amount of tax in order (if exists) +- order_details['shipping]: shipping price +- order_details['subtotal']: total value/cost of order +- order_details['bonus]: bonus amount which we deduct from total cost of order (if + exists) +- order_details['total']: final value/amount of order to pay diff --git a/eComZone Dropshipping - API documentation.pdf b/eComZone Dropshipping - API documentation.pdf new file mode 100644 index 0000000..4b88063 Binary files /dev/null and b/eComZone Dropshipping - API documentation.pdf differ diff --git a/modules.zip b/modules.zip new file mode 100644 index 0000000..d4d63db Binary files /dev/null and b/modules.zip differ diff --git a/modules/ecomzone.zip b/modules/ecomzone.zip new file mode 100644 index 0000000..6fb1ba0 Binary files /dev/null and b/modules/ecomzone.zip differ diff --git a/modules/ecomzone/classes/EcomZoneAPI.php b/modules/ecomzone/classes/EcomZoneAPI.php new file mode 100644 index 0000000..6cf70a1 --- /dev/null +++ b/modules/ecomzone/classes/EcomZoneAPI.php @@ -0,0 +1,106 @@ +apiKey = Configuration::get('ECOMZONE_API_KEY'); + $this->apiUrl = Configuration::get('ECOMZONE_API_URL'); + $this->lastRequestTime = time(); + } + + private function checkRateLimit() + { + $currentTime = time(); + if ($currentTime - $this->lastRequestTime >= self::RATE_WINDOW) { + $this->requestCount = 0; + $this->lastRequestTime = $currentTime; + } + + if ($this->requestCount >= self::RATE_LIMIT) { + throw new Exception('Rate limit exceeded. Please wait before making more requests.'); + } + + $this->requestCount++; + } + + public function getCatalog($perPage = 1000) + { + $this->checkRateLimit(); + $url = $this->apiUrl . '/catalog'; + return $this->makeRequest('GET', $url, ['per_page' => $perPage]); + } + + public function getProduct($sku) + { + $this->checkRateLimit(); + $url = $this->apiUrl . '/product/' . urlencode($sku); + return $this->makeRequest('GET', $url); + } + + public function createOrder($orderData) + { + $this->checkRateLimit(); + $url = $this->apiUrl . '/ordering'; + return $this->makeRequest('POST', $url, $orderData); + } + + private function makeRequest($method, $url, $params = []) + { + try { + $curl = curl_init(); + + $options = [ + CURLOPT_URL => $url . ($method === 'GET' && !empty($params) ? '?' . http_build_query($params) : ''), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $this->apiKey, + 'Accept: application/json', + 'Content-Type: application/json' + ], + ]; + + if ($method === 'POST') { + $options[CURLOPT_POSTFIELDS] = json_encode($params); + } + + curl_setopt_array($curl, $options); + + $response = curl_exec($curl); + $err = curl_error($curl); + $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + curl_close($curl); + + if ($err) { + throw new Exception("cURL Error: " . $err); + } + + if ($httpCode >= 400) { + throw new Exception("HTTP Error: " . $httpCode . " Response: " . $response); + } + + return json_decode($response, true); + } catch (Exception $e) { + PrestaShopLogger::addLog( + 'EcomZone API Error: ' . $e->getMessage(), + 3, + null, + 'EcomZone' + ); + throw $e; + } + } +} \ No newline at end of file diff --git a/modules/ecomzone/config.xml b/modules/ecomzone/config.xml new file mode 100644 index 0000000..cfac764 --- /dev/null +++ b/modules/ecomzone/config.xml @@ -0,0 +1,12 @@ + + + ecomzone + + + + + + 1 + 0 + + \ No newline at end of file diff --git a/modules/ecomzone/ecomzone.php b/modules/ecomzone/ecomzone.php new file mode 100644 index 0000000..86e6341 --- /dev/null +++ b/modules/ecomzone/ecomzone.php @@ -0,0 +1,157 @@ +name = 'ecomzone'; + $this->tab = 'market_place'; + $this->version = '1.0.0'; + $this->author = 'Your Name'; + $this->need_instance = 0; + $this->bootstrap = true; + $this->ps_versions_compliancy = [ + 'min' => '1.7.0.0', + 'max' => _PS_VERSION_ + ]; + + parent::__construct(); + + $this->displayName = $this->l('EcomZone Integration'); + $this->description = $this->l('Integrates PrestaShop with EcomZone Dropshipping API'); + + // Initialize API + require_once(dirname(__FILE__) . '/classes/EcomZoneAPI.php'); + $this->api = new EcomZoneAPI(); + } + + public function install() + { + if (!parent::install()) { + $this->errors[] = $this->l('Could not install the module'); + return false; + } + + if (!$this->registerHook('actionProductUpdate')) { + $this->errors[] = $this->l('Could not register hooks'); + return false; + } + + if (!Configuration::updateValue('ECOMZONE_API_KEY', '') || + !Configuration::updateValue('ECOMZONE_API_URL', 'https://dropship.ecomzone.eu/api')) { + $this->errors[] = $this->l('Could not set default configuration'); + return false; + } + + return true; + } + + public function getContent() + { + $output = ''; + + if (Tools::isSubmit('submitEcomZone')) { + $apiKey = Tools::getValue('ECOMZONE_API_KEY'); + + if (!$apiKey) { + $output .= $this->displayError($this->l('API Key is required')); + } else { + Configuration::updateValue('ECOMZONE_API_KEY', $apiKey); + $output .= $this->displayConfirmation($this->l('Settings updated')); + } + } + + return $output . $this->displayForm(); + } + + public function displayForm() + { + $fields_form[0]['form'] = [ + 'legend' => [ + 'title' => $this->l('Settings'), + ], + 'input' => [ + [ + 'type' => 'text', + 'label' => $this->l('API Key'), + 'name' => 'ECOMZONE_API_KEY', + 'required' => true, + 'size' => 50 + ], + ], + 'submit' => [ + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right' + ] + ]; + + $helper = new HelperForm(); + $helper->module = $this; + $helper->name_controller = $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; + $helper->default_form_language = Configuration::get('PS_LANG_DEFAULT'); + $helper->fields_value['ECOMZONE_API_KEY'] = Configuration::get('ECOMZONE_API_KEY'); + + return $helper->generateForm($fields_form); + } + + public function getErrors() + { + return $this->errors; + } + + public function hookActionProductUpdate($params) + { + try { + $product = $params['product']; + $productId = $product->id; + + // Get product reference (SKU) + $reference = $product->reference; + + if (!empty($reference)) { + // Get product data from EcomZone + $ecomZoneProduct = $this->api->getProduct($reference); + + if ($ecomZoneProduct && isset($ecomZoneProduct['data'])) { + // Update stock + if (isset($ecomZoneProduct['data']['stock'])) { + StockAvailable::setQuantity($productId, 0, (int)$ecomZoneProduct['data']['stock']); + } + + // Update price + if (isset($ecomZoneProduct['data']['product_price'])) { + $product->price = (float)$ecomZoneProduct['data']['product_price']; + $product->update(); + } + + PrestaShopLogger::addLog( + sprintf('EcomZone: Updated product %s (ID: %d)', $reference, $productId), + 1, // Notice level + null, + 'Product', + $productId, + true + ); + } + } + } catch (Exception $e) { + PrestaShopLogger::addLog( + sprintf('EcomZone: Error updating product %d: %s', $productId, $e->getMessage()), + 3, // Error level + null, + 'Product', + $productId, + true + ); + } + } +} \ No newline at end of file diff --git a/modules/ecomzone/index.php b/modules/ecomzone/index.php new file mode 100644 index 0000000..0daf2f2 --- /dev/null +++ b/modules/ecomzone/index.php @@ -0,0 +1,8 @@ + +
+
+ {l s='EcomZone Settings' mod='ecomzone'} +
+
+
+ +
+ +
+
+
+ +
+ \ No newline at end of file