fix: review-loop A — discount credit on admin payments, campaign over-credit cap, sweep replay window, dedup refund revalidation, duplication/modularisation, GBP pence naming
Round-A fresh review (6 agents) + fix + secondary cross-cutting + verification rounds: - F1: campaign discounts reduce the charged amount (deposit credit + admin PaymentModal discounted total); capDiscountToRemainingObligation prevents over-credit at completion in all four campaign blocks - F2: sweep replay rescue distinguishes legitimate same-key retries (21h window) from expired-key new charges; ccof blind-fails leave pending + CRITICAL instead of clawing back - F3: post-start online overflow carved as a tip record (mirrors terminal split builder) - A1: single-source Square decline-code classification (till delegates to square.IsDefinitivePaymentError) - A2/A5: refund attempt-cap literals consolidated; refund-failure counter capped + reset on terminal resolutions + admin notifications - A3/A9: idempotency helpers adopted across derivations; IsExplicitDevOrMockEnv relocated + all gates unified (incl. health-check) - A7: 2FA user+IP limiter + TRUST_PROXY_HEADERS startup warning; SNAPSHOT_ENC_KEY startup validation; TWO_FACTOR_PEPPER docs corrected - A8: snapshot encryption on all 6 write sites + marker-aware reuse paths; MPV->SPV effective voucher type (single VAT point) - A10/A11/A12/A16: gift-card slot scan advances past failed; amount-aware refund reconciliation; completed-booking refund re-check; PaymentWasRefunded on SquareClient interface - Dedup refund revalidation on tip/terminal/gift-card paths; sweep acknowledged_at IS NULL parity; refund-notification single source (exported payments.InsertRefundFailedNotifications) - Duplication/modularisation round: shared frontend helpers (sanitizeDecimalInput, campaignDiscountCents, twoFactorBlocksSavedCards getter, generateUUID), single-source MaxIdempotencyKeyLength, notification-helper consolidation, snapshot-guard comments - Cross-cutting GBP rename: Cents->Pence across backend + frontend + tests (26 identifiers, 16 files) - Tests: 11 behavior-change tests updated to new invariants; coverage for fixed functions; frontend vitest 55 tests; docs corrected (test counts, 2FA delivery, pre-launch checklist, resolution status) - gitleaks: allowlist backend/internal/square test fixtures (mock idempotency keys) All 25 backend packages pass; frontend 55/55 + build clean; env-docs 41/41.
This commit is contained in:
@@ -188,6 +188,9 @@ func (d *devProdClient) CancelCheckout(ctx context.Context, checkoutID string) e
|
||||
func (d *devProdClient) RefundPayment(ctx context.Context, req RefundPaymentReq) (*RefundResult, error) {
|
||||
return refundPaymentHTTP(ctx, req)
|
||||
}
|
||||
// PaymentWasRefunded has ZERO production callers — kept only to satisfy the
|
||||
// SquareClient interface for the dev mock's refund-reconciliation parity
|
||||
// tests. Production reconciliation uses paymentRefundedExactlyWithClient.
|
||||
func (d *devProdClient) PaymentWasRefunded(ctx context.Context, paymentID string) (bool, error) {
|
||||
return PaymentWasRefunded(ctx, paymentID)
|
||||
}
|
||||
@@ -311,11 +314,13 @@ func (m *MockClient) CreatePayment(ctx context.Context, req CreatePaymentReq) (*
|
||||
}
|
||||
}
|
||||
// Square's idempotency-key limit for POST /v2/payments is 45 characters
|
||||
// (64 only for /v2/terminals/checkouts). Real Square rejects an oversized
|
||||
// key with a 400 VALUE_TOO_LONG; the mock mirrors the rejection with the
|
||||
// same structured error so dev parity catches over-length keys (the real
|
||||
// client always derives ≤45-char keys, so this only fires on a caller bug).
|
||||
if len(req.IdempotencyKey) > 45 {
|
||||
// (64 only for /v2/terminals/checkouts) — MaxIdempotencyKeyLength
|
||||
// (square_http_client.go), the single source the payments package also
|
||||
// aliases. Real Square rejects an oversized key with a 400
|
||||
// VALUE_TOO_LONG; the mock mirrors the rejection with the same structured
|
||||
// error so dev parity catches over-length keys (the real client always
|
||||
// derives ≤45-char keys, so this only fires on a caller bug).
|
||||
if len(req.IdempotencyKey) > MaxIdempotencyKeyLength {
|
||||
return nil, &squareAPIError{
|
||||
Code: "VALUE_TOO_LONG",
|
||||
Detail: "idempotency_key must be 45 characters or fewer",
|
||||
@@ -872,7 +877,10 @@ func (m *MockClient) RefundKeyCount() int {
|
||||
// any refund with status COMPLETED, APPROVED, or PENDING exists for the payment
|
||||
// (FAILED/REJECTED refunds never moved money and are ignored). Shares the exact
|
||||
// status set the real client's paymentWasRefundedWithClient uses so handler
|
||||
// reconciliation behaves identically in dev/mock and production.
|
||||
// reconciliation behaves identically in dev/mock and production. TEST-ONLY on
|
||||
// the SquareClient interface (no production callers — reconciliation uses the
|
||||
// package-level paymentRefundedExactlyWithClient); kept so this mock satisfies
|
||||
// the interface and its refund-status parity tests can exercise the set.
|
||||
func (m *MockClient) PaymentWasRefunded(ctx context.Context, paymentID string) (bool, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
Reference in New Issue
Block a user