From cbd67523fd5fd328967ad2d7629099511d1cb485 Mon Sep 17 00:00:00 2001 From: egecankomur Date: Thu, 14 May 2026 23:31:27 +0300 Subject: [PATCH] fix(billing): validate discount code via server action, use DEV_DISCOUNT_CODE server-only env var - validateDiscountCodeAction: new server action, reads DEV_DISCOUNT_CODE at runtime (not baked into client bundle like NEXT_PUBLIC_ vars) - applyDiscount(): calls server action instead of client-side check - Removes validateDiscountCode client-side import from upgrade-section --- .../billing/components/upgrade-section.tsx | 21 +++++++++++-------- src/lib/appwrite/subscription-actions.ts | 8 +++++++ src/lib/appwrite/subscription-types.ts | 5 ++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx b/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx index 77180b5..66a282b 100644 --- a/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx +++ b/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx @@ -11,8 +11,9 @@ import { startCheckoutAction, getPayTRTokenAction, requestEnterpriseInquiryAction, + validateDiscountCodeAction, } from "@/lib/appwrite/subscription-actions"; -import { PLAN_CATALOG, PLAN_RANK, planPriceDisplay, planPrice, validateDiscountCode } from "@/lib/appwrite/subscription-types"; +import { PLAN_CATALOG, PLAN_RANK, planPriceDisplay, planPrice } from "@/lib/appwrite/subscription-types"; import type { TenantPlan, PlanPeriod } from "@/lib/appwrite/schema"; const PLAN_ICONS: Record = { @@ -69,14 +70,16 @@ export function UpgradeSection({ function applyDiscount() { if (!discountCode.trim()) return; - const fraction = validateDiscountCode(discountCode); - if (fraction === null) { - toast.error("Geçersiz indirim kodu."); - setDiscountApplied(null); - return; - } - setDiscountApplied(fraction); - toast.success(`İndirim kodu uygulandı: %${Math.round(fraction * 100)} indirim`); + startTransition(async () => { + try { + const fraction = await validateDiscountCodeAction(discountCode.trim()); + setDiscountApplied(fraction); + toast.success(`İndirim kodu uygulandı: %${Math.round(fraction * 100)} indirim`); + } catch { + toast.error("Geçersiz indirim kodu."); + setDiscountApplied(null); + } + }); } function getDisplayPrice(plan: typeof plans[0]): number { diff --git a/src/lib/appwrite/subscription-actions.ts b/src/lib/appwrite/subscription-actions.ts index de6ed13..9436de1 100644 --- a/src/lib/appwrite/subscription-actions.ts +++ b/src/lib/appwrite/subscription-actions.ts @@ -166,6 +166,14 @@ export async function startPolarCheckoutAction(formData: FormData): Promise { + const fraction = validateDiscountCode(code); + if (fraction === null) throw new Error("Geçersiz indirim kodu."); + return fraction; +} + // ── PayTR checkout ───────────────────────────────────────────────────────────── export async function getPayTRTokenAction(formData: FormData): Promise { diff --git a/src/lib/appwrite/subscription-types.ts b/src/lib/appwrite/subscription-types.ts index 917edec..79adbf5 100644 --- a/src/lib/appwrite/subscription-types.ts +++ b/src/lib/appwrite/subscription-types.ts @@ -15,9 +15,8 @@ export const DISCOUNT_CODES: Record = { export function validateDiscountCode(code: string): number | null { const normalized = code.trim().toUpperCase(); - // NEXT_PUBLIC_DEV_DISCOUNT_CODE: forces price to 1 TL for testing - // Set in Coolify env vars — remove when done testing - const devCode = process.env.NEXT_PUBLIC_DEV_DISCOUNT_CODE?.trim().toUpperCase(); + // DEV_DISCOUNT_CODE: server-only env var, forces price to 1 TL for testing + const devCode = process.env.DEV_DISCOUNT_CODE?.trim().toUpperCase(); if (devCode && normalized === devCode) return 1; return DISCOUNT_CODES[normalized] ?? null; }