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
+94 -2
View File
@@ -55,6 +55,21 @@ CREATE OR REPLACE FUNCTION generate_user_id() RETURNS CHAR(12) AS $$ SELECT gene
CREATE OR REPLACE FUNCTION generate_service_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('services'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_booking_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('bookings'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_payment_id() RETURNS CHAR(12) AS $$ SELECT generate_short_id('payments'); $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_user_saved_card_id() RETURNS CHAR(12) AS $$
SELECT generate_short_id('user_saved_cards');
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_refund_id() RETURNS CHAR(12) AS $$
SELECT generate_short_id('refunds');
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_affiliate_payout_id() RETURNS CHAR(12) AS $$
SELECT generate_short_id('affiliate_payouts');
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_square_deposit_id() RETURNS CHAR(12) AS $$
SELECT generate_short_id('square_deposits');
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION generate_verification_code() RETURNS CHAR(12) AS $$ SELECT substr(encode(gen_random_bytes(6), 'hex'), 1, 12); $$ LANGUAGE sql;
@@ -384,6 +399,11 @@ CREATE TABLE payments (
vat_rate NUMERIC(5,2),
vat_amount NUMERIC(10,2),
net_amount NUMERIC(10,2),
user_saved_card_id CHAR(12),
square_payment_id TEXT,
square_deposit_id CHAR(12),
idempotency_key VARCHAR(64) UNIQUE,
fees NUMERIC(10,2) DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by CHAR(12)
@@ -392,6 +412,8 @@ CREATE TABLE payments (
CREATE INDEX idx_payments_bookingid ON payments(booking_id);
CREATE INDEX idx_payments_status ON payments(status);
CREATE INDEX idx_payments_createdat ON payments(created_at);
CREATE INDEX idx_payments_booking_id_status ON payments(booking_id, status);
CREATE INDEX idx_payments_created_at_status ON payments(created_at, status);
-- =======================================
-- LOYALTY REDEMPTIONS TABLE
@@ -546,10 +568,8 @@ CREATE TABLE user_notification_preferences (
CREATE INDEX idx_user_notification_preferences_user_id ON user_notification_preferences(user_id);
CREATE INDEX idx_payments_booking_id_status ON payments(booking_id, status);
CREATE INDEX idx_bookings_start_time_status ON bookings(start_time, status);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_payments_created_at_status ON payments(created_at, status);
create table images (
id uuid primary key default gen_random_uuid(),
@@ -1293,6 +1313,78 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
-- =======================================
-- USER SAVED CARDS TABLE (Square Integration)
-- =======================================
CREATE TABLE user_saved_cards (
id CHAR(12) PRIMARY KEY DEFAULT generate_user_saved_card_id(),
user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
square_card_id TEXT NOT NULL UNIQUE,
brand TEXT NOT NULL,
last_4 TEXT NOT NULL,
exp_month INT NOT NULL,
exp_year INT NOT NULL,
fingerprint TEXT,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
deleted_at TIMESTAMPTZ,
deleted_by CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
retained_until TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_user_saved_cards_user ON user_saved_cards(user_id);
CREATE INDEX idx_user_saved_cards_fingerprint ON user_saved_cards(fingerprint);
CREATE INDEX idx_user_saved_cards_active ON user_saved_cards(user_id, deleted_at) WHERE deleted_at IS NULL;
-- =======================================
-- REFUNDS TABLE
-- =======================================
CREATE TABLE refunds (
id CHAR(12) PRIMARY KEY DEFAULT generate_refund_id(),
payment_id CHAR(12) NOT NULL REFERENCES payments(id) ON DELETE CASCADE,
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
amount NUMERIC(10,2) NOT NULL CHECK (amount > 0),
square_refund_id TEXT,
status payment_status NOT NULL DEFAULT 'pending',
reason TEXT NOT NULL,
created_by CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_refunds_payment ON refunds(payment_id);
CREATE INDEX idx_refunds_booking ON refunds(booking_id);
-- =======================================
-- AFFILIATE PAYOUTS TABLE
-- =======================================
CREATE TABLE affiliate_payouts (
id CHAR(12) PRIMARY KEY DEFAULT generate_affiliate_payout_id(),
affiliate_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
amount NUMERIC(10,2) CHECK (amount >= 0),
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_affiliate_payouts_affiliate ON affiliate_payouts(affiliate_id);
-- =======================================
-- SQUARE DEPOSITS TABLE (Bank Reconciliation)
-- =======================================
CREATE TABLE square_deposits (
id CHAR(12) PRIMARY KEY DEFAULT generate_square_deposit_id(),
square_deposit_id TEXT,
amount NUMERIC(10,2) NOT NULL,
fees_deducted NUMERIC(10,2),
deposited_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_square_deposits_deposited ON square_deposits(deposited_at);
-- =======================================
-- FUNCTION USAGE SUMMARY
-- =======================================