# API управления товарами — SmartSales AI

Документация по интеграции внешних систем с каталогом товаров.

## Содержание

1. [Обзор](#обзор)
2. [Авторизация](#авторизация)
3. [Настройка API-ключа](#настройка-api-ключа)
4. [Эндпоинты](#эндпоинты)
5. [Поля запроса](#поля-запроса)
6. [Логика работы (upsert)](#логика-работы-upsert)
7. [Примеры запросов](#примеры-запросов)
8. [Удаление товаров](#удаление-товаров)
9. [Список товаров](#список-товаров)
10. [Формат ответа](#формат-ответа)
11. [Коды ошибок](#коды-ошибок)
12. [Примеры интеграции](#примеры-интеграции)
13. [Ограничения и заметки](#ограничения-и-заметки)

---

## Обзор

API позволяет добавлять, обновлять, удалять и получать список товаров в каталоге SmartSales AI из внешней системы (CRM, интернет-магазин, ERP, парсер и т.д.). Поддерживается загрузка как одного товара, так и пакета (batch) за один запрос.

Обновление существующих товаров происходит по внешнему идентификатору `ext_id` — если товар с таким `ext_id` уже есть в базе, он обновляется; иначе создаётся новый.

---

## Авторизация

API-ключ передаётся как query-параметр `hash` в URL запроса:

```
POST /api.php?action=add_product&hash=ВАШ_API_КЛЮЧ
```

> **Важно:** Ключ передаётся именно в URL (query-параметр `hash`), а **не** в заголовке `Authorization`. Запросы без ключа или с неверным ключом отклоняются с HTTP 401.

---

## Настройка API-ключа

API-ключ хранится в таблице `settings` (колонка `key` = `api_key`).

### Способ 1. Через SQL

```sql
INSERT INTO settings (`key`, `value`)
VALUES ('api_key', 'ваш_секретный_ключ')
ON DUPLICATE KEY UPDATE `value` = 'ваш_секретный_ключ';
```

### Способ 2. Через phpMyAdmin / админку БД

1. Откройте таблицу `settings`.
2. Найдите (или создайте) строку с `key` = `api_key`.
3. В поле `value` впишите ваш секретный ключ.

### Рекомендации

- Используйте случайную строку длиной не менее 32 символов, например:
  ```bash
  openssl rand -hex 32
  ```
- Не публикуйте ключ в открытых репозиториях.
- Если ключ отсутствует в таблице `settings`, **все запросы к API будут отклоняться** с 401.

---

## Эндпоинты

Все эндпоинты используют одинаковый способ авторизации — query-параметр `hash`.

| Действие | URL | Метод | Описание |
|---|---|---|---|
| **Добавить / обновить** | `/api.php?action=add_product&hash=КЛЮЧ` | `POST` | Создаёт новые товары или обновляет существующие по `ext_id` (upsert) |
| **Удалить** | `/api.php?action=delete_product&hash=КЛЮЧ` | `POST` | Удаляет товары по `ext_id` или `id` |
| **Список** | `/api.php?action=list_products&hash=КЛЮЧ` | `GET` | Возвращает товары с пагинацией |

### Общие параметры

| Параметр | Где | Описание |
|---|---|---|
| `hash` | query | API-ключ (обязательный) |
| `Content-Type: application/json` | header | Для POST-запросов |

---

## Поля запроса

Запрос отправляется в теле в формате JSON. Можно передать один объект товара или массив объектов (batch).

| Поле | Тип | Обязательное | Значение по умолчанию | Описание |
|---|---|---|---|---|
| `name` | string | **Да** | — | Название товара. Если пусто — товар пропускается |
| `price` | number | **Да** | — | Цена. Если пусто — товар пропускается |
| `description` | string | Нет | `""` | Описание товара |
| `category` | string | Нет | `"Общее"` | Категория товара |
| `url` | string \| null | Нет | `null` | Ссылка на страницу товара (до 500 символов) |
| `ext_id` | string \| null | Нет | `null` | Внешний идентификатор товара для синхронизации. Используется для upsert |

### Пример одного товара

```json
{
  "name": "iPhone 15 Pro",
  "description": "256GB, титановый корпус, A17 Pro",
  "category": "Электроника",
  "price": 129000,
  "url": "https://shop.ru/iphone-15-pro",
  "ext_id": "ext_12345"
}
```

### Пример пакета (batch)

```json
[
  {
    "name": "iPhone 15 Pro",
    "price": 129000,
    "ext_id": "ext_001",
    "category": "Электроника"
  },
  {
    "name": "Samsung Galaxy S24",
    "price": 99000,
    "ext_id": "ext_002",
    "category": "Электроника"
  }
]
```

---

## Логика работы (upsert)

Для каждого товара в запросе:

1. Проверяется наличие `name` и `price`. Если хотя бы одно поле пусто — товар **пропускается** (без ошибки).
2. Если указан `ext_id` и товар с таким `ext_id` уже существует в базе → **обновляются** поля `name`, `description`, `category`, `price`, `url`.
3. Если `ext_id` не указан или товар не найден → **создаётся** новый товар.

Поля `attributes`, `stock`, `popularity` через API не передаются и не обновляются:
- `stock` = 1 (в наличии)
- `popularity` = 0
- `attributes` = NULL

Эти поля можно изменить вручную через админ-панель.

---

## Примеры запросов

### curl — один товар

```bash
curl -X POST "https://ваш-домен/api.php?action=add_product&hash=ВАШ_API_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "iPhone 15 Pro",
    "description": "256GB, титановый корпус, A17 Pro",
    "category": "Электроника",
    "price": 129000,
    "url": "https://shop.ru/iphone-15-pro",
    "ext_id": "ext_12345"
  }'
```

### curl — пакет товаров

```bash
curl -X POST "https://ваш-домен/api.php?action=add_product&hash=ВАШ_API_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "Товар A", "price": 100, "ext_id": "a1"},
    {"name": "Товар B", "price": 200, "ext_id": "b2", "category": "Аксессуары"}
  ]'
```

---

## Удаление товаров

Эндпоинт: `POST /api.php?action=delete_product&hash=КЛЮЧ`

Удаляет товары по `ext_id` или `id`. Можно передать один объект или массив.

### Запрос

```json
[
  {"ext_id": "bx_123"},
  {"ext_id": "bx_456"},
  {"id": 42}
]
```

### curl

```bash
curl -X POST "https://ваш-домен/api.php?action=delete_product&hash=ВАШ_API_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '[{"ext_id": "bx_123"}, {"ext_id": "bx_456"}]'
```

### Ответ

```json
{
  "success": true,
  "results": {
    "deleted": 2
  }
}
```

---

## Список товаров

Эндпоинт: `GET /api.php?action=list_products&hash=КЛЮЧ`

Возвращает товары с пагинацией. Поддерживает фильтр по категории.

### Параметры query

| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| `page` | int | 1 | Номер страницы |
| `limit` | int | 50 | Товаров на странице (макс. 100) |
| `category` | string | — | Фильтр по категории (необязательный) |

### curl

```bash
curl "https://ваш-домен/api.php?action=list_products&hash=ВАШ_API_КЛЮЧ&page=1&limit=50"
```

### Ответ

```json
{
  "success": true,
  "results": {
    "products": [
      {
        "id": 12,
        "ext_id": "bx_123",
        "name": "iPhone 15 Pro",
        "description": "256GB",
        "category": "Электроника",
        "price": "129000.00",
        "url": "https://shop.ru/iphone-15-pro",
        "stock": 1,
        "created_at": "2024-01-15 10:30:00",
        "updated_at": "2024-01-15 12:00:00"
      }
    ],
    "total": 150,
    "page": 1,
    "limit": 50,
    "pages": 3
  }
}
```

---

## Формат ответа

### Успешный ответ (HTTP 200)

**add_product:**
```json
{
  "success": true,
  "results": {
    "added": 2,
    "updated": 1
  }
}
```

**delete_product:**
```json
{
  "success": true,
  "results": {
    "deleted": 3
  }
}
```

| Поле | Описание |
|---|---|
| `success` | `true` при успешной обработке |
| `results.added` | Количество созданных товаров (add_product) |
| `results.updated` | Количество обновлённых товаров (add_product) |
| `results.deleted` | Количество удалённых товаров (delete_product) |

> Товары без `name` или `price` считаются пропущенными и не отражаются в ответе отдельно — они просто не учитываются в `added`/`updated`.

---

## Коды ошибок

| HTTP-код | Условие | Тело ответа |
|---|---|---|
| **200** | Успех | `{"success": true, "results": {...}}` |
| **401** | Нет ключа `hash` или ключ неверный | `{"success": false, "error": "Invalid or missing API hash"}` |
| **500** | Внутренняя ошибка БД | `{"success": false, "error": "<текст ошибки>"}` |

---

## Примеры интеграции

### PHP — добавление товаров

```php
$apiKey = 'ВАШ_API_КЛЮЧ';
$baseUrl = 'https://ваш-домен/api.php';

$products = [
    ['name' => 'Товар A', 'price' => 1000, 'ext_id' => 'a1', 'category' => 'Общее'],
    ['name' => 'Товар B', 'price' => 2000, 'ext_id' => 'b2', 'category' => 'Общее'],
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?action=add_product&hash=' . $apiKey);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($products));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP $httpCode: $response\n";
```

### PHP — удаление товаров

```php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?action=delete_product&hash=' . $apiKey);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([['ext_id' => 'a1'], ['ext_id' => 'b2']]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response . "\n";
```

### PHP — получение списка товаров

```php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?action=list_products&hash=' . $apiKey . '&page=1&limit=50');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "Всего товаров: " . $data['results']['total'] . "\n";
```

### Python

```python
import requests

api_key = 'ВАШ_API_КЛЮЧ'
url = 'https://ваш-домен/api.php'

products = [
    {'name': 'Товар A', 'price': 1000, 'ext_id': 'a1', 'category': 'Общее'},
    {'name': 'Товар B', 'price': 2000, 'ext_id': 'b2', 'category': 'Общее'},
]

response = requests.post(
    f'{url}?action=add_product&hash={api_key}',
    json=products,
    headers={'Content-Type': 'application/json'}
)

print(response.status_code, response.json())
```

### JavaScript (fetch)

```javascript
const apiKey = 'ВАШ_API_КЛЮЧ';
const url = `https://ваш-домен/api.php?action=add_product&hash=${apiKey}`;

const products = [
  { name: 'Товар A', price: 1000, ext_id: 'a1', category: 'Общее' },
  { name: 'Товар B', price: 2000, ext_id: 'b2', category: 'Общее' },
];

const response = await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(products),
});

const data = await response.json();
console.log(response.status, data);
```

---

## Ограничения и заметки

- **Ограничение размера пакета** не задано явно, но при больших пакетах (тысячи товаров за один запрос) возможны таймаут PHP или нехватка памяти. Рекомендуется отправлять пакетами по 100–500 товаров.
- **Rate limiting** на эндпоинте импорта не настроен.
- **Поля `stock`, `attributes`, `popularity`** не передаются через API. После импорта их можно задать вручную в админ-панели.
- **Товары без `name` или `price`** пропускаются без ошибки — в ответе не отражается, сколько товаров было пропущено.
- **`ext_id` рекомендуется указывать всегда** — это обеспечивает идемпотентность: повторная отправка того же пакета обновит существующие товары, а не создаст дубликаты.
- При первом запросе на импорт колонка `ext_id` создаётся автоматически (если её ещё нет в таблице `products`).