All checks were successful
Deploy / deploy (push) Successful in 7m0s
Refactored the iyzico payment integration to improve the handling of payment callbacks and order status updates. Added signature validation for responses to ensure data integrity. Enhanced the `initializeCheckoutForm` function to include buyer's name and surname, and updated the order schema to enforce unique constraints on iyzico tokens. Improved error handling and logging for payment processing, ensuring better tracking of payment states and issues. Updated the app database to reflect these changes.
100 lines
5.1 KiB
Markdown
100 lines
5.1 KiB
Markdown
# iyzico ödeme entegrasyonu
|
||
|
||
Ödeme akışı iyzico **Checkout Form (CF)** üzerinden yürür: kart bilgisi hiçbir
|
||
zaman bize ulaşmaz, kullanıcı iyzico'nun barındırdığı ödeme sayfasına gider.
|
||
|
||
## 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` |
|
||
| `NEXT_PUBLIC_APP_URL` | callback ve webhook URL'lerinin kökü; **https ve geçerli SSL şart** |
|
||
|
||
`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ı.
|
||
|
||
> Lokal `http://localhost:3000` ile uçtan uca test edilemez: iyzico callback
|
||
> adresinden geçerli SSL ister. Sandbox testinde tünel (cloudflared/ngrok) açıp
|
||
> `NEXT_PUBLIC_APP_URL`'i o https adrese ayarla.
|
||
|
||
## Akış
|
||
|
||
1. `baslatOdeme` (`src/features/odeme/odeme-actions.ts`) — `orders` satırını
|
||
`pending` olarak yazar, `checkoutFormInitialize` çağırır, dönen `token`'ı
|
||
siparişe iliştirir ve kullanıcıyı `paymentPageUrl`'e yönlendirir.
|
||
`conversationId` = `basketId` = sipariş id'miz.
|
||
2. Kullanıcı ödemeyi bitirince iyzico `/api/odeme/callback` adresine
|
||
**cross-site POST** atar; gövdede yalnızca `token` vardır. SameSite=Lax
|
||
nedeniyle session çerezi gelmez, bu yüzden kullanıcı token'dan çözülür.
|
||
3. `odemeyiSonuclandir` (`src/lib/odeme.ts`) iyzico'ya `checkoutForm.retrieve`
|
||
ile sorar ve krediyi **idempotent** tanımlar. Kullanıcı 303 ile
|
||
`/odeme/sonuc?siparis=…` sayfasına düşer.
|
||
4. `/odeme/sonuc` self-healing'dir: sipariş hâlâ `pending` ise aynı fonksiyonu
|
||
tekrar çağırır (callback kaybolduysa kurtarır).
|
||
5. `/api/odeme/webhook` iyzico bildirimini karşılar — sekmesini kapatan ya da
|
||
fraud incelemesinde bekleyen ödemeler için yedek yol.
|
||
|
||
## Doğruluk kuralları (bunlara dokunurken dikkat)
|
||
|
||
- **Kredi yalnızca `retrieve` yanı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.
|
||
- **`paid` geçişi koşulludur** (`WHERE status != 'paid'`): eşzamanlı
|
||
callback + sayfa render'ı ikinci kez kredi yazamaz. `grantCredits`'teki
|
||
`UNIQUE(reason, ref_id)` ikinci katman güvencedir.
|
||
- **Ara durumlar `failed` damgalanmaz.** `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`**: `1` onaylı → kredi verilir. `0` incelemede → `pending`
|
||
bırakılır (çekim kesinleşmemiştir). `-1` reddedildi → `failed`.
|
||
- **Erken `failed` kurtarılabilir**: geçiş koşulu `status != 'paid'` olduğu için
|
||
yanlışlıkla `failed` damgalanmış bir sipariş, iyzico SUCCESS derse yine
|
||
`paid`'e döner. Para çekildiyse kredi mutlaka tanımlanır.
|
||
- **Tutar kontrolü**: `paidPrice` sipariş 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.
|
||
|
||
## Kaynaklar
|
||
|
||
- [CF-Initialize](https://docs.iyzico.com/en/payment-methods/checkoutform/cf-implementation/cf-initialize)
|
||
- [CF-Retrieve](https://docs.iyzico.com/en/payment-methods/checkoutform/cf-implementation/cf-retrieve)
|
||
- [Response Signature Validation](https://docs.iyzico.com/en/advanced/response-signature-validation)
|
||
- [Webhook](https://docs.iyzico.com/en/advanced/webhook)
|