-
Notifications
You must be signed in to change notification settings - Fork 16
/
rates.go
130 lines (106 loc) · 3.17 KB
/
rates.go
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
package vat
import (
"encoding/json"
"errors"
"net/http"
"strings"
"sync"
"time"
)
// RatePeriod represents a time and the various activate rates at that time.
type RatePeriod struct {
EffectiveFrom time.Time
Rates map[string]float32
}
// CountryRates holds the various differing VAT rate periods for a given country
type CountryRates struct {
CountryCode string `json:"country_code"`
Periods []RatePeriod
}
var mutex = &sync.Mutex{} // protect countriesRates
var countriesRates []CountryRates
// ErrInvalidCountryCode will be returned when calling GetCountryRates with an invalid country code
var ErrInvalidCountryCode = errors.New("vat: unknown country code")
// ErrInvalidRateLevel will be returned when getting wrong rate level
var ErrInvalidRateLevel = errors.New("vat: unknown rate level")
// GetRateOn returns the effective VAT rate on a given date
func (cr *CountryRates) GetRateOn(t time.Time, level string) (float32, error) {
var activePeriod RatePeriod
// find active period for the given time
for _, p := range cr.Periods {
if t.After(p.EffectiveFrom) && (activePeriod.EffectiveFrom.IsZero() || p.EffectiveFrom.After(activePeriod.EffectiveFrom)) {
activePeriod = p
}
}
activeRate, ok := activePeriod.Rates[level]
if !ok {
return 0.00, ErrInvalidRateLevel
}
return activeRate, nil
}
// GetRate returns the currently active rate
func (cr *CountryRates) GetRate(level string) (float32, error) {
now := time.Now()
return cr.GetRateOn(now, level)
}
// GetCountryRates gets the CountryRates struct for a country by its ISO-3166-1-alpha2 country code.
func GetCountryRates(countryCode string) (CountryRates, error) {
var rate CountryRates
rates, err := GetRates()
if err != nil {
return rate, err
}
for _, r := range rates {
if r.CountryCode == countryCode {
return r, nil
}
}
return rate, ErrInvalidCountryCode
}
// GetRates returns the in-memory VAT rates
func GetRates() ([]CountryRates, error) {
var err error
mutex.Lock()
if countriesRates == nil {
countriesRates, err = FetchRates()
}
mutex.Unlock()
return countriesRates, err
}
// FetchRates fetches the latest VAT rates from ibericode/vat-rates and updates the in-memory rates
func FetchRates() ([]CountryRates, error) {
client := http.Client{
Timeout: (time.Duration(ServiceTimeout) * time.Second),
}
r, err := client.Get("https://raw.githubusercontent.com/ibericode/vat-rates/master/vat-rates.json")
if err != nil {
return nil, err
}
apiResponse := &struct {
Details string
Version float32
Items map[string][]struct {
EffectiveFrom string `json:"effective_from"`
Rates map[string]float32
}
}{}
err = json.NewDecoder(r.Body).Decode(&apiResponse)
if err != nil {
return nil, err
}
rates := []CountryRates{}
for code, periods := range apiResponse.Items {
rate := CountryRates{CountryCode: code}
for _, period := range periods {
from := strings.Replace(period.EffectiveFrom, "0000-", "2000-", 1)
fromTime, _ := time.Parse("2006-01-02", from)
rperiod := RatePeriod{
EffectiveFrom: fromTime,
Rates: period.Rates,
}
rate.Periods = append(rate.Periods, rperiod)
}
rates = append(rates, rate)
}
return rates, err
}