29-credit-card-number-checker (#81)
# Describe Request Credit card checker added. Fixes #29 # Change Type New checker.
This commit is contained in:
parent
4384351c4c
commit
26c9fd6ea9
5 changed files with 453 additions and 0 deletions
|
|
@ -73,6 +73,7 @@ This package currently provides the following checkers:
|
|||
- [alphanumeric](doc/checkers/alphanumeric.md) checks if the given string consists of only alphanumeric characters.
|
||||
- [ascii](doc/checkers/ascii.md) checks if the given string consists of only ASCII characters.
|
||||
- [cidr](doc/checkers/cidr.md) checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
- [credit-card](doc/checkers/credit_card.md) checks if the given value is a valid credit card number.
|
||||
- [digits](doc/checkers/digits.md) checks if the given string consists of only digit characters.
|
||||
- [email](doc/checkers/email.md) checks if the given string is an email address.
|
||||
- [fqdn](doc/checkers/fqdn.md) checks if the given string is a fully qualified domain name.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const ResultValid Result = "VALID"
|
|||
var makers = map[string]MakeFunc{
|
||||
CheckerAlphanumeric: makeAlphanumeric,
|
||||
CheckerASCII: makeASCII,
|
||||
CheckerCreditCard: makeCreditCard,
|
||||
CheckerCidr: makeCidr,
|
||||
CheckerDigits: makeDigits,
|
||||
CheckerEmail: makeEmail,
|
||||
|
|
|
|||
146
credit_card.go
Normal file
146
credit_card.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckerCreditCard is the name of the checker.
|
||||
const CheckerCreditCard = "credit-card"
|
||||
|
||||
// ResultNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
const ResultNotCreditCard = "NOT_CREDIT_CARD"
|
||||
|
||||
// amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits.
|
||||
var amexExpression = "(?:^(?:3[47])[0-9]{13}$)"
|
||||
var amexPattern = regexp.MustCompile(amexExpression)
|
||||
|
||||
// dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits.
|
||||
var dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)"
|
||||
var dinersPattern = regexp.MustCompile(dinersExpression)
|
||||
|
||||
// discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits.
|
||||
var discoverExpression = "(?:^6011[0-9]{12}$)"
|
||||
var discoverPattern = regexp.MustCompile(discoverExpression)
|
||||
|
||||
// jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits.
|
||||
var jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)"
|
||||
var jcbPattern = regexp.MustCompile(jcbExpression)
|
||||
|
||||
// masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits.
|
||||
var masterCardExpression = "(?:^5[12345][0-9]{14}$)"
|
||||
var masterCardPattern = regexp.MustCompile(masterCardExpression)
|
||||
|
||||
// unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits.
|
||||
var unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)"
|
||||
var unionPayPattern = regexp.MustCompile(unionPayExpression)
|
||||
|
||||
// visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits.
|
||||
var visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)"
|
||||
var visaPattern = regexp.MustCompile(visaExpression)
|
||||
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
var anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{
|
||||
amexExpression,
|
||||
dinersExpression,
|
||||
discoverExpression,
|
||||
jcbExpression,
|
||||
masterCardExpression,
|
||||
unionPayExpression,
|
||||
visaExpression,
|
||||
}, "|"))
|
||||
|
||||
// creditCardPatterns is the mapping for credit card names to patterns.
|
||||
var creditCardPatterns = map[string]*regexp.Regexp{
|
||||
"amex": amexPattern,
|
||||
"diners": dinersPattern,
|
||||
"discover": discoverPattern,
|
||||
"jcb": jcbPattern,
|
||||
"mastercard": masterCardPattern,
|
||||
"unionpay": unionPayPattern,
|
||||
"visa": visaPattern,
|
||||
}
|
||||
|
||||
// IsAnyCreditCard checks if the given value is a valid credit card number.
|
||||
func IsAnyCreditCard(number string) Result {
|
||||
return isCreditCard(number, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
// IsAmexCreditCard checks if the given valie is a valid AMEX credit card.
|
||||
func IsAmexCreditCard(number string) Result {
|
||||
return isCreditCard(number, amexPattern)
|
||||
}
|
||||
|
||||
// IsDinersCreditCard checks if the given valie is a valid Diners credit card.
|
||||
func IsDinersCreditCard(number string) Result {
|
||||
return isCreditCard(number, dinersPattern)
|
||||
}
|
||||
|
||||
// IsDiscoveryCreditCard checks if the given valie is a valid Discovery credit card.
|
||||
func IsDiscoveryCreditCard(number string) Result {
|
||||
return isCreditCard(number, discoverPattern)
|
||||
}
|
||||
|
||||
// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card.
|
||||
func IsJcbCreditCard(number string) Result {
|
||||
return isCreditCard(number, jcbPattern)
|
||||
}
|
||||
|
||||
// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card.
|
||||
func IsMasterCardCreditCard(number string) Result {
|
||||
return isCreditCard(number, masterCardPattern)
|
||||
}
|
||||
|
||||
// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card.
|
||||
func IsUnionPayCreditCard(number string) Result {
|
||||
return isCreditCard(number, unionPayPattern)
|
||||
}
|
||||
|
||||
// IsVisaCreditCard checks if the given valie is a valid Visa credit card.
|
||||
func IsVisaCreditCard(number string) Result {
|
||||
return isCreditCard(number, visaPattern)
|
||||
}
|
||||
|
||||
// makeCreditCard makes a checker function for the credit card checker.
|
||||
func makeCreditCard(config string) CheckFunc {
|
||||
patterns := []*regexp.Regexp{}
|
||||
|
||||
if config != "" {
|
||||
for _, card := range strings.Split(config, ",") {
|
||||
pattern, ok := creditCardPatterns[card]
|
||||
if !ok {
|
||||
panic("unknown credit card name")
|
||||
}
|
||||
|
||||
patterns = append(patterns, pattern)
|
||||
}
|
||||
} else {
|
||||
patterns = append(patterns, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
return func(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
number := value.String()
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if isCreditCard(number, pattern) == ResultValid {
|
||||
return ResultValid
|
||||
}
|
||||
}
|
||||
|
||||
return ResultNotCreditCard
|
||||
}
|
||||
}
|
||||
|
||||
// isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit.
|
||||
func isCreditCard(number string, pattern *regexp.Regexp) Result {
|
||||
if !pattern.MatchString(number) {
|
||||
return ResultNotCreditCard
|
||||
}
|
||||
|
||||
return IsLuhn(number)
|
||||
}
|
||||
243
credit_card_test.go
Normal file
243
credit_card_test.go
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test numbers from https://stripe.com/docs/testing
|
||||
var invalidCard = "1234123412341234"
|
||||
var amexCard = "378282246310005"
|
||||
var dinersCard = "36227206271667"
|
||||
var discoverCard = "6011111111111117"
|
||||
var jcbCard = "3530111333300000"
|
||||
var masterCard = "5555555555554444"
|
||||
var unionPayCard = "6200000000000005"
|
||||
var visaCard = "4111111111111111"
|
||||
|
||||
// changeToInvalidLuhn increments the luhn digit to make the number invalid. It assumes that the given number is valid.
|
||||
func changeToInvalidLuhn(number string) string {
|
||||
luhn, err := strconv.Atoi(number[len(number)-1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
luhn = (luhn + 1) % 10
|
||||
|
||||
return number[:len(number)-1] + strconv.Itoa(luhn)
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardValid(t *testing.T) {
|
||||
if IsAnyCreditCard(amexCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsAnyCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardValid(t *testing.T) {
|
||||
if IsAmexCreditCard(amexCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsAmexCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardValid(t *testing.T) {
|
||||
if IsDinersCreditCard(dinersCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsDinersCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardValid(t *testing.T) {
|
||||
if IsDiscoveryCreditCard(discoverCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsDiscoveryCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsDiscoveryCreditCard(changeToInvalidLuhn(discoverCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardValid(t *testing.T) {
|
||||
if IsJcbCreditCard(jcbCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsJcbCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardValid(t *testing.T) {
|
||||
if IsMasterCardCreditCard(masterCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsMasterCardCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardValid(t *testing.T) {
|
||||
if IsUnionPayCreditCard(unionPayCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsUnionPayCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardValid(t *testing.T) {
|
||||
if IsVisaCreditCard(visaCard) != ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidPattern(t *testing.T) {
|
||||
if IsVisaCreditCard(invalidCard) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidLuhn(t *testing.T) {
|
||||
if IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
CreditCard int `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{}
|
||||
|
||||
Check(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardValid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
_, valid := Check(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,unknown"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
Check(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,visa"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: discoverCard,
|
||||
}
|
||||
|
||||
_, valid := Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
62
doc/checkers/credit_card.md
Normal file
62
doc/checkers/credit_card.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Credit Card Checker
|
||||
|
||||
The ```credit-card``` checker checks if the given value is a valid credit card number. If the given value is not a valid credit card number, the checker will return the ```NOT_CREDIT_CARD``` result.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := Check(order)
|
||||
if valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The checker currently knows about AMEX, Diners, Discover, JCB, MasterCard, UnionPay, and VISA credit card numbers.
|
||||
|
||||
If you would like to check for a subset of those credit cards, you can specify them through the checker config parameter. Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,visa"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "6011111111111117",
|
||||
}
|
||||
|
||||
_, valid := Check(order)
|
||||
if valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
If you would like to verify a credit card that is not listed here, please use the [luhn](luhn.md) checker to use the Luhn Algorithm to verify the check digit.
|
||||
|
||||
In your custom checkers, you can call the ```credit-card``` checker functions below to validate the user input.
|
||||
|
||||
- IsAnyCreditCard: checks if the given value is a valid credit card number.
|
||||
- IsAmexCreditCard: checks if the given value is a valid AMEX credit card number.
|
||||
- IsDinersCreditCard: checks if the given value is a valid Diners credit card number.
|
||||
- IsDiscoverCreditCard: checks if the given value is a valid Discover credit card number.
|
||||
- IsJcbCreditCard: checks if the given value is a valid JCB credit card number.
|
||||
- IsMasterCardCreditCard: checks if the given value is a valid MasterCard credit card number.
|
||||
- IsUnionPayCreditCard: checks if the given value is a valid UnionPay credit card number.
|
||||
- IsVisaCreditCard: checks if the given value is a valid VISA credit card number.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
result := IsAnyCreditCard("6011111111111117")
|
||||
|
||||
if result != ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue