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 PayTR Ödeme Entegrasyonu: iframe ve Webhook Rehberi (App Router)

Next.js 16 App Router ile PayTR iframe entegrasyonu: HMAC-SHA256 token üretimi, postback webhook doğrulama ve çalışan TypeScript kod örnekleri. Test kartları ve sık hatalar dahil.

İlker
18 Mart 2026
20 dk
Next.js ile PayTR Ödeme Entegrasyonu: iframe ve Webhook Rehberi (App Router)

Ö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.

Türkiye'deki e-ticaret projelerinde en sık sorulan sorulardan biri: "PayTR'ı Next.js'e nasıl entegre ederim?" Geliştirici forumlarında bu soruya doyurucu bir yanıt bulmak neredeyse imkânsız. Bu rehberde Next.js 16 App Router kullanarak PayTR iframe entegrasyonunu adım adım, çalışan TypeScript kod örnekleriyle aktarıyoruz.

Bu projede de PayTR kullandık — rehberdeki kod örnekleri gerçek production deneyiminden gelmektedir.


PayTR Nedir?

PayTR, Türkiye'de en yaygın kullanılan KOBİ ödeme altyapılarından biridir. TCMB (Türkiye Cumhuriyet Merkez Bankası) tarafından lisanslı bir ödeme kuruluşudur. 2019'da 6493 sayılı Kanun kapsamında ödeme kuruluşu denetimi BDDK'dan TCMB'ye geçtiğinden "BDDK lisanslı" ifadesi doğru değildir.

