Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Credit card validation #924

Merged
merged 6 commits into from May 1, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions baked_in.go
Expand Up @@ -201,6 +201,7 @@ var (
"bic": isIsoBicFormat,
"semver": isSemverFormat,
"dns_rfc1035_label": isDnsRFC1035LabelFormat,
"credit_card": isCreditCard,
}
)

Expand Down Expand Up @@ -2436,3 +2437,40 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool {
val := fl.Field().String()
return dnsRegexRFC1035Label.MatchString(val)
}

func isCreditCard(fl FieldLevel) bool {
val := fl.Field().String()
var creditCard bytes.Buffer
parts := strings.Split(val, " ")
for _, part := range parts {
if len(part) < 2 {
return false
}
creditCard.WriteString(part)
}

ccDigits := strings.Split(creditCard.String(), "")
size := len(ccDigits)
if size < 14 || size > 16 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Globally, card numbers (PAN) can be 12 - 19 digits
(see https://en.wikipedia.org/wiki/Payment_card_number)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marrow16 thank you for pointing out the min and max size of a PAN. I've updated the code according to the documentation that you provided.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marrow16 @alessmar
Hi folks,
Actually PAN length is in range 10-19

IIN length has been extended to 8-digits in fifth edition of ISO/IEC 7812 published in 2017[[2]](https://en.wikipedia.org/wiki/Payment_card_number#cite_note-2) and PAN will continue to remain variable length, ranging from 10 to 19 digits.

return false
}

sum := 0
for i := 0; i < size; i++ {
value, err := strconv.Atoi(ccDigits[i])
if err != nil {
return false
}
if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 {
v := value * 2
if v >= 10 {
sum += 1 + (v % 10)
} else {
sum += v
}
} else {
sum += value
}
}
return (sum % 10) == 0
}
6 changes: 6 additions & 0 deletions doc.go
Expand Up @@ -1283,6 +1283,12 @@ More information on https://semver.org/

Usage: semver

Credit Card

This validates that a string value contains a valid credit card number using Luhn algoritm.

Usage: credit_card

Alias Validators and Tags

NOTE: When returning an error, the tag returned in "FieldError" will be
Expand Down
38 changes: 37 additions & 1 deletion validator_test.go
Expand Up @@ -11460,7 +11460,7 @@ func TestSemverFormatValidation(t *testing.T) {
}
}
}

func TestRFC1035LabelFormatValidation(t *testing.T) {
tests := []struct {
value string `validate:"dns_rfc1035_label"`
Expand Down Expand Up @@ -11611,3 +11611,39 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) {
_ = New().Struct(test{"ABC", 123, false})
t.Errorf("Didn't panic as expected")
}

func TestCreditCardFormatValidation(t *testing.T) {
tests := []struct {
value string `validate:"credit_card"`
tag string
expected bool
}{
{"4624748233249780", "credit_card", true},
{"4624748233349780", "credit_card", false},
{"378282246310005", "credit_card", true},
{"378282146310005", "credit_card", false},
{"4624 7482 3324 9780", "credit_card", true},
{"4624 7482 3324 9780", "credit_card", false},
}

validate := New()

for i, test := range tests {
errs := validate.Var(test.value, test.tag)

if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d credit_card failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d credit_card failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "credit_card" {
t.Fatalf("Index: %d credit_card failed Error: %s", i, errs)
}
}
}
}
}