package validation import ( "errors" "slices" "strconv" "strings" "time" ) type Card struct { Pan string Month string Year string } type ValidationResult struct { Code int Error error } var ErrValid = ValidationResult{0, errors.New("the card is valid")} var ErrInvalidPAN = ValidationResult{1, errors.New("invalid PAN")} var ErrInvalidYear = ValidationResult{2, errors.New("invalid year")} var ErrInvalidMonth = ValidationResult{3, errors.New("invalid month")} var ErrExpire = ValidationResult{4, errors.New("expired card")} func Luhn(pan string) bool { if pan == "" { return false } lastIndex := len(pan) - 1 asDigits := []int{} for _, char := range strings.Split(pan, "") { digit, err := strconv.Atoi(char) if err != nil { return false } asDigits = append(asDigits, digit) } sum := 0 // do we really need reversed iteration? for i, digit := range slices.Backward(asDigits[:lastIndex]) { if i%2 == len(pan)%2 { if digit > 4 { sum += 2*digit - 9 } else { sum += 2 * digit } } else { sum += digit } } return asDigits[lastIndex] == (10-(sum%10))%10 } func Validate(card Card) ValidationResult { if !Luhn(card.Pan) { return ErrInvalidPAN } year, yearErr := strconv.Atoi(card.Year) if yearErr != nil || len(card.Year) != 2 { return ErrInvalidYear } month, monthErr := strconv.Atoi(card.Month) if monthErr != nil || len(card.Month) != 2 || month > 12 || month < 1 { return ErrInvalidMonth } now := time.Now() // not sure about time zones cardExpire := time.Date(year+2000, time.Month(month), 1, 0, 0, 0, 0, time.UTC) if now.After(cardExpire) { return ErrExpire } return ErrValid }