Next.js ile Kampanya & Kupon Sistemi
Bu makalemde Next.js App Router mimarisini kullanarak, gerçek hayatta e-ticaret ve SaaS projelerindeki ihtiyaçları kapsayan kampanya ve kupon sistemini tasarlamaya çalışacağım.
Bu makalenin içeriği; veri modeli, iş kuralları, API katmanı, güvenlik, performans ve frontend entegrasyonunu kapsamaktadır.
1. Kampanya & Kupon Mantığını Ayırmak
Çoğu projede yapılan temel hata, kampanya ile kuponu aynı olaymış gibi ele almaktır.
Kampanya: Otomatik uygulanır. (%20 indirim, sepette 3 ürün olunca geçerli gibi)
Kupon: Kullanıcı aksiyonu gerektirir. (kod girişi gibi)
Bu 2 durumu da ayırmak;
- İş kurallarını sadeleştirir,
- Performans kazandırır,
- Yönetimi kolaylaştırır.
2. Temel Veri Modeli
Modelleme olarak Prisma ORM üzerinden kurgulayacağım, fakat diğer ORM paketleri ile de uyarlayabilirsiniz.
Kampanya Modeli
model Campaign {
id String @id @default(uuid())
title String
description String?
discountType DiscountType
discountValue Float
minCartTotal Float?
startDate DateTime
endDate DateTime
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
Kupon Modeli
model Coupon {
id String @id @default(uuid())
code String @unique
discountType DiscountType
discountValue Float
usageLimit Int?
usedCount Int @default(0)
expiresAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
Ortam Enum
enum DiscountType {
PERCENT
FIXED
}
3. İndirim Hesaplama Stratejisi
İndirim hesaplama işlemi asla frontend kısmında olmamalıdır. Bu durum hem güvenlik hem de tutarlılık açısından kritiktir.
Hesaplama Fonksiyonu
export function calculateDiscount(
total: number,
type: 'PERCENT' | 'FIXED',
value: number
) {
if (type === 'PERCENT') {
return total - total * (value / 100)
}
return Math.max(total - value, 0)
}
Bu fonksiyon hem kampanya hem de kupon için yeniden kullanılabilir.
4. Aktif Kampanya Fonksiyonu
Kampanya Servisi
export async function getActiveCampaign(total: number) {
return prisma.campaign.findFirst({
where: {
isActive: true,
startDate: { lte: new Date() },
endDate: { gte: new Date() },
OR: [
{ minCartTotal: null },
{ minCartTotal: { lte: total } }
]
},
orderBy: { discountValue: 'desc' }
})
}
5. Kupon Doğrulama API'si (App Router)
API Route
// app/api/coupon/validate/route.ts
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const { code, total } = await req.json()
const coupon = await prisma.coupon.findUnique({
where: { code }
})
if (!coupon || !coupon.isActive) {
return NextResponse.json({ error: 'Geçersiz kupon' }, { status: 400 })
}
if (coupon.expiresAt && coupon.expiresAt < new Date()) {
return NextResponse.json({ error: 'Kupon süresi dolmuş' }, { status: 400 })
}
if (coupon.usageLimit && coupon.usedCount >= coupon.usageLimit) {
return NextResponse.json({ error: 'Kupon kullanım limiti doldu' }, { status: 400 })
}
const discountedTotal = calculateDiscount(
total,
coupon.discountType,
coupon.discountValue
)
return NextResponse.json({ discountedTotal })
}
6. Güvenli Kupon Kullanımı
Kupon kullanım sayısı ödeme başarıyla tamamlandıktan sonra arttırılmalıdır.
await prisma.coupon.update({
where: { id: couponId },
data: {
usedCount: { increment: 1 }
}
})
Bu işlem genellikle payment webhook içinde yapılır.
7. Frontend Entegrasyonu (Client Component)
'use client'
import { useState } from 'react'
export default function CouponInput({ total }: { total: number }) {
const [code, setCode] = useState('')
const [price, setPrice] = useState(total)
const applyCoupon = async () => {
const res = await fetch('/api/coupon/validate', {
method: 'POST',
body: JSON.stringify({ code, total })
})
const data = await res.json()
if (res.ok) {
setPrice(data.discountedTotal)
}
}
return (
<div>
<input value={code} onChange={e => setCode(e.target.value)} />
<button onClick={applyCoupon}>Uygula</button>
<p>Toplam: {price} ₺</p>
</div>
)
}
8. Kampanya & Kupon Çakışmasını Önleme
- Kampanya varsa kuponu devre dışı bırak,
- Sadece en yüksek indirimi uygula.
Sonuç
Bu makalemde güvenli, ölçeklenebilir ve gerçek projelere uygun bir kampanya & kupon sistemi tasarlamaya çalıştım. Bu yapı; e-ticaret, SaaS ve üyelik tabanlı sistemlerde kullanılabilir ve ya genişletilebilir.