Files
Crussell/frontend/src/routes/cancellation-policy/+page.svelte
T
popertots b7122be3a0 fix: SCA review round + gitea pipeline green — GDPR audit scrub, backend test gaps, frontend SCA/Square-API, docs parity
7 review agents (pipeline run, self-review, codebase-context, frontend-placement,
backend testing-gaps, Square-API, docs-parity) audited the SCA-primary work.
ALL findings fixed, including every pre-existing red CI job:

GDPR (HIGH):
- anonymize_user() now scrubs admin_audit_log.target_user_id (mirrors
  delete_guest_user) so 2fa_fallback_charge rows (customer id + card_last4 PII)
  no longer survive registered-user account deletion; gdpr test added

BACKEND TEST GAPS (all 10):
- delivery-unavailable 503 branch: prod-tag predicate test + dev-variant marker
- twoFactorFallbackEnabled alias/case/default matrix tests + exported wrapper
- insertTwoFAFallbackAudit details-JSON shape + audit-row assertions for all
  6 gate sites (booking/tip/gift-card/payment-method/terminal/till, both actors)
- CreateTerminalPayment.VerificationToken: passthrough, too-long 400, 2FA-skip,
  token-less fallback + SCA-required (new terminal_sca_test.go)
- isVerificationRequiredError at all 5 charge sites (402 + code:verification_required)
- customer_initiated handler-level assertions (MIT false admin / CIT true customer)
- Mock: ApprovePendingVerification, ChallengeResult auto/deny, _deny token suffix,
  parseVerifyToken unit tests

FRONTEND SCA + Square-API (CRITICAL):
- tokenizeSavedCardWithVerification reads result.token (the verified token) not
  result.verificationResult (deprecated verifyBuyer shape — saved-card SCA could
  never succeed in production before); parseTokenizeVerificationResult pure fn
  extracted + pinned in square.test.ts; 'verified' with no token proceeds tokenless
- HIGH: saved-card idempotency key regenerated after a definitive 402 (fresh token
  under the same key = IDEMPOTENCY_KEY_REUSED dead-loop); kept on 503/cancelled
- challenge-cancelled copy no longer promises a 2FA fallback the UI doesn't show;
  'waiting for approval in your banking app' state on CIT surfaces
- sca-unavailable demotion resets per attempt; card selection disabled mid-challenge;
  genuine saved-card declines no longer relabeled 'requires verification';
  modal-close guard during processing; retry affordance standardized

PIPELINE (every red job now green):
- prod-tag build break fixed (shared square stub + test_helpers_test.go, prod-safe)
- govulncheck: x/image 0.45.0 bumped (x/text resolved); go mod tidy clean
- race: TestDeleteAccount_InvalidatesSquareCustomerCache made deterministic
- DAV_ADMIN_PASSWORD placeholder in .env.example (compose config passes)
- frontend: prettier 28 files, eslint, a11y 38 errors, knip (currentZIndex),
  deps in-range, audit vulns (nanoid/postcss) — all fixed; 67 vitest cases

DOCS PARITY (6 DRIFTs + 5 GAPs): payments doc Ch4/Ch14/Appendix A, Technical
Manual 2FA + counter-reset + payment sections, README test counts + SNAPSHOT_ENC_KEY,
Feature Catalog, .env.example REQUIRE_2FA — SCA-primary/2FA-backup posture verified
against code everywhere

Verified: 26/26 dev + 24/24 prod packages, both vet tags, golangci-lint/staticcheck/
gosec 0 on both tags, gitleaks clean, 2,464 backend + 67 frontend tests.
2026-08-22 00:34:50 +01:00

