# Originos Merchant Admin API

Bu rehber, mağaza sahibinin veya mağaza ekibinin React, Vue, Next.js, mobil uygulama ya da başka bir istemciyle kendi yönetim panelini geliştirmesi içindir. Merchant authentication, dashboard, katalog, stok, sipariş, ödeme, kargo, indirim, dijital ürün, EPIN, ayar, tema ve webhook API'lerinin tamamını kapsar.

## 1. API adresi

Production:

```text
https://originos.netkora.com/api/v1
```

Local:

```text
http://127.0.0.1:8000/api/v1
```

Merchant endpointleri `/admin` prefix'i altındadır.

```text
https://originos.netkora.com/api/v1/admin
```

## 2. Authentication

### Login

```http
POST /admin/auth/login
Accept: application/json
Content-Type: application/json
X-Store-Key: pk_live_...
```

```json
{
  "email": "owner@example.com",
  "password": "YOUR_PASSWORD",
  "device_name": "merchant-dashboard"
}
```

`email` ve `password` zorunlu, `device_name` opsiyonel ve en fazla 100 karakterdir. Kullanıcı aktif olmalı ve `X-Store-Key` ile çözülen mağazaya bağlı olmalıdır.

Başarılı yanıt:

```json
{
  "data": {
    "token": "1|MERCHANT_BEARER_TOKEN",
    "token_type": "Bearer",
    "user": {
      "id": "USER_UUID",
      "name": "Mağaza Sahibi",
      "email": "owner@example.com"
    }
  }
}
```

Aynı `device_name` ile yeniden login olunduğunda önceki aynı adlı token iptal edilir.

### Authenticated istekler

Login dışındaki bütün merchant endpointlerinde iki header birlikte gönderilir:

```http
Accept: application/json
Authorization: Bearer 1|MERCHANT_BEARER_TOKEN
X-Store-Key: pk_live_...
```

JSON mutasyonlarında ayrıca:

```http
Content-Type: application/json
```

### Logout

```http
POST /admin/auth/logout
```

Yalnız geçerli bearer token iptal edilir.

### Güvenlik notları

- Merchant token yalnız mağazanın güvenilir yönetim panelinde kullanılmalıdır.
- Token URL, analytics, public log veya tema bundle'ına yazılmamalıdır.
- `X-Store-Key` ile seçilen mağaza ve bearer kullanıcısının üyeliği eşleşmelidir.
- Aynı kullanıcı birden fazla mağazaya bağlı olabilir; mağaza değiştirirken ilgili store key kullanılmalıdır.
- Secret `sk_...` API key'i tarayıcıya gömülmemelidir. Merchant login için public store key yeterlidir.

## 3. Rol ve izin sistemi

Mağaza üyeliği rolleri:

| Rol | Varsayılan erişim |
|---|---|
| `owner` | Tüm merchant işlemleri |
| `admin` | Tüm merchant işlemleri |
| `manager` | Dashboard, ürün, sipariş, müşteri CRM, stok, kargo, indirim, EPIN, webhook, ayar ve tema yönetimi |
| `staff` | Ürün ve sipariş görüntüleme |

Üyeliğe özel permission değerleri role ek yetki verebilir. Başlıca permission anahtarları:

```text
dashboard.view
products.view
products.create
products.update
products.delete
orders.view
orders.update
customers.view
customers.update
inventory.view
inventory.update
shipping.view
shipping.update
discounts.view
discounts.update
epins.view
epins.update
epins.import
epins.reveal
webhooks.view
webhooks.update
settings.view
settings.update
themes.view
themes.update
notifications.view
notifications.update
reports.view
```

Yetki yoksa `403 PERMISSION_DENIED` veya policy kaynaklı `403` döner. Frontend menüleri role göre gizleyebilir; gerçek güvenlik kontrolü her zaman backend'dedir.

## 4. Plan ve mağaza durumları

- Yazma işlemlerinin çoğu aktif veya trialing subscription gerektirir.
- Subscription süresi dolmuşsa `403 SUBSCRIPTION_EXPIRED` döner.
- Suspended/disabled mağazada merchant API `403 STORE_SUSPENDED` döner.
- `maintenance` durumu merchant yönetimini engellemez; storefront yazma işlemlerini engeller.
- Plan ürün limiti aşılırsa `409 PLAN_LIMIT_REACHED` döner.
- EPIN endpointleri `epin_enabled` gerektirir.
- Dijital dosya endpointleri `digital_products_enabled` gerektirir.
- Tema endpointleri `themes_enabled`, ücretli tema ise ayrıca `premium_themes_enabled` gerektirir.
- Webhook endpointleri `webhooks_enabled` gerektirir.

## 5. Genel veri kuralları

### Para

Bütün parasal değerler integer minor unit olarak gönderilir:

```json
{"amount": 12990, "currency": "TRY"}
```

Bu değer `129,90 TRY` anlamına gelir. Ürün `price_amount`, discount minimumu ve refund amount değerlerinde decimal kullanılmaz.

### Kimlikler

Public kaynak kimlikleri UUID'dir. Route parametrelerinde response içindeki `id` kullanılır. Ürün detay route'u admin tarafında slug değil UUID kullanır.

### Tarihler

Tarih/zaman değerleri ISO 8601 formatındadır. Order filtrelerinde `YYYY-MM-DD` kullanılabilir.

### Sayfalama

Çoğu liste endpointi:

```text
?page=1&per_page=20
```

`per_page` maksimum `100` değeridir. Shipping, discount, theme, EPIN pool ve webhook listeleri şu anda sabit 50 kayıtlık pagination kullanır.

### Rate limit

- Merchant API: kullanıcı/mağaza başına 60 istek/dakika
- Login: e-posta/IP başına 10 istek/dakika
- EPIN reveal: kullanıcı/mağaza başına 10 istek/dakika

Limit aşıldığında `429 Too Many Requests` döner.

## 6. Tüm endpointler

### Auth ve dashboard

| Metot | Endpoint |
|---|---|
| POST | `/admin/auth/login` |
| POST | `/admin/auth/logout` |
| GET | `/admin/dashboard` |

### Ürünler, seçenekler ve medya

| Metot | Endpoint |
|---|---|
| GET | `/admin/products` |
| POST | `/admin/products` |
| GET | `/admin/products/{product}` |
| PATCH | `/admin/products/{product}` |
| DELETE | `/admin/products/{product}` |
| PUT | `/admin/products/{product}/options` |
| POST | `/admin/products/{product}/media` |
| PUT | `/admin/products/{product}/media/order` |
| DELETE | `/admin/products/{product}/media/{media}` |
| GET | `/admin/products/{product}/digital-files` |
| POST | `/admin/products/{product}/digital-files` |
| DELETE | `/admin/products/{product}/digital-files/{digitalFile}` |

### Müşteri CRM

