vellTRDocs
RehberlerKart Ödemelerini Kabul Etme

Kart Ödemelerini Kabul Etme

Orta25 min

Barındırılan (hosted) checkout oturumları, doğrudan API ile kart tahsilatı ve uçtan uca 3DS 2.0 doğrulama akışı.


Kvell ile kart ödemesi kabul etmenin iki yolu vardır: tüm kart formunu Kvell'in barındırılan checkout'una devretmek, ya da kartı kendiniz toplayıp sunucunuzdan bir token'ı tahsil etmek. İkisi de aynı yerde biter — bir payment.captured webhook'u — bu yüzden akışın ne kadarının sahibi olmak istediğinize göre seçin.

Bir entegrasyon yolu seçin

Barındırılan checkout oturumuDoğrudan API
Kart formuKvell tarafından barındırılırSiz oluşturursunuz (veya bir SDK'nın Elements benzeri bileşenini kullanırsınız)
3DS 2.0 doğrulamasıTamamen Kvell tarafından yönetilirAlıcıyı yönlendirir ve geri dönüşü siz işlersiniz
Taksit seçiciinstallmentEnabled: true olduğunda otomatik olarak gösterilirTaksit API'sini siz çağırır, seçiciyi siz oluşturursunuz
PCI kapsamıMinimal — kart numarası hiçbir zaman ön yüzünüze veya sunucunuza ulaşmazHam kart verisinin sunucunuza dokunmasına izin vermemelisiniz; tarayıcıda tokenize edin
En uygun olduğu durumEn hızlı entegrasyon, standart checkout akışlarıÖzel checkout arayüzleri, native mobil, kayıtlı kart akışları

Tutarlar her iki yolda da her zaman kuruş cinsinden (1 TL'nin 1/100'ü) tam sayıdır — 15000 demek 150,00 TL demektir. Para birimi her zaman "TRY" olur.

Yol A: Barındırılan checkout oturumu

1. Bir oturum oluşturun

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/sessions \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <hesaplanan imza>" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionId": "ORDER-2026-001",
    "amount": 15000,
    "currency": "TRY",
    "description": "Order #1234",
    "returnUrl": "https://merchant.example.com/return"
  }'

transactionId sizin kendi sipariş id'nizdir ve aynı zamanda idempotency güvencesi görevi görür — üye işyeri başına benzersizdir, dolayısıyla aynı siparişi aynı id ile tekrar denemek her zaman güvenlidir. Bu endpoint'te bilinmesi gereken birkaç alan daha:

AlanAçıklama
installmentEnabledBarındırılan sayfada taksit seçiciyi göster — bkz. Taksitli Ödemeler
customerIdOturumu kayıtlı bir müşteriye bağlar, kart kaydetme onay kutusunu etkinleştirir
callbackUrlYalnızca bu ödeme için mağazanızın varsayılan webhook URL'sini geçersiz kılar
mode"payment" (varsayılan), "card_update_only" veya kart formu yerine bir TR QR ödemesi için "qr"

Yanıt, barındırılan sayfanızdır:

JSON
{
  "sessionId": "3f1c9a2e-6b4d-4e8a-9c1f-2a7b8c9d0e1f",
  "checkoutUrl": "https://pay.kvell.group/s/3f1c9a2e-6b4d-4e8a-9c1f-2a7b8c9d0e1f",
  "expiresAt": "2026-04-13T10:30:00Z",
  "merchantId": "mrc_8a1b2c3d4e5f",
  "amount": 15000,
  "currency": "TRY",
  "status": "active"
}

2. Alıcıyı yönlendirin

Alıcının tarayıcısını checkoutUrl'e gönderin. Oturum, oluşturulduktan 30 dakika sonra sona erer (expiresAt) — alıcı checkout'u bundan daha uzun sürede tamamlarsa yenisini oluşturun.

3. Kvell kart formunu ve 3DS'i yönetir

Barındırılan sayfa kartı toplar, ihraççı gerektirdiğinde 3DS 2.0 doğrulamasını çalıştırır ve etkinleştirdiyseniz taksit seçiciyi gösterir. Bunların hiçbirini siz uygulamazsınız — bu yolun tüm amacı budur.

4. Alıcı returnUrl'inize geri döner

Kvell, işlemi başarılı ya da başarısız olsun, alıcıyı bitirdiğinde geri yönlendirir. Bu yönlendirmeye dayanarak asla bir siparişi ödendi olarak işaretlemeyin — alıcı, yönlendirme tetiklenmeden sekmeyi kapatabilir ya da işlemi hiç tamamlamadan manuel olarak geri gidebilir. Yönlendirmeyi bir "işleniyor" ekranı göstermek için bir ipucu olarak ele alın ve gerçek sonucu payment.captured webhook olayından doğrulayın (bkz. Webhook'lar).

5. Durumu doğrulayın

Backend gidiş-dönüşü olmadan alıcıya yönelik bir durum sayfası için, herkese açık durum endpoint'ini yoklayın — ödeme id'sinin kendisi yetki kanıtı olduğundan imza veya API anahtarı gerektirmez:

Shell
curl https://api.pay.kvell.group/v1/payments/pay_1a2b3c4d5e6f/public-status
JSON
{
  "paymentId": "pay_1a2b3c4d5e6f",
  "status": "captured"
}

Bu, alıcının tarayıcısından doğrudan çağırmak için güvenli olacak şekilde bilinçli olarak dardır — tutar yok, kart bilgisi yok, üye işyeri id'si yok. Sunucunuzdan tam kaydı almak için imzalı GET /v1/payments/{id} endpoint'ini kullanın.

Yol B: Doğrudan API entegrasyonu

1. Kartı tarayıcıda tokenize edin

Kart alanlarını kendi formunuzda toplayın ve doğrudan tarayıcıdan, yalnızca genel anahtarınızı kullanarak Kvell'e gönderin — ham kart numarası hiçbir zaman sunucunuza dokunmaz:

Shell
curl -X POST https://api.pay.kvell.group/v1/cards/tokens \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "Content-Type: application/json" \
  -d '{
    "pan": "4155650000000007",
    "expMonth": 12,
    "expYear": 2028,
    "cvv": "123"
  }'
JSON
{
  "token": "tok_9f8e7d6c5b4a",
  "last4": "0007",
  "bin": "415565",
  "brand": "visa"
}

Token, tek bir mağazaya bağlıdır ve kendi sunucunuza vermek için güvenlidir — mağazanızın bağlamı dışında işe yaramaz.

2. Token'ı sunucunuzdan tahsil edin

Bu çağrı, para hareketi yapan bir sunucudan-sunucuya çağrı olduğu için gizli anahtarınızla imzalanır:

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/card \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <hesaplanan imza>" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionId": "ORDER-2026-001",
    "amount": 15000,
    "currency": "TRY",
    "cardToken": "tok_9f8e7d6c5b4a",
    "installmentCount": 3
  }'

Bilinmesi gereken birkaç parametre:

AlanVarsayılanNotlar
installmentCount11–12, kartın BIN'i ve tutarı için uygun planlara karşı sunucu tarafında yeniden doğrulanır
threeDSecuretrue3DS doğrulamasını zorunlu kılar; özel bir nedeniniz olmadıkça açık bırakın
capturetrueAnında tahsilat yerine bir ön yetkilendirme (preauth) yerleştirmek için false yapın — aşağıya bakın
saveCardfalseBaşarılı bir tahsilattan sonra kartı customerId'ye ekler — yalnızca tahsilat, zaten bağlı bir customerId'si olan bir oturuma bağlıysa geçerlidir

Kart bir 3DS doğrulamasına ihtiyaç duymuyorsa (aşağıya bakın), sonucu hemen alırsınız:

JSON
{
  "paymentId": "pay_1a2b3c4d5e6f",
  "status": "captured",
  "amount": 15000,
  "currency": "TRY",
  "commission": 449,
  "netAmount": 14551,
  "cardMask": "415565******4321",
  "cardBrand": "visa",
  "cardType": "credit",
  "installmentCount": 3,
  "threeDSecure": true,
  "authCode": "123456",
  "createdAt": "2026-04-13T10:00:00Z",
  "completedAt": "2026-04-13T10:00:02Z"
}

3. 3DS 2.0 doğrulamasını işleyin

İhraççı bir doğrulama gerektiriyorsa yanıt farklı görünür — status alanına göre dallanın:

JSON
{
  "status": "three_d_redirect",
  "threeDUrl": "https://acs.example.com/challenge/c0rr-3f1c-9a2e-6b4d",
  "correlationId": "c0rr-3f1c-9a2e-6b4d"
}

Alıcının tarayıcısını threeDUrl'e yönlendirin. Doğrulamayı tamamladıktan sonra ACS, sorgu dizesinde bir pares doğrulaması ve aynı correlationId ile onu geri yönlendirir — tahsilatı tamamlamak için ikisini de gönderin:

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/pay_1a2b3c4d5e6f/3ds-callback \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:05:00Z" \
  -H "X-Signature: <hesaplanan imza>" \
  -H "Content-Type: application/json" \
  -d '{
    "pares": "eyJjaGFsbGVuZ2VJZCI6Ii4uLiJ9.9a8b7c6d",
    "correlationId": "c0rr-3f1c-9a2e-6b4d"
  }'

Bu çağrı idempotent'tir — alıcının tarayıcısı geri dönüşü iki kez gönderirse, ikinci çağrı ikinci bir tahsilat denemesi yerine aynı nihai sonucu döndürür.

150 TL'nin (15000 kuruş) altındaki işlemler, Türkiye'deki düzenleyici taban gereği 3DS doğrulamasını tamamen atlayabilir. Her tahsilatın bir yönlendirme tetikleyeceğini varsaymak yerine, her zaman tahsilat yanıtındaki status alanına göre dallanın.

4. Ödeme durumunu okuyun

DurumAnlamı
newAz önce oluşturuldu, henüz bankaya bir çağrı yapılmadı
authorizedBanka bir ön yetkilendirmeyi onayladı (capture: false); ledger'a hiçbir kayıt düşülmedi, tutar tutuluyor ama hareket etmedi
capturedPara hareket etti — nihai başarı durumu
failedBanka tarafından reddedildi veya 3DS doğrulaması başarısız oldu/süresi doldu
refundedTamamen iade edildi (aşağıya bakın)

Ön yetkilendirme ve tahsilat

Para hareket ettirmeden bir tutma (hold) yerleştirmek için tahsilat sırasında capture: false gönderin — "sipariş anında yetkilendir, kargoya verme anında tahsil et" gibi akışlar için kullanışlıdır. Ödeme status: "authorized" durumunda başlar.

Tutarı almaya hazır olduğunuzda:

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/pay_1a2b3c4d5e6f/capture \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:05:00Z" \
  -H "X-Signature: <hesaplanan imza>"

Tahsilat yalnızca tam tutar üzerinden yapılır (kısmi tahsilat henüz yok) ve zaten tahsil edilmiş bir kayıt için idempotent'tir. Tutarı hiç almayacaksanız, bunun yerine tutmayı serbest bırakın:

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/pay_1a2b3c4d5e6f/void \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:05:00Z" \
  -H "X-Signature: <hesaplanan imza>" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Order abandoned"}'

Bir void hiçbir parayı hareket ettirmez — ledger'ınıza hiçbir şey düşülmez ve alıcının ihraççısı kendi tarafındaki tutmayı serbest bırakır.

İadeler

Tahsil edilmiş bir ödemeyi tamamen veya kısmen iade edin:

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/pay_1a2b3c4d5e6f/refund \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T14:00:00Z" \
  -H "X-Signature: <hesaplanan imza>" \
  -H "Content-Type: application/json" \
  -d '{"amount": 5000, "reason": "Customer request"}'

Tam iade için amount alanını atlayın; kısmi iade için (GET /v1/payments/{id}'den gelen refundableAmount ile sınırlı olacak şekilde) ekleyin.

JSON
{
  "paymentId": "pay_1a2b3c4d5e6f",
  "status": "captured",
  "refundAmount": 5000,
  "refundableAmount": 10000,
  "lastRefund": {
    "refundId": "rfn_7c8d9e0f1a2b",
    "status": "completed",
    "amount": 5000,
    "bankRefundId": "rfn_mock_44182",
    "createdAt": "2026-04-13T14:00:00Z",
    "completedAt": "2026-04-13T14:00:01Z"
  }
}

İade senkron olarak tamamlandığında 200, hâlâ işlemdeyken (banka bacağı asenkron olarak bitiyorken) 202 alırsınız. 202 durumunda yeni bir Idempotency-Key üretip yeniden denemeyin — lastRefund.status alanı completed olana kadar GET /v1/payments/{id}'yi yoklayın ya da payment.refunded webhook'unu bekleyin.

Red ve hataları işleme

Bir banka reddi veya iş kuralı reddi, standart hata zarfını (envelope) döndürür:

JSON
{
  "code": "KVELL-PAY-4001",
  "message": "Payment declined by issuing bank.",
  "requestId": "7b9e5c21-4a3f-4e8a-b9d2-1f4a8c9e5b10"
}

4xxx aralığındaki kodlar entegrasyonunuzdaki bir hata değil, iş kuralı sonuçlarıdır — gerçek bir ret, uygun olmayan bir taksit sayısı (bkz. Taksitli Ödemeler) veya eskimiş bir 3DS oturumu hepsi buraya düşer. Bir alıcı sonucu itiraz ederse destekle ilişkilendirebilmek için requestId'yi siparişle birlikte loglayın.

Reddedilen veya iptal edilen (void) bir ödeme, ledger'ınıza hiçbir şey düşürmez — sizin tarafınızda sallantıda kalan kısmi bir tahsilat ya da bekleyen bir tutma olmaz.

Sonraki adımlar

RehberNeyi kapsar
Taksitli ÖdemelerBIN sorgulama, seçici ve komisyon hesabı
Webhook'larYönlendirmelere güvenmek yerine payment.captured'ı güvenilir şekilde doğrulama
Sandbox Ortamında Test EtmeX-Sim-Scenario ile belirli sonuçları zorlama — retler, 3DS doğrulamaları, zaman aşımları
API Referansı — PaymentsHer payments endpoint'indeki her alan