"use server"; import { eq } from "drizzle-orm"; import { headers } from "next/headers"; 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"; // 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 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?"; /** "Bilal Gürsen" → ["Bilal", "Gürsen"]; iyzico ad ve soyadı ayrı ve dolu ister. */ function adSoyadAyir(tamAd: string): [string, string] { const parcalar = tamAd.trim().split(/\s+/).filter(Boolean); if (parcalar.length === 0) return ["KolayTercih", "Kullanıcısı"]; if (parcalar.length === 1) return [parcalar[0], parcalar[0]]; return [parcalar.slice(0, -1).join(" "), parcalar[parcalar.length - 1]]; } export async function baslatOdeme( product: "paket" | "topup", ): Promise { // 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 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(); await appDb.insert(schema.orders).values({ id: orderId, userId: session.user.id, product, amountKurus: urun.amountKurus, credits: urun.credits, status: "pending", createdAt: new Date(), }); const h = await headers(); const buyerIp = h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "85.34.78.112"; const appUrl = siteKok(); const [ad, soyad] = adSoyadAyir(session.user.name ?? ""); let init; try { init = await initializeCheckoutForm({ orderId, fiyatKurus: urun.amountKurus, urunAdi: `KolayTercih — ${urun.label}`, email: session.user.email, userId: session.user.id, ad, soyad, // iyzico bu adrese hem başarıyı hem başarısızlığı POST'lar; geçerli SSL şart callbackUrl: `${appUrl}/api/odeme/callback`, buyerIp, }); } catch (err) { if (err instanceof Error && err.message === "IYZICO_KEYS_MISSING") { // Yapılandırma detayı log'a; kullanıcıya altyapı sızdırmayan mesaj console.error("[odeme] iyzico anahtarları eksik — ödeme başlatılamadı"); return { error: GENEL_HATA }; } console.error("[odeme] iyzico initialize hatası", err); return { error: GENEL_HATA }; } if (init.status !== "success" || !init.checkoutFormContent || !init.token) { // initialize aşamasında karta hiç dokunulmaz: buradaki hatalar kart/kullanıcı // kaynaklı değil, bizim yapılandırmamızdandır (anahtar, üye işyeri ayarı, // sepet kuralı). iyzico'nun mesajı log'a gider, kullanıcıya genel mesaj. console.error("[odeme] initialize başarısız", { orderId, errorCode: init.errorCode, errorMessage: init.errorMessage, }); return { error: GENEL_HATA }; } // Yanıtın bütünlüğü: imza tutmuyorsa kullanıcıyı o ödeme sayfasına yollama. if (initImzaDurumu(init) === "gecersiz") { console.error("[odeme] initialize imzası geçersiz", { orderId }); return { error: GENEL_HATA }; } // Form içeriği de saklanır: /odeme/[siparis] sayfası yenilense bile aynı // token'lı form yeniden gösterilir, ikinci bir initialize gerekmez. await appDb .update(schema.orders) .set({ iyzicoToken: init.token, iyzicoFormContent: init.checkoutFormContent }) .where(eq(schema.orders.id, orderId)); return { yonlendir: `/odeme?siparis=${orderId}` }; }