> ## 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.

# Kimlik Doğrulama

> OAuth 2.0 Client Credentials akışıyla access token alın ve Payven API'lerine erişin.

Payven API'leri **OAuth 2.0 Client Credentials** akışı ile çalışır. Backend'inizden Identity servisinden bir access token alır, ürün API'lerine `Authorization: Bearer <token>` header'ı ile istek atarsınız.

İki kısa adım:

1. **Token al** — `client_id` + `client_secret` ile Identity'den `access_token` iste.
2. **API'yi çağır** — `Authorization: Bearer <access_token>` header'ı ile Sanal POS, Para Transferi, Fraud veya Identity endpoint'lerini kullan.

Token süresi dolduğunda yenisini alır veya `refresh_token` ile yenilersin. Kod örnekleri bu döngüyü sizin için yönetir.

## Akış (özet)

```mermaid theme={null}
sequenceDiagram
    participant App as Sizin Backendiniz
    participant Id as Payven Identity
    participant API as Ürün API (Sanal POS / Para Transferi / Fraud)

    App->>Id: POST /auth/{slug}/token<br/>{ client_id, client_secret }
    Id-->>App: { access_token, refresh_token, expires_in: 300, ... }
    Note over App: access_token cache'le<br/>5 dakika geçerli

    App->>API: POST /payments<br/>Authorization: Bearer <access_token>
    API-->>App: 200 OK

    Note over App: 5dk sonra access_token<br/>expiry'sine yakın
    App->>Id: POST /auth/{slug}/refresh<br/>{ refresh_token }
    Id-->>App: { access_token (yeni), refresh_token (rotasyonlu) }
```

## 1. Adım — Token al

```http theme={null}
POST https://identity.payven.com.tr/api/v1/auth/{slug}/token
Content-Type: application/json
```

`{slug}` = tenant slug'ınız (örn. `payven`). Onboarding sırasında size verilir.

```bash theme={null}
curl https://identity.payven.com.tr/api/v1/auth/payven/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id":     "pvk_live_xxxxxxxxxxxxxxxxxxxx",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'
```

**Yanıt** (`200 OK`):

```json theme={null}
{
  "access_token":        "eyJhbGciOiJSUzI1NiI...",
  "refresh_token":       "eyJhbGciOiJIUzI1NiI...",
  "expires_in":          300,
  "refresh_expires_in":  0,
  "token_type":          "Bearer",
  "scope":               "openid profile email"
}
```

| Alan                 | Anlam                                                                                                                                         |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `access_token`       | API çağrılarında kullanacağınız Bearer token (imzalı JWT formatında)                                                                          |
| `refresh_token`      | Access token expire olunca yenisini almak için                                                                                                |
| `expires_in`         | Access token kaç saniye geçerli (varsayılan **300s = 5 dakika**)                                                                              |
| `refresh_expires_in` | Refresh token süresi (`0` = idle timeout uygulanır, bkz. tablo)                                                                               |
| `token_type`         | Daima `Bearer`                                                                                                                                |
| `scope`              | Tahsis edilen scope'lar (kimlik bilgisi üst kümesi). Endpoint-bazlı yetki bu alan üzerinden değil, kullanıcı/anahtar rollerinizden çözümlenir |

## 2. Adım — API'yi çağır

```bash theme={null}
curl https://vpos.payven.com.tr/api/v1/payments \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiI..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001-payment" \
  -d '{
    "external_id": "ORDER-1001",
    "amount":      { "amount": 15000, "currency": "TRY" },
    "installment": 1,
    "card": {
      "holder_name":  "Test Kullanici",
      "number":       "4546711234567894",
      "expire_month": "12",
      "expire_year":  "2030",
      "cvv":          "000"
    }
  }'
```

Ek bir merchant header'ı göndermeniz gerekmez; merchant bağlamı token'dan çözümlenir. Birden fazla merchant adına işlem yapıyorsanız `X-Merchant-Id` ile override edebilirsiniz — bkz. [Sanal POS → Kimlik Doğrulama](/sanal-pos/authentication).

## 3. Adım — Token yenile

Access token expire olduğunda (`expires_in` süresi geçince) yenisini almak için `refresh_token` ile:

```bash theme={null}
curl https://identity.payven.com.tr/api/v1/auth/payven/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiI..."
  }'
```

Yanıt yine `access_token` + (rotasyonlu) `refresh_token` döner. Eski `refresh_token` artık geçersizdir.

## Token süreleri

| Token             | Varsayılan süre | Anlam                                                                                                                                                                      |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Access token**  | 5 dakika (300s) | API isteklerinde kullanılır. Süre dolunca refresh ile yenilenir.                                                                                                           |
| **Refresh token** | 30 gün (idle)   | 30 gün hareketsiz kalırsa expire olur. Düzenli kullanımda her refresh'te rotate edilir, pratikte expire olmaz. Şüpheli durumda `client_credentials` ile baştan token alın. |

<Note>
  **Pratik öneri:** Access token'ı **expiry'sine 60 saniye kala** yenileyin (clock-skew + network latency için margin). Aşağıdaki kod örnekleri bunu otomatik yapar.
</Note>

<Warning>
  **Token cache'leyin** — her API çağrısında yeniden token almayın. Identity üzerinde
  rate limit vardır (token endpoint'i IP başına dakikada 10). Token'ı bellekte tutup
  expire olana kadar yeniden kullanın.
</Warning>

## Kod örnekleri (auto-refresh)

<CodeGroup>
  ```javascript Node.js theme={null}
  class PayvenClient {
    constructor({ slug, clientId, clientSecret, identityBaseUrl }) {
      this.slug = slug;
      this.clientId = clientId;
      this.clientSecret = clientSecret;
      this.identityBaseUrl = identityBaseUrl;
      this.accessToken = null;
      this.refreshToken = null;
      this.expiresAt = 0;
    }

    async getToken() {
      if (this.accessToken && Date.now() < this.expiresAt - 60_000) {
        return this.accessToken;
      }

      if (this.refreshToken && (await this.tryRefresh())) {
        return this.accessToken;
      }

      const res = await fetch(`${this.identityBaseUrl}/api/v1/auth/${this.slug}/token`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          client_id: this.clientId,
          client_secret: this.clientSecret,
        }),
      });
      if (!res.ok) throw new Error(`Token alınamadı: ${res.status}`);
      const data = await res.json();
      this.accessToken = data.access_token;
      this.refreshToken = data.refresh_token;
      this.expiresAt = Date.now() + data.expires_in * 1000;
      return this.accessToken;
    }

    async tryRefresh() {
      const res = await fetch(`${this.identityBaseUrl}/api/v1/auth/${this.slug}/refresh`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresh_token: this.refreshToken }),
      });
      if (!res.ok) {
        this.refreshToken = null;
        return false;
      }
      const data = await res.json();
      this.accessToken = data.access_token;
      this.refreshToken = data.refresh_token;
      this.expiresAt = Date.now() + data.expires_in * 1000;
      return true;
    }

    async call(productBaseUrl, path, init = {}) {
      const token = await this.getToken();
      return fetch(productBaseUrl + path, {
        ...init,
        headers: {
          ...(init.headers ?? {}),
          Authorization: `Bearer ${token}`,
        },
      });
    }
  }
  ```

  ```python Python theme={null}
  import time
  import httpx


  class PayvenClient:
      def __init__(self, slug: str, client_id: str, client_secret: str, identity_base_url: str):
          self.slug = slug
          self.client_id = client_id
          self.client_secret = client_secret
          self.identity_base_url = identity_base_url
          self.access_token: str | None = None
          self.refresh_token: str | None = None
          self.expires_at: float = 0
          self._http = httpx.Client()

      def get_token(self) -> str:
          if self.access_token and time.time() < self.expires_at - 60:
              return self.access_token
          if self.refresh_token and self._refresh():
              return self.access_token  # type: ignore[return-value]

          r = self._http.post(
              f"{self.identity_base_url}/api/v1/auth/{self.slug}/token",
              json={"client_id": self.client_id, "client_secret": self.client_secret},
          )
          r.raise_for_status()
          data = r.json()
          self.access_token = data["access_token"]
          self.refresh_token = data["refresh_token"]
          self.expires_at = time.time() + data["expires_in"]
          return self.access_token

      def _refresh(self) -> bool:
          r = self._http.post(
              f"{self.identity_base_url}/api/v1/auth/{self.slug}/refresh",
              json={"refresh_token": self.refresh_token},
          )
          if r.status_code != 200:
              self.refresh_token = None
              return False
          data = r.json()
          self.access_token = data["access_token"]
          self.refresh_token = data["refresh_token"]
          self.expires_at = time.time() + data["expires_in"]
          return True
  ```

  ```csharp C# theme={null}
  public class PayvenClient
  {
      private readonly HttpClient _http = new();
      private readonly string _slug, _clientId, _clientSecret, _identityBase;
      private string? _accessToken, _refreshToken;
      private DateTimeOffset _expiresAt;
      private readonly SemaphoreSlim _lock = new(1, 1);

      public PayvenClient(string slug, string clientId, string clientSecret, string identityBaseUrl)
          => (_slug, _clientId, _clientSecret, _identityBase) = (slug, clientId, clientSecret, identityBaseUrl);

      public async Task<string> GetTokenAsync(CancellationToken ct = default)
      {
          await _lock.WaitAsync(ct);
          try
          {
              if (_accessToken is not null && DateTimeOffset.UtcNow < _expiresAt.AddSeconds(-60))
                  return _accessToken;

              if (_refreshToken is not null && await TryRefreshAsync(ct))
                  return _accessToken!;

              var resp = await _http.PostAsJsonAsync(
                  $"{_identityBase}/api/v1/auth/{_slug}/token",
                  new { client_id = _clientId, client_secret = _clientSecret }, ct);
              resp.EnsureSuccessStatusCode();
              var data = await resp.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken: ct);
              _accessToken = data!.access_token;
              _refreshToken = data.refresh_token;
              _expiresAt = DateTimeOffset.UtcNow.AddSeconds(data.expires_in);
              return _accessToken;
          }
          finally { _lock.Release(); }
      }

      private async Task<bool> TryRefreshAsync(CancellationToken ct)
      {
          var resp = await _http.PostAsJsonAsync(
              $"{_identityBase}/api/v1/auth/{_slug}/refresh",
              new { refresh_token = _refreshToken }, ct);
          if (!resp.IsSuccessStatusCode) { _refreshToken = null; return false; }
          var data = await resp.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken: ct);
          _accessToken = data!.access_token;
          _refreshToken = data.refresh_token;
          _expiresAt = DateTimeOffset.UtcNow.AddSeconds(data.expires_in);
          return true;
      }

      private record TokenResponse(string access_token, string refresh_token, int expires_in);
  }
  ```

  ```go Go theme={null}
  package payven

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "net/http"
      "sync"
      "time"
  )

  type Client struct {
      Slug, ClientID, ClientSecret, IdentityBase string
      accessToken, refreshToken                  string
      expiresAt                                  time.Time
      mu                                         sync.Mutex
  }

  type tokenResponse struct {
      AccessToken  string `json:"access_token"`
      RefreshToken string `json:"refresh_token"`
      ExpiresIn    int    `json:"expires_in"`
  }

  func (c *Client) GetToken() (string, error) {
      c.mu.Lock()
      defer c.mu.Unlock()

      if c.accessToken != "" && time.Now().Before(c.expiresAt.Add(-60*time.Second)) {
          return c.accessToken, nil
      }
      if c.refreshToken != "" {
          if err := c.refresh(); err == nil {
              return c.accessToken, nil
          }
      }
      body, _ := json.Marshal(map[string]string{
          "client_id":     c.ClientID,
          "client_secret": c.ClientSecret,
      })
      return c.fetchToken(fmt.Sprintf("%s/api/v1/auth/%s/token", c.IdentityBase, c.Slug), body)
  }
  ```

  ```php PHP theme={null}
  <?php
  class PayvenClient {
      private string $slug, $clientId, $clientSecret, $identityBase;
      private ?string $accessToken = null, $refreshToken = null;
      private int $expiresAt = 0;

      public function __construct(string $slug, string $clientId, string $clientSecret, string $identityBase) {
          $this->slug = $slug; $this->clientId = $clientId;
          $this->clientSecret = $clientSecret; $this->identityBase = $identityBase;
      }

      public function getToken(): string {
          if ($this->accessToken && time() < $this->expiresAt - 60) return $this->accessToken;
          if ($this->refreshToken && $this->refresh()) return $this->accessToken;

          $resp = $this->post("/api/v1/auth/{$this->slug}/token", [
              "client_id"     => $this->clientId,
              "client_secret" => $this->clientSecret,
          ]);
          $this->accessToken = $resp["access_token"];
          $this->refreshToken = $resp["refresh_token"];
          $this->expiresAt = time() + $resp["expires_in"];
          return $this->accessToken;
      }

      private function post(string $path, array $body): array {
          $ch = curl_init($this->identityBase . $path);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POST => true,
              CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
              CURLOPT_POSTFIELDS => json_encode($body),
          ]);
          $body = curl_exec($ch);
          $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
          curl_close($ch);
          if ($code !== 200) throw new Exception("Token request failed: $code");
          return json_decode($body, true);
      }
  }
  ```
</CodeGroup>

## Hata response'ları

| HTTP  | Code                    | Anlam                                              | Çözüm                                                                                                        |
| ----- | ----------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `400` | `bad_request`           | Request body geçersiz JSON veya zorunlu alan eksik | Body'nin geçerli JSON olduğunu ve `client_id` + `client_secret` alanlarının dolu gönderildiğini kontrol edin |
| `401` | `invalid_credentials`   | `client_id` / `client_secret` yanlış               | Konsoldan API anahtarınızı kontrol edin                                                                      |
| `401` | `invalid_token`         | Access token geçersiz                              | Refresh akışını çalıştırın                                                                                   |
| `401` | `token_expired`         | Access token süresi geçti                          | Refresh edin                                                                                                 |
| `401` | `invalid_refresh_token` | Refresh token expire / revoke                      | Yeni `client_credentials` ile baştan token alın                                                              |
| `403` | `realm_suspended`       | Tenant askıya alınmış                              | Müşteri ilişkileriniz ile iletişime geçin                                                                    |
| `404` | `realm_not_found`       | `{slug}` geçersiz                                  | Onboarding'de verilen slug'ı kontrol edin                                                                    |
| `429` | `rate_limit_exceeded`   | Token endpoint'ine çok istek                       | `Retry-After` header'ına uyun, token'ı cache'leyin                                                           |

Tam yanıt formatı için: [Hata Yönetimi](/documentation/concepts/errors).

## Güvenlik kuralları

<Check>**`client_secret` server-side saklanır** — frontend kodunuza, mobil uygulamanıza, public repo'ya **asla** koymayın.</Check>
<Check>**Token cache bellek-içi** — diske yazmayın; restart sonrası yeniden token alın.</Check>
<Check>**HTTPS zorunlu** — HTTP istekleri reddedilir.</Check>
<Check>**Token rotasyonu** — refresh otomatik yapar; manuel rotation gerekmez. Şüpheli durumda Identity'den client'ın secret'ını rotate edin.</Check>
<Check>**Loglarda maskele** — `access_token`, `refresh_token`, `client_secret` değerlerini log'lara yazmayın.</Check>

## SSS

<AccordionGroup>
  <Accordion title="Tek bir secret ile doğrudan API çağıramaz mıyım?">
    Hayır — Payven iki adımlı bir akış kullanır: önce `client_id` + `client_secret` ile kısa ömürlü bir access token alırsınız, sonra her API çağrısında bu token'ı kullanırsınız. Üstteki kod örneklerindeki istemci sınıfı bu adımı sizin için saklar; tek bir `call(...)` ile çağırırsınız.
  </Accordion>

  <Accordion title="Token süresini uzatabilir miyim?">
    Standart yapılandırmada 5dk access / 30 gün refresh idle. Daha uzun süre ihtiyacı için müşteri başarısı ekibine ulaşın — `offline_access` scope ile expire olmayan refresh token tahsis edilebilir.
  </Accordion>

  <Accordion title="Birden fazla servisi tek token'la çağırabilir miyim?">
    Evet — Identity'den alınan access token, planınızdaki tüm aktif ürünlerde geçerlidir. Sanal POS, Para Transferi, Fraud, Identity — hepsi aynı token ile çağrılır.
  </Accordion>

  <Accordion title="Test (sandbox) ortamı?">
    Sandbox endpoint'leri ayrı bir tenant slug ile yayında: `https://identity-sandbox.payven.com.tr` ve `https://vpos-sandbox.payven.com.tr`. Sandbox `client_id`'leri canlıdan ayrıdır; üretim parasıyla işlem yapılmaz, kart numarası test BIN'leri kullanır.
  </Accordion>
</AccordionGroup>
