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 all commits
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
7 changes: 5 additions & 2 deletions .github/workflows/workflow.yml
Expand Up @@ -41,8 +41,11 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v3
with:
go-version: 1.16
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: v1.45.2
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -153,6 +153,7 @@ Baked-in Validations
| bcp47_language_tag | Language tag (BCP 47) |
| btc_addr | Bitcoin Address |
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
| credit_card | Credit Card Number |
| datetime | Datetime |
| e164 | e164 formatted phone number |
| email | E-mail String
Expand Down
39 changes: 39 additions & 0 deletions baked_in.go
Expand Up @@ -203,6 +203,7 @@ var (
"bic": isIsoBicFormat,
"semver": isSemverFormat,
"dns_rfc1035_label": isDnsRFC1035LabelFormat,
"credit_card": isCreditCard,
}
)

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

// isCreditCard is the validation function for validating if the current field's value is a valid credit card number
func isCreditCard(fl FieldLevel) bool {
val := fl.Field().String()
var creditCard bytes.Buffer
segments := strings.Split(val, " ")
for _, segment := range segments {
if len(segment) < 3 {
return false
}
creditCard.WriteString(segment)
}

ccDigits := strings.Split(creditCard.String(), "")
size := len(ccDigits)
if size < 12 || size > 19 {
Copy link

@grandeto grandeto Apr 29, 2022

Choose a reason for hiding this comment

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

Hi,
I wrote this comment below the previous comments' thread.
However just in case putting it also here

The correct PAN validation nowadays should be size < 10 || size > 19

Here is the official statement:

https://www.iso.org/news/2016/11/Ref2146.html

Within the current version of ISO/IEC 7812-1, an IIN is defined as a fixed-length numeric of six digits. The standard also defines the Primary Account Number (PAN), a number which is used to identify an individual account holder. The PAN is of variable length, ranging from 8 to 19 digits.

Due to the increasing number of card issuers, there is expected to be a shortage in the available supply of IINs. Therefore, ISO/IEC 7812-1 is being revised to expand the IIN to an eight-digit numeric value from the current six digits. The PAN will continue to remain a variable length, ranging from 10 to 19 digits.

return false
}

sum := 0
for i, digit := range ccDigits {
value, err := strconv.Atoi(digit)
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 @@ -1317,6 +1317,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: 38 additions & 0 deletions validator_test.go
Expand Up @@ -11750,3 +11750,41 @@ 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
}{
{"586824160825533338", "credit_card", true},
{"586824160825533328", "credit_card", false},
{"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)
}
}
}
}
}