# Originos Storefront & Theme Developer API

Bu doküman Originos üzerinde web, mobil uygulama veya özel tema geliştiren frontend ekipleri içindir. Mağaza vitrininin ihtiyaç duyduğu tema config'i, katalog, sepet, checkout, müşteri hesabı, adres, sipariş ve dijital teslimat API'lerinin tamamını kapsar.

## 1. Hızlı başlangıç

Production API kökü:

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

Local API kökü:

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

Storefront isteklerinde mağazaya ait **public** API anahtarı gönderilir:

```http
Accept: application/json
Content-Type: application/json
X-Store-Key: pk_live_...
```

Public storefront key tarayıcı veya mobil uygulamada kullanılabilir. `sk_...` ile başlayan secret anahtarlar frontend bundle'ına, public repository'ye veya tarayıcı storage'ına kesinlikle konulmamalıdır.

İlk bağlantı testi:

```bash
curl 'https://originos.netkora.com/api/v1/storefront/store' \
  -H 'Accept: application/json' \
  -H 'X-Store-Key: YOUR_PUBLIC_STORE_KEY'
```

JavaScript istemci örneği:

```js
const API_URL = 'https://originos.netkora.com/api/v1'
const STORE_KEY = 'YOUR_PUBLIC_STORE_KEY'

async function originos(path, options = {}) {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'X-Store-Key': STORE_KEY,
      ...options.headers,
    },
  })

  const body = response.status === 204 ? null : await response.json()
  if (!response.ok) {
    throw Object.assign(new Error(body?.message || 'Originos API error'), {
      status: response.status,
      code: body?.code,
      errors: body?.errors,
      requestId: response.headers.get('X-Request-ID'),
    })
  }

  return body
}
```

## 2. Temel kurallar

### Mağaza izolasyonu

`X-Store-Key` hangi mağazanın verisinin döneceğini belirler. Bir mağazaya ait ürün slug'ı, sepet token'ı, müşteri token'ı veya sipariş UUID'si başka mağazada kullanılamaz.

Doğrulanmış özel domain kullanılıyorsa mağaza host üzerinden de çözülebilir. Tema entegrasyonunda açık ve taşınabilir davranış için public store key gönderilmesi önerilir.

### Kimlik doğrulama katmanları

| Katman | Header | Kullanım |
|---|---|---|
| Public storefront | `X-Store-Key` | Config, ürün, kategori, kargo, sepet |
| Customer hesabı | `Authorization: Bearer {customer_token}` | Profil, adresler, müşteri siparişleri |
| Guest sipariş | `X-Order-Token: {guest_access_token}` | Yalnız ilgili misafir siparişini görüntüleme |
| Checkout retry güvenliği | `Idempotency-Key: {unique-value}` | Siparişin iki kez oluşmasını engelleme |

Customer endpointlerinde hem `X-Store-Key` hem customer bearer token gönderilmelidir.

### Para değerleri

Bütün `amount` alanları integer **minor unit** değeridir. Ondalıklı sayı kullanılmaz.

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

Bu değer `129,90 TRY` anlamına gelir. Gösterim için para biriminin fraction digit bilgisi kullanılmalıdır:

```js
function formatMoney(money, locale = 'tr-TR') {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: money.currency,
  }).format(money.amount / 100)
}
```

### Tarih, UUID ve sayfalama

- Tarihler ISO 8601 formatındadır.
- Public kaynak kimlikleri UUID'dir. Sepet item `id` değeri integer olabilir.
- Liste endpointleri genel olarak `?page=1&per_page=20` kabul eder.
- `per_page` en fazla `100` olabilir.
- Sayfalı yanıtlar Laravel resource formatındadır:

```json
{
  "data": [],
  "links": {
    "first": "...",
    "last": "...",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "per_page": 20,
    "to": 1,
    "total": 1
  }
}
```

### Rate limit

| Grup | Sınır |
|---|---:|
| Storefront | 120 istek/dakika |
| Register/login | 10 istek/dakika |
| Checkout | 10 istek/dakika |
| Dijital indirme | 30 istek/dakika |

Limit aşılırsa `429 Too Many Requests` döner. İstemci exponential backoff uygulamalı ve checkout için aynı `Idempotency-Key` değerini korumalıdır.

## 3. Originos Theme Contract 1.5.0

Hero alanı çoklu slider destekler. `hero_slides` en fazla 8 öğeden oluşur; her öğe `title`, `subtitle`, `image`, `button_label` ve `button_url` alanlarını taşır. `hero_slider_autoplay` otomatik geçişi, `hero_slider_interval` ise 3–15 saniye arasındaki geçiş süresini belirler. `hero_slides` boşsa geriye uyumluluk için `hero_title`, `hero_subtitle`, `hero_image`, `hero_button_label` ve `hero_button_url` alanları tek slide olarak kullanılmalıdır.

Her Originos teması aynı config şemasını kullanır. Müşteri tema değiştirdiğinde uyumlu mağaza ayarları korunur, eksik veya geçersiz değerler yeni temanın varsayılanlarıyla tamamlanır. Frontend'in tema bazında farklı config eşlemesi yapması gerekmez.

Tema config'i şu endpointten alınır:

