# Подключение своего магазина к NEDURNO

Инструкция для разработчиков магазинов, ботов и middleware.  
Цель: ваш магазин остаётся **источником правды**, NEDURNO — второй канал продаж (витрина + эскроу + чат + деньги).

```
Ваш магазин ──push каталога (SKU)──▶ NEDURNO (лоты)
       ▲                                │
       │                         оплата / эскроу
       │                                ▼
       └──── deal.paid webhook ◀── сделка ──▶ deliver
```

Базовый URL API: `{API}/api/v1`  
Пример стенда: `http://HOST:8080/api/v1`

OpenAPI: `{API}/api/v1/openapi.yaml` или `/openapi/seller.yaml` на витрине.

---

## 1. Модель на NEDURNO (как устроены деньги и роли)

| Зона | Кто отвечает |
|------|----------------|
| Каталог (SKU, цена, сток, картинка) | **Ваш магазин** → push/upsert на NEDURNO |
| Оплата покупателя | **NEDURNO** (кошелёк / PSP) |
| Эскроу, споры, возвраты | **NEDURNO** |
| Чат сделки | **NEDURNO** (вы отвечаете через API) |
| Выдача цифрового товара | **Вы** (webhook `deal.paid` → `deliver`) |
| Вывод средств продавца | **NEDURNO wallet / payouts** |

Покупатель платит **цену лота**. Комиссия площадки удерживается у **продавца** из суммы (seller-paid fee).  
На сделке продавец видит: `price_minor`, `fee_minor`, `seller_net_minor`.

Деньги в `*_minor` — копейки (19900 = 199,00 ₽).

### Статусы сделки (упрощённо)

```
created → paid → delivered → confirmed → settled
              ↘ disputed / cancelled / expired / refunded
```

- `created` — ждёт оплаты  
- `paid` — оплачено, нужна выдача (**ваш коннектор**)  
- `delivered` — вы вызвали `deliver`, идёт гарантийное окно  
- `confirmed` / `settled` — покупатель подтвердил / автоподтверждение, деньги продавцу  
- `disputed` — спор (в т.ч. если bot не выдал вовремя)

Для канала магазина на лоте ставьте `delivery_mode: "bot"`.

---

## 2. Онбординг в кабинете (без кода)

Профиль → **Integrations**:

1. Стать продавцом (если ещё нет).  
2. Создать **API key** (`mk_live_…`) — секрет один раз.  
3. Указать **Webhook URL** + сохранить HMAC secret.  
4. Настроить **маппинг категорий**: ключ вашего магазина → UUID категории NEDURNO.  
5. Опционально: CSV-импорт, коннекторы Woo/Shopify, тест webhook.

Публичные категории NEDURNO: `GET /api/v1/categories?tree=1`.

---

## 3. Аутентификация

### Bot / Integrations API (машина)

```http
Authorization: Bearer mk_live_…
# или
X-API-Key: mk_live_…
```

Scopes ключа: `listings:read|write`, `deals:read|write`, `chat:read|write`.

Проверка:

```bash
curl -s "$API/api/v1/integrations/v1/me" \
  -H "Authorization: Bearer mk_live_…"
```

### Кабинет (человек)

JWT из `/auth/login` — управление ключами, webhook, category-maps, CSV, shop connectors.

---

## 4. Каталог: upsert по SKU

Идентификатор товара у вас = `external_id` (SKU). Повторный вызов **обновляет** тот же лот (без дублей).

```bash
curl -X PUT "$API/api/v1/integrations/v1/listings/by-external/SKU-100" \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "external_category": "steam-keys",
    "title": "Game Key EU",
    "description": "Region EU",
    "delivery_terms": "Мгновенно после оплаты",
    "price_minor": 49900,
    "delivery_mode": "bot",
    "image_url": "https://cdn.example/sku-100.jpg",
    "quantity": 12,
    "publish": true
  }'
```

### Поля

| Поле | Обязательно | Описание |
|------|-------------|----------|
| `title` | да | Название |
| `description` | да* | Описание (`*` при создании) |
| `delivery_terms` | да* | Условия выдачи |
| `price_minor` | да | Цена в копейках |
| `category_id` **или** `external_category` | да* | UUID NEDURNO или ключ из маппинга (`*` при **создании**; для существующего SKU можно опустить) |
| `delivery_mode` | нет | `manual` \| `auto` \| **`bot`** (для магазина) |
| `image_url` | нет | Публичный http(s) URL; NEDURNO зеркалирует в `/uploads/` (см. §4.2) |
| `quantity` | нет | Остаток |
| `publish` | нет | Отправить на публикацию; первый выход может вернуть `202 pending_review` или сразу `published` (low-risk auto-publish) |
| `title_en` / `description_en` / `delivery_terms_en` | нет | EN |