PayTR'ı öne çıkaran başlıca özellikler:

  • Türk bankaları ile doğrudan entegrasyon
  • iframe tabanlı ödeme formu (PCI uyumlu — kart verisi merchant sunucusuna taşınmaz)
  • 3D Secure desteği (Türkiye'deki bankalar için pratikte zorunludur)
  • Taksit desteği (2–12 taksit)
  • Tekrarlayan ödeme / abonelik desteği (ayrı Sanal POS başvurusu gerektirir)
  • Test modu (canlı hesap kimlik bilgileriyle test_mode=1 parametresi)
  • Çoklu döviz desteği (TL, EUR, USD, GBP, RUB)

Güncel komisyon oranları ve fiyatlandırma bilgileri için paytr.com adresini ziyaret edin — oranlar merchant hacmine ve sektöre göre özel belirlenir.


Başlamadan Önce: Gereksinimler

  • PayTR merchant hesabı (paytr.com üzerinden başvuru)
  • Merchant ID, Merchant Key, Merchant Salt (PayTR paneli → Hesabım → Geliştirici Bilgileri)
  • Next.js 16 projesi
  • Bun paket yöneticisi

Sandbox testleri için ayrı bir hesaba gerek yok — canlı hesap kimlik bilgilerinizle test_mode=1 parametresini kullanabilirsiniz.


Entegrasyon Mimarisi

PayTR iframe entegrasyonu iki ana adımdan oluşur:

Step 1 — Token Alma: Müşteri ödeme sayfasına geldiğinde, sunucu tarafında PayTR API'sine POST isteği atılır ve tek kullanımlık bir token alınır. Token, iframe'i render etmek için kullanılır.

Step 2 — Postback (Webhook): Müşteri ödemeyi tamamladığında (veya başarısız olduğunda), PayTR belirlediğiniz Callback URL'ye POST atar. Sipariş durumu bu noktada güncellenir.

Müşteri → /sepet/odeme [Server Component]
  → POST /api/paytr/token [Route Handler, server-side]
      → PayTR API https://www.paytr.com/odeme/api/get-token
      ← token
  → <PaytrIframe token={token} /> [Client Component]
      → PayTR iframe (güvenli ödeme formu)

PayTR → POST /api/paytr/callback [Route Handler]
  → Hash doğrula
  → Sipariş durumunu güncelle (Drizzle ORM)
  → "OK" döndür

Ortam Değişkenleri

.env.local dosyanıza ekleyin:

PAYTR_MERCHANT_ID=123456 PAYTR_MERCHANT_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx PAYTR_MERCHANT_SALT=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx NEXT_PUBLIC_BASE_URL=https://siteniz.com

Güvenlik: Bu değerleri asla client-side koda aktarmayın. PAYTR_MERCHANT_KEY ve PAYTR_MERCHANT_SALT yalnızca Route Handler'lar ve Server Components'da kullanılmalıdır.


Step 1: Token Endpoint'i

Token isteği sunucu tarafında gerçekleşmelidir. Client'dan doğrudan PayTR API'sine istek atmak güvenlik açığı yaratır çünkü merchant_key ve merchant_salt ifşa olur.

app/api/paytr/token/route.ts

import crypto from "node:crypto" import { type NextRequest, NextResponse } from "next/server" interface TokenRequestBody { orderId: string email: string amount: number // TL cinsinden, ondalıklı (örn: 299.90) basket: Array<{ name: string price: string // "299.90" formatında string quantity: number }> userIp?: string } export async function POST(request: NextRequest) { try { const body = (await request.json()) as TokenRequestBody const merchantId = process.env.PAYTR_MERCHANT_ID! const merchantKey = process.env.PAYTR_MERCHANT_KEY! const merchantSalt = process.env.PAYTR_MERCHANT_SALT! // payment_amount: TL × 100, tam sayı (örn: 299.90 TL → 29990) const paymentAmount = Math.round(body.amount * 100).toString() // user_basket: Base64 kodlanmış JSON array // Format: [[isim, fiyat_string, adet], ...] const basketItems = body.basket.map((item) => [ item.name, item.price, item.quantity, ]) const userBasket = Buffer.from(JSON.stringify(basketItems)).toString("base64") // Müşterinin gerçek harici IP'si (proxy arkasında X-Forwarded-For kullanın) const userIp = body.userIp ?? request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "127.0.0.1" // merchant_oid: yalnızca alfanümerik, max 64 karakter const merchantOid = body.orderId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 64) const noInstallment = "0" // 0 = taksit göster, 1 = taksitsiz zorunlu const maxInstallment = "0" // 0 = tüm taksitler, 1–12 = maksimum taksit sayısı const currency = "TL" const testMode = process.env.NODE_ENV === "production" ? "0" : "1" // ─── HMAC-SHA256 Token Formülü ─────────────────────────────────────── // Sıra KRİTİKTİR — ayraç kullanılmaz, doğrudan birleştirin const hashString = [ merchantId, userIp, merchantOid, body.email, paymentAmount, userBasket, noInstallment, maxInstallment, currency, testMode, merchantSalt, // ← merchant_salt hash'e giriyor ].join("") // merchant_KEY ile imzalıyoruz (merchant_salt değil) const paytrToken = crypto .createHmac("sha256", merchantKey) .update(hashString) .digest("base64") // ────────────────────────────────────────────────────────────────────── const params = new URLSearchParams({ merchant_id: merchantId, user_ip: userIp, merchant_oid: merchantOid, email: body.email, payment_amount: paymentAmount, paytr_token: paytrToken, user_basket: userBasket, debug_on: process.env.NODE_ENV === "production" ? "0" : "1", no_installment: noInstallment, max_installment: maxInstallment, user_name: "", user_address: "", user_phone: "", merchant_ok_url: `${process.env.NEXT_PUBLIC_BASE_URL}/odeme/basarili`, merchant_fail_url: `${process.env.NEXT_PUBLIC_BASE_URL}/odeme/basarisiz`, timeout_limit: "30", currency, test_mode: testMode, lang: "tr", }) const response = await fetch("https://www.paytr.com/odeme/api/get-token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString(), }) const result = (await response.json()) as { status: string token?: string reason?: string } if (result.status !== "success") { console.error("PayTR token hatası:", result.reason) return NextResponse.json( { error: "Token alınamadı", reason: result.reason }, { status: 400 }, ) } return NextResponse.json({ token: result.token }) } catch (error) { console.error("PayTR token endpoint hatası:", error) return NextResponse.json({ error: "Sunucu hatası" }, { status: 500 }) } }

Temel parametreler:

ParametreAçıklama
merchant_oidSiparişinizin benzersiz ID'si (max 64 alfanümerik karakter)
payment_amountTL × 100, tam sayı (örn: 10.50 TL → 1050)
user_basketBase64 kodlanmış JSON: [[isim, fiyat, adet], ...]
paytr_tokenHMAC-SHA256 imzası — alan sırası kritiktir
test_mode"1" = sandbox, "0" = canlı
no_installment"1" = taksit seçeneği gösterme
currencyTL, EUR, USD, GBP, RUB

Step 2: PayTR iframe Bileşeni

Token alındıktan sonra PayTR'ın iframe'ini render edin. PayTR, yüksekliği otomatik ayarlamak için postMessage kullanır.

components/paytr-iframe.tsx

"use client" import { useEffect, useRef } from "react" interface PaytrIframeProps { token: string } export function PaytrIframe({ token }: PaytrIframeProps) { const iframeRef = useRef<HTMLIFrameElement>(null) useEffect(() => { function handleMessage(event: MessageEvent) { // Yalnızca PayTR kaynaklı mesajları işle if (event.origin !== "https://www.paytr.com") return const data = event.data as { height?: number } if (data.height && iframeRef.current) { iframeRef.current.style.height = `${data.height}px` } } window.addEventListener("message", handleMessage) return () => window.removeEventListener("message", handleMessage) }, []) return ( <iframe ref={iframeRef} src={`https://www.paytr.com/odeme/guvenli/${token}`} id="paytriframe" title="PayTR Güvenli Ödeme" frameBorder="0" scrolling="no" style={{ width: "100%", height: "600px" }} allowFullScreen /> ) }

Ödeme Sayfası (Server Component)

// app/sepet/odeme/page.tsx import { PaytrIframe } from "@/components/paytr-iframe" interface OdemePageProps { searchParams: Promise<{ orderId?: string }> } export default async function OdemePage({ searchParams }: OdemePageProps) { const { orderId } = await searchParams if (!orderId) { return <div>Geçersiz sipariş.</div> } // Gerçek uygulamada siparişi veritabanından çekin: // const order = await db.query.orders.findFirst({ // where: eq(orders.id, orderId), // }) const tokenResponse = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/paytr/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orderId, email: "musteri@ornek.com", // session'dan alın amount: 299.0, basket: [{ name: "Ürün Adı", price: "299.00", quantity: 1 }], }), }, ) const { token, error } = (await tokenResponse.json()) as { token?: string error?: string } if (error || !token) { return <div>Ödeme başlatılamadı: {error}</div> } return ( <div className="mx-auto max-w-2xl px-4 py-8"> <h1 className="mb-6 font-bold text-2xl">Güvenli Ödeme</h1> <PaytrIframe token={token} /> </div> ) }

Step 3: Postback (Webhook) Endpoint'i

PayTR, ödeme sonucunu Callback URL'ye bildirir. Bu endpoint'in düzgün çalışması kritiktir — aksi takdirde siparişler "In Progress" durumunda kalır.

app/api/paytr/callback/route.ts

import crypto from "node:crypto" import { type NextRequest, NextResponse } from "next/server" export async function POST(request: NextRequest) { try { const formData = await request.formData() const merchantOid = formData.get("merchant_oid") as string const status = formData.get("status") as "success" | "failed" const totalAmount = formData.get("total_amount") as string const hash = formData.get("hash") as string const failedReasonCode = formData.get("failed_reason_code") as string | null const failedReasonMsg = formData.get("failed_reason_msg") as string | null const paymentType = formData.get("payment_type") as string const currency = formData.get("currency") as string const merchantKey = process.env.PAYTR_MERCHANT_KEY! const merchantSalt = process.env.PAYTR_MERCHANT_SALT! // ─── Postback Hash Doğrulama ────────────────────────────────────────── // Formül: HMAC-SHA256(merchant_oid + merchant_salt + status + total_amount, merchant_key) const hashString = merchantOid + merchantSalt + status + totalAmount const expectedHash = crypto .createHmac("sha256", merchantKey) .update(hashString) .digest("base64") // ────────────────────────────────────────────────────────────────────── if (expectedHash !== hash) { console.error("PayTR postback: hash uyuşmazlığı! Muhtemel replay saldırısı.") // Loglayın ama yine de "OK" dönün; aksi takdirde PayTR tekrar dener return new Response("OK", { status: 200, headers: { "Content-Type": "text/plain" } }) } if (status === "success") { // Sipariş durumunu güncelle — Drizzle ORM örneği: // await db // .update(orders) // .set({ status: "paid", paidAt: new Date(), paymentType }) // .where(eq(orders.paytrOid, merchantOid)) console.log(`Ödeme başarılı: ${merchantOid}, Tür: ${paymentType}, Döviz: ${currency}`) } else { // await db // .update(orders) // .set({ status: "failed", failReason: failedReasonMsg }) // .where(eq(orders.paytrOid, merchantOid)) console.log( `Ödeme başarısız: ${merchantOid}, Kod: ${failedReasonCode}, Mesaj: ${failedReasonMsg}`, ) } // PayTR'ın beklediği yanıt: düz metin "OK" // Bu döndürülmezse PayTR callback'i tekrar dener return new Response("OK", { status: 200, headers: { "Content-Type": "text/plain" }, }) } catch (error) { console.error("PayTR callback hatası:", error) return new Response("OK", { status: 200 }) } }

Callback URL'yi PayTR paneline kaydedin: Hesabım → Geliştirici Bilgileri → Callback URL.

Yerel geliştirme notu: PayTR, localhost adreslerine ulaşamaz. Geliştirme sırasında ngrok veya Cloudflare Tunnel kullanın:

ngrok http 3000 # Üretilen HTTPS URL'yi PayTR paneline Callback URL olarak kaydedin

Postback'te gelen alanlar

AlanDeğerAçıklama
merchant_oidstringSipariş ID'niz
statussuccess / failedÖdeme sonucu
total_amountstringTahsil edilen tutar (kuruş cinsinden)
hashstringDoğrulama hash'i
failed_reason_codestringHata kodu (başarısız ödemelerde)
failed_reason_msgstringHata açıklaması
payment_typestringcard, eft vb.
test_mode0 / 1Sandbox mi?

İade (Refund) API'si

// lib/paytr.ts — yardımcı fonksiyon import crypto from "node:crypto" interface RefundParams { merchantOid: string returnAmount: number // TL cinsinden (örn: 49.90) } interface RefundResult { status: "success" | "failed" is_test: string merchant_oid: string return_amount: string reference_no: string err_no: string err_msg: string } export async function paytrRefund(params: RefundParams): Promise<RefundResult> { const merchantId = process.env.PAYTR_MERCHANT_ID! const merchantKey = process.env.PAYTR_MERCHANT_KEY! const merchantSalt = process.env.PAYTR_MERCHANT_SALT! // Nokta ayraçlı decimal string (örn: "49.90") const returnAmount = params.returnAmount.toFixed(2) // İade HMAC: merchant_id + merchant_oid + return_amount + merchant_salt const hashString = merchantId + params.merchantOid + returnAmount + merchantSalt const paytrToken = crypto .createHmac("sha256", merchantKey) .update(hashString) .digest("base64") const requestParams = new URLSearchParams({ merchant_id: merchantId, merchant_oid: params.merchantOid, return_amount: returnAmount, paytr_token: paytrToken, }) const response = await fetch("https://www.paytr.com/odeme/iade", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: requestParams.toString(), }) return (await response.json()) as RefundResult }

İade hata kodları:

KodAnlam
001Geçersiz merchant bilgileri
004Token eksik veya hatalı
005Bu sipariş için başarılı ödeme bulunamadı
010Yetersiz bakiye (kısmi iade söz konusu olabilir)
0111 yıldan eski işlem iade edilemez

Taksit Seçenekleri

Token isteğinde iki parametre taksit davranışını yönetir:

no_installment: "0", // 0 = taksit seçeneklerini göster, 1 = taksitsiz satışa zorla max_installment: "0", // 0 = tüm seçenekler, 2–12 = en fazla kaç taksit

Hangi bankaların hangi taksit seçeneklerini sunduğu PayTR panelinden yapılandırılır. 3D Secure Türkiye'de tüm kartlı işlemlerde pratikte zorunludur.


Test Kartları

Sandbox testleri için test_mode=1 ile şu kartları kullanabilirsiniz:

Kart NoİsimSon KullanmaCVVTür
4355 0843 5508 4358PAYTR TEST12/30000Visa
5406 6754 0667 5403PAYTR TEST12/30000MasterCard
9792 0303 9444 0796PAYTR TEST12/30000Troy

3D Secure ekranında herhangi bir OTP girebilirsiniz — test modunda doğrulama yapılmaz.


npm Paket Seçimi

PayTR'ın resmi bir npm paketi yoktur. Topluluk alternatifleri:

PaketTypeScriptDurum
paytr (themisir/paytr-js)~%99 TSAktif bakımlı; iframe token + basket
node-paytrJavaScript12+ aydır güncelleme yok

Bu rehberdeki yaklaşım gibi node:crypto modülüyle kendi implementasyonunuzu yazmak da geçerli bir seçenek — ~50 satır kod ile ekstra bağımlılık olmadan tam kontrol sağlar.


Sık Hatalar ve Çözümleri

1. "In Progress" Durumu

Sorun: Sipariş PayTR panelinde "In Progress" görünüyor, postback gelmiyor.

Çözüm: Callback URL'nizin OK döndürdüğünü doğrulayın. PayTR Paneli → İşlemler → Detay → Callback Log bölümünden geçmişi inceleyin.

2. Hash Uyuşmazlığı

Sorun: Token isteği veya postback hash doğrulaması başarısız.

Çözüm:

  • Alan sırasını kontrol edin — her iki hash için sıra kritiktir
  • merchant_salt ile merchant_key'i karıştırmayın: salt birleştirme string'ine girer, key HMAC imzasında kullanılır
  • Token isteğinde debug_on=1 ile detaylı hata mesajı alın

3. Minimum Tutar Hatası

Sorun: Token isteği reddediliyor.

Çözüm: payment_amount en az 100 olmalıdır (1 TL = 100 kuruş). 0 veya çok küçük değerler geçersizdir.

4. user_ip Sorunu

Sorun: Geçersiz IP adresi hatası.

Çözüm: user_ip müşterinin gerçek harici IP'si olmalıdır. Vercel gibi proxy ortamlarında:

const userIp = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "127.0.0.1"

127.0.0.1 canlı ortamda çalışmaz.

5. Yerel Geliştirme Callback Sorunu

Sorun: Postback callback hiç gelmiyor.

Çözüm: PayTR, local URL'lere ulaşamaz. ngrok veya Cloudflare Tunnel ile geçici public URL oluşturun:

# ngrok ngrok http 3000 # Cloudflare Tunnel (ücretsiz, kalıcı) cloudflared tunnel --url http://localhost:3000

Üretilen HTTPS URL'yi PayTR panelinde Callback URL olarak kaydedin.

6. merchant_oid Format Hatası

Sorun: Token isteği reddediliyor, merchant_oid hatası.

Çözüm: merchant_oid yalnızca alfanümerik karakterler içermeli (tire, nokta, boşluk geçersiz), maksimum 64 karakter. UUID kullanıyorsanız tireleri kaldırın:

const merchantOid = orderId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 64)

Tekrarlayan Ödeme (Abonelik)

Tekrarlayan ödeme için standart iframe Sanal POS'unuzdan ayrı bir Sanal POS başvurusu gerekir. Onaylandıktan sonra https://www.paytr.com/odeme endpoint'ine şu parametrelerle istek atabilirsiniz:

// Kritik parametreler — recurring için non_3d: "1", // Tekrarlayan ödemeler 3DS dışı olmalı (banka yetkisi gerekli) recurring_payment: "1", utoken: "...", // Müşterinin önceki başarılı postback'inden gelen token ctoken: "...", // Kart Listesi API'sinden alınan kart token'ı payment_type: "card",

Önemli: PayTR ödeme zamanlamasını otomatik yönetmez. Her ödeme döngüsünü API çağrısıyla siz tetiklersiniz. Vercel Cron ile düzenli ödeme tetiklemesi yapabilirsiniz.

HMAC formülü tekrarlayan ödemelerde farklıdır:

// Recurring HMAC birleştirme sırası (ayraç yok) const hashString = [ merchantId, userIp, merchantOid, email, paymentAmount, paymentType, installmentCount, currency, testMode, non3d, merchantSalt ].join("")

Production Kontrol Listesi

  • test_mode=0 (canlı modda)
  • Callback URL HTTPS (zorunlu)
  • Callback URL PayTR paneline kayıtlı
  • merchant_ok_url ve merchant_fail_url HTTPS
  • PAYTR_MERCHANT_KEY ve PAYTR_MERCHANT_SALT .env'de, git'e gönderilmemiş
  • Postback'te hash doğrulama etkin ve loglanıyor
  • debug_on=0 (canlı ortamda)
  • user_ip gerçek müşteri IP'sini alıyor (proxy header'ları yapılandırıldı)
  • Hata logları izleniyor (Vercel Logs, Sentry vb.)
  • Tekrarlayan ödeme kullanacaksanız: ayrı Sanal POS başvurusu tamamlanmış

Sık Sorulan Sorular

PayTR entegrasyonu için resmi bir Next.js veya npm paketi var mı?

Hayır, PayTR'ın resmi npm paketi bulunmuyor. Topluluk tarafından geliştirilen paytr (themisir/paytr-js) paketi aktif bakımlıdır; alternatif olarak bu rehberdeki gibi native node:crypto modülü ile ~50 satır kodla kendiniz uygulayabilirsiniz.

3D Secure Türkiye'de zorunlu mu?

Kanuni bir zorunluluk değildir, ancak Türkiye'deki bankalar pratikte tüm kartlı işlemlerde 3DS SMS OTP uyguladığından non-3DS işlem büyük olasılıkla başarısız olacaktır. Tekrarlayan ödeme senaryolarında non_3d=1 kullanımı için ayrı banka yetkisi gerekmektedir.

Taksitli ödemelerde postback nasıl çalışır?

Taksitli ödemelerde bile postback tek seferlik gelir ve ödeme "success" olarak raporlanır. PayTR taksit tahsilatlarını arka planda yönetir. total_amount alanı tahsil edilen toplam tutarı gösterir.

Ödeme başarılı ama postback gelmiyor — ne yapmalıyım?

PayTR Paneli → İşlemler → Detay → Callback Log bölümünü inceleyin. Olası nedenler: Callback URL hatalı veya erişilemiyor, sunucunuz zaman aşımına uğradı ya da OK yerine farklı yanıt döndü. PayTR başarısız callback'leri birkaç kez otomatik olarak yeniden dener.

Kısmi iade yapılabilir mi?

Evet, return_amount parametresine kısmi tutar vererek iade API'sine istek atabilirsiniz. Hata kodu 010 yetersiz bakiye durumunu, 011 ise 1 yılı aşmış işlem girişimini gösterir.

PayTR ile Drizzle ORM entegrasyonu nasıl yapılır?

Postback endpoint'inde merchantOid ile siparişi bulup durumu güncelleyebilirsiniz. Şema örneği:

// drizzle/schema/orders.ts export const orders = pgTable("orders", { id: text("id").primaryKey(), paytrOid: text("paytr_oid").unique(), status: text("status").notNull().default("pending"), paymentType: text("payment_type"), paidAt: timestamp("paid_at"), failReason: text("fail_reason"), // ... diğer alanlar })

Bu rehber ilkkod.com tarafından hazırlanmıştır. Güncel PayTR dokümantasyonu ve entegrasyon desteği için paytr.com/destek adresini ziyaret edin.

Paylaş: