From 1cdefb1834889590967aaeb72ed92e3113fb0e5a Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Sat, 1 Aug 2026 18:19:06 +0100 Subject: [PATCH] Update P11 plan and docs to reflect gated new-card state The P11 plan was written when the frontend sent raw PANs and the backend had CreateCardOnFileRaw blocked. Four rounds of payment-safety work changed the reality: the backend is fully P11-ready (CreateCardOnFileRaw deleted; all card-creation paths accept cnon:/ccof: tokens via CreateCardOnFile) and new-card entry is gated behind CardEntryUnavailable. - Rewrite plans/p11-square-web-payments-sdk.md: accurate current state (8 gated flows), backend-readiness section with verified line evidence, Steps 4/5 marked done, revised to a frontend-only 2-3 day integration (SquareCardInput replaces the deleted CardInput.svelte) - README: online payments via saved cards; new-card entry gated until nonces land; backend accepts tokens only (PCI-DSS parity) - Gap Backlog P11: backend groundwork DONE, remaining work is frontend-only re-enable of the 8 gated flows - Feature Catalog 2.1/2.5 and Technical Manual: gated-not-dead-end; Add Card posts card_token to CreatePaymentMethodFromToken --- README.md | 2 +- obsidian/Crussell/Feature Catalog.md | 6 +- .../Crussell/Future Work - Gap Backlog.md | 2 +- obsidian/Crussell/Technical Manual.md | 2 +- .../plans/p11-square-web-payments-sdk.md | 107 +++++++++--------- 5 files changed, 58 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index ee877cc..1bd796f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Nail salon booking platform — Go 1.26.5 backend + SvelteKit 5 SPA + PostgreSQL **Booking**: Self-service (customer), walk-in (admin), call-in (admin). Slot reservations prevent double-booking (4 TTL types). **Self-blocking prevention**: `excludeUserID` parameter filters a user's own `RESERVATION` entries from time blocker overlap checks, allowing re-reservation and booking at overlapping slots. **Explicit cancellation**: `DELETE /api/bookings/reserve` releases a user reservation; `DELETE /api/admin/bookings/reserve` releases an admin walk-in/call-in reservation. **Background cleanup**: Centralised cron scheduler (`backend/internal/jobs/`) runs 21 maintenance jobs: reservation/deposit cleanup every 5min, hourly campaign transitions, daily unpaid-booking notifications, staged default hours auto-apply, GDPR anonymization, financial aggregation, and token/code cleanup. Guest accounts with GDPR-compliant anonymization (including `RESERVATION:edit_request:%` scrubbing). Service eligibility based on age + patch test validity. Overlap checks use `FOR UPDATE` row locks inside transactions. Closing-hours validation (`closing_time.go`) resolves both current and staged default hours. -**Payments**: Square Terminal (in-person, via `CreateTerminalCheckout`) + online card payments (currently raw-PAN entry in dev only; production nonce integration is backlog item P11 — see `obsidian/Crussell/Future Work - Gap Backlog.md`). Cash with change calculation. Gift cards (12-digit code or account balance). Saved cards for faster checkout. Tips on completed bookings. Refunds with notice-period tiers and deposit protection (72h/24h thresholds). All payment types: deposit, full, partial, balance, tip. Payment >20% of total promotes `pending_release` bookings back to `confirmed`. Deposit paid is computed from payments on-the-fly. The first 50% of each payment is always carved out as deposit (via `buildSplitRecords`); any overflow beyond the booking total becomes a tip. A PostgreSQL `pg_advisory_lock` serializes payment attempts per-booking to prevent two-tab double-payment races. Gift card purchases insert a pending payment record with VAT before calling Square — the DB transaction commits first, so Square failures leave a retryable pending record (same-key retries reuse it). +**Payments**: Square Terminal (in-person, via `CreateTerminalCheckout`) + online card payments via saved cards; new-card entry is tokenized through Square Web Payments SDK nonces (`cnon:`) and gated until nonces are available (backlog P11 — see `obsidian/Crussell/plans/p11-square-web-payments-sdk.md`). The backend accepts only tokens, never raw PANs (PCI-DSS parity, mirrored in the dev mock). Cash with change calculation. Gift cards (12-digit code or account balance). Saved cards for faster checkout. Tips on completed bookings. Refunds with notice-period tiers and deposit protection (72h/24h thresholds). All payment types: deposit, full, partial, balance, tip. Payment >20% of total promotes `pending_release` bookings back to `confirmed`. Deposit paid is computed from payments on-the-fly. The first 50% of each payment is always carved out as deposit (via `buildSplitRecords`); any overflow beyond the booking total becomes a tip. A PostgreSQL `pg_advisory_lock` serializes payment attempts per-booking to prevent two-tab double-payment races. Gift card purchases insert a pending payment record with VAT before calling Square — the DB transaction commits first, so Square failures leave a retryable pending record (same-key retries reuse it). **Gift Cards**: Multi-method purchase (cash, card machine, online card, giveaway). Inventory cards for stock management. 24-month rolling expiry. Idle account cleanup (2yr/5yr thresholds). Expired balance recovery with admin audit trail. Transaction audit log. Idempotency keys for purchases. diff --git a/obsidian/Crussell/Feature Catalog.md b/obsidian/Crussell/Feature Catalog.md index f853e0d..1c92323 100644 --- a/obsidian/Crussell/Feature Catalog.md +++ b/obsidian/Crussell/Feature Catalog.md @@ -162,8 +162,8 @@ Multi-method payment system accepting Square (card terminal & online), cash, gif **Related:** [[Booking System|1. Booking System]] (deposits), [[Gift Cards|4. Gift Cards]] (pay by gift card), [[Admin Dashboard|5. Admin Dashboard]] (till purchases) -### 2.1 Online Card Payment (Square — saved cards; new-card entry pending P11) -**What it does:** Customers pay online with a card. Saved-card payments work end-to-end via Square tokenized card IDs (`ccof:`). New-card entry (raw PAN entry in the UI) is a documented dead end until Square Web Payments SDK nonces land — see `plans/p11-square-web-payments-sdk.md`. The dev mock rejects raw PANs to mirror production. Used for deposits, full payments, balance payments, and tips. +### 2.1 Online Card Payment (Square — saved cards; new-card entry gated pending P11) +**What it does:** Customers pay online with a card. Saved-card payments work end-to-end via Square tokenized card IDs (`ccof:`). New-card entry is currently gated in the UI (a `CardEntryUnavailable` notice) pending Square Web Payments SDK nonce tokenization (P11) — the backend already accepts `cnon:` nonces everywhere and rejects raw PANs (PCI-DSS parity, mirrored by the dev mock). See `plans/p11-square-web-payments-sdk.md`. Used for deposits, full payments, balance payments, and tips. **Layman summary:** "Pay online with your card — just like any online shop." @@ -191,7 +191,7 @@ Multi-method payment system accepting Square (card terminal & online), cash, gif **Related:** [[Gift Cards|4. Gift Cards]], [[VAT Calculation|2.10 VAT Calculation]] ### 2.5 Saved Cards -**What it does:** Customers can save their card details for faster checkout next time. Cards are tokenized via Square (`ccof:` card IDs; the full PAN exists only in Square's vault — our DB stores only the reference + brand/last4/fingerprint). The dev mock mirrors this (raw PANs rejected). Soft-deleted with 7-year UK retention. Note: the "Add Card" UI currently sends raw PAN and is a dead end until P11 (Web Payments SDK nonces) — see `plans/p11-square-web-payments-sdk.md`. +**What it does:** Customers can save their card details for faster checkout next time. Cards are tokenized via Square (`ccof:` card IDs; the full PAN exists only in Square's vault — our DB stores only the reference + brand/last4/fingerprint). The dev mock mirrors this (raw PANs rejected). Soft-deleted with 7-year UK retention. The "Add Card" flow posts a `card_token` (nonce) to `CreatePaymentMethodFromToken`, which calls `CreateCardOnFile` — the UI is currently gated pending P11 (Web Payments SDK nonces) — see `plans/p11-square-web-payments-sdk.md`. **Layman summary:** "Save your card for next time — one-click payment." diff --git a/obsidian/Crussell/Future Work - Gap Backlog.md b/obsidian/Crussell/Future Work - Gap Backlog.md index 43b106f..5adb0c8 100644 --- a/obsidian/Crussell/Future Work - Gap Backlog.md +++ b/obsidian/Crussell/Future Work - Gap Backlog.md @@ -35,7 +35,7 @@ These are things that work fine in dev (with mocks) but need real implementation | P8 | **Social auth stubs (Google/Microsoft/Facebook)** | L (2-3d) | Backend + Frontend | `handlers/auth/social.go` is 1 line (`package auth`). Frontend login page has 3 social buttons that show `toast.info("${provider} login coming soon")`. The `user_social_logins` table and `account_type` enum values exist from early schema design. | The schema was designed for social auth from the start (table + enum values). The OAuth flow itself was never implemented. Buttons exist as UI placeholders. | | P9 | **Tip payments: replace placeholder card tokens** | S (1d) | Frontend | ✅ COMPLETED July 2026 — `card_token: 'placeholder'` replaced with real saved card selection + CardInput with Luhn/expiry/CVC validation across all 3 tip pages. | | | P10 | **No automated database backups** | M (1d) | Infrastructure | PostgreSQL volume is persistent in Docker but no `pg_dump` cron, no point-in-time recovery. | Standard production DB setup task. | -| P11 | **Square Web Payments SDK: replace CardInput with nonce-based flow** | M (3-5d) | Frontend + Backend | Frontend still sends raw PAN, expiry, and CVC as `new_card_token` for all card entry flows (tips, booking payment, gift cards, account add card, till purchases). In production, Square's API requires a `cnon:xxx` nonce generated by the Web Payments SDK. The mock now mirrors production and rejects raw PANs (`CreateCardOnFileRaw` is blocked; `CreateCardOnFile` accepts only `cnon:`/`ccof:` tokens) — so these new-card flows are currently a dead end in BOTH dev and prod until this is landed or the UI is gated. | **Action plan:** 1) Load Square Web Payments SDK (script tag in `app.html` or via `@square/web-payments-sdk` npm). 2) Replace `CardInput.svelte` (hand-rolled inputs) with Square's native card form (`payments.card()`). 3) Call `card.tokenize()` to get `cnon:xxx` nonce client-side. 4) Send only the nonce as `new_card_token`. 5) Remove `card_expiry`/`card_cvc` from request bodies (already removed from tip flows). 6) Remove `CreateCardOnFileRaw` from production paths. | +| P11 | **Square Web Payments SDK: re-enable new-card entry with nonce-based flow** | S-M (2-3d) | Frontend | Backend groundwork is DONE (Aug 2026): `CreateCardOnFileRaw` deleted; all card-creation paths (`CreatePaymentMethodFromToken`, till `online_square`, tip/booking/gift-card `new_card_token`) accept `cnon:`/`ccof:` tokens via `CreateCardOnFile`. The frontend no longer sends raw PAN anywhere — new-card entry is **gated** behind `CardEntryUnavailable` (saved-card payments work). Remaining work is frontend-only: load the Web Payments SDK, create a `SquareCardInput` (the old hand-rolled `CardInput.svelte` was deleted), tokenize to `cnon:xxx`, and re-enable the 8 gated flows (tips ×3, booking payment, deposit, Buy a Gift Card, account Add Card, till `online_square`). See `plans/p11-square-web-payments-sdk.md` for the full plan. | **Action plan:** 1) Load Square Web Payments SDK (script tag in `app.html` or `@square/web-payments-sdk` npm). 2) Create `SquareCardInput.svelte` using `payments.card()` + `card.tokenize()`. 3) Send only the nonce as `new_card_token` / `card_token` in each flow. 4) Set `newCardDisabled={false}` / remove the `CardEntryUnavailable` gate. 5) Re-enable admin till `online_square` and account Add Card. 6) Update docs. | --- diff --git a/obsidian/Crussell/Technical Manual.md b/obsidian/Crussell/Technical Manual.md index 0ef7d6b..1eb0d4a 100644 --- a/obsidian/Crussell/Technical Manual.md +++ b/obsidian/Crussell/Technical Manual.md @@ -50,7 +50,7 @@ Backend (:8080) |---------|--------|---------| | SabreDAV (CardDAV/CalDAV) | Active | Contact sync (profile photos), calendar events | | S3/R2 | Active (dev) | Portfolio images (AVIF), profile pictures (WebP) | -| Square | **Active** | Payment processing — in-person Terminal (`CreateTerminalCheckout`) + online card payments (saved cards working; new-card entry pending Web Payments SDK nonces — backlog P11, see `plans/p11-square-web-payments-sdk.md`). Dev mock (`//go:build dev`) mirrors production PCI-DSS behaviour (rejects raw PANs; accepts `cnon:`/`ccof:` tokens); prod client (`!dev`) connects to live API. | +| Square | **Active** | Payment processing — in-person Terminal (`CreateTerminalCheckout`) + online card payments (saved cards working; new-card entry gated pending Square Web Payments SDK nonce integration — backlog P11, see `plans/p11-square-web-payments-sdk.md`). Backend accepts only `cnon:`/`ccof:` tokens (raw PANs rejected). Dev mock (`//go:build dev`) mirrors production PCI-DSS behaviour; prod client (`!dev`) connects to live API. | | SMTP | Not implemented | Email/SMS notifications — backend not wired | --- diff --git a/obsidian/Crussell/plans/p11-square-web-payments-sdk.md b/obsidian/Crussell/plans/p11-square-web-payments-sdk.md index 75e78da..8f3432c 100644 --- a/obsidian/Crussell/plans/p11-square-web-payments-sdk.md +++ b/obsidian/Crussell/plans/p11-square-web-payments-sdk.md @@ -1,41 +1,44 @@ # P11 — Square Web Payments SDK Implementation Plan -**Status:** READY TO PICK UP (deferred from July 2026 session) +**Status:** READY TO PICK UP (updated August 2026 — revised after the P0/P1/P3 payment-safety work landed) **Owner:** Agent implementing P11 (Square Web Payments SDK) -**Estimated effort:** 3-5 days +**Estimated effort:** 2-3 days (backend groundwork already landed; this is now a frontend-only integration) **Backlog reference:** `Future Work - Gap Backlog.md` item P11 --- ## Executive Summary -Every "enter a new card" flow in the app is currently a **dead end**. The frontend sends raw PAN, expiry, and CVC as `new_card_token` with no Square Web Payments SDK tokenization. The backend mock AND production both now reject raw PANs (PCI-DSS parity — `CreateCardOnFileRaw` is blocked, `CreateCardOnFile` accepts only `cnon:`/`ccof:` tokens). So a user entering a new card gets a guaranteed 500. **This plan makes new-card payments actually work** by integrating Square's Web Payments SDK client-side to generate `cnon:xxx` nonces. +New-card entry is currently **gated off** in the UI (a deliberate interim state after the PCI-DSS parity work): every "Use a new card" path shows a `CardEntryUnavailable` notice because the frontend has no way to produce a Square nonce. Saved-card payments work end-to-end. The **backend is fully P11-ready** — it already accepts `cnon:`/`ccof:` tokens everywhere (`CreateCardOnFileRaw` is deleted; all card-creation paths call `CreateCardOnFile` with a token). **This plan re-enables new-card payments** by integrating Square's Web Payments SDK client-side to generate `cnon:xxx` nonces, then removing the gating. --- -## Current State (verified July 2026) +## Current State (verified August 2026) -### Frontend — 6 flows send raw PAN as `new_card_token`: -1. `frontend/src/routes/tip/+page.svelte` (~line 283) — `body.new_card_token = newCardNumber.replace(/\s/g, '')` -2. `frontend/src/routes/pay-tip/[id]/+page.svelte` (~line 331) — same -3. `frontend/src/lib/components/account/UserBookingModal.svelte` (~line 353) — same (tip modal) -4. `frontend/src/lib/components/payments/UserPaymentModal.svelte` (~line 348) — booking payment -5. `frontend/src/lib/components/booking/BookingFlow.svelte` (~line 359) — deposit -6. `frontend/src/routes/account/+page.svelte` (~line 332) — Buy a Gift Card +### Frontend — new-card entry is GATED (not sending raw PAN): +No `new_card_token` / `card_number` / `card_cvc` fields remain in any request body. Each flow now has a saved-card list and, when the user has no saved card (or tries to add one), shows `CardEntryUnavailable` (`frontend/src/lib/components/payments/CardEntryUnavailable.svelte`, message in `frontend/src/lib/constants/payments.ts`): -Plus raw PAN + CVC to add-card (`account/+page.svelte:577` → `CreatePaymentMethodFromDetails` → `CreateCardOnFileRaw`) and admin till (`GiftCardsManagement.svelte:655` → `till.go:439` → `CreateCardOnFileRaw`). +1. `frontend/src/routes/tip/+page.svelte` — tip; saved-card list + `card_id`, gated new-card +2. `frontend/src/routes/pay-tip/[id]/+page.svelte` — tip; same pattern +3. `frontend/src/lib/components/account/UserBookingModal.svelte` — tip modal; same pattern +4. `frontend/src/lib/components/payments/UserPaymentModal.svelte` — booking payment; uses `CardSelection` with `newCardDisabled` +5. `frontend/src/lib/components/booking/BookingFlow.svelte` — deposit; saved-card list + `card_id`, gated new-card +6. `frontend/src/routes/account/+page.svelte` — Buy a Gift Card; saved-card list + `card_id`, gated new-card +7. `frontend/src/lib/components/admin/GiftCardsManagement.svelte` — admin till; `online_square`/`saved_card` card options removed from the UI (cash / card_machine / on_the_house only) +8. Account "Add a Card" — gated off (no raw-PAN add-card UI remains) -### The reusable `CardSelection.svelte` component: -`frontend/src/lib/components/payments/CardSelection.svelte` — the standard card-selection UI (saved card list + "Use a new card" + `CardInput` with blur-based Luhn/expiry/CVC validation). **Currently applied to only 1 of 5 card UIs** (UserPaymentModal). The tip flows, account Buy Gift Card, account Add Card, and BookingFlow still have ~100 duplicated lines each. +### `CardSelection.svelte` (the reusable card picker): +`frontend/src/lib/components/payments/CardSelection.svelte` — saved-card list + "Use a new card" toggle + `newCardDisabled` prop that swaps the new-card section for the `CardEntryUnavailable` notice. **Currently used only by UserPaymentModal.** The other flows (tip ×3, account Buy Gift Card, BookingFlow) have their own simpler saved-card lists with `CardEntryUnavailable` — the ~100-line duplicated validation blocks were deleted during the raw-PAN cleanup, so there is far less to consolidate than when this plan was first written. ### `CardInput.svelte`: -`frontend/src/lib/components/payments/CardInput.svelte` — the hand-rolled card entry form (number/expiry/CVC inputs, formatNumber/formatExpiry, onfieldblur/onfieldinput callbacks). **This is what gets replaced by Square's native card form.** +**DELETED.** The hand-rolled card entry form was removed in the P3 cleanup. P11 creates a fresh `SquareCardInput.svelte` from scratch — nothing to migrate. -### Backend (already P11-ready): +### Backend (already P11-ready — verified): - `backend/internal/square/square_http_client.go` — `createCardOnFileHTTP` accepts a `source_id` token and calls `POST /v2/cards`. Works with `cnon:xxx` nonces. -- `backend/handlers/payments/handlers.go` — `CreateTipPayment` accepts `new_card_token` and passes it as the source. Works with nonces. -- `CreatePaymentMethodFromDetails` (service.go:496) calls `CreateCardOnFileRaw` — **needs migration to the nonce path**. -- Till `online_square` (till.go:439) calls `CreateCardOnFileRaw` — **needs migration to the nonce path**. +- `backend/handlers/payments/handlers.go` — `CreateTipPayment`, `CreateBookingPayment`, and `BuyGiftCard` accept `new_card_token` and pass it to `CreateCardOnFile`. Works with nonces. +- `CreatePaymentMethodFromToken` (service.go:504) — account add-card uses `CreateCardOnFile` with the token (raw-PAN path deleted). +- Till `online_square` (till.go:497) — requires `card_token` and calls `CreateCardOnFile`. Works with nonces. +- `CreateCardOnFileRaw` — **deleted** from the `SquareClient` interface (`types.go:158` shows only `CreateCardOnFile`) and all implementations. --- @@ -60,9 +63,9 @@ Two options (pick one): Load based on `SQUARE_ENVIRONMENT` so sandbox/prod use the right URL. -### Step 2 — Create a Square card form component +### Step 2 — Create a Square card form component (`SquareCardInput.svelte`, new) -Replace `CardInput.svelte`'s hand-rolled inputs with Square's native card form: +The deleted `CardInput.svelte` is replaced by a new Square-backed component: ```js const payments = window.Square.payments(appId, locationId); const card = await payments.card(); @@ -72,54 +75,48 @@ const tokenResult = await card.tokenize(); // tokenResult.token → "cnon:xxx" ``` -**Design decision**: Either: -- (a) Embed the Square form inside `CardInput.svelte` (keep the `bind:cardNumber` etc. API surface but use Square's iframe internally — the inputs become read-only display), OR -- (b) Create a new `SquareCardInput.svelte` and swap it into `CardSelection.svelte` when a Square app ID is configured, falling back to the hand-rolled form when `SQUARE_APPLICATION_ID` is absent (keeps dev/testing working without Square). +**Design decision**: create `SquareCardInput.svelte` and swap it into `CardSelection.svelte` (and the gated flows) when a Square app ID is configured. Because the frontend no longer has a raw-PAN fallback form, the gating logic (`newCardDisabled` / `CardEntryUnavailable`) is what the tokenized form replaces — there is no hand-rolled form left to fall back to. Local dev without Square credentials keeps the gated state. -**Recommendation**: (b) — a `SquareCardInput.svelte` with a fallback. This keeps local dev usable when no Square app ID is configured, while production uses real tokenization. +### Step 3 — Re-enable the payment flows with nonces -### Step 3 — Update the 6 payment flows to send nonces +For each gated flow, replace the `CardEntryUnavailable` notice / `newCardDisabled` gate with the tokenized `SquareCardInput` and send the resulting `cnon:xxx` as `new_card_token` (the backend already accepts it in `CreateTipPayment`, `CreateBookingPayment`, `BuyGiftCard`, `CreatePaymentMethodFromToken`, and till `online_square`): -For each flow, `new_card_token` must become the `cnon:xxx` token from `card.tokenize()`, NOT the raw PAN. Since all 6 flows go through `CardSelection.svelte` (or the fallback path), the cleanest approach: +1. Extend `CardSelection.svelte` to the 5 remaining card UIs (tip ×3, account Buy Gift Card, BookingFlow) OR wire `SquareCardInput` directly into each saved-card list — the duplication is now small (saved-card list only), so either approach is cheap. +2. UserPaymentModal: set `newCardDisabled={false}` and use the tokenized form inside `CardSelection`. +3. Re-enable the account "Add a Card" flow (posts `card_token` → `CreatePaymentMethodFromToken`). +4. Re-enable the admin till `online_square` option (posts `card_token` → till.go nonce path) and remove the `CardEntryUnavailable` notice. +5. `card_expiry`/`card_cvc` are already removed from all request bodies — no work needed. -1. First **extend `CardSelection.svelte` to all 5 remaining card UIs** (tip ×3, account Buy Gift Card, account Add Card, BookingFlow) — this centralises the card-selection logic so P11's change is one place, not six. -2. Then swap the card entry inside `CardSelection.svelte` to use `SquareCardInput` (Step 2b). -3. Remove `card_expiry`/`card_cvc` from all request bodies (already removed from tip flows). +### Step 4 — Migrate the two raw-PAN backend paths ✅ **DONE (previous sessions)** -### Step 4 — Migrate the two raw-PAN backend paths +- `CreatePaymentMethodFromDetails` → replaced by `CreatePaymentMethodFromToken` using `CreateCardOnFile` with the nonce. **Verified at service.go:504-526.** +- Till `online_square` → `CreateCardOnFileRaw` → `CreateCardOnFile` with a nonce. **Verified at till.go:497-509.** -- `CreatePaymentMethodFromDetails` (account add-card): change `CreateCardOnFileRaw` call to use the nonce path (`CreateCardOnFile` with the `cnon:xxx` token). -- Till `online_square` (`till.go:439`): same migration — `CreateCardOnFileRaw` → `CreateCardOnFile` with a nonce. The frontend `GiftCardsManagement.svelte` till flow must send the nonce instead of raw PAN. +### Step 5 — Remove `CreateCardOnFileRaw` entirely ✅ **DONE (previous sessions)** -### Step 5 — Remove `CreateCardOnFileRaw` entirely - -Once both callers are migrated: -- Delete `CreateCardOnFileRaw` from the `SquareClient` interface (`backend/internal/square/types.go:140`) -- Delete from all 3 implementations (MockClient, ProdClient, devProdClient) -- Delete the PCI-block error stubs -- Update the `square_dev_test.go` tests that assert the raw-PAN rejection (`TestDevClient_CreateCardOnFileRaw_Rejected_ProdParity` → the method no longer exists) -- Update handler tests that assert the 500 on raw-PAN add-card +- Deleted from `SquareClient` interface (`types.go`), all 3 implementations (MockClient, ProdClient, devProdClient), and the PCI-block error stubs. +- Tests updated (`TestDevClient_CreateCardOnFileRaw_Rejected_ProdParity` removed; handler tests assert the nonce-based success path). ### Step 6 — Update docs -- `README.md` line 9: remove the "P11 — production nonce integration is backlog item P11" caveat once landed +- `README.md` line 9: update the "currently raw-PAN entry in dev only; production nonce integration is backlog item P11" caveat — the current truth is "new-card entry is gated pending P11 nonce integration". - `Future Work - Gap Backlog.md` P11: mark completed -- `Feature Catalog.md` and `Technical Manual.md`: update "Web Payments SDK (online)" claims if they reference the old state +- `Feature Catalog.md` (lines ~166, ~194): update the "raw PAN entry in the UI is a documented dead end" claims — new-card entry is gated, not raw-PAN +- `Technical Manual.md` (line ~53): update the "new-card entry pending Web Payments SDK nonces" claim — now that P11 lands, say "new-card payments tokenized via Web Payments SDK nonces" --- ## Testing Plan 1. **Unit tests**: - - `square_http_client_test.go`: add request-shape tests via `httptest.Server` for `createCardOnFileHTTP` (verify `source_id` is the token, `idempotency_key` deterministic, no raw PAN in body) — the HTTP client currently has only 2 tests (both `paymentFromSquare`). + - `square_http_client_test.go`: `createCardOnFileHTTP` request-shape tests via `httptest.Server` (verify `source_id` is the token, `idempotency_key` deterministic sha256, no raw PAN in body). - Error-path tests for the HTTP client (non-2xx, malformed body). 2. **Integration tests**: - - Handler tests using `cnon:` tokens through the mock (the mock accepts `cnon:` nonces) — restore success-path coverage for `TestCreatePaymentMethod_HappyPath` etc. (currently rewritten to assert the raw-PAN 500). + - Handler tests using `cnon:` tokens through the mock (mock accepts `cnon:` nonces) — `TestCreatePaymentMethod_HappyPath` (now asserts 200 with `cnon:visa`) and `TestDevClient_CreateCardOnFile_...` cover the backend; extend to the payment flows (tip/booking/giftcard with a `cnon:` source). - Add a test: card created with nonce → payment with saved card works. 3. **Manual/sandbox tests** (requires Square sandbox credentials): - - Each of the 6 flows: enter new card → tokenize → pay → verify charge in Square dashboard. - - Saved-card flow still works. - - Refund still works. + - Each of the 8 flows: enter new card → tokenize → pay → verify charge in Square dashboard. + - Saved-card flow still works. Refund still works. --- @@ -127,18 +124,18 @@ Once both callers are migrated: - **Square iframe requires HTTPS** — localhost is exempt, but any non-local dev URL needs TLS. - **Tokenization is one-shot** — a `cnon:` nonce is single-use. The idempotency key logic (cached per payment attempt) handles retries, but a retry must NOT re-tokenize if the first tokenize succeeded and the payment failed — the backend's idempotency dedup handles this, but the frontend should reuse the cached token on retry if the payment record is pending. -- **The hand-rolled `CardInput.svelte` Luhn/expiry validation becomes cosmetic** — Square's iframe does the real validation. Keep the display validation for UX, but don't block submission on it alone. -- **PCI-DSS scope**: with nonces, PAN never touches our server. The `CreateCardOnFileRaw` migration is essential — do NOT leave it in place. +- **Do not regress the PCI-DSS parity work** — the backend rejects raw PANs by design; the tokenized form must never fall back to sending PAN/CVC to our server. +- **`CardEntryUnavailable` stays as the offline/dev fallback** — when no Square app ID is configured, flows keep the gated notice rather than breaking. --- ## Definition of Done - [ ] Square Web Payments SDK loads (sandbox + prod URLs, env-gated) -- [ ] `SquareCardInput` (or embedded form) tokenizes cards → `cnon:xxx` -- [ ] All 6 payment flows send nonces, not raw PANs -- [ ] `CardSelection.svelte` used by all 5 card UIs -- [ ] `CreateCardOnFileRaw` deleted from interface + all implementations +- [ ] `SquareCardInput` tokenizes cards → `cnon:xxx` +- [ ] All 8 flows re-enabled to send nonces, not PANs (tip ×3, booking payment, deposit, Buy a Gift Card, account Add Card, admin till `online_square`) +- [ ] `CardEntryUnavailable` notice removed from active flows (kept only as the no-credentials fallback) +- [ ] Backend nonce paths already in place — verified unchanged (Step 4/5 done) - [ ] All handler tests pass with nonce-based flows - [ ] Sandbox smoke test: new-card payment succeeds end-to-end -- [ ] Docs updated +- [ ] Docs updated (README, Gap Backlog, Feature Catalog, Technical Manual)