Follow-up to the comprehensive payment-system review. Fixes the issues the
review found in the initial integration, plus the rough edges it introduced.
Money-safety:
- Replay-by-key now replays the FULL original request verbatim from a stored
square_request_snapshot, so a retained idempotency key returns the original
payment instead of IDEMPOTENCY_KEY_REUSED (previously the row sat pending
forever). IDEMPOTENCY_KEY_REUSED remains ambiguous (never proof of no charge).
- Dev mock mirrors real Square for unknown-key replays: ccof: saved-card
sources are charged and rescued; spent cnon: nonces surface
ErrReplayKeyNotRetained. (Fixes dev/prod parity divergence.)
- Webhook dedup row committed AFTER dispatch (at-least-once); FAILED till sales
claw back gift-card funding; event-type strings match Square's real catalog.
- Expired-gift-card cancellation refunds set creditFailed (never a phantom
'completed' refund); cancellation refunds lock all payment rows ascending.
- Sweep never rescue-completes a gift-card purchase without delivering the card.
- Tip no-client-key fallback is a deterministic count-based key under the
booking advisory lock (retry-safe, distinct tips don't collapse).
- M-cap subtracts completed refunds, clamped to [0, total].
2FA (PSD2 SCA stand-in) for online saved-card payments:
- Full feature: status/setup/verify/disable endpoints, gating helper wired into
all 7 saved-card charge paths (incl. BuyGiftCard + admin saved-card), account
admin-tab settings UI, frontend gating across all payment surfaces.
- Enforcement is FAIL-CLOSED: on unless REQUIRE_2FA=false or an explicit
mock/dev SQUARE_ENVIRONMENT; startup warning when off in a non-dev env.
- Verify is brute-force hardened (5-attempt lockout, timing-safe compare);
plaintext codes only logged when enforcement is off (dev).
- GDPR: anonymize_user also scrubs 2FA columns and staff notes.
Infra/docs:
- nginx: /api/ response cache removed (cross-user disclosure); port 80
redirects to HTTPS (localhost/RFC1918 exempt, end-anchored regexes); HSTS;
separate webhook rate-limit zone.
- Schema: users 2FA columns; payments/till_sales square_source_id +
square_request_snapshot.
- Legal docs: gift-card cooling-off, international-transfers section, tips
policy; Gap Backlog P3 webhooks marked done; stale counts/wording corrected.
- Flaky test race fixed (t.Parallel + global mock mutation); suite 26/26
packages green, 2,142 tests, svelte-check clean.
golang.org/x/text/cases.Caser is not safe for concurrent use, but UpdateProfileHandler shared one package-level instance. A fresh caser is now created per call via titleCase().
Restore processImage (images.go) and nonDepositPaymentType (handlers.go) with //nolint:unused — used in test files.
Fix 97 tx.Rollback defers to silently discard expected "tx is closed" error after commit.
Frontend: remove 44 unused shadcn-svelte files, 2 dead components, 9 stale npm deps, prune unused exports.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
errcheck: add proper error handling with slog.Error for tx.Rollback, key generation, and s3/dav operations. Add nolint comments for intentionally discarded DB scan errors and HTTP write errors.
unused: remove dead code (svcRow type, processImage, nonDepositPaymentType, generateSecureCode, colorBold, nGreen, nRed)
gosimple S1021: merge var declaration with assignment in manage.go
ineffassign: remove dead assignments in settings.go, till.go, images.go
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Fix customer_relationship.go: use consistent 'cnt' alias instead of duplicate 'count' column. Fix profile.go: replace SELECT * with explicit column list to avoid new computed booking columns breaking the admin listing query.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Replace direct *pgxpool.Pool usage with PoolProxy wrapper across the entire backend:
- db.DB renamed to db.Conn (*pgxpool.Pool -> *PoolProxy)
- JWT functions now accept context.Context instead of using context.Background()
- Handler DB calls route through PoolProxy for per-test transaction support
- Fixture/helper/testdb functions accept Querier interface for decoupling
- Query ordering fixed in bookings handlers: COUNT after data query to avoid pgx conn busy
- Time truncation fixed: time.Date instead of Truncate(24*time.Hour) for week start calc
- testmain_test.go files updated with SeedBaseline and NewPoolProxy
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Name history system tracks user name changes and displays former names on bookings, appointments, and admin views until consumed by the first completed booking post-change.
- Add name_history queries to today.go, bookings.go, manage.go, profile.go
- Show previous first/last name on today appointments, pending approvals,
booking details, edit requests, and admin user views
- Paginate bookings by start_time instead of created_at (more intuitive ordering)
- Add name change detection in UpdateProfileHandler with history insert
- Support DAV_BASE_URL env var for configurable CardDAV endpoint
- Add referral_savings to profile response
- Add ParseCursor3 validator for user list cursor pagination
- Consume name_history entries when a booking is completed (ProgressBookingHandler)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Replace patch_test_duration_hours on services with separate
patch_tests table
- Add user_patch_tests table to track user patch test records
- Add booking edit request system: users can request time changes
- Add admin handlers to list, approve, and reject edit requests
- Add validation to prevent editing completed/cancelled bookings
- Add overlap and closed-day checks for booking edits
- Add TestMain to set test env vars and testdb.TruncateTables for test
isolation
- Add chi routing context to test helpers for path parameter extraction
- Fix SQL error handling to use errors.Is() instead of ==
- Add validators package with ID validation
- Fix admin test middleware chain (RequireAdmin wrapper)
- Update test user inserts to include phone and date_of_birth fields
- Update service delete test to check soft-delete (is_active=false)
- Update holiday hours test to use new schema (weekday, is_open)
- Add phone number validation tests for UK mobile numbers
Backend:
- Fix GetAllAdminBookingsHandler and SearchAdminBookingsHandler to
return totalPages in response
- Auto-record patch tests when booking status progresses to "completed"
- Add GET/POST /api/admin/users/{id}/patch-tests endpoints
Frontend:
- BookingsCard: proper pagination with 4 per page, prev/next buttons
- UsersCard, BookingCreateModal, WalkInCreateModal: per_page=4 for user
search
- Add PatchTestModal for manual patch test entry in UserModal
- Hide patch test section when user has no eligible services
Database:
- Add UNIQUE constraint on user_service_patch_tests(user_id, service_id)
- Add editable phone field in /account General tab with UK phone
validation
- Create PUT /api/user/change-password endpoint in backend
- Add zxcvbn password strength meter to change password modal
- Add "passwords don't match" validation message to both /account and
/register
- Fix navbar logout reactivity with invalidateAll and $derived values
- Fix a11y warnings: add labels, roles, and keyboard handlers
- Remove unused CSS from account page
Backend:
- Enriched GetAllUserBookings response with calculated total_amount,
amount_paid, and duration_minutes.
- Refactored GetBookingHandler to return a flat booking object matching
frontend expectations.
- Added account_role to admin user list response and sorted users by
booking activity.
- Corrected function name oo to AdminCreateBookingForUserHandler.
Frontend:
- Rebuilt BookingCreateModal into a 4-step wizard supporting guest
bookings, service overrides, and real-time availability checks.
- Fixed account dashboard logic to correctly identify upcoming vs past
bookings and sort unpaid items to the top.
- Extracted booking flow into a shared BookingFlow component.
- Redirected admin users from home page to /today.