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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
package lulu
import (
"encoding/json"
"fmt"
)
//go:generate go run github.com/yawnak/string-enumer -t ShippingLevel -t Title --text -o ./ship_gen.go .
// ShippingLevel is the quality/speed with which a package is shipped.
type ShippingLevel string
const (
Mail ShippingLevel = "MAIL" // Slowest ship method. Depending on the destination, tracking might not be available.
PriorityMail ShippingLevel = "PRIORITY_MAIL" // Priority mail shipping
Ground ShippingLevel = "GROUND" // Courier based shipping using ground transportation in the US.
Expedited ShippingLevel = "EXPEDITED" // Expedited (2nd day) delivery via air mail or equivalent.
Express ShippingLevel = "EXPRESS" // Overnight delivery. Fastest shipping available.
)
type Title string
const (
Mr Title = "MR"
Miss Title = "MISS"
Mrs Title = "MRS"
Ms Title = "MS"
Dr Title = "DR"
)
type ShippingAddress struct {
// ISO 3166-2 country code
Country string `json:"country_code"`
// 2 or 3 letter state code (officially called ISO-3166-2
// subdivision codes). They are required for some countries (e.g.
// US, MX, CA, AU).
State string `json:"state"`
City string `json:"city"`
Street1 string `json:"street1"` // First address line
Street2 string `json:"street2"` // Second address line
PostCode string `json:"postcode"` // Required for most countries
// Only relevant for US addresses. Some US carriers don't deliver
// to business-addresses on Saturday.
IsBusiness bool `json:"is_business"`
// Full name of the person, including first and last name.
Name string `json:"name"`
Title Title `json:"title"`
// Name of an organization. Required if no person name is given.
Organization string `json:"organization"`
// Shipping carriers require an email address for notifications
// or handling delivery issues. If no email is given, the default
// email in the user profile will be used.
Email EmailAddress `json:"email"`
// Shipping carriers require a phone number for handling delivery
// issues. If no phone number is given, the default in the API
// user profile will be used.
Phone PhoneNumber `json:"phone_number"`
// The recipient's tax identification number. Required for
// shipping addresses to Brazil, Chile, and Mexico.
TaxId string `json:"recipient_tax_id"`
}
func (a *ShippingAddress) UnmarshalJSON(data []byte) error {
var alias extShippingAddress
if err := json.Unmarshal(data, &alias); err != nil {
return err
}
addr, err := alias.addr()
if err != nil {
return err
}
*a = addr
return nil
}
type ShippingAddressValidation struct {
Address ShippingAddress // The original address that was given to the server.
Suggested ShippingAddress // The address that the server recommends.
Warnings []ShippingAddressWarning
}
func (av *ShippingAddressValidation) UnmarshalJSON(data []byte) error {
var alias extShippingAddress
if err := json.Unmarshal(data, &alias); err != nil {
return err
}
addr, err := alias.addr()
if err != nil {
return err
}
av.Address = addr
av.Suggested = alias.Suggested
av.Warnings = alias.Warnings
return nil
}
type ShippingAddressWarning struct {
Type string `json:"type"` // eg "validation_warning"
Path string `json:"path"` // eg "external"
Code string `json:"code"` // eg "REPLACED"
Msg string `json:"message"` // eg "street1: Holstenstr. 40 -> Holstenstraße 40"
}
// extShippingAddress is the extended shipping address that is seen in
// some responses. It includes "warnings" and "suggested_address". It
// also unmarshals either "country" or "country_code" etc. to account for
// the irregularities in the API.
type extShippingAddress struct {
Country string `json:"country"`
CountryCode string `json:"country_code"`
State string `json:"state"`
StateCode string `json:"state_code"`
City string `json:"city"`
Street1 string `json:"street1"`
Street2 string `json:"street2"`
PostCode string `json:"postcode"`
IsBusiness bool `json:"is_business"`
Name string `json:"name"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Title Title `json:"title"`
Organization string `json:"organization"`
Email EmailAddress `json:"email"`
Phone PhoneNumber `json:"phone_number"`
TaxId string `json:"recipient_tax_id"`
Warnings []ShippingAddressWarning `json:"warnings"`
Suggested ShippingAddress `json:"suggested_address"`
}
// addr takes the disjunctive union of the API's irregular fields
// ("country"/"country_code", etc.) and returns only those fields of the
// actual shipping address: excluding the suggested address and warnings.
// It returns error if two or more of the "same field" are present, eg.
// both "country" and "country_code".
func (ext extShippingAddress) addr() (ShippingAddress, error) {
country, both := either(ext.Country, ext.CountryCode)
if both {
return ShippingAddress{}, fmt.Errorf(`address contains both "country" and "country_code"`)
}
state, both := either(ext.State, ext.StateCode)
if both {
return ShippingAddress{}, fmt.Errorf(`address contains both "state" and "state_code"`)
}
var name string
hasName := ext.Name != ""
hasFirst := ext.FirstName != ""
hasLast := ext.LastName != ""
if hasName && (hasFirst || hasLast) {
return ShippingAddress{}, fmt.Errorf(`address contains both "name" and {"first_name", "last_name"}`)
} else if hasName {
name = ext.Name
} else if hasFirst && hasLast {
name = fmt.Sprintf("%s %s", ext.FirstName, ext.LastName)
} else if hasLast {
name = ext.LastName
} else if hasFirst {
name = ext.FirstName
}
return ShippingAddress{
Country: country,
State: state,
City: ext.City,
Street1: ext.Street1,
Street2: ext.Street2,
PostCode: ext.PostCode,
IsBusiness: ext.IsBusiness,
Name: name,
Title: ext.Title,
Organization: ext.Organization,
Email: ext.Email,
Phone: ext.Phone,
TaxId: ext.TaxId,
}, nil
}
func either(a, b string) (string, bool) {
if a != "" && b != "" {
return a, true
} else if a != "" {
return a, false
}
return b, false
}
|