# Uçtan uca kılavuz

Bu sayfa, Borkol API'nin tüm storefront yeteneklerini bir storefront'u inşa edeceğiniz sırayla ele alır: katalog gezinme, ürün yapılandırma, sepetler, indirimler, checkout, sipariş takibi, B2B teklif akışı, müşteri hesapları ve webhook'lar. Her adım size çalışan kod verir ve her alanı belgeleyen referans sayfasına bağlantı sunar. Bu sayfa ile referans bölümleri elinizdeyken, başka hiçbir şey okumadan eksiksiz bir storefront oluşturabilirsiniz.

Tüm request'ler için temel URL: `https://api.borkol.com`. Her storefront route'u `/v1/api` altında yaşar ve `Authorization: Bearer {api_key}` olarak gönderilen bir tenant API anahtarıyla doğrulanır. Anahtar mağazayı tanımlar, dolayısıyla herhangi bir tenant header'ına gerek yoktur. Tüm para değerleri, ayrı bir ISO 4217 para birimi alanıyla birlikte minör birim (kuruş/cent) cinsinden tam sayılardır. Tüm kaynak ID'leri UUID string'leridir (sipariş numaraları düz tam sayılardır). Zaman damgaları ISO 8601 biçimindedir.

Herhangi bir koddan önce tek bir kural: API anahtarı sunucu tarafında tutulması gereken bir sırdır. Tüm mağaza için sepet yazma, checkout ve sipariş okuma yetkisi verir. Onu asla bir tarayıcıya veya mobil uygulamaya göndermeyin; storefront arayüzü ile Borkol API arasına ince bir sunucu katmanı koyun. [Mimari sayfası](/tr/api/storefront-guide/architecture) bu deseni ayrıntılı olarak ele alır.

## Adım 1: API anahtarı ve scope'ları edinin

Satıcı, API anahtarlarını client dashboard'da (app.borkol.com) Settings > API keys altında oluşturur. Oluşturma sırasında bir ad ve bir scope kümesi seçer; düz metin anahtar tam olarak bir kez gösterilir ve tekrar alınamaz. Anahtarların süresi asla dolmaz; iptal edilene kadar çalışırlar.

Scope'lar modül başına okuma/yazma çiftleridir. Bir anahtar yalnızca mağazada etkin olan modüllerin scope'larını taşıyabilir; her zaman verilebilen `tenant:read` bunun dışındadır. Eksiksiz bir storefront için şu scope'lara sahip bir anahtar isteyin:

| Scope | Gerekli olduğu yerler |
| --- | --- |
| `commerce:read` | Ürünler, kategoriler, configurator şeması ve fiyat teklifleri, sepet okuma, kargo yöntemleri, siparişler, token ile teklif okuma |
| `commerce:write` | Sepet oluşturma ve değiştirme, checkout, teklif talepleri, teklif kabul/ret |
| `customers:read` | Müşteri profili ve adres okuma (yalnızca hesap özelliği geliştiriyorsanız) |
| `customers:write` | Kayıt, giriş, çıkış, profil ve adres yazma (yalnızca hesap özelliği geliştiriyorsanız) |

`stock:read` ve `stock:write` isteğe bağlıdır: ürün response'ları zaten bir `stock_status` alanı içerir, bu yüzden çoğu storefront stok modülünü doğrudan hiç çağırmaz. `objects:read` yalnızca mağaza CMS tarzı içerik için Objects modülünü kullanıyorsa gereklidir.

Gerekli scope'a sahip olmayan bir anahtarla yapılan request `403 {"message": "Invalid ability provided."}` ile başarısız olur. Mağazada etkin olmayan bir modüle yapılan request `403 {"error": "Module not enabled: commerce"}` ile başarısız olur (bu tek envelope'ta `error` anahtarına dikkat edin). Tüm ayrıntılar: [Kimlik doğrulama](/tr/api/getting-started/authentication) ve [Hatalar ve rate limit'ler](/tr/api/getting-started/errors-and-rate-limits).

Rate limit, o mağazanın tüm anahtarları genelinde, mağaza başına dakikada 120 request'tir. Aşıldığında `Retry-After` header'ıyla birlikte `429 {"message": "Too Many Attempts."}` döner.

## Adım 2: Sağlık kontrolü ve kimlik

`GET /v1/ping` kimlik doğrulaması gerektirmez; bağlantıyı ve temel URL'yi doğrular. `GET /v1/api/me` kimlik doğrulamalı ilk çağrı için standart yoldur: hiçbir scope gerektirmez, anahtarın ait olduğu mağazayı döner ve anahtarın taşıdığı scope'ları listeler. Bozuk veya iptal edilmiş bir anahtarda erken hata almak için uygulamanız açılırken çağırın.

```javascript title="Request (JS)"
const ping = await fetch("https://api.borkol.com/v1/ping");
// { "status": "ok", "version": "v1" }

const me = await fetch("https://api.borkol.com/v1/api/me", {
  headers: { Authorization: `Bearer ${process.env.BORKOL_API_KEY}` },
});
const identity = await me.json();
```

```json title="Response"
{
  "tenant": {
    "id": "9c4e7f0a-2b31-4d6e-8f0a-1d2c3b4a5e6f",
    "name": "Four Seasons Garden",
    "slug": "four-seasons-garden"
  },
  "scopes": ["tenant:read", "commerce:read", "commerce:write", "customers:read", "customers:write"]
}
```

`401 {"message": "Unauthenticated."}` anahtarın eksik, hatalı biçimli veya iptal edilmiş olduğu anlamına gelir. `403 {"message": "Tenant suspended.", "code": "tenant_suspended"}` mağazanın askıya alındığı anlamına gelir; askı süresi boyunca her `/v1/api` route'u bunu döner, bu yüzden bunu storefront genelinde bir kesinti durumu olarak ele alın.

## Adım 3: Kategorileri ve ürünleri çekin, bir katalog oluşturun

Tüm kataloğu iki endpoint yönetir. `GET /v1/api/commerce/categories` aktif kategori ağacının tamamını iç içe, pozisyona göre sıralı ve sayfalamasız döner. `GET /v1/api/commerce/products` aktif ürünleri en yeniden eskiye, sayfa başına 20 kayıt olarak döner (sabittir; `per_page`, sıralama veya arama parametresi yoktur) ve isteğe bağlı olarak `filter[category]={slug}` ile bir kategori alt ağacına filtrelenebilir. Bilinmeyen veya pasif bir slug 404 değil, boş bir sayfa döner.

```javascript title="Request (JS)"
const headers = { Authorization: `Bearer ${apiKey}` };

const cats = await fetch("https://api.borkol.com/v1/api/commerce/categories", { headers });
const { data: tree } = await cats.json();

const prods = await fetch(
  "https://api.borkol.com/v1/api/commerce/products?filter[category]=verandas&page=1",
  { headers }
);
const { data: products, meta } = await prods.json();
// meta.current_page, meta.last_page, meta.total drive your pager
```

```json title="Response (products, abridged)"
{
  "data": [
    {
      "id": "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
      "capability": "standard",
      "capability_active": true,
      "purchasability": "direct",
      "unit": "piece",
      "sku": "TABLE-OAK",
      "slug": "oak-table",
      "name": "Oak Table",
      "description": "Solid oak table.",
      "status": "active",
      "price": { "amount": 54900, "currency": "EUR" },
      "price_display": { "mode": "inc" },
      "categories": [
        { "id": "7a1b3c5d-2e4f-4a6b-8c0d-1e2f3a4b5c6d", "name": "Tables", "slug": "tables", "is_active": true }
      ],
      "variants": [],
      "has_variants": false,
      "price_from": { "amount": 54900, "currency": "EUR" },
      "image": {
        "url": "https://cdn.borkol.com/t/9c2f/table.jpg",
        "variants": { "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80", "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80", "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80" },
        "alt": "Oak table"
      },
      "stock_status": "in_stock",
      "created_at": "2026-07-02T09:14:33.000000Z",
      "updated_at": "2026-08-01T15:20:11.000000Z"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 47 }
}
```

Görselleştirme notları:

- Duyarlı (responsive) görseller için `image.variants` URL'lerini kullanın (`thumb` 160w, `card` 480w, `detail` 1200w); `url` orijinaldir.
- `price.amount` `null` olabilir: mağazalar fiyatları misafirlerden gizleyebilir. Bu durumda "fiyatlar için giriş yapın" gösterin.
- `stock_status` şu değerlerden biridir: `in_stock`, `low_stock`, `out_of_stock`, `backorder`, `untracked` ve yalnızca stock modülü kayıtlıysa mevcuttur. `untracked` (ürünün `track_stock: false` değeri var veya stoğu sayılmıyor) ürünün asla tükenmeyeceği anlamına gelir.
- `unit` adet girişini belirler: `piece` ürün tam sayı kabul eder, diğer tüm birimler (`kg`, `m2`, `hour`, ...) en fazla üç ondalık basamak. `capability` ürünü sepete nasıl ekleyeceğinizi söyler (adım 5); `capability_active: false` ise ürünün şu anda satın alınamayacağı anlamına gelir: satın al düğmesini gizleyin.
- Storefront'unuzdaki bir ürün veya kategori URL'si artık çözümlenmiyorsa, 404 döndürmeden önce yeni adresini `GET /v1/api/redirects?path=...` ile sorun ve `sitemap.xml` dosyasını `GET /v1/api/sitemap` üzerinden oluşturun. Bkz. [Yönlendirmeler ve site haritası](/tr/api/catalog/seo).
- Liste response'u `options` alanını ve tam `images` galerisini içermez; ürün detay sayfası için `GET /v1/api/commerce/products/{id}` çağırın; bu ikisine ek olarak SEO için `meta_title`/`meta_description` alanlarını da ekler.
- `has_variants: true` olan ürünler, `options` (adlar ve değerler) ile `variants[].option_values` eşleştirilerek oluşturulan bir varyant seçici gerektirir.
- Giriş yapmış bir müşterinin oturum token'ını ürün request'lerinde `X-Customer-Token` olarak iletirseniz o müşteriye özel fiyatlandırma alırsınız (grup fiyatları, B2B KDV hariç gösterim, `quantity_tiers`). Token olmadan fiyatlandırma misafir için hesaplanır.

Alan alan referans: [Ürünler](/tr/api/catalog/products) ve [Kategoriler](/tr/api/catalog/categories).

## Adım 4: Yapılandırılabilir ürünler: şema ve fiyat teklifleri

`capability: "made_to_measure"` olan ürünler (bir veranda, ölçüye göre üretilen bir tente) sepete düz bir ürün ID'siyle eklenmez. Önce yapılandırma şemasını çekin, ondan bir configurator arayüzü oluşturun, sonra ziyaretçinin seçimini quote endpoint'iyle fiyatlandırın. Her ikisi de yalnızca `catalog:read` veya `commerce:read` gerektirir ve mağazada configurator modülü etkin olmalıdır (aksi halde `403 {"error": "Module not enabled: configurator"}`).

`GET /v1/api/commerce/configurator/products/{id}/schema` üç blok döner: `dimensions` (min/max değerleri ve seçime bağlı kısıt daraltması olan, anahtarlı sayısal girdiler), `price_matrices` (her birinin hangi seçim koşullarında geçerli olduğunu belirten id, ad ve koşullar) ve `option_groups` (tekli veya çoklu seçim, zorunluluk bayrağı, fiyat farklı seçenekler, `requires`/`excludes` kuralları, görseller ve `requires_quote` bayrakları). Ürün aktif ve ölçüye göre üretilen (made to measure) bir ürün değilse 404 döner.

`POST /v1/api/commerce/configurator/products/{id}/quote` somut bir seçimi fiyatlandırır. Saf bir hesaplamadır: hiçbir şey oluşturulmaz, hiçbir şey yazılmaz.

```javascript title="Request (JS)"
const schemaRes = await fetch(
  `https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/schema`,
  { headers }
);
const { data: schema } = await schemaRes.json();

const quoteRes = await fetch(
  `https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/quote`,
  {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      dimensions: { width: 420, depth: 300 },
      choices: ["c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c", "f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0"],
    }),
  }
);
const { data: quote } = await quoteRes.json();
```

```json title="Response"
{
  "data": {
    "total": 561500,
    "currency": "EUR",
    "breakdown": [
      { "label": "Base", "amount": 530000 },
      { "label": "LED lighting: LED set (6)", "amount": 31500 }
    ],
    "matrix": { "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a", "name": "Glass roof" }
  }
}
```

Frontend'inizin uygulaması gereken configurator arayüz kuralları (API bunların tümünü quote sırasında yeniden doğrular ve ihlal edilen her alan için bir 422 mesajı döner):

- Şemadaki her boyut zorunludur ve kendi `min`/`max` aralığında bir tam sayı olmalıdır. Bir boyutun `constraints` listesinde yer alan bir seçeneğin seçilmesi o aralığı daraltır.
- `single` grup başına en fazla bir seçenek; her `required` grup karşılanmalıdır; `requires` listeleri hedef grup içinde OR, gruplar arasında AND olarak değerlendirilir; `excludes` kesin çakışmalardır.
- Görüntülenen fiyatın her zaman güncel olması için her seçim değişikliğinde yeniden quote alın. Debounce uygulayın ama agresif önbelleklemeyin: quote response'ları tam seçime bağlıdır.
- `requires_quote: true` olan bir seçenek, bu yapılandırmanın doğrudan checkout yerine B2B teklif talebi akışından (adım 9) geçmesi gerektiği anlamına gelir; quote endpoint'i görüntüleme amacıyla yine de fiyatlandırır.
- `errors.configuration = ["not_priceable"]` içeren bir 422, satıcının yapılandırma uzayının bu bölgesini fiyatlandırmadığı anlamına gelir. "Teklif isteyin" şeklinde bir geri dönüş gösterin.

Tam algoritma (matris seçimi, delta geçişleri, yuvarlama) ve tüm 422 mesajları: [Configurator nasıl çalışır](/tr/api/configurator/index), [Yapılandırma şeması](/tr/api/configurator/schema), [Fiyat teklifi](/tr/api/configurator/quote).

## Adım 5: Sepet oluşturun ve satır ekleyin

Sepetler sunucu tarafında tutulan nesnelerdir. `POST /v1/api/commerce/carts` ile bir tane oluşturun (body yok; `commerce:write` gerektirir), dönen `id` değerini ziyaretçi için saklayın (kendi oturumunuza bağlı bir cookie veya local storage) ve satır endpoint'leriyle değiştirin. Sepetler EUR para birimiyle, `active` durumunda ve her yazma işleminin uzattığı 30 günlük bir geçerlilik süresiyle oluşturulur.

```javascript title="Request (JS)"
const cartRes = await fetch("https://api.borkol.com/v1/api/commerce/carts", {
  method: "POST",
  headers,
});
const { data: cart } = await cartRes.json(); // cart.id is the visitor's handle

// Standard product: product_id (+ variant_id when has_variants)
await fetch(`https://api.borkol.com/v1/api/commerce/carts/${cart.id}/lines`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    product_id: "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    variant_id: "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
    quantity: 2,
  }),
});

// Made-to-measure product: configuration instead of variant_id
await fetch(`https://api.borkol.com/v1/api/commerce/carts/${cart.id}/lines`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    product_id: "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
    quantity: 1,
    configuration: {
      dimensions: { width: 420, depth: 300 },
      choices: ["c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c"],
    },
  }),
});
```

Her sepet mutasyonu tam sepeti döner (`GET /v1/api/commerce/carts/{id}` ile aynı şekil): anlık görüntüler ve para nesneleri içeren satırlar, `tax_breakdown` içeren `totals`, tenant'ın `policy` bloğu, `requires_quote` ile birlikte `quote_reasons` ve indirim durumu. Sepet arayüzünüzü her response'tan yeniden oluşturun; toplamları asla istemci tarafında hesaplamayın.

Satır endpoint'lerine gömülü kurallar:

- `variant_id`, ürünün aktif varyantları olduğunda zorunlu, aksi halde yasaktır. `configuration`, `made_to_measure` ürünler için zorunlu, aksi halde yasaktır. 422 mesajları açıktır ("Choose a variant for this product.", "Configure this product before adding it.") ve dallanmak için en üst düzeyde bir `code` taşır (`CAPABILITY_INACTIVE`, `INVALID_CONFIGURATION`, `NOT_PURCHASABLE`).
- `quantity` bir sayıdır: `piece` ürün için tam sayı, diğer birimler için en fazla üç ondalık basamak (`0.75` kg). Sepet, sipariş ve stok adetleri JSON sayısı olarak döner.
- Aynı ürün + varyant + yapılandırma mevcut satırla birleştirilir: miktar toplanır ve birim fiyat yeni miktar kademesinde yeniden hesaplanır.
- `{"quantity": n}` body'si ile `PATCH .../lines/{lineId}` miktarı değiştirir; `DELETE .../lines/{lineId}` satırı kaldırır.
- `policy` bloğu mağazanın limitlerini taşır: `max_lines` (varsayılan 100), `max_quantity_per_line` (varsayılan 100), `min_order_value_amount`, `identity_mode` (`guest_allowed`, `login_required`, `guest_only`) ve `audience`. Limit ihlalleri `422 {"message": "...", "code": "cart_constraint"}` döner.
- `PATCH /v1/api/commerce/carts/{id}` ile `email` ve/veya `metadata` (en fazla 16 skaler anahtar) ayarlanır. E-posta ayarlamak, sepeti bir misafir müşteri kaydına bağlar ve satıcı tarafında terk edilmiş sepet kurtarmayı etkinleştirir.
- Yazma endpoint'leri yalnızca `active` durumundaki sepetleri kabul eder; dönüştürülmüş veya birleştirilmiş bir sepet yazma işlemlerinde 404 döner (tek istisna checkout'tur, bkz. adım 7).
- Bir sepet gerçek bir müşteri hesabına bağlıysa, her sepet request'i o müşterinin `X-Customer-Token` değerini de taşımalıdır; aksi halde `403 {"message": "This cart belongs to a customer."}` ile başarısız olur.

Referans: [Sepetler](/tr/api/carts-and-checkout/carts), [Sepet satırları](/tr/api/carts-and-checkout/cart-lines), [Sepet sahiplenme](/tr/api/carts-and-checkout/claim).

## Adım 6: İndirim kodları ve kargo yöntemleri

Bir sepet en fazla bir indirim kodu taşır. `{"code": "WELCOME10"}` body'si ile `PUT /v1/api/commerce/carts/{id}/discount-code` kodu olduğu gibi saklar ve kullanılamaz bir kod için asla hata dönmez: bunun yerine her sepet response'u kodu değerlendirir ve sonucu bildirir. `discount_code_error` alanını kontrol edin (`null` veya `invalid`, `expired`, `min_order`, `not_eligible`, `usage_limit` değerlerinden biri) ve `discounts[]` dizisini görüntüleyin (her biri `{id, name, code, kind, amount}`; `kind` şunlardan biridir: `order`, `products`, `shipping`, `bogo`). Aynı URL'ye `DELETE` kodu kaldırır.

Checkout'tan önce, bu sepete özel geçerli fiyatlarıyla kargo seçeneklerini listeleyin:

```javascript title="Request (JS)"
const shipRes = await fetch(
  `https://api.borkol.com/v1/api/commerce/carts/${cart.id}/shipping-methods`,
  { headers }
);
const { data: methods } = await shipRes.json();
```

```json title="Response"
{
  "data": [
    { "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f", "name": "Express", "amount": 995, "free_over_amount": null, "effective_amount": 995 },
    { "id": "4d5e6f7a-8b9c-4d0e-1f2a-3b4c5d6e7f8a", "name": "Standard", "amount": 495, "free_over_amount": 5000, "effective_amount": 0 }
  ]
}
```

`effective_amount`, bu sepetin gerçekte ödeyeceği tutardır: sepetin ürün toplamı yöntemin `free_over_amount` değerine ulaştığında 0'a düşer. Bu tutarlar para nesnesi değil, minör birim cinsinden düz tam sayılardır. Referans: [İndirim kodları](/tr/api/carts-and-checkout/discounts), [Kargo yöntemleri](/tr/api/carts-and-checkout/shipping-methods).

## Adım 7: Checkout ile siparişe dönüştürme

`POST /v1/api/commerce/carts/{id}/checkout` sepeti `pending_payment` durumunda bir siparişe dönüştürür. Her satırı yeniden doğrular, her fiyatı yeniden hesaplar, stoğu yeniden kontrol eder, mağazanın sepet politikasını uygular, B2B teklif tetikleyicilerini değerlendirir ve indirim kullanımını kilitler. `shipping_method_id`, sepette kargolanabilir satırlar varsa (`requires_shipping: true` olan ürünler) ve mağazanın en az bir aktif kargo yöntemi varsa tam olarak o durumda zorunludur; yalnızca kargo gerektirmeyen ürünler içeren sepetler bunu atlar.

```javascript title="Request (JS)"
const res = await fetch(`https://api.borkol.com/v1/api/commerce/carts/${cart.id}/checkout`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "jane@example.com",
    billing_address: {
      name: "Jane Doe",
      street: "Keizersgracht 1",
      postal_code: "1015 CC",
      city: "Amsterdam",
      country: "NL",
    },
    shipping_address: null,
    shipping_method_id: "4d5e6f7a-8b9c-4d0e-1f2a-3b4c5d6e7f8a",
  }),
});

if (res.status === 201 || res.status === 200) {
  const { data: order } = await res.json(); // order.id, order.number, order.status
} else if (res.status === 409) {
  const err = await res.json();
  if (err.code === "QUOTE_REQUIRED") {
    // route the buyer to the quote request flow (step 9); err.reasons explains why
  } else {
    // "Cart contents changed.": err.cart is the refreshed cart (lines carry
    // "unavailable": true where relevant), err.out_of_stock and
    // err.discount_unavailable list the specifics when applicable.
    // Re-render the cart from err.cart and let the buyer retry.
  }
} else if (res.status === 422) {
  const err = await res.json();
  // err.code (when present): login_required | guest_only | b2b_approval_required
  // | b2c_only | min_order_value | cart_constraint. Otherwise field validation
  // in err.errors ("Cart is empty.", "Choose a valid shipping method.", addresses).
}
```

Body gereksinimleri: `email` (zorunlu, geçerli e-posta), `billing_address` (zorunlu nesne; `name`, `street`, `postal_code`, `city` zorunlu ve `country` tam 2 karakterlik ISO kodu), `shipping_address` (isteğe bağlı, null olabilir, serbest biçimli), `shipping_method_id` (UUID, yalnızca yukarıdaki koşul altında zorunlu). Response tam siparişi içerir: tam sayı `number`, para nesnesi toplamlar, `tax_breakdown`, satır başına vergi, kargo ücreti ve adı.

Checkout idempotent'tir: zaten dönüştürülmüş bir sepet için tekrar POST atmak mevcut siparişi 200 ile döner (ilk başarı 201'dir), dolayısıyla bir ağ yeniden denemesi asla yinelenen sipariş oluşturamaz. Checkout'a geçerli bir `X-Customer-Token` eşlik ederse sipariş o müşteriye bağlanır ve sipariş geçmişinde görünür; geçersiz bir token kesin bir `401 {"message": "Invalid customer token."}` hatasıdır.

Her hata şekli ve tam sipariş alan referansı: [Checkout](/tr/api/carts-and-checkout/checkout) ve [Siparişler](/tr/api/orders-and-quotes/orders).

## Adım 8: Sipariş durumu ve sipariş geçmişi

Siparişler, tamamen satıcı tarafından yönetilen sabit bir durum makinesinde ilerler: `pending_payment` durumundan `paid` veya `cancelled` durumuna; `paid` durumundan `shipped`, `cancelled` veya `refunded` durumuna; `shipped` durumundan `completed` veya `refunded` durumuna; `completed` durumundan `refunded` durumuna; `cancelled` ve `refunded` son durumlardır. Storefront durumu yalnızca okur.

İki okuma yolu vardır:

- `GET /v1/api/commerce/orders/{id}`, `commerce:read` ile bir siparişi UUID üzerinden çeker. Müşteri sahipliği kontrolü yoktur; API anahtarı tek başına yetkilendirir. Storefront sunucunuz, bu çağrıyı proxy'lemeden önce isteği yapanın o siparişi görmeye yetkili olduğunu (örneğin siparişin kendi oturumunun checkout'undan geldiğini) doğrulamalıdır.
- `GET /v1/api/commerce/my/orders` güvenli "siparişlerim" sayfasıdır: `X-Customer-Token` gerektirir ve yalnızca o müşterinin siparişlerini, en yeniden eskiye, sayfalanmış olarak döner (`per_page` 1 ile 100 arası, varsayılan 25).

```javascript title="Request (JS)"
// Confirmation page polling (your server verified ownership)
const orderRes = await fetch(`https://api.borkol.com/v1/api/commerce/orders/${orderId}`, { headers });
const { data: order } = await orderRes.json();
// order.status, order.tracking_carrier, order.tracking_number

// Account order history
const myOrders = await fetch("https://api.borkol.com/v1/api/commerce/my/orders?per_page=25", {
  headers: { ...headers, "X-Customer-Token": customerToken },
});
const { data: orders, meta } = await myOrders.json();
```

Bir sipariş durumu sayfasını birkaç saniyede bir sorgulamak, dakikada 120 request bütçesine karşı savurganlıktır; sayfa yüklendiğinde ve kullanıcı eyleminden sonra sorgulayın, gerçek zamanlı olması gereken her şeyi webhook'larla (adım 11) yönetin. Referans: [Siparişler](/tr/api/orders-and-quotes/orders).

## Adım 9: B2B yolu: teklif talebi, token'lı kabul ve ret

Sales modülü etkin ve teklif talepleri açık olan mağazalar bir RFQ (teklif talebi) yolunu zorunlu kılabilir veya sunabilir. Bunu sepet size söyler: `requires_quote: true` ile birlikte makine tarafından okunabilir `quote_reasons` (`cart_threshold`, `configurator_choice`, `customer_group`, `product_purchasability`). Böyle bir sepette checkout `409 {"code": "QUOTE_REQUIRED", "reasons": [...]}` ile reddedilir; bu yüzden alıcıyı ödeme adımı yerine bir teklif talebi formuna yönlendirin. `purchasability: "quote"` olan ürünler ve `requires_quote` bayraklı configurator seçenekleri, doğrudan ürün sayfasından oraya yönlendirilmelidir.

`POST /v1/api/commerce/carts/{id}/quote-request` sepeti bir teklif olarak gönderir (durum `submitted`, `Q-17` gibi bir numara) ve sepeti dönüştürür. Geçerli bir `X-Customer-Token` bunları müşteri profilinden sağlamıyorsa `email` ve `name` zorunludur. Mağazada teklif talepleri kapalıysa veya sales modülü devre dışıysa endpoint 404 döner.

```javascript title="Request (JS)"
const rfq = await fetch(`https://api.borkol.com/v1/api/commerce/carts/${cart.id}/quote-request`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "buyer@corp.example",
    name: "Bob Buyer",
    customer_note: "Need delivery before October.",
  }),
});
const { data: quote } = await rfq.json(); // quote.number: "Q-17", quote.status: "submitted"
```

Ardından satıcı teklifi fiyatlandırır ve bir öneri gönderir. Alıcı, 48 karakterlik düz bir kabul token'ı içeren bir bağlantıyla e-posta alır (sunucu tarafında yalnızca SHA-256 hash'i saklanır). Bu bağlantının işaret ettiği sayfayı storefront'unuz barındırır ve üç token endpoint'ini yönetir; token'ın kendisi kimlik bilgisidir, `X-Customer-Token` devrede değildir:

- `GET /v1/api/commerce/quotes/by-token/{token}` müşteriye dönük teklifi çeker (`commerce:read`): düz tam sayı tutarlar olarak dondurulmuş toplamlar, `valid_until`, `quoted_unit_price` içeren satırlar.
- `POST /v1/api/commerce/quotes/by-token/{token}/accept` (`commerce:write`, body yok) teklifi teklif edilen fiyatlarla bir siparişe dönüştürür ve yalın bir `{"order_id": "...", "order_number": 1043}` döner. Idempotent'tir: tekrar kabul etmek aynı siparişi döner. Şu 409 kodlarıyla reddedebilir: `QUOTE_NOT_OFFERED` (şu anda teklif aşamasında değil; `valid_until` geçtikten sonra tembel süre aşımı dahil), `OFFER_CHANGED` (yeniden hesaplanan toplamlar dondurulmuş teklifle artık eşleşmiyor) veya `OUT_OF_STOCK` (bir `out_of_stock` dizisiyle).
- `POST /v1/api/commerce/quotes/by-token/{token}/decline` isteğe bağlı bir `{"reason": "..."}` ile teklifi reddedilmiş olarak işaretler.

Teklif yaşam döngüsü: `submitted` durumundan `offered` durumuna (satıcı gönderir, toplamlar dondurulur), oradan `accepted` veya `declined` durumuna; `valid_until` süresi geçmiş bir teklif bir sonraki erişimde `expired` durumuna geçer; satıcı `cancel` edebilir. Referans: [Teklif talepleri](/tr/api/orders-and-quotes/quote-requests) ve [Teklif token'ları](/tr/api/orders-and-quotes/quote-tokens).

## Adım 10: Müşteri hesapları

Customers modülü kayıt, giriş, profiller ve adres defterleri ekler. API anahtarınızın yanına ikinci bir kimlik bilgisi getirir: `register` ve `login` tarafından dönen ve `X-Customer-Token` header'ı olarak gönderilen müşteri oturum token'ı. API anahtarı storefront sunucunuzu mağazaya karşı doğrular; müşteri token'ı request'in hangi alışverişçi adına yapıldığını belirler.

```javascript title="Request (JS)"
const login = await fetch("https://api.borkol.com/v1/api/customers/login", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@example.com", password: "s3cret-pass" }),
});
const { data: customer, token: customerToken } = await login.json();

// Attach the anonymous cart to the account (merges any older active cart in)
await fetch(`https://api.borkol.com/v1/api/commerce/carts/${cart.id}/claim`, {
  method: "POST",
  headers: { ...headers, "X-Customer-Token": customerToken },
});
```

Müşteri token'ının platform genelinde yaptıkları:

- Ürün ve sepet okumaları müşterinin grubuna göre fiyatlanır (kademeli fiyatlar, B2B KDV hariç gösterim).
- `POST /v1/api/commerce/carts/{id}/claim` anonim bir sepeti hesaba bağlar ve müşterinin diğer tüm aktif sepetlerini içine birleştirir.
- Token'lı checkout siparişi hesaba bağlar; `GET /v1/api/commerce/my/orders` sayfasını dolduran budur.
- Profil ve adresler `GET`/`PATCH /v1/api/customers/me` ve `GET`/`POST`/`PATCH`/`DELETE /v1/api/customers/me/addresses` altında yaşar; checkout'u önceden doldurmak için adres defterini kullanın.

Kayıt, misafir kayıtlarını yerinde yükseltir: e-posta zaten bir sepette yakalanmışsa (adım 5), yeni hesap aynı müşteri id'sini ve sipariş geçmişini korur. `register` ve `login`, mağaza genelindeki limite ek olarak IP başına dakikada 10 request'lik ekstra bir limit taşır ve her ikisi de `customers:write` gerektirir. Müşteri token'ını kendi oturumunuzda sunucu tarafında saklayın; bir bearer kimlik bilgisidir.

Referans: [Müşteri hesapları](/tr/api/customers/index), [Kayıt ve oturumlar](/tr/api/customers/accounts), [Profil](/tr/api/customers/profile), [Adresler](/tr/api/customers/addresses), [Parola sıfırlama](/tr/api/customers/passwords).

## Adım 11: Sipariş olayları için webhook'lar

Polling neyin değiştiğini siz sorduğunuzda söyler; webhook'lar olay olduğu anda söyler. Satıcı, client dashboard'da (Settings > Webhooks) bir URL, abone olunan olay listesi ve oluşturma sırasında bir kez gösterilen `whsec_` önekli bir secret ile endpoint'leri yapılandırır. Borkol, endpoint'inize şu envelope ile POST atar:

```json title="Delivery body"
{
  "event": "order.created",
  "created_at": "2026-08-14T09:12:44+00:00",
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "number": "1042",
    "status": "pending_payment",
    "total_amount": 15900,
    "currency": "EUR",
    "email": "buyer@example.com"
  }
}
```

Abone olunabilir olaylar: `order.created` ve `order.status_changed` (her ikisi de yukarıdaki sipariş veri şekliyle) ve `stock.adjusted` (yeni mutlak miktarla `{product_id, variant_id, quantity}`). Dashboard'daki "Send test" düğmesi için bir `ping` olayı mevcuttur.

Her teslimat `X-Borkol-Event` (olay adı) ve `X-Borkol-Signature` taşır; ikincisi, endpoint secret'ı ile ham request body'sinin tam hali üzerinden hesaplanan hex bir HMAC-SHA256 imzasıdır. Güvenmeden önce doğrulayın:

```javascript title="Signature verification (Node)"
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHeader, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
```

10 saniye içinde herhangi bir 2xx ile yanıt verin. Bunun dışındaki her şey (veya bir timeout) yeniden denenir: 60 saniye, ardından 300 saniye geri çekilme ile toplam 3 deneme; sonrasında teslimat başarısız olarak işaretlenir. Asıl işi (önbellek geçersiz kılma, e-posta, sipariş karşılama senkronizasyonu) yanıt verdikten sonra, bir kuyrukta yapın. Tipik storefront kullanımları: `order.status_changed` ile sipariş onay sayfasını "ödendi" durumuna geçirmek ve `stock.adjusted` ile önbelleğe alınmış bir ürün sayfasını geçersiz kılmak.

Tam payload ve teslimat referansı: [Webhook'lar](/tr/api/webhooks/index).

## Sıradaki adımlar

- [Storefront mimarisi](/tr/api/storefront-guide/architecture): sunucu tarafı proxy deseni, önbellekleme, idempotency ve ortam kuralları.
- [Hatalar ve rate limit'ler](/tr/api/getting-started/errors-and-rate-limits): proxy'nizin eşlemesi gereken eksiksiz hata kataloğu.
- [Stok durumu](/tr/api/stock/availability): ürünlerdeki `stock_status` yeterli değilse toplu stok sorguları.
- [Objects](/tr/api/objects/index): satıcı tarafından yönetilen sayfalar için CMS tarzı içerik tipleri ve kayıtları.
