fix(odeme): action redirect fırlatmaz, hedef döndürür + çift paket çekimine sunucu koruması
All checks were successful
Deploy / deploy (push) Successful in 8m46s

- baslatOdeme { error } | { yonlendir } döndürür; SatinAlForm client'ta
  router.push yapar (cacheComponents altında geri dönüşte buton tıklamaya
  sağır kalıyordu); tıklamada ok → dönen halka
- paketi olan yeniden 'paket', paketi olmayan 'topup' başlatamaz
- /odeme, paket alınmışken eski bekleyen paket siparişinin formunu göstermez

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-21 16:13:30 +03:00
parent d7c29ac4a6
commit 8f4669e31d
4 changed files with 93 additions and 32 deletions

View File

@@ -2,14 +2,18 @@
import { eq } from "drizzle-orm";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { verifySession } from "@/lib/session";
import { getCurrentUser, getSession } from "@/lib/session";
import { appUrl as siteKok } from "@/lib/app-url";
import { appDb, schema } from "@/lib/appdb";
import { URUNLER } from "@/lib/credits";
import { initializeCheckoutForm, initImzaDurumu } from "@/lib/iyzico";
export type OdemeBaslatDurum = { error?: string } | undefined;
// Action bilerek `redirect()` FIRLATMAZ, hedefi döndürür; yönlendirmeyi client
// yapar (bkz. satin-al-form.tsx). Fırlatılan redirect, client'ta action
// promise'ini reddedip formu RedirectBoundary üzerinden yeniden mount ediyor;
// cacheComponents altında /paket gizli <Activity> içinde canlı tutulduğu için
// bu yarım kalmış durum geri dönüşte butonu tıklamalara sağır bırakıyordu.
export type OdemeBaslatDurum = { error: string } | { yonlendir: string };
const GENEL_HATA =
"Ödeme şu anda başlatılamıyor; kartından çekim yapılmadı. Birazdan tekrar dener misin?";
@@ -23,15 +27,26 @@ function adSoyadAyir(tamAd: string): [string, string] {
}
export async function baslatOdeme(
_prev: OdemeBaslatDurum,
formData: FormData,
product: "paket" | "topup",
): Promise<OdemeBaslatDurum> {
// Ucuz senkron doğrulama pahalı oturum turundan önce
const product = formData.get("urun");
// Ucuz senkron doğrulama pahalı oturum turundan önce (argüman client'tan
// gelir; tipine güvenilmez)
if (product !== "paket" && product !== "topup") {
return { error: "Geçersiz ürün." };
}
const session = await verifySession("/paket");
const session = await getSession();
if (!session) {
return { yonlendir: `/giris?callback=${encodeURIComponent("/paket")}` };
}
// Arayüz bu kuralları zaten uygular ama bayat sekmeden (ör. paket başka
// sekmede alındı) gelen istek ikinci kez ücret çekmesin.
const kullanici = await getCurrentUser();
if (product === "paket" && kullanici?.hasPaket) {
return { error: "Paketin zaten aktif; yeniden ödeme alınmadı." };
}
if (product === "topup" && !kullanici?.hasPaket) {
return { error: "Ek kredi için önce paketi alman gerekiyor." };
}
const urun = URUNLER[product];
const orderId = crypto.randomUUID();
@@ -100,5 +115,5 @@ export async function baslatOdeme(
.set({ iyzicoToken: init.token, iyzicoFormContent: init.checkoutFormContent })
.where(eq(schema.orders.id, orderId));
redirect(`/odeme?siparis=${orderId}`);
return { yonlendir: `/odeme?siparis=${orderId}` };
}