Рекомендуемый цикл синка у вас:

1. При создании/изменении товара → `PUT …/by-external/{sku}`  
2. При нулевом стоке → `POST …/listings/{id}/pause` или `quantity: 0`  
3. При появлении стока снова → upsert + `publish`

Также: `GET/POST /integrations/v1/listings`, `PUT …/offers`, `POST …/stock` (для `auto`).

### 4.2 Обложки (`image_url`)

NEDURNO **не показывает** внешние URL покупателям. При upsert с `image_url`:

1. Скачивает файл по **GET** (до 10 МБ; JPEG, PNG, WebP, GIF).
2. Проверяет magic bytes. Расширение в URL **не обязательно** (например `/api/files/{uuid}`).
3. Сохраняет копию → на витрине `https://nedurno.com/uploads/{uuid}.webp`.

URL должен отвечать **200** на GET по **HTTPS** (production). HEAD может быть 404 — это нормально.

Для существующего SKU можно отправить только `{ "image_url": "…" }`. При `delivery_mode: bot` обложку в кабинете NEDURNO загружать не обязательно — достаточно синка из магазина.

Маппинг категорий (JWT):  
`GET/PUT /integrations/category-maps`  
Тело: `{ "maps": [ { "external_key": "steam-keys", "category_id": "…" } ] }`

CSV (JWT, без своего кода):  
`POST /integrations/import/csv` (multipart `file`).

---

## 5. Выдача: webhook → deliver

### 5.1. Событие `deal.paid`

NEDURNO POST на ваш webhook:

```http
POST /your/hooks/market
Content-Type: application/json
X-Market-Event: deal.paid
X-Market-Signature: sha256=<hmac_hex>
X-Market-Delivery-Attempt: 1
```

Тело:

```json
{
  "id": "event-id",
  "subject": "deal.paid",
  "occurred_at": "2026-08-06T12:00:00Z",
  "payload": {
    "deal_id": "…",
    "seller_id": "…",
    "listing_id": "…",
    "title": "…",
    "status": "paid",
    "price_minor": 49900
  }
}
```

Подпись: HMAC-SHA256(**raw body**, webhook secret) → hex, префикс `sha256=`.

Ответьте **2xx** быстро. При ошибке NEDURNO ретраит до **8** раз с backoff.

Другие события: `deal.created`, `deal.message`, `deal.delivered`, `deal.confirmed`, `deal.disputed`, `deal.cancelled`, `deal.settled`, тест `integration.ping`.

### 5.2. Выдать товар

```bash
curl -X POST "$API/api/v1/integrations/v1/deals/$DEAL_ID/deliver" \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"body":"Ваш ключ: XXXX-YYYY-ZZZZ"}'
```

`body` уходит в чат сделки как сообщение выдачи.

Если `delivery_mode=bot` и выдача не пришла вовремя — откроется спор (таймаут на стороне NEDURNO).

### 5.3. Типичный алгоритм коннектора

```
on deal.paid:
  1. verify HMAC
  2. load listing by listing_id (or cache SKU map)
  3. allocate item in YOUR shop by external_id / SKU (атомарно!)
  4. POST …/deals/{id}/deliver with the code
  5. optionally decrease stock and upsert quantity
```

**Важно:** не отдавайте один и тот же ключ дважды. Allocate в вашем магазине должен быть идемпотентен по `deal_id`.

### 5.4. Pull fulfillment (площадка сама запрашивает)

По умолчанию — **push**: webhook `deal.paid` → ваш бот → `POST …/deliver`.

Опционально на лоте `delivery_mode=bot`:

```http
PUT /api/v1/listings/{id}/pull-fulfillment
# или Agent:
PUT /integrations/v1/listings/{id}/pull-fulfillment
```

```json
{ "enabled": true, "url": "https://supplier.example/fulfill", "timeout_ms": 8000, "rotate_secret": true }
```

После оплаты NEDURNO делает HTTPS POST (публичные адреса only) с `X-Market-Signature` и телом `{ deal_id, listing_id, external_id, price_minor, currency, quantity, buyer_input }`. Ответ `200` `{ "delivery_text": "…" }` → та же выдача, что Agent `deliver`. Без pull-конфига поведение не меняется (webhook / Woo-Shopify).

