Add booking checkout and webhook flow

This commit is contained in:
2026-05-24 23:55:43 +00:00
parent dbdca5c023
commit 090741e6ad
10 changed files with 879 additions and 24 deletions

View File

@@ -0,0 +1,108 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { Section } from '@/components/Section';
import { formatPoundsFromCents } from '@/lib/booking';
import { getBookingCheckoutContext } from '@/lib/payments';
import { site } from '@/lib/site';
type CheckoutPageProps = {
params: Promise<{
bookingId: string;
}>;
};
export async function generateMetadata({ params }: { params: Promise<{ bookingId: string }> }): Promise<Metadata> {
const { bookingId } = await params;
return {
title: `Checkout ${bookingId} | ${site.name}`,
description: 'Checkout handoff page for the booking flow.',
};
}
export default async function BookingCheckoutPage({ params }: CheckoutPageProps) {
const { bookingId } = await params;
const booking = await getBookingCheckoutContext(bookingId);
if (!booking) {
notFound();
}
return (
<>
<section className="page-hero">
<p className="brand-kicker">Checkout</p>
<h2>Finish payment for {booking.property.title}</h2>
<p>
Review the quote, then continue to Stripe if the session is configured or use the local simulation path in
development.
</p>
</section>
<div className="page-layout">
<Section
eyebrow="Quote"
title="Booking summary"
description="This page is the last step before the Stripe session or the local dev fallback."
>
<div className="admin-summary-grid">
<article className="admin-card">
<p className="footer-label">Stay</p>
<h3>{booking.property.title}</h3>
<p className="mb-0">
{booking.arrivalDate.toISOString().slice(0, 10)} to {booking.departureDate.toISOString().slice(0, 10)}
</p>
</article>
<article className="admin-card">
<p className="footer-label">Total</p>
<h3>{formatPoundsFromCents(booking.totalCents)}</h3>
<p className="mb-0">Current payment state: {booking.payment?.status ?? 'REQUIRES_PAYMENT'}</p>
</article>
</div>
<article className="content-card" style={{ marginTop: '1rem' }}>
<h3>What happens next</h3>
<ul>
<li>Stripe Checkout collects payment when keys are configured.</li>
<li>The webhook finalises the payment and booking state.</li>
<li>Email notifications are composed from the payment outcome.</li>
</ul>
</article>
<article className="content-card" style={{ marginTop: '1rem' }}>
<h3>Development fallback</h3>
<p>
If Stripe is not configured in this environment, open the booking status page and use the simulation
button to trigger the same webhook finalisation path.
</p>
<Link className="btn btn-dark" href={`/bookings/${booking.id}`}>
Open booking status
</Link>
</article>
</Section>
<aside className="content-sidebar">
<article className="content-card">
<p className="footer-label">Guest</p>
<p className="mb-0">
{booking.firstName} {booking.lastName}
<br />
{booking.email}
</p>
</article>
<article className="content-card">
<h3>Navigation</h3>
<ul className="link-list">
<li>
<Link href="/">Back to home</Link>
</li>
<li>
<Link href="/admin">Open admin console</Link>
</li>
</ul>
</article>
</aside>
</div>
</>
);
}