```http
GET /storefront/store
```

Manifest yapısı:

```json
{
  "schema_version": "1.1.0",
  "assets": {
    "css": "https://cdn.example.com/originos/theme.css",
    "js": null
  },
  "settings": [
    {
      "key": "primary_color",
      "label": "Primary color",
      "group": "colors",
      "type": "color",
      "required": true,
      "default": "#111827"
    }
  ]
}
```

Runtime değerleri `data.theme.settings` içindeki key/value objesidir. Tasarım çalışırken manifestteki `default` yerine öncelikle bu resolved değerler kullanılmalıdır.

### Kanonik tema ayarları

| Grup | Key | Tip | Varsayılan / seçenekler |
|---|---|---|---|
| colors | `primary_color` | color | `#111827` |
| colors | `secondary_color` | color | `#ffffff` |
| colors | `accent_color` | color | `#10b981` |
| colors | `background_color` | color | `#ffffff` |
| colors | `text_color` | color | `#111827` |
| typography | `font_family` | select | `system`; `system`, `inter`, `manrope`, `serif` |
| layout | `border_radius` | select | `medium`; `none`, `small`, `medium`, `large` |
| layout | `header_style` | select | `minimal`; `minimal`, `centered`, `mega` |
| layout | `footer_style` | select | `columns`; `minimal`, `columns` |
| catalog | `product_card_style` | select | `bordered`; `minimal`, `bordered`, `elevated` |
| catalog | `catalog_columns` | number | `4`; minimum `2`, maksimum `6` |
| catalog | `show_search` | boolean | `true` |
| catalog | `show_categories` | boolean | `true` |
| hero | `hero_enabled` | boolean | `true` |
| hero | `hero_title` | text | `Welcome` |
| hero | `hero_subtitle` | text | boş metin |
| hero | `hero_image` | image URL | `null`, yalnız HTTP(S) |
| hero | `hero_button_label` | text | `Shop now` |
| hero | `hero_button_url` | URL | `null`, yalnız HTTP(S) |
| announcement | `announcement_position` | select | `top`; `top`, `below_header`, `above_footer` |
| announcement | `announcement_style` | select | `bar`; `bar`, `floating`, `minimal` |
| homepage | `homepage_section_order` | list | `hero`, `announcement`, `featured_products`, `categories`, `new_arrivals`, `promo` |
| homepage | `homepage_featured_products_enabled` | boolean | `true` |
| homepage | `homepage_featured_products_title` | text | `Featured products` |
| homepage | `homepage_featured_products_limit` | number | `8`; minimum `1`, maksimum `24` |
| homepage | `homepage_categories_enabled` | boolean | `true` |
| homepage | `homepage_categories_title` | text | `Shop by category` |
| homepage | `homepage_categories_limit` | number | `8`; minimum `1`, maksimum `24` |
| homepage | `homepage_new_arrivals_enabled` | boolean | `true` |
| homepage | `homepage_new_arrivals_title` | text | `New arrivals` |
| homepage | `homepage_new_arrivals_limit` | number | `8`; minimum `1`, maksimum `24` |
| promo | `promo_enabled` | boolean | `false` |
| promo | `promo_title` | text | `Special offer` |
| promo | `promo_subtitle` | text | `Discover our latest offers.` |
| promo | `promo_image` | image URL | `null`, yalnız HTTP(S) |
| promo | `promo_button_label` | text | `Explore` |
| promo | `promo_button_url` | URL | `null`, yalnız HTTP(S) |
| account | `account_auth_mode` | select | `pages`; `pages`, `combined`, `modal` |
| account | `account_default_tab` | select | `login`; `login`, `register` |
| account | `account_allow_registration` | boolean | `true` |
| account | `account_login_title` | text | `Tekrar hoş geldiniz.` |
| account | `account_register_title` | text | `Hesabınızı oluşturun.` |
| account | `account_show_benefits` | boolean | `true` |
| account | `account_marketing_consent` | boolean | `true` |
| engagement | `wishlist_enabled` | boolean | `true` |
| engagement | `wishlist_drawer_title` | text | `Favorilerim` |
| engagement | `recently_viewed_enabled` | boolean | `true` |
| engagement | `recently_viewed_title` | text | `Son baktıklarınız` |
| engagement | `sale_badge_enabled` | boolean | `true` |
| engagement | `sticky_mobile_buy` | boolean | `true` |
| advanced | `custom_css` | css | boş metin, maksimum 50.000 karakter |

### Önerilen CSS variable eşlemesi

```js
function applyTheme(settings) {
  const root = document.documentElement

  root.style.setProperty('--color-primary', settings.primary_color)
  root.style.setProperty('--color-secondary', settings.secondary_color)
  root.style.setProperty('--color-accent', settings.accent_color)
  root.style.setProperty('--color-background', settings.background_color)
  root.style.setProperty('--color-text', settings.text_color)

  root.dataset.font = settings.font_family
  root.dataset.radius = settings.border_radius
  root.dataset.header = settings.header_style
  root.dataset.footer = settings.footer_style
  root.dataset.productCard = settings.product_card_style

  let customStyle = document.getElementById('originos-custom-css')
  if (!customStyle) {
    customStyle = document.createElement('style')
    customStyle.id = 'originos-custom-css'
    document.head.appendChild(customStyle)
  }
  customStyle.textContent = settings.custom_css || ''
}
```

