{#if groupedTimeSlots.length > 0}
{#if formattedDate}
@@ -57,7 +57,7 @@
onselect(slot.startTime);
}
}}
- class={`w-full hover:bg-fuchsia-50 ${slot.startTime === selectedTime ? 'bg-fuchsia-100' : ''}`}
+ class={`min-h-11 w-full hover:bg-fuchsia-50 ${slot.startTime === selectedTime ? 'bg-fuchsia-100' : ''}`}
>
{formatTime(slot.startTime)}
- {formatTime(slot.endTime)}
@@ -66,7 +66,7 @@
{formatTime(slot.startTime)}
diff --git a/frontend/src/lib/components/payments/MockCardForm.svelte b/frontend/src/lib/components/payments/MockCardForm.svelte
index 6b2055f..bf6735a 100644
--- a/frontend/src/lib/components/payments/MockCardForm.svelte
+++ b/frontend/src/lib/components/payments/MockCardForm.svelte
@@ -7,9 +7,11 @@
* dynamically imports this module and calls this export when the mock form is
* active, so the saved-card (ccof) 3DS/SCA path runs end to end. The token is
* deterministic and stateless — it encodes the card prefix, the bound amount and
- * the encoded outcome (`_ok`) — and the backend dev mock
- * (square_dev.go parseVerifyToken) parses the same shape back, so local-dev
- * mirrors production without shared state.
+ * the encoded outcome (`_ok`) — as a GENUINE tokenize-result shape
+ * (`cnon:sca-__ok`) that the backend dev mock's saved-card SCA
+ * gate recognises via isSCATokenizeResultSource (square_dev.go checks the
+ * `cnon:sca-` prefix), matching Square's real card-on-file SCA contract where
+ * the tokenize-result token IS the charge source.
*/
export function tokenizeSavedCard(
amount: number,
@@ -22,7 +24,7 @@
const stripped = squareCardId.startsWith('ccof:') ? squareCardId.slice(5) : squareCardId;
const prefix = stripped.slice(0, 4) || 'test';
return Promise.resolve({
- verificationToken: `verify_mock_${prefix}_${String(Math.round(amount))}_ok`,
+ verificationToken: `cnon:sca-${prefix}_${String(Math.round(amount))}_ok`,
outcome: 'verified'
});
}
@@ -30,6 +32,7 @@
diff --git a/frontend/src/lib/components/payments/OverflowTipConfirm.svelte b/frontend/src/lib/components/payments/OverflowTipConfirm.svelte
index dbce4e6..9ba5bf3 100644
--- a/frontend/src/lib/components/payments/OverflowTipConfirm.svelte
+++ b/frontend/src/lib/components/payments/OverflowTipConfirm.svelte
@@ -45,9 +45,11 @@
-
+
Confirm
- Cancel
+
+ Cancel
+
diff --git a/frontend/src/lib/components/payments/PaymentModal.svelte b/frontend/src/lib/components/payments/PaymentModal.svelte
index 14206f9..c52ca00 100644
--- a/frontend/src/lib/components/payments/PaymentModal.svelte
+++ b/frontend/src/lib/components/payments/PaymentModal.svelte
@@ -1,5 +1,5 @@
Gift Card Terms
+
@@ -12,6 +46,12 @@
Last updated: August 2026
+ {#if format === 'pdf' && pdfNotice}
+
+ Generating PDF… If the print dialog does not appear, use Ctrl+P / Cmd+P.
+
+ {/if}
+
1. What These Terms Cover
diff --git a/frontend/src/routes/privacy-policy/+page.svelte b/frontend/src/routes/privacy-policy/+page.svelte
index 1116192..372eab3 100644
--- a/frontend/src/routes/privacy-policy/+page.svelte
+++ b/frontend/src/routes/privacy-policy/+page.svelte
@@ -2,6 +2,7 @@
import { page } from '$app/stores';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
+ import { SUPPORT_EMAIL, BUSINESS_ADDRESS, TRADER_LEGAL_NAME } from '$lib/constants/contact';
let format = $state('html');
@@ -69,9 +70,9 @@
Data Controller
Crussell Salon
-
Edinburgh, Scotland
-
-
Email: {'{{SUPPORT_EMAIL}}'}
+
Trader: {TRADER_LEGAL_NAME}
+
{BUSINESS_ADDRESS}
+
Email: {SUPPORT_EMAIL}
ICO registration (operator responsibility)
As a data controller, the salon must register with the Information Commissioner's Office
@@ -159,7 +160,7 @@
Gift card codes and balances
Account balances
-
Payment transaction records (processed via Square, not stored by us)
+
Payment transaction records (our ledger of record, retained for 7 years for HMRC)
Saved-card references (tokenised, stored with our payment provider Square — see
§2.2)
@@ -173,7 +174,7 @@
When you choose to save a card for next time, we store a tokenised
reference to your card with our payment processor, Square (a data processor),
- rather than on our own systems.
+ and keep the display metadata (last 4 digits and expiry) in your account record.
@@ -211,8 +212,10 @@
- We never store full card numbers, card security codes (CVV), or card expiry data on our own
- systems at any point.
+ We never store full card numbers (PANs) or card security codes (CVV). When you save a card,
+ we store a Square tokenised reference plus the last 4 digits and the expiry month/year of the
+ card on our systems, so the card can be displayed in your account and refunds can be matched
+ to the original payment method.
@@ -270,12 +273,82 @@
SNAPSHOT_ENC_KEY before go-live so buyer email and card-token data in these records
is encrypted.
+
+
+ 2.6 Third Parties & Infrastructure
+
+
+ We use a small number of third-party services to operate the Platform. Each receives only
+ the data needed for its function:
+
+
+
+ Cloudflare — our edge proxy and CDN. Cloudflare routes traffic to
+ the Platform and enforces our UK-only geo-block; its edge servers see the IP address you
+ connect from (conveyed to us as CF-Connecting-IP where we need to identify a
+ connection).
+
+
+ Cloudflare R2 / S3-compatible object storage — profile pictures are
+ stored in object storage (the crussell-profile-pics bucket).
+
+
+ CardDAV / sabre/dav sync — your profile photo is synchronised to a
+ CardDAV address-book endpoint so it displays consistently across the Platform.
+
+
+ Google Fonts — the Playfair Display typeface is loaded from
+ fonts.googleapis.com; Google’s servers see your IP address when your
+ device fetches the font.
+
+
+ CARTO — map tiles on the contact page are served from
+ basemaps.cartocdn.com; CARTO’s servers see your IP address when your
+ device fetches map tiles.
+
+
+
+
3. International Transfers
+
+ Our payment processor, Square, is based in the United States. When you pay
+ by card or save a card, the personal data that supports the payment — your name, email
+ address, and card-payment references — is processed by Square and may be transferred
+ outside the UK.
+
+
+
+ What actually crosses the border: Square’s payment script (Square.js)
+ runs in your browser and tokenises your card details into a one-time nonce or a stored-card
+ reference before anything is sent to our servers. We never send your full card number to
+ Square’s US systems ourselves; only these nonces and references (plus the name and
+ email we already hold) travel to Square.
+
+
+ Lawful basis and safeguards: transfers are made under UK GDPR
+ Article 46 on the basis of appropriate safeguards. We rely on
+ Square’s Data Processing Addendum, which incorporates the
+ UK International Data Transfer Addendum and/or the
+ Standard Contractual Clauses issued by the Information Commissioner’s
+ Office, to protect your data when it leaves the UK.
+
+
+ More information: Square’s privacy policy (linked in §2.2)
+ explains how Square handles data on our behalf.
+
+
+
+ This is a summary of a general nature, not legal advice; please verify the position with a
+ solicitor before going live.
+
+
+
+
- 3. Data Retention & Deletion Process
+ 4. Data Retention & Deletion Process
+ 7 years (soft-deleted rows are scrubbed of Square ids, last-4 digits and expiry,
+ then retained for chargeback and audit)
+
+
Card-scheme chargeback rules; HMRC record-keeping
+
Allergy/health records
7 years
@@ -408,9 +489,9 @@
-
+
-
4. Your Rights
+
5. Your Rights
Under UK GDPR, you have the right to:
Access your personal data (Article 15)
@@ -424,7 +505,7 @@
Withdraw Consent (Article 7(3))
- To exercise these rights, contact {'{{SUPPORT_EMAIL}}'}. You also have the right to complain
+ To exercise these rights, contact {SUPPORT_EMAIL}. You also have the right to complain
to the Information Commissioner’s Office (ICO) at any time — via the ICO website
(ico.org.uk) or by writing to the ICO, Wycliffe House, Water Lane, Wilmslow, Cheshire SK9
5AF. If you have concerns, we would ask you to contact us first so we can try to resolve
diff --git a/frontend/src/routes/terms/+page.svelte b/frontend/src/routes/terms/+page.svelte
index 0357811..ac9e01e 100644
--- a/frontend/src/routes/terms/+page.svelte
+++ b/frontend/src/routes/terms/+page.svelte
@@ -2,6 +2,7 @@
import { page } from '$app/stores';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
+ import { SUPPORT_EMAIL, BUSINESS_NAME, BUSINESS_ADDRESS, TRADER_LEGAL_NAME } from '$lib/constants/contact';
let format = $state('html');
@@ -66,10 +67,10 @@
Business Details
-
Trading name: Crussell Salon
-
Registered address: Edinburgh, Scotland
-
-
Contact email: {'{{SUPPORT_EMAIL}}'}
+
Trading name: {BUSINESS_NAME}
+
Trader: {TRADER_LEGAL_NAME}
+
Address: {BUSINESS_ADDRESS}
+
Contact email: {SUPPORT_EMAIL}
VAT: Not currently registered (threshold £90,000; will register when reached)
@@ -90,10 +91,11 @@
You will lose access to loyalty stamps, referral codes, and booking history.
- If your account has a balance: your balance becomes dormant and is
- transferred to our recovery registry. You will receive your
- Account ID by email (once email delivery is available) and can recover your balance
- at any time by providing it. All other personal data is anonymized.
+ If your account has a balance: the balance is retained on your anonymised
+ account record after deletion, and you can recover it with your
+ Account ID. Your Account ID will be sent to you by email once email
+ delivery is available. Contact us if you believe a balance is missing. All other personal
+ data is anonymized.
Warning: account deletion is permanent. You will lose access to your account,
@@ -127,7 +129,11 @@
You will receive confirmation on-screen and in your account (via email/SMS once email
delivery is available).
-
Some services require a deposit (typically 20–50% of the service cost).
+
+ Some services require a deposit (typically 20% of the service cost; a
+ protected-deposit cap of 50% may be retained on short-notice cancellation, capped at
+ what you actually paid).
+
Cancellations & rescheduling
@@ -156,7 +162,12 @@
No-show: all booking payments and deposits are retained; may affect future
booking eligibility.
-
Business cancellation: full refund or reschedule offered.
+
+ Business cancellation: if we have to cancel, you are offered a full
+ refund or a free reschedule. If you choose a refund, it is processed under our standard
+ refund tiers above unless we waive them (for example when the cancellation is our own
+ scheduling conflict).
+
Deposits
@@ -165,7 +176,7 @@
the booking value between 24 and 72 hours' notice; all payments retained under 24 hours).
Deposits are applied to your final bill.
-
If we cancel, the deposit is fully refunded.
+
If we cancel, any deposit paid is refunded under the business-cancellation terms above.
Service changes
@@ -300,7 +311,43 @@
-
6. Liability
+
+ 6. Distance Contracts & Right to Cancel
+
+
+ Purchases made on our Platform (rather than face-to-face in the salon) are
+ distance contracts under the Consumer Contracts (Information, Cancellation
+ and Additional Charges) Regulations 2013. This gives you a
+ 14-day right to cancel most online purchases, running from the day after
+ purchase.
+
+
+
+ Gift cards bought online carry this 14-day right, refunded to the
+ original payment method — in full if unused, or the unspent balance if partly used
+ on salon services (the card is then cancelled). See section 5 and our
+ Gift Card Terms
+ for the full position.
+
+
+ Appointment bookings made online for a specific date are services with a
+ specified date of performance (regulation 28(1)(h) — services related to leisure
+ activities), so the 14-day right does not apply to the service itself; our cancellation and
+ refund policy in section 3 applies instead.
+
+
+ How to exercise it: for gift cards, cancel in-app from your account
+ within 14 days (no email needed); for anything else, email {SUPPORT_EMAIL} within 14 days.
+ Refunds are made within 14 days, to the original payment method.
+
+
+
+
+
+
7. Liability
Nothing in these Terms excludes or limits any rights you have under consumer law —
@@ -324,7 +371,7 @@
If you are unhappy with any part of our service, please contact us first at
- {'{{SUPPORT_EMAIL}}'} — we will do our best to resolve your complaint fairly. You can get
+ {SUPPORT_EMAIL} — we will do our best to resolve your complaint fairly. You can get
free, impartial consumer advice from
-