diff --git a/estore/src/app/api/checkout/route.ts b/estore/src/app/api/checkout/route.ts index 1664ec5..2bbea90 100644 --- a/estore/src/app/api/checkout/route.ts +++ b/estore/src/app/api/checkout/route.ts @@ -293,12 +293,13 @@ export async function POST(req: NextRequest) { // We capture only AFTER the calendar write succeeds. If the calendar fails, // we void the hold — the customer is never charged without a confirmed booking. const payment = await createSquarePayment({ - sourceId: sourceId, - orderId: order.id!, - amountMoney: { amount: order.totalMoney.amount!, currency: 'USD' }, + sourceId: sourceId, + orderId: order.id!, + amountMoney: { amount: order.totalMoney.amount!, currency: 'USD' }, note, - idempotencyKey: paymentIdempotencyKey, - autocomplete: false, // hold only — capture after calendar write + idempotencyKey: paymentIdempotencyKey, + autocomplete: false, // hold only — capture after calendar write + buyerEmailAddress: customerEmail, }) if (!payment?.id) throw new Error('Pre-authorization returned no payment ID') diff --git a/estore/src/components/CartDrawer.tsx b/estore/src/components/CartDrawer.tsx index 9b07238..7ec395f 100644 --- a/estore/src/components/CartDrawer.tsx +++ b/estore/src/components/CartDrawer.tsx @@ -272,19 +272,12 @@ export default function CartDrawer() { }), }, { - name: `18" ${e.vinylShapeName ?? 'Shape'} Balloon`, + name: `18" ${e.vinylShapeName ?? 'Shape'} Balloon w/ Vinyl`, quantity: e.quantity, - priceCents: e.vinylShapePriceCents ?? 450, + priceCents: (e.vinylShapePriceCents ?? 450) + vinylCents, catalogItemId: e.vinylShapeVariationId, - note: `Vinyl add-on for: ${e.product.name}`, - }, - { - name: 'Custom Vinyl', - quantity: e.quantity, - priceCents: vinylCents, - catalogItemId: e.product.variations[0]?.id ?? e.product.id, note: [ - `Add-on for: ${e.product.name}`, + `Vinyl add-on for: ${e.product.name}`, `Text: "${e.vinylText}"`, e.vinylFontName ? `Font: ${e.vinylFontName}` : null, ].filter(Boolean).join(' | ') || undefined, @@ -423,10 +416,14 @@ export default function CartDrawer() { if (!optIds.length) return null const ml = entry.product.modifiers?.find((m) => m.id === listId) if (!ml) return null - const names = optIds.map((id) => ml.options.find((o) => o.id === id)?.name ?? id) + const labels = optIds.map((id) => { + const opt = ml.options.find((o) => o.id === id) + if (!opt) return id + return opt.priceDelta ? `${opt.name} (+${fmt(opt.priceDelta)})` : opt.name + }) return (
- {ml.name}: {names.join(', ')} + {ml.name}: {labels.join(', ')}
) })} diff --git a/estore/src/lib/square.ts b/estore/src/lib/square.ts index 427b873..37ca90b 100644 --- a/estore/src/lib/square.ts +++ b/estore/src/lib/square.ts @@ -273,7 +273,7 @@ export async function createSquareOrder(params: { ? params.fulfillment.type === 'delivery' ? [{ type: 'DELIVERY', - state: 'PROPOSED', + state: 'RESERVED', deliveryDetails: { recipient: { displayName: params.fulfillment.recipientName, @@ -288,7 +288,7 @@ export async function createSquareOrder(params: { }] : [{ type: 'PICKUP', - state: 'PROPOSED', + state: 'RESERVED', pickupDetails: { recipient: { displayName: params.fulfillment.recipientName, @@ -339,22 +339,24 @@ export async function createSquareOrder(params: { } export async function createSquarePayment(params: { - sourceId: string - orderId: string - amountMoney: { amount: bigint; currency: string } - note: string - idempotencyKey: string - autocomplete?: boolean // false = pre-authorize (hold) without capturing + sourceId: string + orderId: string + amountMoney: { amount: bigint; currency: string } + note: string + idempotencyKey: string + autocomplete?: boolean // false = pre-authorize (hold) without capturing + buyerEmailAddress?: string // triggers Square's automatic payment receipt email }) { const client = getClient() const { result } = await client.paymentsApi.createPayment({ - sourceId: params.sourceId, - idempotencyKey: params.idempotencyKey, - amountMoney: params.amountMoney, - orderId: params.orderId, - locationId: process.env.SQUARE_LOCATION_ID!, - note: params.note, - autocomplete: params.autocomplete ?? true, + sourceId: params.sourceId, + idempotencyKey: params.idempotencyKey, + amountMoney: params.amountMoney, + orderId: params.orderId, + locationId: process.env.SQUARE_LOCATION_ID!, + note: params.note, + autocomplete: params.autocomplete ?? true, + buyerEmailAddress: params.buyerEmailAddress, }) return result.payment } diff --git a/main-site/gallery/gallery.js b/main-site/gallery/gallery.js index 63c6987..6deb395 100644 --- a/main-site/gallery/gallery.js +++ b/main-site/gallery/gallery.js @@ -78,16 +78,12 @@ document.addEventListener('DOMContentLoaded', () => { }; const apiBaseCandidates = (() => { - const protocol = window.location.protocol; - const host = window.location.hostname; const hints = [ window.GALLERY_API_URL || '', - 'https://photobackend.beachpartyballoons.com', - `${protocol}//${host}:5000`, - `${protocol}//${host}:5001`, + '', // same-origin via nginx proxy ]; - // Remove duplicates/empties - return [...new Set(hints.filter(Boolean))]; + // Remove duplicates/empties — empty string means same-origin (/photos, /uploads) + return [...new Set(hints)]; })(); let activeApiBase = ''; @@ -219,9 +215,7 @@ document.addEventListener('DOMContentLoaded', () => { const resolveUrl = (p) => { if (typeof p !== 'string') return ''; if (p.startsWith('http') || p.startsWith('assets') || p.startsWith('/assets') || p.startsWith('../assets')) return p; - const base = activeApiBase - || 'https://photobackend.beachpartyballoons.com' - || `${window.location.protocol}//${window.location.hostname}:5000`; + const base = activeApiBase || ''; const path = p.startsWith('/') ? p.slice(1) : p; return `${base.replace(/\/$/, '')}/${path}`; }; diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 48c8bd3..cd112b0 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -16,6 +16,10 @@ http { server main-site:3050; } + upstream gallery { + server gallery-backend:5000; + } + server { listen 80; server_name _; @@ -38,6 +42,25 @@ http { location = /refund { return 301 /shop/refund; } location = /refund/ { return 301 /shop/refund; } + # ── Gallery API and uploaded images ────────────────────────────────────── + location ^~ /photos { + proxy_pass http://gallery; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ^~ /uploads { + proxy_pass http://gallery; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # ── eStore: /shop and everything under it ──────────────────────────────── location ^~ /shop { proxy_pass http://estore;