# Hepiyiol Tedarikçi Entegrasyon API'si — v1

Bu doküman, Hepiyiol platformunda ürün satan anlaşmalı tedarikçi firmaların (ecza deposu, ilaç/sağlık ürünü
tedarikçisi vb.) kendi sistemlerini platforma bağlaması için hazırlanmıştır.

API ile firmanız:

1. Ürün kataloğunda barkod ile ürün arayabilir,
2. Kendi ilanlarını (stok, fiyat, miad) düzenli olarak toplu güncelleyebilir veya satıştan kaldırabilir,
3. Kendisine gelen siparişleri ve **faturası henüz yüklenmemiş siparişleri** listeleyebilir, onaylayabilir, gerekirse iptal edebilir,
4. Sipariş detayındaki alıcı **TCKN / VKN, vergi dairesi, ticari unvan ve kurumsal/bireysel** ayrımına göre e-Fatura veya e-Arşiv kesip siparişe **PDF / UBL-XML / HTML / görsel dosya veya harici HTTPS fatura linki** yükleyebilir,
5. Siparişi **platformun kargo anlaşmasıyla** (DHL eCommerce / MNG Kargo) barkod + etiket oluşturarak gönderebilir
   **veya** kendi kargo anlaşmasıyla gönderip takip numarasını ve kargo hareketlerini bildirebilir,
6. Müşterilerin açtığı **iade ve iptal taleplerini (RMA / Claims)** sorgulayabilir, onaylayabilir, depoya ulaşan iade kargosunu **teslim alındı** olarak işaretleyebilir veya gerekçeyle reddedebilir,
7. Sipariş, **fatura**, **iade/iptal** ve kargo değişikliklerinden **webhook** (anlık bildirim) ya da **olay akışı** (polling) ile haberdar olabilir.

---

## İçindekiler

1. [Genel bilgiler](#1-genel-bilgiler)
2. [Kimlik doğrulama ve güvenlik](#2-kimlik-doğrulama-ve-güvenlik)
3. [Hata biçimi ve HTTP durum kodları](#3-hata-biçimi-ve-http-durum-kodları)
4. [Önerilen entegrasyon akışı](#4-önerilen-entegrasyon-akışı)
5. [Hesap uçları](#5-hesap-uçları)
6. [Ürün kataloğu ve ilanlar](#6-ürün-kataloğu-ve-ilanlar)
7. [Siparişler](#7-siparişler)
8. [Faturalar (e-Fatura / e-Arşiv)](#8-faturalar-e-fatura--e-arşiv)
9. [Kargo / gönderiler](#9-kargo--gönderiler)
10. [İade ve iptal talepleri (RMA)](#10-iade-ve-iptal-talepleri-rma)
11. [Webhook (anlık bildirim)](#11-webhook-anlık-bildirim)
12. [Olay akışı (polling)](#12-olay-akışı-polling)
13. [Kod ve durum tabloları](#13-kod-ve-durum-tabloları)
14. [Canlıya geçiş kontrol listesi](#14-canlıya-geçiş-kontrol-listesi)

---

## 1. Genel bilgiler

| Konu | Değer |
|---|---|
| Temel adres (base URL) | `https://mordepo-api.azurewebsites.net/api/integration/v1` |
| Biçim | JSON (`Content-Type: application/json; charset=utf-8`) |
| Alan adları | camelCase (`orderId`, `trackingNumber` …) |
| Karakter seti | UTF-8 (Türkçe karakterler desteklenir) |
| Para birimi | TRY, ondalık ayıracı nokta (`189.90`) — tutarlar **KDV dahildir** |
| Tarih/saat | ISO-8601 (`2026-11-06T14:30:00`). Yanıtlardaki tarih/saatler **Türkiye saatidir (UTC+3)**. İstekte saat dilimi belirtilmezse Türkiye saati kabul edilir. |
| Kimlikler | GUID (`23a4baff-72be-4105-b962-f792d65ecba6`) |
| İstek limiti | Varsayılan **dakikada 120 istek** (API anahtarı başına) |
| Sayfalama | `page` (1'den başlar), `pageSize` (en fazla 100, varsayılan 50) |
| Swagger / OpenAPI | `https://mordepo-api.azurewebsites.net/swagger` → "integration" dokümanı |
| Güncel doküman | [hepiyiol.com/entegrasyon-api](https://hepiyiol.com/entegrasyon-api) |

Sayfalı yanıtların ortak biçimi:

```json
{ "items": [ ... ], "page": 1, "pageSize": 50, "totalCount": 132 }
```

---

## 2. Kimlik doğrulama ve güvenlik

### 2.1 API anahtarı

Her istekte `X-Api-Key` başlığı gönderilmelidir:

```http
GET /api/integration/v1/ping HTTP/1.1
Host: mordepo-api.azurewebsites.net
X-Api-Key: mdp_live_96f70cda42fe_EYChxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

- Anahtar, firma hesabı açıldığında Hepiyiol yöneticisi tarafından **yalnızca bir kez** gösterilir ve güvenli bir kanal
  ile iletilir. Platform anahtarın kendisini saklamaz (yalnızca özetini saklar); kaybedilirse yenisi üretilir ve eskisi
  **anında** geçersiz olur.
- Anahtarı yalnızca sunucu tarafında saklayınız; tarayıcı/mobil uygulama koduna, log'lara veya kaynak kod deposuna koymayınız.
- Tüm istekler **HTTPS** üzerinden yapılmalıdır.

### 2.2 IP kısıtlaması (önerilir)

Hesabınız için sabit çıkış IP adreslerinizi bildirirseniz, API yalnızca bu adreslerden gelen istekleri kabul eder
(tek IP veya CIDR, ör. `85.105.10.20, 10.20.0.0/24`). İzin verilmeyen IP'den gelen istekler `403 forbidden` alır.

### 2.3 İstek limiti

Limit aşıldığında `429 Too Many Requests` döner, `Retry-After: 60` başlığı eklenir:

```json
{ "error": { "code": "rate_limited", "message": "İstek limiti aşıldı. Lütfen 1 dakika sonra tekrar deneyiniz." } }
```

Toplu işlemler için tek tek istek yerine toplu uçları (`PUT /listings`, 1000 kayda kadar) kullanınız.

### 2.4 Veri kapsamı

Bir API anahtarı yalnızca **firmanıza bağlı depo**nun ilanlarını ve siparişlerini görebilir/değiştirebilir. Başka bir
firmaya ait sipariş kimliği gönderilirse `404 not_found` döner.

---

## 3. Hata biçimi ve HTTP durum kodları

Tüm hatalar aynı biçimdedir:

```json
{
  "error": {
    "code": "validation_error",
    "message": "Sipariş 'shipped' durumunda; onaylanamaz.",
    "details": null
  }
}
```

| HTTP | `error.code` | Anlamı / Ne yapmalı |
|---|---|---|
| 200 | – | Başarılı |
| 201 | – | Kayıt oluşturuldu (gönderi) |
| 202 | – | Talep alındı, yönetici onayı bekleniyor (iptal talebi) |
| 207 | – | Toplu işlemde **kısmi başarı** (bazı kayıtlar hatalı; `errors` alanını inceleyiniz) |
| 400 | `invalid_body`, `invalid_query` | İstek gövdesi/parametreleri eksik veya hatalı |
| 401 | `unauthorized` | `X-Api-Key` yok veya geçersiz |
| 403 | `forbidden` | Hesap pasif, IP izni yok veya işlem hesabınızın kargo moduna uygun değil |
| 404 | `not_found` | Kayıt yok veya firmanıza ait değil |
| 422 | `validation_error` | İş kuralı hatası (mesajı kullanıcıya/operasyona gösterebilirsiniz) |
| 429 | `rate_limited` | İstek limiti aşıldı; `Retry-After` kadar bekleyiniz |
| 500 | `internal_error` | Beklenmeyen hata. Mesajdaki **izleme kodunu** destek talebinize ekleyiniz |
| 503 | `unavailable` | Entegrasyon API'si geçici olarak kapalı |

**Yeniden deneme önerisi:** 429, 500, 502, 503, 504 ve zaman aşımlarında artan aralıklarla (ör. 2 sn, 10 sn, 60 sn) tekrar
deneyiniz. 4xx (429 hariç) hatalarını tekrar denemeyiniz; isteği düzeltiniz. Onay, iptal ve gönderi oluşturma uçları
tekrar çağrılmaya karşı güvenlidir (aynı sipariş için mükerrer gönderi açılmaz).

---

## 4. Önerilen entegrasyon akışı

```mermaid
sequenceDiagram
    autonumber
    participant F as Firma Sistemi
    participant M as Hepiyiol API
    participant K as Kargo (DHL eCom/MNG veya firmanın kargosu)

    F->>M: GET /ping (bağlantı testi)
    F->>M: PUT /webhook (bildirim adresi)
    loop Düzenli (ör. 5-15 dk) veya stok değiştikçe
        F->>M: PUT /listings (stok, fiyat, miad)
    end
    M-->>F: webhook order.created (yeni sipariş)
    F->>M: GET /orders/{id} (detay, vergi/fatura bilgileri, kalemler)
    alt Sipariş "waiting_approval" ise
        F->>M: POST /orders/{id}/approve
    end
    F->>M: PUT /orders/{id}/invoice veya POST /orders/{id}/invoice/upload (e-Fatura / e-Arşiv)
    alt Platform kargosu
        F->>M: POST /orders/{id}/shipments/platform
        M->>K: Kargo kaydı + barkod
        F->>M: GET .../label?format=html|zpl (etiketi yazdır)
        K-->>M: Kargo hareketleri (otomatik takip)
    else Firmanın kendi kargosu
        F->>K: Kendi kargo anlaşmanızla gönderim
        F->>M: POST /orders/{id}/shipments (takip no)
        F->>M: POST .../shipments/{sid}/events (hareket / teslim)
    end
    M-->>F: webhook order.invoice_uploaded / order.shipped / shipment.updated / order.delivered
```

**Stok senkronizasyonu önerisi**

- Değişen ürünleri anlık (veya 5–15 dakikada bir) `PUT /listings` ile gönderiniz.
- Günde bir kez tüm aktif stok listenizi gönderip, listenizde artık olmayan barkodları `POST /listings/deactivate` ile kapatınız
  (`GET /listings?active=true` ile platformdaki aktif ilanlarınızı karşılaştırabilirsiniz).
- Stok 0 olan ürünü `quantity: 0` ile göndermeniz yeterlidir; ilan otomatik olarak satışa kapatılır.

**Sipariş ve fatura takibi önerisi**

- Birincil yöntem **webhook**'tur. Ek güvence için birkaç dakikada bir `GET /events?after=<son sequence>` ile kaçan olay
  olup olmadığını kontrol ediniz (bkz. [Bölüm 12](#12-olay-akışı-polling)).
- Alternatif: `GET /orders?updatedSince=<son kontrol zamanı>` ile değişen siparişleri çekebilirsiniz.
- ERP / muhasebe tarafında henüz faturalanmamış siparişleri düzenli toplamak için `GET /orders/uninvoiced` (veya
  `GET /orders?hasInvoice=false`) ucunu kullanıp kesilen e-Fatura / e-Arşiv belgesini `PUT /orders/{orderId}/invoice` ya da
  `POST /orders/{orderId}/invoice/upload` ile yükleyiniz (bkz. [Bölüm 8](#8-faturalar-e-fatura--e-arşiv)).
- Müşteri iade/iptal taleplerini `return.*` webhook olaylarıyla anlık alabilir veya `GET /returns?status=received,awaiting_product` ile açık RMA kuyruğunuzu çekebilirsiniz (bkz. [Bölüm 10](#10-iade-ve-iptal-talepleri-rma)).

---

## 5. Hesap uçları

### 5.1 `GET /ping` — Bağlantı ve kimlik testi

```json
{
  "status": "ok",
  "serverTime": "2026-11-06T22:47:20.442953+03:00",
  "partner": {
    "id": "8b4b9b93-4746-447b-b16d-c37841a4c4ee",
    "name": "Sancar Sağlık",
    "code": "SANCAR",
    "cargoMode": "hybrid",
    "listingUserType": 0,
    "allowOrderCancellation": false,
    "webhookEnabled": true
  }
}
```

| Alan | Açıklama |
|---|---|
| `cargoMode` | `platform`: siparişleri platformun kargo anlaşmasıyla gönderirsiniz · `partner`: kendi kargo anlaşmanızla gönderirsiniz · `hybrid`: sipariş bazında ikisinden birini seçebilirsiniz |
| `listingUserType` | İlanlarınızın satış kanalı: `0` = son tüketici (B2C), `1` = eczane (B2B) |
| `allowOrderCancellation` | `true` ise iptal isteğiniz anında uygulanır; `false` ise talep olarak yöneticiye iletilir (202) |

### 5.2 `GET /webhook` — Webhook ayarı

```json
{ "url": "https://entegrasyon.firma.com/hepiyiol/webhook", "enabled": true, "hasSecret": true }
```

### 5.3 `PUT /webhook` — Webhook adresini güncelle

```json
{ "url": "https://entegrasyon.firma.com/hepiyiol/webhook", "enabled": true }
```

- Adres `https://` olmalı ve herkese açık bir adres olmalıdır (yerel/iç ağ adresleri reddedilir — `422`).
- `url: null` veya `enabled: false` ile bildirimler durdurulur (olaylar yine de [olay akışında](#12-olay-akışı-polling) tutulur).

### 5.4 `POST /webhook/rotate-secret` — İmza anahtarını yenile

```json
{ "secret": "whsec_3f9a…" }
```

Yeni anahtar **yalnızca bu yanıtta** döner; eski anahtar anında geçersiz olur. Yenilemeden hemen sonra sisteminizdeki
anahtarı güncelleyiniz.

### 5.5 `POST /webhook/test` — Test bildirimi gönder

Kayıtlı adrese imzalı bir `webhook.test` olayı gönderir ve sonucu döner:

```json
{ "success": true, "statusCode": 200, "error": null, "durationMs": 142 }
```

---

## 6. Ürün kataloğu ve ilanlar

**Kavramlar**

- **Ürün (product):** Platform kataloğundaki ürün (barkod tekildir). Katalogda olmayan bir barkod için ilan açılamaz;
  yeni ürün eklenmesi için bizimle iletişime geçiniz.
- **İlan (listing):** Firmanızın bir ürünü satışa sunduğu kayıt (stok, fiyat, miad). Bir ilan
  **barkod + firmanızın deposu + satış kanalı (`listingUserType`)** ile tekil olarak tanımlanır. Aynı barkod için tekrar
  gönderim mevcut ilanı günceller.

### 6.1 `GET /products` — Katalogda ürün ara

| Parametre | Açıklama |
|---|---|
| `barcode` | Barkod ile tam eşleşme |
| `search` | Ürün adında arama (en az 3 karakter) |
| `page`, `pageSize` | Sayfalama |

`barcode` veya `search` parametrelerinden biri zorunludur.

```http
GET /api/integration/v1/products?barcode=8690000000011
```

```json
{
  "items": [
    {
      "productId": "1c05196a-db4b-4407-8dee-76914a7e129e",
      "barcode": "8690000000011",
      "name": "Vitamin C 1000mg",
      "vatRate": 10.0,
      "referenceMarketPrice": 250.0
    }
  ],
  "page": 1, "pageSize": 50, "totalCount": 1
}
```

### 6.2 `GET /listings` — İlanlarım

| Parametre | Açıklama |
|---|---|
| `barcode` | Belirli bir barkod |
| `active` | `true`: yalnızca satışta olanlar, `false`: yalnızca kapalı olanlar |
| `page`, `pageSize` | Sayfalama |

```json
{
  "items": [
    {
      "listingId": "5eaadc6c-8806-4ec4-8c10-ac805befeba8",
      "productId": "1c05196a-db4b-4407-8dee-76914a7e129e",
      "barcode": "8690000000011",
      "productName": "Vitamin C 1000mg",
      "quantity": 100,
      "maxQuantity": 10,
      "deadline": "1227",
      "marketPrice": 250.0,
      "warehousePrice": 200.0,
      "isActive": true,
      "updatedAt": "2026-11-06T19:36:48.982539"
    }
  ],
  "page": 1, "pageSize": 50, "totalCount": 1
}
```

### 6.3 `PUT /listings` — İlan ekle / güncelle (toplu)

Tek istekte en fazla **1000** kayıt.

```json
{
  "items": [
    {
      "barcode": "8690000000011",
      "quantity": 100,
      "maxQuantity": 10,
      "deadline": "2027-12",
      "marketPrice": 249.90,
      "warehousePrice": 200.00
    },
    {
      "barcode": "8690000000028",
      "quantity": 0
    }
  ]
}
```

| Alan | Zorunlu | Açıklama |
|---|---|---|
| `barcode` | Evet | Katalogdaki ürün barkodu. Aynı istekte aynı barkod bir kez bulunmalıdır. |
| `quantity` | Evet | Satılabilir stok adedi (0 – 1.000.000). **`0` gönderilirse ilan satışa kapatılır.** |
| `maxQuantity` | Hayır | Tek siparişte alınabilecek en fazla adet |
| `deadline` | **Yeni ilan için evet** | Son kullanma tarihi (miad). Kabul edilen biçimler: `0627`, `06/27`, `06/2027`, `2027-06`, `2027-06-30`. Platformda `AAYY` biçiminde saklanır. |
| `marketPrice` | **Yeni ilan için evet** | Tüketiciye satış fiyatı, **KDV dahil** |
| `warehousePrice` | Hayır | Depo fiyatı (bilgi amaçlı) |

Mevcut bir ilanı güncellerken gönderilmeyen (null) `maxQuantity`, `deadline`, `marketPrice` ve `warehousePrice` alanları
mevcut değerini korur; yalnızca `barcode` + `quantity` göndererek stok güncellemesi yapabilirsiniz. `quantity > 0`
gönderilen ilan tekrar satışa açılır.

**Yanıt**

| HTTP | Durum |
|---|---|
| `200` | Tüm kayıtlar başarılı |
| `207` | Bir kısmı başarılı, bir kısmı hatalı |
| `422` | Hiçbir kayıt işlenemedi |

```json
{
  "received": 2,
  "succeeded": 1,
  "failed": 1,
  "errors": [
    {
      "barcode": "8699504010015",
      "error": "Barkod ürün kataloğunda bulunamadı. Ürünün kataloğa eklenmesi için bizimle iletişime geçiniz."
    }
  ]
}
```

Olası kayıt hataları: `Geçersiz barkod.`, `Aynı barkod istekte birden fazla kez gönderilmiş.`,
`quantity 0 ile 1.000.000 arasında olmalıdır.`, `maxQuantity negatif olamaz.`, `Fiyatlar pozitif olmalıdır.`,
`Yeni ilan için marketPrice zorunludur.`, `Yeni ilan için deadline (miad) zorunludur.`, `Geçersiz miad (deadline)…`, `Barkod ürün kataloğunda bulunamadı…`

### 6.4 `POST /listings/deactivate` — İlanları satıştan kaldır

```json
{ "barcodes": ["8690000000011", "8690000000028"] }
```

```json
{ "deactivated": 1, "notFound": ["8690000000028"] }
```

---

## 7. Siparişler

Platformdaki her sipariş, **bir depoya ait** alt siparişlerden oluşur. Bu API'deki "sipariş" (`orderId`) firmanıza düşen
alt siparişi ifade eder; `orderNumber` müşterinin gördüğü sipariş numarasıdır (ör. `MD.26.501`).

### 7.1 `GET /orders` — Sipariş listesi

| Parametre | Açıklama |
|---|---|
| `status` | Bir veya virgülle ayrılmış birden fazla durum kodu (ör. `waiting_approval,preparing`). Bkz. [13.1](#131-sipariş-durumları) |
| `updatedSince` | Bu tarihten sonra oluşturulan/güncellenen siparişler (ör. `2026-11-06T00:00:00`) |
| `createdFrom` | Sipariş oluşturulma başlangıç tarihi (`CreatedOn >= createdFrom`) |
| `createdTo` | Sipariş oluşturulma bitiş tarihi (`CreatedOn <= createdTo`) |
| `hasInvoice` | `false`: faturası henüz yüklenmemiş siparişler · `true`: faturası yüklenmiş siparişler |
| `invoiceNumber` | Fatura numarasında arama (büyük/küçük harf duyarsız kısmi eşleşme, ör. `MDP2026`) |
| `hasOpenReturn` | `true`: üzerinde sonuçlanmamış müşteri iade/iptal talebi bulunan siparişler · `false`: açık talebi olmayanlar |
| `page`, `pageSize` | Sayfalama (en yeni güncellenen önce) |

```json
{
  "items": [
    {
      "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
      "orderNumber": "MD.26.501",
      "statusId": 3,
      "status": "shipped",
      "statusName": "Kargoda",
      "createdAt": "2026-11-06T19:36:48.984881",
      "updatedAt": "2026-11-06T22:46:15.636112",
      "itemCount": 1,
      "totalQuantity": 2,
      "amounts": {
        "itemsTotal": 500.0,
        "vatTotal": 45.45,
        "discountTotal": 0.0,
        "shippingFee": 30.0,
        "grandTotal": 530.0,
        "currency": "TRY"
      },
      "fulfillmentMode": "hybrid",
      "hasInvoice": true,
      "invoiceNumber": "MDP2026000001452",
      "invoice": {
        "invoiceId": "9a7d2c10-4a8e-4d1b-8b02-71c92218b511",
        "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
        "invoiceNumber": "MDP2026000001452",
        "invoiceType": "earsiv",
        "ettn": "550e8400-e29b-41d4-a716-446655440000",
        "invoiceDate": "2026-11-06T20:15:00",
        "totalAmount": 530.0,
        "vatAmount": 48.18,
        "currency": "TRY",
        "hasFile": true,
        "fileName": "MDP2026000001452.pdf",
        "contentType": "application/pdf",
        "fileSizeBytes": 84210,
        "downloadPath": "/api/integration/v1/orders/23a4baff-72be-4105-b962-f792d65ecba6/invoice/file",
        "externalUrl": null,
        "uploadedAt": "2026-11-06T20:16:04.112000"
      },
      "hasOpenReturn": false,
      "shipment": {
        "shipmentId": "bdc65348-6699-4a5f-97d2-c609e87e76bb",
        "providerCode": "MNG",
        "carrierName": "DHL eCommerce (MNG Kargo)",
        "shippedBy": "platform",
        "trackingNumber": "614118757013",
        "trackingUrl": "https://…/614118757013",
        "status": "in_transit",
        "statusName": "Transfer Aşamasında",
        "hasLabel": true,
        "shippedAt": "2026-11-06T12:00:00",
        "deliveredAt": null
      }
    }
  ],
  "page": 1, "pageSize": 50, "totalCount": 1
}
```

| Alan | Açıklama |
|---|---|
| `amounts.itemsTotal` | Ürün satırlarının toplamı (KDV dahil, indirim düşülmüş) |
| `amounts.vatTotal` | Bu toplamın içindeki KDV (bilgi amaçlı) |
| `amounts.shippingFee` | Kargo ücreti: müşteriden sepet başına tek ücret alınır; birden çok depodan oluşan siparişte bu ücretin ürün tutarı oranında sizin siparişinize düşen payıdır |
| `amounts.grandTotal` | `itemsTotal + shippingFee` |
| `fulfillmentMode` | Hesabınızın kargo modu (`platform` / `partner` / `hybrid`) |
| `hasInvoice` | Siparişe fatura yüklenip yüklenmediği (`true` / `false`) |
| `invoiceNumber` | Yüklenen faturanın resmi numarası; fatura yoksa `null` |
| `invoice` | Yüklenen faturanın özet/detay nesnesi (bkz. [Bölüm 8](#8-faturalar-e-fatura--e-arşiv)); fatura yoksa `null` |
| `hasOpenReturn` | Sipariş üzerinde açık (sonuçlanmamış) bir müşteri iade/iptal talebi olup olmadığı (`true` / `false`) |
| `shipment` | Siparişin **güncel** (aktif) gönderisi; yoksa `null` |

### 7.2 `GET /orders/uninvoiced` — Faturası henüz yüklenmemiş siparişler

ERP / ön muhasebe yazılımlarının fatura kesilecek sipariş kuyruğunu tek uçtan çekmesi için hazırlanmış kısayoldur.

- Yalnızca `hasInvoice == false` olan siparişleri döner.
- `status` parametresi **gönderilmezse** varsayılan olarak yalnızca faturalanabilir aktif durumları filtreler:
  `waiting_approval,preparing,shipped,delivered` (iptal edilen `cancelled` ve iade edilen `refunded` siparişler hariç tutulur).
- İsterseniz `status=preparing,shipped`, `updatedSince`, `createdFrom`, `createdTo`, `page`, `pageSize` parametreleriyle daraltabilirsiniz.

```http
GET /api/integration/v1/orders/uninvoiced?status=preparing,shipped&page=1&pageSize=50
```

Yanıt biçimi `GET /orders` (7.1) ile birebir aynıdır (`items`, `page`, `pageSize`, `totalCount`).

### 7.3 `GET /orders/{orderId}` — Sipariş detayı

Listedeki alanlara ek olarak müşteri kimlik/vergi bilgileri, teslimat ve fatura adresleri, kalemler, fatura nesnesi,
tüm gönderiler ve o an yapılabilecek işlemler döner:

```json
{
  "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
  "orderNumber": "MD.26.501",
  "statusId": 2,
  "status": "preparing",
  "statusName": "Hazırlanıyor / Kargolanmayı Bekliyor",
  "createdAt": "2026-11-06T19:36:48.984881",
  "updatedAt": "2026-11-06T20:01:10.120000",
  "itemCount": 1,
  "totalQuantity": 2,
  "amounts": { "itemsTotal": 500.0, "vatTotal": 45.45, "discountTotal": 0.0, "shippingFee": 30.0, "grandTotal": 530.0, "currency": "TRY" },
  "fulfillmentMode": "hybrid",
  "hasInvoice": false,
  "invoiceNumber": null,
  "invoice": null,
  "hasOpenReturn": false,
  "shipment": null,
  "customer": {
    "fullName": "Ayşe Yılmaz",
    "phone": "05321112233",
    "email": "ayse@ornek.com",
    "taxNumber": "11111111110",
    "taxOffice": null,
    "gln": null,
    "isCorporate": false
  },
  "shippingAddress": {
    "title": "Ev Adresim",
    "fullName": "Ayşe Yılmaz",
    "phone": "05321112233",
    "city": "İstanbul",
    "district": "Kadıköy",
    "address": "Caferağa Mah. Moda Cad. No:1",
    "taxNumber": null,
    "taxOffice": null,
    "isCorporate": false
  },
  "invoiceAddress": {
    "title": "Ayşe Yılmaz",
    "fullName": "Ayşe Yılmaz",
    "phone": "05321112233",
    "city": "İstanbul",
    "district": "Kadıköy",
    "address": "Caferağa Mah. Moda Cad. No:1",
    "taxNumber": "11111111110",
    "taxOffice": null,
    "isCorporate": false
  },
  "items": [
    {
      "lineId": "191534f9-ebc2-41e3-bca2-26f498ba81e2",
      "listingId": "5eaadc6c-8806-4ec4-8c10-ac805befeba8",
      "productId": "1c05196a-db4b-4407-8dee-76914a7e129e",
      "barcode": "8690000000011",
      "productName": "Vitamin C 1000mg",
      "quantity": 2,
      "unitPrice": 227.27,
      "vatRate": 10.0,
      "unitPriceWithVat": 250.0,
      "lineTotal": 500.0,
      "deadline": "1227"
    }
  ],
  "shipments": [],
  "returns": [],
  "actions": {
    "canApprove": false,
    "canCancel": true,
    "canCreatePlatformShipment": true,
    "canRegisterOwnShipment": true,
    "canUploadInvoice": true
  }
}
```

**Fatura kesimi için alıcı alanları (`customer` ve `invoiceAddress`):**

| Alan | Açıklama |
|---|---|
| `invoiceAddress.title` | Fatura unvanı / başlığı (kurumsal alıcıda ticari unvan veya eczane adı) |
| `invoiceAddress.taxNumber` | 11 haneli TCKN veya 10 haneli VKN (fatura adresinde girilmemişse alıcı hesabındaki değer döner) |
| `invoiceAddress.taxOffice` | Vergi dairesi adı (kurumsal/eczane alıcılarda dolu; bireysel alıcılarda `null`) |
| `invoiceAddress.isCorporate` | `true`: kurumsal / eczane alıcı (VKN veya vergi dairesi mevcut) · `false`: bireysel tüketici (e-Arşiv) |
| `customer.gln` | Eczane alıcılarda 13 haneli GLN numarası (İTS / B2B süreçleri için); bireysel üyelerde `null` |
| `actions.canUploadInvoice` | Siparişe fatura yüklenip güncellenebileceğini belirtir (`true` / `false`) |

> **Kişisel veri:** Müşteri ad, TCKN/VKN, telefon ve adres bilgileri yalnızca siparişin faturalandırılması, hazırlanması
> ve teslimatı amacıyla paylaşılır (KVKK / VUK). Bu verileri başka amaçla kullanmayınız, gereğinden uzun saklamayınız ve log'lamayınız.

`actions` alanı, siparişin o anki durumuna ve hesabınızın kargo moduna göre hangi işlemleri yapabileceğinizi gösterir;
arayüzünüzde butonları buna göre açıp kapatabilirsiniz.

### 7.4 `POST /orders/{orderId}/approve` — Siparişi onayla

Gövde gerekmez. Yalnızca `waiting_approval` durumundaki siparişler onaylanabilir; onay sonrası sipariş `preparing`
durumuna geçer. Zaten `preparing` durumundaki siparişte tekrar çağrılırsa hata vermez (idempotent). Yanıt: güncel sipariş
detayı (7.3).

> Ödeme sonrası siparişler çoğunlukla doğrudan `preparing` durumunda oluşur; bu durumda onay gerekmez.

### 7.5 `POST /orders/{orderId}/cancel` — Siparişi iptal et

Stok yetersizliği vb. nedenlerle siparişi karşılayamıyorsanız kullanınız. Yalnızca **henüz kargoya verilmemiş**
(`waiting_approval` / `preparing`, aktif gönderisi olmayan) siparişler iptal edilebilir.

```json
{ "reason": "Ürün stokta kalmadı." }
```

`reason` zorunludur (en fazla 500 karakter).

| Yanıt | Durum |
|---|---|
| `200` + `"result": "cancelled"` | Sipariş iptal edildi, müşteriye ödeme iadesi başlatıldı (hesabınızda otomatik iptal yetkisi açıksa) |
| `202` + `"result": "cancel_requested"` | İptal talebiniz Hepiyiol operasyon ekibine iletildi; iptal gerçekleştiğinde `order.cancelled` olayı gelir |
| `422` | Sipariş bu durumda iptal edilemez |

```json
{
  "result": "cancel_requested",
  "message": "İptal talebiniz alındı; müşteri hizmetlerimiz siparişi inceleyip iptal/iade işlemini başlatacaktır.",
  "order": { "...": "sipariş detayı" }
}
```

### 7.6 `GET /orders/{orderId}/returns` — Siparişin iade / iptal talepleri

Belirli bir alt siparişe ait geçmiş ve açık tüm müşteri iade/iptal taleplerini (`PartnerReturnSummaryDto` dizisi, en yeni önce) döner. Şema ve detay uçları için bkz. [Bölüm 10](#10-iade-ve-iptal-talepleri-rma).

---

## 8. Faturalar (e-Fatura / e-Arşiv)

Siparişe ait e-Fatura veya e-Arşiv belgesini platforma yüklemek için **iki yöntem** desteklenir (ERP / entegratör
altyapınıza uygun olanı seçebilirsiniz):

| Yöntem | Uç | Ne zaman tercih edilmeli |
|---|---|---|
| **A. JSON ile fatura kaydı** (`externalUrl` ve/veya `fileBase64`) | `PUT /orders/{orderId}/invoice` | e-Fatura entegratörünüzün (İzibiz, Foriba, Logo, EDM, QNB eFinans, Uyumsoft vb.) görüntüleme linkini göndermek **veya** tek JSON isteğinde Base64 PDF/UBL-XML iletmek istediğinizde |
| **B. Doğrudan dosya yükleme** (`multipart/form-data`) | `POST /orders/{orderId}/invoice/upload` | Ham PDF, UBL-TR XML, e-Arşiv HTML veya PNG/JPG dosyasını Base64'e çevirmeden doğrudan yüklemek istediğinizde |

**Temel kurallar ve güvenlik**

- Her alt sipariş (`orderId`) için tek bir aktif fatura kaydı tutulur. Aynı sipariş için `PUT` veya `POST .../upload`
  tekrar çağrıldığında mevcut fatura **idempotent** olarak güncellenir (yeni dosya gönderilirse eski dosya otomatik temizlenir).
- **En az bir belge kaynağı zorunludur:** İlk kayıtta `externalUrl` (herkese açık `https://` fatura adresi) **veya**
  fatura dosyası (`fileBase64` / `multipart file`) gönderilmelidir; ikisi birden de gönderilebilir.
- **Desteklenen dosya biçimleri ve boyut:** `PDF` (`%PDF-`), `UBL-TR XML`, `e-Arşiv HTML`, `PNG`, `JPEG` — en fazla **10 MB**.
  Dosya içeriği sunucuda magic-byte kontrolünden geçirilir; uzantısı değiştirilmiş geçersiz/çalıştırılabilir dosyalar `422` ile reddedilir.
- **Gizlilik:** Yüklenen fatura dosyaları halka açık CDN'de **tutulmaz**; özel (private) depolamada saklanır ve yalnızca
  yetkili API uçları üzerinden alıcı müşteriye, ilgili tedarikçiye ve platform operasyonuna sunulur.
- **Otomatik senkronizasyon:** Fatura yüklendiğinde müşterinin **"Siparişlerim → e-Faturamı Görüntüle"** ekranında anında
  görünür olur; varsa aktif kargo kaydındaki fatura numarası alanı da otomatik doldurulur ve `order.invoice_uploaded` olayı üretilir.

### Fatura nesnesi

```json
{
  "invoiceId": "9a7d2c10-4a8e-4d1b-8b02-71c92218b511",
  "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
  "invoiceNumber": "MDP2026000001452",
  "invoiceType": "earsiv",
  "ettn": "550e8400-e29b-41d4-a716-446655440000",
  "invoiceDate": "2026-11-06T20:15:00",
  "totalAmount": 530.00,
  "vatAmount": 48.18,
  "currency": "TRY",
  "hasFile": true,
  "fileName": "MDP2026000001452.pdf",
  "contentType": "application/pdf",
  "fileSizeBytes": 84210,
  "downloadPath": "/api/integration/v1/orders/23a4baff-72be-4105-b962-f792d65ecba6/invoice/file",
  "externalUrl": "https://earsiv.entegrator.com.tr/goruntule?ettn=550e8400-e29b-41d4-a716-446655440000",
  "uploadedAt": "2026-11-06T20:16:04.112000"
}
```

### 8.1 `GET /orders/{orderId}/invoice` — Sipariş faturasını sorgula

Siparişe yüklenmiş faturanın üst verisini (fatura nesnesi) döner. Henüz fatura yüklenmemişse `404 not_found` döner.

### 8.2 `PUT /orders/{orderId}/invoice` — Fatura kaydet / güncelle (JSON)

Harici `https://` fatura linki ve/veya Base64 kodlu fatura dosyası ile fatura kaydeder ya da günceller.

**Örnek 1 — Base64 PDF dosyası ile yükleme:**

```json
{
  "invoiceNumber": "MDP2026000001452",
  "invoiceType": "earsiv",
  "ettn": "550e8400-e29b-41d4-a716-446655440000",
  "invoiceDate": "2026-11-06T20:15:00",
  "totalAmount": 530.00,
  "vatAmount": 48.18,
  "currency": "TRY",
  "fileName": "MDP2026000001452.pdf",
  "contentType": "application/pdf",
  "fileBase64": "JVBERi0xLjcKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyA+PgplbmRvYmoKJSVFT0Y="
}
```

**Örnek 2 — Özel entegratör görüntüleme linki (`externalUrl`) ile yükleme:**

```json
{
  "invoiceNumber": "MDP2026000001453",
  "invoiceType": "efatura",
  "ettn": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "invoiceDate": "2026-11-06T20:20:00",
  "totalAmount": 1250.00,
  "vatAmount": 113.64,
  "externalUrl": "https://portal.ornekentegrator.com.tr/fatura/goruntule?ettn=7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
```

| Alan | Zorunlu | Açıklama |
|---|---|---|
| `invoiceNumber` | Evet | GİB resmi fatura numarası (en fazla 100 karakter, ör. `MDP2026000001452`) |
| `invoiceType` | Hayır | `earsiv` veya `efatura`. Boş bırakılırsa alıcı tipine göre otomatik belirlenir (eczane/kurumsal → `efatura`, bireysel → `earsiv`) |
| `ettn` | Hayır (önerilir) | GİB Evrensel Tekil Tanımlama Numarası (UUID, 8–64 karakter) |
| `invoiceDate` | Hayır | Fatura düzenleme tarihi (ISO-8601; boşsa yükleme anı kaydedilir) |
| `totalAmount` | Hayır | KDV dahil fatura genel toplamı |
| `vatAmount` | Hayır | Toplam KDV tutarı (`totalAmount` değerinden büyük olamaz) |
| `currency` | Hayır | Para birimi (varsayılan `TRY`) |
| `externalUrl` | Koşullu* | Herkese açık `https://` fatura görüntüleme/indirme linki (yerel/iç ağ IP'leri güvenlik nedeniyle reddedilir) |
| `fileBase64` | Koşullu* | Ham Base64 veya `data:application/pdf;base64,...` biçiminde kodlanmış dosya içeriği (en fazla 10 MB) |
| `fileName` | Hayır | Dosya adı (ör. `MDP2026000001452.pdf`; boşsa `invoiceNumber` + algılanan uzantı kullanılır) |
| `contentType` | Hayır | Bildirilen MIME tipi (gerçek tip dosya imzasından doğrulanır) |

\* İlk kayıtta `externalUrl` veya `fileBase64` alanlarından **en az biri** dolu olmalıdır. Mevcut faturayı güncellerken
yalnızca üst veriyi (ör. `ettn`, `totalAmount`) değiştirmek isterseniz önceden yüklenmiş dosya korunur.

Yanıt: `200` + güncel fatura nesnesi.

### 8.3 `POST /orders/{orderId}/invoice/upload` — Fatura dosyası yükle (multipart/form-data)

Ham dosyayı Base64 dönüşümü olmadan doğrudan `multipart/form-data` olarak yükler.

| Form alanı | Zorunlu | Açıklama |
|---|---|---|
| `file` | Evet | Fatura dosyası (`PDF`, `UBL-XML`, `HTML`, `PNG`, `JPG` — en fazla 10 MB) |
| `invoiceNumber` | Evet | GİB resmi fatura numarası |
| `invoiceType` | Hayır | `earsiv` veya `efatura` |
| `ettn` | Hayır | GİB ETTN (UUID) |
| `invoiceDate` | Hayır | ISO-8601 fatura tarihi |
| `totalAmount` | Hayır | KDV dahil toplam tutar |
| `vatAmount` | Hayır | KDV tutarı |
| `currency` | Hayır | Para birimi (`TRY`) |
| `externalUrl` | Hayır | Ek olarak harici `https://` görüntüleme linki |

```bash
curl -X POST "https://mordepo-api.azurewebsites.net/api/integration/v1/orders/23a4baff-72be-4105-b962-f792d65ecba6/invoice/upload" \
  -H "X-Api-Key: $HEPIYIOL_API_KEY" \
  -F "file=@MDP2026000001452.pdf;type=application/pdf" \
  -F "invoiceNumber=MDP2026000001452" \
  -F "invoiceType=earsiv" \
  -F "ettn=550e8400-e29b-41d4-a716-446655440000" \
  -F "invoiceDate=2026-11-06T20:15:00" \
  -F "totalAmount=530.00" \
  -F "vatAmount=48.18"
```

Yanıt: `200` + güncel fatura nesnesi.

### 8.4 `GET /orders/{orderId}/invoice/file` — Fatura dosyasını indir

Platforma yüklenmiş (`hasFile: true`) fatura dosyasının ikili (binary) içeriğini döner (`Content-Type: application/pdf`,
`application/xml` vb.). Dosya yüklenmemişse (yalnızca `externalUrl` varsa veya hiç fatura yoksa) `404 not_found` döner.

### 8.5 `DELETE /orders/{orderId}/invoice` — Fatura kaydını sil

Hatalı kesilmiş/iptal edilmiş bir faturayı siparişten kaldırır ve yüklenmiş özel dosyayı siler. Ardından yeni faturayı
8.2 veya 8.3 ile yükleyebilirsiniz.

```json
{ "deleted": true }
```

---

## 9. Kargo / gönderiler

Hesabınızın kargo moduna göre iki yöntemden biri (hybrid modda ikisi de) kullanılabilir:

| Yöntem | Uç | Kargo modu |
|---|---|---|
| **A. Platform kargosu** (DHL eCommerce / MNG Kargo anlaşması) | `POST /orders/{id}/shipments/platform` | `platform`, `hybrid` |
| **B. Firmanın kendi kargosu** | `POST /orders/{id}/shipments` | `partner`, `hybrid` |

Bir siparişin aynı anda yalnızca **bir aktif gönderisi** olabilir. Kendi kargonuzla bildirdiğiniz gönderinin bilgileri
(kargo firması, takip no, link, fatura no) [9.7](#97-patch-ordersorderidshipmentsshipmentid--kendi-kargonuzun-bilgilerini-düzenle) ile
düzenlenebilir. Yöntem değiştirmek (kendi kargosu ↔ platform kargosu) için mevcut gönderi iptal edilip (9.6) yenisi açılır.

### Gönderi nesnesi

```json
{
  "shipmentId": "bdc65348-6699-4a5f-97d2-c609e87e76bb",
  "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
  "providerCode": "MNG",
  "carrierName": "DHL eCommerce (MNG Kargo)",
  "shippedBy": "platform",
  "referenceId": "MD26501-23A4",
  "trackingNumber": "614118757013",
  "trackingUrl": "https://…/614118757013",
  "barcodes": ["C@6B@H21FMLRPNAAA6J"],
  "hasLabel": true,
  "status": "in_transit",
  "statusName": "Transfer Aşamasında",
  "statusDescription": "Transfer Aşamasında",
  "isActive": true,
  "pieceCount": 2,
  "desi": 4.00,
  "kg": 3.00,
  "createdAt": "2026-11-06T22:46:15.011178",
  "shippedAt": "2026-11-06T12:00:00",
  "deliveredAt": null,
  "deliveredTo": null,
  "cancelledAt": null,
  "lastError": null,
  "events": [
    { "date": "2026-11-06T12:00:00", "status": "in_transit", "statusName": "Transfer Aşamasında", "description": "Transfer Aşamasında", "location": "HUB", "source": "carrier" },
    { "date": "2026-11-06T10:00:00", "status": "label_created", "statusName": "Barkod Oluşturuldu", "description": "Gönderi Hazırlandı", "location": "BAĞCILAR", "source": "carrier" }
  ]
}
```

| Alan | Açıklama |
|---|---|
| `shippedBy` | `platform` (platform kargosu), `partner` (firmanın kendi kargosu), `manual` (Hepiyiol operasyonunca girilen) |
| `referenceId` | Kargo firmasına iletilen sipariş referansı (platform gönderilerinde) |
| `barcodes` | Paket barkodları (parça sayısı kadar) |
| `status` | Gönderi durum kodu — bkz. [13.2](#132-gönderi-durumları) |
| `lastError` | Kargo siparişi açıldı ancak barkod henüz alınamadıysa nedeni (sistem otomatik tekrar dener) |
| `events` | Kargo hareketleri (en yeni önce). `source`: `carrier` (kargo firması), `partner` (siz), `admin:…`, `system` |

### 9.1 `GET /orders/{orderId}/shipments` — Siparişin gönderileri

İptal edilmiş eski gönderiler dahil tüm gönderileri (gönderi nesnesi dizisi) döner.

### 9.2 `POST /orders/{orderId}/shipments/platform` — Platform kargosu ile gönderi + barkod oluştur

Siparişi platformun DHL eCommerce (MNG Kargo) anlaşmasıyla gönderir: kargo firmasında sipariş açılır, barkod alınır,
etiket hazırlanır. Alıcı adres/telefon bilgileri siparişten otomatik alınır.

```json
{
  "pieceCount": 2,
  "desi": 4,
  "kg": 3,
  "content": "Sağlık ürünü",
  "invoiceNumber": "ABC2026000123"
}
```

| Alan | Zorunlu | Açıklama |
|---|---|---|
| `pieceCount` | Hayır (vars. 1) | Koli/paket adedi; her paket için ayrı barkod üretilir |
| `desi` | Hayır | **Toplam** desi (paketlere eşit bölünür). Boşsa varsayılan |
| `kg` | Hayır | **Toplam** ağırlık. Boşsa varsayılan |
| `content` | Hayır | İçerik açıklaması |
| `invoiceNumber` | Hayır | Fatura / irsaliye numarası |

Yanıt: `201` + gönderi nesnesi (`status: "label_created"`). Ardından:

1. Etiketi indirip yazdırınız (9.5) ve pakete yapıştırınız,
2. Paketi DHL eCommerce / MNG şubesine teslim ediniz veya kurye ile alımı bekleyiniz,
3. Kargo hareketleri **otomatik olarak** (yaklaşık 10–15 dakikada bir) kargo firmasından alınır; kargo teslim alındığında
   sipariş `shipped`, teslim edildiğinde `delivered` olur ve size webhook gönderilir.

> Nadiren kargo firması siparişi kabul edip barkodu geciktirebilir. Bu durumda `status: "order_created"` ve `lastError`
> dolu döner; sistem barkodu otomatik olarak tekrar dener ve hazır olduğunda `shipment.updated` olayı gönderilir.

Olası hatalar: `403` (hesabınız yalnızca kendi kargonuzla çalışıyor), `422` (sipariş gönderime uygun değil,
adres/telefon eksik, aktif gönderi zaten var, kargo firması hatası — mesajda nedeni yer alır).

### 9.3 `POST /orders/{orderId}/shipments` — Kendi kargonuzla gönderiyi bildir

Siparişi kendi kargo anlaşmanızla gönderdiğinizde takip bilgisini bildirin. Sipariş anında `shipped` (Kargoda) durumuna
geçer ve müşteri bilgilendirilir.

```json
{
  "carrierName": "Aras Kargo",
  "trackingNumber": "ARS998877",
  "trackingUrl": "https://kargotakip.araskargo.com.tr/?code=ARS998877",
  "invoiceNumber": "ABC2026000124",
  "shippedAt": "2026-11-06T16:30:00"
}
```

| Alan | Zorunlu | Açıklama |
|---|---|---|
| `carrierName` | Evet | Kargo firması adı |
| `trackingNumber` | Evet | Takip numarası |
| `trackingUrl` | Hayır (önerilir) | Müşterinin takip edebileceği adres |
| `invoiceNumber` | Hayır | Fatura / irsaliye no |
| `shippedAt` | Hayır | Kargoya veriliş zamanı (boşsa şimdi) |

Yanıt: `201` + gönderi nesnesi (`shippedBy: "partner"`, `status: "in_transit"`).

### 9.4 `POST /orders/{orderId}/shipments/{shipmentId}/events` — Kargo hareketi / teslimat bildir

**Yalnızca kendi kargonuzla gönderdiğiniz** (`shippedBy: "partner"`) gönderiler içindir. Platform gönderilerinin hareketleri
kargo firmasından otomatik alınır (`403`).

```json
{
  "status": "delivered",
  "description": "Alıcıya teslim edildi",
  "location": "Kadıköy Şube",
  "occurredAt": "2026-11-07T11:20:00",
  "deliveredTo": "Ayşe Yılmaz"
}
```

| Alan | Zorunlu | Açıklama |
|---|---|---|
| `status` | Evet | `in_transit`, `at_delivery_branch`, `out_for_delivery`, `delivered`, `delivery_failed`, `returning`, `returned` |
| `description` | Hayır | Açıklama |
| `location` | Hayır | Konum / şube |
| `occurredAt` | Hayır | Hareket zamanı (boşsa şimdi) |
| `deliveredTo` | Hayır | Teslim alan kişi (`delivered` için önerilir) |

`delivered` bildirildiğinde sipariş `delivered` (Tamamlandı) durumuna geçer. Yanıt: güncel gönderi nesnesi.

### 9.5 `GET /orders/{orderId}/shipments/{shipmentId}/label?format=…` — Kargo etiketi

| `format` | İçerik | Kullanım |
|---|---|---|
| `html` (varsayılan) | `text/html` | Tarayıcıda açıp 10x15 cm etiket olarak yazdırma (barkod + alıcı + gönderici bilgisi) |
| `zpl` | `text/plain` | Zebra vb. termal yazıcılara doğrudan gönderim (platform gönderilerinde kargo firmasının resmi etiketi) |
| `png` | `image/png` | Görsel etiket (platformda etkinse) |

### 9.6 `POST /orders/{orderId}/shipments/{shipmentId}/cancel` — Gönderiyi iptal et

```json
{ "reason": "Yanlış koli adedi" }
```

- Platform gönderileri yalnızca paket **kargo şubesine teslim edilmeden önce** iptal edilebilir; kargo firmasındaki kayıt da
  iptal edilir.
- Kendi kargonuzla bildirdiğiniz gönderi de iptal edilebilir; sipariş tekrar `preparing` durumuna döner ve yeni
  gönderi (kendi kargonuzla veya platform kargosuyla) açabilirsiniz. Yalnızca bilgileri düzeltmek için iptal yerine 9.7'yi kullanınız.

Yanıt: gönderi nesnesi (`status: "cancelled"`, `isActive: false`).

### 9.7 `PATCH /orders/{orderId}/shipments/{shipmentId}` — Kendi kargonuzun bilgilerini düzenle

Kendi kargonuzla bildirdiğiniz (`shippedBy: "partner"`) aktif gönderinin bilgilerini düzeltir. **Yalnızca gönderdiğiniz
alanlar değişir**; sipariş durumu değişmez (`shipped` olarak kalır).

```json
{
  "carrierName": "Yurtiçi Kargo",
  "trackingNumber": "YK123456789",
  "trackingUrl": "https://www.yurticikargo.com/tr/online-servisler/gonderi-sorgula?code=YK123456789",
  "invoiceNumber": "ABC2026000125",
  "shippedAt": "2026-11-06T17:00:00"
}
```

| Alan | Açıklama |
|---|---|
| `carrierName` | Kargo firması adı (boş olamaz) |
| `trackingNumber` | Yeni takip numarası. Değişir ve `trackingUrl` gönderilmezse eski link silinir |
| `trackingUrl` | Takip linki (`""` gönderilirse silinir) |
| `invoiceNumber` | Fatura / irsaliye no |
| `shippedAt` | Kargoya veriş zamanı |

- Yanıt: `200` + güncel gönderi nesnesi. Müşterinin sipariş takibindeki bilgi de güncellenir, `shipment.updated` olayı üretilir.
- Platform kargosu gönderileri düzenlenemez (`403`); bilgileri kargo firmasından gelir.
- İptal edilmiş, teslim edilmiş veya iade edilmiş gönderi düzenlenemez (`422`).

---

## 10. İade ve iptal talepleri (RMA)

Müşteriler sipariş durumuna göre iki tür talep oluşturabilir:

| `type` | `typeId` | Ne zaman açılabilir | Akış özeti |
|---|---|---|---|
| `cancel` | `1` | Sipariş henüz kargoya verilmeden önce (`waiting_approval` veya `preparing`) | `received` → onaylandığında sipariş iptal edilir ve tam ödeme iadesi başlatılır (`completed`) |
| `return` | `2` | Sipariş teslim edildikten (`delivered`) sonraki **14 gün** içinde (satır/adet seçerek) | `received` → `approve` (`awaiting_product`) → müşteri iade kargosunu gönderir → `receive` (`product_received`) → platform finans kontrolü sonrası ödeme iadesi tamamlanır (`completed`) |

**Yetki ve finansal güvenlik sınırları**

- **Teslimat sonrası iade (`return`):** Firmanız talebi **onaylayabilir** (`approve` — müşteriye kargo gönderim talimatı iletilir), kargo deponuza ulaştığında **teslim alabilir** (`receive`) veya inceleme sonucunda gerekçeyle **reddedebilir** (`reject`). Fiziksel teslim alım (`product_received`) sonrasındaki kart/ödeme sağlayıcı para iadesi (`completed`), mutabakat güvenliği için Hepiyiol finans operasyonu tarafından gerçekleştirilir ve tamamlandığında `return.completed` olayı gönderilir.
- **Kargo öncesi iptal (`cancel`):** İptal talebinin onaylanması siparişi anında iptal edip ödeme iadesi başlattığından, `POST /returns/{returnId}/approve` çağrısı `cancel` tipindeki taleplerde yalnızca hesabınızda `allowOrderCancellation: true` yetkisi açıksa doğrudan uygulanır (`actions.canApprove: true`). Yetki kapalıysa iptal onayı müşteri hizmetlerimiz tarafından sonuçlandırılır; firmanız gerekirse talebi reddedebilir (`reject`).
- Tüm durum geçiş uçları (`approve`, `receive`, `reject`) **idempotent**'tir: talep zaten hedef durumdaysa hata üretmeden güncel talep detayını döner.

### Talep özet nesnesi

```json
{
  "returnId": "c8d12a90-4f88-4c79-a611-94e8bc1102a4",
  "requestNo": "IAD-2026-000142",
  "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
  "orderNumber": "MD.26.501",
  "typeId": 2,
  "type": "return",
  "typeName": "İade Talebi",
  "statusId": 2,
  "status": "awaiting_product",
  "statusName": "Onaylandı, Ürün Bekleniyor",
  "reasonId": 3,
  "reason": "damaged",
  "reasonName": "Hasarlı / Kusurlu Ürün",
  "isOpen": true,
  "itemCount": 1,
  "totalQuantity": 1,
  "requestedAmount": 250.00,
  "refundAmount": null,
  "currency": "TRY",
  "returnCarrier": "MNG Kargo",
  "returnTrackingNumber": "614998877665",
  "createdAt": "2026-11-08T14:10:00",
  "updatedAt": "2026-11-08T15:05:22",
  "resolvedAt": null
}
```

### 10.1 `GET /returns` — İade ve iptal taleplerini listele

Firmanızın deposuna ait siparişler için açılmış tüm müşteri iade ve iptal taleplerini sayfalı listeler.

| Parametre | Açıklama |
|---|---|
| `status` | Bir veya virgülle ayrılmış durum kodları (ör. `received,awaiting_product`). Bkz. [13.4](#134-iade--iptal-talep-durumları-ve-nedenleri) |
| `type` | `cancel` (kargo öncesi iptal) veya `return` (teslimat sonrası iade) |
| `orderId` | Yalnızca belirli bir alt siparişe ait talepler |
| `search` | Talep numarası (`IPT-...` / `IAD-...`), sipariş numarası (`MD.26...`) veya iade kargo takip numarası |
| `updatedSince` | Bu tarihten sonra oluşturulan veya güncellenen talepler |
| `createdFrom`, `createdTo` | Talep oluşturulma tarih aralığı |
| `page`, `pageSize` | Sayfalama (varsayılan `50`, en fazla `200`) |

```http
GET /api/integration/v1/returns?status=received,awaiting_product&page=1&pageSize=50
```

Yanıt: `{ "items": [ ... ], "page": 1, "pageSize": 50, "totalCount": 1 }` (her kayıt bir talep özet nesnesidir).

### 10.2 `GET /returns/{returnId}` — Talep detayı, kalemler ve geçmiş

Talep özet alanlarına ek olarak müşteri adı/açıklaması, iade kalemleri, durum geçmişi ve o an izinli işlemler (`actions`) döner:

```json
{
  "returnId": "c8d12a90-4f88-4c79-a611-94e8bc1102a4",
  "requestNo": "IAD-2026-000142",
  "orderId": "23a4baff-72be-4105-b962-f792d65ecba6",
  "orderNumber": "MD.26.501",
  "typeId": 2,
  "type": "return",
  "typeName": "İade Talebi",
  "statusId": 2,
  "status": "awaiting_product",
  "statusName": "Onaylandı, Ürün Bekleniyor",
  "reasonId": 3,
  "reason": "damaged",
  "reasonName": "Hasarlı / Kusurlu Ürün",
  "isOpen": true,
  "itemCount": 1,
  "totalQuantity": 1,
  "requestedAmount": 250.00,
  "refundAmount": null,
  "currency": "TRY",
  "returnCarrier": "MNG Kargo",
  "returnTrackingNumber": "614998877665",
  "createdAt": "2026-11-08T14:10:00",
  "updatedAt": "2026-11-08T15:05:22",
  "resolvedAt": null,
  "customerName": "Ayşe Yılmaz",
  "customerNote": "Kutu ezik ulaştı.",
  "returnInstructions": "Ürünleri orijinal ambalajında ve faturasıyla birlikte gönderiniz.",
  "rejectReason": null,
  "approvedAt": "2026-11-08T14:30:00",
  "trackingUpdatedAt": "2026-11-08T15:05:22",
  "receivedAt": null,
  "refundedAt": null,
  "items": [
    {
      "lineId": "191534f9-ebc2-41e3-bca2-26f498ba81e2",
      "productId": "1c05196a-db4b-4407-8dee-76914a7e129e",
      "barcode": "8690000000011",
      "productName": "Vitamin C 1000mg",
      "orderedQuantity": 2,
      "quantity": 1,
      "unitPrice": 250.00,
      "lineTotal": 250.00
    }
  ],
  "events": [
    {
      "date": "2026-11-08T15:05:22",
      "type": "tracking_updated",
      "title": "İade kargo bilgisi girildi",
      "description": "MNG Kargo · 614998877665",
      "fromStatus": "awaiting_product",
      "toStatus": "awaiting_product"
    },
    {
      "date": "2026-11-08T14:30:00",
      "type": "approved",
      "title": "Talep onaylandı, ürün bekleniyor",
      "description": null,
      "fromStatus": "received",
      "toStatus": "awaiting_product"
    }
  ],
  "actions": {
    "canApprove": false,
    "canReject": true,
    "canMarkReceived": true,
    "approveCancelsOrderImmediately": false
  }
}
```

### 10.3 `POST /returns/{returnId}/approve` — Talebi onayla

`received` durumundaki talebi onaylar:

- **`return` (iade) talebinde:** Durum `awaiting_product` olur ve müşteriye e-posta/SMS ile gönderim talimatı iletilir. İsteğe bağlı `instructions` gönderilmezse standart talimat metni kullanılır.
- **`cancel` (iptal) talebinde:** Hesabınızda `allowOrderCancellation: true` yetkisi açıksa sipariş iptal edilir, müşteriye tam ödeme iadesi başlatılır ve talep `completed` durumuna geçer.

```json
{
  "instructions": "Ürünleri faturasıyla birlikte MNG Kargo anlaşma kodumuzla karşı ödemeli gönderebilirsiniz."
}
```

Yanıt: `200` + güncel talep detayı (10.2).

### 10.4 `POST /returns/{returnId}/receive` — İade ürününü teslim al

Müşterinin gönderdiği iade paketi deponuza ulaştığında `awaiting_product` durumundaki `return` talebini `product_received` (Ürün Teslim Alındı) durumuna geçirir. Ardından Hepiyiol finans ekibi tutar iadesini tamamlar (`return.completed`).

```json
{
  "note": "Koli ve ambalaj sağlam şekilde depoya teslim alındı."
}
```

Yanıt: `200` + güncel talep detayı (10.2).

### 10.5 `POST /returns/{returnId}/reject` — Talebi reddet

Sonuçlanmamış (`received`, `awaiting_product` veya `product_received`) bir talebi gerekçe belirterek `rejected` durumuna alır. `reason` müşteriye bildirileceği için açık ve kurumsal bir dille yazılmalıdır (zorunlu, 3–1000 karakter).

```json
{
  "reason": "Ürünün koruyucu emniyet bandı açılmış olduğundan hijyen kuralları gereği iade kabul edilememektedir."
}
```

Yanıt: `200` + güncel talep detayı (10.2).

---

## 11. Webhook (anlık bildirim)

### 11.1 İstek biçimi

Olay gerçekleştiğinde kayıtlı adresinize `POST` isteği gönderilir:

```http
POST /hepiyiol/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Hepiyiol-Webhook/1.0
X-Hepiyiol-Event: order.shipped
X-Hepiyiol-Delivery: df36f0e7-8925-4095-b29d-d5e1f3b38829
X-Hepiyiol-Sequence: 2
X-Hepiyiol-Timestamp: 1793995153
X-Hepiyiol-Signature: sha256=5c1f0f6d0b…

{
  "id": "df36f0e7-8925-4095-b29d-d5e1f3b38829",
  "sequence": 2,
  "type": "order.shipped",
  "createdAt": "2026-11-06T22:39:13.182254",
  "data": {
    "order": { "orderId": "6eeccb90-…", "orderNumber": "MD.26.502", "status": "shipped", "...": "sipariş özeti (7.1)" },
    "previousStatus": "preparing",
    "status": "shipped"
  }
}
```

| Başlık | Açıklama |
|---|---|
| `X-Hepiyiol-Event` | Olay tipi |
| `X-Hepiyiol-Delivery` | Olay kimliği (gövdedeki `id` ile aynı; tekrar denemelerde değişmez) |
| `X-Hepiyiol-Sequence` | Firmanıza özel artan sıra numarası |
| `X-Hepiyiol-Timestamp` | İmza zamanı (Unix saniye, UTC) |
| `X-Hepiyiol-Signature` | `sha256=` + HMAC-SHA256 imzası |

### 11.2 Olay tipleri

| Olay | Ne zaman | `data` içeriği |
|---|---|---|
| `order.created` | Firmanıza yeni sipariş düştüğünde (ödeme tamamlandıktan sonra) | `order` (**tam detay**, 7.3) |
| `order.approved` | Onay bekleyen sipariş onaylandığında | `order` (özet), `previousStatus`, `status` |
| `order.shipped` | Sipariş kargoya verildiğinde | `order`, `previousStatus`, `status` |
| `order.delivered` | Sipariş teslim edildiğinde / tamamlandığında | `order`, `previousStatus`, `status` |
| `order.cancelled` | Sipariş iptal edildiğinde | `order`, `previousStatus`, `status` |
| `order.status_changed` | Diğer durum değişiklikleri (ör. iade) | `order`, `previousStatus`, `status` |
| `order.invoice_uploaded` | Siparişe fatura yüklendiğinde veya güncellendiğinde (API veya yönetim/depo panelinden) | `order` (özet), `invoice` (fatura nesnesi) |
| `order.invoice_deleted` | Siparişteki fatura kaydı silindiğinde | `order` (özet), `invoice` (`null`) |
| `return.requested` | Müşteri yeni bir iptal veya iade talebi açtığında | `order` (özet), `returnRequest` (talep özeti, Bölüm 10) |
| `return.approved` | İade talebi onaylandığında (`awaiting_product`) | `order` (özet), `returnRequest` |
| `return.tracking_updated` | Müşteri iade kargo firması ve takip numarasını girdiğinde/güncellediğinde | `order` (özet), `returnRequest` |
| `return.received` | İade kargosu depoda teslim alındığında (`product_received`) | `order` (özet), `returnRequest` |
| `return.completed` | İptal veya iade talebi sonuçlanıp ödeme iadesi tamamlandığında (`completed`) | `order` (özet), `returnRequest` |
| `return.rejected` | İade veya iptal talebi reddedildiğinde (`rejected`) | `order` (özet), `returnRequest` |
| `return.cancelled` | Müşteri açık talebini geri çektiğinde (`cancelled_by_customer`) | `order` (özet), `returnRequest` |
| `shipment.created` | Gönderi/barkod oluşturulduğunda | `order` (özet), `shipment` (özet) |
| `shipment.updated` | Kargo durumu değiştiğinde (transfer, dağıtımda, teslim edilemedi vb.) | `order`, `shipment` |
| `shipment.cancelled` | Gönderi iptal edildiğinde | `order`, `shipment` |
| `webhook.test` | `POST /webhook/test` ile | test verisi |

Yeni olay tipleri ileride eklenebilir; tanımadığınız tipleri `2xx` döndürerek yok sayınız.

### 11.3 İmza doğrulama (zorunlu)

İmza şu şekilde hesaplanır:

```
signature = "sha256=" + hex( HMAC_SHA256( webhookSecret, X-Hepiyiol-Timestamp + "." + <ham istek gövdesi> ) )
```

- İmzayı **ham gövde** (byte'lar değiştirilmeden, JSON parse edilmeden önce) üzerinden hesaplayınız.
- Karşılaştırmayı sabit zamanlı (constant-time) yapınız.
- Tekrar saldırılarına karşı `X-Hepiyiol-Timestamp` değeri şimdiki zamandan 5 dakikadan eski/yeniyse isteği reddediniz.
- İmza geçersizse `401` döndürünüz ve işlemi yapmayınız.

**C# (.NET)**

```csharp
using System.Security.Cryptography;
using System.Text;

static bool VerifyHepiyiolSignature(string secret, string timestamp, string rawBody, string signatureHeader)
{
    if (!long.TryParse(timestamp, out var ts) ||
        Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300)
        return false;

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{rawBody}"));
    var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signatureHeader ?? ""));
}
```

**Node.js (Express)**

```js
const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/hepiyiol/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Hepiyiol-Timestamp');
  const sig = req.get('X-Hepiyiol-Signature') || '';
  const raw = req.body.toString('utf8');

  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);

  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.HEPIYIOL_WEBHOOK_SECRET)
    .update(`${ts}.${raw}`).digest('hex');
  if (sig.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401);

  const event = JSON.parse(raw);
  // event.id daha önce işlendiyse tekrar işlemeyin (idempotency)
  queue.push(event);            // ağır işleri kuyruğa atın
  res.sendStatus(200);          // 15 sn içinde 2xx dönün
});
```

**PHP**

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_HEPIYIOL_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_HEPIYIOL_SIGNATURE'] ?? '';

if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300) { http_response_code(401); exit; }

$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, getenv('HEPIYIOL_WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }

$event = json_decode($raw, true);
// $event['id'] ile mükerrer kontrolü yapıp işleyin
http_response_code(200);
```

### 11.4 Teslim, tekrar deneme ve sıralama

- Adresiniz **15 saniye** içinde `2xx` dönmelidir. Ağır işlemleri kuyruğa alıp hemen yanıt veriniz.
- `2xx` dışındaki yanıtlar, zaman aşımı ve bağlantı hataları başarısız sayılır ve şu aralıklarla tekrar denenir:
  **1 dk, 5 dk, 15 dk, 1 sa, 3 sa, 6 sa, 12 sa, 24 sa** (toplam 8 deneme). Sonrasında olay "teslim edilemedi" olarak
  işaretlenir; Hepiyiol operasyon ekibi elle yeniden gönderebilir, siz de [olay akışından](#12-olay-akışı-polling) alabilirsiniz.
- Yönlendirmeler (3xx) izlenmez; adresiniz doğrudan `2xx` dönmelidir.
- Aynı olay birden fazla kez gelebilir (**en az bir kez** teslim). `id` alanına göre mükerrer kontrolü yapınız.
- Olayların geliş sırası garanti değildir. Sıralama için `sequence` değerini kullanınız; şüphede kalırsanız
  `GET /orders/{orderId}` ile güncel durumu çekiniz (en güvenilir kaynak her zaman API'dir).

---

## 12. Olay akışı (polling)

### `GET /events?after={sequence}&limit={n}`

Webhook'a alternatif veya yedek olarak, firmanıza ait tüm olayları sırayla okuyabilirsiniz. Webhook kapalı olsa bile olaylar
kaydedilir.

| Parametre | Açıklama |
|---|---|
| `after` | Son işlediğiniz `sequence` değeri (ilk çağrıda `0`) |
| `limit` | Sayfa boyutu (1–500, varsayılan 100) |

```json
{
  "events": [
    {
      "id": "22a78df8-3b2b-444d-94c7-87ce48f9d4b9",
      "sequence": 1,
      "type": "shipment.created",
      "createdAt": "2026-11-06T22:39:13.112975",
      "data": { "order": { "...": "..." }, "shipment": { "...": "..." } }
    }
  ],
  "nextCursor": 1,
  "hasMore": false
}
```

Kullanım: `nextCursor` değerini saklayınız ve bir sonraki çağrıda `after` olarak gönderiniz. `hasMore: true` ise hemen
tekrar çağırınız; `false` ise birkaç dakika sonra tekrar kontrol ediniz.

---

## 13. Kod ve durum tabloları

### 13.1 Sipariş durumları

| `statusId` | `status` | Açıklama | Firma ne yapmalı |
|---|---|---|---|
| 1 | `waiting_approval` | Onay bekliyor | `approve` veya `cancel` |
| 2 | `preparing` | Hazırlanıyor / kargolanmayı bekliyor | Siparişi hazırlayıp fatura ve gönderi oluşturun |
| 3 | `shipped` | Kargoda | Henüz yüklenmediyse faturayı yükleyin; teslimatı takip edin |
| 4 | `delivered` | Teslim edildi / tamamlandı | Henüz yüklenmediyse faturayı yükleyin |
| 5 | `cancelled` | İptal edildi | Hazırlığı durdurun |
| 6 | `waiting_customer_approval` | Müşteri/eczane onayı bekleniyor | Bekleyin |
| 7 | `refunded` | İade edildi | – |

> `payment_pending` (0) durumundaki, ödemesi tamamlanmamış siparişler firmalara gösterilmez.

### 13.2 Gönderi durumları

| `status` | Açıklama | Son durum mu? |
|---|---|---|
| `order_created` | Kargo firmasında sipariş açıldı, barkod bekleniyor | |
| `label_created` | Barkod/etiket oluşturuldu, kargoya teslim bekleniyor | |
| `in_transit` | Kargoya verildi / transfer aşamasında | |
| `at_delivery_branch` | Varış şubesinde | |
| `out_for_delivery` | Dağıtımda | |
| `delivered` | Teslim edildi | Evet |
| `delivery_failed` | Teslim edilemedi | |
| `returning` | İade sürecinde (göndericiye dönüyor) | |
| `returned` | Göndericiye iade edildi | Evet |
| `support_needed` | Kargo firması müdahale gerektiren durum bildirdi | |
| `cancelled` | Gönderi iptal edildi | Evet |
| `error` | Hata | |

Gönderi ↔ sipariş ilişkisi: gönderi `in_transit` veya sonrası bir duruma geçtiğinde sipariş `shipped`, `delivered` olduğunda
sipariş `delivered` olur.

### 13.3 Kargo modları

| `cargoMode` | Açıklama | Kullanılacak uçlar |
|---|---|---|
| `platform` | Gönderiler platformun DHL eCommerce (MNG Kargo) anlaşmasıyla yapılır; kargo ücreti ve takibi platformdadır | `shipments/platform`, `label`, `cancel` |
| `partner` | Firma kendi kargo anlaşmasıyla gönderir, takip no ve hareketleri bildirir | `shipments`, `shipments/{id}/events`, `cancel` |
| `hybrid` | Sipariş bazında ikisinden biri seçilebilir | Hepsi |

Kargo modu hesap açılışında Hepiyiol ile birlikte belirlenir; değişiklik için bizimle iletişime geçiniz.

### 13.4 İade / iptal talep durumları ve nedenleri

**Talep durumları (`status`)**

| `statusId` | `status` | Açıklama | Açık mı (`isOpen`)? | Firma ne yapmalı |
|---|---|---|---|---|
| 1 | `received` | Talep alındı (inceleme bekliyor) | Evet | `approve` veya `reject` |
| 2 | `awaiting_product` | Onaylandı, müşteriden iade kargosu bekleniyor | Evet | Paket depoya ulaştığında `receive` (veya inceleme sonrası `reject`) |
| 3 | `product_received` | İade ürünü depoda teslim alındı | Evet | Kalite kontrol sorunluysa `reject`; uygunsa platform finans ekibi iadeyi tamamlar |
| 4 | `completed` | İade / iptal tamamlandı, ödeme iade edildi | Hayır | İade gider pusulası / muhasebe kaydını işleyin |
| 5 | `rejected` | Talep reddedildi | Hayır | – |
| 6 | `cancelled_by_customer` | Müşteri talebini geri çekti | Hayır | – |

**Talep nedenleri (`reason`)**

| `reasonId` | `reason` | Açıklama |
|---|---|---|
| 1 | `changed_mind` | Vazgeçtim |
| 2 | `wrong_item` | Yanlış ürün gönderildi |
| 3 | `damaged` | Hasarlı / kusurlu ürün |
| 4 | `missing_item` | Eksik ürün |
| 5 | `expiry` | Miadı yakın / geçmiş |
| 6 | `other` | Diğer (`customerNote` alanında açıklama yer alır) |

---

## 14. Canlıya geçiş kontrol listesi

**Hepiyiol tarafından iletilecekler**

- [ ] API temel adresi ve Swagger adresi
- [ ] API anahtarı (`mdp_live_…`) — güvenli kanaldan, bir kez
- [ ] Webhook imza anahtarı (`whsec_…`) — webhook adresi tanımlıysa
- [ ] Hesap ayarları: kargo modu, satış kanalı (B2C/eczane), otomatik iptal yetkisi

**Firmanızdan istenenler**

- [ ] Teknik ve operasyonel iletişim kişisi (ad, e-posta, telefon)
- [ ] Sabit çıkış IP adresleri (IP kısıtlaması için)
- [ ] Webhook adresi (`https://…`), herkese açık ve 15 sn içinde `2xx` dönen
- [ ] Kendi kargonuzla gönderim yapacaksanız: kullandığınız kargo firmaları

**Test senaryoları (önerilen)**

1. `GET /ping` → `200`
2. `POST /webhook/test` → sisteminizde imza doğrulamasıyla alındı
3. `GET /products?barcode=…` ile 2–3 ürününüzü doğrulayın
4. `PUT /listings` ile bir ilan açın, `GET /listings` ile kontrol edin, `quantity: 0` ile kapatın
5. Test siparişi (Hepiyiol ile koordineli) → `order.created` webhook'u → `GET /orders/{id}` (vergi / fatura adresi kontrolü)
6. `GET /orders/uninvoiced` ile siparişin faturasız listede geldiğini doğrulayın; `PUT /orders/{id}/invoice` veya `POST /orders/{id}/invoice/upload` ile test faturasını yükleyip listeden düştüğünü ve `GET /orders/{id}/invoice` yanıtını kontrol edin
7. Kargo modunuza göre gönderi oluşturun; platform kargosunda etiketi (`html` ve/veya `zpl`) yazdırın
8. Kendi kargonuzda `delivered` hareketi bildirin → `order.delivered` webhook'u
9. Teslim edilmiş test siparişi için iade talebi açtırın → `return.requested` webhook'u → `GET /returns` → `POST /returns/{returnId}/approve` → `POST /returns/{returnId}/receive` akışını doğrulayın
10. Webhook adresinizi geçici olarak kapatıp `GET /events` ile kaçan olayları okuyun

**Destek:** Hata bildirimlerinde istek zamanı, uç adresi, sipariş numarası ve (varsa) `internal_error` mesajındaki izleme
kodunu iletiniz. API anahtarınızı hiçbir zaman e-posta/mesaj içinde paylaşmayınız.