Tema kodu bilinmeyen gelecekteki alanları görmezden gelmeli ve `schema_version` değerini kontrol etmelidir. Desteklenmeyen major sürüm görülürse güvenli bir varsayılan tasarım gösterilmelidir.

### Tema paketi manifesti

Tema Originos kataloğuna eklenirken 38 alanın tamamını tekrar tanımlamak gerekmez. Tema yalnız değiştirmek istediği default değerlerini ve asset URL'lerini verir:

```json
{
  "name": "Originos Modern",
  "slug": "originos-modern",
  "version": "1.1.0",
  "type": "free",
  "is_active": true,
  "is_public": true,
  "manifest": {
    "schema_version": "1.1.0",
    "assets": {
      "css": "https://cdn.example.com/originos-modern/1.1.0/theme.css",
      "js": null
    },
    "settings": [
      {"key": "primary_color", "default": "#2563eb"},
      {"key": "font_family", "default": "inter"},
      {"key": "header_style", "default": "centered"},
      {"key": "product_card_style", "default": "elevated"}
    ]
  }
}
```

Originos manifesti otomatik olarak tam sözleşmeye genişletir. `1.0.0`–`1.4.0` manifestler geriye uyumlulukla `1.5.0` sözleşmesine yükseltilir. Bilinmeyen key, farklı tip, farklı select seçenekleri veya desteklenmeyen schema version reddedilir.

## 4. Endpoint özeti

Tüm yollar `https://originos.netkora.com/api/v1` köküne göredir.

### Mağaza ve katalog

| Metot | Endpoint | Açıklama |
|---|---|---|
| GET | `/storefront/store` | Mağaza, core settings ve resolved tema config'i |
| GET | `/storefront/products` | Yayındaki ürünler |
| GET | `/storefront/products/{slug}` | Ürün detayı |
| GET | `/storefront/categories` | Kök kategori ağacı |
| GET | `/storefront/categories/{slug}` | Kategori ve doğrudan ürünleri |
| GET | `/storefront/shipping-methods` | Aktif kargo seçenekleri |

### Sepet ve checkout

| Metot | Endpoint | Açıklama |
|---|---|---|
| POST | `/storefront/cart` | Yeni sepet oluşturur |
| GET | `/storefront/cart/{token}` | Sepeti getirir |
| POST | `/storefront/cart/{token}/items` | Varyant ekler veya miktarı değiştirir |
| PATCH | `/storefront/cart/{token}/items/{item}` | Sepet satırı miktarını değiştirir |
| DELETE | `/storefront/cart/{token}/items/{item}` | Sepet satırını siler |
| POST | `/storefront/cart/{token}/calculate` | Kupon, kargo ve vergi dahil server-side hesaplar |
| POST | `/storefront/checkout` | İdempotent sipariş oluşturur |

### Customer hesabı

| Metot | Endpoint | Açıklama |
|---|---|---|
| POST | `/storefront/customer/auth/register` | Kayıt ve customer token |
| POST | `/storefront/customer/auth/login` | Login ve customer token |
| POST | `/storefront/customer/auth/logout` | Aktif customer token'ını iptal eder |
| GET | `/storefront/customer/me` | Aktif müşteri profili |
| GET | `/storefront/customer/addresses` | Adres listesi |
| POST | `/storefront/customer/addresses` | Adres oluşturur |
| GET | `/storefront/customer/addresses/{address}` | Adres detayı |
| PUT/PATCH | `/storefront/customer/addresses/{address}` | Adres günceller |
| DELETE | `/storefront/customer/addresses/{address}` | Adres siler |
| GET | `/storefront/customer/orders` | Customer siparişleri |
| GET | `/storefront/customer/orders/{order}` | Customer sipariş detayı |
| GET | `/storefront/guest/orders/{order}` | Guest sipariş detayı |
| GET | `/storefront/downloads/{grant}?expires=...&signature=...&token=...` | Signed dijital dosya indirme |

## 5. Mağaza config'i

### `GET /storefront/store`

Storefront uygulamasının ilk açılışında çağrılması önerilir.

```json
{
  "data": {
    "id": "STORE_UUID",
    "name": "Demo Store",
    "currency": "TRY",
    "locale": "tr",
    "timezone": "Europe/Istanbul",
    "maintenance": false,
    "logo_url": "https://cdn.example.com/logo.png",
    "favicon_url": "https://cdn.example.com/favicon.png",
    "settings": {
      "commerce": {
        "show_prices_with_tax": true,
        "show_out_of_stock": true
      },
      "contact": {
        "support_email": "support@example.com",
        "support_phone": "+905551112233"
      },
      "seo": {
        "title": "Demo Store",
        "description": "Mağaza açıklaması"
      },
      "announcement": {
        "enabled": true,
        "message": "Aynı gün kargo"
      },
      "payments": {
        "default_method": "bank_transfer",
        "bank_transfer": {
          "enabled": true,
          "bank_name": "Örnek Banka",
          "account_holder": "Demo Store A.Ş.",
          "iban": "TR00 0000 0000 0000 0000 0000 00",
          "branch": "Merkez",
          "instructions": "Sipariş numaranızı ödeme açıklamasına yazınız."
        }
      }
    },
    "homepage": {
      "section_order": ["hero", "announcement", "featured_products", "categories", "new_arrivals", "promo"],
      "hero": {"enabled": true, "title": "Welcome", "subtitle": "", "image": null, "button_label": "Shop now", "button_url": null},
      "announcement": {"enabled": true, "message": "Aynı gün kargo", "position": "top", "style": "bar"},
      "featured_products": {"enabled": true, "title": "Featured products", "limit": 8},
      "categories": {"enabled": true, "title": "Shop by category", "limit": 8},
      "new_arrivals": {"enabled": true, "title": "New arrivals", "limit": 8},
      "promo": {"enabled": false, "title": "Special offer", "subtitle": "Discover our latest offers.", "image": null, "button_label": "Explore", "button_url": null},
      "custom_css": ""
    },
    "theme": {
      "id": "THEME_UUID",
      "name": "Originos Starter",
      "slug": "originos-starter",
      "version": "1.1.0",
      "manifest": {
        "schema_version": "1.1.0",
        "assets": {"css": null, "js": null},
        "settings": "38 kanonik setting definition"
      },
      "settings": {
        "primary_color": "#111827",
        "secondary_color": "#ffffff",
        "accent_color": "#10b981",
        "background_color": "#ffffff",
        "text_color": "#111827",
        "font_family": "system",
        "border_radius": "medium",
        "header_style": "minimal",
        "footer_style": "columns",
        "product_card_style": "bordered",
        "catalog_columns": 4,
        "show_search": true,
        "show_categories": true,
        "hero_enabled": true,
        "hero_title": "Welcome",
        "hero_subtitle": "",
        "hero_image": null,
        "hero_button_label": "Shop now",
        "hero_button_url": null,
        "announcement_position": "top",
        "announcement_style": "bar",
        "homepage_section_order": ["hero", "announcement", "featured_products", "categories", "new_arrivals", "promo"],
        "homepage_featured_products_enabled": true,
        "homepage_featured_products_title": "Featured products",
        "homepage_featured_products_limit": 8,
        "homepage_categories_enabled": true,
        "homepage_categories_title": "Shop by category",
        "homepage_categories_limit": 8,
        "homepage_new_arrivals_enabled": true,
        "homepage_new_arrivals_title": "New arrivals",
        "homepage_new_arrivals_limit": 8,
        "promo_enabled": false,
        "promo_title": "Special offer",
        "promo_subtitle": "Discover our latest offers.",
        "promo_image": null,
        "promo_button_label": "Explore",
        "promo_button_url": null,
        "custom_css": ""
      }
    }
  }
}
```

`theme` aktif tema yoksa `null` olabilir. `maintenance: true` iken vitrin okunabilir; sepet oluşturma/değiştirme, customer register ve checkout `STORE_MAINTENANCE` ile durdurulur.

## 6. Ürünler

### `GET /storefront/products`

Query parametreleri:

| Parametre | Tip | Açıklama |
|---|---|---|
| `search` | string | Ürün adı veya SKU içinde arama |
| `page` | integer | Sayfa |
| `per_page` | integer | Varsayılan 20, maksimum 100 |

```http
GET /storefront/products?search=kulaklık&page=1&per_page=24
```

Ürün örneği:

```json
{
  "id": "PRODUCT_UUID",
  "type": "physical",
  "name": "Kablosuz Kulaklık",
  "slug": "kablosuz-kulaklik",
  "short_description": "Kısa açıklama",
  "description": "Uzun ürün açıklaması",
  "brand": "Originos",
  "image_url": "https://cdn.example.com/product.jpg",
  "categories": [
    {"id": "CATEGORY_UUID", "name": "Elektronik", "slug": "elektronik", "image_url": "https://cdn.example.com/category.jpg"}
  ],
  "options": [
    {
      "id": "OPTION_UUID",
      "name": "Renk",
      "values": [
        {"id": "VALUE_UUID", "value": "Siyah"}
      ]
    }
  ],
  "media": [
    {
      "id": "MEDIA_UUID",
      "type": "image",
      "url": "https://cdn.example.com/product.jpg",
      "alt_text": "Siyah kablosuz kulaklık"
    }
  ],
  "variants": [
    {
      "id": "VARIANT_UUID",
      "sku": "KB-SIYAH",
      "title": "Siyah",
      "price": {"amount": 129900, "currency": "TRY"},
      "option_values": ["Siyah"],
      "available": true
    }
  ]
}
```

Yalnız `active`, `public` ve yayın zamanı gelmiş ürünler döner. Ürün tipleri `physical`, `digital` veya `epin` olabilir. Sepete eklerken ürün ID'si değil `variants[].id` gönderilir. `available: false` varyantın satın alma butonu kapatılmalıdır.

### `GET /storefront/products/{slug}`

Listeyle aynı ürün şemasını tek `data` objesi olarak döndürür. SEO/product detail sayfalarında canonical kimlik olarak slug kullanılabilir.

## 7. Kategoriler