| Metot | Endpoint |
|---|---|
| GET | `/admin/customers` |
| GET | `/admin/customers/{customer}` |
| PATCH | `/admin/customers/{customer}` |
| GET | `/admin/customers/{customer}/orders` |
| POST | `/admin/customers/{customer}/addresses` |
| PATCH/DELETE | `/admin/customers/{customer}/addresses/{address}` |
| GET/POST | `/admin/customers/{customer}/notes` |
| PATCH/DELETE | `/admin/customers/{customer}/notes/{note}` |

### Kategoriler ve stok

| Metot | Endpoint |
|---|---|
| GET | `/admin/categories` |
| POST | `/admin/categories` |
| GET | `/admin/categories/{category}` |
| PUT/PATCH | `/admin/categories/{category}` |
| DELETE | `/admin/categories/{category}` |
| POST/DELETE | `/admin/categories/{category}/image` |
| GET | `/admin/inventory` |
| GET | `/admin/inventory/movements` |
| GET | `/admin/inventory/reservations` |
| POST | `/admin/inventory/reservations/{reservation}/release` |
| GET | `/admin/inventory/{variant}` |
| POST | `/admin/inventory/{variant}/adjustments` |
| PATCH | `/admin/inventory/{variant}/settings` |

### Kargo ve indirimler

| Metot | Endpoint |
|---|---|
| GET | `/admin/shipping-methods` |
| POST | `/admin/shipping-methods` |
| GET | `/admin/shipping-methods/{shipping_method}` |
| PUT/PATCH | `/admin/shipping-methods/{shipping_method}` |
| DELETE | `/admin/shipping-methods/{shipping_method}` |
| GET | `/admin/discounts` |
| POST | `/admin/discounts` |
| GET | `/admin/discounts/{discount}` |
| PUT/PATCH | `/admin/discounts/{discount}` |
| DELETE | `/admin/discounts/{discount}` |

### Sipariş ve ödeme

| Metot | Endpoint |
|---|---|
| GET | `/admin/orders` |
| GET | `/admin/orders/{order}` |
| PATCH | `/admin/orders/{order}/status` |
| PATCH | `/admin/orders/{order}/fulfillment-status` |
| POST | `/admin/orders/{order}/cancel` |
| POST | `/admin/orders/{order}/refund-requests` |
| POST | `/admin/orders/{order}/payments/manual/complete` |
| POST | `/admin/orders/{order}/payments/bank-transfer/complete` |

### EPIN

| Metot | Endpoint |
|---|---|
| GET | `/admin/epin-pools` |
| POST | `/admin/epin-pools` |
| GET | `/admin/epin-pools/{epinPool}` |
| PUT | `/admin/epin-pools/{epinPool}` |
| DELETE | `/admin/epin-pools/{epinPool}` |
| GET | `/admin/epin-pools/{epinPool}/codes` |
| POST | `/admin/epin-pools/{epinPool}/codes/import` |
| POST | `/admin/epin-pools/{epinPool}/imports` |
| GET | `/admin/epin-imports/{epinImport}` |
| POST | `/admin/epin-codes/{epinCode}/reveal` |

### Ayarlar ve temalar

| Metot | Endpoint |
|---|---|
| GET | `/admin/settings` |
| PATCH | `/admin/settings` |
| POST/DELETE | `/admin/settings/logo` |
| GET | `/admin/themes` |
| GET | `/admin/theme` |
| PUT | `/admin/theme` |
| PUT | `/admin/theme/settings` |

### Webhooklar

| Metot | Endpoint |
|---|---|
| GET | `/admin/webhook-events` |
| GET | `/admin/webhooks` |
| POST | `/admin/webhooks` |
| GET | `/admin/webhooks/{webhook}` |
| PUT/PATCH | `/admin/webhooks/{webhook}` |
| DELETE | `/admin/webhooks/{webhook}` |
| POST | `/admin/webhooks/{webhook}/rotate-secret` |
| GET | `/admin/webhook-deliveries` |
| GET | `/admin/webhook-deliveries/{delivery}` |
| POST | `/admin/webhook-deliveries/{delivery}/retry` |
| POST | `/admin/webhook-deliveries/{delivery}/replay` |

## 7. Dashboard

```http
GET /admin/dashboard?days=30
```

`days` 1–365 arasındadır, varsayılan 30.

```json
{
  "data": {
    "period": {"days": 30, "from": "2026-07-12", "to": "2026-08-10"},
    "orders_count": 120,
    "paid_orders_count": 98,
    "gross_revenue": {"amount": 1589000, "currency": "TRY"},
    "average_order_value": {"amount": 16214, "currency": "TRY"},
    "customers_count": 84,
    "active_products_count": 52,
    "low_stock_count": 4,
    "out_of_stock_count": 2,
    "backordered_count": 1,
    "orders_by_status": {"pending": 10, "processing": 6, "completed": 104},
    "revenue_by_day": [
      {"date": "2026-08-10", "amount": 129900, "orders": 5}
    ],
    "recent_orders": [
      {
        "id": "ORDER_UUID",
        "order_number": "100001",
        "status": "pending",
        "payment_status": "pending",
        "total_amount": 129900,
        "currency": "TRY",
        "placed_at": "2026-08-10T12:00:00+03:00"
      }
    ]
  }
}
```

Gelir metrikleri yalnız `paid` siparişleri kapsar. Recent orders en fazla 10 kayıttır.

## 8. Ürün yönetimi

### Ürün listesi

```http
GET /admin/products?search=kulaklık&filter[status]=active&filter[type]=physical&sort=-created_at&page=1&per_page=20
```

Desteklenen filtreler:

- `filter[status]`: `draft`, `active`, `archived`
- `filter[type]`: `physical`, `digital`, `epin`
- `filter[sku]`
- `search`: name, slug veya SKU
- `sort`: `name`, `created_at`, `published_at`; başına `-` gelirse descending

Liste response'u ürünler, temel varyantlar ve kategorileri içerir. Tam seçenek/media/stok verisi için detail çağrılır.

### Ürün oluşturma

```http
POST /admin/products
```

Fiziksel ürün:

```json
{
  "name": "Kablosuz Kulaklık",
  "slug": "kablosuz-kulaklik",
  "type": "physical",
  "status": "active",
  "visibility": "public",
  "description": "Ürün açıklaması",
  "short_description": "Kısa açıklama",
  "brand": "Originos",
  "taxable": true,
  "sku": "KB-001",
  "price_amount": 129900,
  "currency": "TRY",
  "quantity": 20,
  "category_ids": ["CATEGORY_UUID"],
  "physical": {
    "weight": 350,
    "weight_unit": "g",
    "width": 20,
    "height": 10,
    "length": 25,
    "dimension_unit": "cm"
  }
}
```

Dijital ürün:

