From 3e90928986b8ef3a86cdf9a5e396fe89a6a7296b Mon Sep 17 00:00:00 2001 From: Andymick Date: Sun, 19 Jul 2026 07:50:55 +0100 Subject: [PATCH] Feature: Add 24-hour cleanup for incomplete orders and update status display - Create cleanupOrders utility to delete pending orders older than 24 hours - Update admin notifications to only count recent pending orders (< 24h old) - Automatically call cleanup when customer initiates checkout - Change status display from "PENDING" to "Waiting payment" for customer-facing views - Update both customer account page and admin orders page with new status label - Incomplete orders automatically purged, reducing admin clutter Co-Authored-By: Claude Haiku 4.5 --- .claude/settings.local.json | 3 ++- src/app/account/page.tsx | 1 + src/app/admin/orders/page.tsx | 9 ++++++++- src/app/api/checkout/route.ts | 4 ++++ src/components/AdminNotifications.tsx | 11 ++++++++++- src/lib/cleanupOrders.ts | 23 +++++++++++++++++++++++ 6 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/lib/cleanupOrders.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5cbe86c..8fa1079 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -88,7 +88,8 @@ "Bash(git commit -m 'Add: Prominent '\\\\''Shop plain items'\\\\'' section to homepage *)", "Bash(git commit -m 'Feature: Royal Mail shipping integration with weight-based postage *)", "Bash(git commit -m 'Add: Weight field to product admin pages *)", - "Bash(git commit -m 'Add: Shipping selector component and checkout integration *)" + "Bash(git commit -m 'Add: Shipping selector component and checkout integration *)", + "Bash(node clear-pending-orders.js)" ] } } diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index ed099da..b22727b 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -13,6 +13,7 @@ function formatPrice(cents: number) { function statusLabel(status: string) { if (status === 'PAID') return 'Confirmed'; if (status === 'FAILED') return 'Failed'; + if (status === 'PENDING') return 'Waiting payment'; return 'Processing'; } diff --git a/src/app/admin/orders/page.tsx b/src/app/admin/orders/page.tsx index ff0d2e9..59cc2e1 100644 --- a/src/app/admin/orders/page.tsx +++ b/src/app/admin/orders/page.tsx @@ -7,6 +7,13 @@ function formatPrice(cents: number) { return `£${(cents / 100).toFixed(2)}`; } +function statusLabel(status: string) { + if (status === 'PAID') return 'Confirmed'; + if (status === 'FAILED') return 'Failed'; + if (status === 'PENDING') return 'Waiting payment'; + return 'Processing'; +} + const STATUS_STYLES: Record = { PAID: 'bg-splash-blue/10 text-splash-blue', PENDING: 'bg-splash-orange/10 text-splash-orange', @@ -121,7 +128,7 @@ export default async function OrdersPage({ searchParams }: { searchParams: { arc )} {formatPrice(order.total)} - {order.status} + {statusLabel(order.status)}
diff --git a/src/app/api/checkout/route.ts b/src/app/api/checkout/route.ts index 89fc260..252ffdc 100644 --- a/src/app/api/checkout/route.ts +++ b/src/app/api/checkout/route.ts @@ -6,6 +6,7 @@ import { getEffectivePrice } from '@/lib/pricing'; import { fetchActivePromotions } from '@/lib/promotions'; import { checkRateLimit, getClientIp } from '@/lib/rateLimit'; import { getRoyalMailShippingQuotes } from '@/lib/royalmail-shipping'; +import { cleanupPendingOrders } from '@/lib/cleanupOrders'; type CheckoutCartItem = { productId: string; @@ -51,6 +52,9 @@ export async function POST(req: Request) { ); } + // Clean up old incomplete orders (non-blocking) + cleanupPendingOrders().catch((err) => console.error('Cleanup error:', err)); + const body = await req.json(); console.log('Checkout request received with items:', body.items?.length, 'customerId:', body.customerId); const items: CheckoutCartItem[] = body.items ?? []; diff --git a/src/components/AdminNotifications.tsx b/src/components/AdminNotifications.tsx index db9954d..45b600c 100644 --- a/src/components/AdminNotifications.tsx +++ b/src/components/AdminNotifications.tsx @@ -1,15 +1,24 @@ import { prisma } from '@/lib/prisma'; +import { cleanupPendingOrders } from '@/lib/cleanupOrders'; import AdminNotificationsBadge from './AdminNotificationsBadge'; export default async function AdminNotifications() { try { + // Clean up old incomplete orders (older than 24 hours) + await cleanupPendingOrders(); + + // Only count pending orders that are less than 24 hours old (still waiting for payment) + const recentThreshold = new Date(Date.now() - 24 * 60 * 60 * 1000); const [ pendingOrders, designApprovalsInReview, photoRequests, ] = await Promise.all([ prisma.order.count({ - where: { status: 'PENDING' }, + where: { + status: 'PENDING', + createdAt: { gte: recentThreshold }, + }, }), prisma.designApproval.count({ where: { status: 'IN_REVIEW' }, diff --git a/src/lib/cleanupOrders.ts b/src/lib/cleanupOrders.ts new file mode 100644 index 0000000..ef3dcd5 --- /dev/null +++ b/src/lib/cleanupOrders.ts @@ -0,0 +1,23 @@ +import { prisma } from './prisma'; + +export async function cleanupPendingOrders() { + try { + const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + + const result = await prisma.order.deleteMany({ + where: { + status: 'PENDING', + createdAt: { lt: twentyFourHoursAgo }, + }, + }); + + if (result.count > 0) { + console.log(`✓ Cleaned up ${result.count} incomplete orders older than 24 hours`); + } + + return result.count; + } catch (error) { + console.error('Error cleaning up pending orders:', error); + return 0; + } +}