Rocky_Mountain_Vending/convex/orders.ts

200 lines
5.7 KiB
TypeScript

// @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))!);
},
});