```json
{
  "name": "E-kitap",
  "slug": "e-kitap",
  "type": "digital",
  "status": "active",
  "price_amount": 29900,
  "currency": "TRY",
  "digital": {
    "delivery_type": "download",
    "download_limit": 5,
    "download_expiry_hours": 720
  }
}
```

EPIN ürün oluşturmak için `type: epin` ve aktif `epin_enabled` plan özelliği gerekir.

Alan kuralları:

| Alan | Kural |
|---|---|
| `name` | Zorunlu, max 255 |
| `slug` | Zorunlu, mağazada unique, alpha-dash |
| `type` | `physical`, `digital`, `epin` |
| `status` | `draft`, `active`, `archived` |
| `visibility` | `public`, `hidden`; varsayılan `public` |
| `price_amount` | Integer, minimum 0 |
| `currency` | Üç harf; kayıtta uppercase olur |
| `quantity` | Integer, minimum 0 |
| `category_ids` | Aynı mağazaya ait category UUID listesi |
| `weight_unit` | `g`, `kg`, `lb`, `oz` |
| `dimension_unit` | `mm`, `cm`, `m`, `in` |
| `delivery_type` | `download`, `external` |

Ürün ilk oluşturulduğunda `Default` adlı bir varyant otomatik oluşur. Physical ürün için inventory, digital ürün için digital metadata oluşturulur.

### Ürün detayı

```http
GET /admin/products/{product}
```

Response; categories, variants/inventory, option values, media, physical/digital metadata içerir.

### Ürün güncelleme

```http
PATCH /admin/products/{product}
```

Kısmi güncellemedir:

```json
{
  "name": "Yeni ürün adı",
  "status": "active",
  "price_amount": 139900,
  "compare_at_price_amount": 159900,
  "quantity": 25,
  "category_ids": ["CATEGORY_UUID"]
}
```

`type` ve ilk SKU bu endpoint üzerinden değiştirilmez. `category_ids: []` tüm kategori bağlantılarını kaldırır. Aktif yapılan ürünün `published_at` değeri atanır.

### Ürün silme

```http
DELETE /admin/products/{product}
```

Başarılı yanıt `204 No Content`.

## 9. Ürün seçenekleri ve varyantlar

```http
PUT /admin/products/{product}/options
```

En fazla 3 option, option başına 50 value ve toplam 200 varyant kabul edilir.

```json
{
  "options": [
    {"name": "Renk", "values": ["Siyah", "Beyaz"]},
    {"name": "Beden", "values": ["S", "M"]}
  ],
  "variants": [
    {
      "sku": "TS-SIYAH-S",
      "title": "Siyah / S",
      "price_amount": 49900,
      "currency": "TRY",
      "quantity": 10,
      "values": ["Siyah", "S"]
    },
    {
      "sku": "TS-BEYAZ-M",
      "title": "Beyaz / M",
      "price_amount": 51900,
      "currency": "TRY",
      "quantity": 7,
      "values": ["Beyaz", "M"]
    }
  ]
}
```

Her varyantın `values` kombinasyonu tanımlanan option değerleriyle eşleşmelidir. İşlem atomiktir ve product detail döndürür.

## 10. Ürün medyası

```http
POST /admin/products/{product}/media
Content-Type: multipart/form-data
```

Form alanları:

| Alan | Kural |
|---|---|
| `file` | Zorunlu; jpg, jpeg, png, webp, gif veya mp4; max 10 MB |
| `variant_id` | Opsiyonel, ürüne ait variant UUID |
| `alt_text` | Opsiyonel, max 255 |
| `sort_order` | Opsiyonel integer, min 0 |

Response `201`:

```json
{
  "data": {
    "id": "MEDIA_UUID",
    "type": "image",
    "url": "https://cdn.example.com/product.jpg",
    "alt_text": "Ürün görseli"
  }
}
```

Silme:

```http
DELETE /admin/products/{product}/media/{media}
```

Galeri sırasını kalıcı olarak değiştirmek için ürünün bütün medya kimlikleri istenen sırada gönderilir. Liste eksik, tekrarlı veya başka ürüne ait bir kimlik içeriyorsa işlem atomik olarak reddedilir.

```http
PUT /admin/products/{product}/media/order
Content-Type: application/json

{
  "media_ids": ["media-uuid-2", "media-uuid-1"]
}
```

Storage dosyası ve DB kaydı silinir; yanıt `204`.

## 11. Dijital ürün dosyaları

Yalnız `digital` ürün ve `digital_products_enabled` plan özelliği için kullanılabilir.

```http
GET /admin/products/{product}/digital-files
```

Yükleme:

```http
POST /admin/products/{product}/digital-files
Content-Type: multipart/form-data
```

| Alan | Kural |
|---|---|
| `file` | pdf, zip, epub, mp3, mp4 veya txt; max 100 MB |
| `name` | Zorunlu, max 255 |
| `variant_id` | Opsiyonel, aynı ürüne ait varyant |
| `status` | `active`, `inactive` |
| `sort_order` | Integer, min 0 |

Dosya private disk üzerinde saklanır; storage path API'de gösterilmez.

Silme:

```http
DELETE /admin/products/{product}/digital-files/{digitalFile}
```

Satın alınmış/download grant'i bulunan dosya `422 DIGITAL_FILE_IN_USE` nedeniyle silinemez.

## 12. Kategoriler

### Liste

```http
GET /admin/categories?search=elektronik&filter[status]=active&sort=sort_order&page=1&per_page=20
```

Filtre: `filter[status]`. Sort: `name`, `sort_order`, `created_at`; `-` prefix descending.

### Oluşturma

```http
POST /admin/categories
```

```json
{
  "name": "Elektronik",
  "slug": "elektronik",
  "parent_id": null,
  "description": "Kategori açıklaması",
  "status": "active",
  "sort_order": 10,
  "meta_title": "Elektronik Ürünler",
  "meta_description": "Elektronik ürün kataloğu"
}
```

`name`, `slug` ve `status` zorunludur. `status`: `active` veya `inactive`. `parent_id` aynı mağazaya ait kategori UUID'sidir.

Detail/update/delete:

```http
GET /admin/categories/{category}
PUT /admin/categories/{category}
PATCH /admin/categories/{category}
DELETE /admin/categories/{category}
```

Kategori görseli `multipart/form-data` ile yönetilir. `image` alanı jpg, jpeg, png, webp veya gif ve en fazla 10 MB olabilir:

```http
POST   /admin/categories/{category}/image
DELETE /admin/categories/{category}/image
```

Category response `image_url` döndürür. Yeni upload eski dosyayı güvenli biçimde değiştirir.

Kategori request'i update sırasında da zorunlu alanların tamamını bekler; PATCH kullanılsa bile `name`, `slug` ve `status` gönderilmelidir.

## 13. Stok

### Liste

```http
GET /admin/inventory?search=KB-001&status=low_stock&sort=available_quantity&direction=asc&page=1&per_page=20
```

