Jack
Clean import with LFS-tracked assets
6a30288
"use server";
import { getCart } from "@/server-actions/get-cart-details";
import { db } from "@/db/db";
import { addresses, carts, orders, products, stores } from "@/db/schema";
import { getCurrentUser } from "@/lib/auth";
import { routes } from "@/lib/routes";
import { CheckoutItem } from "@/lib/types";
import { and, eq, inArray, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
import { randomBytes } from "crypto";
import { z } from "zod";
const checkoutSchema = z.object({
storeSlug: z.string().trim().min(1),
name: z.string().trim().min(2),
email: z.string().trim().email(),
line1: z.string().trim().min(3),
line2: z.string().trim().optional(),
city: z.string().trim().min(2),
state: z.string().trim().min(2),
postalCode: z.string().trim().min(3),
country: z.string().trim().min(2),
cardName: z.string().trim().min(2),
cardNumber: z.string().trim().min(12),
expiry: z.string().trim().regex(/^\d{2}\/\d{2}$/),
cvc: z.string().trim().regex(/^\d{3,4}$/),
});
export async function submitOrder(values: z.infer<typeof checkoutSchema>) {
try {
const input = checkoutSchema.parse(values);
const cartId = Number(cookies().get("cartId")?.value);
if (isNaN(cartId)) {
throw new Error("Cart not found");
}
const { cartItems, cartItemDetails } = await getCart(cartId);
const [store] = await db
.select({
id: stores.id,
name: stores.name,
slug: stores.slug,
})
.from(stores)
.where(eq(stores.slug, input.storeSlug));
if (!store) {
throw new Error("Store not found");
}
const storeItems = cartItemDetails.filter((item) => item.storeId === store.id);
if (!storeItems.length) {
throw new Error("This store has no items in the cart");
}
const checkoutItems = storeItems.map((product) => {
const qty = cartItems.find((item) => item.id === product.id)?.qty ?? 0;
if (qty < 1) {
throw new Error("Cart quantity is invalid");
}
return {
id: product.id,
price: Number(product.price),
qty,
};
}) as CheckoutItem[];
const outOfStockItem = storeItems.find((product) => {
const qty = cartItems.find((item) => item.id === product.id)?.qty ?? 0;
return Number(product.inventory) < qty;
});
if (outOfStockItem) {
return {
error: true,
message: "Inventory changed",
action: `${outOfStockItem.name} no longer has enough stock to fulfill this order.`,
};
}
const [addressInsert] = await db.insert(addresses).values({
line1: input.line1,
line2: input.line2 || "",
city: input.city,
state: input.state,
postal_code: input.postalCode,
country: input.country,
});
const [storeOrderCount] = await db
.select({
count: sql<number>`count(*)`,
})
.from(orders)
.where(eq(orders.storeId, store.id));
const reference = `LOCAL-${randomBytes(4).toString("hex").toUpperCase()}`;
const subtotal = checkoutItems.reduce(
(sum, item) => sum + item.qty * Number(item.price),
0
);
const shipping = subtotal > 50 ? 0 : 7.5;
const total = subtotal + shipping;
const currentUser = await getCurrentUser();
await db.insert(orders).values({
prettyOrderId: Number(storeOrderCount?.count ?? 0) + 1,
storeId: store.id,
userId: currentUser?.id ?? null,
items: JSON.stringify(checkoutItems),
total: total.toFixed(2),
stripePaymentIntentId: reference,
stripePaymentIntentStatus: "paid",
name: input.name,
email: input.email,
createdAt: Math.floor(Date.now() / 1000),
addressId: Number(addressInsert.insertId),
});
const orderedProductIds = checkoutItems.map((item) => item.id);
const inventoryRows = await db
.select({
id: products.id,
inventory: products.inventory,
})
.from(products)
.where(inArray(products.id, orderedProductIds));
for (const item of checkoutItems) {
const currentInventory = inventoryRows.find((row) => row.id === item.id);
const nextInventory = Math.max(
0,
Number(currentInventory?.inventory ?? 0) - item.qty
);
await db
.update(products)
.set({
inventory: String(nextInventory),
})
.where(and(eq(products.id, item.id), eq(products.storeId, store.id)));
}
const remainingCartItems = cartItems.filter(
(item) => !orderedProductIds.includes(item.id)
);
await db
.update(carts)
.set({
items: JSON.stringify(remainingCartItems),
isClosed: remainingCartItems.length === 0,
paymentIntentId: null,
clientSecret: null,
})
.where(eq(carts.id, cartId));
revalidatePath(routes.cart);
revalidatePath(`${routes.checkout}/${store.slug}`);
revalidatePath(routes.account);
return {
error: false,
message: "Order placed",
action: "Your local demo checkout completed successfully.",
redirectTo: `${routes.checkout}/${store.slug}/${routes.orderConfirmation}?order=${reference}`,
};
} catch (error) {
console.log(error);
return {
error: true,
message: "Checkout failed",
action: "Please review your details and try again.",
};
}
}