186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
import { Elysia, t } from "elysia";
|
|
import prisma from "../lib/prisma";
|
|
export const couponsRoute = new Elysia({ prefix: "/coupons" })
|
|
// Get all coupons (Admin)
|
|
.get("/", async () => {
|
|
return await prisma.coupon.findMany({
|
|
orderBy: { createdAt: 'desc' }
|
|
});
|
|
})
|
|
// Get active coupons (Storefront)
|
|
.get("/active", async () => {
|
|
const now = new Date();
|
|
return await prisma.coupon.findMany({
|
|
where: {
|
|
isActive: true,
|
|
OR: [
|
|
{ expiresAt: null },
|
|
{ expiresAt: { gt: now } }
|
|
]
|
|
},
|
|
orderBy: { createdAt: 'desc' }
|
|
});
|
|
})
|
|
// Create new coupon (Admin)
|
|
.post("/", async ({ body, set }) => {
|
|
try {
|
|
const data = body;
|
|
// Check if code already exists
|
|
const existing = await prisma.coupon.findUnique({
|
|
where: { code: data.code.toUpperCase() }
|
|
});
|
|
if (existing) {
|
|
set.status = 400;
|
|
return { error: "Coupon code already exists" };
|
|
}
|
|
const coupon = await prisma.coupon.create({
|
|
data: {
|
|
code: data.code.toUpperCase(),
|
|
type: data.type,
|
|
value: data.value,
|
|
minPurchase: data.minPurchase || 0,
|
|
maxDiscount: data.maxDiscount || null,
|
|
usageLimit: data.usageLimit || null,
|
|
expiresAt: data.expiresAt ? new Date(data.expiresAt) : null,
|
|
isActive: data.isActive !== undefined ? data.isActive : true
|
|
}
|
|
});
|
|
return coupon;
|
|
}
|
|
catch (err) {
|
|
console.error("[Coupons] Create Error:", err);
|
|
set.status = 500;
|
|
return { error: "Failed to create coupon" };
|
|
}
|
|
}, {
|
|
body: t.Object({
|
|
code: t.String(),
|
|
type: t.String(),
|
|
value: t.Number(),
|
|
minPurchase: t.Optional(t.Number()),
|
|
maxDiscount: t.Optional(t.Nullable(t.Number())),
|
|
usageLimit: t.Optional(t.Nullable(t.Number())),
|
|
expiresAt: t.Optional(t.Nullable(t.String())),
|
|
isActive: t.Optional(t.Boolean())
|
|
})
|
|
})
|
|
// Update coupon (Admin)
|
|
.patch("/:id", async ({ params: { id }, body, set }) => {
|
|
try {
|
|
const data = body;
|
|
const coupon = await prisma.coupon.update({
|
|
where: { id: Number(id) },
|
|
data: {
|
|
...(data.code && { code: data.code.toUpperCase() }),
|
|
...(data.type && { type: data.type }),
|
|
...(data.value !== undefined && { value: data.value }),
|
|
...(data.minPurchase !== undefined && { minPurchase: data.minPurchase }),
|
|
...(data.maxDiscount !== undefined && { maxDiscount: data.maxDiscount }),
|
|
...(data.usageLimit !== undefined && { usageLimit: data.usageLimit }),
|
|
...(data.expiresAt !== undefined && { expiresAt: data.expiresAt ? new Date(data.expiresAt) : null }),
|
|
...(data.isActive !== undefined && { isActive: data.isActive })
|
|
}
|
|
});
|
|
return coupon;
|
|
}
|
|
catch (err) {
|
|
console.error("[Coupons] Update Error:", err);
|
|
set.status = 500;
|
|
return { error: "Failed to update coupon" };
|
|
}
|
|
}, {
|
|
body: t.Object({
|
|
code: t.Optional(t.String()),
|
|
type: t.Optional(t.String()),
|
|
value: t.Optional(t.Number()),
|
|
minPurchase: t.Optional(t.Number()),
|
|
maxDiscount: t.Optional(t.Nullable(t.Number())),
|
|
usageLimit: t.Optional(t.Nullable(t.Number())),
|
|
expiresAt: t.Optional(t.Nullable(t.String())),
|
|
isActive: t.Optional(t.Boolean())
|
|
})
|
|
})
|
|
// Delete coupon (Admin)
|
|
.delete("/:id", async ({ params: { id }, set }) => {
|
|
try {
|
|
await prisma.coupon.delete({
|
|
where: { id: Number(id) }
|
|
});
|
|
return { success: true };
|
|
}
|
|
catch (err) {
|
|
set.status = 500;
|
|
return { error: "Failed to delete coupon" };
|
|
}
|
|
})
|
|
// Validate coupon (Storefront)
|
|
.post("/validate", async ({ body, set }) => {
|
|
try {
|
|
const { code, cartTotal } = body;
|
|
const upperCode = code.toUpperCase();
|
|
const coupon = await prisma.coupon.findUnique({
|
|
where: { code: upperCode }
|
|
});
|
|
if (!coupon) {
|
|
set.status = 404;
|
|
return { valid: false, error: "Coupon not found" };
|
|
}
|
|
if (!coupon.isActive) {
|
|
set.status = 400;
|
|
return { valid: false, error: "Coupon is no longer active" };
|
|
}
|
|
if (coupon.expiresAt && new Date() > coupon.expiresAt) {
|
|
set.status = 400;
|
|
return { valid: false, error: "Coupon has expired" };
|
|
}
|
|
if (coupon.usageLimit && coupon.usedCount >= coupon.usageLimit) {
|
|
set.status = 400;
|
|
return { valid: false, error: "Coupon usage limit reached" };
|
|
}
|
|
if (cartTotal < coupon.minPurchase) {
|
|
set.status = 400;
|
|
return { valid: false, error: `Minimum purchase of ฿${coupon.minPurchase} required` };
|
|
}
|
|
// Calculate discount amount
|
|
let discountAmount = 0;
|
|
if (coupon.type === "PERCENTAGE") {
|
|
discountAmount = cartTotal * (coupon.value / 100);
|
|
if (coupon.maxDiscount && discountAmount > coupon.maxDiscount) {
|
|
discountAmount = coupon.maxDiscount;
|
|
}
|
|
}
|
|
else if (coupon.type === "FIXED_AMOUNT") {
|
|
discountAmount = coupon.value;
|
|
}
|
|
else if (coupon.type === "FREE_SHIPPING") {
|
|
// We handle free shipping separately, returning discountAmount = 0 but a flag
|
|
return {
|
|
valid: true,
|
|
coupon,
|
|
discountAmount: 0,
|
|
isFreeShipping: true
|
|
};
|
|
}
|
|
// Ensure discount doesn't exceed cart total
|
|
if (discountAmount > cartTotal) {
|
|
discountAmount = cartTotal;
|
|
}
|
|
return {
|
|
valid: true,
|
|
coupon,
|
|
discountAmount,
|
|
isFreeShipping: false
|
|
};
|
|
}
|
|
catch (err) {
|
|
console.error("[Coupons] Validate Error:", err);
|
|
set.status = 500;
|
|
return { error: "Failed to validate coupon" };
|
|
}
|
|
}, {
|
|
body: t.Object({
|
|
code: t.String(),
|
|
cartTotal: t.Number()
|
|
})
|
|
});
|
|
//# sourceMappingURL=coupons.js.map
|