Improve error emails and invoice mailto with full order details
Admin error email now includes: Square error code, category, and detail string; full customer name/email/phone; fulfillment type, address, date/time, and delivery instructions; per-item options, colors, and notes; itemised price breakdown with delivery and estimated tax; idempotency key for debugging. Customer invoice mailto now includes: full name/email/phone; per-item options, colors, and notes; delivery date, address, and instructions; itemised subtotal/delivery/tax/total breakdown. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
84167c1ae8
commit
8aaf2f1dcb
@ -515,21 +515,51 @@ export async function POST(req: NextRequest) {
|
|||||||
if (li.note) parts.push(` Note: ${li.note}`)
|
if (li.note) parts.push(` Note: ${li.note}`)
|
||||||
return parts.join('\n')
|
return parts.join('\n')
|
||||||
}).join('\n')
|
}).join('\n')
|
||||||
const fulfillmentStr = deliveryAddress
|
|
||||||
? `Delivery to ${deliveryAddress}${deliverySlotISO ? ` at ${new Date(deliverySlotISO).toLocaleString('en-US', { timeZone: 'America/New_York', dateStyle: 'medium', timeStyle: 'short' })}` : ''}`
|
const fmtET = (iso: string) =>
|
||||||
: pickupSlotISO ? `Pickup at ${new Date(pickupSlotISO).toLocaleString('en-US', { timeZone: 'America/New_York', dateStyle: 'medium', timeStyle: 'short' })}` : 'Unknown'
|
new Date(iso).toLocaleString('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'short' })
|
||||||
|
|
||||||
|
const fulfillmentLines = deliveryAddress
|
||||||
|
? [
|
||||||
|
`Type: Delivery`,
|
||||||
|
`Address: ${deliveryAddress}`,
|
||||||
|
deliverySlotISO ? `Date/time: ${fmtET(deliverySlotISO)}` : null,
|
||||||
|
deliveryNotes ? `Instructions: ${deliveryNotes}` : null,
|
||||||
|
].filter(Boolean)
|
||||||
|
: pickupSlotISO
|
||||||
|
? [`Type: Pickup`, `Date/time: ${fmtET(pickupSlotISO)}`]
|
||||||
|
: [`Type: Unknown`]
|
||||||
|
|
||||||
|
const itemSubtotal = lineItems.reduce((s, li) => s + li.priceCents * li.quantity, 0)
|
||||||
|
const estTax = Math.round(itemSubtotal * 0.0635)
|
||||||
|
const estTotal = itemSubtotal + (deliveryCents ?? 0) + estTax
|
||||||
|
|
||||||
|
const squareDetail = squareErrors?.[0]?.detail ?? ''
|
||||||
|
const squareCat = squareErrors?.[0]?.category ?? ''
|
||||||
|
|
||||||
await sendAdminErrorAlert({
|
await sendAdminErrorAlert({
|
||||||
subject: 'Checkout error',
|
subject: 'Checkout error',
|
||||||
message: [
|
message: [
|
||||||
err instanceof Error ? err.message : String(err),
|
`Error code: ${code || '(none)'}`,
|
||||||
|
squareCat ? `Category: ${squareCat}` : null,
|
||||||
|
squareDetail ? `Detail: ${squareDetail}` : null,
|
||||||
'',
|
'',
|
||||||
`Customer: ${customerName ?? '(unknown)'} <${customerEmail ?? '(none)'}> ${customerPhone ?? ''}`,
|
`Customer: ${customerName ?? '(unknown)'}`,
|
||||||
`${fulfillmentStr}`,
|
`Email: ${customerEmail ?? '(none)'}`,
|
||||||
|
`Phone: ${customerPhone ?? '(none)'}`,
|
||||||
|
'',
|
||||||
|
...fulfillmentLines,
|
||||||
'',
|
'',
|
||||||
'Order:',
|
'Order:',
|
||||||
itemLines,
|
itemLines,
|
||||||
].join('\n'),
|
'',
|
||||||
context: { code: code || '(none)' },
|
`Items: $${(itemSubtotal / 100).toFixed(2)}`,
|
||||||
|
deliveryCents ? `Delivery: $${(deliveryCents / 100).toFixed(2)}` : null,
|
||||||
|
`Est. tax: $${(estTax / 100).toFixed(2)}`,
|
||||||
|
`Est. total: $${(estTotal / 100).toFixed(2)}`,
|
||||||
|
idempotencyKey ? `\nIdempotency key: ${idempotencyKey}` : null,
|
||||||
|
].filter(Boolean).join('\n'),
|
||||||
|
context: {},
|
||||||
})
|
})
|
||||||
} catch { /* best effort */ }
|
} catch { /* best effort */ }
|
||||||
})()
|
})()
|
||||||
|
|||||||
@ -82,6 +82,9 @@ const SDK_URL =
|
|||||||
|
|
||||||
function buildMailtoLink(payload: CheckoutPayload): string {
|
function buildMailtoLink(payload: CheckoutPayload): string {
|
||||||
const fmtCents = (c: number) => `$${(c / 100).toFixed(2)}`
|
const fmtCents = (c: number) => `$${(c / 100).toFixed(2)}`
|
||||||
|
const fmtDate = (iso: string) =>
|
||||||
|
new Date(iso).toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' })
|
||||||
|
|
||||||
const items = payload.lineItems
|
const items = payload.lineItems
|
||||||
.map((li) => {
|
.map((li) => {
|
||||||
const lines = [` • ${li.quantity}× ${li.name} — ${fmtCents(li.priceCents * li.quantity)}`]
|
const lines = [` • ${li.quantity}× ${li.name} — ${fmtCents(li.priceCents * li.quantity)}`]
|
||||||
@ -91,11 +94,28 @@ function buildMailtoLink(payload: CheckoutPayload): string {
|
|||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
const fulfillment = payload.deliverySlotISO
|
|
||||||
? `Delivery: ${new Date(payload.deliverySlotISO).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })} to ${payload.deliveryAddress ?? ''}`
|
const fulfillmentLines: string[] = payload.deliverySlotISO
|
||||||
|
? [
|
||||||
|
`Delivery date: ${fmtDate(payload.deliverySlotISO)}`,
|
||||||
|
`Address: ${payload.deliveryAddress ?? '(not set)'}`,
|
||||||
|
...(payload.deliveryNotes ? [`Instructions: ${payload.deliveryNotes}`] : []),
|
||||||
|
]
|
||||||
: payload.pickupSlotISO
|
: payload.pickupSlotISO
|
||||||
? `Pickup: ${new Date(payload.pickupSlotISO).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })}`
|
? [`Pickup: ${fmtDate(payload.pickupSlotISO)}`]
|
||||||
: 'Fulfillment: not set'
|
: [`Fulfillment: not set`]
|
||||||
|
|
||||||
|
const itemSubtotal = payload.lineItems.reduce((s, li) => s + li.priceCents * li.quantity, 0)
|
||||||
|
const deliveryCents = payload.deliveryCents ?? 0
|
||||||
|
const estTax = Math.round(itemSubtotal * 0.0635)
|
||||||
|
|
||||||
|
const priceLines = [
|
||||||
|
`Items: ${fmtCents(itemSubtotal)}`,
|
||||||
|
deliveryCents ? `Delivery: ${fmtCents(deliveryCents)}` : null,
|
||||||
|
`Est. tax: ${fmtCents(estTax)}`,
|
||||||
|
`Total: ${fmtCents(payload.grandTotal)}`,
|
||||||
|
].filter(Boolean).join('\n')
|
||||||
|
|
||||||
const body = [
|
const body = [
|
||||||
`Hi Beach Party Balloons,`,
|
`Hi Beach Party Balloons,`,
|
||||||
``,
|
``,
|
||||||
@ -108,8 +128,9 @@ function buildMailtoLink(payload: CheckoutPayload): string {
|
|||||||
`Order:`,
|
`Order:`,
|
||||||
items,
|
items,
|
||||||
``,
|
``,
|
||||||
fulfillment,
|
...fulfillmentLines,
|
||||||
`Total: ${fmtCents(payload.grandTotal)}`,
|
``,
|
||||||
|
priceLines,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const addr = atob('aW5mb0BiZWFjaHBhcnR5YmFsbG9vbnMuY29t')
|
const addr = atob('aW5mb0BiZWFjaHBhcnR5YmFsbG9vbnMuY29t')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user