Self-serve booking for seven rooms. Stripe PaymentIntent + one Google Calendar event per day. No customer confirmation email yet — see incident 1.
At a glance
| Worker | labour-temple-bookings.labourtemple.workers.dev |
| Deploy | cd cloudflare-worker-bookings && wrangler deploy |
| Logs | wrangler tail --format pretty |
| Room config | rooms.js (pricing ¢, capacity, min hours, discount eligibility, BOOKABLE list) |
| Operating hours | 8 AM – 5 PM Pacific, Mon–Fri (hardcoded) |
| Inter-booking buffer | 15 min (hardcoded) |
| Booking window | 24 h – 60 d out (hardcoded) |
| Multi-day max | 10 weekdays per booking |
| Calendar auth | Google service account JWT (no attendee invites possible) |
| Webhook | Stripe payment_intent.succeeded → /webhook |
The two gates (memorize this)
- Gate 1 — availability re-checked at
POST /create-booking, before PaymentIntent creation. Blocks if the slot got taken. - Gate 2 — availability re-checked in the webhook, after Stripe confirms payment, before any Calendar event. Fail → auto-refund in full, no event created.
Multi-day: one event per day. Any single day fails → delete all already-created events for that booking + auto-refund full amount.
A Stripe refund with no calendar event is not a bug — it's the safety net. Always check Worker logs first.
The five surfaces
- Stripe → Payments / Customers / Events (
payment_intent.succeeded) / Logs - Google Calendar → room calendars (one event per booking day)
- Worker logs →
wrangler tail - Browser → Console + Network on the room page
rooms.js→ authoritative for pricing, capacity, min hours,BOOKABLE
When to escalate
- Customer charged, no calendar event, no refund within 5 min
- Multi-day cleanup partially succeeded (orphan events + no refund, or events deleted but no refund)
- Webhook returning 4xx / 5xx repeatedly
- Double-booking despite both gates passing
Incident playbooks
1. "I booked but got no confirmation email" — expected today
Track 2 does not yet send a customer confirmation email. The customer has only the on-screen redirect to /meeting-booking-confirmation + the Stripe receipt.
Fix. Send a manual confirmation from admin@labourtemple.com with date, room, time, cancellation policy. Do not tell them to "check spam" — there's no email to find.
Priority. A MailerSend confirmation for Track 2 is a documented follow-up (see roadmap.md). Meanwhile, keep a manual-confirmation template in the team inbox.
2. Stripe payment succeeded, no calendar event
Check.
- Worker logs filtered by PaymentIntent ID. Look for: "Gate 2 conflict" / "availability check failed" / "Google Calendar event creation failed" — in each case, an auto-refund should have fired.
- Stripe → Developers → Events → find the
payment_intent.succeeded→ check delivery. Failed/retrying = Worker had a transient error.
Fix.
- Auto-refund already fired → email customer: slot was taken at the moment of payment, card refunded, offer help finding an alternative.
- Webhook didn't reach us → Stripe → Developers → Events → "Resend" the event; Worker will process.
- Last resort → manually create the calendar event from PaymentIntent metadata (
booking_dates,booking_time, customer info).
3. Calendar event exists, customer has no confirmation
Same as #1 — we don't send an email yet. Send manually.
4. Multi-day booking — some days on calendar, others missing
Check. Worker logs filtered by PaymentIntent ID.
- "Multi-day partial failure — cleaning up" → atomic cleanup fired. Confirm all events deleted and refund issued.
- Orphan events and no refund → cleanup itself failed. Worst case.
Fix.
- Cleanup + refund both succeeded → email customer and offer rebook.
- Cleanup failed → delete orphans manually + refund manually in Stripe + email customer.
Escalate to Chris same-day if cleanup failed — indicates a code path that needs hardening.
5. Double-booking on the calendar
Check. Was one event added manually? Our Worker can't see manual additions — no race, just a human mistake.
If both were Worker-created, check log timestamps; both gates should have prevented this.
Fix. Contact one customer (usually the later or lower-commercial-value one) with goodwill gesture + rebooking offer. Refund if they can't rebook. Report to Chris for RCA.
Prevention. Do not add manual Google Calendar events to any bookable room's calendar. Use a separate "internal" calendar for non-paid events.
6. Calendar shows no availability when it should
Check. Is the date within 24 h – 60 d? Is it a weekend (disabled)? Is it a Canadian holiday (currently not handled — customer can book, but space may be closed)? Check Google Calendar directly for capacity.
Fix. Suggest alternatives. If the day is staff-blocked by a manual calendar event, that's expected.
7. Customer wants to cancel
No self-service cancellation.
Fix. Confirm booking details → agree refund amount per policy → refund in Stripe (note reason if partial) → delete Calendar event → email confirmation.
Policy to publish. Typical: full refund 48+ h out, 50% 24–48 h, none under 24 h. Post near the booking form. Handle case-by-case until published.
8. Chargeback / dispute
Fix. Respond via Stripe Dashboard with: booking metadata, calendar event (showing the slot was reserved), published cancellation policy, any emails. Stripe + card network decide.
Dispute fee is $15–25 per case regardless of outcome. Losing also costs the original amount. Respond before Stripe's deadline.
9. Sonic Studio booking missing extras
Check. Did the customer actually see the Sonic extras on the form? Extras are triggered by the Sonic slug — if slug or JS is wrong, extras don't render.
Fix. Email the customer to gather podcast/show name, group size, set choice. Update the Calendar event description.
10. CORS / "Failed to fetch" on submit
Happens when a new origin (staging, preview) hits the Worker without being in ALLOWED_ORIGINS.
Fix. Add the origin to ALLOWED_ORIGINS in worker.js and redeploy.