Resmi iyzipay SDK'ları imzalama işini sizin için yapar; ama bazen bağımlılık eklemeden, ham REST API'yi anlayarak entegre etmek istersiniz - örneğin paylaşımlı bir hosting'de Composer kullanamadığınızda veya isteğin tam olarak ne gönderdiğini görmek istediğinizde. Bu rehber, SDK'sız, saf PHP + cURL ile yazılmış açık kaynak bir iyzico entegrasyon örneğini baştan sona inceliyor: IYZWSv2 imza şeması, NON-3DS, 3D Secure, Checkout Form ve PWI akışlarının tamamı.

İlgili içerikler: iyzico Node.js entegrasyon rehberi · REST API güvenlik rehberi

Açık Kaynak Örnek Proje

Bu rehberdeki tüm kod örnekleri, github.com/EgemenKEYDAL/Iyzico-Payment-Samples adresindeki açık kaynak projeden alınmıştır - NON-3DS, 3DS, PWI, Checkout Form, BIN sorgulama ve webhook için tamamen Türkçe açıklamalı, kopyala-çalıştır PHP kodları içerir. Projeyi klonlayıp kendi apiKey/secretKey bilgilerinizle doğrudan test edebilirsiniz.

IYZWSv2: Manuel İmza Oluşturma

Resmi SDK kullanmadığınızda, her isteğin Authorization header'ını kendiniz üretmeniz gerekir. iyzico'nun IYZWSv2 şeması şu adımları izler: rastgele bir randomKey üretilir, randomKey + uri + JSON-encode edilmiş istek gövdesi birleştirilip secretKey ile HMAC-SHA256'dan geçirilir, sonuç apiKey:...&randomKey:...&signature:... formatında birleştirilip Base64'e çevrilir.

php
public function generateAuthorizationHeader($uri, $requestBody = [])
{
    $randomKey = (string)(microtime(true) * 10000) . rand(100000, 999999);
    $jsonBody = empty($requestBody) ? '' : json_encode($requestBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

    $dataToEncrypt = $randomKey . $uri . $jsonBody;
    $signature = hash_hmac('sha256', $dataToEncrypt, $this->secretKey);

    $authString = "apiKey:" . $this->apiKey .
                  "&randomKey:" . $randomKey .
                  "&signature:" . $signature;

    return base64_encode($authString);
}

// İstek başlığı:
// Authorization: IYZWSv2 <base64-encoded-auth-string>

Dört Ödeme Yöntemi: Hangisini Seçmeli?

YöntemAkışNe Zaman Tercih Edilir
NON-3DSTek istekle tamamlanır, 3DS yokDüşük tutarlı, düşük riskli işlemler
3DSİki adımlı: initialize - auth, banka OTP'si gerekirChargeback riskini azaltmak istediğiniz işlemler
Checkout Form4 görüntüleme modu (redirect/responsive/popup/iframe), kart verisi sizde değilÖzelleştirilebilir, PCI kapsamını küçültmek isteyenler
PWI (iyzico ile Öde)Kullanıcı iyzico hesabıyla tek tıkla öder, sadece redirectAlıcı koruma programından faydalanmak isteyenler

NON-3DS Ödeme

En basit akıştır: kart bilgileri, alıcı ve sepet bilgileriyle birlikte tek bir /payment/auth isteği atılır, yanıt anında gelir.

php
$paymentRequest = [
    'locale' => 'tr',
    'conversationId' => 'order-' . uniqid(),
    'price' => '100.00',
    'paidPrice' => '100.00',
    'currency' => 'TRY',
    'installment' => 1,
    'basketId' => 'B' . time(),
    'paymentChannel' => 'WEB',
    'paymentGroup' => 'PRODUCT',
    'paymentCard' => [
        'cardHolderName' => $cardHolderName,
        'cardNumber' => $cardNumber,
        'expireMonth' => $expireMonth,
        'expireYear' => $expireYear,
        'cvc' => $cvc,
        'registerCard' => 0
    ],
    'buyer' => $buyer,
    'shippingAddress' => $shippingAddress,
    'billingAddress' => $billingAddress,
    'basketItems' => $basketItems
];

$response = $iyzico->sendRequest('/payment/auth', $paymentRequest);

if ($response['status'] == 'success') {
    // paymentId'yi kaydet, siparişi onayla
}

3D Secure Akışı: Initialize + Auth

3DS ödemeler iki ayrı isteğe bölünür. Initialize adımı bir threeDSHtmlContent (Base64) döner - bu HTML, kullanıcının bankasından gelen OTP formunu içerir ve doğrudan tarayıcıya basılmalıdır:

php
$initResponse = $iyzico->sendRequest('/payment/3dsecure/initialize', $initializeRequest);

// HTML'i tarayıcıya bas
echo base64_decode($initResponse['threeDSHtmlContent']);

// Kullanıcı OTP'yi girip onayladıktan sonra banka,
// callbackUrl'inize POST yapar (paymentId, mdStatus, conversationId ile)
php
// callback.php - banka yönlendirmesi sonrası
$authRequest = [
    'locale' => 'tr',
    'conversationId' => 'auth-' . uniqid(),
    'paymentId' => $_POST['paymentId'],
    'conversationData' => $_POST['conversationData'] ?? ''
];

$authResponse = $iyzico->sendRequest('/payment/3dsecure/auth', $authRequest);

if ($authResponse['status'] == 'success' && $authResponse['fraudStatus'] == 1) {
    // ödeme kesinleşti
}

mdStatus = 1 başarılı 3DS doğrulamasını gösterir; farklı bir değer banka tarafında doğrulamanın tamamlanmadığı anlamına gelir ve auth isteği başarısız döner.

Checkout Form: 4 Görüntüleme Modu

Checkout Form'u başlattığınızda hem bir token + paymentPageUrl, hem de gömülebilir bir checkoutFormContent (Base64 script) alırsınız. Bunu dört farklı şekilde kullanabilirsiniz:

  • Redirect (en yaygın) - header('Location: ' . $paymentPageUrl);
  • Responsive - sayfanıza gömülü: <div id="iyzipay-checkout-form" class="responsive"></div> + script
  • Popup - aynı script, class="popup" ile açılır pencere olarak
  • iFrame - <iframe src="{paymentPageUrl}&iframe=true">

Token 30 dakika geçerlidir. Kullanıcı bu süre içinde ödemeyi tamamlamazsa yeniden initialize etmeniz gerekir. Callback'te token ile sonucu sorgularsınız:

php
$retrieveRequest = [
    'locale' => 'tr',
    'conversationId' => 'cf-retrieve-' . uniqid(),
    'token' => $_POST['token']
];

$result = $iyzico->sendRequest('/payment/iyzipos/checkoutform/auth/ecom/detail', $retrieveRequest);

if ($result['status'] == 'success' && $result['paymentStatus'] == 'SUCCESS' && $result['fraudStatus'] == 1) {
    // siparişi onayla
}

Yanıt İmzasının Doğrulanması

İstek imzalamak yeterli değildir - gelen yanıtın da gerçekten iyzico'dan geldiğini doğrulamalısınız. Burada parametre sırası endpoint'e göre değişir; örneğin Checkout Form sonuç sorgulamasında sıra paymentStatus, paymentId, currency, basketId, conversationId, paidPrice, price, token'dır. Parametreler : ile birleştirilip aynı secretKey ile HMAC-SHA256'dan geçirilir ve hash_equals() ile sabit zamanlı karşılaştırılır:

php
public function verifyResponseSignature($response, $parameterOrder)
{
    $params = [];
    foreach ($parameterOrder as $param) {
        if (isset($response[$param])) {
            $value = $response[$param];
            if (in_array($param, ['price', 'paidPrice'])) {
                $value = rtrim(rtrim((string)$value, '0'), '.'); // trailing zero temizliği
            }
            $params[] = $value;
        }
    }
    $dataToEncrypt = implode(':', $params);
    $calculated = hash_hmac('sha256', $dataToEncrypt, $this->secretKey);

    return hash_equals($calculated, $response['signature']);
}

Fraud ve 3DS Zorunluluğu Kontrolü

Bir ödemeyi onayladıktan sonra ürünü göndermeden önce mutlaka fraudStatus alanını kontrol edin; bazı bankalar işlemi anlık onaylasa da iyzico'nun fraud motoru ek inceleme isteyebilir.

fraudStatusAnlamıAksiyon
1OnaylandıÜrünü kargoya verebilirsiniz
0İncelemedeBekleyin, otomatik sonuçlanır
-1ReddedildiÜrünü göndermeyin, siparişi iptal edin

Benzer şekilde, BIN sorgulama (/payment/bin/check) yanıtındaki force3ds alanı kartın 3DS zorunlu olup olmadığını söyler - 1 ise yalnızca 3DS akışıyla ödeme alabilirsiniz, NON-3DS isteği reddedilir.

Webhook: Event Tipleri ve İdempotency

iyzico, ödeme tamamlandığında sunucunuza bir POST bildirimi gönderir; sunucunuz 2xx ile yanıt verene kadar 10 dakikada bir, en fazla 3 kez tekrar dener. Webhook'taki iyziEventType alanı, hangi ödeme yönteminin tetiklediğini söyler:

iyziEventTypeÖdeme Yöntemi
API_AUTHNON-3DS ödeme
THREE_DS_AUTH3DS ödeme
CHECKOUTFORM_AUTHCheckout Form / PWI ödeme
BKM_AUTHBKM Express ödeme

Webhook'u callback'le birlikte kullanırken aynı ödemeyi iki kez işlemediğinizden emin olun - paymentId üzerinden veritabanında bir tekillik (unique) kısıtı tanımlamak en güvenli yöntemdir.

Sıkça Sorulan Sorular

SDK kullanmadan entegrasyon yapmak güvenli mi?

Evet, doğru uygulandığında güvenlidir - imzalama mantığı (HMAC-SHA256) açık ve dokümante edilmiştir. Avantajı bağımlılık eklememek ve isteğin tam içeriğini kontrol etmektir; dezavantajı imzalama/doğrulama kodunu siz yazıp test etmeniz gerektiğidir.

"Signature geçersiz" hatası en çok neden alınır?

En sık nedenler: parametre sırasının endpoint'e uymaması, price/paidPrice alanlarında trailing zero temizliğinin yapılmaması, veya JSON body'nin Türkçe karakterler/slash'lar farklı escape edilerek üretilmesi (JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE flag'leri bu yüzden kritiktir).

NON-3DS mi 3DS mi tercih edilmeli?

Mümkünse 3DS tercih edin: chargeback (ters ibraz) riskini bankaya devreder ve PCI sorumluluğunuzu azaltır. Bazı kart tipleri (BIN sorgulamada force3ds=1) zaten yalnızca 3DS ile çalışır.