From 15a5955d75b8ae8c32b21d496ebf04c97a28d704 Mon Sep 17 00:00:00 2001 From: Alessandro Marino Date: Fri, 25 Mar 2022 17:00:21 +0100 Subject: [PATCH 1/5] credit card validation implemented Luhn algoritm to validate credit card string --- baked_in.go | 38 ++++++++++++++++++++++++++++++++++++++ doc.go | 6 ++++++ validator_test.go | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index 7868b66f..c8c9c67c 100644 --- a/baked_in.go +++ b/baked_in.go @@ -201,6 +201,7 @@ var ( "bic": isIsoBicFormat, "semver": isSemverFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat, + "credit_card": isCreditCard, } ) @@ -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 { + 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 +} diff --git a/doc.go b/doc.go index b284c379..c78f1b98 100644 --- a/doc.go +++ b/doc.go @@ -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 diff --git a/validator_test.go b/validator_test.go index 3730fb9d..0067a945 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11460,7 +11460,7 @@ func TestSemverFormatValidation(t *testing.T) { } } } - + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"` @@ -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) + } + } + } + } +} From 175ec9e76d8a14de4505bc4b2357a27d9e161ff0 Mon Sep 17 00:00:00 2001 From: Alessandro Marino Date: Tue, 26 Apr 2022 16:25:24 +0200 Subject: [PATCH 2/5] update to handle credit card's length from 12 to 19 added doc to README --- baked_in.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/baked_in.go b/baked_in.go index c8c9c67c..92f8295d 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2438,26 +2438,27 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool { 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 - parts := strings.Split(val, " ") - for _, part := range parts { - if len(part) < 2 { + segments := strings.Split(val, " ") + for _, segment := range segments { + if len(segment) < 3 { return false } - creditCard.WriteString(part) + creditCard.WriteString(segment) } ccDigits := strings.Split(creditCard.String(), "") size := len(ccDigits) - if size < 14 || size > 16 { + if size < 12 || size > 19 { return false } sum := 0 - for i := 0; i < size; i++ { - value, err := strconv.Atoi(ccDigits[i]) + for i, digit := range ccDigits { + value, err := strconv.Atoi(digit) if err != nil { return false } From a664b6e012ceef3889bc4916e2b945c17f8ac554 Mon Sep 17 00:00:00 2001 From: Alessandro Marino Date: Tue, 26 Apr 2022 16:54:58 +0200 Subject: [PATCH 3/5] added test case for 19 digits credit card --- README.md | 1 + validator_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 6712e95a..f58e4940 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/validator_test.go b/validator_test.go index 0067a945..65d8de09 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11618,6 +11618,7 @@ func TestCreditCardFormatValidation(t *testing.T) { tag string expected bool }{ + {"586824160825533338", "credit_card", true}, {"4624748233249780", "credit_card", true}, {"4624748233349780", "credit_card", false}, {"378282246310005", "credit_card", true}, From c4ec5ff50a293fae415d2d79a69b9fd89cd1527c Mon Sep 17 00:00:00 2001 From: Alessandro Marino Date: Thu, 28 Apr 2022 16:48:08 +0200 Subject: [PATCH 4/5] added not valid test case for 19 digits card --- validator_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/validator_test.go b/validator_test.go index 65d8de09..af2f3301 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11619,6 +11619,7 @@ func TestCreditCardFormatValidation(t *testing.T) { expected bool }{ {"586824160825533338", "credit_card", true}, + {"586824160825533328", "credit_card", false}, {"4624748233249780", "credit_card", true}, {"4624748233349780", "credit_card", false}, {"378282246310005", "credit_card", true}, From f5920db229c0f22fa1164a7c3331d25d6624d806 Mon Sep 17 00:00:00 2001 From: Alessandro Marino Date: Thu, 28 Apr 2022 16:57:07 +0200 Subject: [PATCH 5/5] update golangci-lint action --- .github/workflows/workflow.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d1ca1e8f..218f46e7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -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.41.1 + version: v1.45.2