- `search`: varyant SKU veya ürün adı
- `status`: `in_stock`, `low_stock`, `out_of_stock`, `backordered` veya `untracked`
- `track_inventory`, `allow_backorder`: boolean filtreler
- `product_id`: ürün UUID'sine göre filtre
- `sort`: `quantity`, `reserved_quantity`, `available_quantity`, `updated_at`
- Response metrikleri toplam varyant, tracked/untracked, düşük/tükenmiş/backorder sayıları ile on-hand, reserved ve available toplamlarını içerir.
- Eski `low_stock=true` filtresi geriye uyumluluk için çalışmaya devam eder.

```json
{
  "data": [
    {
      "variant": {
        "id": "VARIANT_UUID",
        "sku": "KB-001",
        "title": "Default",
        "product": {"id": "PRODUCT_UUID", "name": "Kulaklık"}
      },
      "quantity": 20,
      "on_hand_quantity": 20,
      "reserved_quantity": 3,
      "available_quantity": 17,
      "stock_status": "in_stock",
      "track_inventory": true,
      "allow_backorder": false,
      "low_stock_threshold": 5,
      "is_low_stock": false
    }
  ],
  "metrics": {
    "total_variants": 42,
    "tracked_count": 40,
    "untracked_count": 2,
    "low_stock_count": 2,
    "out_of_stock_count": 1,
    "backordered_count": 0,
    "on_hand_quantity": 840,
    "reserved_quantity": 23,
    "available_quantity": 817
  }
}
```

### Atomik stok düzeltmesi

```http
POST /admin/inventory/{variant}/adjustments
```

```json
{
  "operation": "increase",
  "quantity": 25,
  "reason": "Mal kabulü",
  "reference": "IRSALIYE-2026-001"
}
```

- `set`: on-hand stoğu gönderilen mutlak değere getirir.
- `increase` ve `decrease`: mevcut on-hand değerine delta uygular.
- İşlem satır kilidiyle atomik çalışır ve `inventory_movements` defterine önce/sonra bakiyeleriyle yazılır.
- Backorder kapalıyken on-hand değer aktif rezervasyon toplamının altına indirilemez; `422 INVENTORY_BELOW_RESERVED` döner.
- `reason` zorunludur; `reference` irsaliye, sayım veya harici ERP referansı için kullanılabilir.

### Tracking, backorder ve düşük stok ayarları

```http
PATCH /admin/inventory/{variant}/settings
```

```json
{
  "track_inventory": true,
  "allow_backorder": false,
  "low_stock_threshold": 5
}
```

Aktif rezervasyon varken tracking kapatılamaz. Negatif available stok varken backorder kapatılamaz. Ayar değişiklikleri de `settings_updated` hareketi olarak denetlenebilir deftere yazılır.

### Varyant stok zaman çizelgesi

```http
GET /admin/inventory/{variant}
```

`{variant}` product variant UUID'sidir. En fazla 100 aktif reservation ve 100 movement döner. Movement alanları quantity/reserved delta, işlem sonrası değer, reason ve tarih içerir.

### Hareket defteri

```http
GET /admin/inventory/movements?variant_id={variant}&type=adjustment_increase&date_from=2026-08-01&date_to=2026-08-31&per_page=20
```

Hareket tipleri: `reserved`, `consumed`, `released`, `expired`, `adjustment_set`, `adjustment_increase`, `adjustment_decrease`, `settings_updated`. Yanıt variant/product, order/reservation referansları, quantity/reserved delta, işlem sonrası on-hand/reserved/available, reason, metadata ve zamanı döndürür.

### Rezervasyon yönetimi

```http
GET  /admin/inventory/reservations?status=active&variant_id={variant}
POST /admin/inventory/reservations/{reservation}/release
```

Liste `active`, `consumed`, `released`, `expired` durumlarını; cart/order referanslarını ve süre aşımı bilgisini gösterir. `expired=true` scheduler henüz çalışmadan süresi dolmuş aktif kayıtları bulur. Manuel release zorunlu bir reason alır, available stoğu geri açar ve hareket oluşturur.

Ürün PATCH ve option/variant sync içindeki quantity değişiklikleri de aynı atomik adjustment servisini kullanır. Böylece aktif rezervasyonlar atlanamaz ve stok hareket geçmişi sessizce kaybolmaz.

## 14. Kargo yöntemleri

Liste/CRUD:

```http
GET    /admin/shipping-methods
POST   /admin/shipping-methods
GET    /admin/shipping-methods/{shipping_method}
PUT    /admin/shipping-methods/{shipping_method}
PATCH  /admin/shipping-methods/{shipping_method}
DELETE /admin/shipping-methods/{shipping_method}
```

Request:

```json
{
  "name": "Standart Kargo",
  "code": "standard",
  "type": "flat_rate",
  "price_amount": 4990,
  "currency": "TRY",
  "is_active": true,
  "settings": {}
}
```

`type`: `free` veya `flat_rate`. `free` seçildiğinde `price_amount` otomatik 0 olur. Code mağaza içinde unique ve lowercase normalize edilir. Update tam request bekler.

## 15. İndirimler

Liste/CRUD:

```http
GET    /admin/discounts
POST   /admin/discounts
GET    /admin/discounts/{discount}
PUT    /admin/discounts/{discount}
PATCH  /admin/discounts/{discount}
DELETE /admin/discounts/{discount}
```

Percentage örneği:

```json
{
  "name": "Hoş Geldin İndirimi",
  "code": "welcome10",
  "type": "percentage",
  "value": 10,
  "minimum_order_amount": 50000,
  "usage_limit": 1000,
  "starts_at": "2026-08-10T00:00:00+03:00",
  "ends_at": "2026-12-31T23:59:59+03:00",
  "is_active": true
}
```

Code uppercase normalize edilir. `type`: `fixed` veya `percentage`. Percentage value 1–100; fixed value minor unit'tir. `ends_at`, `starts_at` sonrasında olmalıdır. Update tam request bekler.

## 16. Sipariş listesi ve detayı

### Liste

```http
GET /admin/orders?status=processing&payment_status=paid&fulfillment_status=partial&search=buyer@example.com&date_from=2026-08-01&date_to=2026-08-10&sort=created_at&direction=desc&per_page=20
```

Status değerleri:

- Order: `pending`, `confirmed`, `processing`, `completed`, `cancelled`, `refunded`
- Payment: `pending`, `authorized`, `paid`, `failed`, `partially_refunded`, `refunded`
- Fulfillment: `unfulfilled`, `partial`, `fulfilled`, `cancelled`

Search order number veya customer email üzerinde çalışır. Sort: `created_at`, `total_amount`, `order_number`.

### Detay

```http
GET /admin/orders/{order}
```

Response şunları içerir:

