- baslatOdeme { error } | { yonlendir } döndürür; SatinAlForm client'ta
router.push yapar (cacheComponents altında geri dönüşte buton tıklamaya
sağır kalıyordu); tıklamada ok → dönen halka
- paketi olan yeniden 'paket', paketi olmayan 'topup' başlatamaz
- /odeme, paket alınmışken eski bekleyen paket siparişinin formunu göstermez
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
5.8 KiB
iyzico ödeme entegrasyonu
Ödeme akışı iyzico Checkout Form (CF) üzerinden yürür: kart bilgisi hiçbir
zaman bize ulaşmaz; kart formu iyzico iframe'i olarak kendi /odeme sayfamıza
gömülür.
Ortam değişkenleri
| Değişken | Açıklama |
|---|---|
IYZICO_API_KEY |
Merchant Portal → Ayarlar → API anahtarları |
IYZICO_SECRET_KEY |
Aynı ekran. İmza doğrulamasında da kullanılır |
IYZICO_BASE_URL |
Sandbox: https://sandbox-api.iyzipay.com · Canlı: https://api.iyzipay.com |
APP_URL |
callback ve yönlendirme URL'lerinin kökü; https ve geçerli SSL şart. Verilmezse BETTER_AUTH_URL'e düşer |
IYZICO_BASE_URL verilmezse sandbox'a düşülür. Prod'da bu durum log'a uyarı
basar — canlıya çıkarken bu satır mutlaka ayarlanmalı.
Kök adres NEXT_PUBLIC_APP_URL ile verilemez: NEXT_PUBLIC_* değişkenleri
build sırasında koda gömülür, Docker build'i ise .env.production'ı görmez
(.dockerignore .env*'i dışlar). Sonuç, env doğru girilmiş olsa bile imaja
http://localhost:3000 çakılması ve iyzico'nun initialize'ı reddetmesiydi.
Ayrıntı: src/lib/app-url.ts.
Lokal
http://localhost:3000ile uçtan uca test edilemez: iyzico callback adresinden geçerli SSL ister. Sandbox testinde tünel (cloudflared/ngrok) açıpAPP_URL'i o https adrese ayarla.
Akış
baslatOdeme(src/features/odeme/odeme-actions.ts) —orderssatırınıpendingolarak yazar,checkoutFormInitializeçağırır, dönentoken'ı siparişe iliştirir ve/odeme?siparis=<id>adresini döndürür (redirect()fırlatmaz); yönlendirmeyiSatinAlFormclient'tarouter.pushile yapar.conversationId=basketId= sipariş id'miz. Oturum yoksa/giris?callback=/paketdöner. Sunucu koruması: paketi olan yenidenpaket, paketi olmayantopupbaşlatamaz;/odemesayfası da paket alınmışken eski bekleyen paket siparişinin formunu göstermez (bayat sekme / ikinci cihazdan çift çekim önlemi).- Kullanıcı ödemeyi bitirince iyzico
/api/odeme/callbackadresine cross-site POST atar; gövdede yalnızcatokenvardır. SameSite=Lax nedeniyle session çerezi gelmez, bu yüzden kullanıcı token'dan çözülür. odemeyiSonuclandir(src/lib/odeme.ts) iyzico'yacheckoutForm.retrieveile sorar ve krediyi idempotent tanımlar. Kullanıcı 303 ile/odeme/sonuc?siparis=…sayfasına düşer./odeme/sonucself-healing'dir: sipariş hâlâpendingise aynı fonksiyonu tekrar çağırır (callback kaybolduysa kurtarır)./api/odeme/webhookiyzico bildirimini karşılar — sekmesini kapatan ya da fraud incelemesinde bekleyen ödemeler için yedek yol.
Doğruluk kuralları (bunlara dokunurken dikkat)
- Kredi yalnızca
retrieveyanıtına göre tanımlanır. Ne callback gövdesine ne webhook gövdesine güvenilir; ikisi de sadece "iyzico'ya tekrar sor" tetikleyicisidir. paidgeçişi koşulludur (WHERE status != 'paid'): eşzamanlı callback + sayfa render'ı ikinci kez kredi yazamaz.grantCredits'tekiUNIQUE(reason, ref_id)ikinci katman güvencedir.- Ara durumlar
faileddamgalanmaz.INIT_THREEDS,CALLBACK_THREEDS,PENDING_CREDIT,INIT_BANK_TRANSFER… ödemenin sonuçlanmadığı anlamına gelir; damgalarsak dakikalar sonra SUCCESS'e dönen ödemede kredi kaybolur. fraudStatus:1onaylı → kredi verilir.0incelemede →pendingbırakılır (çekim kesinleşmemiştir).-1reddedildi →failed.- Erken
failedkurtarılabilir: geçiş koşulustatus != 'paid'olduğu için yanlışlıklafaileddamgalanmış bir sipariş, iyzico SUCCESS derse yinepaid'e döner. Para çekildiyse kredi mutlaka tanımlanır. - Tutar kontrolü:
paidPricesipariş tutarıyla eşleşmiyorsa kredi otomatik tanımlanmaz, log'a düşer.
İmza doğrulaması
iyzico yanıtlarında HMAC-SHA256 signature döner; alanlar : ile birleşir ve
fiyatlarda sondaki sıfırlar atılır (299.00 → 299). Tümü
src/lib/iyzico.ts içinde:
| Yer | Alan sırası |
|---|---|
initImzaDurumu |
conversationId:token |
retrieveImzaDurumu |
paymentStatus:paymentId:currency:basketId:conversationId:paidPrice:price:token |
webhookImzaDurumu (V3, HPP) |
HMAC(secret, secret + iyziEventType + iyziPaymentId + token + paymentConversationId + status) |
Karar kuralı: imza tutmuyorsa işlem reddedilir; imza alanı hiç yoksa (hesapta kapalıysa) akış sürer ve log'a uyarı düşer — retrieve zaten kimliği doğrulanmış sunucu-sunucu çağrısıdır, bu yüzden imza yokluğu ödemeyi bloklamaz.
Webhook kurulumu
Merchant Portal → Ayarlar → İşyeri Ayarları → İşyeri Bildirimleri →
https://<alan-adı>/api/odeme/webhook (HTTPS zorunlu).
X-IYZ-SIGNATURE-V3 başlığının gönderilmesi ayrıca aktifleştirilmelidir
(entegrasyon@iyzico.com). Aktif değilse başlık gelmez; route yine güvenlidir
çünkü durumu gövdeden değil retrieve'den okur.
iyzico 2xx alana kadar 15 dakika arayla 3 kez dener — bu yüzden işleyemediğimiz durumlarda bile 200 döneriz, yalnızca geçersiz imzada 401.
Test
Sandbox test kartları: https://docs.iyzico.com/en/add-ons/test-cards. Son kullanma tarihi gelecekte olmak kaydıyla SKT ve CVV serbesttir.
Dev panelinden (src/components/dev/dev-panel.tsx) iyzico'ya hiç gitmeden
"ödenmiş sahte sipariş" üretilebilir — sonuç ekranını denemek için.