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
This commit is contained in:
egecankomur
2026-05-14 23:31:27 +03:00
parent 2d67d49255
commit cbd67523fd
3 changed files with 22 additions and 12 deletions
@@ -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<string, React.ReactNode> = {
@@ -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 {
+8
View File
@@ -166,6 +166,14 @@ export async function startPolarCheckoutAction(formData: FormData): Promise<void
redirect(checkout.url);
}
// ── Discount code validation (server-side, reads DEV_DISCOUNT_CODE env var) ────
export async function validateDiscountCodeAction(code: string): Promise<number> {
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<string> {
+2 -3
View File
@@ -15,9 +15,8 @@ export const DISCOUNT_CODES: Record<string, number> = {
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;
}