// @ts-nocheck import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; const orderStatus = v.union( v.literal("pending"), v.literal("paid"), v.literal("fulfilled"), v.literal("cancelled"), v.literal("refunded"), ); const orderItem = v.object({ productId: v.optional(v.id("products")), stripeProductId: v.optional(v.string()), stripePriceId: v.string(), productName: v.string(), image: v.optional(v.string()), price: v.number(), quantity: v.number(), }); const shippingAddress = v.object({ name: v.optional(v.string()), address: v.optional(v.string()), city: v.optional(v.string()), state: v.optional(v.string()), zipCode: v.optional(v.string()), country: v.optional(v.string()), }); async function attachItems(ctx: any, order: any) { const items = await ctx.db .query("orderItems") .withIndex("by_orderId", (q: any) => q.eq("orderId", order._id)) .collect(); return { id: order._id, customerEmail: order.customerEmail, customerName: order.customerName, totalAmount: order.totalAmount, currency: order.currency, status: order.status, stripeSessionId: order.stripeSessionId ?? null, paymentIntentId: order.stripePaymentIntentId ?? null, createdAt: new Date(order.createdAt).toISOString(), updatedAt: new Date(order.updatedAt).toISOString(), shippingAddress: order.shippingAddress, items: items.map((item: any) => ({ productId: item.productId ?? null, productName: item.productName, price: item.price, quantity: item.quantity, priceId: item.stripePriceId, image: item.image, })), }; } export const listAdmin = query({ args: { status: v.optional(orderStatus), search: v.optional(v.string()), }, handler: async (ctx, args) => { let orders = await ctx.db .query("orders") .withIndex("by_createdAt") .collect(); orders = orders.sort((a, b) => b.createdAt - a.createdAt); if (args.status) { orders = orders.filter((order) => order.status === args.status); } const search = args.search?.trim().toLowerCase(); if (search) { orders = orders.filter((order) => { return ( order.customerEmail.toLowerCase().includes(search) || order.customerName?.toLowerCase().includes(search) || order._id.toLowerCase().includes(search) ); }); } return await Promise.all(orders.map((order) => attachItems(ctx, order))); }, }); export const getMetrics = query({ args: {}, handler: async (ctx) => { const orders = await ctx.db.query("orders").collect(); const paidOrders = orders.filter((order) => order.status === "paid" || order.status === "fulfilled"); const revenue = paidOrders.reduce((sum, order) => sum + order.totalAmount, 0); return { totalOrders: orders.length, totalRevenue: revenue, pendingOrders: orders.filter((order) => order.status === "pending").length, completedOrders: orders.filter((order) => order.status === "fulfilled").length, paidOrders: orders.filter((order) => order.status === "paid").length, refundedOrders: orders.filter((order) => order.status === "refunded").length, }; }, }); export const upsertStripeOrder = mutation({ args: { customerEmail: v.string(), customerName: v.optional(v.string()), status: orderStatus, totalAmount: v.number(), currency: v.string(), stripeSessionId: v.optional(v.string()), stripePaymentIntentId: v.optional(v.string()), shippingAddress: v.optional(shippingAddress), items: v.array(orderItem), }, handler: async (ctx, args) => { const now = Date.now(); let existing = args.stripeSessionId ? await ctx.db .query("orders") .withIndex("by_stripeSessionId", (q) => q.eq("stripeSessionId", args.stripeSessionId), ) .unique() : null; let orderId; if (existing) { orderId = existing._id; await ctx.db.patch(orderId, { customerEmail: args.customerEmail, customerName: args.customerName, status: args.status, totalAmount: args.totalAmount, currency: args.currency, stripePaymentIntentId: args.stripePaymentIntentId, shippingAddress: args.shippingAddress, updatedAt: now, }); const existingItems = await ctx.db .query("orderItems") .withIndex("by_orderId", (q) => q.eq("orderId", orderId)) .collect(); for (const item of existingItems) { await ctx.db.delete(item._id); } } else { orderId = await ctx.db.insert("orders", { customerEmail: args.customerEmail, customerName: args.customerName, status: args.status, totalAmount: args.totalAmount, currency: args.currency, stripeSessionId: args.stripeSessionId, stripePaymentIntentId: args.stripePaymentIntentId, shippingAddress: args.shippingAddress, createdAt: now, updatedAt: now, }); } for (const item of args.items) { await ctx.db.insert("orderItems", { orderId, productId: item.productId, stripeProductId: item.stripeProductId, stripePriceId: item.stripePriceId, productName: item.productName, image: item.image, price: item.price, quantity: item.quantity, createdAt: now, }); } return await attachItems(ctx, (await ctx.db.get(orderId))!); }, }); export const updateStatus = mutation({ args: { id: v.id("orders"), status: orderStatus, }, handler: async (ctx, args) => { await ctx.db.patch(args.id, { status: args.status, updatedAt: Date.now(), }); return await attachItems(ctx, (await ctx.db.get(args.id))!); }, });