344 lines
15 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { page } from '$app/stores';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
let format = $state('html');
let pdfNotice = $state(true);
onMount(() => {
format = $page.url.searchParams.get('format') || 'html';
if (format === 'pdf') {
// Strip ?format=pdf from the URL so a refresh doesn't re-trigger the print dialog.
const clean = window.location.pathname + window.location.hash;
history.replaceState(null, '', clean);
// Open the print dialog once the page is rendered.
// The notice element is removed before print so it won't appear in the PDF.
setTimeout(() => {
pdfNotice = false;
// Small delay so Svelte can remove the element before the print engine snapshots.
setTimeout(() => window.print(), 50);
}, 100);
}
});
</script>
<svelte:head>
<title>Booking, Deposit & Cancellation Policy</title>
<style>
@media print {
:global(nav),
:global(.no-print) {
display: none !important;
}
:global(body) {
padding-top: 0 !important;
}
}
</style>
</svelte:head>
<div class="mx-auto max-w-2xl px-4 py-8 text-gray-900">
<h1 class="mb-2 border-b border-gray-200 pb-4 text-2xl font-bold">
Booking, Deposit & Cancellation Policy
</h1>
<p class="mb-8 font-mono text-xs text-gray-500">Last updated: 5 August 2026</p>
{#if format === 'pdf' && pdfNotice}
<p class="mb-6 rounded border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600 italic">
Generating PDF… If the print dialog does not appear, use Ctrl+P / Cmd+P.
</p>
{/if}
<div class="space-y-8 text-sm leading-relaxed text-gray-700">
<!-- Section 1 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">
1. Deposit Requirements & Booking Rules
</h2>
<p class="mb-3">
Where a deposit is required to secure your appointment, you must pay at least
<strong>20%</strong> of the total service cost before the 24-hour deadline prior to the
appointment. The 20% can be paid in a single payment or accumulated across multiple payments
— what matters is the total when the deadline passes. When deposit restrictions are active
on your account, appointments must be scheduled at least
<strong>36 hours in advance</strong>.
</p>
<p class="mb-3">
Payments toward your booking are capped at 100% of the total booking value based on service
prices at time of booking. Once you have paid the full amount, any additional payments above
100% of the booking value will be processed as tips (see Section 8 below).
</p>
<p class="mb-3">
To maintain fairness and prevent scheduling abuse, accounts with outstanding deposit
requirements are limited to <strong>one (1) active booking</strong> at any given time. No additional
appointments can be scheduled until your current appointment is either completed, cancelled, or
officially lapsed.
</p>
</section>
<!-- Section 2 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">
2. Unpaid Deposits & The "Pending Release" Window
</h2>
<p class="mb-3">
If a required deposit is not paid at least 24 hours before the appointment begins, the
booking is shifted into a <strong>"Pending Release"</strong> status. The slot becomes vulnerable
— if another customer books an overlapping time and pays, your original booking is automatically
evicted.
</p>
<p class="mb-3">
While in this status, your appointment is <strong>not guaranteed</strong>. The system will
make this time slot visible to other clients. If another user attempts to book an
overlapping time and completes their deposit payment first, your unpaid booking will be
immediately and automatically evicted.
</p>
<p class="mb-3">
If the slot has not yet been claimed by another client, paying your outstanding deposit will
instantly restore your booking to a fully confirmed status.
</p>
</section>
<!-- Section 3 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">3. Cancellation & Refund Tiers</h2>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund before service</h3>
<p class="mb-3">
We understand that plans can change. Eligibility for a refund before your service depends
strictly on the amount of notice provided prior to your scheduled appointment time. These
thresholds represent a genuine pre-estimate of the operational costs and loss of business
incurred by late cancellations. Refunds apply to <strong>booking payments only</strong>, up
to 100% of the total booking value.
</p>
<div class="mt-4 divide-y divide-gray-200 rounded-md border border-gray-200">
<div class="bg-gray-50/50 p-4">
<p class="font-semibold text-gray-900">Notice of more than 72 hours</p>
<p class="mt-1 text-xs text-gray-600">
You are entitled to a full 100% refund of all booking payments made.
</p>
</div>
<div class="p-4">
<p class="font-semibold text-gray-900">Notice between 24 and 72 hours</p>
<p class="mt-1 text-xs text-gray-600">
Any booking payments made up to 50% of the total booking value are treated as a
Protected Deposit. This Protected Deposit is retained to cover the short-notice vacancy,
while any balance paid above 50% will be fully refunded.
</p>
</div>
<div class="bg-gray-50/50 p-4">
<p class="font-semibold text-gray-900">Notice of less than 24 hours</p>
<p class="mt-1 text-xs text-gray-600">
All booking payments and deposits are entirely non-refundable and will be retained. The
cancellation will be logged as a missed appointment history strike.
</p>
</div>
</div>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund after service</h3>
<p class="mb-3 text-sm leading-relaxed text-gray-700">
Refunds after booked appoinments have been carried out are at the salon owners discretion
based on the booking and reason, to arrange a refund please <a
href={resolve('/contact')}
class="font-medium text-blue-600 underline hover:text-blue-800">contact</a
> us to discuss a fair refund up to 100% of the value of the booking. Any paid tips will not be
considered as part of the refund as they are processed differently.
</p>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund Payment Method</h3>
<p class="mb-3 text-sm leading-relaxed text-gray-700">
Refunds are returned to the original payment method where possible:
</p>
<ul class="mb-4 list-disc space-y-2 pl-5 text-sm leading-relaxed text-gray-700">
<li>
<strong>Card payments</strong> (debit/credit card processed online or in-person): Refunded directly
back to the original card via Square. Processing times vary by card issuer (typically 310 working
days).
</li>
<li>
<strong>Gift card payments</strong>: Refunded back to the original gift card (or, if you
paid from your account balance, back to that balance). The gift card's remaining balance
is incremented and is immediately available for use. Expired gift cards are
non-refundable.
</li>
<li>
<strong>Cash payments</strong>: Credited to your account balance, available for immediate
use against future bookings or services.
</li>
</ul>
<p class="mb-4 text-xs leading-relaxed text-gray-500 italic">
If a card refund cannot be processed (e.g. the card is expired or the Square payment
reference is unavailable), we will notify you via your account and arrange collection of the
refund in person at the salon — please allow at least a day's notice so we can have cash on
hand. You will never be left out of pocket.
</p>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Guest / Walk-In Bookings</h3>
<p class="mb-3 text-sm leading-relaxed text-gray-700">
Walk-in customers who book without creating an account are issued a <strong
>guest account</strong
> to hold their booking and payment records. The same cancellation and refund tiers above apply
to guest bookings. Refunds are returned as follows:
</p>
<ul class="mb-4 list-disc space-y-2 pl-5 text-sm leading-relaxed text-gray-700">
<li>
<strong>Card payments</strong>: Refunded back to the original card via Square (same as
registered users).
</li>
<li>
<strong>Gift card payments</strong>: Refunded back to the original gift card. The gift
card's remaining balance is incremented. Expired gift cards are non-refundable.
</li>
<li>
<strong>Cash payments</strong>: Refunded in person at the salon. Please bring your receipt
and an admin will process your cash refund at the till.
</li>
</ul>
<p class="mb-3 text-xs leading-relaxed text-gray-500 italic">
Guest accounts do not hold rolling balances. All refunds for walk-in bookings are returned
via the original payment method or processed manually by salon staff.
</p>
</section>
<!-- Section 4 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">4. Missed Appointments (No-Shows)</h2>
<p class="mb-3">
Failing to attend a confirmed appointment without notifying us in advance constitutes a
"No-Show". Cancelling a pending or deposit-lapsed booking within 24 hours does
<strong>not</strong> count as a no-show — only confirmed bookings (where the slot was secured
with a payment) can incur no-show strikes.
</p>
<p class="mb-3">
If your account accumulates <strong
>two (2) No-Show strikes within a rolling 6-month period</strong
>, our system will automatically restrict your account privileges, making upfront deposits
mandatory for future booking attempts (3 deposits required before deposits are automatically
cleared). No-show strikes older than 6 months are automatically excluded from the count.
</p>
<p class="mb-3">
Each completed booking with a payment reduces the required deposit count by one. Once the
count reaches zero, all prior no-show records within the 6-month window are forgiven and
your account returns to normal — no upfront deposits required — until a new no-show occurs.
The salon can also forgive individual no-shows at management's discretion, which immediately
removes them from the count.
</p>
</section>
<!-- Section 5 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">
5. Statutory Rights & Exceptional Circumstances
</h2>
<p class="mb-3">
This policy is strictly aligned with the <strong>Consumer Rights Act 2015</strong>. Nothing
in these terms limits your statutory right to receive services carried out with reasonable
care and skill, or your right to a full refund if we are forced to cancel your appointment
due to our own scheduling conflicts.
</p>
<p class="mb-3">
Please note that in accordance with UK statutory exclusions for distance contracts, the
standard 14-day statutory cancellation "cooling-off" period under the Consumer Contracts
Regulations 2013 does not apply to online bookings scheduled for a specific date or time.
</p>
<p class="mb-3">
That exclusion does not apply to gift cards: online gift-card purchases may be cancelled
within 14 days for a refund to the original payment method under the Consumer Contracts
Regulations 2013. If the card has been partly used, the amount already spent on salon
services is not refundable, and the remaining unspent balance is refunded to the original
payment method; the card is then cancelled. A card that has been redeemed to an account
balance or fully spent cannot be cancelled.
</p>
<p class="mb-3">
Where a partly-used card is cancelled, the card is cancelled automatically when the refund
is issued, so the remaining balance cannot then be spent. See our Gift Card Terms for the
full position.
</p>
<p class="mb-3">
If you believe your statutory consumer rights have not been met, you can get free, impartial
advice from
<a
href="https://consumeradvice.scot"
target="_blank"
rel="noopener noreferrer"
class="font-medium text-blue-600 underline hover:text-blue-800">consumeradvice.scot</a
>
(advice.scot). If that does not resolve the issue, you can escalate your complaint to your local
Trading Standards office. Claims up to &pound;5,000 can also be pursued through the Scottish courts'
Simple Procedure.
</p>
<p class="mb-3">
We recognize that genuine emergencies, sudden severe illness, or bereavement can occur. Our
management team retains complete administrative system access to waive cancellation fees,
refund deposits, or clear no-show history strikes on a case-by-case basis under exceptional
circumstances.
</p>
</section>
<!-- Section 6 -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">6. Policy Amendments and Updates</h2>
<p class="mb-3">
We reserve the right to amend, update, or modify this Booking, Deposit & Cancellation Policy
at any time to reflect changes in our business operations, system features, or legal
obligations under UK and Scottish law.
</p>
<p class="mb-3">
Any updates will be published directly to this page, and the "Last updated" date at the top
will change accordingly. For active bookings scheduled prior to an amendment, the terms in
place at the exact time your booking was created will apply. Continued use of our booking
system after a policy revision implies formal agreement to the updated terms.
</p>
</section>
<!-- Section 7 -->
<section class="border-t border-gray-200 pt-6">
<h2 class="mb-2 text-base font-semibold text-gray-900">7. Governing Law</h2>
<p class="mb-4">
These terms and conditions are governed by and construed in accordance with the laws of
Scotland. Any disputes or legal claims arising from these provisions shall be subject to the
exclusive jurisdiction of the Scottish courts.
</p>
<p class="text-xs text-gray-500">
If you need to request an adjustment due to exceptional circumstances or have questions
regarding your upcoming appointments, please use our official <a
href={resolve('/contact')}
class="font-medium text-blue-600 underline hover:text-blue-800">Contact Channels</a
> to get in touch.
</p>
</section>
<!-- Section 8: Tips -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">8. Tips</h2>
<p class="mb-3">
Tips are voluntary payments made in addition to the booking total. Tips can only be added
<strong>after your booking has started</strong> — they cannot be paid in advance.
</p>
<p class="mb-3">
Unlike booking payments, <strong>tips are not refundable</strong>. Tips are not considered
payment for the service itself, but rather a voluntary expression of satisfaction. If you
cancel your booking, any tips already paid will not be included in the cancellation refund.
</p>
<p class="mb-3">
Tips can be added via your account after the booking has started, or at the time of payment
when paying in person at the salon. When paying by card at the terminal, you will be
prompted to add a tip if you wish.
</p>
<p class="mb-3 text-xs text-gray-500 italic">
If you believe a tip was added in error, please contact us via our official
<a
href={resolve('/contact')}
class="font-medium text-blue-600 underline hover:text-blue-800">Contact Channels</a
> and we will review your case.
</p>
</section>
</div>
</div>