# Sepet satırları

Sepet satırları, sepetteki ürünleri tutar. Her satır, ekleme anında ürün adının ve SKU'nun anlık görüntüsünü alır, çözümlenen birim fiyatı minör birim cinsinden saklar ve made-to-measure ürünlerde tam bir yapılandırma anlık görüntüsü tutar. Üç endpoint'in tümü `commerce:write` scope'u ve `active` bir sepet gerektirir ve sepet sahipliği kontrolünden geçer (Sepetler sayfasına bakın). Her endpoint, `GET /v1/api/commerce/carts/{id}` ile aynı şekle sahip tam sepet zarfını döndürür.

## Capability kuralları

Bir ürünün nasıl ekleneceği `capability` değerine bağlıdır (bkz. [Ürünler](/tr/api/catalog/products)):

- **Aktif varyantı olan `standard` ürünler**: `variant_id` zorunludur ve ürünün aktif bir varyantı olmalıdır.
- **Varyantı olmayan `standard` ürünler**: `variant_id` gönderilmemelidir.
- **`made_to_measure` ürünler**: `configuration` zorunludur ve `variant_id` yasaktır. Yapılandırma, configurator motoru tarafından fiyatlandırılan `{"dimensions": {...}, "choices": ["choice_id", ...]}` şeklinde bir nesnedir; boyutlar tam sayıdır. Satır, bir `config_hash` ile birlikte `attributes.configuration` altında tam bir anlık görüntü saklar (boyutlar, seçimler, fiyat dökümü, bileşen listesi, matris hücresi). Bileşen stoku, configurator bileşeni başına rezerve edilir.
- `standard` ürünler için `configuration` yasaktır.
- `capability_active` değeri `false` olan bir ürün eklenemez: `standard` ürünleri commerce modülü, `made_to_measure` ürünleri configurator modülü satar.

## Adetler

`quantity`, sıfırdan büyük, en fazla 8 tam sayı basamağı ve en fazla 3 ondalık basamağı olan bir JSON sayısıdır (en fazla `99999999.999`). JSON sayısı olarak veya noktalı düz bir ondalık string olarak (`"0.75"`) gönderin; ondalık virgül veya üs içeren bir string (`"0,75"`, `"1e3"`) reddedilir. `unit` değeri `piece` olan bir ürün yalnızca tam sayı kabul eder (`2` veya `2.0`, asla `2.5`); diğer birimlerdeki bir ürün (`kg`, `m2`, `hour`, ...) en fazla üç ondalık basamak kabul eder, örneğin `0.75`. Adetler JSON sayısı olarak döner (`0.75`, `12`). Satır toplamı, birim fiyat çarpı adettir ve yarım yukarı yuvarlanarak tam küçük birime getirilir.

## Ret kodları

Bir satır eklenemediğinde 422 gövdesi, `message` ve `errors` alanlarının yanında en üst düzeyde makine tarafından okunabilir bir `code` taşır:

| `code` | Anlamı |
| --- | --- |
| `CAPABILITY_INACTIVE` | Ürünün capability'si şu anda satılamaz (`capability_active` `false`): onu satan modül mağaza için devre dışı. `errors.product_id` üzerinde bildirilir. |
| `INVALID_CONFIGURATION` | Varyant veya yapılandırma ürüne uymuyor: varyant eksik, bilinmiyor veya izinli değil; yapılandırma eksik veya bir `standard` ürün için gönderilmiş; ya da configurator motoru yapılandırmayı reddetti (hata anahtarları, örneğin `dimensions.width`, aynen aktarılır). |
| `NOT_PURCHASABLE` | Ürün satılamaz, örneğin çözümlenebilir bir fiyatı yok. `errors.product_id` üzerinde bildirilir. |

```json title="Response (422, line refusal)"
{
  "message": "Width must be a whole number.",
  "errors": {
    "dimensions.width": ["Width must be a whole number."]
  },
  "code": "INVALID_CONFIGURATION"
}
```

Düz girdi doğrulaması (eksik veya hatalı `product_id` ya da `quantity`, bilinmeyen veya pasif bir ürün, `piece` ürün için ondalıklı adet) `code` olmadan standart 422 zarfını kullanır. `code` varsa ona göre dallanın, yoksa `errors` alanına dönün.

Satırlar; ürün, varyant ve yapılandırma hash'i kombinasyonu üzerinden tekilleştirilir: aynı kombinasyonu tekrar eklemek mevcut satırla birleşir, adetleri toplar ve birim fiyatı yeni adet kademesinde yeniden çözümler.

Birim fiyatlar, request'in fiyatlandırma bağlamına göre çözümlenir: varsa `X-Customer-Token` ile tanımlanan müşteri, yoksa sepetin bağlı müşterisi, o da yoksa misafir fiyatlandırması.

## POST /v1/api/commerce/carts/{id}/lines

Sepete bir ürün ekler veya mevcut özdeş bir satırla birleştirir. Tam sepeti 201 durumuyla döndürür.

### Parametreler

**Path**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `id` | string (UUID) | evet | Sepet id'si. `active` bir sepet olmalıdır. |

**Body**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `product_id` | string (UUID) | evet | Eklenecek ürün. Mağazanın aktif bir ürünü olmalıdır. |
| `variant_id` | string (UUID) veya null | koşullu | Ürünün aktif varyantları varsa zorunludur; aksi halde yasaktır ve `made_to_measure` ürünler için her zaman yasaktır. |
| `quantity` | number | evet | Sıfırdan büyük, en fazla 3 ondalık basamak; `piece` ürün için tam sayı (yukarıdaki Adetler bölümüne bakın). Birleştirilmiş satır adedi, politikanın `max_quantity_per_line` değerini aşamaz. |
| `configuration` | object | koşullu | `made_to_measure` ürünler için zorunlu, diğerleri için yasaktır. Şekil: `{"dimensions": {...}, "choices": [choice_ids]}`, boyutlar tam sayı olarak. Configurator motoru tarafından doğrulanır. |

**Header'lar**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `X-Customer-Token` | string | hayır | Fiyatlandırma bağlamını seçer; sepet misafir olmayan bir müşteriye bağlıysa zorunludur. |

```bash title="Request"
curl -X POST https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "variant_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
    "quantity": 2
  }'
```

```javascript title="Request (JS)"
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/carts/${cartId}/lines`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      product_id: "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      variant_id: "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
      quantity: 2,
    }),
  }
);
const { data: cart } = await res.json();
```

```json title="Response"
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "status": "active",
    "lines": [
      {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "variant_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Garden Chair",
        "sku": "CHAIR-GRN-M",
        "quantity": 2,
        "unit_price_amount": 2250,
        "unit_price": { "amount": 2250, "currency": "EUR" },
        "line_total": { "amount": 4500, "currency": "EUR" },
        "attributes": null
      }
    ],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}
```

Bir `made_to_measure` ürün için oluşturulan satır, yapılandırma anlık görüntüsünü taşır. `components[].qty`, yapılandırılmış birim başına kesin bir ondalık string'dir (rezerve edilen stok için satır adediyle çarpın):

```json title="Response (made-to-measure line excerpt)"
{
  "attributes": {
    "configuration": {
      "dimensions": { "width": 120, "depth": 180 },
      "choices": ["c1d2e3f4-..."],
      "breakdown": [
        { "label": "Base", "amount": 18900 },
        { "label": "Motor: Motorised", "amount": 7500 }
      ],
      "components": [{ "product_id": "...", "qty": "2.16" }],
      "matrix": { "id": "...", "name": "Fabric blinds" }
    }
  }
}
```

### Hatalar

Satır retleri, mesajın `errors` içinde ilgili alana eklendiği ve en üst düzeyde bir `code` taşıyan 422 hatalarıdır (yukarıdaki Ret kodları bölümüne bakın):

| Durum | Gövde | Ne zaman |
| --- | --- | --- |
| 422 | `errors.product_id: ["Product is not available."]` | Ürün bilinmiyor veya aktif değil. `code` yok. |
| 422 | `errors.product_id: ["Product is not available."]`, `code: "NOT_PURCHASABLE"` | Ürün mevcut ve aktif ama satılamaz, örneğin çözümlenebilir bir fiyatı yok. |
| 422 | `errors.product_id: ["Product is not available."]`, `code: "CAPABILITY_INACTIVE"` | Ürünün capability'si mağaza için aktif değil (`capability_active` `false`). |
| 422 | `errors.variant_id: ["Choose a variant for this product."]`, `code: "INVALID_CONFIGURATION"` | Ürünün aktif varyantları var ve `variant_id` gönderilmemiş. |
| 422 | `errors.variant_id: ["Variant is not available."]`, `code: "INVALID_CONFIGURATION"` | Varyant id'si, ürünün aktif bir varyantına çözümlenmiyor. |
| 422 | `errors.variant_id: ["This product has no variants."]`, `code: "INVALID_CONFIGURATION"` | Varyantı olmayan bir ürün için `variant_id` gönderilmiş. |
| 422 | `errors.variant_id: ["Configurable products have no variants."]`, `code: "INVALID_CONFIGURATION"` | Bir `made_to_measure` ürün için `variant_id` gönderilmiş. |
| 422 | `errors.configuration: ["Configure this product before adding it."]`, `code: "INVALID_CONFIGURATION"` | `configuration` olmadan gönderilen `made_to_measure` ürün. |
| 422 | `errors.configuration: ["This product is not configurable."]`, `code: "INVALID_CONFIGURATION"` | Bir `standard` ürün için `configuration` gönderilmiş. |
| 422 | `errors.choices`, `errors.choices.{choiceId}`, `errors.dimensions.{key}`, `errors.configuration`, `code: "INVALID_CONFIGURATION"` | Configurator motoru hataları, [teklif hata kataloğunda](/tr/api/configurator/quote) belgelendiği gibi kendi alan anahtarlarına eklenir; ondalıklı bir boyut için `{Label} must be a whole number.` dahil. |
| 422 | `errors.quantity: ["The quantity must be a whole number."]` | `piece` ürün için ondalıklı adet. `code` yok. |
| 422 | `errors.quantity: ["..."]` | Adet eksik, düz bir ondalık sayı değil (virgül, üs, 3'ten fazla ondalık basamak veya 8'den fazla tam sayı basamağı) ya da sıfırdan büyük değil. `code` yok. |
| 422 | `{"message": "Quantity exceeds the per-line maximum.", "code": "cart_constraint"}` | Birleştirilmiş adet, `max_quantity_per_line` değerini aşacaktı. |
| 422 | `{"message": "Cart line limit reached.", "code": "cart_constraint"}` | Yeni bir ayrı satır eklemek, `max_lines` değerini aşacaktı. |
| 404 | `{"message": "..."}` | Sepet bilinmiyor, süresi dolmuş veya `active` değil. |
| 403 | sahiplik, scope, modül veya askıya alma gövdesi | Sepetler sayfasına bakın. |
| 429 | `{"message": "Too Many Attempts."}` | Rate limit aşıldı. |

## PATCH /v1/api/commerce/carts/{id}/lines/{lineId}

Bir satırın adedini, eklemeyle aynı adet kurallarıyla değiştirir (`piece` ürün için tam sayı). Yapılandırılmamış ürün satırları, request'in fiyatlandırma bağlamı kullanılarak yeni adette (kademeli fiyatlandırma) yeniden fiyatlandırılır. Yapılandırılmış satırlar ve varyantı sonradan devre dışı bırakılmış satırlar, saklanan birim fiyatlarını korur; kullanılamazlık checkout'ta ortaya çıkar. Tam sepeti 200 durumuyla döndürür.

### Parametreler

**Path**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `id` | string (UUID) | evet | Sepet id'si. `active` bir sepet olmalıdır. |
| `lineId` | string (UUID) | evet | Satır id'si. Bu sepete ait olmalıdır. |

**Body**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `quantity` | number | evet | Sıfırdan büyük, en fazla 3 ondalık basamak, `piece` ürün için tam sayı; en fazla politikanın `max_quantity_per_line` değeri. |

**Header'lar**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `X-Customer-Token` | string | hayır | Fiyatlandırma bağlamını seçer; sepet misafir olmayan bir müşteriye bağlıysa zorunludur. |

```bash title="Request"
curl -X PATCH https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"quantity": 3}'
```

```json title="Response"
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "lines": [{ "id": "5e6f7a8b-...", "quantity": 3, "...": "..." }],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}
```

### Hatalar

| Durum | Gövde | Ne zaman |
| --- | --- | --- |
| 422 | `{"message": "...", "errors": {"quantity": ["..."]}}` | Adet eksik veya hatalı, sıfırdan büyük değil ya da `piece` ürün için ondalıklı. Bu endpoint hiçbir zaman bir ret `code`'u ile yanıt vermez: artık çözümlenemeyen bir satır, ödemeye kadar fiyatını korur. |
| 422 | `{"message": "Quantity exceeds the per-line maximum.", "code": "cart_constraint"}` | `max_quantity_per_line` değerinin üzerinde. |
| 404 | `{"message": "..."}` | Sepet bilinmiyor veya `active` değil ya da satır bu sepete ait değil. |
| 403 | sahiplik, scope, modül veya askıya alma gövdesi | Sepetler sayfasına bakın. |
| 429 | `{"message": "Too Many Attempts."}` | Rate limit aşıldı. |

## DELETE /v1/api/commerce/carts/{id}/lines/{lineId}

Bir satırı sepetten kaldırır ve sepet için tutulan tüm stok rezervlerini serbest bırakır. Kaldırılan satır olmaksızın tam sepeti 200 durumuyla döndürür.

### Parametreler

**Path**

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `id` | string (UUID) | evet | Sepet id'si. `active` bir sepet olmalıdır. |
| `lineId` | string (UUID) | evet | Satır id'si. Bu sepete ait olmalıdır. |

Body yok.

```bash title="Request"
curl -X DELETE https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 \
  -H "Authorization: Bearer {api_key}"
```

```json title="Response"
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "lines": [],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}
```

### Hatalar

| Durum | Gövde | Ne zaman |
| --- | --- | --- |
| 404 | `{"message": "..."}` | Sepet bilinmiyor veya `active` değil ya da satır bu sepete ait değil. |
| 403 | sahiplik, scope, modül veya askıya alma gövdesi | Sepetler sayfasına bakın. |
| 429 | `{"message": "Too Many Attempts."}` | Rate limit aşıldı. |