- Order/payment/fulfillment status
- Subtotal, discount, shipping, tax, total
- Payment UUID, provider, transaction ID, amount ve paid_at
- Customer email/telefon
- Billing/shipping adresleri
- Sipariş satırları
- Status timeline ve actor
- Refund request'ler
- Maskeli EPIN kodları ve EPIN delivery durumu

## 17. Sipariş state machine

İzin verilen order geçişleri:

```text
pending -> confirmed -> processing -> completed
   |           |             |
   +-----------+-------------+-> cancelled
```

- `completed` için payment `paid` olmalıdır.
- `completed` için fulfillment `fulfilled` olmalıdır.
- Paid/partially-refunded/refunded sipariş doğrudan cancel edilemez; refund workflow kullanılmalıdır.
- Aynı status tekrar gönderilirse işlem idempotent davranır.

Status değiştirme:

```http
PATCH /admin/orders/{order}/status
```

```json
{
  "status": "confirmed",
  "note": "Sipariş kontrol edildi."
}
```

Fulfillment geçişleri:

```text
unfulfilled -> partial -> fulfilled
unfulfilled ----------> fulfilled
```

Yalnız `confirmed` veya `processing` order fulfill edilebilir.

```http
PATCH /admin/orders/{order}/fulfillment-status
```

```json
{
  "status": "fulfilled",
  "note": "Kargoya teslim edildi."
}
```

Cancel:

```http
POST /admin/orders/{order}/cancel
```

```json
{"reason": "Müşteri talebi"}
```

Cancel aktif fiziksel stok ve EPIN reservation'larını serbest bırakır.

## 18. Ödeme yöntemleri, PayTR ve refund

Originos mağaza ayarları `bank_transfer` ve `paytr` yöntemlerini destekler. PayTR için `merchant_id`, `merchant_key`, `merchant_salt`, test/taksit ayarları ile başarı ve hata dönüş URL'leri `PATCH /admin/settings` üzerinden kaydedilir. Key ve salt şifreli saklanır; okuma yanıtlarında değerleri yerine yalnız `merchant_key_configured` ve `merchant_salt_configured` bayrakları döner.

Checkout sonrasında PayTR iframe tokeni şu endpoint ile oluşturulur:

```http
POST /storefront/payments/paytr/token
Content-Type: application/json

{"order_id":"order-uuid","guest_access_token":"64-karakter-token"}
```

PayTR panelindeki Bildirim URL değeri `https://originos.netkora.com/api/v1/payment/paytr/callback` olmalıdır. Sipariş yalnız bu sunucudan sunucuya callback'in HMAC doğrulaması başarılı olduğunda ödenmiş sayılır. Başarı dönüş URL'si ödeme onayı amacıyla kullanılmaz.

### Banka havalesini tamamla

```http
POST /admin/orders/{order}/payments/bank-transfer/complete
```

```json
{
  "transaction_id": "BANK-TRANSFER-2026-001",
  "note": "Havale kontrol edildi."
}
```

`bank_transfer` yeni siparişlerde varsayılan provider'dır. `transaction_id` opsiyoneldir; gönderilmezse sistem üretir. Aynı transaction ID ile retry idempotent'tir. Eski entegrasyonlar için `/payments/manual/complete` alias'ı korunur. Başarılı ödeme fulfillment işlerini ve ilgili webhook eventlerini tetikler.

### Refund isteği

```http
POST /admin/orders/{order}/refund-requests
```

```json
{
  "amount": 5000,
  "reason": "Eksik ürün"
}
```

`amount` opsiyoneldir; gönderilmezse kalan refundable tutarın tamamı kullanılır. Yalnız paid veya partially refunded order için açılır. EPIN içeren siparişlerde `manual_review_required: true` olur.

```json
{
  "data": {
    "id": "REFUND_UUID",
    "amount": {"amount": 5000, "currency": "TRY"},
    "status": "pending",
    "reason": "Eksik ürün",
    "manual_review_required": false,
    "created_at": "2026-08-10T12:00:00+03:00"
  }
}
```

## 19. EPIN yönetimi

EPIN kodları şifreli saklanır. Normal liste yanıtları tam kodu hiçbir zaman göstermez.

### Pool CRUD

```http
GET    /admin/epin-pools
POST   /admin/epin-pools
GET    /admin/epin-pools/{epinPool}
PUT    /admin/epin-pools/{epinPool}
DELETE /admin/epin-pools/{epinPool}
```

```json
{
  "product_id": "EPIN_PRODUCT_UUID",
  "variant_id": null,
  "name": "Standart Kod Havuzu",
  "status": "active"
}
```

Product `epin` tipinde olmalıdır. Variant verilirse aynı ürüne ait olmalıdır. Kod içeren pool silinemez.

Pool response'u total, available, reserved ve sold sayaçlarını içerir.

### Kod listesi

```http
GET /admin/epin-pools/{epinPool}/codes?status=available&page=1&per_page=50
```

Kod maskeli döner:

```json
{
  "id": "EPIN_CODE_UUID",
  "code": "********ABCD",
  "status": "available",
  "order_id": null,
  "reserved_at": null,
  "reservation_expires_at": null,
  "sold_at": null,
  "delivered_at": null
}
```

### JSON import

```http
POST /admin/epin-pools/{epinPool}/codes/import
```

```json
{
  "codes": ["CODE-001", "CODE-002", "CODE-003"]
}
```

### CSV import

```http
POST /admin/epin-pools/{epinPool}/imports
Content-Type: multipart/form-data
```

Form alanı `file`; csv/txt ve maksimum 10 MB. İlk kolon okunur. İlk satır `code`, `epin` veya `epin_code` ise header kabul edilir.

Büyük import queue'ya alınabilir. Import takibi:

```http
GET /admin/epin-imports/{epinImport}
```

Response; status, total/processed/imported/duplicate/failed sayaçları ve kod içermeyen hata raporu döndürür.

### Tam kod reveal

```http
POST /admin/epin-codes/{epinCode}/reveal
```

```json
{"reason": "Müşteri destek talebi #1234"}
```

Yalnız satılmış ve order'a bağlı kod açılabilir. `epins.reveal` izni gerekir. Reason 3–500 karakterdir. Yanıt `Cache-Control: no-store, private` taşır ve audit log oluşur. Tam kod loglanmamalı ve UI'da gereğinden uzun tutulmamalıdır.

## 20. Mağaza ayarları

```http
GET /admin/settings
PATCH /admin/settings
```

PATCH kısmi güncellemedir:

```json
{
  "commerce": {
    "show_prices_with_tax": true,
    "show_out_of_stock": false
  },
  "contact": {
    "support_email": "support@example.com",
    "support_phone": "+905551112233"
  },
  "seo": {
    "title": "Mağaza başlığı",
    "description": "En fazla 160 karakter açıklama"
  },
  "announcement": {
    "enabled": true,
    "message": "Aynı gün kargo"
  },
  "payments": {
    "default_method": "bank_transfer",
    "bank_transfer": {
      "enabled": true,
      "bank_name": "Originos Bank",
      "account_holder": "Store Ltd.",
      "iban": "TR33 0006 1005 1978 6457 8413 26",
      "branch": null,
      "instructions": "Sipariş numaranızı açıklamaya yazınız."
    }
  }
}
```

Sınırlar: SEO title 70, description 160, announcement message 280, phone 30 karakter. Bilinmeyen nested key'ler validation ile reddedilir. Güncelleme storefront config cache'ini temizler.

## 21. Tema yönetimi

### Tema kataloğu

```http
GET /admin/themes
```

Aktif ve public temaları 50 kayıtlık pagination ile döndürür. Her theme response'u Originos Theme Contract `1.5.0` manifestini ve 84 setting definition'ı içerir. Bunlara ana sayfa section order, çoklu hero slider, ürün galerisi, giriş/kayıt sunumu, favoriler, son görüntülenenler, indirim ve mobil satın alma araçları, featured products, categories, promo, announcement presentation ve custom CSS dahildir.

Tema görselleri `POST /api/v1/admin/theme/assets/images` adresine `multipart/form-data` içindeki `image` alanıyla yüklenir. İsteğe bağlı `purpose` alanı `hero`, `promo` veya `editorial` olabilir; gönderilmezse `hero` kabul edilir. JPG, PNG, WEBP ve GIF desteklenir, üst sınır 10 MB'dir. Dosya mağazaya özel `stores/{store_uuid}/themes/{purpose}` dizininde saklanır ve yanıttaki `data.url` değeri ilgili tema görseli alanına yazılır.

Mağaza logosu `POST /api/v1/admin/settings/logo` adresine `multipart/form-data` içindeki `logo` alanıyla yüklenir. JPG, PNG, WEBP ve GIF desteklenir; üst sınır 5 MB'dir. Logo mağazaya özel `stores/{store_uuid}/branding` dizininde saklanır ve storefront config içindeki `logo_url` alanında yayınlanır. `DELETE /api/v1/admin/settings/logo` mevcut logoyu kaldırır.

### Aktif tema

```http
GET /admin/theme
```

```json
{
  "data": {
    "theme": {
      "id": "THEME_UUID",
      "name": "Originos Starter",
      "slug": "originos-starter",
      "version": "1.0.0",
      "manifest": {"schema_version": "1.1.0", "assets": {}, "settings": []}
    },
    "settings": {
      "primary_color": "#111827",
      "font_family": "system",
      "header_style": "minimal"
    }
  }
}
```

### Tema ata

```http
PUT /admin/theme
```

```json
{"theme_id": "THEME_UUID"}
```

Tema geçişinde ortak sözleşmeyle uyumlu mağaza ayarları korunur, eksik/geçersiz değerler yeni tema defaults değerleriyle tamamlanır. Ek kurulum isteği gerekmez.

### Tema ayarları

```http
PUT /admin/theme/settings
```

Kısmi ayar güncellemesidir:

```json
{
  "settings": {
    "primary_color": "#2563eb",
    "hero_title": "Mağazamıza hoş geldiniz",
    "catalog_columns": 4,
    "show_search": true
  }
}
```

Renk `#RRGGBB`, URL/image HTTP(S), select ve numeric değerler manifest kurallarına uygun olmalıdır. Bilinmeyen key `422 THEME_SETTING_UNKNOWN` döndürür.

Tam 84 ayar, grouped homepage config, müşteri hesabı ve etkileşim araçları sunumu ile custom CSS entegrasyonu için Storefront & Theme API rehberine bakın: `https://originos.netkora.com/docs/storefront-theme-api`.

## 22. Webhook yönetimi

### Event kataloğu

```http
GET /admin/webhook-events
```

Desteklenen eventler:

```text
order.created
order.paid
order.cancelled
order.completed
payment.succeeded
payment.failed
stock.updated
epin.delivered
digital.delivered
product.created
product.updated
customer.created
customer.updated
```

### Webhook CRUD

```http
GET    /admin/webhooks
POST   /admin/webhooks
GET    /admin/webhooks/{webhook}
PUT    /admin/webhooks/{webhook}
PATCH  /admin/webhooks/{webhook}
DELETE /admin/webhooks/{webhook}
```

Create:

```json
{
  "name": "ERP entegrasyonu",
  "url": "https://erp.example.com/webhooks/originos",
  "events": ["order.created", "order.paid"],
  "is_active": true
}
```

URL public HTTP(S) olmalı; localhost, private/reserved IP ve 80/443 dışı portlar reddedilir. Update kısmi olabilir.

Create response'undaki `signing_secret` yalnız bir kez gösterilir:

```json
{
  "data": {
    "id": "WEBHOOK_UUID",
    "name": "ERP entegrasyonu",
    "url": "https://erp.example.com/webhooks/originos",
    "events": ["order.created", "order.paid"],
    "is_active": true,
    "secret_last_four": "aB3x",
    "signing_secret": "ONE_TIME_SECRET"
  }
}
```

Secret rotation:

```http
POST /admin/webhooks/{webhook}/rotate-secret
```

Yeni secret yine yalnız bu response'ta düz metin döner. Consumer güncellenmeden eski secret kaybedilmemelidir.

### İmza doğrulama

Originos şu header'ları gönderir:

```text
X-Originos-Event
X-Originos-Delivery
X-Originos-Timestamp
X-Originos-Signature
```

İmza:

```php
$expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
$valid = hash_equals($expected, $signature);
```

Consumer ham request body ile imzayı doğrulamalı ve delivery UUID'yi idempotency key olarak saklamalıdır.

### Delivery izleme

```http
GET /admin/webhook-deliveries?status=failed&event=order.created&page=1&per_page=20
GET /admin/webhook-deliveries/{delivery}
```

Delivery response; payload, status, attempt count, response status/body, error code/message, retry zamanları ve replay bağlantısını içerir.

Retry aynı delivery UUID'yi sıfırlar ve yeniden kuyruğa alır:

```http
POST /admin/webhook-deliveries/{delivery}/retry
```

Replay yeni delivery/event UUID üretir ve original delivery'yi `replay_of_id` ile bağlar:

```http
POST /admin/webhook-deliveries/{delivery}/replay
```

## 23. Mağaza müşterileri ve CRM

### Liste, arama ve segment filtreleri

```http
GET /admin/customers?search=ada&status=active&has_orders=true&sort=total_spent_amount&direction=desc&per_page=20
```

Filtreler:

- `search`: ad, soyad, e-posta veya telefon
- `status`: `active` veya `inactive`
- `has_orders`: siparişi olan/olmayan müşteriler
- `date_from`, `date_to`: kayıt tarihi aralığı
- `sort`: `first_name`, `created_at`, `orders_count`, `total_spent_amount`, `last_order_at`

Liste response'u müşteri bazında toplam sipariş, paid sipariş, yaşam boyu harcama, ortalama sepet, son sipariş ve not sayısını döndürür. Üst `metrics` alanında toplam, aktif, pasif, siparişli ve son 30 günde yeni müşteri sayıları bulunur.

### Detay ve sipariş geçmişi

```http
GET /admin/customers/{customer}
GET /admin/customers/{customer}/orders?payment_status=paid&per_page=20
```

Detay response'u profil, CRM metrikleri, varsayılan önce sıralanmış adresler ve pinned notlar önce olacak şekilde iç notları içerir. Sipariş endpoint'i normal admin order şemasını ve filtrelerini kullanır.

### Profil ve durum yönetimi

```http
PATCH /admin/customers/{customer}
```

```json
{
  "first_name": "Ada",
  "last_name": "Lovelace",
  "email": "ada@example.com",
  "phone": "+905551112233",
  "status": "inactive"
}
```

Alanların tamamı opsiyoneldir. E-posta mağaza içinde unique olmalıdır. Müşteri `inactive` yapıldığında bütün storefront customer token'ları iptal edilir ve yeniden login engellenir. Güncelleme `customer.updated` webhook event'i yayınlar.

### Adres yönetimi

```http
POST   /admin/customers/{customer}/addresses
PATCH  /admin/customers/{customer}/addresses/{address}
DELETE /admin/customers/{customer}/addresses/{address}
```

Adres gövdesi storefront customer address şemasıyla aynıdır. `is_default: true` gönderildiğinde aynı tipteki önceki varsayılan adres kaldırılır.

### İç CRM notları

```http
GET    /admin/customers/{customer}/notes
POST   /admin/customers/{customer}/notes
PATCH  /admin/customers/{customer}/notes/{note}
DELETE /admin/customers/{customer}/notes/{note}
```

```json
{
  "body": "VIP müşteri; siparişleri öncelikli hazırlanmalı.",
  "is_pinned": true
}
```

Notlar yalnız merchant API'de görünür; storefront customer response'larına dahil edilmez. Her not yazar UUID/adı ve oluşturma/güncelleme zamanını taşır. Tüm endpointler store scope, `customers.view` veya `customers.update` izni ve yazma işlemlerinde aktif abonelik kontrolüyle korunur.

## 24. Gelişmiş katalog, CRM, bildirim ve rapor API'leri

Bu bölüm panelin 5–8 numaralı geliştirme adımlarında eklenen uçları kapsar.

### Gelişmiş ürün yönetimi

| Metot | Endpoint | Açıklama |
|---|---|---|
| GET/POST | `/admin/brands` | Marka listesi/oluşturma |
| PATCH/DELETE | `/admin/brands/{brand}` | Marka güncelleme/silme |
| GET/POST | `/admin/collections` | Koleksiyon listesi/oluşturma |
| PATCH/DELETE | `/admin/collections/{collection}` | Koleksiyon güncelleme/silme |
| GET/POST | `/admin/product-tags` | Ürün etiketi listesi/oluşturma |
| PATCH/DELETE | `/admin/product-tags/{tag}` | Ürün etiketi güncelleme/silme |
| PATCH | `/admin/products/bulk` | En fazla 500 üründe durum, görünürlük ve sınıflandırma güncelleme |
| GET | `/admin/products/export/csv` | Ürünleri CSV indir |
| POST | `/admin/products/import/csv` | En fazla 5 MB/5000 satır CSV içe aktar |
| PUT | `/admin/products/{product}/media/{media}/primary` | Ana ürün görselini seç |

Ürün create/update gövdesi artık `brand_id`, `collection_ids`, `tag_ids`, `barcode`, `cost_amount` ve `compare_at_price_amount` alanlarını kabul eder. CSV zorunlu sütunları `sku,name,price_amount,currency`; önerilen tam başlık ise `sku,name,slug,type,status,visibility,price_amount,compare_at_price_amount,cost_amount,currency,barcode,quantity` biçimindedir. Miktar değişiklikleri doğrudan yazılmaz, stok hareketi oluşturur.

Toplu güncelleme örneği:

```json
{
  "product_ids": ["PRODUCT_UUID_1", "PRODUCT_UUID_2"],
  "status": "active",
  "visibility": "public",
  "brand_id": "BRAND_UUID",
  "collection_ids": ["COLLECTION_UUID"],
  "tag_ids": ["TAG_UUID"]
}
```

### Gelişmiş CRM

| Metot | Endpoint | Açıklama |
|---|---|---|
| POST | `/admin/customers` | Panelden müşteri oluştur |
| GET | `/admin/customers/export/csv` | Müşteri CSV dışa aktarımı |
| POST | `/admin/customers/import/csv` | Müşteri CSV içe aktarımı |
| GET | `/admin/customers/{customer}/data-export` | Tek müşterinin profil, adres, sipariş, not ve etiket verisi |
| GET/POST | `/admin/crm-tags` | CRM etiketi listesi/oluşturma |
| PATCH/DELETE | `/admin/crm-tags/{crmTag}` | CRM etiketi güncelleme/silme |
| PUT | `/admin/customers/{customer}/tags` | Müşteri etiketlerini eşitle |

Müşteri listesi `tag_id`, `marketing_email_consent` ve `marketing_sms_consent` filtrelerini destekler. Durum `active`, `inactive` veya `blocked` olabilir. `blocked` ve `inactive` yapıldığında aktif müşteri tokenları iptal edilir. Profilde `marketing_email_consent`, `marketing_sms_consent`, `blocked_reason` alanları yönetilebilir.

### Bildirim merkezi

| Metot | Endpoint | Açıklama |
|---|---|---|
| GET | `/admin/notification-templates` | Varsayılan ve özelleştirilmiş şablonlar |
| PUT | `/admin/notification-templates/{event}` | E-posta şablonunu güncelle |
| GET | `/admin/notification-logs` | Kuyruk/gönderim/hata geçmişi |
| POST | `/admin/notifications/test` | Test bildirimi kuyruğa al |

Eventler: `customer.created`, `order.created`, `payment.succeeded`, `payment.failed`, `order.fulfilled`, `inventory.low_stock`. Şablonlar `{{customer_name}}`, `{{order_number}}`, `{{order_total}}`, `{{currency}}`, `{{product_name}}`, `{{variant_name}}` ve `{{available_quantity}}` değişkenlerini kullanabilir. Gönderim queue worker üzerinden yapılır; production'da `php artisan queue:work --queue=notifications,default` çalışmalıdır.

### Raporlama

| Metot | Endpoint |
|---|---|
| GET | `/admin/reports/overview` |
| GET | `/admin/reports/products` |
| GET | `/admin/reports/customers` |
| GET | `/admin/reports/discounts` |
| GET | `/admin/reports/inventory` |
| GET | `/admin/reports/export/csv` |

Tarih bazlı raporlar `date_from`, `date_to` (en fazla 366 gün) ve sıralı raporlar `limit` parametresini alır. CSV için `type=overview|products|customers|discounts|inventory` kullanılır. Para değerleri diğer API'lerde olduğu gibi minor unit integer döner.

## 25. Hata formatları

Domain hatası:

```json
{
  "message": "This action is forbidden.",
  "code": "PERMISSION_DENIED"
}
```

Validation hatası:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "price_amount": ["The price amount field is required."]
  }
}
```

Önemli hata kodları:

| HTTP | Kod | Anlamı |
|---:|---|---|
| 401 | `INVALID_CREDENTIALS` | Login bilgileri hatalı |
| 401 | `API_KEY_INVALID` / `API_KEY_REVOKED` / `API_KEY_EXPIRED` | Store key geçersiz |
| 403 | `STORE_MEMBERSHIP_REQUIRED` | Kullanıcı mağazaya bağlı değil veya token merchant değil |
| 403 | `PERMISSION_DENIED` | Gerekli mağaza izni yok |
| 403 | `STORE_SUSPENDED` | Mağaza operasyonel değil |
| 403 | `SUBSCRIPTION_EXPIRED` | Yazma hakkı yok |
| 403 | `FEATURE_NOT_AVAILABLE` | Plan özelliği kapalı |
| 409 | `PLAN_LIMIT_REACHED` | Ürün limiti doldu |
| 422 | `INVALID_ORDER_TRANSITION` | Sipariş status geçişi yasak |
| 422 | `ORDER_PAYMENT_REQUIRED` | Complete için ödeme gerekli |
| 422 | `ORDER_FULFILLMENT_REQUIRED` | Complete için fulfillment gerekli |
| 422 | `PAID_ORDER_REQUIRES_REFUND` | Paid order cancel yerine refund ister |
| 422 | `INVALID_FULFILLMENT_TRANSITION` | Fulfillment geçişi yasak |
| 422 | `ORDER_NOT_REFUNDABLE` | Order refund koşulunu sağlamıyor |
| 422 | `INVALID_REFUND_AMOUNT` | Refund tutarı kalan tutarı aşıyor |
| 422 | `INVENTORY_BELOW_RESERVED` | Stok aktif rezervasyon toplamının altına indirilemez |
| 422 | `INVENTORY_HAS_ACTIVE_RESERVATIONS` | Aktif rezervasyon varken tracking kapatılamaz |
| 422 | `INVENTORY_BACKORDER_EXISTS` | Negatif available stok temizlenmeden backorder kapatılamaz |
| 422 | `INVENTORY_QUANTITY_OUT_OF_RANGE` | İşlem sonucu desteklenen integer aralığı dışında |
| 404 | `MANUAL_PAYMENT_NOT_FOUND` | Manual payment yok |
| 409 | `PAYMENT_ALREADY_COMPLETED` | Başka transaction ile ödeme tamamlanmış |
| 422 | `DIGITAL_PRODUCT_REQUIRED` | Dosya hedefi digital ürün değil |
| 422 | `DIGITAL_VARIANT_INVALID` | Variant ürüne ait değil |
| 422 | `DIGITAL_FILE_IN_USE` | Satılmış dosya silinemez |
| 422 | `EPIN_PRODUCT_INVALID` / `EPIN_VARIANT_INVALID` | Pool hedefi geçersiz |
| 422 | `EPIN_POOL_NOT_EMPTY` | Kodlu pool silinemez |
| 422 | `EPIN_NOT_SOLD` | Kod reveal edilemez |
| 403 | `THEME_NOT_INCLUDED` | Paid tema planda yok |
| 422 | `THEME_NOT_AVAILABLE` | Tema aktif/public değil |
| 422 | `THEME_NOT_ASSIGNED` | Aktif tema yok |
| 422 | `THEME_SETTING_UNKNOWN` | Ayar ortak contract'ta yok |
| 422 | webhook URL validation | Webhook hedefi güvenli değil |
| 429 | rate limit | İstek limiti aşıldı |

Her response'ta `X-Request-ID` bulunur. Hata loglarında status, code ve request ID birlikte saklanmalıdır.

## 25. Önerilen yönetim paneli akışı

1. Store key seçtir ve `/admin/auth/login` ile bearer token al.
2. Token ile `/admin/dashboard` yükle.
3. Menüleri role/permission ve plan feature'larına göre göster.
4. Product list/detail üzerinden katalog ekranlarını oluştur.
5. Physical üründe inventory; digital üründe digital file; EPIN üründe pool yönetimini göster.
6. Order list/detail ekranında state machine'e göre yalnız geçerli aksiyonları etkinleştir.
7. Paid order için cancel yerine refund ekranı göster.
8. Tema/ayar değişikliklerinden sonra storefront config'in otomatik invalid edildiğini kabul et.
9. Webhook secret'ı create/rotate yanıtında kullanıcıya bir kez göster ve yeniden okunamayacağını belirt.
10. EPIN reveal'i ek onay, zorunlu reason ve no-cache UI ile koru.

## 26. Frontend güvenlik kontrol listesi

- Merchant token'ı localStorage yerine mümkünse güvenli server session/BFF veya kontrollü storage ile yönet.
- Secret ve tam EPIN kodlarını console/log/analytics'e yazma.
- Ürün açıklaması gibi HTML alanlarını sanitize et.
- Delete, refund, cancel, reveal ve secret rotation işlemlerine onay modalı ekle.
- `204` yanıtlarında JSON parse etmeye çalışma.
- `422 errors` objesini form alanlarına eşleştir.
- `403 PERMISSION_DENIED` durumunda UI butonunu kaldır ve session'ı gereksiz yere kapatma.
- `401` durumunda token/session temizleyip login'e yönlendir.
- Para değerlerini minor unit olarak gönder ve göster.
- Multipart upload sırasında JSON `Content-Type` header'ını elle verme; browser boundary oluştursun.
- Pagination meta bilgisini kullan; bütün kayıtların tek sayfada olduğunu varsayma.
- Aynı mağazaya ait olmayan UUID'lerin çoğunlukla `404` döneceğini hesaba kat.

## 27. API artifact'leri

- Tarayıcı rehberi: `https://originos.netkora.com/docs/merchant-admin-api`
- Markdown: `https://originos.netkora.com/docs/merchant-admin-api.md`
- Storefront/tema rehberi: `https://originos.netkora.com/docs/storefront-theme-api`
- OpenAPI: `https://originos.netkora.com/docs/openapi.yaml`
- Postman Collection: `https://originos.netkora.com/docs/postman/collection.json`
- Production Environment: `https://originos.netkora.com/docs/postman/production-environment.json`

Postman collection merchant login, katalog, inventory, shipping, discount, checkout/order, payment, EPIN, digital file, webhook, settings, theme ve dashboard örneklerini çalıştırılabilir sırada içerir.
