feat: kimlik doğrulama, ödeme, kredi ve AI rapor sihirbazı ekle

- better-auth ile giriş/oturum yönetimi (src/lib/auth, giris sayfası)
- iyzico ödeme entegrasyonu ve paket satın alma akışı
- kredi sistemi ve uygulama veritabanı şeması (drizzle)
- AI destekli rapor üretimi ve tercih sihirbazı
- rapor yazdırma sayfası ve site header/kullanıcı menüsü

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bilalgursen
2026-07-22 02:13:39 +03:00
parent 7c3f17b9b5
commit cee6200237
64 changed files with 7966 additions and 89 deletions

63
src/lib/odeme.ts Normal file
View File

@@ -0,0 +1,63 @@
import { and, eq } from "drizzle-orm";
import { appDb, schema } from "./appdb";
import { grantCredits, URUNLER } from "./credits";
import { retrieveCheckoutForm } from "./iyzico";
const { orders } = schema;
/**
* Token'la iyzico'dan sonucu çeker ve başarılıysa krediyi İDEMPOTENT tanımlar.
* Hem callback route'u hem /odeme/sonuc self-healing fallback'i bunu kullanır.
* Dönen değer: siparişin son durumu.
*/
export async function odemeyiSonuclandir(
orderId: string,
): Promise<"paid" | "pending" | "failed" | "not_found"> {
const order = await appDb.query.orders.findFirst({
where: eq(orders.id, orderId),
});
if (!order) return "not_found";
if (order.status === "paid") return "paid";
if (!order.iyzicoToken) return order.status;
let sonuc;
try {
sonuc = await retrieveCheckoutForm(order.iyzicoToken);
} catch {
return order.status; // iyzico'ya ulaşılamadı; durumu değiştirme
}
if (sonuc.status !== "success" || sonuc.paymentStatus !== "SUCCESS") {
await appDb
.update(orders)
.set({ status: "failed" })
.where(and(eq(orders.id, orderId), eq(orders.status, "pending")));
return "failed";
}
if (sonuc.conversationId && sonuc.conversationId !== order.id) {
return order.status; // conversationId uyuşmazlığı: işleme
}
// pending -> paid koşullu geçiş: 0 satır = başka istek zaten işledi
const res = await appDb
.update(orders)
.set({
status: "paid",
iyzicoPaymentId: sonuc.paymentId ?? null,
paidAt: new Date(),
})
.where(and(eq(orders.id, orderId), eq(orders.status, "pending")));
if (res.rowsAffected === 0) return "paid";
// UNIQUE(reason, refId) ikinci katman güvence
await grantCredits({
userId: order.userId,
delta: order.credits,
reason: order.product === "paket" ? "purchase" : "topup",
refId: order.id,
setHasPaket: order.product === "paket",
});
return "paid";
}
export { URUNLER };