Next.js ile Craftgate Entegrasyonu: Tüm Bankalara Tek API ile Ödeme Alma
Craftgate'in resmi TypeScript SDK'sı ile Next.js 16 App Router'da ödeme entegrasyonu. Common Payment Page, taksit sorgulama ve akıllı yönlendirme adım adım.

Ö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. Craftgate fiyatlandırması için craftgate.io adresini ziyaret edin.
Türkiye'de bir e-ticaret sitesi veya SaaS ürünü geliştiriyorsanız, ödeme entegrasyonu kaçınılmaz bir adım. Tek bir bankaya bağlı sanal POS yerine birden fazla bankayı ve ödeme sağlayıcısını tek bir API üzerinden yönetmek istiyorsanız, ödeme orkestrasyonu tam size göre.
Craftgate, Ocak–Mayıs 2025 arasında 76.86 milyar TL işlem hacmi işlemiş ve Türkiye'nin en hızlı büyüyen ödeme platformlarından biri haline gelmiştir. En önemli avantajlarından biri: resmi @craftgate/craftgate npm paketi tam TypeScript desteği sunar — iyzipay veya resmi PayTR paketi bulunmaması ile kıyaslandığında bu büyük bir geliştirici deneyimi farkı yaratır.
Bu rehberde Next.js 16 App Router kullanarak Craftgate entegrasyonunu adım adım ele alacağız.
Craftgate Nedir? Ödeme Orkestrasyonu Konsepti
Geleneksel sanal POS entegrasyonunda tek bir bankaya bağlanırsınız. Başarısız ödemede yapabileceğiniz fazla bir şey yoktur. Ödeme orkestrasyonu ise birden fazla banka ve ödeme sağlayıcısını tek bir API arkasına toplar:
- Akıllı yönlendirme (Smart Routing): Ödeme girişimi başarısız olursa, sistem otomatik olarak alternatif bir bankaya yönlendirir (failover routing)
- Komisyon optimizasyonu: Yapılandırılmış banka POS'ları arasından en avantajlı olanı seçer
- Tek entegrasyon: İş mantığınızı değiştirmeden arka planda banka değiştirebilirsiniz
- Sub-merchant desteği: Marketplace kuruyorsanız satıcı bazlı ödeme dağıtımı yapabilirsiniz
Craftgate'i özellikle ajanslar ve freelancer'lar için ideal yapan şey: tek bir entegrasyonla birden fazla müşterinizin e-ticaret sitesini yönetebilirsiniz.
Craftgate vs Diğer Sağlayıcılar — Ne Zaman Craftgate?
| Kriter | Craftgate | iyzico | PayTR |
|---|---|---|---|
| Resmi npm paketi | ✅ @craftgate/craftgate | ✅ iyzipay | ❌ Resmi yok |
| TypeScript desteği | ✅ Tam (%65.4 TS) | ❌ Yok | Topluluk (%99 TS) |
| Sandbox | ✅ sandbox-api.craftgate.io | ✅ Var | ✅ test_mode=1 |
| Ödeme orkestrasyonu | ✅ Temel özellik | ❌ | ❌ |
| Sub-merchant | ✅ Tam destek | ✅ Var | Sınırlı |
Senaryo bazlı değerlendirme için Sanal POS Maliyetleri 2025 yazımıza da bakabilirsiniz.
Ön Koşullar
- Node.js 18+ veya Bun 1.x
- Next.js 16 App Router projesi
- Craftgate sandbox hesabı
Craftgate hesabı için vergi numarası olan bir şahıs şirketi veya limited şirket gerektirir. Bireysel başvuru yapılamaz. Başvuru süreci ve gerekli belgeler için güncel bilgiyi craftgate.io adresinden edinin.
SDK Kurulumu
bun add @craftgate/craftgate
@craftgate/craftgate sürüm 1.0.65 itibarıyla ayrı bir @types paketi kurmanıza gerek yoktur — tip tanımları pakete dahildir.
Ortam Değişkenleri
.env.local dosyasına ekleyin:
CRAFTGATE_API_KEY=your-api-key-here CRAFTGATE_SECRET_KEY=your-secret-key-here CRAFTGATE_BASE_URL=https://sandbox-api.craftgate.io NEXT_PUBLIC_SITE_URL=http://localhost:3000
Production'a geçerken CRAFTGATE_BASE_URL'i https://api.craftgate.io olarak güncelleyin.
lib/env.ts ile Zod doğrulaması:
import { z } from "zod"
const envSchema = z.object({
CRAFTGATE_API_KEY: z.string().min(1),
CRAFTGATE_SECRET_KEY: z.string().min(1),
CRAFTGATE_BASE_URL: z.string().url().default("https://sandbox-api.craftgate.io"),
NEXT_PUBLIC_SITE_URL: z.string().url(),
})
export const env = envSchema.parse(process.env)
Craftgate Client
Tek bir yerde başlatıp export edin; her Route Handler'da tekrar başlatmayın:
// lib/craftgate.ts
import Craftgate from "@craftgate/craftgate"
import { env } from "@/lib/env"
export const craftgate = new Craftgate.Client({
apiKey: env.CRAFTGATE_API_KEY,
secretKey: env.CRAFTGATE_SECRET_KEY,
baseUrl: env.CRAFTGATE_BASE_URL,
})
Common Payment Page ile Ödeme Akışı
Craftgate'in önerilen entegrasyon yöntemi Common Payment Page (CPP)'dir. Ödeme sayfasını sıfırdan oluşturmanıza gerek yoktur; Craftgate'in hosted sayfasına kullanıcıyı yönlendirirsiniz.
Akış 3 adımdan oluşur:
- Server-side: Ödeme token'ı oluştur →
pageUrlvetokenal - Client-side: Kullanıcıyı
pageUrl'e yönlendir - Callback: Craftgate
callbackUrl'e sonucu POST atar
Step 1 — Ödeme Başlatma Route Handler
// app/api/craftgate/init/route.ts
import { craftgate } from "@/lib/craftgate"
import { env } from "@/lib/env"
import { NextResponse } from "next/server"
interface CartItem {
id: string
name: string
price: number
quantity: number
}
export async function POST(request: Request) {
const body = await request.json()
const { orderId, items }: { orderId: string; items: CartItem[] } = body
// Kritik: items toplamı === price olmalı
const totalPrice = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
const response = await craftgate.payment().initCheckoutPayment({
price: totalPrice,
paidPrice: totalPrice, // Taksitsiz ödemede eşit; faiz eklenirse paidPrice > price olabilir
currency: "TRY",
callbackUrl: `${env.NEXT_PUBLIC_SITE_URL}/api/craftgate/callback`,
conversationId: orderId,
items: items.map((item) => ({
externalId: item.id,
name: item.name,
price: item.price * item.quantity,
itemType: "PHYSICAL_GOODS" as const,
})),
})
if (response.errors) {
return NextResponse.json(
{ error: response.errors.errorDescription },
{ status: 400 },
)
}
return NextResponse.json({
token: response.token,
pageUrl: response.pageUrl,
})
}
Önemli detay: items[] dizisindeki fiyatların toplamı price değerine tam olarak eşit olmalıdır. Aksi hâlde API hata döner.
Step 2 — Client-Side Yönlendirme
// components/checkout-button.tsx
"use client"
import { useState } from "react"
interface CartItem {
id: string
name: string
price: number
quantity: number
}
interface CheckoutButtonProps {
orderId: string
items: CartItem[]
}
export function CheckoutButton({ orderId, items }: CheckoutButtonProps) {
const [loading, setLoading] = useState(false)
const handleCheckout = async () => {
setLoading(true)
try {
const res = await fetch("/api/craftgate/init", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ orderId, items }),
})
const data = await res.json()
if (data.pageUrl) {
window.location.href = data.pageUrl
}
} finally {
setLoading(false)
}
}
return (
<button
type="button"
onClick={handleCheckout}
disabled={loading}
className="h-12 px-6 bg-blue-600 text-white rounded-lg disabled:opacity-50"
>
{loading ? "Yönlendiriliyor..." : "Ödemeye Geç"}
</button>
)
}
Step 3 — Callback Handler
Ödeme tamamlandığında (başarılı veya başarısız) Craftgate callbackUrl'e token içeren bir form POST atar. Bu token ile ödeme sonucunu sunucu tarafında sorgularsınız:
// app/api/craftgate/callback/route.ts
import { craftgate } from "@/lib/craftgate"
import { db } from "@/lib/db"
import { orders } from "@/lib/db/schema"
import { eq } from "drizzle-orm"
import { NextResponse } from "next/server"
export async function POST(request: Request) {
const formData = await request.formData()
const token = formData.get("token") as string | null
if (!token) {
return new Response("token missing", { status: 400 })
}
// Token ile ödeme sonucunu sorgula
const paymentResult = await craftgate.payment().retrieveCheckoutPayment(token)
if (paymentResult.paymentStatus === "SUCCESS") {
// Drizzle ORM ile sipariş güncelle
await db
.update(orders)
.set({
status: "paid",
paymentId: String(paymentResult.id ?? ""),
paidAt: new Date(),
})
.where(eq(orders.externalId, paymentResult.conversationId ?? ""))
return NextResponse.redirect(
`${process.env.NEXT_PUBLIC_SITE_URL}/siparis/tamamlandi`,
)
}
const errorReason = paymentResult.paymentError?.errorDescription ?? "unknown"
return NextResponse.redirect(
`${process.env.NEXT_PUBLIC_SITE_URL}/siparis/hata?reason=${encodeURIComponent(errorReason)}`,
)
}
Not: Callback URL'inizin HTTPS olması ve harici erişime açık olması gerekir. Local geliştirmede ngrok kullanın.
Taksit Sorgulama API
Craftgate'in taksit API'si, kullanıcının kartının ilk 8 hanesi (binNumber) ile dinamik taksit seçenekleri döndürür. Kullanıcı kart numarasını girerken bu API'yi çağırarak gerçek zamanlı taksit tablosu gösterebilirsiniz.
binNumber opsiyoneldir. Gönderilmezse genel taksit seçenekleri; gönderilirse kullanıcının kartına özgü seçenekler döner.
Taksit Sorgulama Route Handler
// app/api/craftgate/installments/route.ts
import { craftgate } from "@/lib/craftgate"
import { NextResponse } from "next/server"
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const price = searchParams.get("price")
const binNumber = searchParams.get("binNumber")
if (!price) {
return NextResponse.json({ error: "price parametresi gerekli" }, { status: 400 })
}
const result = await craftgate.installment().retrieveInstallments({
price: Number.parseFloat(price),
currency: "TRY",
...(binNumber ? { binNumber } : {}),
})
return NextResponse.json({
installmentPrices: result.installmentPrices ?? [],
cardBrand: result.cardBrand,
bankName: result.bankName,
cardType: result.cardType,
})
}
Client-Side Taksit Bileşeni
// components/installment-options.tsx
"use client"
import { useEffect, useState } from "react"
interface InstallmentPrice {
installmentNumber: number
totalPrice: number
installmentPrice: number
}
interface InstallmentOptionsProps {
binNumber: string
price: number
}
export function InstallmentOptions({ binNumber, price }: InstallmentOptionsProps) {
const [installments, setInstallments] = useState<InstallmentPrice[]>([])
const [bankName, setBankName] = useState<string>("")
useEffect(() => {
if (binNumber.length < 8) {
setInstallments([])
return
}
fetch(`/api/craftgate/installments?price=${price}&binNumber=${binNumber}`)
.then((r) => r.json())
.then((data) => {
setInstallments(data.installmentPrices ?? [])
setBankName(data.bankName ?? "")
})
}, [binNumber, price])
if (installments.length === 0) return null
return (
<div className="mt-4 rounded-lg border border-gray-200 p-4">
{bankName && (
<p className="mb-2 text-xs text-gray-500">{bankName} taksit seçenekleri</p>
)}
<div className="space-y-2">
{installments.map((inst) => (
<div key={inst.installmentNumber} className="flex justify-between text-sm">
<span>
{inst.installmentNumber === 1
? "Tek çekim"
: `${inst.installmentNumber} taksit`}
</span>
<span className="font-medium">
{inst.installmentNumber === 1
? `${inst.totalPrice.toFixed(2)} TL`
: `${inst.installmentPrice.toFixed(2)} TL × ${inst.installmentNumber}`}
</span>
</div>
))}
</div>
</div>
)
}
Akıllı Yönlendirme (Smart Routing)
Craftgate'in en güçlü özelliklerinden biri Smart Routing — failover ile otomatik banka değişimi.
Nasıl çalışır:
- Craftgate ödeme girişimini alır
- Merchant panelinde yapılandırılmış banka POS'ları arasından en uygun olanı seçer
- Ödeme başarısız olursa alternatif bankaya otomatik olarak failover yapar
- Tüm bu akış size şeffaf — kodunuzda herhangi bir değişiklik gerektirmez
Bu özelliği aktif hale getirmek için Craftgate merchant panelinde birden fazla banka POS tanımlamanız gerekir. Kod tarafında tek bir entegrasyon yeterlidir.
Sub-Merchant (Marketplace) Entegrasyonu
Bir marketplace veya platform kuruyorsanız, Craftgate'in sub-merchant desteği satıcı başına ödeme dağıtımını yönetmenizi sağlar.
Sub-Merchant Oluşturma (Tek Seferlik)
// app/api/craftgate/sub-merchant/route.ts
import { craftgate } from "@/lib/craftgate"
import { NextResponse } from "next/server"
export async function POST(request: Request) {
const body = await request.json()
const member = await craftgate.member().createMember({
memberType: "PERSONAL",
name: body.name,
email: body.email,
iban: body.iban,
contactName: body.contactName,
contactSurname: body.contactSurname,
address: body.address,
taxOffice: body.taxOffice,
identityNumber: body.identityNumber,
})
return NextResponse.json({ memberId: member.id })
}
Ödeme Sırasında Sub-Merchant Kalemi
const response = await craftgate.payment().initCheckoutPayment({
price: totalPrice,
paidPrice: totalPrice,
currency: "TRY",
callbackUrl: callbackUrl,
items: [
{
externalId: "urun-1",
name: "Ürün Adı",
price: 100,
itemType: "PHYSICAL_GOODS" as const,
subMerchantMemberId: saticiMemberId,
subMerchantMemberPrice: 85, // Satıcıya gidecek tutar (15 TL platform kesintisi)
},
],
})
Escrow tamamlandığında yerleşim için createInstantWalletSettlement() metodunu kullanabilirsiniz.
İade İşlemleri
// app/api/craftgate/refund/route.ts
import { craftgate } from "@/lib/craftgate"
import { NextResponse } from "next/server"
export async function POST(request: Request) {
const { paymentTransactionId, refundAmount } = await request.json()
const refund = await craftgate.payment().refundPaymentTransaction({
paymentTransactionId: paymentTransactionId,
refundPrice: refundAmount,
currency: "TRY",
conversationId: `refund-${Date.now()}`,
})
if (refund.status === "SUCCESS") {
return NextResponse.json({ success: true, refundId: refund.id })
}
return NextResponse.json(
{ error: refund.errors?.errorDescription ?? "İade başarısız" },
{ status: 400 },
)
}
Test Ortamı
Craftgate sandbox ortamı https://sandbox-api.craftgate.io adresinde çalışır. Sandbox API anahtarlarını Craftgate merchant panel üzerinden alabilirsiniz.
Sandbox'ta gerçek Craftgate ödeme sayfasına yönlendirilirsiniz. Test kart numaraları sandbox panelinde mevcuttur.
Local geliştirme için callback URL:
Callback URL'inizin dışarıdan erişilebilir olması gerekir. ngrok ile çözün:
bunx ngrok http 3000
# Çıktıdaki URL'i .env.local'a ekleyin:
# NEXT_PUBLIC_SITE_URL=https://xxxx.ngrok-free.app
Test sırasında dikkat edilecekler:
CRAFTGATE_BASE_URL'in sandbox URL (https://sandbox-api.craftgate.io) olduğunu doğrulayın- Her test isteğinde
conversationIddeğerini benzersiz tutun items[]toplamınınprice'a eşit olduğunu kontrol edin
Production Geçiş Kontrol Listesi
-
CRAFTGATE_BASE_URL'ihttps://api.craftgate.ioolarak güncelleyin - Production API anahtarlarını hosting ortam değişkenlerine ekleyin
- Callback URL'inizin HTTPS olduğunu doğrulayın
- Callback endpoint'ini beklenmedik POST isteklerine karşı test edin
- Callback alınamayan siparişler için fallback sorgulama mekanizması ekleyin (örn. Vercel Cron ile pending siparişleri kontrol edin)
- Hata izleme (Sentry vb.) kurun — ödeme hatalarını takip edin
- Ödeme başarısız senaryosunda kullanıcıya anlamlı Türkçe hata mesajı gösterin
- Merchant panelinde Smart Routing için birden fazla banka POS yapılandırın
Sıkça Sorulan Sorular
Craftgate hesabı açmak için ne gerekiyor?
Vergi numarası olan şahıs şirketi veya limited şirket gerektirir. Bireysel başvuru yapılamaz. Güncel başvuru koşulları için craftgate.io/tr adresini ziyaret edin.
Common Payment Page ile kendi ödeme formumu tasarlayabilir miyim?
CPP, Craftgate'in hosted sayfasıdır. Kısmi özelleştirme (logo, renk) mümkündür ancak kapsamı sınırlıdır. Tam özel ödeme formu isterseniz Craftgate'in Direct API'sini kullanabilirsiniz; bu yaklaşım PCI uyumluluğu açısından daha fazla sorumluluk gerektirir.
price ile paidPrice farkı nedir?
price sepet toplamıdır (taksitsiz, faizsiz fiyat). paidPrice ise tahsil edilecek gerçek tutardır. Taksitli ödemelerde banka faizi eklenerek paidPrice > price olabilir. Tek çekim ödemelerde ikisi eşit olmalıdır.
binNumber olmadan taksit sorgulama yapılabilir mi?
Evet, binNumber opsiyoneldir. Gönderilmezse genel taksit seçenekleri döner. Kullanıcı kart numarasının ilk 8 hanesini girdikten sonra binNumber ile sorgu yapmak, kullanıcının kartına özgü gerçek taksit seçeneklerini göstereceğinden daha iyi bir deneyim sunar.
Webhook / IPN desteği var mı?
Evet. Craftgate, callbackUrl mekanizması dışında ayrıca webhook desteği sunar. Merchant panelinden webhook endpoint'i yapılandırabilirsiniz. Ödeme durumu değişimlerinde (başarı, başarısız, iade) anlık bildirim alabilirsiniz.
Craftgate yabancı para birimi destekliyor mu?
Evet. Desteklenen para birimleri: TRY, USD, EUR, GBP, CNY, ARS, BRL, AED, IQD, AZN, KZT, KWD, SAR, BHD, RUB, JPY, EGP. Yurt dışı kartlardan yabancı para birimi üzerinden ödeme alabilirsiniz.
Smart Routing için ek kod yazmak gerekiyor mu?
Hayır. Smart Routing tamamen Craftgate altyapısında çalışır. Merchant panelinde birden fazla banka POS yapılandırdığınızda otomatik devreye girer. Kodunuzda herhangi bir değişiklik gerekmez.
Sonuç
Craftgate, ödeme orkestrasyonu konseptiyle çoklu banka yönetimini tek API'ye indirgeyerek Türkiye'de benzersiz bir çözüm sunuyor. Resmi TypeScript SDK'sı, CPP ile hızlı entegrasyon, smart routing ve sub-merchant desteği — özellikle birden fazla müşteri için e-ticaret geliştiren ajanslar ve ödeme başarı oranını artırmak isteyen projeler için güçlü bir seçenek.
Bu projede de PayTR ile birlikte ödeme entegrasyonu deneyimi mevcut. Projenize ödeme entegrasyonu eklemeye ihtiyaç duyuyorsanız iletişime geçin.
İlgili yazılar:


