Files
Crussell/backend/handlers/payments/validators.go
T
popertots 4abcb324c9 Square payment integration: real HTTP client, tip flow rewrite, card UI/validation overhaul
Backend:
- Create square_http_client.go: real Square REST API client (Payments, Terminal Checkouts, Refunds, Cards, Locations) with proper JSON types, auth, error handling
- Update ProdClient in square.go to delegate to shared HTTP functions
- Wire devProdClient in square_dev.go to also make real HTTP calls for sandbox/prod env
- Rewrite CreateTipPayment handler: accept card_id OR new_card_token (+save_card), advisory lock, idempotency check, max amount validation
- Add ValidateCardInfo, bump ValidateAmount max to £10,000
- Fix mock CreateCardOnFile to detect brand/last4 from raw card numbers
- Fix mock RefundPayment to index by SquarePayID and accept unknown payment IDs
- Remove dead types (ProcessingFee, sqAddress), add Deadline parity
- Fix AMEX brand inconsistency (AMEX -> AMERICAN_EXPRESS)
- Pre-existing fix: remove unused context import in giftcards.go

Frontend:
- CardInput.svelte: add onfieldblur/onfieldinput callbacks for blur-based validation
- CardBrandIcon.svelte: brand SVGs for VISA, MC, AMEX, Discover, Diners, JCB, Square Gift Card, UnionPay, Interac, EFTPOS
- tip/+page, pay-tip/[id], UserBookingModal tip: saved card list + CardInput + Luhn/expiry/CVC validation + blur-based errors + no-saved-cards edge case
- UserPaymentModal, BookingFlow: card validation parity (blur-based, all-valid check)
- account page: replace text brand badges with CardBrandIcon
- Fix handleCustomTip bug (state mutations outside if block)
- Remove dead pageState variable
- Add tip modal scroll (max-h-[90vh] overflow-y-auto)
- Submit button disabled on !isCardValid

Tests:
- 30 square package tests (+new: CreateCardOnFile raw number path, detectCardInfo variants)
- 5 tip handler tests (HappyPath, NoPriorPayment, WrongOwner, MultipleTips, TxFailure)
- All +-race clean, refund tests fixed
2026-08-22 00:34:49 +01:00

63 lines
1.6 KiB
Go

package payments
import (
"errors"
"fmt"
)
// Valid payment types
var validPaymentTypes = map[string]bool{
"deposit": true,
"full": true,
"tip": true,
"balance": true,
"partial": true,
}
// ValidateAmount checks that amount is greater than 0 and has valid precision (max 2 decimal places when in pence)
func ValidateAmount(amount int64) error {
if amount <= 0 {
return errors.New("amount must be greater than 0")
}
if amount > 1_000_000 { // £10,000 in pence
return errors.New("amount exceeds maximum (£10,000)")
}
return nil
}
// ValidatePartialAmount checks that the partial amount doesn't exceed the remaining balance
func ValidatePartialAmount(amountCents int64, remainingCents int64) error {
if amountCents > remainingCents {
return fmt.Errorf("partial amount (£%.2f) exceeds remaining balance (£%.2f)",
float64(amountCents)/100, float64(remainingCents)/100)
}
if amountCents <= 0 {
return errors.New("amount must be greater than 0")
}
return nil
}
// ValidatePaymentType checks that payment type is valid
func ValidatePaymentType(pt string) error {
if !validPaymentTypes[pt] {
return errors.New("invalid payment type")
}
return nil
}
// ValidateRefundReason checks that refund reason is not empty
func ValidateRefundReason(reason string) error {
if reason == "" {
return errors.New("refund reason is required")
}
return nil
}
// ValidateCardInfo checks that at least one of cardID or newCardToken is provided
func ValidateCardInfo(cardID, newCardToken *string) error {
if cardID == nil && (newCardToken == nil || *newCardToken == "") {
return errors.New("either card_id or new_card_token is required")
}
return nil
}