"use server"; import { redirect } from "next/navigation"; import { Query } from "node-appwrite"; import { DATABASE_ID, TABLES, type TenantPlan } from "./schema"; import { createAdminClient } from "./server"; import { requireRole, requireTenant } from "./tenant-guard"; import { getEffectivePlan } from "./plan-limits"; import { PLAN_CATALOG } from "./subscription-types"; import { getShopierPlanUrl, isShopierEnabled } from "../payments/shopier"; import { createPolarCheckout, isPolarEnabled } from "../payments/polar"; const PRO_VALIDITY_DAYS = 30; function generateOrderId(): string { const t = Date.now().toString(36); const r = Math.random().toString(36).slice(2, 10); return `ord_${t}_${r}`; } // Webhook handler'larından da çağrılabilir — provider "polar" | "shopier" | "mock" export async function activatePlanInDb( tenantId: string, plan: TenantPlan, provider: string, ): Promise { const { tablesDB } = createAdminClient(); // tenant_settings satırını bul const result = await tablesDB.listRows({ databaseId: DATABASE_ID, tableId: TABLES.tenantSettings, queries: [Query.equal("tenantId", tenantId), Query.limit(1)], }); const row = result.rows[0]; if (!row) throw new Error(`tenant_settings bulunamadı: ${tenantId}`); const now = new Date(); const expires = new Date(now.getTime() + PRO_VALIDITY_DAYS * 24 * 60 * 60 * 1000); await tablesDB.updateRow(DATABASE_ID, TABLES.tenantSettings, row.$id, { plan, planExpiresAt: expires.toISOString(), planProvider: provider, }); } export async function deactivatePlanInDb(tenantId: string): Promise { const { tablesDB } = createAdminClient(); const result = await tablesDB.listRows({ databaseId: DATABASE_ID, tableId: TABLES.tenantSettings, queries: [Query.equal("tenantId", tenantId), Query.limit(1)], }); const row = result.rows[0]; if (!row) return; await tablesDB.updateRow(DATABASE_ID, TABLES.tenantSettings, row.$id, { plan: "free", planExpiresAt: null, planProvider: null, }); } // ── Mock checkout (geliştirme ortamı) ───────────────────────────────────────── export async function startMockCheckoutAction(formData: FormData): Promise { const plan = String(formData.get("plan") ?? "") as TenantPlan; if (plan !== "pro") throw new Error("Geçersiz plan."); const ctx = await requireTenant(); requireRole(ctx, ["owner"]); // Mock: direkt aktive et await activatePlanInDb(ctx.tenantId, plan, "mock"); redirect("/settings/billing?upgraded=1"); } // ── Shopier checkout ─────────────────────────────────────────────────────────── export async function startShopierCheckoutAction(formData: FormData): Promise { const plan = String(formData.get("plan") ?? "") as TenantPlan; if (plan !== "pro") throw new Error("Geçersiz plan."); const ctx = await requireTenant(); requireRole(ctx, ["owner"]); const storeUrl = getShopierPlanUrl(plan); if (!storeUrl) throw new Error("Shopier mağaza URL'i ayarlanmamış."); // Shopier mağaza sayfasına yönlendir — ödeme tamamlanınca webhook gelir redirect(storeUrl); } // ── Polar checkout ───────────────────────────────────────────────────────────── export async function startPolarCheckoutAction(formData: FormData): Promise { const plan = String(formData.get("plan") ?? "") as TenantPlan; if (plan !== "pro") throw new Error("Geçersiz plan."); const ctx = await requireTenant(); requireRole(ctx, ["owner"]); const catalog = PLAN_CATALOG[plan]; const orderId = generateOrderId(); const appUrl = process.env.APP_URL?.replace(/\/$/, "") ?? "http://localhost:3001"; const checkout = await createPolarCheckout({ orderId, tenantId: ctx.tenantId, userEmail: ctx.user.email, successUrl: `${appUrl}/settings/billing?upgraded=1`, }); // orderId'yi tenant_settings'e geçici olarak kaydedebiliriz ama // minimal yaklaşımda metadata.tenant_id yeterli — webhook okur void catalog; // fiyat bilgisi ileride log için kullanılabilir redirect(checkout.url); } // ── Unified entry point ──────────────────────────────────────────────────────── export async function startCheckoutAction(formData: FormData): Promise { if (isPolarEnabled()) return startPolarCheckoutAction(formData); if (isShopierEnabled()) return startShopierCheckoutAction(formData); return startMockCheckoutAction(formData); } // ── Downgrade ────────────────────────────────────────────────────────────────── export async function downgradeToFreeAction(): Promise { const ctx = await requireTenant(); requireRole(ctx, ["owner"]); await deactivatePlanInDb(ctx.tenantId); redirect("/settings/billing?downgraded=1"); } // ── Mevcut plan bilgisi ──────────────────────────────────────────────────────── export async function getCurrentPlanAction(): Promise<{ plan: TenantPlan; expiresAt: string | null; provider: string | null; }> { const ctx = await requireTenant(); const plan = getEffectivePlan(ctx); return { plan, expiresAt: ctx.settings?.planExpiresAt ?? null, provider: ctx.settings?.planProvider ?? null, }; }