> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payven.com.tr/llms.txt
> Use this file to discover all available pages before exploring further.

# Hata Yönetimi

> Payven hata yanıt formatı (RFC 9457), HTTP kategorileri, hata kodları ve retry stratejisi.

Payven hata yanıtları [RFC 9457 — Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457) standardına uyar. Tüm hata response'larının body'leri `application/problem+json` MIME tipindedir; HTTP durum kodu kategoriyi, gövdedeki `code` alanı ise hatanın programatik tipini belirtir.

## Hata yanıt formatı

```http theme={null}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
X-Correlation-Id: 9f1c8e76-2a3b-4f12-9c8d-12cb24a8a8a8
```

```json theme={null}
{
  "type":           "https://docs.payven.com.tr/errors/refund_exceeds_original",
  "title":          "İade tutarı orijinali aşıyor",
  "status":         422,
  "code":           "refund_exceeds_original",
  "detail":         "Toplam iade tutarı orijinal tutarı (15000 kuruş) aşamaz.",
  "instance":      "/api/v1/payments/8e3f5c12-.../refund",
  "correlation_id": "9f1c8e76-2a3b-4f12-9c8d-12cb24a8a8a8"
}
```

| Alan             | Zorunluluk | Açıklama                                                                                         |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------ |
| `type`           | Zorunlu    | Hata tipinin kanonik URI'si. Aynı zamanda dokümantasyon linki olarak çalışır.                    |
| `title`          | Zorunlu    | Kısa, insan-okur başlık (Türkçe).                                                                |
| `status`         | Zorunlu    | HTTP status kodu (header ile aynı değer, gövdede yinelenir).                                     |
| `code`           | Zorunlu    | Programatik hata kodu (`snake_case`). `type` URI'sinin son segmenti.                             |
| `detail`         | Opsiyonel  | Bu özel duruma ilişkin açıklayıcı mesaj.                                                         |
| `instance`       | Opsiyonel  | Hatanın oluştuğu kaynak yolu.                                                                    |
| `correlation_id` | Opsiyonel  | İstek zinciri kimliği. Response header'ı `X-Correlation-Id` ile aynı. Destek talebinde paylaşın. |

## HTTP kategorileri

| HTTP                       | Kategori                                            | Örnek `code`                                                     | Retry                                                |
| -------------------------- | --------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
| `400` Bad Request          | Geçersiz JSON, eksik alan, format hatası            | `bad_request`, `invalid_json`                                    | Düzeltmeden tekrarlamayın                            |
| `401` Unauthorized         | Token yok, geçersiz, expire                         | `unauthorized`, `invalid_token`, `token_expired`                 | Yeniden token alın                                   |
| `403` Forbidden            | Yetki yok, lisans yok, merchant pasif               | `forbidden`, `merchant_inactive`, `product_not_licensed`         | Hayır — yetki/lisans çözülmeden tekrar göndermeyin   |
| `404` Not Found            | Kaynak bulunamadı                                   | `resource_not_found`, `payment_not_found`                        | Hayır                                                |
| `409` Conflict             | Idempotency çakışması, durum geçişi geçersiz        | `conflict`, `idempotency_key_in_use`, `invalid_state_transition` | Hayır                                                |
| `422` Unprocessable Entity | İş kuralı veya validasyon ihlali                    | `validation_failed`, `refund_exceeds_original`, `bank_declined`  | Hayır — istek/iş kuralı düzeltilmeden retry yapmayın |
| `429` Too Many Requests    | Rate limit aşıldı (`Retry-After` header gönderilir) | `rate_limit_exceeded`                                            | Backoff                                              |
| `500/502/503`              | Sunucu / bağlantı hatası                            | `internal_server_error`, `bad_gateway`, `service_unavailable`    | Backoff                                              |
| Network timeout            | Ağ veya gateway timeout                             | —                                                                | Idempotency-Key ile                                  |

<Note>
  **Banka reddi de bir hata yanıtıdır.** Gateway'e ulaşmış ama banka tarafında reddedilen
  ödeme `422 Unprocessable Entity` + `application/problem+json` ile döner; gövdede
  `code: "bank_declined"` ve banka ham yanıt kodu (`provider_error_code`, örn. `"51"` —
  yetersiz bakiye) yer alır. Sonuç değerlendirmesini yalnız HTTP durum kodu üzerinden
  yapın.
</Note>

## Yaygın hata kodları

### Kimlik doğrulama / yetki

