207 lines
5.7 KiB
TypeScript
207 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))!)
|
|
},
|
|
})
|