---

## 6. Чат

```bash
# история
GET /integrations/v1/deals/{id}/messages

# ответ
POST /integrations/v1/deals/{id}/messages
{"body":"Здравствуйте! Ключ в сообщении выше."}
```

Либо слушайте `deal.message` webhook.

---

## 7. Споры и возвраты

Споры / RMA / рефанды — **только на NEDURNO**.  
Вам достаточно реагировать на `deal.disputed` / статусы и не трогать оплату у себя.

Не проводите оплату за этот заказ в своём PSP — деньги уже на NEDURNO.

---

## 8. Три уровня интеграции

### A. Универсальный коннектор (любой движок)

Ваш middleware:

- sync → `PUT …/by-external/{sku}`  
- listen webhook → `deliver`  

Reference: репозиторий backend `connectors/reference/` (`sync_catalog.py`, `webhook_server.py`).

### B. Hosted Woo / Shopify

В кабинете → Integrations → готовые коннекторы: ключи магазина → кнопка «Синхронизировать».  
Выдача через auto_fulfill на `deal.paid` (если включено).

### C. Без разработки

CSV в кабинете + ручная выдача или `auto`-сток.

---

## 9. Деньги и комиссия

- Покупатель платит `price_minor`.  
- `fee_minor` удерживается у продавца.  
- Продавец получает `seller_net_minor` после settle.  
- Процент комиссии настраивает админ NEDURNO; превью:  
  `GET /fees/preview?price_minor=100000`

Выводы — через кабинет NEDURNO (wallet / payouts), не через ваш магазин.

---

## 10. Ошибки и лимиты

Формат:

```json
{ "error": { "code": "validation", "message": "…" } }
```

Частые коды: `unauthorized`, `forbidden`, `validation`, `not_found`, `conflict`.

Ориентир rate limit Integrations ≈ **120 req/min** с IP → `429`.

Для create/deliver рекомендуется `Idempotency-Key`.

---

## 11. Чеклист перед продом

- [ ] API key с нужными scopes  
- [ ] Webhook HTTPS + проверка подписи  
- [ ] Маппинг категорий  
- [ ] Upsert всех продаваемых SKU с `delivery_mode=bot`  
- [ ] Атомарный allocate по `deal_id`  
- [ ] Обработка `deal.paid` + `deliver` < таймаута bot  
- [ ] Пауза лотов при нулевом стоке  
- [ ] Тест: кабинет → «Тест webhook» / покупка mock  

---

## 12. Полезные эндпоинты (сводка)

| Метод | Путь | Auth |
|-------|------|------|
| GET | `/integrations/v1/me` | API key |
| PUT | `/integrations/v1/listings/by-external/{external_id}` | API key |
| GET/POST | `/integrations/v1/listings` | API key |
| POST | `/integrations/v1/listings/{id}/publish\|pause` | API key |
| GET | `/integrations/v1/deals` | API key |
| POST | `/integrations/v1/deals/{id}/deliver` | API key |
| GET/POST | `/integrations/v1/deals/{id}/messages` | API key |
| GET | `/integrations/v1/category-maps` | API key |
| POST/GET/DELETE | `/integrations/keys` | JWT |
| PUT/GET | `/integrations/webhook` | JWT |
| POST | `/integrations/webhook/test` | JWT |
| GET/PUT | `/integrations/category-maps` | JWT |
| POST | `/integrations/import/csv` | JWT |
| GET/PUT/DELETE | `/integrations/shops` … `/sync` | JWT |
| GET | `/categories?tree=1` | public |
| GET | `/fees/preview?price_minor=` | public |

Вопросы по спеке — Swagger UI на странице **Developers** витрины.

---

## 13. Справка: PlayGate → NEDURNO

| Что | Как |
|-----|-----|
| `external_id` | `pg:{product_id}` |
| `external_category` | `key`, `account`, `offline`, `subscribe`, `steam-gift` |
| `image_url` | `{DOMAIN}/api/files/{file_id}` — id файла обложки, не UUID товара |
| Выдача | webhook `deal.paid` → `deliver` на PlayGate |

На PlayGate: `MARKET_API_*`, webhook secret, `DOMAIN` или `MARKET_FILES_PUBLIC_URL`.  
На NEDURNO: API key, webhook, маппинг категорий с теми же ключами.
