-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvalidate.go
More file actions
149 lines (129 loc) · 4.42 KB
/
validate.go
File metadata and controls
149 lines (129 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package vat
import (
"fmt"
"regexp"
"strings"
)
// Validate validates a VAT number by both format and existence. If no error then it is valid.
// Note: for backwards compatibility this is a variadic function that effectively makes it optional to pass in options.
// If no opts are passed in, VIES numbers will still be validated as always, but GB numbers will not.
// If multiple opts arguments passed in, only the first one is used.
func Validate(vatNumber string, opts ...ValidatorOpts) error {
err := ValidateFormat(vatNumber)
if err != nil {
return err
}
return ValidateExists(vatNumber, opts...)
}
// ValidateWithResponse validates a VAT number by both format and existence, returning the full
// service response on success. If no error then the VAT number is valid.
func ValidateWithResponse(vatNumber string, opts ...ValidatorOpts) (*LookupResponse, error) {
err := ValidateFormat(vatNumber)
if err != nil {
return nil, err
}
return ValidateExistsWithResponse(vatNumber, opts...)
}
// ValidateFormat validates a VAT number by its format. If no error is returned then it is valid.
func ValidateFormat(vatNumber string) error {
patterns := map[string]string{
"AT": `U[A-Z0-9]{8}`,
"BE": `(0[0-9]{9}|[0-9]{10})`,
"BG": `[0-9]{9,10}`,
"CH": `(?:E(?:-| )[0-9]{3}(?:\.| )[0-9]{3}(?:\.| )[0-9]{3}( MWST)?|E[0-9]{9}(?:MWST)?)`,
"CY": `[0-9]{8}[A-Z]`,
"CZ": `[0-9]{8,10}`,
"DE": `[0-9]{9}`,
"DK": `[0-9]{8}`,
"EE": `[0-9]{9}`,
"EL": `[0-9]{9}`,
"ES": `[A-Z][0-9]{7}[A-Z]|[0-9]{8}[A-Z]|[A-Z][0-9]{8}`,
"FI": `[0-9]{8}`,
"FR": `[A-Z0-9]{2}[0-9]{9}`,
// Supposedly the regex for GB numbers is `[0-9]{9}|[0-9]{12}|(GD|HA)[0-9]{3}`,
// but our validator service only accepts numbers with 9 or 12 digits following the country code.
// Seems like the official site only accepts 9 digits... https://www.gov.uk/check-uk-vat-number
"GB": `([0-9]{9}|[0-9]{12})`,
"HR": `[0-9]{11}`,
"HU": `[0-9]{8}`,
"IE": `[A-Z0-9]{7}[A-Z]|[A-Z0-9]{7}[A-W][A-I]`,
"IT": `[0-9]{11}`,
"LT": `([0-9]{9}|[0-9]{12})`,
"LU": `[0-9]{8}`,
"LV": `[0-9]{11}`,
"MT": `[0-9]{8}`,
"NL": `[0-9]{9}B[0-9]{2}`,
"PL": `[0-9]{10}`,
"PT": `[0-9]{9}`,
"RO": `[0-9]{2,10}`,
"SE": `[0-9]{12}`,
"SI": `[0-9]{8}`,
"SK": `[0-9]{10}`,
"XI": `([0-9]{9}|[0-9]{12})`, // Northern Ireland, same format as GB
}
if len(vatNumber) < 3 {
return ErrInvalidVATNumberFormat
}
vatNumber = strings.ToUpper(vatNumber)
pattern, ok := patterns[vatNumber[0:2]]
if !ok {
return ErrInvalidCountryCode
}
matched, err := regexp.MatchString(fmt.Sprintf("^%s$", pattern), vatNumber[2:])
if err != nil {
return err
}
if !matched {
return ErrInvalidVATNumberFormat
}
return nil
}
// ValidateExists validates that the given VAT number exists in the external lookup service.
func ValidateExists(vatNumber string, optsSlice ...ValidatorOpts) error {
if len(vatNumber) < 3 {
return ErrInvalidVATNumberFormat
}
vatNumber = strings.ToUpper(vatNumber)
lookupService := ViesLookupService
if strings.HasPrefix(vatNumber, "GB") {
lookupService = UKVATLookupService
}
opts := ValidatorOpts{}
if len(optsSlice) > 0 {
opts = optsSlice[0]
}
return lookupService.Validate(vatNumber, opts)
}
// ValidateExistsWithResponse validates that the given VAT number exists in the external lookup
// service, returning the full service response on success. If no error then the VAT number is valid.
func ValidateExistsWithResponse(vatNumber string, optsSlice ...ValidatorOpts) (*LookupResponse, error) {
if len(vatNumber) < 3 {
return nil, ErrInvalidVATNumberFormat
}
vatNumber = strings.ToUpper(vatNumber)
lookupService := ViesLookupService
if strings.HasPrefix(vatNumber, "GB") {
lookupService = UKVATLookupService
}
opts := ValidatorOpts{}
if len(optsSlice) > 0 {
opts = optsSlice[0]
}
if svc, ok := lookupService.(lookupServiceWithResponse); ok {
return svc.validateWithResponse(vatNumber, opts)
}
return nil, lookupService.Validate(vatNumber, opts)
}
// ValidatorOpts are options for the VAT number validator.
type ValidatorOpts struct {
UKClientID string
UKClientSecret string
UKAccessToken *UKAccessToken
IsUKTest bool
}
// LookupResponse contains the response from the VAT lookup service.
// Exactly one of VIESResponse or UKVATResponse will be populated, depending on the service used.
type LookupResponse struct {
VIESResponse *VIESResponse `json:"viesResponse,omitempty"`
UKVATResponse *UKVATResponse `json:"ukVATResponse,omitempty"`
}