### `GET /storefront/categories`

Aktif kök kategorileri `sort_order` sırasıyla ve `children` alanıyla döndürür. Varsayılan `per_page=50`, maksimum `100`.

```json
{
  "data": [
    {
      "id": "CATEGORY_UUID",
      "name": "Elektronik",
      "slug": "elektronik",
      "description": null,
      "image_url": "https://cdn.example.com/category.jpg",
      "sort_order": 10,
      "parent_id": null,
      "children": []
    }
  ]
}
```

### `GET /storefront/categories/{slug}`

Kategori bilgisi, çocuk kategoriler ve `products` alanını döndürür. Doğrudan ürünlerde varyantlar, stok durumu, medya ve birincil `image_url` bulunur.

## 8. Kargo seçenekleri

### `GET /storefront/shipping-methods`

```json
{
  "data": [
    {
      "id": "SHIPPING_UUID",
      "name": "Standart Kargo",
      "code": "standard",
      "type": "flat",
      "price": {"amount": 4990, "currency": "TRY"}
    }
  ]
}
```

Checkout ve hesaplama isteklerinde UUID değil `code` gönderilir. Sepette fiziksel ürün yoksa kargo otomatik olarak `0` olur.

## 9. Sepet

Sepet token'ı 64 karakterdir, mağazaya özeldir ve varsayılan olarak 7 gün geçerlidir. Tarayıcıda cookie veya local storage içinde saklanabilir. Customer token ile aynı şey değildir.

### Sepet oluşturma

```http
POST /storefront/cart
```

Body gerekmez.

```json
{
  "data": {
    "id": "CART_UUID",
    "token": "64_CHARACTER_CART_TOKEN",
    "status": "active",
    "currency": "TRY",
    "items": [],
    "subtotal": {"amount": 0, "currency": "TRY"},
    "expires_at": "2026-08-17T12:00:00+03:00"
  }
}
```

### Sepeti getirme

```http
GET /storefront/cart/{token}
```

### Varyant ekleme

```http
POST /storefront/cart/{token}/items
```

```json
{
  "variant_id": "VARIANT_UUID",
  "quantity": 2
}
```

`quantity` 1–100 arasındadır. Aynı varyant yeniden gönderildiğinde yeni satır açılmaz; mevcut satır miktarı verilen değerle değiştirilir.

Sepet item örneği:

```json
{
  "id": 42,
  "product_id": "PRODUCT_UUID",
  "variant_id": "VARIANT_UUID",
  "name": "Kablosuz Kulaklık",
  "quantity": 2,
  "unit_price": {"amount": 129900, "currency": "TRY"},
  "line_total": {"amount": 259800, "currency": "TRY"}
}
```

### Miktar güncelleme

```http
PATCH /storefront/cart/{token}/items/{item}
```

```json
{"quantity": 3}
```

Buradaki `{item}` sepet response'undaki integer item `id` değeridir.

### Satır silme

```http
DELETE /storefront/cart/{token}/items/{item}
```

Başarılı cevap `204 No Content`.

### Server-side hesaplama

```http
POST /storefront/cart/{token}/calculate
```

```json
{
  "coupon_code": "WELCOME10",
  "shipping_method_code": "standard",
  "payment_method": "bank_transfer"
}
```

Her iki alan da opsiyoneldir.

```json
{
  "data": {
    "currency": "TRY",
    "items": [
      {
        "cart_item_id": 42,
        "product_id": "PRODUCT_UUID",
        "variant_id": "VARIANT_UUID",
        "name": "Kablosuz Kulaklık",
        "quantity": 2,
        "unit_price_amount": 129900,
        "subtotal_amount": 259800,
        "discount_amount": 25980,
        "tax_amount": 0,
        "total_amount": 233820
      }
    ],
    "subtotal": {"amount": 259800, "currency": "TRY"},
    "discount": {"amount": 25980, "currency": "TRY", "code": "WELCOME10"},
    "shipping": {"amount": 4990, "currency": "TRY", "method": "standard"},
    "tax": {"amount": 0, "currency": "TRY"},
    "total": {"amount": 238810, "currency": "TRY"}
  }
}
```

Frontend fiyat, indirim, kargo, vergi veya total üretmemelidir. Gösterilen nihai tutarlar her zaman bu endpointten veya checkout response'undan alınmalıdır.

## 10. Checkout

```http
POST /storefront/checkout
Idempotency-Key: 0194f25b-6e24-7d12-a843-29f14e9bc542
```

Guest checkout body:

```json
{
  "cart_token": "64_CHARACTER_CART_TOKEN",
  "email": "buyer@example.com",
  "phone": "+905551112233",
  "billing_address": {
    "first_name": "Ali",
    "last_name": "Yılmaz",
    "country": "TR",
    "city": "İstanbul",
    "address_line_1": "Örnek Mah. No: 1"
  },
  "shipping_address": {
    "first_name": "Ali",
    "last_name": "Yılmaz",
    "country": "TR",
    "city": "İstanbul",
    "address_line_1": "Örnek Mah. No: 1"
  },
  "coupon_code": "WELCOME10",
  "shipping_method_code": "standard"
}
```

Kurallar:

- `Idempotency-Key` zorunlu ve 16–255 karakterdir. Her yeni checkout denemesi için UUID önerilir.
- Network timeout veya belirsiz cevapta **aynı body ve aynı key** ile retry yapılır.
- Aynı key farklı body ile kullanılırsa `409 IDEMPOTENCY_KEY_REUSED` döner.
- Registered checkout için aynı isteğe `Authorization: Bearer {customer_token}` eklenir. Backend email'i müşteri hesabından alır.
- `billing_address` zorunludur. `country` iki harf ISO ülke kodudur.
- `shipping_address`, `coupon_code`, `shipping_method_code`, `payment_method` ve `phone` opsiyoneldir.
- `payment_method` gönderilmezse `settings.payments.default_method` kullanılır. `bank_transfer` ve `paytr` desteklenir. PayTR seçildiğinde sipariş oluşturulduktan sonra `/storefront/payments/paytr/token` çağrılarak güvenli iframe URL'si alınır; sipariş durumu yalnız PayTR callback'i sonrasında değişir.
- Backend cart fiyatlarını yeniden okur, kuponu/kargoyu yeniden doğrular, fiziksel stok ile EPIN stoklarını transaction içinde rezerve eder.

Guest checkout response'unda `guest_access_token` yalnız sipariş ilk oluşturulduğunda düz metin olarak döner. Güvenli biçimde saklanmalıdır:

```json
{
  "data": {
    "order_id": "ORDER_UUID",
    "order_number": "100001",
    "status": "pending",
    "payment_status": "pending",
    "fulfillment_status": "unfulfilled",
    "subtotal": {"amount": 259800, "currency": "TRY"},
    "discount": {"amount": 25980, "currency": "TRY", "code": "WELCOME10"},
    "shipping": {"amount": 4990, "currency": "TRY"},
    "tax": {"amount": 0, "currency": "TRY"},
    "total": {"amount": 238810, "currency": "TRY"},
    "items": [],
    "placed_at": "2026-08-10T12:00:00+03:00",
    "created_at": "2026-08-10T12:00:00+03:00",
    "guest_access_token": "ONE_TIME_GUEST_ORDER_TOKEN"
  }
}
```

Siparişe `bank_transfer` provider'ı eklenir ve ödeme durumu yönetici tahsilatı onaylayana kadar `pending` kalır. Tema kart bilgisi veya payment status üretmemeli; banka adı, hesap sahibi, IBAN, şube ve açıklamayı storefront config'ten göstermelidir.

## 11. Customer kayıt ve login

### Kayıt

```http
POST /storefront/customer/auth/register
```

```json
{
  "first_name": "Ayşe",
  "last_name": "Demir",
  "email": "ayse@example.com",
  "phone": "+905551112233",
  "password": "Secure123",
  "password_confirmation": "Secure123",
  "device_name": "web-storefront"
}
```

Parola en az 8 karakter, en az bir harf ve bir rakam içermelidir. E-posta benzersizliği mağaza bazındadır.

`201` yanıtı:

```json
{
  "data": {
    "token": "1|CUSTOMER_TOKEN",
    "token_type": "Bearer",
    "customer": {
      "id": "CUSTOMER_UUID",
      "first_name": "Ayşe",
      "last_name": "Demir",
      "email": "ayse@example.com",
      "phone": "+905551112233",
      "created_at": "2026-08-10T12:00:00+03:00"
    }
  }
}
```

### Login

```http
POST /storefront/customer/auth/login
```

```json
{
  "email": "ayse@example.com",
  "password": "Secure123",
  "device_name": "web-storefront"
}
```

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

### Profil

```http
GET /storefront/customer/me
Authorization: Bearer CUSTOMER_TOKEN
```

### Logout

```http
POST /storefront/customer/auth/logout
Authorization: Bearer CUSTOMER_TOKEN
```

Yalnız aktif token iptal edilir.

## 12. Customer adresleri

Customer bearer token gerektirir.

Adres şeması:

```json
{
  "type": "both",
  "label": "Ev",
  "first_name": "Ayşe",
  "last_name": "Demir",
  "company": null,
  "phone": "+905551112233",
  "address_line_1": "Örnek Mah. No: 1",
  "address_line_2": "Daire 4",
  "city": "İstanbul",
  "state": "Kadıköy",
  "postal_code": "34710",
  "country": "TR",
  "is_default": true
}
```

`type`: `billing`, `shipping` veya `both`. Aynı tipte yeni bir adres `is_default: true` yapıldığında önceki varsayılan adres otomatik kaldırılır.

| İşlem | İstek |
|---|---|
| Liste | `GET /storefront/customer/addresses` |
| Oluştur | `POST /storefront/customer/addresses` |
| Detay | `GET /storefront/customer/addresses/{address}` |
| Tam güncelleme | `PUT /storefront/customer/addresses/{address}` |
| Kısmi güncelleme | `PATCH /storefront/customer/addresses/{address}` |
| Sil | `DELETE /storefront/customer/addresses/{address}` |

Başka müşterinin adresi güvenlik amacıyla `404` döner.

## 13. Sipariş görüntüleme

### Registered customer siparişleri

