Fix review findings: BuyGiftCard concurrency lock, amount guards, NULL scan, mock dedup, docs

N1 (HIGH) — BuyGiftCard concurrent same-key retry could double-issue gift
cards (2× value for 1 charge). Added pg_advisory_lock on the idempotency key
(mirroring the tip pattern) acquired before the idempotency check, so
concurrent same-key retries serialize and only one executes gift-card
creation.

N2 — Amount-equality guards in both reuse branches (CreateTipPayment and
BuyGiftCard). A same-key retry with a different amount now returns 400
instead of silently mutating the pending record's books/VAT/refund caps.

N3 — test coverage:
- TestBuyGiftCard_RetryPending_ReattemptsCharge: pending record + same-key
  retry re-attempts, reuses the record (count=1), completes, and issues the
  gift card exactly once.
- TestCreateCheckoutHTTP_DeviceOptionsWireShape: httptest.Server asserts
  device_id is under checkout.device_options (not top-level). Extracted
  createCheckoutHTTPWithClient for injectable base URL.
- MockClient.CreatePayment now dedups on idempotency key (paymentByKey map),
  matching real Square behaviour.

N4 — Corrected the savepoint comments in handlers.go and giftcards.go: the
savepoint only exists in the test harness; in production db.Conn.Begin is a
plain tx and the status UPDATE runs on a separate pooled connection. Commit
is a harmless no-op in prod but required in tests.

Bonus bug fixed: CheckIdempotencyByKey scanned NULL booking_id/gift_card_id
(gift-card purchases) into plain string, failing with 'cannot scan NULL'.
Now uses sql.NullString.

Docs: Technical Manual.md:53 and Feature Catalog.md (2.1, 2.5) corrected —
no longer claim Web Payments SDK is live; new-card entry is documented as
pending P11, saved-card flow works via ccof tokens, dev mock rejects raw PANs.
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 3db8b54923
commit 4f5dd5c426
9 changed files with 231 additions and 21 deletions
+3 -3
View File
@@ -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 Web Payments SDK)
**What it does:** Customers pay online using a credit/debit card via Square's Web Payments SDK. Used for deposits, full payments, balance payments, and tips.
### 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.
**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 (raw card numbers never touch the server). Soft-deleted with 7-year UK retention.
**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`.
**Layman summary:** "Save your card for next time — one-click payment."
+1 -1
View File
@@ -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 + online Web Payments SDK. Dev mock (`//go:build dev`) simulates async checkout; prod stub (`//go:build !dev`) connects to live API. |
| 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. |
| SMTP | Not implemented | Email/SMS notifications — backend not wired |
---