chore: update iyzico integration and enhance payment processing logic
All checks were successful
Deploy / deploy (push) Successful in 7m0s
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.
This commit is contained in:
@@ -3,15 +3,27 @@ import { NextResponse, type NextRequest } from "next/server";
|
||||
import { appDb, schema } from "@/lib/appdb";
|
||||
import { odemeyiSonuclandir } from "@/lib/odeme";
|
||||
|
||||
// iyzico ödeme formunu bitiren kullanıcıyı buraya POST'lar; gövdede yalnızca
|
||||
// `token` vardır (Checkout Form akışında imza başlığı gelmez — durum her zaman
|
||||
// odemeyiSonuclandir içindeki sunucu-sunucu retrieve çağrısından okunur).
|
||||
//
|
||||
// DİKKAT: Bu route iyzico'dan gelen cross-site form POST'udur.
|
||||
// SameSite=Lax nedeniyle session çerezi GELMEZ — kullanıcı token'dan çözülür,
|
||||
// verifySession ÇAĞRILMAZ. Kredi tanımlama odemeyiSonuclandir içinde idempotenttir.
|
||||
export async function POST(request: NextRequest) {
|
||||
const form = await request.formData();
|
||||
const token = form.get("token");
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
|
||||
|
||||
if (typeof token !== "string" || !token) {
|
||||
let token: string | undefined;
|
||||
try {
|
||||
const form = await request.formData();
|
||||
const t = form.get("token");
|
||||
token = typeof t === "string" ? t.trim() : undefined;
|
||||
} catch {
|
||||
token = undefined;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("[odeme] callback: token yok");
|
||||
return NextResponse.redirect(`${appUrl}/paket?hata=token`, 303);
|
||||
}
|
||||
|
||||
@@ -19,12 +31,12 @@ export async function POST(request: NextRequest) {
|
||||
where: eq(schema.orders.iyzicoToken, token),
|
||||
});
|
||||
if (!order) {
|
||||
console.error("[odeme] callback: token'a ait sipariş yok");
|
||||
return NextResponse.redirect(`${appUrl}/paket?hata=siparis`, 303);
|
||||
}
|
||||
|
||||
await odemeyiSonuclandir(order.id);
|
||||
return NextResponse.redirect(
|
||||
`${appUrl}/odeme/sonuc?siparis=${order.id}`,
|
||||
303,
|
||||
);
|
||||
// 303: iyzico'nun POST'u GET'e döner, kullanıcı sonuç sayfasında yenileme
|
||||
// yaptığında form yeniden gönderilmez.
|
||||
return NextResponse.redirect(`${appUrl}/odeme/sonuc?siparis=${order.id}`, 303);
|
||||
}
|
||||
|
||||
87
src/app/api/odeme/webhook/route.ts
Normal file
87
src/app/api/odeme/webhook/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { appDb, schema } from "@/lib/appdb";
|
||||
import { odemeyiSonuclandir } from "@/lib/odeme";
|
||||
import { webhookImzaDurumu } from "@/lib/iyzico";
|
||||
|
||||
// iyzico ödeme bildirimi (webhook). Callback'in yedeğidir: kullanıcı ödeme
|
||||
// sonrası sekmeyi kapatır ya da 3DS/fraud incelemesi ödemeyi dakikalar sonra
|
||||
// SUCCESS'e çevirirse kredi yine de tanımlansın diye.
|
||||
//
|
||||
// Kurulum: iyzico Merchant Portal → Ayarlar → İşyeri Ayarları → İşyeri
|
||||
// Bildirimleri; HTTPS URL zorunlu. İmza başlığı (X-IYZ-SIGNATURE-V3) hesapta
|
||||
// ayrıca aktifleştirilmelidir (entegrasyon@iyzico.com).
|
||||
//
|
||||
// GÜVENLİK NOTU: Gövdeye hiç güvenilmez — sadece "şu sipariş için iyzico'ya
|
||||
// tekrar sor" tetikleyicisidir. Ödeme durumu her hâlükârda kimliği doğrulanmış
|
||||
// sunucu-sunucu retrieve çağrısından okunur (odemeyiSonuclandir). Bu yüzden imza
|
||||
// başlığı yoksa da işlem güvenle sürdürülebilir; varsa sahtesi reddedilir.
|
||||
//
|
||||
// iyzico 2xx alana kadar 15 dakika arayla 3 kez dener — bu yüzden işleyemediğimiz
|
||||
// durumlarda bile 200 döneriz (yeniden deneme bize bir şey kazandırmaz).
|
||||
|
||||
interface WebhookGovde {
|
||||
iyziEventType?: string;
|
||||
iyziPaymentId?: string | number;
|
||||
iyziReferenceCode?: string;
|
||||
token?: string;
|
||||
paymentConversationId?: string;
|
||||
paymentId?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
let govde: WebhookGovde;
|
||||
try {
|
||||
govde = (await request.json()) as WebhookGovde;
|
||||
} catch {
|
||||
return NextResponse.json({ ok: false }, { status: 400 });
|
||||
}
|
||||
|
||||
const imza =
|
||||
request.headers.get("x-iyz-signature-v3") ??
|
||||
request.headers.get("X-IYZ-SIGNATURE-V3") ??
|
||||
undefined;
|
||||
|
||||
let imzaDurumu;
|
||||
try {
|
||||
imzaDurumu = webhookImzaDurumu(govde, imza);
|
||||
} catch {
|
||||
// IYZICO_KEYS_MISSING — doğrulayamıyorsak işlemeyi de denemeyiz
|
||||
console.error("[odeme] webhook: iyzico anahtarları eksik");
|
||||
return NextResponse.json({ ok: false }, { status: 200 });
|
||||
}
|
||||
if (imzaDurumu === "gecersiz") {
|
||||
console.error("[odeme] webhook imzası geçersiz", {
|
||||
referans: govde.iyziReferenceCode,
|
||||
});
|
||||
return NextResponse.json({ ok: false }, { status: 401 });
|
||||
}
|
||||
|
||||
// Siparişi önce conversationId (= sipariş id'miz), yoksa token üzerinden bul
|
||||
const order = govde.paymentConversationId
|
||||
? await appDb.query.orders.findFirst({
|
||||
where: eq(schema.orders.id, govde.paymentConversationId),
|
||||
})
|
||||
: govde.token
|
||||
? await appDb.query.orders.findFirst({
|
||||
where: eq(schema.orders.iyzicoToken, govde.token),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (!order) {
|
||||
console.warn("[odeme] webhook: sipariş bulunamadı", {
|
||||
referans: govde.iyziReferenceCode,
|
||||
});
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
const durum = await odemeyiSonuclandir(order.id);
|
||||
console.info("[odeme] webhook işlendi", {
|
||||
siparis: order.id,
|
||||
olay: govde.iyziEventType,
|
||||
iyzicoDurum: govde.status,
|
||||
durum,
|
||||
});
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user