Ana Sayfa / Blog / PayTR entegrasyonu: webhook ve callback notları

PayTR entegrasyonu: webhook ve callback notları

PayTR'yi 6 WooCommerce sitesinde kurdum. iyzico'dan farklı akışı, iFrame checkout, callback timing, taksit politikaları üzerine öğrendiklerim.

Türkiye’de ödeme entegrasyonları için iyzico ve PayTR iki popüler seçenek. 6 farklı WooCommerce sitesinde PayTR kurdum. Özellikleri, zorlukları, iyzico’dan farkı üzerine notlar.

PayTR neden seçiliyor

PayTR’nin öne çıkan farkları:
– Komisyon oranları bazı kart tipleri için iyzico’dan düşük
– Non-3D ve 3D Secure her ikisi de mevcut, daha esnek konfigürasyon
– iFrame-based checkout daha kolay entegrasyon
– Link with Pay ödeme linki (mesaj ile gönder, tıkla-ödeme) güçlü
– Taksit sistemi kart bazında granular

Dezavantajları:
– Dokümantasyon iyzico’ya göre daha karışık
– Test kartları az, test senaryoları sınırlı
– API response formatları tutarsız (bazı endpoint JSON, bazı form-encoded)
– Dashboard iyzico’dan daha az polish

İlk kurulum: merchant onboarding

PayTR merchant hesabı açmak 1-2 hafta sürüyor:
– Vergi levhası, faaliyet belgesi yükleme
– Banka hesap bilgisi doğrulama
– İşyeri inceleme
– Test hesap açıldıktan sonra test ortamında teknik entegrasyon
– Production geçişi için ek inceleme

Bu süreç başından sonuna developer’ın elinde değil. Müşterinin operation ekibi ile paralel yürütülüyor.

iFrame checkout: en basit entegrasyon

PayTR’nin iFrame API’si en hızlı yol:

  1. Backend’de get_token endpoint’ine POST. Sipariş bilgisi + merchant credentials.
  2. PayTR bir token dönüyor.
  3. Frontend’de https://www.paytr.com/odeme/guvenli/ URL’i iframe’e embed ediliyor.
  4. Kullanıcı iframe içinde kart bilgisi giriyor, 3D OTP giriyor.
  5. Sonuç PayTR’den callback URL’inize POST ediyor.

iFrame’in avantajı: kart bilgisi sizin sunucunuza hiç gelmiyor, PCI compliance yükü PayTR’de. En düşük güvenlik riski.

Dezavantajı: iFrame UI’ı PayTR branding’i ile geliyor. Custom UX çok sınırlı.

Hash hesabı: dikkat gerektiren yer

Her PayTR request’i hash ile imzalanıyor. Hash yanlış = request reject.

$merchant_id = 'XXXXXX';
$merchant_key = 'XXXXXXX';
$merchant_salt = 'XXXXXXX';

$hash_str = $merchant_id . $user_ip . $merchant_oid . $email . $payment_amount . $user_basket . $no_installment . $max_installment . $currency . $test_mode;
$paytr_token = base64_encode(hash_hmac('sha256', $hash_str . $merchant_salt, $merchant_key, true));

Yaygın hatalar:
$user_basket base64 encode edildikten sonra mı önce mi hash’e gidecek: base64’den sonra
$payment_amount integer olmalı, ondalık değil (100 TL = 10000)
$test_mode string ‘1’ veya ‘0’ olmalı, boolean değil

Hash hesabında 1 karakter farkla token invalid geliyor, debug saat alabiliyor. PayTR’nin PHP SDK’sını kullanın manuel yazmaktan kaçının.

Callback URL: kritik endpoint

PayTR ödeme sonucunu callback URL’inize POST ediyor. Handler:

add_action('init', function() {
    if (!isset($_POST['merchant_oid'])) return;
    
    $post = $_POST;
    $hash_verify = base64_encode(hash_hmac(
        'sha256',
        $post['merchant_oid'] . $merchant_salt . $post['status'] . $post['total_amount'],
        $merchant_key,
        true
    ));
    
    if ($hash_verify !== $post['hash']) {
        echo 'PAYTR notification failed: bad hash';
        exit;
    }
    
    if ($post['status'] === 'success') {
        // Siparişi başarılı olarak işaretle
        $order_id = get_order_by_oid($post['merchant_oid']);
        $order = wc_get_order($order_id);
        $order->payment_complete($post['merchant_oid']);
    } else {
        $order->update_status('failed', $post['failed_reason_msg']);
    }
    
    echo 'OK';
    exit;
});

