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
+75
View File
@@ -228,3 +228,78 @@ func DeleteTimeBlocker(pool *pgxpool.Pool, blockerID string) error {
_, err := pool.Exec(ctx, "DELETE FROM time_blockers WHERE id = $1", blockerID)
return err
}
// CreateTestPayment creates a payment record for testing
// Returns payment ID
func CreateTestPayment(db *pgxpool.Pool, bookingID string, amount float64, method string, ptype string, status string) (string, error) {
ctx := context.Background()
var paymentID string
err := db.QueryRow(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, status, amount, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
RETURNING id
`, bookingID, ptype, method, status, amount).Scan(&paymentID)
if err != nil {
return "", fmt.Errorf("failed to create payment: %w", err)
}
return paymentID, nil
}
// CreateTestRefund creates a refund record for testing
// Returns refund ID
func CreateTestRefund(db *pgxpool.Pool, paymentID string, bookingID string, amount float64) (string, error) {
ctx := context.Background()
var refundID string
err := db.QueryRow(ctx, `
INSERT INTO refunds (payment_id, booking_id, amount, status, reason, created_at)
VALUES ($1, $2, $3, 'completed', 'test refund', NOW())
RETURNING id
`, paymentID, bookingID, amount).Scan(&refundID)
if err != nil {
return "", fmt.Errorf("failed to create refund: %w", err)
}
return refundID, nil
}
// CreateTestPaymentMethod creates a saved card for a user
// Returns card ID
func CreateTestPaymentMethod(db *pgxpool.Pool, userID string, squareCardID string, brand string, last4 string) (string, error) {
ctx := context.Background()
var cardID string
err := db.QueryRow(ctx, `
INSERT INTO user_saved_cards (user_id, square_card_id, brand, last_4, exp_month, exp_year, fingerprint, is_default, created_at)
VALUES ($1, $2, $3, $4, 12, 2030, 'test_fp', false, NOW())
RETURNING id
`, userID, squareCardID, brand, last4).Scan(&cardID)
if err != nil {
return "", fmt.Errorf("failed to create payment method: %w", err)
}
return cardID, nil
}
// DeletePayment deletes a payment from the database
func DeletePayment(pool *pgxpool.Pool, paymentID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM payments WHERE id = $1", paymentID)
return err
}
// DeleteRefund deletes a refund from the database
func DeleteRefund(pool *pgxpool.Pool, refundID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM refunds WHERE id = $1", refundID)
return err
}
// DeletePaymentMethod deletes a saved card from the database
func DeletePaymentMethod(pool *pgxpool.Pool, cardID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM user_saved_cards WHERE id = $1", cardID)
return err
}