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

Exposes a interface so Card methods can be mocked #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions company.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package creditcard

// Company holds a short and long names of who has issued the credit card
type Company struct {
Long string `json:"long,omitempty"`
Short string `json:"short,omitempty"`
}

type CompanyName string

const (
CompanyAmericanExpress CompanyName = "American Express"
CompanyAura CompanyName = "Aura"
CompanyBankCard CompanyName = "Bankcard"
CompanyCabal CompanyName = "Cabal"
CompanyChinaUnionPay CompanyName = "China UnionPay"
CompanyDankort CompanyName = "Dankort"
CompanyDinersClubCarteBlance CompanyName = "Diners Club Carte Blanche"
CompanyDinersClubEnRoute CompanyName = "Diners Club enRoute"
CompanyDinersClubInternational CompanyName = "Diners Club International"
CompanyDiscover CompanyName = "Discover"
CompanyElo CompanyName = "Elo"
CompanyHipercard CompanyName = "Hipercard"
CompanyInstaPayment CompanyName = "InstaPayment"
CompanyInterPayment CompanyName = "InterPayment"
CompanyJCB CompanyName = "JCB"
CompanyMaestro CompanyName = "Maestro"
CompanyMasterCard CompanyName = "MasterCard"
CompanyNaranja CompanyName = "Naranja"
CompanyVisa CompanyName = "Visa"
CompanyVisaElectron CompanyName = "Visa Electron"
)

const (
CompanyNameLongAmericanExpress = "American Express"
CompanyNameLongAura = "Aura"
CompanyNameLongBankcard = "Bankcard"
CompanyNameLongCabal = "Cabal"
CompanyNameLongChinaUnionPay = "China UnionPay"
CompanyNameLongDankort = "Dankort"
CompanyNameLongDinersClubCarteBlanche = "Diners Club Carte Blanche"
CompanyNameLongDinersClubEnRoute = "Diners Club enRoute"
CompanyNameLongDinersClubInternational = "Diners Club International"
CompanyNameLongDiscover = "Discover"
CompanyNameLongElo = "Elo"
CompanyNameLongHipercard = "Hipercard"
CompanyNameLongInstaPayment = "InstaPayment"
CompanyNameLongInterPayment = "InterPayment"
CompanyNameLongJCB = "JCB"
CompanyNameLongMaestro = "Maestro"
CompanyNameLongMasterCard = "MasterCard"
CompanyNameLongNaranja = "Naranja"
CompanyNameLongVisa = "Visa"
CompanyNameLongVisaElectron = "Visa Electron"

CompanyNameShortAmericanExpress = "amex"
CompanyNameShortAura = "aura"
CompanyNameShortBankcard = "bankcard"
CompanyNameShortCabal = "cabal"
CompanyNameShortChinaUnionPay = "china unionpay"
CompanyNameShortDankort = "dankort"
CompanyNameShortDinersClubCarteBlanche = "diners club carte blanche"
CompanyNameShortDinersClubEnRoute = "diners club enroute"
CompanyNameShortDinersClubInternational = "diners club international"
CompanyNameShortDiscover = "discover"
CompanyNameShortElo = "elo"
CompanyNameShortHipercard = "hipercard"
CompanyNameShortInstaPayment = "instapayment"
CompanyNameShortInterPayment = "interpayment"
CompanyNameShortJCB = "jcb"
CompanyNameShortMaestro = "maestro"
CompanyNameShortMasterCard = "mastercard"
CompanyNameShortNaranja = "naranja"
CompanyNameShortVisa = "visa"
CompanyNameShortVisaElectron = "visa electron"
)

