Files
octopus/myproject/integrations/services/marketplaces/recommerce.py
Andrey Smakotin 9fceab9de1 feat(integrations): реализованы методы работы с API Recommerce
Добавлены методы для управления категориями и товарами (CRUD), а также
получение списка заказов с поддержкой пагинации и фильтрации.
2026-01-12 03:51:08 +03:00

169 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сервис для работы с 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)