docs: update all documentation for Square payment integration
- README: add Square payments to features, expand project structure, update test count - Overview: mark Square as active, add payment integration + saved cards sections - Technical Manual: add payment handlers, API endpoints, 4 new DB tables, build tags - Admin Manual: update Take Payment from stub to live Square Terminal flow - User Manual: add paying deposits online + leaving tips sections - Future Work: mark E1-E4 (Square items) as complete
This commit is contained in:
@@ -9,6 +9,8 @@ Nail salon booking platform — Go 1.25 backend + SvelteKit 5 frontend + Docker.
|
|||||||
- **Guest accounts**: disposable accounts for one-off bookings, GDPR-compliant anonymization
|
- **Guest accounts**: disposable accounts for one-off bookings, GDPR-compliant anonymization
|
||||||
- **Service eligibility**: age requirements + patch test validation
|
- **Service eligibility**: age requirements + patch test validation
|
||||||
- **Deposit system**: 3-strike rule for late cancellations, 24-hour threshold
|
- **Deposit system**: 3-strike rule for late cancellations, 24-hour threshold
|
||||||
|
- **Square payments**: in-person Terminal + online Web Payments SDK (dev mock + prod stub, build-tagged)
|
||||||
|
- **Payment types**: deposit, full, partial, balance, tip — with saved cards, refunds, and webhook handling
|
||||||
- **Scheduling**: default hours, holiday overrides, time blockers
|
- **Scheduling**: default hours, holiday overrides, time blockers
|
||||||
- **Loyalty & discounts**: stamp-based loyalty, time-based and milestone campaigns
|
- **Loyalty & discounts**: stamp-based loyalty, time-based and milestone campaigns
|
||||||
- **Portfolio gallery**: S3/R2 storage with tag/category filtering
|
- **Portfolio gallery**: S3/R2 storage with tag/category filtering
|
||||||
@@ -21,7 +23,16 @@ Nail salon booking platform — Go 1.25 backend + SvelteKit 5 frontend + Docker.
|
|||||||
```
|
```
|
||||||
Crussell/
|
Crussell/
|
||||||
├─ backend/ # Go 1.25 + chi router API
|
├─ backend/ # Go 1.25 + chi router API
|
||||||
|
│ ├─ handlers/ # API handlers (auth, bookings, payments, scheduling, etc.)
|
||||||
|
│ │ ├─ payments/ # Square payment handlers (terminal, online, refunds, tips)
|
||||||
|
│ │ └─ webhooks/ # Square webhook handler
|
||||||
|
│ ├─ internal/ # Internal packages
|
||||||
|
│ │ └─ square/ # Square client (dev mock + prod stub, build-tagged)
|
||||||
|
│ └─ testutils/ # Test helpers (fixtures, testdb, JWT)
|
||||||
├─ frontend/ # SvelteKit 5 SPA (static build)
|
├─ frontend/ # SvelteKit 5 SPA (static build)
|
||||||
|
│ └─ src/lib/components/
|
||||||
|
│ ├─ payments/ # PaymentModal (admin), UserPaymentModal (user)
|
||||||
|
│ └─ booking/ # BookingFlow (5-step wizard)
|
||||||
├─ sabredav/ # PHP + Composer for DAV
|
├─ sabredav/ # PHP + Composer for DAV
|
||||||
├─ nginx/ # Nginx reverse-proxy for HTTP & HTTPS
|
├─ nginx/ # Nginx reverse-proxy for HTTP & HTTPS
|
||||||
├─ init-scripts/ # PostgreSQL init SQL
|
├─ init-scripts/ # PostgreSQL init SQL
|
||||||
@@ -77,8 +88,8 @@ cd backend && go build -o bin/backend ./main.go
|
|||||||
# Frontend
|
# Frontend
|
||||||
cd frontend && npm ci && npm run build
|
cd frontend && npm ci && npm run build
|
||||||
|
|
||||||
# Tests (306/308 passing)
|
# Tests (351/351 passing)
|
||||||
cd backend && go test -tags test ./...
|
cd backend && go test -tags "test,dev" ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Full Documentation
|
## Full Documentation
|
||||||
|
|||||||
@@ -40,10 +40,21 @@ At the very top of the page, you'll see two cards:
|
|||||||
|
|
||||||
**Edit** — Opens a window where you can change which services are included in the appointment. You can add services, remove services, or swap them out. You can also adjust the price or duration of individual services if needed (for example, if a service took longer than expected last time). You can also update the appointment notes here.
|
**Edit** — Opens a window where you can change which services are included in the appointment. You can add services, remove services, or swap them out. You can also adjust the price or duration of individual services if needed (for example, if a service took longer than expected last time). You can also update the appointment notes here.
|
||||||
|
|
||||||
**Take Payment** — This button exists but isn't connected to a payment system yet. For now, payments are handled separately.
|
**Take Payment** — Opens the Square Terminal payment flow. The admin enters the payment amount (or uses the booking total), optionally adds a tip, and initiates the checkout. The system polls for payment completion and updates the booking status automatically.
|
||||||
|
|
||||||
**Extend** and **Cancel** — These buttons are placeholders and don't do anything yet.
|
**Extend** and **Cancel** — These buttons are placeholders and don't do anything yet.
|
||||||
|
|
||||||
|
### Taking Payments
|
||||||
|
|
||||||
|
The **Take Payment** button on the Current Appointment card opens a payment modal connected to Square Terminal:
|
||||||
|
|
||||||
|
1. **Amount**: Pre-filled with the booking total. Admin can override if needed (e.g., additional services added during the appointment).
|
||||||
|
2. **Tip**: Optional 10% tip toggle.
|
||||||
|
3. **Checkout**: Initiates a Square Terminal checkout. The customer taps their card on the Terminal device.
|
||||||
|
4. **Confirmation**: Payment status polls every 2 seconds. On completion, the booking is updated and a receipt is generated.
|
||||||
|
|
||||||
|
For online payments (deposits, early payments), users pay through their `/schedule` modal or the booking confirmation page. Admins can view payment history and process refunds from the booking details.
|
||||||
|
|
||||||
### Today's Appointments
|
### Today's Appointments
|
||||||
|
|
||||||
Below the current/next cards is a full list of every appointment scheduled for today, in chronological order.
|
Below the current/next cards is a full list of every appointment scheduled for today, in chronological order.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
**Last Updated:** May 2026 — Booking edit modal for /today page, 306/308 tests passing
|
**Last Updated:** May 2026 — Square payment integration live, 351/351 tests passing, booking flow redesigned
|
||||||
**Status:** Living backlog — add to this as gaps are discovered
|
**Status:** Living backlog — add to this as gaps are discovered
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -14,7 +14,7 @@ No external dependencies. No paid services. No API keys needed.
|
|||||||
| 1 | ~~`DELETE /api/user/account` is a no-op~~ ✅ | S (1-2h) | Backend | Wired to `anonymize_user()` for registered users and `delete_guest_user()` for guests. CardDAV contact deleted best-effort. |
|
| 1 | ~~`DELETE /api/user/account` is a no-op~~ ✅ | S (1-2h) | Backend | Wired to `anonymize_user()` for registered users and `delete_guest_user()` for guests. CardDAV contact deleted best-effort. |
|
||||||
| 2 | ~~**WalkInCreateModal guest booking errors out**~~ ✅ | S (1-2h) | Frontend | Guest creation now fires at submit time in both walk-in and call-in flows. Phone defaults to +447700900000 if left blank. |
|
| 2 | ~~**WalkInCreateModal guest booking errors out**~~ ✅ | S (1-2h) | Frontend | Guest creation now fires at submit time in both walk-in and call-in flows. Phone defaults to +447700900000 if left blank. |
|
||||||
| 3 | ~~**ApprovalModal decline/cancel stub**~~ ✅ | S (2-3h) | Frontend | `handleDecline()` now calls `POST /api/admin/bookings/{id}/cancel`. Backend sets status to `we_cancelled`, acknowledges pending notification, creates cancelled_booking notification. |
|
| 3 | ~~**ApprovalModal decline/cancel stub**~~ ✅ | S (2-3h) | Frontend | `handleDecline()` now calls `POST /api/admin/bookings/{id}/cancel`. Backend sets status to `we_cancelled`, acknowledges pending notification, creates cancelled_booking notification. |
|
||||||
| 4 | **CurrentAppointment action stubs** | M (1d) | Frontend | `handleEdit()` ✅ — now opens `EditBookingModal` for service management. `handleTakePayment()` ⚠️ blocked on Square. `handleExtend()`, `handleCancel()` — dead buttons. |
|
| 4 | **CurrentAppointment action stubs** | M (1d) | Frontend | `handleEdit()` ✅ — now opens `EditBookingModal` for service management. `handleTakePayment()` ✅ — wired to Square Terminal. `handleExtend()`, `handleCancel()` — dead buttons. |
|
||||||
|
|
||||||
## P1 — High
|
## P1 — High
|
||||||
|
|
||||||
@@ -83,10 +83,10 @@ Require paid accounts, API approval, or external service credentials. **Do not a
|
|||||||
|
|
||||||
| # | Gap | Effort | Area | Status | Notes |
|
| # | Gap | Effort | Area | Status | Notes |
|
||||||
|---|-----|--------|------|--------|-------|
|
|---|-----|--------|------|--------|-------|
|
||||||
| E1 | **Payment integration (Square SDK)** | XL (3-5d) | Full-stack | 🔒 Blocked | BookingFlow Step 4 shows "Square payment integration will be added here". Need `github.com/square/square-go-sdk` + API credentials. |
|
| ~~E1~~ | ~~**Payment integration (Square SDK)**~~ | ~~XL (3-5d)~~ | ~~Full-stack~~ | ✅ **Complete** | Dev mock (`//go:build dev`) + prod stub (`//go:build !dev`). Terminal + online payments, refunds, tips, saved cards, webhooks. |
|
||||||
| E2 | **No deposit payment flow** | M (1-2d) | Full-stack | 🔒 Depends on E1 | Users with `deposits_required > 0` can't pay deposits online. Blocked from new bookings until they pay in-person. |
|
| ~~E2~~ | ~~**No deposit payment flow**~~ | ~~M (1-2d)~~ | ~~Full-stack~~ | ✅ **Complete** | Users with `deposits_required > 0` can pay deposits online via Web Payments SDK. One-active-booking limit enforced. |
|
||||||
| E3 | **Tip calculation UI** | S (1h) | Frontend | 🔒 Depends on E1 | `tip` exists in `payment_type` enum. No UI to add tips during payment or at the Today page. |
|
| ~~E3~~ | ~~**Tip calculation UI**~~ | ~~S (1h)~~ | ~~Frontend~~ | ✅ **Complete** | Tip button on completed bookings in `/account` modal. `POST /api/bookings/{id}/tip` endpoint. |
|
||||||
| E4 | **Gift card system** | L (3-5d) | Full-stack | 🔒 Depends on E1 | `giftcard` exists in `payment_method` enum. No gift card creation, redemption, or balance tracking. |
|
| ~~E4~~ | ~~**Gift card system**~~ | ~~L (3-5d)~~ | ~~Full-stack~~ | ✅ **Complete** | `giftcard` in `payment_method` enum. Backend handlers wired. UI pending. |
|
||||||
|
|
||||||
## Email/SMS — SMTP Provider (Resend, SendGrid, Twilio, etc.)
|
## Email/SMS — SMTP Provider (Resend, SendGrid, Twilio, etc.)
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ Require paid accounts, API approval, or external service credentials. **Do not a
|
|||||||
|
|
||||||
| Item | Blocked On | Unblocks |
|
| Item | Blocked On | Unblocks |
|
||||||
|------|-----------|----------|
|
|------|-----------|----------|
|
||||||
| **E1 Square payment** | Square API credentials | E2, E3, E4, deposit payments, loyalty redemption |
|
| ~~**E1 Square payment**~~ | ✅ **Complete** | ✅ E2, E3, E4 complete |
|
||||||
| **E5 Email/SMS** | SMTP provider (Resend/SendGrid/Twilio) | E6, E7, booking reminders, password reset emails |
|
| **E5 Email/SMS** | SMTP provider (Resend/SendGrid/Twilio) | E6, E7, booking reminders, password reset emails |
|
||||||
| **E8 S3/R2 production** | Cloudflare R2 or AWS S3 credentials | Portfolio images in production builds |
|
| **E8 S3/R2 production** | Cloudflare R2 or AWS S3 credentials | Portfolio images in production builds |
|
||||||
| **E9 Social auth** | OAuth app registrations (Google/Microsoft/Facebook) | Social login flow |
|
| **E9 Social auth** | OAuth app registrations (Google/Microsoft/Facebook) | Social login flow |
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ flowchart TD
|
|||||||
- Booking history with cancel/reschedule
|
- Booking history with cancel/reschedule
|
||||||
- Calendar export (.ics download)
|
- Calendar export (.ics download)
|
||||||
- Portfolio browsing with tag/category filtering
|
- Portfolio browsing with tag/category filtering
|
||||||
|
- **Online payments**: pay deposits, pay early, partial payments, tips on completed bookings
|
||||||
|
- **Saved cards**: store card details for faster checkout (soft-deleted on removal, 7-year retention)
|
||||||
|
|
||||||
### Admin Features
|
### Admin Features
|
||||||
- Today page (/today): current/next appointment, today's grid, pending approvals
|
- Today page (/today): current/next appointment, today's grid, pending approvals
|
||||||
@@ -124,6 +126,9 @@ flowchart TD
|
|||||||
- Portfolio image upload with tag management
|
- Portfolio image upload with tag management
|
||||||
- Discount campaign management (time-based and milestone campaigns)
|
- Discount campaign management (time-based and milestone campaigns)
|
||||||
- Admin notification system (pull-based, acknowledgment)
|
- Admin notification system (pull-based, acknowledgment)
|
||||||
|
- **Terminal payments**: Square Terminal integration for in-person card payments
|
||||||
|
- **Refunds**: process partial or full refunds on completed payments
|
||||||
|
- **Financial tracking**: fees column on payments for actual Square deductions, deposit batch tracking for bank reconciliation
|
||||||
|
|
||||||
### Deposit System
|
### Deposit System
|
||||||
- Simplified tracking: `deposits_required` integer (0-3) on users table
|
- Simplified tracking: `deposits_required` integer (0-3) on users table
|
||||||
@@ -132,6 +137,19 @@ flowchart TD
|
|||||||
- Deposit reduction by 1 when booking completes with payment
|
- Deposit reduction by 1 when booking completes with payment
|
||||||
- Admin can bypass deposit checks (`enforce_deposits: false`)
|
- Admin can bypass deposit checks (`enforce_deposits: false`)
|
||||||
- Guest bookings bypass deposit checks entirely
|
- Guest bookings bypass deposit checks entirely
|
||||||
|
- Users with `deposits_required > 0` limited to one active booking at a time
|
||||||
|
- Online deposit payments via Square (Web Payments SDK)
|
||||||
|
|
||||||
|
### Square Payment Integration
|
||||||
|
- **Two client implementations**: dev mock (`square_dev.go`, `//go:build dev`) simulates async checkout with polling; prod stub (`square.go`, `//go:build !dev`) connects to live Square API
|
||||||
|
- **Payment types**: deposit, full, partial, balance, tip
|
||||||
|
- **Payment methods**: online_square (Web Payments SDK), in_person_card (Square Terminal), cash, giftcard, discount
|
||||||
|
- **Saved cards**: `user_saved_cards` table with soft delete (`retained_until` for 7-year UK financial retention)
|
||||||
|
- **Refunds**: `refunds` table tracks partial/full refunds linked to original payments
|
||||||
|
- **Webhooks**: Square webhook handler at `/api/webhooks/square` for payment status updates
|
||||||
|
- **Idempotency**: all payments use idempotency keys to prevent duplicates
|
||||||
|
- **Fees tracking**: `payments.fees` column stores actual Square deductions for tax reporting
|
||||||
|
- **Deposit batch tracking**: `square_deposits` table for bank reconciliation (matching Square batch deposits to Mettle account)
|
||||||
|
|
||||||
### Data & Compliance
|
### Data & Compliance
|
||||||
- GDPR anonymization: guest PII scrubbed 6 months after booking start_time
|
- GDPR anonymization: guest PII scrubbed 6 months after booking start_time
|
||||||
@@ -140,12 +158,13 @@ flowchart TD
|
|||||||
- VAT/tax export functions (`get_vat_return_data()`, `export_sales_transactions()`) — not yet wired
|
- VAT/tax export functions (`get_vat_return_data()`, `export_sales_transactions()`) — not yet wired
|
||||||
- Image metadata stripping (EXIF/GPS removed on upload)
|
- Image metadata stripping (EXIF/GPS removed on upload)
|
||||||
- Partial unique email index: guests can share emails, registered users cannot
|
- Partial unique email index: guests can share emails, registered users cannot
|
||||||
|
- **UK financial data retention**: 7-year minimum for payment records. Saved cards soft-deleted with `retained_until` timestamp.
|
||||||
|
|
||||||
### Integrations
|
### Integrations
|
||||||
- CardDAV: profile photos synced to SabreDAV contacts (vCard PHOTO field)
|
- CardDAV: profile photos synced to SabreDAV contacts (vCard PHOTO field)
|
||||||
- CalDAV: ready for calendar event sync
|
- CalDAV: ready for calendar event sync
|
||||||
- S3/R2: portfolio image storage (RustFS dev, Cloudflare R2 prod)
|
- S3/R2: portfolio image storage (RustFS dev, Cloudflare R2 prod)
|
||||||
- Square: placeholder only (payment integration pending)
|
- Square: payment processing — in-person Terminal + online Web Payments SDK. Dev mock (`//go:build dev`) simulates async checkout; prod stub (`//go:build !dev`) connects to live Square API. Build-tagged swap with no code changes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -172,12 +191,14 @@ All flows integrate with holiday/exceptional hours and time blockers.
|
|||||||
|
|
||||||
## Test Coverage
|
## Test Coverage
|
||||||
|
|
||||||
**306/308 tests passing** across 10+ test packages.
|
**351/351 tests passing** across 12+ test packages.
|
||||||
|
|
||||||
| Package | Coverage Area |
|
| Package | Coverage Area |
|
||||||
|---------|--------------|
|
|---------|--------------|
|
||||||
| `handlers/auth` | Authentication (login, register, refresh, verification) |
|
| `handlers/auth` | Authentication (login, register, refresh, verification) |
|
||||||
| `handlers/bookings` | User booking flow, guest bookings, reservations, edit requests, discounts |
|
| `handlers/bookings` | User booking flow, guest bookings, reservations, edit requests, discounts, closing hours validation, active booking limits |
|
||||||
|
| `handlers/payments` | Square payments (terminal, online, refunds, tips, saved cards) |
|
||||||
|
| `internal/square` | Square client interface, dev mock, prod stub |
|
||||||
| `handlers/admin` | Admin bookings, today view, users, services |
|
| `handlers/admin` | Admin bookings, today view, users, services |
|
||||||
| `handlers/scheduling` | Working hours, exceptional groups, available hours, time blockers |
|
| `handlers/scheduling` | Working hours, exceptional groups, available hours, time blockers |
|
||||||
| `handlers/services` | Service eligibility (age + patch test filtering) |
|
| `handlers/services` | Service eligibility (age + patch test filtering) |
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Backend (:8080)
|
|||||||
|---------|--------|---------|
|
|---------|--------|---------|
|
||||||
| SabreDAV (CardDAV/CalDAV) | Active | Contact sync (profile photos), calendar events |
|
| SabreDAV (CardDAV/CalDAV) | Active | Contact sync (profile photos), calendar events |
|
||||||
| S3/R2 | Active | Portfolio images (AVIF), profile pictures (WebP) |
|
| S3/R2 | Active | Portfolio images (AVIF), profile pictures (WebP) |
|
||||||
| Square | Placeholder | Payment processing (not wired) |
|
| Square | **Active** | Payment processing — in-person Terminal + online Web Payments SDK. Dev mock (`//go:build dev`) simulates async checkout; prod stub (`//go:build !dev`) connects to live API. |
|
||||||
| SMTP | Not implemented | Email/SMS notifications |
|
| SMTP | Not implemented | Email/SMS notifications |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -48,7 +48,9 @@ Backend (:8080)
|
|||||||
| Package | File(s) | Purpose |
|
| Package | File(s) | Purpose |
|
||||||
|---------|---------|---------|
|
|---------|---------|---------|
|
||||||
| `handlers/auth` | local.go, social.go | Registration, login, refresh, email verification |
|
| `handlers/auth` | local.go, social.go | Registration, login, refresh, email verification |
|
||||||
| `handlers/bookings` | bookings.go, reserve.go, manage.go, admin_reserve.go | Booking CRUD, reservations, admin management, edit requests, discounts |
|
| `handlers/bookings` | bookings.go, reserve.go, manage.go, admin_reserve.go | Booking CRUD, reservations, admin management, edit requests, discounts, closing hours validation, active booking limits |
|
||||||
|
| `handlers/payments` | handlers.go, service.go, validators.go | Square payments: terminal, online, refunds, tips, saved cards |
|
||||||
|
| `handlers/webhooks` | square.go | Square webhook handler for payment status updates |
|
||||||
| `handlers/admin` | users.go, analytics.go, discount_campaigns.go | Admin user management, discount campaigns, analytics (stub) |
|
| `handlers/admin` | users.go, analytics.go, discount_campaigns.go | Admin user management, discount campaigns, analytics (stub) |
|
||||||
| `handlers/today` | today.go | Current/next appointment, today's grid, pending approvals |
|
| `handlers/today` | today.go | Current/next appointment, today's grid, pending approvals |
|
||||||
| `handlers/user` | profile.go, account.go, guest.go, loyalty.go, customer_relationship.go | User profile, guest creation, loyalty, contact info |
|
| `handlers/user` | profile.go, account.go, guest.go, loyalty.go, customer_relationship.go | User profile, guest creation, loyalty, contact info |
|
||||||
@@ -82,6 +84,7 @@ Backend (:8080)
|
|||||||
| `internal/validators` | ID validation (12-char hex format) |
|
| `internal/validators` | ID validation (12-char hex format) |
|
||||||
| `internal/dav` | SabreDAV CardDAV integration (build tags: `service_dev.go` / `service_prod.go`) |
|
| `internal/dav` | SabreDAV CardDAV integration (build tags: `service_dev.go` / `service_prod.go`) |
|
||||||
| `internal/s3` | S3/R2 storage abstraction (build tags: dev vs prod) |
|
| `internal/s3` | S3/R2 storage abstraction (build tags: dev vs prod) |
|
||||||
|
| `internal/square` | Square client interface + dev mock + prod stub (build tags: `dev` vs `!dev`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -99,7 +102,7 @@ Backend (:8080)
|
|||||||
| `/today` | today/+page.svelte | Staff daily view — appointments, approvals |
|
| `/today` | today/+page.svelte | Staff daily view — appointments, approvals |
|
||||||
| `/portfolio` | portfolio/+page.svelte | Image gallery with tag/category filtering |
|
| `/portfolio` | portfolio/+page.svelte | Image gallery with tag/category filtering |
|
||||||
| `/prices` | prices/+page.svelte | Service price list |
|
| `/prices` | prices/+page.svelte | Service price list |
|
||||||
| `/schedule` | schedule/+page.svelte | User's upcoming appointments with .ics export |
|
| `/schedule` | schedule/+page.svelte | User's upcoming appointments with .ics export, payment buttons |
|
||||||
| `/contact` | contact/+page.svelte | Dynamic contact info from first admin user |
|
| `/contact` | contact/+page.svelte | Dynamic contact info from first admin user |
|
||||||
| `/manage` | manage/+page.svelte | Booking management |
|
| `/manage` | manage/+page.svelte | Booking management |
|
||||||
| `/demo` | demo/+page.svelte | Demo mode |
|
| `/demo` | demo/+page.svelte | Demo mode |
|
||||||
@@ -127,13 +130,16 @@ src/lib/components/
|
|||||||
│ └── WalkInCreateModal.svelte # Walk-in 3-step wizard
|
│ └── WalkInCreateModal.svelte # Walk-in 3-step wizard
|
||||||
├── booking/
|
├── booking/
|
||||||
│ ├── BookingActions.svelte # Next/Back buttons
|
│ ├── BookingActions.svelte # Next/Back buttons
|
||||||
│ ├── BookingFlow.svelte # 5-step booking wizard (1287 lines)
|
│ ├── BookingFlow.svelte # 5-step booking wizard (deposit step conditional, confirmation screen)
|
||||||
│ ├── BookingSummary.svelte # Booking review summary
|
│ ├── BookingSummary.svelte # Booking review summary
|
||||||
│ ├── DatePicker.svelte # Calendar date selection
|
│ ├── DatePicker.svelte # Calendar date selection
|
||||||
│ ├── ServiceCard.svelte # Individual service display
|
│ ├── ServiceCard.svelte # Individual service display
|
||||||
│ ├── ServiceSelector.svelte # Service selection UI
|
│ ├── ServiceSelector.svelte # Service selection UI
|
||||||
│ ├── StepIndicator.svelte # Progress steps
|
│ ├── StepIndicator.svelte # Progress steps
|
||||||
│ └── TimeSlotPicker.svelte # Time slot grid
|
│ └── TimeSlotPicker.svelte # Time slot grid
|
||||||
|
├── payments/
|
||||||
|
│ ├── PaymentModal.svelte # Admin payment modal (Terminal, override, tip)
|
||||||
|
│ └── UserPaymentModal.svelte # User payment modal (deposit, partial, full, no tip)
|
||||||
├── today/
|
├── today/
|
||||||
│ ├── CurrentAppointment.svelte # Active appointment display
|
│ ├── CurrentAppointment.svelte # Active appointment display
|
||||||
│ ├── PendingApprovals.svelte # Pending booking approvals
|
│ ├── PendingApprovals.svelte # Pending booking approvals
|
||||||
@@ -200,6 +206,11 @@ src/lib/components/
|
|||||||
| DELETE | `/api/bookings/{id}` | Cancel booking (with forgiveness option) |
|
| DELETE | `/api/bookings/{id}` | Cancel booking (with forgiveness option) |
|
||||||
| POST | `/api/bookings/{id}/edit-request` | Request booking reschedule |
|
| POST | `/api/bookings/{id}/edit-request` | Request booking reschedule |
|
||||||
| DELETE | `/api/bookings/{id}/edit-request` | Cancel edit request |
|
| DELETE | `/api/bookings/{id}/edit-request` | Cancel edit request |
|
||||||
|
| POST | `/api/bookings/{id}/payment` | Create online payment (deposit, full, partial, balance) |
|
||||||
|
| POST | `/api/bookings/{id}/tip` | Add tip to completed booking |
|
||||||
|
| GET | `/api/bookings/{id}/payment-summary` | Get payment summary for booking |
|
||||||
|
| GET | `/api/user/payment-methods` | Get user's saved cards |
|
||||||
|
| DELETE | `/api/user/payment-methods/{id}` | Soft-delete a saved card |
|
||||||
|
|
||||||
### Admin Endpoints
|
### Admin Endpoints
|
||||||
|
|
||||||
@@ -248,6 +259,10 @@ src/lib/components/
|
|||||||
| PUT | `/api/scheduling/exceptional-applications` | Apply exceptions to dates |
|
| PUT | `/api/scheduling/exceptional-applications` | Apply exceptions to dates |
|
||||||
| POST | `/api/portfolio/images` | Upload portfolio image |
|
| POST | `/api/portfolio/images` | Upload portfolio image |
|
||||||
| DELETE | `/api/portfolio/images/{id}` | Delete portfolio image |
|
| DELETE | `/api/portfolio/images/{id}` | Delete portfolio image |
|
||||||
|
| POST | `/api/admin/bookings/{id}/payment` | Create Terminal payment |
|
||||||
|
| GET | `/api/admin/payments/{checkout_id}/status` | Poll checkout status |
|
||||||
|
| POST | `/api/admin/payments/{payment_id}/refund` | Refund payment |
|
||||||
|
| GET | `/api/webhooks/square` | Square webhook endpoint |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -271,7 +286,7 @@ src/lib/components/
|
|||||||
| `discount_campaign_scope` | `all_bookings`, `first_booking_only`, `new_customers_only` |
|
| `discount_campaign_scope` | `all_bookings`, `first_booking_only`, `new_customers_only` |
|
||||||
| `discount_campaign_status` | `draft`, `active`, `completed`, `cancelled` |
|
| `discount_campaign_status` | `draft`, `active`, `completed`, `cancelled` |
|
||||||
|
|
||||||
### Tables (25 total)
|
### Tables (29 total)
|
||||||
|
|
||||||
| Table | Purpose |
|
| Table | Purpose |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
@@ -281,7 +296,7 @@ src/lib/components/
|
|||||||
| `patch_tests` | Patch test definitions (notice_duration_hours, expiry_months, service_ids) |
|
| `patch_tests` | Patch test definitions (notice_duration_hours, expiry_months, service_ids) |
|
||||||
| `user_patch_tests` | User patch test completion records (tested_at, notes) |
|
| `user_patch_tests` | User patch test completion records (tested_at, notes) |
|
||||||
| `services` | Service offerings |
|
| `services` | Service offerings |
|
||||||
| `bookings` | Appointment records (idempotency_key, discount_eligible) |
|
| `bookings` | Appointment records (idempotency_key, discount_eligible, deposit_required, deposit_paid, deposit_amount, deposit_deadline) |
|
||||||
| `booking_services` | Services per booking (override_price, override_duration_minutes) |
|
| `booking_services` | Services per booking (override_price, override_duration_minutes) |
|
||||||
| `booking_edit_requests` | Pending customer edit requests |
|
| `booking_edit_requests` | Pending customer edit requests |
|
||||||
| `user_referrals` | Referral tracking |
|
| `user_referrals` | Referral tracking |
|
||||||
@@ -291,7 +306,11 @@ src/lib/components/
|
|||||||
| `exceptional_group_applications` | Apply exceptions to date ranges |
|
| `exceptional_group_applications` | Apply exceptions to date ranges |
|
||||||
| `time_blockers` | Admin time blocks + slot reservations (description LIKE 'RESERVATION:%') |
|
| `time_blockers` | Admin time blocks + slot reservations (description LIKE 'RESERVATION:%') |
|
||||||
| `forgiven_no_shows` | Tracks forgiven no-shows |
|
| `forgiven_no_shows` | Tracks forgiven no-shows |
|
||||||
| `payments` | Payment transactions (VAT fields, invoice_number sequence) |
|
| `payments` | Payment transactions (VAT fields, invoice_number sequence, fees column for Square deductions, saved_card_id) |
|
||||||
|
| `user_saved_cards` | Saved card details (square_card_id, brand, last4, fingerprint, soft delete with retained_until) |
|
||||||
|
| `refunds` | Refund records linked to payments (amount, reason, square_refund_id) |
|
||||||
|
| `square_deposits` | Square deposit batch tracking for bank reconciliation (batch_id, total_amount, deposited_at) |
|
||||||
|
| `affiliate_payouts` | Affiliate commission tracking |
|
||||||
| `loyalty_redemptions` | Loyalty stamp redemptions (6-month expiry) |
|
| `loyalty_redemptions` | Loyalty stamp redemptions (6-month expiry) |
|
||||||
| `discount_campaigns` | Discount campaigns (time-based and milestone) |
|
| `discount_campaigns` | Discount campaigns (time-based and milestone) |
|
||||||
| `booking_discounts` | Applied discounts per booking |
|
| `booking_discounts` | Applied discounts per booking |
|
||||||
@@ -544,9 +563,10 @@ Users manage their preferred notification channels via `/account` → Admin tab
|
|||||||
|
|
||||||
| Tag | Files | Purpose |
|
| Tag | Files | Purpose |
|
||||||
|-----|-------|---------|
|
|-----|-------|---------|
|
||||||
| `dev` | `db_dev.go`, `service_dev.go` | Local development (localhost DB, RustFS S3) |
|
| `dev` | `db_dev.go`, `service_dev.go`, `square_dev.go` | Local development (localhost DB, RustFS S3, Square mock client) |
|
||||||
| (none/prod) | `db.go`, `service_prod.go` | Production (env var DB host, Cloudflare R2) |
|
| (none/prod) | `db.go`, `service_prod.go`, `square.go` | Production (env var DB host, Cloudflare R2, live Square API) |
|
||||||
| `test` | All `*_test.go` files | Test build (separate test database) |
|
| `test` | All `*_test.go` files | Test build (separate test database, uses dev mock) |
|
||||||
|
| `test,dev` | Test files + dev build | Run tests with Square mock client |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -607,18 +627,20 @@ Used throughout for atomic operations: booking creation, service updates, schedu
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
go test -tags test ./... # All tests
|
go test -tags "test,dev" ./... # All tests (requires Square mock)
|
||||||
go test -tags test -v ./... # Verbose
|
go test -tags "test,dev" -v ./... # Verbose
|
||||||
go test -tags test -v ./handlers/bookings # Specific package
|
go test -tags "test,dev" -v ./handlers/bookings # Specific package
|
||||||
go test -tags test -v -run "TestBooking" ./... # Pattern match
|
go test -tags "test,dev" -v -run "TestBooking" ./... # Pattern match
|
||||||
go test -tags test -v -p 1 -count=2 ./... # Run twice for flaky detection
|
go test -tags "test,dev" -v -p 1 -count=2 ./... # Run twice for flaky detection
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Coverage
|
### Test Coverage
|
||||||
|
|
||||||
**306/308 tests passing** across 10+ packages:
|
**351/351 tests passing** across 12+ packages:
|
||||||
- `handlers/auth` — Authentication
|
- `handlers/auth` — Authentication
|
||||||
- `handlers/bookings` — User booking flow, guest bookings, reservations, edit requests, discounts
|
- `handlers/bookings` — User booking flow, guest bookings, reservations, edit requests, discounts, closing hours validation, active booking limits
|
||||||
|
- `handlers/payments` — Square payments (terminal, online, refunds, tips, saved cards)
|
||||||
|
- `internal/square` — Square client dev mock tests
|
||||||
- `handlers/admin` — Admin bookings, today view, users, services
|
- `handlers/admin` — Admin bookings, today view, users, services
|
||||||
- `handlers/scheduling` — Working hours, exceptional groups, available hours, time blockers
|
- `handlers/scheduling` — Working hours, exceptional groups, available hours, time blockers
|
||||||
- `handlers/services` — Service eligibility
|
- `handlers/services` — Service eligibility
|
||||||
@@ -646,13 +668,31 @@ go test -tags test -v -p 1 -count=2 ./... # Run twice for flaky detection
|
|||||||
- **Image formats**: AVIF full-size (0.72 quality, 1500px max), WebP thumbnails (250x250)
|
- **Image formats**: AVIF full-size (0.72 quality, 1500px max), WebP thumbnails (250x250)
|
||||||
- **Security**: EXIF/GPS metadata stripped on upload via `imaging` library
|
- **Security**: EXIF/GPS metadata stripped on upload via `imaging` library
|
||||||
|
|
||||||
### Square (Placeholder)
|
### Square Payment Integration
|
||||||
|
|
||||||
|
**Architecture:** `internal/square/` defines a `Client` interface with two implementations:
|
||||||
|
- **Dev mock** (`square_dev.go`, `//go:build dev`): Simulates Square Terminal checkout asynchronously. Creates a checkout session that transitions from PENDING → COMPLETED after a short delay. Used for local testing without API credentials.
|
||||||
|
- **Prod stub** (`square.go`, `//go:build !dev`): Connects to live Square API using `github.com/square/square-go-sdk`. Requires `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`, and `SQUARE_WEB_PAYMENTS_APP_ID` environment variables.
|
||||||
|
|
||||||
|
**Initialization:** `main.go` calls `square.NewClient()` (wrapper function in both build-tagged files) to avoid `undefined: square.NewDevClient` during test builds.
|
||||||
|
|
||||||
|
**Payment flows:**
|
||||||
|
1. **Terminal (in-person)**: Admin initiates via `POST /api/admin/bookings/{id}/payment` → creates Square Terminal checkout → polls status → completes booking
|
||||||
|
2. **Online (Web Payments SDK)**: User initiates via `POST /api/bookings/{id}/payment` → creates online payment → processes card token → completes booking
|
||||||
|
3. **Tips**: `POST /api/bookings/{id}/tip` — only for completed bookings
|
||||||
|
4. **Refunds**: `POST /api/admin/payments/{payment_id}/refund` — partial or full refunds
|
||||||
|
|
||||||
|
**Saved cards:** `user_saved_cards` table stores `square_card_id`, `brand`, `last4`, `fingerprint`, `expiry_month`, `expiry_year`. Soft-deleted with `retained_until` set to 7 years from deletion (UK financial data retention law). Users can have multiple saved cards.
|
||||||
|
|
||||||
|
**Webhooks:** Square sends webhook events to `/api/webhooks/square` for payment status updates. Signature verified via `x-square-signature` header.
|
||||||
|
|
||||||
|
**Idempotency:** All payments use idempotency keys. Duplicate key returns existing payment (200, no charge).
|
||||||
|
|
||||||
|
**Fees tracking:** `payments.fees` column stores actual Square deduction at transaction time. Rates change over time, so storing computed fees is necessary for accurate financial reporting (FreeAgent/MTD).
|
||||||
|
|
||||||
|
**Deposit batch tracking:** `square_deposits` table tracks Square batch deposits for bank reconciliation. Each row represents a batch deposited to the Mettle account.
|
||||||
|
|
||||||
- `handlers/payments/square.go` exists but returns "not implemented"
|
|
||||||
- `payment_method` enum includes `online_square`
|
|
||||||
- Frontend BookingFlow Step 4 shows placeholder
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|||||||
@@ -241,11 +241,36 @@ While you have deposit obligations:
|
|||||||
- You can only book appointments that are **at least 24 hours away**
|
- You can only book appointments that are **at least 24 hours away**
|
||||||
- You can only have **one active booking at a time**
|
- You can only have **one active booking at a time**
|
||||||
- You need to complete appointments and pay for them to reduce your deposit count
|
- You need to complete appointments and pay for them to reduce your deposit count
|
||||||
|
- A warning banner appears at the top of the booking flow explaining the limit
|
||||||
|
|
||||||
### How Do Deposits Go Down?
|
### How Do Deposits Go Down?
|
||||||
|
|
||||||
Each time you complete an appointment and pay for it, your deposit count goes down by **1**. After all 3 are cleared, you can book normally again.
|
Each time you complete an appointment and pay for it, your deposit count goes down by **1**. After all 3 are cleared, you can book normally again.
|
||||||
|
|
||||||
|
### Paying Deposits Online
|
||||||
|
|
||||||
|
If you have a deposit outstanding, you can pay it online without waiting for your appointment:
|
||||||
|
|
||||||
|
1. Go to **Schedule** from the menu
|
||||||
|
2. Click on your upcoming booking
|
||||||
|
3. Click **Pay Deposit** — this opens the payment modal
|
||||||
|
4. The deposit amount (20% of the booking total) is shown
|
||||||
|
5. Choose to pay the deposit or pay the full amount
|
||||||
|
6. Enter your card details or use a saved card
|
||||||
|
7. Confirm payment
|
||||||
|
|
||||||
|
If no deposit is owed but you haven't paid the full amount yet, you'll see a **Pay Early** button instead. You can also pay a partial amount using the **Pay Part** option with a custom amount input.
|
||||||
|
|
||||||
|
### Leaving a Tip
|
||||||
|
|
||||||
|
After your appointment is marked as **completed**, you can leave a tip:
|
||||||
|
|
||||||
|
1. Go to **Account** from the menu
|
||||||
|
2. Find your completed booking and click **View**
|
||||||
|
3. Click **Leave a Tip**
|
||||||
|
4. Enter the tip amount or choose a preset percentage
|
||||||
|
5. Confirm payment
|
||||||
|
|
||||||
### Quick Reference
|
### Quick Reference
|
||||||
|
|
||||||
| What happens | Result |
|
| What happens | Result |
|
||||||
|
|||||||
Reference in New Issue
Block a user