```http
GET /storefront/customer/orders?page=1&per_page=20
Authorization: Bearer CUSTOMER_TOKEN
```

```http
GET /storefront/customer/orders/{order}
Authorization: Bearer CUSTOMER_TOKEN
```

Customer yalnız kendi siparişlerini görebilir.

### Guest sipariş

```http
GET /storefront/guest/orders/{order}
X-Order-Token: GUEST_ACCESS_TOKEN
```

Guest token yalnız checkout sırasında düz metin döner ve yalnız o sipariş için geçerlidir.

Sipariş durum alanları:

- `status`: `pending`, `confirmed`, `processing`, `completed`, `cancelled`
- `payment_status`: `pending`, `authorized`, `paid`, `failed`, `partially_refunded`, `refunded`
- `fulfillment_status`: `unfulfilled`, `partial`, `fulfilled`, `cancelled`

Order detail, ürün satırlarını ve yüklenmişse dijital `downloads` listesini döndürür. Müşteri response'unda yöneticiye özel payment transaction, fatura adresi ve dahili operasyon alanları gösterilmez.

## 14. Dijital ürün indirme

Ödeme ve fulfillment tamamlandıktan sonra customer veya guest order detail içindeki `downloads[].download_url` kullanılır:

```json
{
  "id": "GRANT_UUID",
  "file": {
    "id": "FILE_UUID",
    "name": "E-kitap",
    "original_name": "kitap.pdf",
    "mime_type": "application/pdf",
    "size": 4829102
  },
  "download_url": "https://originos.netkora.com/api/v1/storefront/downloads/...?...",
  "download_count": 0,
  "max_downloads": 5,
  "expires_at": "2026-09-10T12:00:00+03:00"
}
```

`download_url` kısa süreli signed URL'dir ve olduğu gibi çağrılmalıdır. Registered customer grant'ında ayrıca customer bearer token gönderilir. Guest grant'ında URL içindeki signature ve token yeterlidir. Başarılı yanıt JSON değil dosya stream'idir.

Frontend URL süresi dolduğunda order detail'i yeniden çağırarak yeni signed URL almalıdır.

## 15. Hata formatları

Domain hatası:

```json
{
  "message": "Cart has expired.",
  "code": "CART_EXPIRED"
}
```

