feat: Square payment integration, booking flow redesign, and timezone/weekday fixes

- Add Square payment integration (mock + handlers + UI): terminal/online payments,
  refunds, tips, saved cards, webhooks. Build-tagged dev/prod clients.
- Redesign booking flow: Step 4 conditional (deposit only), Step 5 confirmation
  screen with booking ID, auto-submit on transition.
- Redesign schedule modal: 2x3 button grid with Pay Deposit/Pay Early logic.
- Add deposit warning banner at Step 1 for users with outstanding deposits.
- Fix weekday conversion bug: Go 0=Sunday vs DB 0=Monday mismatch in 6 locations.
- Fix timezone bug: UTC vs London time in closing hours validation.
- Fix frontend error parsing: plain text backend errors now displayed correctly.
- Fix crypto.randomUUID fallback for environments without Web Crypto.
- Add 7 new regression tests: closing hours, advance check, active booking limit,
  weekday conversion, UTC/London, deposit snapshot, exceptional hours.
- Fix 3 flaky tests: dynamic dates instead of fixed, no-show timing.
This commit is contained in:
2026-05-23 11:29:34 +01:00
parent bcd5ed2bd9
commit 2e1ab9d745
31 changed files with 6155 additions and 229 deletions
+24
View File
@@ -5,6 +5,7 @@ import (
"crussell/auth"
"crussell/internal/dav"
"crussell/internal/s3"
"crussell/internal/square"
"encoding/json"
"fmt"
"log"
@@ -24,11 +25,13 @@ import (
"crussell/handlers/admin"
"crussell/handlers/bookings"
"crussell/handlers/notifications"
"crussell/handlers/payments"
"crussell/handlers/portfolio"
"crussell/handlers/scheduling"
"crussell/handlers/services"
"crussell/handlers/today"
"crussell/handlers/user"
"crussell/handlers/webhooks"
)
func init() {
@@ -61,6 +64,11 @@ func initS3() {
}
}
func initSquare() {
payments.SquareClient = square.NewClient()
fmt.Println("Square client initialized (dev mock)")
}
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
status := "ok"
services := map[string]string{
@@ -101,6 +109,7 @@ func main() {
initDB()
initDav()
initS3()
initSquare()
r := chi.NewRouter()
@@ -218,6 +227,13 @@ func main() {
r.Delete("/bookings/{id}", bookings.DeleteBookingHandler)
r.Post("/bookings/{id}/edit-request", bookings.RequestEditHandler)
r.Delete("/bookings/{id}/edit-request", bookings.DeleteEditRequestHandler)
// User payment routes
r.Post("/bookings/{id}/payment", payments.CreateBookingPayment)
r.Get("/user/payment-methods", payments.GetUserPaymentMethods)
r.Delete("/user/payment-methods/{id}", payments.DeletePaymentMethod)
r.Post("/bookings/{id}/tip", payments.CreateTipPayment)
r.Get("/bookings/{id}/payment-summary", payments.GetBookingPaymentSummary)
})
// Admin-only (no rate limit - trusted users with authenticated sessions)
@@ -283,9 +299,17 @@ r.Route("/admin/users", func(r chi.Router) {
r.Delete("/{id}", admin.DeleteDiscountCampaign)
r.Get("/{id}/stats", admin.GetCampaignStats)
})
// Admin payment routes
r.Post("/admin/bookings/{id}/payment", payments.CreateTerminalPayment)
r.Get("/admin/payments/{checkout_id}/status", payments.GetCheckoutStatus)
r.Post("/admin/payments/{payment_id}/refund", payments.RefundPayment)
})
})
// Webhooks (no auth - Square sends to base path)
r.Post("/webhooks/square", webhooks.HandleSquareWebhook)
srv := &http.Server{
Addr: ":8080",
Handler: r,