feat: promo code
This commit is contained in:
parent
0ddb431536
commit
bcac2584ed
12 changed files with 1908 additions and 54 deletions
112
website/src/routes/api/promo/verify/+server.ts
Normal file
112
website/src/routes/api/promo/verify/+server.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { auth } from '$lib/auth';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { user, promoCode, promoCodeRedemption } from '$lib/server/db/schema';
|
||||
import { eq, and, count } from 'drizzle-orm';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const session = await auth.api.getSession({ headers: request.headers });
|
||||
if (!session?.user) {
|
||||
throw error(401, 'Not authenticated');
|
||||
}
|
||||
|
||||
const { code } = await request.json();
|
||||
|
||||
if (!code || typeof code !== 'string' || code.trim().length === 0) {
|
||||
return json({ error: 'Promo code is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const normalizedCode = code.trim().toUpperCase();
|
||||
const userId = Number(session.user.id);
|
||||
|
||||
return await db.transaction(async (tx) => {
|
||||
const [promoData] = await tx
|
||||
.select({
|
||||
id: promoCode.id,
|
||||
code: promoCode.code,
|
||||
rewardAmount: promoCode.rewardAmount,
|
||||
maxUses: promoCode.maxUses,
|
||||
expiresAt: promoCode.expiresAt,
|
||||
isActive: promoCode.isActive,
|
||||
description: promoCode.description
|
||||
})
|
||||
.from(promoCode)
|
||||
.where(eq(promoCode.code, normalizedCode))
|
||||
.limit(1);
|
||||
|
||||
if (!promoData) {
|
||||
return json({ error: 'Invalid promo code' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!promoData.isActive) {
|
||||
return json({ error: 'This promo code is no longer active' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (promoData.expiresAt && new Date() > promoData.expiresAt) {
|
||||
return json({ error: 'This promo code has expired' }, { status: 400 });
|
||||
}
|
||||
|
||||
const [existingRedemption] = await tx
|
||||
.select({ id: promoCodeRedemption.id })
|
||||
.from(promoCodeRedemption)
|
||||
.where(and(
|
||||
eq(promoCodeRedemption.userId, userId),
|
||||
eq(promoCodeRedemption.promoCodeId, promoData.id)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (existingRedemption) {
|
||||
return json({ error: 'You have already used this promo code' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (promoData.maxUses !== null) {
|
||||
const [{ totalUses }] = await tx
|
||||
.select({ totalUses: count() })
|
||||
.from(promoCodeRedemption)
|
||||
.where(eq(promoCodeRedemption.promoCodeId, promoData.id));
|
||||
|
||||
if (totalUses >= promoData.maxUses) {
|
||||
return json({ error: 'This promo code has reached its usage limit' }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const [userData] = await tx
|
||||
.select({ baseCurrencyBalance: user.baseCurrencyBalance })
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!userData) {
|
||||
throw error(404, 'User not found');
|
||||
}
|
||||
|
||||
const currentBalance = Number(userData.baseCurrencyBalance || 0);
|
||||
const rewardAmount = Number(promoData.rewardAmount);
|
||||
const newBalance = currentBalance + rewardAmount;
|
||||
|
||||
await tx
|
||||
.update(user)
|
||||
.set({
|
||||
baseCurrencyBalance: newBalance.toFixed(8),
|
||||
updatedAt: new Date()
|
||||
})
|
||||
.where(eq(user.id, userId));
|
||||
|
||||
await tx
|
||||
.insert(promoCodeRedemption)
|
||||
.values({
|
||||
userId,
|
||||
promoCodeId: promoData.id,
|
||||
rewardAmount: rewardAmount.toFixed(8)
|
||||
});
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
message: promoData.description || `Promo code redeemed! You received $${rewardAmount.toFixed(2)}`,
|
||||
rewardAmount,
|
||||
newBalance,
|
||||
code: promoData.code
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in a new issue