Validation hatası:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "billing_address.city": [
      "The billing address.city field is required."
    ]
  }
}
```

Önemli storefront hata kodları:

| HTTP | Kod | Frontend davranışı |
|---:|---|---|
| 401 | `API_KEY_INVALID` | Yapılandırma hatası; anahtarı kontrol et |
| 401 | `API_KEY_REVOKED` / `API_KEY_EXPIRED` | Yeni public key gerekir |
| 403 | `API_KEY_ABILITY_DENIED` | Key ability kapsamı eksik |
| 403 | `CUSTOMER_ACCESS_FORBIDDEN` | Customer session'ı temizle, login göster |
| 401 | `INVALID_CREDENTIALS` | Login formunda genel hata göster |
| 403 | `CUSTOMER_AUTH_REQUIRED` | Guest checkout kapalı; login/register göster |
| 403 | `GUEST_ORDER_ACCESS_DENIED` | Guest order token yanlış/eksik |
| 403 | `SUBSCRIPTION_EXPIRED` | Satın almayı durdur, mağaza desteği göster |
| 403 | `STORE_SUSPENDED` / `STORE_DISABLED` | Mağaza kullanılamıyor ekranı |
| 503 | `STORE_MAINTENANCE` | Bakım sayfası; daha sonra retry |
| 404 | `STORE_NOT_FOUND` | Store key/domain yapılandırmasını kontrol et |
| 404 | kaynak bulunamadı | Mağaza dışı veya yayından kalkmış kaynak |
| 409 | `IDEMPOTENCY_KEY_REUSED` | Yeni body için yeni checkout key üret |
| 422 | `IDEMPOTENCY_KEY_REQUIRED` | Geçerli key ile isteği tekrar gönder |
| 425 | `REQUEST_IN_PROGRESS` | Kısa bekle, aynı key/body ile retry |
| 422 | `CART_EMPTY` | Sepete yönlendir |
| 422 | `CART_EXPIRED` | Yeni sepet oluştur |
| 422 | `CURRENCY_MISMATCH` | Sepeti yeniden oluştur veya ürünü çıkar |
| 422 | `INSUFFICIENT_STOCK` | Stok mesajı göster, sepeti yenile |
| 422 | `EPIN_OUT_OF_STOCK` | Dijital kod stoğu yok mesajı |
| 422 | `INVALID_COUPON` / `COUPON_EXPIRED` | Kuponu kaldır ve yeniden hesapla |
| 422 | `COUPON_USAGE_LIMIT_REACHED` | Kupon kullanım sınırı mesajı |
| 422 | `COUPON_MINIMUM_NOT_MET` | Minimum sepet tutarını göster |
| 422 | `INVALID_SHIPPING_METHOD` | Kargo listesini yenile |
| 422 | `PAYMENT_METHOD_UNAVAILABLE` | Ödeme ayarlarını yenile; etkin yöntem göster |
| 403 | `DOWNLOAD_FORBIDDEN` | Doğru customer hesabıyla login iste |
| 403 | `DOWNLOAD_TOKEN_INVALID` | Order detail'den yeni URL al |
| 403 | `DOWNLOAD_EXPIRED` / `DOWNLOAD_REVOKED` | İndirme hakkı kullanılamıyor |
| 403 | `DOWNLOAD_LIMIT_REACHED` | İndirme limiti doldu |
| 404 | `DIGITAL_FILE_NOT_FOUND` | Destek mesajı göster |
| 429 | rate limit | Backoff ve retry uygula |

Her API response'unda `X-Request-ID` header'ı bulunur. Hata ekranında veya loglarda bu değer saklanmalı; destek taleplerinde paylaşılmalıdır.

## 16. Önerilen storefront akışı

1. Uygulama açılışında `GET /storefront/store` çağır.
2. Locale, currency, SEO, announcement ve tema config'ini uygula.
3. `GET /storefront/categories` ile navigasyonu oluştur.
4. `GET /storefront/products` ile kataloğu, slug endpointiyle ürün detayını göster.
5. İlk sepete eklemede `POST /storefront/cart` çağır ve token'ı sakla.
6. Varyantları `POST /cart/{token}/items` ile ekle.
7. Checkout ekranında kargo yöntemlerini al ve her değişiklikte `/calculate` çağır.
8. Customer login olmuşsa bearer token ekle; değilse guest checkout politikasına göre devam et.
9. Yeni UUID `Idempotency-Key` ile checkout çağır.
10. Guest siparişte dönen `guest_access_token` değerini güvenli sakla.
11. Sipariş detayını customer veya guest endpointinden takip et.
12. Dijital ürünlerde order detail'deki signed download URL'lerini kullan.

## 17. Frontend güvenlik kontrol listesi

- Yalnız `pk_...` public store key'i client tarafında kullan.
- Secret API key, merchant token veya platform token'ını temaya gömme.
- API'den gelen HTML metnini sanitize et; ürün açıklamasını kontrolsüz `innerHTML` ile basma.
- `custom_css` yalnız mağaza yöneticisinin güvenilen girdisi olarak ele alınmalı, `<style>.textContent` ile uygulanmalı ve HTML/script olarak çalıştırılmamalıdır.
- Tema asset URL'lerini yalnız güvenilen Originos/CDN kaynaklarından yükle ve mümkünse CSP allowlist kullan.
- Customer token'ını üçüncü taraf scriptlere aktarma.
- Checkout butonunu çift tıklamaya karşı kilitle; network retry'da aynı idempotency key'i koru.
- Fiyatları client tarafında otorite kabul etme.
- Guest order token'ını URL path, analytics event veya public log içine yazma.
- `available: false` varyantları sepete göndermeden engelle; backend kontrolünü yine de esas kabul et.
- `X-Request-ID`, hata `code` ve HTTP status değerlerini gözlemlenebilirlik için kaydet.

## 18. CORS

Storefront domain'i production ortamında `CORS_ALLOWED_ORIGINS` listesine eklenmelidir. İzin verilen request header'ları:

```text
Accept
Authorization
Content-Type
Idempotency-Key
X-Order-Token
X-Request-ID
X-Store-Key
```

`X-Request-ID` browser tarafından okunabilir. Cookie credential kullanılmaz; customer auth bearer token ile çalışır.

## 19. API artifact'leri

- 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`
- Bu rehberin Markdown sürümü: `https://originos.netkora.com/docs/storefront-theme-api.md`
- Tarayıcıda okunabilir sürüm: `https://originos.netkora.com/docs/storefront-theme-api`

OpenAPI dosyası code generation ve şema inceleme; Postman collection ise gerçek akışları sırayla çalıştırmak için kullanılabilir.

## 20. Tema teslim kriterleri

Bir tema Originos'a hazır kabul edilmeden önce:

- Theme Contract `1.5.0` içindeki 84 ayarın tamamıyla çalışmalı. Müşteri hesabı ayarlarına ek olarak favori drawer'ı, cihazda saklanan son görüntülenen ürünler, indirim/karşılaştırma fiyatı ve mobil sticky satın alma seçenekleri desteklenmelidir.
- `homepage.section_order` sırasını, kapalı bölümleri ve bölüm limitlerini dikkate almalı.
- Banka havalesi ekranı `settings.payments.bank_transfer` alanlarını göstermeli; PayTR varsayılan olduğunda resmi PayTR logosu footer'da görünmeli ve ödeme PayTR güvenli iframe'i içinde açılmalıdır.
- Tema değiştirme sonrası ek config migration istememeli.
- `theme: null` durumunda güvenli fallback göstermeli.
- Mobile, tablet ve desktop responsive olmalı.
- Ürün tipi `physical`, `digital` ve `epin` için uygun durumları göstermeli.
- Varyant `available` durumunu dikkate almalı.
- Boş katalog, boş kategori, boş sepet, bakım ve API hata ekranları bulunmalı.
- Guest ve registered checkout akışları çalışmalı.
- Minor-unit para değerleri doğru formatlanmalı.
- Loading, retry ve rate-limit durumları yönetilmeli.
- Customer order ve signed digital download akışları doğrulanmalı.
- Secret credential içermemeli.
