You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
10-min atomic slot lock; overlap query excludes caller's own session
booking_idempotency
Maps checkout_token β booking_id; prevents double-INSERTs on retry
Stripe idempotency key
Same checkout_token passed as Idempotency-Key β same PI returned
D1 atomic batch
Booking + hold-convert + payment INSERT in one db.batch() call
payment_recovery_log
PI success + D1 batch failure β logged; client gets recovery_pending status
Admin integrity scan
GET /api/admin/integrity detects orphan payments, unconfirmed bookings, stale holds
Cloudflare D1 Persistence (β Complete)
33 tables fully applied in production (database_id: 119f9fd4-...)
13 migrations all confirmed applied remotely
5 new tables (migrations 0012β0013): reservation_holds, payment_recovery_log, booking_idempotency, orphan_payments, integrity_log
Booking lifecycle: pending β confirmed β active β completed / cancelled / refunded all tracked in D1
Security & Rate Limiting (β Complete)
/api/holds β 20 req/min per IP
/api/payments/create-intent β 10 req/min per IP
/api/admin/integrity β requires admin session
Structured JSON logging (logEvent) on all hold/payment/booking events for wrangler tail monitoring
SQL alias bug fixed in payments idempotency check (payments.booking_id not p.booking_id)
Map / UI
Popup no longer blocks the walking route pill β popup offset increased to 72px when a walk route is active; the #route-info-pill drops to 1.5rem from the bottom while the popup is open (pill-popup-open class), giving the popup full clearance above the route line
Smart auto-pan β measures actual pin screen position vs pill top and pans by exactly the gap needed (not a fixed constant), so the popup always stays above the pill with β₯16px breathing room
Viewport restored on close β map.easeTo({offset:[0,0]}) on popup close undoes the temporary pan
Security
XSS: loadTopHosts β all host name / count / rating / id fields now escaped via escHtml() before innerHTML
XSS: Admin audit log table β all DB-sourced fields (email, action, reason, IP address) escaped via escapeHtml() before render
XSS: Admin refund log table β all fields escaped
XSS: Admin user detail panel β full_name, email, role, status escaped; e.message no longer shown in error HTML
XSS: Admin blocker reasons list β escaped before innerHTML
escapeHtml() fixed β was missing &, <, > escaping; now full 5-char escape
Error detail leak β detail: e.message stripped from ALL 500 responses in api.ts (9 routes) and all admin-api.ts routes
PII in logs β geocode query strings replaced with query_len; geocode coordinates removed from warning log; listing POST body replaced with title-only log; Twilio SMS Body content redacted
Performance
Migration 0011 β 6 new compound indexes: (listing_id, status) booking guard, stripe_payment_intent_id on payments/bookings, (status, type) listings, availability blocks, notifications composite; applied to both local and production D1
Code Quality
Removed 2x console.debug statements left from walk-route debugging
Cleaned debug comment on el._lng coordinate freeze
π± Contact Verification System (v2.1)
Overview
Full email + phone OTP verification built for the checkout flow. Supports guest checkout (no account login required) using session-scoped verification tokens.
Verified badges β green checkmarks appear on both fields after successful OTP
Resend timer β 60-second cooldown before resend is allowed
Hold token reuse β session_token (= checkoutToken) is the same token used for slot holds, so verification and hold are tied to the same checkout session
Post-Payment Messaging
Trigger
Channel
Contents
Payment success
Email
Booking confirmation + QR code image (200Γ200 PNG) + check-in link
Payment success
Email
Separate payment receipt with last-4 card digits
Payment success
SMS
Booking summary + QR check-in URL (/checkin?t=TOKEN&b=BOOKING_ID)
Both
β
Only sent to verified contacts; unverified contacts fall back to raw body values
Security
OTPs hashed with PBKDF2-SHA-256 (10,000 iterations + random salt)
Constant-time comparison to prevent timing attacks
Verified contacts expire 2 hours after verification
Single-use: used = 1 after payments/confirm reads them (replay prevention)
Rate limits: 3 OTPs per contact per 10 minutes; 5 per IP per minute; 5 confirm attempts per session per 15 minutes