Apply second-round review fixes: idempotency-key length caps, stable-sentinel card keys, test-isolation, naming
Money-safety idempotency hardening (I1, wide): - validate:"max=45" on CreateTerminalPayment/BookingPayment/Refund/Tip/ BuyGiftCard idempotency keys (all feed Square's 45-char /v2/payments, /v2/refunds, /v2/cards caps); BuyGiftCard corrected from a wrongly-loose max=64. Till keeps max=64 (its key also feeds the 64-char terminal-checkout endpoint). - Explicit 45-char guard in RefundPayment: the one handler that decodes RefundRequest without running the struct validator, so the tag alone was inert; a longer key would 400 at Square and be misclassified as a definitive refund decline. - New TestIdempotencyKey_OverLength_RejectedAcrossPaymentHandlers covers all six endpoints (terminal saved-card, booking, tip, gift-card, till, refund). Stable-sentinel card identity in idempotency keys (C1, wide): - BookingFlow deposit key now uses the 'new-card' sentinel instead of embedding the cnon: nonce (matches UserPaymentModal/account). A re-tokenize after a spent nonce no longer regenerates the key, closing a lost-response double-charge window. - TipPayment + UserBookingModal tip keys now include card identity (selectedCardId || 'new-card'); previously keyed on amount only, so a same-amount tip on a DIFFERENT card reused the key and deduped a distinct charge. Resets cleared in every success/close path. Test isolation (R1): TestRefund_PendingResume_NewKeyAfterModalReopen no longer t.Parallel — it swaps the package-level SquareClient mid-test and a concurrent parallel test could observe the swapped instance. Naming/quality (M1/M2/M4): resolveChargeSource local renamed savedRowID (was shadowing the cardID *string parameter); BuyGiftCard fallback prefix "till-" -> "gc-"; saved-card terminal response key "checkout_id" -> "payment_id" (it holds a DB payment row, not a Square checkout) with matching frontend fallback. README maintenance-job count corrected 24 -> 25. Full suite 25/25 + race clean via run-tests.sh lockfile; svelte-check 0 errors/warnings; production build succeeds.
This commit is contained in:
@@ -83,8 +83,8 @@ type BuyGiftCardRequest struct {
|
||||
// IdempotencyKey is required (R2): an empty key would be stored as '' on
|
||||
// the pending payments row, and a second empty-key purchase would 500 on
|
||||
// the UNIQUE(payments.idempotency_key) constraint. The frontend always
|
||||
// sends a per-purchase UUID; the max=64 matches the column width.
|
||||
IdempotencyKey string `json:"idempotency_key" validate:"required,max=64"`
|
||||
// sends a per-purchase UUID; max=45 matches Square's /v2/payments limit.
|
||||
IdempotencyKey string `json:"idempotency_key" validate:"required,max=45"`
|
||||
VerificationToken *string `json:"verification_token,omitempty"`
|
||||
}
|
||||
|
||||
@@ -915,7 +915,7 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// R2: idempotency_key is required (validate:"required,max=64"). An empty
|
||||
// R2: idempotency_key is required (validate:"required,max=45"). An empty
|
||||
// key would be stored as '' on the pending payments row and a second
|
||||
// empty-key purchase would 500 on the UNIQUE(payments.idempotency_key)
|
||||
// constraint. The frontend always sends a per-purchase UUID. The fallback
|
||||
@@ -928,7 +928,7 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if req.IdempotencyKey == "" {
|
||||
req.IdempotencyKey = uniqueChargeKey("till-")
|
||||
req.IdempotencyKey = uniqueChargeKey("gc-")
|
||||
}
|
||||
|
||||
allowedAmounts := map[int64]bool{1000: true, 2000: true, 5000: true}
|
||||
|
||||
Reference in New Issue
Block a user