Добавлены методы для управления категориями и товарами (CRUD), а также получение списка заказов с поддержкой пагинации и фильтрации.
169 lines
6.2 KiB
Python
169 lines
6.2 KiB
Python
"""
|
||
Сервис для работы с Recommerce API.
|
||
|
||
API документация:
|
||
- Запросы отправляются на домен магазина (store_url)
|
||
- Авторизация через заголовок x-auth-token
|
||
- POST данные передаются как form-data
|
||
- Ответы в JSON
|
||
"""
|
||
import requests
|
||
from typing import Tuple
|
||
|
||
|
||
class RecommerceService:
|
||
"""Сервис для работы с Recommerce API"""
|
||
|
||
def __init__(self, integration):
|
||
"""
|
||
Args:
|
||
integration: RecommerceIntegration instance
|
||
"""
|
||
self.integration = integration
|
||
|
||
def _get_headers(self) -> dict:
|
||
"""Заголовки для API запросов"""
|
||
token = self.integration.api_token or ''
|
||
# HTTP заголовки должны быть ASCII-совместимыми
|
||
return {
|
||
'x-auth-token': token.encode('ascii', 'ignore').decode('ascii'),
|
||
'Accept': 'application/json',
|
||
}
|
||
|
||
def _get_url(self, path: str) -> str:
|
||
"""Полный URL для API endpoint"""
|
||
base = self.integration.store_url.rstrip('/')
|
||
return f"{base}/api/v1/{path.lstrip('/')}"
|
||
|
||
def _request(self, method: str, path: str, **kwargs) -> Tuple[bool, dict, str]:
|
||
"""
|
||
HTTP запрос к API.
|
||
|
||
Returns:
|
||
(success, data, error_message)
|
||
"""
|
||
url = self._get_url(path)
|
||
try:
|
||
response = requests.request(
|
||
method,
|
||
url,
|
||
headers=self._get_headers(),
|
||
timeout=15,
|
||
**kwargs
|
||
)
|
||
|
||
if response.status_code in [200, 201, 204]:
|
||
if response.text:
|
||
return True, response.json(), ''
|
||
return True, {}, ''
|
||
else:
|
||
return False, {}, f"HTTP {response.status_code}: {response.text[:200]}"
|
||
|
||
except requests.exceptions.Timeout:
|
||
return False, {}, 'Таймаут соединения (15 сек)'
|
||
except requests.exceptions.ConnectionError:
|
||
return False, {}, 'Не удалось подключиться к серверу'
|
||
except Exception as e:
|
||
return False, {}, str(e)
|
||
|
||
def test_connection(self) -> Tuple[bool, str]:
|
||
"""
|
||
Проверить соединение с Recommerce API.
|
||
|
||
Returns:
|
||
(success, message)
|
||
"""
|
||
if not self.integration.store_url:
|
||
return False, 'Не указан URL магазина'
|
||
if not self.integration.api_token:
|
||
return False, 'Не указан API токен'
|
||
|
||
url = self.integration.store_url.rstrip('/') + '/api/v1/'
|
||
try:
|
||
response = requests.get(
|
||
url,
|
||
headers=self._get_headers(),
|
||
timeout=15
|
||
)
|
||
|
||
if response.status_code == 401:
|
||
return False, 'Неверный API токен'
|
||
if response.status_code == 403:
|
||
return False, 'Доступ запрещён'
|
||
|
||
# Любой ответ от сервера = соединение работает
|
||
return True, 'Соединение установлено'
|
||
|
||
except requests.exceptions.Timeout:
|
||
return False, 'Таймаут соединения (15 сек)'
|
||
except requests.exceptions.ConnectionError:
|
||
return False, 'Не удалось подключиться к серверу'
|
||
except Exception as e:
|
||
return False, str(e)
|
||
|
||
# ========== Categories ==========
|
||
|
||
def get_category(self, sku: str) -> Tuple[bool, dict, str]:
|
||
"""Получить категорию по SKU"""
|
||
return self._request('GET', f'catalog/categories/{sku}')
|
||
|
||
def create_category(self, data: dict) -> Tuple[bool, dict, str]:
|
||
"""
|
||
Создать категорию.
|
||
|
||
Обязательные поля в data:
|
||
- sku: артикул категории
|
||
- title: название категории
|
||
"""
|
||
return self._request('POST', 'catalog/categories', data=data)
|
||
|
||
def update_category(self, sku: str, data: dict) -> Tuple[bool, dict, str]:
|
||
"""Обновить категорию (только переданные поля)"""
|
||
return self._request('POST', f'catalog/categories/{sku}', data=data)
|
||
|
||
def delete_category(self, sku: str) -> Tuple[bool, dict, str]:
|
||
"""Удалить категорию"""
|
||
return self._request('DELETE', f'catalog/categories/{sku}')
|
||
|
||
# ========== Products ==========
|
||
|
||
def get_product(self, sku: str) -> Tuple[bool, dict, str]:
|
||
"""Получить товар по SKU"""
|
||
return self._request('GET', f'catalog/products/{sku}')
|
||
|
||
def create_product(self, data: dict) -> Tuple[bool, dict, str]:
|
||
"""
|
||
Создать товар.
|
||
|
||
Обязательные поля в data:
|
||
- name: название товара
|
||
- sku: артикул товара
|
||
- parent_category_sku: артикул категории
|
||
- price[amount]: цена
|
||
- price[currency]: валюта (BYN|RUB|USD|EUR|KZT)
|
||
"""
|
||
return self._request('POST', 'catalog/products', data=data)
|
||
|
||
def update_product(self, sku: str, data: dict) -> Tuple[bool, dict, str]:
|
||
"""Обновить товар (только переданные поля)"""
|
||
return self._request('POST', f'catalog/products/{sku}', data=data)
|
||
|
||
def delete_product(self, sku: str) -> Tuple[bool, dict, str]:
|
||
"""Удалить товар"""
|
||
return self._request('DELETE', f'catalog/products/{sku}')
|
||
|
||
# ========== Orders ==========
|
||
|
||
def get_orders(self, page: int = 1, updated_after: str = None) -> Tuple[bool, dict, str]:
|
||
"""
|
||
Получить список заказов.
|
||
|
||
Args:
|
||
page: номер страницы (по 100 заказов)
|
||
updated_after: фильтр 'Y-m-d-H-i-s' - только заказы после даты
|
||
"""
|
||
params = {'page': page}
|
||
if updated_after:
|
||
params['updated_after'] = updated_after
|
||
return self._request('GET', 'orders', params=params)
|