| `code`                  | HTTP | Anlam                                                    |
| ----------------------- | ---- | -------------------------------------------------------- |
| `unauthorized`          | 401  | `Authorization` header eksik                             |
| `invalid_token`         | 401  | Access token geçersiz veya kurcalanmış                   |
| `token_expired`         | 401  | Access token süresi doldu — refresh edin                 |
| `invalid_credentials`   | 401  | `client_id` / `client_secret` yanlış                     |
| `invalid_refresh_token` | 401  | Refresh token expire / revoke edilmiş                    |
| `realm_not_found`       | 404  | Auth endpoint'inde `{slug}` geçersiz                     |
| `realm_suspended`       | 403  | Tenant askıya alınmış                                    |
| `forbidden`             | 403  | Bu rol bu kaynağı göremez                                |
| `merchant_inactive`     | 403  | Belirtilen merchant pasif                                |
| `merchant_not_found`    | 403  | `X-Merchant-Id` / `X-External-Merchant-Id` çözümlenemedi |
| `product_not_licensed`  | 403  | Modül planınızda etkin değil                             |
| `ip_not_allowed`        | 403  | İstek IP'si izin listesinde değil                        |

### İstek formatı / validasyon

| `code`                | HTTP | Anlam                                                  |
| --------------------- | ---- | ------------------------------------------------------ |
| `bad_request`         | 400  | Genel istek hatası                                     |
| `invalid_json`        | 400  | Gövde geçersiz JSON                                    |
| `validation_failed`   | 422  | Validasyon kuralı ihlali — `errors[]` ile alan listesi |
| `invalid_amount`      | 422  | Tutar pozitif değil veya minimum altında               |
| `invalid_currency`    | 422  | Para birimi desteklenmiyor (`TRY` dışı için kapalı)    |
| `invalid_card`        | 422  | Kart numarası Luhn checksum'ı geçmiyor                 |
| `invalid_expiry`      | 422  | Son kullanma tarihi geçmiş veya format yanlış          |
| `invalid_installment` | 422  | Taksit sayısı bu BIN için desteklenmiyor               |
| `invalid_iban`        | 422  | IBAN checksum'ı geçmiyor                               |

Validasyon hatalarında alan-bazlı detay `errors[]` array'inde döner:

```json theme={null}
{
  "type":   "https://docs.payven.com.tr/errors/validation_failed",
  "title":  "Validasyon hatası",
  "status": 422,
  "code":   "validation_failed",
  "detail": "1 alan geçersiz",
  "errors": [
    {
      "field":   "amount.amount",
      "code":    "below_minimum",
      "message": "Tutar 100 kuruşun altında olamaz."
    }
  ]
}
```

### Ödeme / işlem kuralları

| `code`                                 | HTTP | Anlam                                                     |
| -------------------------------------- | ---- | --------------------------------------------------------- |
| `payment_not_found`                    | 404  | İşlem bulunamadı                                          |
| `payment_already_refunded`             | 422  | Tüm tutar zaten iade edilmiş                              |
| `payment_not_refundable`               | 422  | Mevcut durumda iade yapılamaz                             |
| `refund_exceeds_original`              | 422  | İade tutarı orijinali aşıyor                              |
| `void_not_allowed_after_settlement`    | 422  | Mutabakat sonrası iptal yapılamaz                         |
| `capture_amount_exceeds_authorization` | 422  | Çekim tutarı ön provizyonu aşıyor                         |
| `invalid_state_transition`             | 409  | Durum geçişi izin verilmiyor (örn. `Failed` → `Captured`) |
| `duplicate_external_id`                | 409  | Aynı `external_id` ile başka işlem zaten var              |

### Yönlendirme ve banka

| `code`                  | HTTP | Anlam                                                            |
| ----------------------- | ---- | ---------------------------------------------------------------- |
| `routing_no_match`      | 422  | Hiçbir konnektör koşullara uymadı                                |
| `connector_unavailable` | 503  | Hedef konnektör geçici olarak devre dışı (circuit breaker açık)  |
| `bank_declined`         | 422  | Banka işlemi reddetti — `provider_error_code` bankadan dönen kod |
| `bank_timeout`          | 504  | Banka yanıt vermedi (Smart Retry tetiklenmiş olabilir)           |
| `fraud_blocked`         | 422  | Fraud kuralı tetiklendi (`fraud.decision: "block"`)              |
| `fraud_review_required` | 422  | Manuel inceleme bekliyor                                         |

### Idempotency / rate limit

