Files
Spanglish/frontend/src/app/admin/scanner/_components/PosChargePanel.tsx
T
MichilisandClaude Opus 5.5 b51c1360e2 Add a per-event walk-in price and a POS tender to the door screen.
Events can now set an optional walk-in price (events.walk_in_price):
null falls back to the ticket price, 0 is a free walk-in. It is set in
the Create/Edit Event modal (EN/ES) and carried through create, update
and duplicate, but it is internal: public event responses and the
attendee-facing ticket/dashboard endpoints drop it, and only admin,
organizer and staff callers receive it.

The door screen no longer trusts the amount the client sends. It sends
a quantity and the server prices the charge from the event record:
walk-ins pay the walk-in price, existing tickets the ticket price. A
typed amount is only honoured with amountOverride from admin/organizer
and is written to audit_logs in the same transaction. The charged
amount stays snapshotted on the payment row.

POS is a new door tender for the physical card terminal. Staff pick
POS, see the amount to key in, charge the card, then confirm with
"Mark as paid"; it is then recorded like any other door payment
(provider and method 'pos'). It can be switched off globally or per
event via payment options, and shows up in the payments filters,
bookings, revenue summaries and door takings. PosChargePanel is where
an automatic terminal push would go later; nothing calls a terminal
today.

tickets.booking_source (online | walk_in | admin) records how a booking
was made. The migration backfills walk-ins from the door screen's
idempotency records; everything else stays online.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:10:50 +00:00

71 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { CreditCardIcon } from '@heroicons/react/24/outline';
import { formatCurrency } from '@/lib/utils';
import type { DoorCharge } from './PaymentButtons';
// The POS step of the door screen.
//
// Unlike cash, the money is not in staff's hand when they pick POS: they first
// key the amount into the physical card terminal, wait for approval, and only
// then confirm here. So POS is two taps: pick POS (this panel opens with the
// amount to type), then "Mark as paid", which records it through the same
// door-checkin call as every other tender.
//
// Nothing talks to the terminal today. If an automatic amount push is added
// later, it starts when this panel opens and its approval replaces (or pre-fills)
// the manual confirmation below; the rest of the door flow stays unchanged.
export function PosChargePanel({
charge,
unitPrice,
currency,
disabled,
onConfirm,
onBack,
}: {
charge: DoorCharge;
unitPrice: number;
currency: string;
disabled?: boolean;
onConfirm: () => void;
onBack: () => void;
}) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between px-1">
<p className="text-sm font-semibold text-white flex items-center gap-1.5">
<CreditCardIcon className="w-5 h-5" />
POS — charge on the terminal
</p>
<button onClick={onBack} className="text-sm text-gray-400 min-h-[48px] px-2 active:text-white">
Back
</button>
</div>
<div className="rounded-2xl bg-gray-900 border border-violet-500/40 px-4 py-3 text-center">
<p className="text-[11px] uppercase tracking-wide text-gray-400">Amount to charge</p>
<p className="text-3xl font-bold text-white tabular-nums">{formatCurrency(charge.amount, currency)}</p>
{charge.override ? (
<p className="text-xs text-amber-400 mt-0.5">Custom amount</p>
) : charge.quantity > 1 ? (
<p className="text-xs text-gray-400 mt-0.5">
{charge.quantity} × {formatCurrency(unitPrice, currency)}
</p>
) : null}
</div>
<button
disabled={disabled}
onClick={onConfirm}
className="w-full min-h-[56px] rounded-2xl bg-violet-600 active:bg-violet-700 text-white text-lg font-bold disabled:opacity-50 active:scale-[0.98] transition-transform"
>
Mark as paid
</button>
<p className="text-xs text-gray-500 text-center px-2">
Only tap once the terminal has approved the card.
</p>
</div>
);
}