var companies = map[CompanyName]Company{
CompanyAmericanExpress: {
Long: CompanyNameLongAmericanExpress,
Short: CompanyNameShortAmericanExpress,
},
CompanyAura: {
Long: CompanyNameLongAura,
Short: CompanyNameShortAura,
},
CompanyBankCard: {
Long: CompanyNameLongBankcard,
Short: CompanyNameShortBankcard,
},
CompanyCabal: {
Long: CompanyNameLongCabal,
Short: CompanyNameShortCabal,
},
CompanyChinaUnionPay: {
Long: CompanyNameLongChinaUnionPay,
Short: CompanyNameShortChinaUnionPay,
},
CompanyDankort: {
Long: CompanyNameLongDankort,
Short: CompanyNameShortDankort,
},
CompanyDinersClubCarteBlance: {
Long: CompanyNameLongDinersClubCarteBlanche,
Short: CompanyNameShortDinersClubCarteBlanche,
},
CompanyDinersClubEnRoute: {
Long: CompanyNameLongDinersClubEnRoute,
Short: CompanyNameShortDinersClubEnRoute,
},
CompanyDinersClubInternational: {
Long: CompanyNameLongDinersClubInternational,
Short: CompanyNameShortDinersClubInternational,
},
CompanyDiscover: {
Long: CompanyNameLongDiscover,
Short: CompanyNameShortDiscover,
},
CompanyElo: {
Long: CompanyNameLongElo,
Short: CompanyNameShortElo,
},
CompanyHipercard: {
Long: CompanyNameLongHipercard,
Short: CompanyNameShortHipercard,
},
CompanyInstaPayment: {
Long: CompanyNameLongInstaPayment,
Short: CompanyNameShortInstaPayment,
},
CompanyInterPayment: {
Long: CompanyNameLongInterPayment,
Short: CompanyNameShortInterPayment,
},
CompanyJCB: {
Long: CompanyNameLongJCB,
Short: CompanyNameShortJCB,
},
CompanyMaestro: {
Long: CompanyNameLongMaestro,
Short: CompanyNameShortMaestro,
},
CompanyMasterCard: {
Long: CompanyNameLongMasterCard,
Short: CompanyNameShortMasterCard,
},
CompanyNaranja: {
Long: CompanyNameLongNaranja,
Short: CompanyNameShortNaranja,
},
CompanyVisa: {
Long: CompanyNameLongVisa,
Short: CompanyNameShortVisa,
},
CompanyVisaElectron: {
Long: CompanyNameLongVisaElectron,
Short: CompanyNameShortVisaElectron,
},
}

func getCompany(name CompanyName) Company {
return companies[name]
}
105 changes: 43 additions & 62 deletions creditcard.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
package creditcard

import (
"errors"
"strconv"
)

// Card holds generic information about the credit card
type Card struct {
Number, Cvv, Month, Year string
Company Company
Company Company `json:"company"`
Cvv string `json:"cvv,omitempty"`
Month string `json:"month,omitempty"`
Number string `json:"number,omitempty"`
Year string `json:"year,omitempty"`
}

// Company holds a short and long names of who has issued the credit card
type Company struct {
Short, Long string
}

type digits [6]int

// At returns the digits from the start to the given length
func (d *digits) At(i int) int {
return d[i-1]
func NewCard(cvv string, month string, number string, year string) *Card {
return &Card{Cvv: cvv, Month: month, Number: number, Year: year}
}

// LastFour returns the last four digits of the credit card's number
func (c *Card) LastFour() (string, error) {
if len(c.Number) < 4 {
return "", errors.New("Credit card number is not long enough")
return "", errCardNumberNotLongEnough
}

return c.Number[len(c.Number)-4 : len(c.Number)], nil
Expand Down Expand Up @@ -81,19 +75,19 @@ func (c *Card) Validate(allowTestNumbers ...bool) error {
return nil
}

return errors.New("Test numbers are not allowed")
return errTestNumbersNotAllowed
}

valid := c.ValidateNumber()

if !valid {
return errors.New("Invalid credit card number")
return errInvalidCardNumber
}

return nil
}

// validates the credit card's expiration date
// ValidateExpiration validates the credit card's expiration date.
func (c *Card) ValidateExpiration() error {
var year, month int
var err error
Expand All @@ -102,39 +96,39 @@ func (c *Card) ValidateExpiration() error {
if len(c.Year) < 3 {
year, err = strconv.Atoi(strconv.Itoa(timeNow.UTC().Year())[:2] + c.Year)
if err != nil {
return errors.New("Invalid year")
return errInvalidYear
}
} else {
year, err = strconv.Atoi(c.Year)
if err != nil {
return errors.New("Invalid year")
return errInvalidYear
}
}

month, err = strconv.Atoi(c.Month)
if err != nil {
return errors.New("Invalid month")
return errInvalidMonth
}

if month < 1 || 12 < month {
return errors.New("Invalid month")
return errInvalidMonth
}

if year < timeNowCaller().UTC().Year() {
return errors.New("Credit card has expired")
if year < timeNow.UTC().Year() {
return errCardHasExpired
}

if year == timeNowCaller().UTC().Year() && month < int(timeNowCaller().UTC().Month()) {
return errors.New("Credit card has expired")
if year == timeNow.UTC().Year() && month < int(timeNow.UTC().Month()) {
return errCardHasExpired
}

return nil
}

// validates the length of the card's CVV value
// ValidateCVV validates the length of the card's CVV value.
func (c *Card) ValidateCVV() error {
if len(c.Cvv) < 3 || len(c.Cvv) > 4 {
return errors.New("Invalid CVV")
return errInvalidCVV
}

return nil
Expand Down Expand Up @@ -163,55 +157,55 @@ func (c *Card) MethodValidate() (Company, error) {
if i < ccLen {
ccDigits[i], err = strconv.Atoi(c.Number[:i+1])
if err != nil {
return Company{"", ""}, errors.New("Unknown credit card method")
return getCompany(""), errUnkownCardMethod
}
}
}

switch {
case isAmex(ccDigits):
return Company{"amex", "American Express"}, nil
return getCompany(CompanyAmericanExpress), nil
case isBankCard(ccDigits):
return Company{"bankcard", "Bankcard"}, nil
return getCompany(CompanyBankCard), nil
case isCabal(ccDigits):
return Company{"cabal", "Cabal"}, nil
return getCompany(CompanyCabal), nil
case isUnionPay(ccDigits):
return Company{"china unionpay", "China UnionPay"}, nil
return getCompany(CompanyChinaUnionPay), nil
case isDinersClubCarteBlance(ccDigits, ccLen):
return Company{"diners club carte blanche", "Diners Club Carte Blanche"}, nil
return getCompany(CompanyDinersClubCarteBlance), nil
case isDinersClubEnroute(ccDigits):
return Company{"diners club enroute", "Diners Club enRoute"}, nil
return getCompany(CompanyDinersClubEnRoute), nil
case isDinersClubInternational(ccDigits, ccLen):
return Company{"diners club international", "Diners Club International"}, nil
return getCompany(CompanyDinersClubInternational), nil
case isDiscover(ccDigits):
return Company{"discover", "Discover"}, nil
return getCompany(CompanyDiscover), nil
// Elo must be checked before interpayment
case isElo(ccDigits):
return Company{"elo", "Elo"}, nil
return getCompany(CompanyElo), nil
case isHipercard(ccDigits):
return Company{"hipercard", "Hipercard"}, nil
return getCompany(CompanyHipercard), nil
case isInterpayment(ccDigits, ccLen):
return Company{"interpayment", "InterPayment"}, nil
return getCompany(CompanyInterPayment), nil
case isInstapayment(ccDigits, ccLen):
return Company{"instapayment", "InstaPayment"}, nil
return getCompany(CompanyInstaPayment), nil
case isJCB(ccDigits):
return Company{"jcb", "JCB"}, nil
return getCompany(CompanyJCB), nil
case isNaranja(ccDigits):
return Company{"naranja", "Naranja"}, nil
return getCompany(CompanyNaranja), nil
case isMaestro(c, ccDigits):
return Company{"maestro", "Maestro"}, nil
return getCompany(CompanyMaestro), nil
case isDankort(ccDigits):
return Company{"dankort", "Dankort"}, nil
return getCompany(CompanyDankort), nil
case isMasterCard(ccDigits):
return Company{"mastercard", "MasterCard"}, nil
return getCompany(CompanyMasterCard), nil
case isVisaElectron(ccDigits):
return Company{"visa electron", "Visa Electron"}, nil
return getCompany(CompanyVisaElectron), nil
case isVisa(ccDigits):
return Company{"visa", "Visa"}, nil
return getCompany(CompanyVisa), nil
case isAura(ccDigits):
return Company{"aura", "Aura"}, nil
return getCompany(CompanyAura), nil
default:
return Company{"", ""}, errors.New("Unknown credit card method")
return getCompany(""), errUnkownCardMethod
}
}

Expand Down Expand Up @@ -245,16 +239,3 @@ func (c *Card) ValidateNumber() bool {

return sum%10 == 0
}

func matchesValue(number int, numbers []int) bool {
for _, v := range numbers {
if v == number {
return true
}
}
return false
}

func isInBetween(n, min, max int) bool {
return n >= min && n <= max
}
13 changes: 12 additions & 1 deletion creditcard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ import (
. "github.com/smartystreets/goconvey/convey"
)

func TestNewCard(t *testing.T) {
Convey("Should return a new Card pointer", t, func() {
res := NewCard("123", "01", "1234567890123456", "2022")
So(res, ShouldNotBeNil)
So(res.Cvv, ShouldEqual, "123")
So(res.Month, ShouldEqual, "01")
So(res.Number, ShouldEqual, "1234567890123456")
So(res.Year, ShouldEqual, "2022")
})
}

func TestFourDigits(t *testing.T) {
Convey("Should be able to retrieve the last four digits of my card", t, func() {
Convey("When I have four or more digits", func() {
Expand Down Expand Up @@ -478,6 +489,6 @@ func TestCard_ValidateExpiration(t *testing.T) {

err := card.ValidateExpiration()
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "Credit card has expired")
So(err.Error(), ShouldEqual, "credit card has expired")
})
}
Loading