| `code`                   | HTTP | Anlam                                               |
| ------------------------ | ---- | --------------------------------------------------- |
| `idempotency_key_in_use` | 409  | Aynı `Idempotency-Key` farklı body için kullanılmış |
| `rate_limit_exceeded`    | 429  | İstek limiti aşıldı (`Retry-After` header'ına uyun) |

Banka yanıt kodlarının ve konnektör hata mesajlarının tam listesi: [Sanal POS → Hata Kodları](/sanal-pos/errors/overview).

## Retry stratejisi

Sadece **geçici hatalar** için retry uygun: `429`, `5xx`, network timeout. **Idempotent olmayan** işlemleri retry ederken mutlaka aynı `Idempotency-Key` kullanın — yoksa aynı işlem birden fazla yapılır.

Önerilen pattern: **exponential backoff + jitter**.

```
1. deneme: hemen
2. deneme: ~1 sn sonra
3. deneme: ~2 sn sonra
4. deneme: ~4 sn sonra
5. deneme: ~8 sn sonra (üst limit)
```

<CodeGroup>
  ```csharp C# theme={null}
  async Task<HttpResponseMessage> SendWithRetryAsync(
      Func<HttpRequestMessage> requestFactory,
      int maxAttempts = 5,
      CancellationToken ct = default)
  {
      var rng = new Random();
      for (int attempt = 0; ; attempt++)
      {
          var response = await _http.SendAsync(requestFactory(), ct);
          if (response.IsSuccessStatusCode) return response;

          var status = (int)response.StatusCode;
          var retryable = status == 429 || status >= 500;
          if (!retryable || attempt >= maxAttempts - 1) return response;

          var delaySec = Math.Pow(2, attempt);
          if (response.Headers.RetryAfter?.Delta is { } ra) delaySec = ra.TotalSeconds;
          var jitter = rng.NextDouble() * 0.5;
          await Task.Delay(TimeSpan.FromSeconds(delaySec + jitter), ct);
      }
  }
  ```

  ```javascript Node.js theme={null}
  async function retry(makeRequest, { maxAttempts = 5 } = {}) {
    for (let attempt = 0; ; attempt++) {
      const res = await makeRequest();
      if (res.ok) return res;

      const retryable = res.status === 429 || res.status >= 500;
      if (!retryable || attempt >= maxAttempts - 1) return res;

      const retryAfter = Number(res.headers.get("retry-after"));
      const baseSec = Number.isFinite(retryAfter) ? retryAfter : 2 ** attempt;
      const jitter = Math.random() * 0.5;
      await new Promise((r) => setTimeout(r, (baseSec + jitter) * 1000));
    }
  }
  ```

  ```python Python theme={null}
  import asyncio, random

  async def retry(make_request, max_attempts: int = 5):
      for attempt in range(max_attempts):
          res = await make_request()
          if 200 <= res.status_code < 300:
              return res

          retryable = res.status_code == 429 or res.status_code >= 500
          if not retryable or attempt == max_attempts - 1:
              return res

          retry_after = res.headers.get("retry-after")
          base = float(retry_after) if retry_after else 2 ** attempt
          await asyncio.sleep(base + random.random() * 0.5)
  ```
</CodeGroup>

<Tip>
  Tekrar denerken **aynı `Idempotency-Key`** ile gönderin. Aynı işlemin iki kez
  oluşmasını engeller. Bkz. [Idempotency](/documentation/concepts/idempotency).
</Tip>

## Müşteriye gösterme

`detail` alanı son kullanıcıya gösterilebilir nitelikte yazılır. Yine de hassas durumlarda (fraud, fraud-review, banka tarafı reddi) genel bir mesajla geçiştirmek daha güvenlidir:

```javascript theme={null}
function userMessage(problem) {
  switch (problem.code) {
    case "validation_failed":
      return problem.detail;
    case "bank_declined":
    case "fraud_blocked":
    case "fraud_review_required":
      return "İşleminiz onaylanmadı. Farklı bir kart deneyebilirsiniz.";
    case "rate_limit_exceeded":
      return "Şu an çok fazla istek alıyoruz. Lütfen kısa bir süre sonra tekrar deneyin.";
    default:
      return "İşleminiz gerçekleştirilemedi. Lütfen tekrar deneyin.";
  }
}
```

## Destek talebi açarken

Her hata yanıtında dönen **`correlation_id`** değerini paylaşın — log zincirini saniyeler içinde bulabiliriz. Response header'ındaki `X-Correlation-Id` ile gövdedeki `correlation_id` aynı değerdir.

```text theme={null}
Konu:   Refund 422 — refund_exceeds_original
Detay:  correlation_id: 9f1c8e76-2a3b-4f12-9c8d-12cb24a8a8a8
        instance:      /api/v1/payments/8e3f5c12-.../refund
        zaman:         2026-05-05T14:23:11+03:00
```