Önemli:
– Hash verification ilk iş
– “OK” cevabı hemen dönüyor, yoksa PayTR retry yapıyor
– Order status sadece success durumunda tamamlanıyor
– failed_reason_code alanı debug için logla

Callback timing ve retry

PayTR callback’i immediate değil, ödeme tamamlandıktan 5-15 saniye sonra geliyor. Kullanıcı iframe’de “ödeme başarılı” gördüğü an callback henüz gelmemiş olabiliyor.

Bu nedenle:
– Frontend success page’inde “ödemeniz işleniyor, size e-posta göndereceğiz” mesajı
– Backend callback’e kadar siparişi pending’de tutun
– Kullanıcı success page’ini yenilerse order status güncellenmişse göstermelisiniz

Callback gelmezse PayTR 3 kez retry yapıyor (5, 15, 60 dakika). Hala başarısız ise manuel reconciliation gerekli.

Payment link: B2B için ideal

PayTR’nin “Payment Link” özelliği B2B siparişlerde güzel kullanım:

  1. Operations panel’den ödeme tutarı + email girilerek link oluşturuluyor
  2. Link WhatsApp / email ile müşteriye gönderiliyor
  3. Müşteri link’e tıklayıp ödüyor
  4. Tamamlanma notification’ı e-posta + callback ile geliyor

Bu akış ERP’li toptan satış siteleri için çok uygun. Checkout süreci olmadan da ödeme alınabiliyor.

Taksit: kart bazında granular

PayTR her kart bankası için farklı taksit seçeneği dönüyor:

  • Yapı Kredi WorldPuan: 6 taksit opsiyonu
  • Garanti BonusCard: 9 taksit
  • İş Bankası Maximum: 3 taksit

BIN inquiry endpoint ile kart numarasının ilk 6 hanesinden hangi banka olduğunu çıkarıyorsunuz, o bankanın taksit opsiyonlarını gösteriyorsunuz.

Not: taksit komisyonu merchant’a kesiliyor, end user’a gösterilen fiyat değişmiyor. Taksit yapan kart sayısı yüksek ise komisyon önemli kalem olabilir.

Production go-live checklist

Test’ten production’a geçmeden önce:

  • [ ] Production credentials alındı
  • [ ] Config environment variable’lar güncellendi
  • [ ] test_mode: 0 olarak set edildi
  • [ ] Callback URL production domain’inde çalışıyor
  • [ ] SSL certificate geçerli (PayTR https olmayan callback’e POST yapmıyor)
  • [ ] Order status transition’ları test edildi (success, failed, cancelled)
  • [ ] Fraud reason code mapping yapıldı
  • [ ] Müşteriye spesifik error mesajları
  • [ ] Webhook signature verification aktif
  • [ ] Refund flow test edildi (refund endpoint ayrı)
  • [ ] Reporting + reconciliation job’ı kurulu

Bu 11 madde olmadan production’a geçince 2. haftada incident yaşıyorsunuz.

Hata mesajları

PayTR failed_reason_msg alanı Türkçe açıklama veriyor:
– “Geçersiz kart numarası”
– “Kart limiti yetersiz”
– “Provizyon alınamadı”
– “İşlem reddedildi, bankanızla görüşünüz”

Bu mesajları olduğu gibi kullanıcıya gösterebilirsiniz. iyzico’nun error code map’ine göre tercüme gerekmiyor.

Önemli: fraud reject’lerde detaylı mesaj vermeyin. “İşlem güvenlik kontrolüne takıldı, desteğe yazın” yeterli. Fraud pattern’ını açıklamak attacker’lara ipucu veriyor.

Son tavsiye

PayTR iyzico’ya kıyasla daha az polish ama bazı kartlarda daha iyi komisyon, Payment Link gibi özgün özellikler sunuyor. Tercih müşterinin volume, kart profili, dashboard kullanım tercihine göre değişiyor.

Ben müşteriye iyzico + PayTR’yi paralel kurmayı öneriyorum: checkout’ta kullanıcı seçebiliyor, biri outage yaşarsa diğeri devam ediyor, komisyon optimizasyonu için trafiği ölçerek dağıtılıyor. Daha sağlam setup.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç