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(amountPence int64, remainingPence int64) error { if amountPence > remainingPence { return fmt.Errorf("partial amount (£%.2f) exceeds remaining balance (£%.2f)", float64(amountPence)/100, float64(remainingPence)/100) } if amountPence <= 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 the card source shape of a payment request. Three // shapes are legal: // // - saved-card only: a non-empty saved-card reference (card_id or // saved_card_id — they are the same user_saved_cards.id, so callers pass // the effective reference) with no new_card_token — a plain saved-card // (ccof:) charge. // - new-card only: a non-empty new_card_token with NO saved-card reference — // a new-card (cnon:) one-off charge. // - saved-card reference + new_card_token together: the SCA tokenize-result // wire contract — the token (card.tokenize(verificationDetails, cardId) // result) is the one-time charge SOURCE and the saved-card row supplies the // Square customer. resolveChargeSource implements exactly this coexistence // (see charge_helpers.go), so the validation must not reject it. // // Both absent is invalid ("either a card reference or new_card_token is // required"). A bare new_card_token remains valid (the new-card path), and a // card reference with no token remains valid (the legacy saved-card path); // only the coexistence that used to be rejected — card ref + token — is now // legal, resolved as the SCA tokenize-result source. func ValidateCardInfo(cardID, savedCardID, newCardToken *string) error { hasCardRef := (cardID != nil && *cardID != "") || (savedCardID != nil && *savedCardID != "") hasToken := newCardToken != nil && *newCardToken != "" if !hasCardRef && !hasToken { return errors.New("either a saved card reference (card_id/saved_card_id) or new_card_token is required") } return nil } // ValidateVerificationToken checks that a Square 3DS/SCA verification token is // non-empty when present and within a sane length. Square's tokens are short // opaque strings; the bound guards against absurd/malformed payloads before // the token is forwarded to Square's API. func ValidateVerificationToken(token *string) error { if token == nil || *token == "" { return nil } if len(*token) > 512 { return errors.New("verification_token is too long") } return nil }