Pposaxi
API Anahtarları← Panele Dön

Entegrasyon Kılavuzu

API Dokümantasyonu

Kendi sitenizden ödeme almak için iki yöntem: kartı kendi sunucunuzdan gönderdiğiniz Direkt API, ve kart verisinin hiç size dokunmadığı iframe / Hosted Checkout. Tüm uçlar JSON döner ve API anahtarınızla doğrulanır.

API Kimliği tanımlı değil

“Test Et” panellerini kullanmak için pk_/sk_ anahtarınızı girin.

Genel Bakış

Base URL
https://thirdparty.tahsilatmatik.com/api/v1

Makine-okunur sözleşme — IDE/Postman'e aktarın:

iframe / Hosted Checkout

Kart bilgisi yalnızca bizde işlenir (PCI SAQ-A). Hızlı, düşük yük. Önerilen.

Direkt API (S2S)

Kartı kendiniz toplar, sunucudan gönderirsiniz. Tam kontrol; ham kart → PCI SAQ-D sorumluluğu sizdedir.

Ödeme Akışları

Satış akışı (tek adım)

POST /v1/payments (pre_auth: false)PAIDSETTLED

Kullanım: anında teslim edebildiğinizde (dijital ürün, abonelik vb.).

Provizyon + Kapama akışı (iki adım)

POST /v1/payments (pre_auth: true)AUTHORIZEDCAPTURED

Kullanım: tahsilattan önce stok/teslimat doğrulamanız gerektiğinde.

Kimlik Doğrulama

Her isteğe API anahtarınızı iki başlıkla ekleyin. Anahtarları API Anahtarları ekranından oluşturursunuz; gizli anahtar (sk_) yalnızca bir kez gösterilir.

X-Api-Key: pk_xxx
X-Api-Secret: sk_xxx

Eksik/geçersiz kimlikte 401 döner.

iframe / Hosted Checkout (önerilen)

Önce bir oturum oluşturun, dönen checkout_url'i ya iframe ile gömün ya da tam sayfa yönlendirin. Kart verisi tarafınıza hiç gelmez; 3D, taksit, yemek kartı ve marka/tema otomatik gelir.

POST/v1/checkout/sessions
İstek
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/checkout/sessions \
  -H "X-Api-Key: pk_xxx" \
  -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 149.90,
    "currency": "TRY",
    "reference": "SIPARIS-1001",
    "callback_url": "https://siteniz.com/odeme/sonuc",
    "customer": { "email": "ahmet@ornek.com", "phone": "5320000000", "code": "CARI-1001" },
    "metadata": { "order_id": 1001 }
  }'
Yanıt
HTTP/1.1 201 Created
{
  "token": "Hh3k...long-unguessable-token",
  "reference": "SIPARIS-1001",
  "checkout_url": "https://thirdparty.tahsilatmatik.com/checkout/Hh3k...long-unguessable-token",
  // Oturumun O ANKİ durumu: created | pending | failed.
  "status": "created",
  // checkout_url'in son geçerlilik anı (ISO-8601); süre tanımlı değilse null.
  "expires_at": "2026-08-06T10:15:00+00:00",
  // Uygulanan koşul grubu; grup gönderilmediyse ve varsayılan yoksa null.
  "condition_group": { "id": 7, "name": "Kampanya", "slug": "kampanya" }
}

Yanıt alanları

AlanTipAçıklama
tokenstringOturumun tahmin edilemez genel kimliği; checkout_url'in son parçasıdır.
referencestringSipariş referansınız (göndermediyseniz bizim ürettiğimiz).
checkout_urlurlMüşteriyi göndereceğiniz (ya da iframe ile gömeceğiniz) ödeme sayfası.
statusstringOturumun o anki durumu: created (yeni) · pending (başlatılmış/3D bekliyor) · failed (önceki deneme reddedildi). Yeni oturumda daima created; mükerrer referansta dönen oturumda geçmişi gösterir.
expires_atstring|nullcheckout_url'in son geçerlilik anı (ISO-8601). Süre tanımlı değilse null. Süresi geçmiş bir oturum aynı referansla yeniden istendiğinde tazelenir.
condition_groupobject|nullUygulanan ödeme koşulu grubu (id, name, slug); grup yoksa null.

Ödeme koşulu grubu seçimi (opsiyonel)

Oturumu belirli bir ödeme koşulu grubuna bağlayabilirsiniz: sayısal condition_group_id veya grup condition_group (slug). O gruba tanımlı taksit / komisyon / POS kuralları uygulanır. Göndermezseniz varsayılan grup (tanımlıysa), o da yoksa genel koşullar geçerlidir. Yanıttaki condition_group hangi grubun uygulandığını (id, name, slug) veya null döner. Grupları Ödeme Linkleri / Koşul Grupları ekranından yönetirsiniz.

İstek (grup ile)
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/checkout/sessions \
  -H "X-Api-Key: pk_xxx" \
  -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 149.90,
    "currency": "TRY",
    "reference": "SIPARIS-1001",
    "callback_url": "https://siteniz.com/odeme/sonuc",
    "condition_group_id": 7
  }'
# ── ya da slug ile: ────────────────────────────────────────────
#   "condition_group": "kampanya"
#
# İkisi de opsiyoneldir. Göndermezseniz varsayılan grup (tanımlıysa),
# o da yoksa genel koşullar (tüm taksit/komisyon/POS kuralları) uygulanır.

Aynı referansla ikinci istek (idempotenlik)

Kendi sipariş numaranızı reference ile gönderirseniz, o referans için tek bir oturum tutulur: ağ hatası, sayfa yenileme ya da yeniden deneme sonucu aynı referansla ikinci kez istek atmanız mükerrer oturum ya da mükerrer tahsilat üretmez. Kural /v1/payments ve /v1/meal-payments ile aynıdır; ayrıştırdığınız yer HTTP durum kodudur:

DurumNe zamanSonuç
201 CreatedReferans yeni (ya da reference hiç göndermediniz — sizin için üretilir).Yeni oturum açıldı; status = created.
200 OKBu referans için tamamlanmamış bir oturum zaten var (created · pending · failed).Mevcut oturum idempotent döner: yeni satır oluşmaz, aynı token ve checkout_url gelir.
422Referans için zaten tamamlanmış bir ödeme var (paid · authorized · refunded).Oturum açılmaz; hata alanı reference. Yeni bir referans kullanın.

Gövde 201 ve 200'de birebir aynıdır — iki durumu gövdeden ayırt etmeye çalışmayın, durum kodunu okuyun. Dönen oturumun geçmişini status alanından görürsünüz; süresi geçmiş oturumun expires_at'i tazelenir ve token'ı yoksa üretilir, böylece dönen checkout_url daima ödenebilir durumdadır.

Yanıt — mevcut oturum idempotent döndü
HTTP/1.1 200 OK
{
  "token": "Hh3k...long-unguessable-token",
  "reference": "SIPARIS-1001",
  "checkout_url": "https://thirdparty.tahsilatmatik.com/checkout/Hh3k...long-unguessable-token",
  // Oturumun O ANKİ durumu: created | pending | failed.
  "status": "failed",
  // checkout_url'in son geçerlilik anı (ISO-8601); süre tanımlı değilse null.
  "expires_at": "2026-08-06T10:15:00+00:00",
  // Uygulanan koşul grubu; grup gönderilmediyse ve varsayılan yoksa null.
  "condition_group": { "id": 7, "name": "Kampanya", "slug": "kampanya" }
}
# Aynı reference ile ikinci istek → YENİ oturum AÇILMAZ; mevcut oturum aynen
# döner (aynı token, aynı checkout_url). Gövde 201 ile BİREBİR aynıdır: iki
# durumu gövdeden değil DURUM KODUNDAN ayırın (201 = oluşturuldu, 200 = mevcut).
# status oturumun geçmişini dürüstçe gösterir — burada önceki deneme banka
# tarafından reddedilmiş (failed), oturum yine de tekrar ödenebilir.
# Süresi geçmiş oturum tazelenir, token'sız kayda token üretilir → dönen
# checkout_url DAİMA ödenebilir. Tutar ve koşul grubu DEĞİŞMEZ.
422 — referans için zaten tamamlanmış ödeme var
HTTP/1.1 422 Unprocessable Content
{
  "message": "Bu sipariş referansı için zaten tamamlanmış bir ödeme var.",
  "errors": {
    "reference": ["Bu sipariş referansı için zaten tamamlanmış bir ödeme var."]
  }
}
# Referans için zaten TAMAMLANMIŞ bir ödeme var (paid / authorized / refunded):
# aynı sipariş ikinci kez tahsil edilmesin diye yeni oturum açılmaz.
# Çözüm: yeni bir reference gönderin. Alan adı ve mesaj /v1/payments ve
# /v1/meal-payments ile AYNIDIR.

Bu uç 402 DÖNMEZ (bilinçli sapma)

/v1/payments'te 402“banka/kart reddetti” demektir. Oturum oluştururken hiçbir tahsilat denenmez, dolayısıyla bu uç 402 üretmez: önceki denemesi başarısız (failed) olan bir referans da 200 ile döner ve aynı checkout_url üzerinden tekrar denenebilir. Geçmişi gövdedeki status alanından okuyun.

Mevcut bir oturum döndüğünde tutar ve koşul grubu değişmez — ilk istekteki değerler geçerli kalır. Farklı bir tutarla ödeme almak istiyorsanız farklı bir referans gönderin.

Sayfayı gömün

HTML
<!-- Ödeme sayfasını sitenize iframe olarak gömün -->
<iframe
  src="CHECKOUT_URL_BURAYA"
  width="100%" height="720"
  style="border:0; max-width:480px"
  allow="payment">
</iframe>

<!-- ya da tam sayfa yönlendirme: -->
<a href="CHECKOUT_URL_BURAYA">Ödemeye Geç</a>

Müşteri ödemeyi tamamlayınca bizim sonuç ekranımızda kalır; sitenize otomatik tarayıcı yönlendirmesi yapılmaz (#332). Ödeme sonucunu callback_url'inize gönderdiğimiz imzalı server-to-server POST ile alır, kesin durumu isterseniz webhook ya da durum sorgusu ile pekiştirirsiniz.

Oturum oluştururken customer (telefon, e-posta, cari kodu + Ayarlar > Müşteri Bilgileri'nde tanımladığınız alanlar) gönderebilirsiniz; göndermezseniz bu bilgiler ödeme sayfasında ödemeyi yapan kişiden istenir ve cari kaydına bağlanır.

Direkt API — Kart ile Ödeme (S2S)

Kartı kendi formunuzda toplar, sunucunuzdan gönderirsiniz. POS yönlendirme, taksit, fraud ve gerekirse failover otomatik uygulanır.

POST/v1/payments
AlanTipAçıklama
amountnumberTahsil edilecek tutar (zorunlu).
currencystringISO-4217, varsayılan TRY.
installment_countintTaksit sayısı (1 = tek çekim).
pre_authbooltrue → provizyon (blokede tut, sonra kapat).
cardobjectnumber, holder, exp_month, exp_year, cvv (zorunlu).
customerobjectname, email, phone, code (cari kodu), ip + ayarladığınız özel alanlar. code/email ile cari kaydına bağlanır.
callback_urlurl3D sonrası müşterinin döneceği adres.
referencestringSipariş numaranız (verilmezse üretilir).
virtual_pos_idintÖdemeyi belirli bir sanal POS'a sabitler (opsiyonel); yönlendirme kuralı ve failover devre dışı kalır. Bilinmeyen, pasif ya da başka bir üye iş yerine ait id → 422, hata alanı "virtual_pos_id".
use_3dboolİşlem bazında 3D seçimi. true → 3D Secure, false → 3D'siz (non-secure). Göndermezseniz POS kaydınızdaki “3D Secure destekli” (supports_3d) ayarı karar verir. Zorlamalar bu alanı ezer — bkz. öncelik tablosu.
condition_group_idintÖdeme koşulu grubu id'si (opsiyonel). O gruba tanımlı taksit / komisyon / POS kuralları uygulanır.
condition_groupstringAynı grubun slug'ı (max 120). id yerine kullanılabilir; ikisini birden göndermeniz gerekmez. Hiçbiri gönderilmezse varsayılan grup, o da yoksa genel koşullar geçerlidir.
metadataobjectİstediğiniz ek alanlar.

İstek örneği

curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/payments \
  -H "X-Api-Key: pk_xxx" \
  -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: SIPARIS-1001" \
  -d '{
    "reference": "SIPARIS-1001",
    "amount": 149.90,
    "currency": "TRY",
    "installment_count": 1,
    "pre_auth": false,
    "card": {
      "number": "4355084355084358",
      "holder": "AHMET YILMAZ",
      "exp_month": "12",
      "exp_year": "30",
      "cvv": "000"
    },
    "customer": {
      "name": "Ahmet Yılmaz",
      "email": "ahmet@ornek.com",
      "phone": "5320000000",
      "code": "CARI-1001",
      "ip": "1.2.3.4"
    },
    "callback_url": "https://siteniz.com/odeme/sonuc",
    "metadata": { "order_id": 1001 }
  }'
Yanıt
HTTP/1.1 201 Created
{
  "data": {
    "id": 42,
    "reference": "SIPARIS-1001",
    "method": "card",
    "status": "paid",            // paid | pending(3D) | failed | authorized(provizyon)
    "amount": "149.90",
    "currency": "TRY",
    "installment_count": 1,
    "capture_mode": "auto",
    "net_amount": 149.90,
    "transactions": [
      { "type": "sale", "provider": "garanti", "provider_ref": "...", "status": "success" }
    ]
  }
}

3D'li mi, 3D'siz mi? (use_3d)

Her isteğe use_3dekleyerek o işlemin 3D Secure'dan geçip geçmeyeceğini işlem bazında seçebilirsiniz. Alanı hiç göndermezseniz davranış değişmez: POS kaydınızdaki supports_3d bayrağı karar verir. Ancak zorlamalar isteğinizi ezer — aşağıdaki sıra geçerlidir (üstteki kazanır):

#KaynakSonuç
1Fraud / yönlendirme kuralı kararı block · rejectİşlem yapılmaz; ödeme reddedilir.
2Zorlama: panel ayarı force_3d · fraud force_3d etkisi · taksit/yönlendirme kuralı force_3d: true3D zorunlu. İstekteki use_3d: false bunu ezemez. Gözetimsiz (saklı kart/abonelik) tahsilatlarda bu adım atlanır — aşağıya bakın.
3Kural force_3d: false (açık override)3D'siz işlenir.
4İstekteki use_3dGönderdiğiniz değer uygulanır.
5POS supports_3d (varsayılan)POS kaydındaki ayar uygulanır.

Gözetimsiz (MIT) tahsilat muafiyeti. Saklı kartla otomatik tahsilat ve abonelik çekimlerinde 3D ekranını onaylayacak bir kart sahibi yoktur; bu yüzden yukarıdaki 2. adımdaki zorlamalar uygulanmaz ve karar POS'un supports_3d ayarına düşer (three_d_reason = mit_exempt). İşyeri panelden Ayarlar → Güvenlikaltındaki “3D zorlaması gözetimsiz tahsilatlara da uygulansın” seçeneğini açarsa eski katı davranış geri gelir. İsteğinizdeki use_3d muafiyetten etkilenmez; müşterinin ödeme sırasında saklı kartını kendi seçtiği işlemler ise gözetimsiz sayılmaz ve muafiyet dışıdır.

Hangi katmanın karar verdiğini ve kararın fiilen uygulanıp uygulanmadığını yanıt üzerinden denetleyebilirsiniz. Ödeme nesnesi bu ikisini ayrı taşır — three_d_requested kararı, three_d_secure ise fiilen koşan akışı anlatır:

AlanTipAçıklama
three_d_requestedbool|nullKARAR: bu ödemenin 3D Secure ile çalışması kararlaştırıldı mı (fiilen kullanılan POS'a göre çözülmüş). Kararı veren katman three_d_reason'dadır. null = karar kaydedilmemiş (eski kayıt / kart dışı ödeme).
three_d_securebool|nullFİİLİ AKIŞ: ödeme gerçekten 3D Secure / sağlayıcı barındırmalı doğrulama adımıyla başlatıldı mı. Karardan sapabilir: daima-3D çalışan sağlayıcılar use_3d=false'ı uygulayamaz (karar false / fiili true). null = kayıt yok (eski kayıt / kart dışı ödeme).
three_d_reasonstring|nullthree_d_requested KARARINI veren katman: setting (işyeri ayarı force_3d) · fraud (risk kuralı) · rule (ödeme akış kuralı) · request (istekteki use_3d) · mit_exempt (gözetimsiz saklı kart/abonelik tahsilatı olduğu için 3D zorlaması uygulanmadı; karar POS'un kendi 3D ayarına düştü). null = kimse karar vermedi, POS'un supports_3d'si geçerli.
condition_group_idint|nullUygulanan ödeme koşulu grubunun id'si.
condition_groupobject|nullUygulanan grubun id, name, slug bilgisi; grup yoksa null.

Bu alanlardan liability shift çıkarımı yapmayın

three_d_secure: true yalnızca işlemin 3D adımıyla başlatıldığını söyler. Bu alan 3D doğrulamasının BAŞARIYLA tamamlandığını ya da sorumluluk aktarımının (liability shift) gerçekleştiğini GÖSTERMEZ.

Ödemenin gerçekten tahsil edilip edilmediği için status alanına, sorumluluğun karta/bankaya geçip geçmediği için ise bankanızın mutabakat kayıtlarına bakın. İtiraz sorumluluğunun aktarımı kart şemasının doğrulama sonucuna (ECI/CAVV), kart hamilinin bankasının kararına ve üye iş yeri anlaşmanıza bağlıdır — 3D adımı başlatılmış olsa bile doğrulama başarısız/atlanmış (attempted) olabilir ve sorumluluk sizde kalabilir.

Bu yüzden three_d_requested / three_d_secure alanlarını risk, muhasebe veya itiraz süreçlerinizde tek başına delil olarak kullanmayın.

3D'li akış — sonuç asenkron

Ödeme pending kalır ve metadata.redirect_html ile müşteri bankaya yönlendirilir.

İstek (use_3d: true)
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/payments \
  -H "X-Api-Key: pk_xxx" \
  -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: SIPARIS-2001" \
  -d '{
    "reference": "SIPARIS-2001",
    "amount": 149.90,
    "currency": "TRY",
    "installment_count": 1,
    "use_3d": true,                      // ← bu işlem 3D Secure ile
    "card": { "number": "4355084355084358", "holder": "AHMET YILMAZ",
              "exp_month": "12", "exp_year": "30", "cvv": "000" },
    "callback_url": "https://siteniz.com/odeme/sonuc"
  }'
Yanıt
HTTP/1.1 201 Created
{
  "data": {
    "id": 51,
    "reference": "SIPARIS-2001",
    "status": "pending",                 // ← henüz tahsil EDİLMEDİ
    "amount": "149.90",
    "three_d_requested": true,           // KARAR: 3D akışı istendi
    "three_d_secure": true,              // FİİLEN: 3D akışıyla yürütülüyor
    "three_d_reason": "request",         // kararı veren katman
    "metadata": {
      "redirect_html": "<form method=\"post\" action=\"https://banka…\">…</form>"
    }
  }
}
# redirect_html'i müşterinin TARAYICISINDA render edin → banka 3D sayfası açılır.
# Kesin sonuç 3D dönüşünde callback_url'inize imzalı S2S POST ile gelir
# (payment.paid / payment.failed).
JavaScript
// status === "pending" → 3D Secure gerekiyor.
const meta = res.data.metadata;
if (res.data.status === "pending") {
  if (meta.redirect_html) {
    // Bankaya otomatik POST eden formu müşterinin tarayıcısında render edin:
    document.open(); document.write(meta.redirect_html); document.close();
  } else if (meta.redirect_url) {
    window.location.href = meta.redirect_url;
  }
}
// 3D bitince müşteri BİZİM sonuç ekranımızda kalır (artık sitenize otomatik
// tarayıcı yönlendirmesi YAPILMAZ, #332). Sonucu callback_url'inize gönderdiğimiz
// imzalı server-to-server POST (payment.paid / payment.failed) ile alın.

3D'siz akış — sonuç senkron

Yönlendirme adımı yoktur: sonuç aynı yanıtta döner — başarılıysa 201 + status: "paid", reddedilirse 402 + status: "failed". Yanıtta redirect_html bulunmaz.

İstek (use_3d: false)
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/payments \
  -H "X-Api-Key: pk_xxx" \
  -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: SIPARIS-2002" \
  -d '{
    "reference": "SIPARIS-2002",
    "amount": 149.90,
    "currency": "TRY",
    "installment_count": 1,
    "use_3d": false,                     // ← 3D'siz (non-secure) işlem
    "condition_group": "kampanya",       // ← opsiyonel: koşul grubu (slug)
    "card": { "number": "4355084355084358", "holder": "AHMET YILMAZ",
              "exp_month": "12", "exp_year": "30", "cvv": "000" }
  }'
Yanıt
HTTP/1.1 201 Created            ← sonuç SENKRON döner
{
  "data": {
    "id": 52,
    "reference": "SIPARIS-2002",
    "status": "paid",                    // tahsil edildi
    "amount": "149.90",
    "currency": "TRY",
    "installment_count": 1,
    "three_d_requested": false,          // KARAR: 3D akışı istenmedi
    "three_d_secure": false,             // FİİLEN: 3D akışı koşmadı
    "three_d_reason": "request",         // kararı veren katman (bkz. aşağıdaki tablo)
    "condition_group_id": 7,
    "condition_group": { "id": 7, "name": "Kampanya", "slug": "kampanya" },
    "paid_at": "2026-08-03T10:00:00+00:00",
    "transactions": [
      { "type": "sale", "provider": "garanti", "status": "success" }
    ]
    // metadata.redirect_html YOKTUR — yönlendirme adımı hiç oluşmaz.
  }
}

# ── Banka/kart reddederse (yine ödeme nesnesi döner): ──────────────
HTTP/1.1 402 Payment Required
{ "data": { "id": 53, "reference": "SIPARIS-2003", "status": "failed", … } }
# Ret nedeni (error_code / error_message) payment.failed bildiriminde gelir.

3D'siz işlem açmadan önce

1. Bankadan yetki gerekir.3D'siz (non-secure) işlem, üye iş yeri anlaşmanızda ayrıca açılması gereken bir yetkidir. Banka/sağlayıcı tarafında kapalıyken use_3d: false göndermek işlemi reddettirir.

2. Chargeback sorumluluğu sizde kalır.3D Secure'da kart hamili doğrulaması bankadadır ve itiraz riski (liability shift) karta/bankaya geçer; 3D'siz işlemde bu geçiş olmaz — sahtecilik/itiraz zararı üye iş yerine yazılır.

3. PCI-DSS kapsamı doğar. Ham kart verisi (PAN, CVV) sizin sunucunuzdan geçtiği için kapsamınız SAQ D'ye çıkar: kart verisini asla saklamayın, loglamayın, e-posta/webhook payload'ına koymayın.

Bu kapsamı istemiyorsanız kart verisinin hiç size dokunmadığı iframe / Hosted Checkout ya da ödeme linki alternatifini kullanın (PCI SAQ-A).

Ödeme koşulu grubu (opsiyonel)

İsteği belirli bir ödeme koşulu grubuna bağlamak için sayısal condition_group_id veya grup slug'ı condition_group gönderin (ikisi birden gerekmez). Göndermezseniz varsayılan grup, o da yoksa genel koşullar uygulanır. Geçersiz ya da başka bir üye iş yerine ait grup 422 döner; hata alanı adı her iki parametre için de condition_group'tur. Grupları Koşul Grupları ekranından yönetirsiniz.

422 — geçersiz koşul grubu
HTTP/1.1 422 Unprocessable Content
{
  "message": "Belirtilen ödeme koşulu grubu bulunamadı veya aktif değil.",
  "errors": {
    "condition_group": ["Belirtilen ödeme koşulu grubu bulunamadı veya aktif değil."]
  }
}
# Geçersiz/pasif grup ya da BAŞKA bir üye iş yerine ait grup id/slug → 422.
# Hata alanı adı her iki parametre için de "condition_group"tur.

Belirli bir sanal POS'a sabitleme (opsiyonel)

Ödemeyi kendi seçtiğiniz bir sanal POS üzerinden geçirmek için virtual_pos_id gönderin; bu durumda yönlendirme kuralı ve failover uygulanmaz — tek deneme yapılır. POS kimliklerini Sanal POS'lar ekranından görürsünüz. Bilinmeyen, pasif ya da başka bir üye iş yerine ait bir id gönderirseniz ödeme 422 ile reddedilir; hata alanı adı gönderdiğiniz parametrenin adıdır: virtual_pos_id.

422 — geçersiz sanal POS
HTTP/1.1 422 Unprocessable Content
{
  "message": "Belirtilen sanal POS bulunamadı veya aktif değil.",
  "errors": {
    "virtual_pos_id": ["Belirtilen sanal POS bulunamadı veya aktif değil."]
  }
}
# Bilinmeyen, PASİF ya da BAŞKA bir üye iş yerine ait id → hepsi AYNI mesaj
# (kaynak numaralandırmaya kapalı: başka işletmenin POS id'lerini tarayamazsınız).
# Hata alanı adı gönderdiğiniz parametrenin adıdır: "virtual_pos_id".
# Gövde hiçbir iç model/sınıf adı, satır kimliği veya SQL içermez.

Taksit Sorgulama

Bir tutar için geçerli taksit seçeneklerini ve müşteriye yansıyan toplamı döner.

GET/v1/installments?amount=&card_brand=&virtual_pos_id=&condition_group=
AlanTipAçıklama
amountnumberSorgulanacak tutar (zorunlu).
card_brandstringKart markası (visa, mastercard, troy…) — verilirse markaya özel planlar süzülür.
virtual_pos_idintBelirli bir sanal POS'un planları (opsiyonel).
condition_group_idintÖdeme koşulu grubu id'si (opsiyonel) — yalnız o grubun taksit/komisyon koşulları uygulanır.
condition_groupstringAynı grubun slug'ı (max 120). id yerine kullanılabilir; ikisi birden gerekmez. Hiçbiri verilmezse varsayılan grup, o da yoksa genel koşullar.
cURL
curl "https://thirdparty.tahsilatmatik.com/api/v1/installments?amount=1000&card_brand=visa" \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx"

// Yanıt:
{ "data": [
  { "installment_count": 1, "card_brand": null,   "commission_rate": "0.00", "total": 1000, "monthly": 1000 },
  { "installment_count": 3, "card_brand": "visa", "commission_rate": "4.50", "total": 1045, "monthly": 348.33 }
] }
cURL (koşul grubu ile)
# Koşul grubuna göre taksit sorgulama (id ya da slug — biri yeterli):
curl "https://thirdparty.tahsilatmatik.com/api/v1/installments?amount=1000&card_brand=visa&condition_group=kampanya" \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx"

curl "https://thirdparty.tahsilatmatik.com/api/v1/installments?amount=1000&condition_group_id=7" \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx"

# Göndermezseniz: varsayılan grup (tanımlıysa) → o da yoksa genel koşullar.
# Geçersiz/başka üye iş yerine ait grup → 422 ("condition_group").

Ödeme Durumu Sorgulama

Referansınızla ödemenin güncel durumunu çekin (webhook'a alternatif/pekiştirici).

GET/v1/payments/{reference}
cURL
curl "https://thirdparty.tahsilatmatik.com/api/v1/payments/SIPARIS-1001" \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx"
// → PaymentResource (status: paid | pending | failed | authorized | refunded)

İade (Refund)

Başarılı bir ödemeyi tam veya kısmi iade edin. Birden çok kısmi iade toplanır.

POST/v1/payments/{reference}/refund
cURL
# Tam iade: body boş. Kısmi iade: "amount" gönderin.
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/payments/SIPARIS-1001/refund \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 50.00 }'

Provizyon (Ön Provizyon / Pre-Auth)

Tutarı kartta bloke edip (provizyon) daha sonra tahsil etmek için direkt ödemede pre_auth: true gönderin. Desteklenen sağlayıcılar: tüm banka POS'ları ve Craftgate.

cURL
# Provizyon (ön provizyon): tutarı blokede tut, sonra kapat.
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/payments \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 500, "pre_auth": true, "card": { ... } }'
# → data.status = "authorized" (henüz tahsil edilmedi).
# Provizyon kapama / iptal: panel > Ödemeler ekranı
# ("Provizyonu Kapat" / "Provizyonu İptal Et").

Yemek Kartı ile Ödeme

Yemek kartı entegrasyonunuzu tanımladıktan sonra bakiye sorgulayıp tahsilat alın.

POST/v1/meal-payments/balance
POST/v1/meal-payments
cURL
# Bakiye sorgu
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/meal-payments/balance \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "meal_card_id": 1, "card": { "number": "6060...." } }'

# Ödeme (Multinet / Sodexo-Pluxee / Edenred-Ticket / Setcard / Metropol)
curl -X POST https://thirdparty.tahsilatmatik.com/api/v1/meal-payments \
  -H "X-Api-Key: pk_xxx" -H "X-Api-Secret: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "meal_card_id": 1, "amount": 80, "reference": "SIP-1002",
        "card": { "number": "6060....", "pin": "1234" } }'

meal_card_id, panelde tanımladığınız yemek kartı entegrasyonunun kimliğidir. Bilinmeyen, pasif ya da başka bir üye iş yerine ait bir id gönderirseniz 422 döner; hata alanı adı meal_card_id, mesaj: Belirtilen yemek kartı tanımı bulunamadı veya aktif değil.

Bakiye sorgu

Yemek kartı ile ödeme

Tüm Uçlar

MethodEndpointAçıklama
POST/v1/paymentsKart ile ödeme (Direkt / S2S)
POST/v1/checkout/sessionsHosted / iframe oturumu oluştur
GET/v1/installmentsTaksit seçeneklerini sorgula
GET/v1/payments/{reference}Ödeme durumunu sorgula
POST/v1/payments/{reference}/refundİade (tam / kısmi)
POST/v1/meal-payments/balanceYemek kartı bakiye sorgu
POST/v1/meal-paymentsYemek kartı ile ödeme
POST/api/callbacks/{provider}3D Secure dönüşü (bankalar çağırır)

Provizyon kapama / iptal ve durum-sorgu (sync) işlemleri panel üzerinden yapılır.

Webhook'lar

Ödeme durumu değişince Webhook'lar ekranında tanımladığınız uca imzalı bir bildirim göndeririz. Kayıtlı bir endpoint tüm olayları alır:

  • payment.authorized — provizyon alındı
  • payment.paid — tahsil edildi
  • payment.failed — başarısız
  • payment.refunded / payment.partially_refunded — iade

Her teslimat üç başlık taşır: imza (X-Tahsilatmatik-Signature), olay (X-Tahsilatmatik-Event) ve tekilleştirme kimliği (X-Tahsilatmatik-Delivery). Zarf sabittir: { id, event, created_at, data }.

Gelen istek
POST https://siteniz.com/webhook
X-Tahsilatmatik-Signature: t=1717500000,v1=9f86d08...   (HMAC-SHA256)
X-Tahsilatmatik-Event:     payment.paid
X-Tahsilatmatik-Delivery:  12345                        (tekilleştirme id'si)

{
  "id": 12345,                                  // id + created_at retry'larda SABİT
  "event": "payment.paid",                      //   → aynı teslimatı idempotent ayıklayın
  "created_at": "2026-06-05T10:00:00+00:00",
  "data": {
    "reference": "SIPARIS-1001",
    "status": "paid",
    "amount": 149.90,
    "refunded_amount": 0,
    "currency": "TRY",
    "installment_count": 1,
    "method": "card",
    "paid_at": "2026-06-05T10:00:00+00:00",
    "transaction_id": 84,                        // opsiyonel (son işlem kimliği)
    "metadata": { "order_id": 1001 }
  }
}

payment.failed olayında data ek olarak banka hata bilgisini içerir (PCI: kart/PAN/CVV taşınmaz):

payment.failed (ek alanlar)
"event": "payment.failed",
"data": {
  "reference": "SIPARIS-1001",
  "status": "failed",
  "amount": 149.90,
  ...
  "error_code": "51",
  "error_message": "Yetersiz bakiye"
}

Gövdeyi ham (raw) haliyle imzalayın; imza t=<unix>,v1=HMAC-SHA256("t.rawBody", secret) biçimindedir ve secret webhook endpoint'i oluştururken bir kez gösterilir. Zaman toleransı 300 sn'dir; başarısız teslimatlar üstel gecikmeyle yeniden denenir; id/created_at sabit kaldığından aynı teslimatı idempotent ayıklayın.

PHP
<?php
// İmza doğrulama (PHP) — secret webhook endpoint'i oluştururken bir kez gösterilir.
$payload = file_get_contents('php://input');
$header  = $_SERVER['HTTP_X_TAHSILATMATIK_SIGNATURE'] ?? '';
$secret  = 'whsec_xxx';   // endpoint'in kendi secret'i

parse_str(str_replace(',', '&', $header), $p);   // t=..., v1=...
$expected = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $payload, $secret);

if (! hash_equals($expected, $p['v1'] ?? '') || abs(time() - (int)($p['t'] ?? 0)) > 300) {
    http_response_code(400); exit('invalid signature');
}
$event = json_decode($payload, true);
// $event['id'] ile tekilleştirin, sonra 2xx dönün.
http_response_code(200);

Parçalı (split) ödeme

Bir sipariş birden çok karta/parçaya bölünse bile bildirim yalnızca parent tamamen tahsil olunca tek bir payment.paid olarak gider — parça başına ayrı webhook gönderilmez. Parça planı yalnızca checkout içindeki dahili bir görünümdür.

callback_url — Server-to-Server POST

Checkout oturumu / direkt ödeme oluştururken callback_url verdiyseniz, ödeme sonucunu o adrese imzalı, sunucudan sunucuya (S2S) POST ile göndeririz — kayıtlı bir webhook endpoint'i gerektirmeden. Bu, eskiden yapılan tarayıcı GET yönlendirmesinin (?reference=...&status=...) yerine geçer (#332): müşteri artık bizim sonuç ekranımızda kalır, tarayıcı sitenize yönlendirilmez.

callback_url yalnızca tamamlanma olaylarını alır: payment.paid, payment.authorized, payment.failed (iade olayları yalnız kayıtlı webhook endpoint'lerine gider). Payload şeması, zarf ve ilk üç başlık webhook ile aynıdır; ek olarak X-Tahsilatmatik-Key başlığı imza anahtarının hangi API anahtarınızdan (pk_) türetildiğini bildirir.

Gelen istek
POST https://siteniz.com/odeme/sonuc          (checkout callback_url)
X-Tahsilatmatik-Signature: t=1717500000,v1=4b2a9c...
X-Tahsilatmatik-Event:     payment.paid
X-Tahsilatmatik-Delivery:  12346
X-Tahsilatmatik-Key:       pk_xxx     (imza anahtarının türetildiği API anahtarı)

{
  "id": 12346,
  "event": "payment.paid",              // yalnız paid | authorized | failed
  "created_at": "2026-06-05T10:00:00+00:00",
  "data": {
    "reference": "SIPARIS-1001",        // eski GET dönüşündeki reference + status
    "status": "paid",                   //   anahtarları burada da mevcuttur
    "amount": 149.90,
    "refunded_amount": 0,
    "currency": "TRY",
    "installment_count": 1,
    "method": "card",
    "paid_at": "2026-06-05T10:00:00+00:00",
    "metadata": { "order_id": 1001 }
  }
}

Geçiş notu (eski GET dönüşü)

Daha önce ?reference=...&status=paid|failed GET dönüşünü bekleyen entegrasyonlar için: aynı reference ve status anahtarları POST'un data gövdesinde de yer alır. Okuma yerinizi query string yerine POST gövdesine taşıyın.

İmza anahtarı türetme

Kayıtlı webhook'tan tek farkı budur: callback_url'in kendi secret'i yoktur; imza anahtarını kendi API secret'inizden (sk_) türetirsiniz. X-Tahsilatmatik-Key ile eşleşen anahtarı seçin, sonra:

signingKey = HMAC_SHA256("posaxi:callback_url:v1", sha256_hex(sk_...))

İmza doğrulaması bu signingKey ile webhook ile bire bir aynıdır (t.rawBody üzerinde HMAC, 300 sn tolerans, hash_equals).

PHP
<?php
// callback_url imza doğrulama (PHP). Kayıtlı webhook'tan farkı: bu adresin kendine
// ait bir secret'i YOKTUR; imza anahtarını KENDİ API secret'inizden (sk_...) türetin.
$payload = file_get_contents('php://input');
$sig     = $_SERVER['HTTP_X_TAHSILATMATIK_SIGNATURE'] ?? '';
$keyId   = $_SERVER['HTTP_X_TAHSILATMATIK_KEY'] ?? '';   // hangi pk_ kullanıldı

// X-Tahsilatmatik-Key (pk_...) ile eşleşen API anahtarınızın gizli secret'i:
$apiSecret = 'sk_xxx';

// Türev imza anahtarı — backend ile birebir aynı reçete:
$signingKey = hash_hmac('sha256', 'posaxi:callback_url:v1', hash('sha256', $apiSecret));

parse_str(str_replace(',', '&', $sig), $p);   // t=..., v1=...
$expected = hash_hmac('sha256', ($p['t'] ?? '') . '.' . $payload, $signingKey);

if (! hash_equals($expected, $p['v1'] ?? '') || abs(time() - (int)($p['t'] ?? 0)) > 300) {
    http_response_code(400); exit('invalid signature');
}
// $event['id'] ile tekilleştirin, sonra 2xx dönün (aksi halde tekrar denenir).
http_response_code(200);

Webhook mü, callback_url mı?

Kayıtlı Webhookcallback_url (S2S)
Payload / zarfAynıAynı
OlaylarHepsi (iade dahil)paid / authorized / failed
İmza secret'iEndpoint'in kendi whsec_ secret'isk_'den türetilen anahtar + X-Tahsilatmatik-Key
KurulumWebhook'lar ekranındanOturum/ödeme isteğinde callback_url

Test Kartları (Sandbox)

POS test modundayken kart numarasının son 4 hanesi sonucu belirler:

AlanTipAçıklama
…0000redKart reddedildi.
…0009hataSağlayıcı hatası → failover (yedek POS).
…00023D3D Secure akışını tetikler.
diğeronayBaşarılı ödeme.

Hatalar & Limitler

  • 401 — kimlik eksik/geçersiz.
  • 402 — banka/kart reddetti (yanıt yine ödeme nesnesidir, status: failed).
  • 404 — istenen kayıt bulunamadı (örn. bilinmeyen referans).
  • 422 — doğrulama hatası (alan bazlı).
  • 429 — hız limiti (dakikada 120 istek).
422 örneği
HTTP/1.1 422 Unprocessable Content
{
  "message": "Tutar alanı zorunludur.",
  "errors": {
    "amount": ["Tutar alanı zorunludur."]
  }
}
# "errors" anahtarları GÖNDERDİĞİNİZ alan adlarıdır (amount, virtual_pos_id,
# condition_group, meal_card_id, reference …). Mesaj dili X-Locale başlığına
# göre döner; varsayılan Türkçe'dir.
404 örneği
HTTP/1.1 404 Not Found
{ "message": "Kayıt bulunamadı." }
# Örn. GET /v1/payments/BILINMEYEN-REF. Eski ham framework metni
# ("No query results for model [App\Models\Payment] BILINMEYEN-REF")
# artık DÖNMEZ — iç model adı ve satır kimliği sızdırılmaz.

Alan bazlı 422 hataları

errors nesnesinin anahtarı isteğinizde gönderdiğiniz alanın adıdır; hangi parametreyi düzelteceğinizi doğrudan buradan okursunuz. En sık karşılaşılanlar:

errors anahtarıNe zamanMesaj
virtual_pos_idBilinmeyen, pasif ya da başka bir üye iş yerine ait sanal POS kimliği.Belirtilen sanal POS bulunamadı veya aktif değil.
condition_groupGeçersiz/pasif ödeme koşulu grubu — condition_group_id ve condition_group için ortak alan adı.Belirtilen ödeme koşulu grubu bulunamadı veya aktif değil.
meal_card_idBilinmeyen ya da pasif yemek kartı tanımı.Belirtilen yemek kartı tanımı bulunamadı veya aktif değil.
referenceReferans için zaten tamamlanmış bir ödeme var (paid · authorized · refunded) — /v1/payments, /v1/meal-payments ve /v1/checkout/sessions'ta aynıdır. Tamamlanmamış bir referans hata değildir: mevcut kayıt idempotent döner.Bu sipariş referansı için zaten tamamlanmış bir ödeme var.
paymentÖdeme akışının genel iş kuralı reddi (uygun POS yok, limit aşımı, risk kuralı vb.).Duruma özel açıklama.

Hata gövdeleri iç yapıyı sızdırmaz

Hata mesajları kullanıcıya dönük ve yerelleştirilmiştir: iç model/sınıf adı (App\Models\…), satır kimliği, dosya yolu ya da SQL içermez. Dolayısıyla mesajı ayrıştırarak (string parse) mantık kurmayın — HTTP durum kodu ve errors alan adları kararlı sözleşmedir.

Bulunamayan bir kaynak için mesaj; kayıt gerçekten yokken, pasifken ve başka bir üye iş yerine aitken aynıdır. Bu bilinçlidir: bu uçlarla başka işletmelerin kayıt kimlikleri taranamaz.

Mesaj dili X-Locale başlığıyla seçilir; başlık yoksa hesabınızın varsayılan dili (Türkçe) kullanılır.

Tekrarlı POST'larda çift tahsilatı önlemek için Idempotency-Key başlığı gönderin; aynı anahtarla gelen ikinci istek ilk yanıtı tekrar döner (Idempotent-Replayed: true).