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.

Ö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=1parametresi) - Ç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_KEYvePAYTR_MERCHANT_SALTyalnı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:
| Parametre | Açıklama |
|---|---|
merchant_oid | Siparişinizin benzersiz ID'si (max 64 alfanümerik karakter) |
payment_amount | TL × 100, tam sayı (örn: 10.50 TL → 1050) |
user_basket | Base64 kodlanmış JSON: [[isim, fiyat, adet], ...] |
paytr_token | HMAC-SHA256 imzası — alan sırası kritiktir |
test_mode | "1" = sandbox, "0" = canlı |
no_installment | "1" = taksit seçeneği gösterme |
currency | TL, 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,
localhostadreslerine 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
| Alan | Değer | Açıklama |
|---|---|---|
merchant_oid | string | Sipariş ID'niz |
status | success / failed | Ödeme sonucu |
total_amount | string | Tahsil edilen tutar (kuruş cinsinden) |
hash | string | Doğrulama hash'i |
failed_reason_code | string | Hata kodu (başarısız ödemelerde) |
failed_reason_msg | string | Hata açıklaması |
payment_type | string | card, eft vb. |
test_mode | 0 / 1 | Sandbox 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ı:
| Kod | Anlam |
|---|---|
001 | Geçersiz merchant bilgileri |
004 | Token eksik veya hatalı |
005 | Bu sipariş için başarılı ödeme bulunamadı |
010 | Yetersiz bakiye (kısmi iade söz konusu olabilir) |
011 | 1 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 | İsim | Son Kullanma | CVV | Tür |
|---|---|---|---|---|
| 4355 0843 5508 4358 | PAYTR TEST | 12/30 | 000 | Visa |
| 5406 6754 0667 5403 | PAYTR TEST | 12/30 | 000 | MasterCard |
| 9792 0303 9444 0796 | PAYTR TEST | 12/30 | 000 | Troy |
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:
| Paket | TypeScript | Durum |
|---|---|---|
paytr (themisir/paytr-js) | ~%99 TS | Aktif bakımlı; iframe token + basket |
node-paytr | JavaScript | 12+ 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_saltilemerchant_key'i karıştırmayın: salt birleştirme string'ine girer, key HMAC imzasında kullanılır- Token isteğinde
debug_on=1ile 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_urlvemerchant_fail_urlHTTPS -
PAYTR_MERCHANT_KEYvePAYTR_MERCHANT_SALT.env'de, git'e gönderilmemiş - Postback'te hash doğrulama etkin ve loglanıyor
-
debug_on=0(canlı ortamda) -
user_ipgerç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.


