Yeni·E-Ticaret Pro Paketi Yayında — Entegre Ödeme, Stok ve Sipariş YönetimiBlog·2025'te Küçük İşletmeler İçin Web Tasarım TrendleriKampanya·Mayıs Ayına Özel %20 İndirim — Kartvizit & Başlangıç Paketleriİçgörü·Müşteri Projelerinde Dönüşüm Oranı Ortalama %40 Artış SağlandıBlog·SEO'ya Yeni Başlayanlar İçin Temel Rehber — Ücretsiz İndirHaber·Ankara'da Yeni Çözüm Ortaklıkları ile Hizmet Ağı GenişliyorGüncelleme·Tüm Projeler İçin Ücretsiz SSL, CDN ve Hız Optimizasyonu DahilYeni·Çözümler Sayfası Açıldı — Sektöre Özel Web ÇözümleriKampanya·Ücretsiz Web Sitesi Değerlendirmesi — Bugün Başvurİçgörü·Ortalama Proje Teslim Süresi: 14 Gün — GarantiliYeni·E-Ticaret Pro Paketi Yayında — Entegre Ödeme, Stok ve Sipariş YönetimiBlog·2025'te Küçük İşletmeler İçin Web Tasarım TrendleriKampanya·Mayıs Ayına Özel %20 İndirim — Kartvizit & Başlangıç Paketleriİçgörü·Müşteri Projelerinde Dönüşüm Oranı Ortalama %40 Artış SağlandıBlog·SEO'ya Yeni Başlayanlar İçin Temel Rehber — Ücretsiz İndirHaber·Ankara'da Yeni Çözüm Ortaklıkları ile Hizmet Ağı GenişliyorGüncelleme·Tüm Projeler İçin Ücretsiz SSL, CDN ve Hız Optimizasyonu DahilYeni·Çözümler Sayfası Açıldı — Sektöre Özel Web ÇözümleriKampanya·Ücretsiz Web Sitesi Değerlendirmesi — Bugün Başvurİçgörü·Ortalama Proje Teslim Süresi: 14 Gün — Garantili
ilkkod
Türkiye Ödeme Sistemleri

Next.js ile İyzico Entegrasyonu: Checkout Form ve 3D Secure Rehberi

iyzipay npm paketi kurulumu, TypeScript tip tanımları, Checkout Form akışı, 3D Secure callback, webhook IPN doğrulama ve sub-merchant. Next.js 16 App Router ile tam entegrasyon rehberi.

İlker
22 Mart 2026
22 dk
Next.js ile İyzico Entegrasyonu: Checkout Form ve 3D Secure Rehberi

Önemli Not: Bu yazıdaki teknik bilgiler yazım tarihi itibarıyla geçerlidir. Kullanılan kütüphaneler, API'ler ve servisler zaman içinde değişebilir. Ücretlendirme, yasal düzenleme ve vergi konularında ilgili resmi kaynakları ve uzmanları referans alınız. Bu içerik bilgilendirme amaçlı olup herhangi bir finansal veya hukuki tavsiye niteliği taşımamaktadır.

İyzico, Türkiye'nin en yüksek işlem hacmine sahip ödeme altyapılarından biri. Sahibinden, Decathlon ve LetGo gibi büyük platformların güvendiği bu sistem, 1 Nisan 2025'te PayU tarafından satın alındı — 165 milyon dolarlık bu anlaşma Türkiye fintech tarihinin en büyük edinimleri arasında yer alıyor. Marka adı "iyzico" olarak devam ediyor.

Bu rehberde Next.js 16 App Router ile iyzico'yu adım adım entegre edeceğiz. Checkout Form (iframe) akışı, 3D Secure callback yönetimi, webhook (IPN) imza doğrulama ve sub-merchant kullanımını TypeScript ile ele alacağız.

Komisyon oranları ve güncel fiyatlandırma için iyzico'nun resmi sayfasını ziyaret edin — bu bilgiler değişkenlik gösterebilir ve burada yer verilmemiştir.


1. Ön Koşullar ve Sandbox Hesabı

Başlamadan önce şunlara ihtiyacınız var:

  • Node.js 18+ veya Bun (bu rehberde Bun kullanıyoruz)
  • Next.js 16 App Router projesi
  • İyzico sandbox hesabı

Sandbox hesabı açma:

  1. sandbox.iyzico.com adresine gidin
  2. Üye iş yeri kaydı oluşturun
  3. Dashboard'dan API Key ve Secret Key alın
# Sandbox endpoint'leri API: https://sandbox-api.iyzipay.com Panel: https://sandbox.iyzico.com

Production'a geçişte sadece uri parametresini https://api.iyzipay.com olarak değiştirmeniz yeterli.


2. iyzipay npm Paketi ve TypeScript Tip Sorunu

iyzico'nun resmi npm paketi iyzipay, Şubat 2026 itibarıyla v2.0.65 sürümündedir ve %100 JavaScript ile yazılmıştır — TypeScript tip tanımları içermez.

bun add iyzipay

Paketi kurar kurmaz TypeScript projenizde şu hatayı görürsünüz:

Could not find a declaration file for module 'iyzipay'.

@types/iyzipay de npm'de mevcut değil. Çözüm: kendi tip tanımlarınızı yazmak.

2.1 Tip Tanımları Oluşturma

Proje kökünde types/iyzipay.d.ts dosyası oluşturun:

// types/iyzipay.d.ts declare module "iyzipay" { interface IyzicoConfig { apiKey: string secretKey: string uri: string } interface Buyer { id: string name: string surname: string gsmNumber: string email: string identityNumber: string lastLoginDate?: string registrationDate?: string registrationAddress: string ip: string city: string country: string zipCode?: string } interface Address { contactName: string city: string country: string address: string zipCode?: string } interface BasketItem { id: string name: string category1: string category2?: string itemType: "PHYSICAL" | "VIRTUAL" price: string subMerchantKey?: string subMerchantPrice?: string } interface CheckoutFormInitializeRequest { locale?: string conversationId?: string price: string paidPrice: string currency: string basketId: string paymentGroup: "PRODUCT" | "LISTING" | "SUBSCRIPTION" callbackUrl: string enabledInstallments?: number[] buyer: Buyer shippingAddress: Address billingAddress: Address basketItems: BasketItem[] debitCardAllowed?: boolean } interface CheckoutFormRetrieveRequest { locale?: string conversationId?: string token: string } interface IyzicoResponse { status: "success" | "failure" errorCode?: string errorMessage?: string errorGroup?: string locale?: string systemTime?: number conversationId?: string } interface CheckoutFormInitializeResponse extends IyzicoResponse { checkoutFormContent?: string tokenExpireTime?: number paymentPageUrl?: string token?: string } interface CheckoutFormRetrieveResponse extends IyzicoResponse { paymentStatus?: string paymentId?: string fraudStatus?: number merchantCommissionRate?: number merchantCommissionRateAmount?: number iyziCommissionRateAmount?: number iyziCommissionFee?: number cardType?: string cardAssociation?: string cardFamily?: string binNumber?: string basketId?: string currency?: string itemTransactions?: ItemTransaction[] } interface ItemTransaction { itemId?: string paymentTransactionId?: string transactionStatus?: number price?: string paidPrice?: string merchantCommissionRate?: number merchantCommissionRateAmount?: number iyziCommissionRateAmount?: number iyziCommissionFee?: number blockageRate?: number blockageRateAmountMerchant?: number blockageRateAmountSubMerchant?: number blockageResolvedDate?: string subMerchantKey?: string subMerchantPrice?: string subMerchantPayoutRate?: number subMerchantPayoutAmount?: number merchantPayoutAmount?: number } class Iyzipay { constructor(config: IyzicoConfig) checkoutFormInitialize: { create( request: CheckoutFormInitializeRequest, callback: (err: Error | null, result: CheckoutFormInitializeResponse) => void ): void } checkoutFormResult: { retrieve( request: CheckoutFormRetrieveRequest, callback: (err: Error | null, result: CheckoutFormRetrieveResponse) => void ): void } } export = Iyzipay }

Bu tip tanımları temel akış için yeterli. Sub-merchant veya taksit API'si kullanırsanız ilgili tipleri ekleyebilirsiniz.

2.2 İyzico Client'ı Başlatma

// lib/iyzico.ts import Iyzipay from "iyzipay" import { env } from "@/lib/env" export const iyzico = new Iyzipay({ apiKey: env.IYZICO_API_KEY, secretKey: env.IYZICO_SECRET_KEY, uri: env.IYZICO_BASE_URL, // sandbox veya production })
# .env.local IYZICO_API_KEY=sandbox-... IYZICO_SECRET_KEY=sandbox-... IYZICO_BASE_URL=https://sandbox-api.iyzipay.com

Üretim ortamında IYZICO_BASE_URL değerini https://api.iyzipay.com olarak ayarlayın.


3. Checkout Form (CF) Akışı

İyzico'nun Checkout Form (iframe) yöntemi önerilen entegrasyon şeklidir. Kart bilgileri doğrudan iyzico'nun güvenli alanında işlenir; PCI uyumluluğu otomatik sağlanır.

Akış iki adımdan oluşur:

  1. CF-Initialize: Sunucu tarafında checkout form token'ı oluştur
  2. CF-Retrieve: Ödeme tamamlandıktan sonra sonucu çek

3.1 CF-Initialize: Token Oluşturma

// app/api/iyzico/checkout/route.ts import { type NextRequest, NextResponse } from "next/server" import { iyzico } from "@/lib/iyzico" import { env } from "@/lib/env" // Promise wrapper — iyzipay callback tabanlı çalışır function initializeCheckoutForm(request: Parameters<typeof iyzico.checkoutFormInitialize.create>[0]) { return new Promise<ReturnType<Parameters<typeof iyzico.checkoutFormInitialize.create>[1]> extends (err: any, result: infer R) => void ? R : never>((resolve, reject) => { iyzico.checkoutFormInitialize.create(request, (err, result) => { if (err) reject(err) else resolve(result as any) }) }) } export async function POST(req: NextRequest) { try { const body = await req.json() const { orderId, amount, customerEmail, basketItems } = body // Müşteri IP'sini al const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? req.headers.get("x-real-ip") ?? "85.34.78.112" // fallback (sandbox için) const request = { locale: "tr", conversationId: orderId, price: amount.toString(), paidPrice: amount.toString(), currency: "TRY", basketId: `BASKET_${orderId}`, paymentGroup: "PRODUCT" as const, callbackUrl: `${env.NEXT_PUBLIC_BASE_URL}/odeme/callback`, enabledInstallments: [2, 3, 6, 9, 12], buyer: { id: `BUYER_${orderId}`, name: body.firstName, surname: body.lastName, gsmNumber: body.phone, email: customerEmail, identityNumber: "74300864791", // TC Kimlik No (sandbox için sabit) registrationAddress: body.address, ip, city: body.city, country: "Turkey", zipCode: body.zipCode ?? "34000", }, shippingAddress: { contactName: `${body.firstName} ${body.lastName}`, city: body.city, country: "Turkey", address: body.address, }, billingAddress: { contactName: `${body.firstName} ${body.lastName}`, city: body.city, country: "Turkey", address: body.address, }, basketItems: basketItems.map((item: { id: string; name: string; price: number; type?: string }) => ({ id: item.id, name: item.name, category1: "Genel", itemType: (item.type === "digital" ? "VIRTUAL" : "PHYSICAL") as "PHYSICAL" | "VIRTUAL", price: item.price.toString(), })), } const result = await initializeCheckoutForm(request) if (result.status !== "success") { return NextResponse.json( { error: result.errorMessage ?? "Ödeme başlatılamadı" }, { status: 400 } ) } return NextResponse.json({ checkoutFormContent: result.checkoutFormContent, token: result.token, tokenExpireTime: result.tokenExpireTime, }) } catch (error) { console.error("İyzico CF Initialize error:", error) return NextResponse.json({ error: "Sunucu hatası" }, { status: 500 }) } }

3.2 Checkout Form Bileşeni (Client-Side)

// components/IyzicoCheckoutForm.tsx "use client" import { useEffect, useRef } from "react" interface IyzicoCheckoutFormProps { checkoutFormContent: string } export function IyzicoCheckoutForm({ checkoutFormContent }: IyzicoCheckoutFormProps) { const containerRef = useRef<HTMLDivElement>(null) useEffect(() => { if (!containerRef.current || !checkoutFormContent) return // iyzico'nun checkout form içeriğini DOM'a ekle containerRef.current.innerHTML = checkoutFormContent // Script tag'lerini çalıştır (innerHTML ile eklenen script'ler çalışmaz) const scripts = containerRef.current.querySelectorAll("script") scripts.forEach((oldScript) => { const newScript = document.createElement("script") Array.from(oldScript.attributes).forEach((attr) => { newScript.setAttribute(attr.name, attr.value) }) newScript.textContent = oldScript.textContent oldScript.parentNode?.replaceChild(newScript, oldScript) }) }, [checkoutFormContent]) return ( <div ref={containerRef} className="min-h-[400px] w-full" id="iyzipay-checkout-form" /> ) }

3.3 Ödeme Sayfası (Server Component)

// app/odeme/page.tsx import { IyzicoCheckoutForm } from "@/components/IyzicoCheckoutForm" async function getCheckoutForm(orderId: string) { const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/iyzico/checkout`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orderId, amount: "150.00", customerEmail: "test@example.com", firstName: "Ahmet", lastName: "Yılmaz", phone: "+905350000000", address: "Nispetiye Cad. No:1", city: "Istanbul", basketItems: [{ id: "URUN_001", name: "Ürün Adı", price: 150 }], }), cache: "no-store", }) return res.json() } export default async function OdemePage() { const { checkoutFormContent, error } = await getCheckoutForm("ORDER_123") if (error) { return <div className="text-red-500">Hata: {error}</div> } return ( <div className="mx-auto max-w-2xl px-4 py-8"> <h1 className="mb-6 text-2xl font-bold">Güvenli Ödeme</h1> <IyzicoCheckoutForm checkoutFormContent={checkoutFormContent} /> </div> ) }

4. CF-Retrieve: Ödeme Sonucunu Çekme

Ödeme tamamlandığında iyzico, callbackUrl'inize bir POST isteği gönderir. Bu isteğin body'sinde token bulunur. Bu token ile ödeme sonucunu sorgulamanız gerekir.

// app/odeme/callback/route.ts import { type NextRequest, NextResponse } from "next/server" import { iyzico } from "@/lib/iyzico" function retrieveCheckoutForm(token: string) { return new Promise<any>((resolve, reject) => { iyzico.checkoutFormResult.retrieve( { locale: "tr", token }, (err, result) => { if (err) reject(err) else resolve(result) } ) }) } export async function POST(req: NextRequest) { try { const formData = await req.formData() const token = formData.get("token") as string if (!token) { return NextResponse.redirect(new URL("/odeme/hata?reason=no-token", req.url)) } const result = await retrieveCheckoutForm(token) if (result.status !== "success" || result.paymentStatus !== "SUCCESS") { console.error("Ödeme başarısız:", result.errorMessage) return NextResponse.redirect( new URL(`/odeme/hata?reason=${result.errorCode ?? "unknown"}`, req.url) ) } // Ödeme başarılı — veritabanında siparişi güncelle const orderId = result.conversationId await updateOrderStatus(orderId, "PAID", result.paymentId) return NextResponse.redirect(new URL(`/odeme/basarili?order=${orderId}`, req.url)) } catch (error) { console.error("CF Retrieve error:", error) return NextResponse.redirect(new URL("/odeme/hata?reason=server-error", req.url)) } } async function updateOrderStatus(orderId: string, status: string, paymentId?: string) { // Drizzle ORM ile veritabanı güncelleme — kendi implementasyonunuza göre uyarlayın console.log(`Sipariş güncellendi: ${orderId}${status} (paymentId: ${paymentId})`) }

Önemli: iyzico callback'i bir form POST olarak gönderir, JSON değil. Bu yüzden req.formData() kullanıyoruz.


5. 3D Secure Akışı (Direct API)

Checkout Form yöntemi 3DS'i otomatik yönetir. Ancak kart bilgilerini kendiniz topladığınız direkt API yöntemini kullanıyorsanız 3DS akışını manuel olarak yönetmeniz gerekir.

Türkiye'de tüm banka kartları için 3DS pratikte zorunludur — tüm bankalar SMS OTP (3DS v1) uygular.

5.1 3DS Akışı — 6 Adım

  1. BIN Kontrolü: Kart tipini ve 3DS zorunluluğunu sorgula
  2. 3DS Başlat: Kart + alıcı bilgileri + callbackUrl ile POST
  3. HTML Decode: Yanıttaki Base64 HTML içeriğini decode edip kullanıcıya render et
  4. OTP Girişi: Kullanıcı SMS kodunu girer → banka otomatik callbackUrl'e yönlendirir
  5. 3DS Doğrula: Callback'ten gelen paymentId ile sonucu onayla
  6. Webhook Bildirimi: IPN sistemi ek bildirim gönderir

Callback URL'den gelen alanlar:

status          — success / failure
paymentId       — iyzico ödeme ID'si
mdStatus        — 1: başarılı 3DS, 0-8: çeşitli hatalar
conversationId  — kendi sipariş ID'niz
conversationData — ek veri

mdStatus değerlerini yorumlamak kritik:

mdStatusAçıklama
1Tam 3DS doğrulama — devam et
2, 3, 4Kısmi doğrulama — riske göre karar ver
0, 5-8Başarısız — ödemeyi reddet

6. Webhook (IPN) Doğrulama

İyzico, ödeme olaylarını webhook ile bildirir. Header: X-IYZ-SIGNATURE-V3

Not: Eski X-IYZ-SIGNATURE header'ı deprecated. V3 kullanın.

6.1 İmza Doğrulama

// app/api/iyzico/webhook/route.ts import { type NextRequest, NextResponse } from "next/server" import { createHmac } from "node:crypto" import { env } from "@/lib/env" function verifyIyzicoSignature( secretKey: string, payload: Record<string, string>, receivedSignature: string ): boolean { // Direct API için alan sırası: // secretKey + iyziEventType + paymentId + paymentConversationId + status const dataToSign = [ secretKey, payload.iyziEventType, payload.paymentId, payload.paymentConversationId, payload.status, ] .filter(Boolean) .join("") const expectedSignature = createHmac("sha256", secretKey) .update(dataToSign) .digest("hex") // Timing-safe karşılaştırma const receivedBuffer = Buffer.from(receivedSignature, "hex") const expectedBuffer = Buffer.from(expectedSignature, "hex") if (receivedBuffer.length !== expectedBuffer.length) return false return ( Buffer.compare( Buffer.from(createHmac("sha256", secretKey).update(receivedSignature).digest("hex"), "hex"), Buffer.from(createHmac("sha256", secretKey).update(expectedSignature).digest("hex"), "hex") ) === 0 ) } export async function POST(req: NextRequest) { const signature = req.headers.get("x-iyz-signature-v3") if (!signature) { return NextResponse.json({ error: "Signature eksik" }, { status: 401 }) } const body = await req.json() const isValid = verifyIyzicoSignature(env.IYZICO_SECRET_KEY, body, signature) if (!isValid) { console.error("Geçersiz iyzico webhook imzası") return NextResponse.json({ error: "Geçersiz imza" }, { status: 401 }) } const { iyziEventType, paymentId, status } = body switch (iyziEventType) { case "CHECKOUT_FORM_AUTH": case "PAYMENT_API": if (status === "SUCCESS") { await handleSuccessfulPayment(paymentId) } else { await handleFailedPayment(paymentId) } break case "THREE_DS_AUTH": case "THREE_DS_CALLBACK": // 3DS olayları — gerekirse işle break default: console.log(`Bilinmeyen iyzico event: ${iyziEventType}`) } // iyzico 200 bekler; aksi hâlde 10 dakikada bir, max 3 kez yeniden dener return NextResponse.json({ status: "ok" }) } async function handleSuccessfulPayment(paymentId: string) { console.log(`Başarılı ödeme işlendi: ${paymentId}`) } async function handleFailedPayment(paymentId: string) { console.log(`Başarısız ödeme kaydedildi: ${paymentId}`) }

Yeniden deneme politikası: İyzico webhook'u başarısız olursa 10 dakikada bir tekrar dener, maksimum 3 deneme. Endpoint'iniz 200 dönmezse bu süreç tetiklenir.

Olay tipleri: PAYMENT_API, API_AUTH, THREE_DS_AUTH, THREE_DS_CALLBACK, CHECKOUT_FORM_AUTH, BANK_TRANSFER_AUTH


7. Sub-Merchant (Marketplace Bölünmüş Ödeme)

Birden fazla satıcının yer aldığı marketplace projelerinde sub-merchant özelliğini kullanabilirsiniz.

7.1 Sub-Merchant Oluşturma

// app/api/iyzico/submerchant/route.ts import { type NextRequest, NextResponse } from "next/server" export async function POST(req: NextRequest) { const { name, email, address, taxNumber, taxOffice, legalCompanyTitle } = await req.json() // İyzico sub-merchant API'si henüz resmi SDK tiplerinde yok // Doğrudan HTTP isteği kullanıyoruz const payload = { locale: "tr", conversationId: `SM_${Date.now()}`, subMerchantExternalId: `MERCHANT_${Date.now()}`, subMerchantType: "LIMITED_OR_JOINT_STOCK_COMPANY", // PERSONAL | PRIVATE_COMPANY | LIMITED_OR_JOINT_STOCK_COMPANY address, taxOffice, taxNumber, legalCompanyTitle, email, iban: "TR630006200119000006672315", // Sandbox IBAN currency: "TRY", } const authHeader = Buffer.from( `${process.env.IYZICO_API_KEY}:${process.env.IYZICO_SECRET_KEY}` ).toString("base64") const response = await fetch(`${process.env.IYZICO_BASE_URL}/onboarding/submerchant`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${authHeader}`, }, body: JSON.stringify(payload), }) const result = await response.json() return NextResponse.json(result) }

7.2 Ödeme Sırasında Sub-Merchant Kullanımı

Basket item'larında subMerchantKey ve subMerchantPrice ekleyin:

basketItems: [ { id: "URUN_001", name: "Satıcı A - Ürün", category1: "Elektronik", itemType: "PHYSICAL", price: "80.00", subMerchantKey: "sub-merchant-key-satici-a", // onboarding'den dönen key subMerchantPrice: "72.00", // satıcıya ödenecek tutar (komisyon düşülmüş) }, { id: "URUN_002", name: "Satıcı B - Ürün", category1: "Giyim", itemType: "PHYSICAL", price: "70.00", subMerchantKey: "sub-merchant-key-satici-b", subMerchantPrice: "63.00", }, ],

8. Test Kartları

Sandbox ortamında şu kartları kullanın:

Kart NoTürSenaryo
4766620000000001VisaBaşarılı ödeme
5528790000000008MasterCardBaşarılı ödeme
374427000000003AmExBaşarılı ödeme
4111111111111129Yetersiz bakiye
4125111111111115Süresi dolmuş kart

Tüm test kartları için son kullanma tarihi olarak gelecekte bir ay/yıl, CVV olarak 123 kullanabilirsiniz. 3DS testinde sandbox otomatik olarak başarı/hata senaryolarını simüle eder.


9. Ortam Değişkenleri ve Tip Güvenliği

lib/env.ts'e iyzico değişkenlerini ekleyin:

// lib/env.ts (ilgili kısım) import { z } from "zod/v4" const envSchema = z.object({ // ... mevcut değişkenler // İyzico IYZICO_API_KEY: z.string().min(1), IYZICO_SECRET_KEY: z.string().min(1), IYZICO_BASE_URL: z.string().url().default("https://sandbox-api.iyzipay.com"), })

10. Sık Hatalar ve Çözümleri

Hata: "conversationId is already used"

Her ödeme girişimi için benzersiz bir conversationId kullanın. Aynı ID ile ikinci deneme yapılırsa bu hatayı alırsınız.

conversationId: `ORDER_${orderId}_${Date.now()}`

Hata: Callback URL'ye POST gelmiyor

İyzico callback URL'sine erişebilmesi için yerel geliştirmede ngrok veya benzeri bir tünel kullanın:

ngrok http 3000 # Çıkan URL'yi callbackUrl olarak kullanın: # https://abc123.ngrok.io/odeme/callback

Hata: "basketItemPrice must be equal to price"

basketItems içindeki tüm price değerlerinin toplamı, form üst seviyedeki price değerine tam olarak eşit olmalıdır. Ondalık hassasiyetine dikkat edin.

// Yanlış: kayan nokta hatası riski const total = items.reduce((sum, item) => sum + item.price, 0) // Doğru: Tamsayı üzerinden hesapla, sonra string'e çevir const totalKurus = items.reduce((sum, item) => sum + Math.round(item.price * 100), 0) const total = (totalKurus / 100).toFixed(2)

Hata: TypeScript tip hataları

types/iyzipay.d.ts dosyasının tsconfig.json'daki include listesinde olduğundan emin olun:

{ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types/**/*.d.ts"] }

Hata: "token is expired"

tokenExpireTime süresi dolmadan kullanıcı formu tamamlamazsa token geçersiz hâle gelir. Kullanıcıya yeni bir token oluşturma seçeneği sunun.

Webhook Doğrulaması Başarısız Oluyor

İmza doğrulamasında alan sırası kritik. Direct API ve Checkout Form için imza hesaplamaları farklıdır. Resmi iyzico dokümantasyonundan kullandığınız entegrasyon tipine uygun sırayı kontrol edin.


11. KVKK ve Güvenlik Notları

  • Kart bilgileri hiçbir zaman kendi sunucunuzdan geçmez; Checkout Form yönteminde kart verileri doğrudan iyzico'ya gönderilir
  • Buyer identityNumber (TC Kimlik No) kişisel veri olarak işlenir — KVKK kapsamında veri işleme politikanızı güncellemeniz gerekebilir
  • IP adresi iyzico'ya gönderilir; aydınlatma metninde belirtin
  • Production'da secretKey ve apiKey yalnızca sunucu tarafında kullanılmalı, client-side bundle'a kesinlikle dahil edilmemeli

12. Production Geçiş Kontrol Listesi

Canlıya geçmeden önce şunları tamamlayın:

  • IYZICO_BASE_URL'i https://api.iyzipay.com olarak güncelle
  • Sandbox apiKey / secretKey yerine production kimlik bilgilerini gir
  • Callback URL'lerin production domain'ine işaret ettiğini doğrula
  • Webhook endpoint'inin gerçek imzaları doğruladığını test et
  • Test siparişi ile uçtan uca ödeme akışını doğrula
  • Başarısız ödeme ve hata durumlarını test et
  • conversationId benzersizliğini garanti altına al (UUID veya timestamp ile)
  • Ödeme loglarını güvenli şekilde sakla (kart numarası asla loglanmamalı)
  • İyzico merchant panelinizde webhook URL'ini production endpoint olarak ayarla
  • Rate limiting ve hata yönetimi endpoint'lerine uygulanmış olmalı

Sıkça Sorulan Sorular

iyzipay paketi yerine fetch ile direkt API kullanabilir miyim?

Evet. iyzipay paketi temel olarak HMAC imzalama ve HTTP isteklerini yönetir. Kendi HTTP client'ınızı yazabilirsiniz. Ancak imzalama mantığını doğru uygulamak kritiktir; bu nedenle resmi SDK genellikle tercih edilir.

Checkout Form ile 3DS akışı arasındaki fark nedir?

Checkout Form (iframe) yönteminde iyzico 3DS'i otomatik yönetir — siz müdahale etmezsiniz. Direkt API yönteminde ise 3DS adımlarını kendiniz orkestrasyonlarsınız. Çoğu proje için Checkout Form yeterledir.

price ve paidPrice arasındaki fark ne?

price ürünlerin gerçek toplam fiyatı, paidPrice ise müşterinin ödeyeceği nihai tutardır. Taksit faizi veya indirim uygulandığında bu iki değer farklılaşabilir.

Yerel geliştirmede callback nasıl test edilir?

ngrok http 3000 komutuyla tünel açın ve çıkan URL'yi callbackUrl olarak kullanın. Vercel önizleme URL'leri de bu amaçla kullanılabilir.

iyzico'nun PayU tarafından satın alınması entegrasyonu etkiler mi?

Teknik API ve SDK açısından şu an için herhangi bir değişiklik yok. Marka "iyzico" olarak devam ediyor. Gelecekteki değişiklikler için resmi iyzico duyurularını takip edin.

Komisyon oranları hakkında bilgi nereden alırım?

Güncel komisyon oranları, sektöre ve işlem hacminize göre belirlenir ve değişkenlik gösterir. Detaylar için iyzico.com üzerinden iyzico ile doğrudan iletişime geçin.


Sonuç

Next.js 16 App Router ile iyzico entegrasyonu birkaç temel bileşenden oluşuyor: TypeScript tip tanımları (SDK'da eksik olduğu için kendiniz yazmanız gerekiyor), CF-Initialize ve CF-Retrieve çifti ile oluşan checkout akışı, ve webhook IPN doğrulaması.

Bu rehberde incelediğimiz temel başlıklar:

  • iyzipay için TypeScript declare module tip tanımları
  • Checkout Form token oluşturma ve iframe render
  • CF callback ve ödeme sonucu sorgulama
  • Webhook imza doğrulama (HMAC-SHA256, X-IYZ-SIGNATURE-V3)
  • Sub-merchant yapısıyla marketplace bölünmüş ödemeleri
  • Sandbox test kartları ve sık karşılaşılan hatalar

E-ticaret projeniz için profesyonel ödeme entegrasyonu ve Next.js geliştirme konusunda destek almak isterseniz iletişime geçin ya da proje teklif formumuzu doldurun.

Paylaş: