-
Notifications
You must be signed in to change notification settings - Fork 0
/
myparcel.go
285 lines (245 loc) · 13.7 KB
/
myparcel.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
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package myparcel
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
const (
// Carriers
CarrierPostnl = 1 // (PostNL)
CarrierBpost = 2 // (bpost. Only available on Sendmyparcel.be)
CarrierCheapCargo = 3 // (CheapCargo/pallets)
CarrierDpd = 4 // (DPD. Only available on Sendmyparcel.be)
CarrierInstabox = 5 // (Instabox. Only available on MyParcel.nl)
CarrierUps = 8 // (UPS. Only available on MyParcel.nl)
// Package types
ParcelPackage = 1 // This is the standard package type used for NL, EU and Global shipments. It supports a variety of additional options such as insurance, xl format etc. We will look at these options in more detail later. This package is most commonly used when creating shipments.
ParcelMailbox = 2 // This package type is only available on MyParcel.nl and Flespakket for NL shipment that fit into a mailbox. It does not support additional options. Note: If you still make the request with additional options, bear in mind that you need to pay more than is necessary!
ParcelLetter = 3 // This package type is available on MyParcel.nl for NL, EU and Global shipments. The label for this shipment is unpaid meaning that you will need to pay the postal office/courier to sent this letter/package. Therefore, it does not support additional options.
ParcelStamp = 4 // This package type is only available on MyParcel.nl for NL shipments and does not support any additional options. Its price is calculated using the package weight. Note: This shipment will appear on your invoice on shipment_status 2 (pending - registered) as opposed to all other package types, which won't appear on your invoice until shipment status 3 (enroute - handed to carrier).
// Delivery types
DeliveryMorning = 1 // Morning delivery
DeliveryStandard = 2 // Standard delivery
DeliveryEvening = 3 // Evening delivery
DeliveryPickup = 4 // Pickup point delivery
)
// JSONTime is a custom time type that can be marshalled to JSON.
type JSONTime time.Time
// MarshalJSON returns the JSON encoding of time specified in the format
func (t JSONTime) MarshalJSON() ([]byte, error) {
stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format("2006-01-02 15:04:05"))
if t.IsZero() {
return []byte("null"), nil
}
return []byte(stamp), nil
}
// IsZero returns true if the time is zero.
func (t JSONTime) IsZero() bool {
return time.Time(t).IsZero()
}
// Client is the MyParcel client.
type Client struct {
apiBaseURL string
ApiKey string
httpClient *http.Client
UserAgent string
}
// Shipment struct
type ShipmentCreateStruct struct {
Recipient RecipientStruct `json:"recipient"` // Required: Yes. The recipient address.
ReferenceIdentifier string `json:"reference_identifier"` // Required: No. Arbitrary reference indentifier to identify this shipment.
Options OptionsStruct `json:"options"` // Required: Yes. The shipment options.
Carrier int `json:"carrier"` // Required: Yes. The carrier that will deliver the package.
Barcode string `json:"barcode,omitempty"` // Required: n/a. Shipment barcode.
SecondaryShipments interface{} `json:"secondary_shipments,omitempty"` // Required: no. You can specify secondary shipments for the shipment with this object. This property is used to create a multi collo shipment: multiple packages to be delivered to the same address at the same time. Secondary shipment can be passed as empty json objects as all required data will be copied from the main shipment. When data is passed with the secondary shipment this data will be used in favor of the main shipment data.
MultiColloMainShipmentID interface{} `json:"multi_collo_main_shipment_id,omitempty"` // Required: n/a. In case of a multi collo shipment this field contains the id of the main shipment.
Created string `json:"created,omitempty"` // Required: n/a. Date of creaton.
Modified string `json:"modified,omitempty"` // Required: n/a. Date of modification.
}
type ShipmentRequestStruct struct {
Recipient RecipientStruct `json:"recipient"` // Required: Yes. The recipient address.
Sender SenderStruct `json:"sender,omitempty"` // Required: n/a. The sender of the package. This field is never set.
ReferenceIdentifier string `json:"reference_identifier"` // Required: No. Arbitrary reference indentifier to identify this shipment.
Options OptionsStruct `json:"options"` // Required: Yes. The shipment options.
Carrier int `json:"carrier"` // Required: Yes. The carrier that will deliver the package.
Barcode string `json:"barcode,omitempty"` // Required: n/a. Shipment barcode.
SecondaryShipments interface{} `json:"secondary_shipments,omitempty"` // Required: no. You can specify secondary shipments for the shipment with this object. This property is used to create a multi collo shipment: multiple packages to be delivered to the same address at the same time. Secondary shipment can be passed as empty json objects as all required data will be copied from the main shipment. When data is passed with the secondary shipment this data will be used in favor of the main shipment data.
MultiColloMainShipmentID interface{} `json:"multi_collo_main_shipment_id,omitempty"` // Required: n/a. In case of a multi collo shipment this field contains the id of the main shipment.
Created string `json:"created,omitempty"` // Required: n/a. Date of creaton.
Modified string `json:"modified,omitempty"` // Required: n/a. Date of modification.
}
// Insurance struct
type RecipientStruct struct {
Cc string `json:"cc"` // Required: yes. The address country code.
Region string `json:"region,omitempty"` // Required: no. The region, department, state or province of the address.
City string `json:"city"` // Required: yes. The address city.
Street string `json:"street"` // Required: yes. The address street name. When shipping to an international destination, you may include street number in this field.
Number string `json:"number"` // Required: yes for domestic shipments in NL and BE. Street number.
PostalCode string `json:"postal_code"` // Required: yes for NL and EU destinations except for IE. The address postal code.
Person string `json:"person"` // Required: yes. The person at this address. Up to 40 characters long.
Phone string `json:"phone,omitempty"` // Required: no. The address phone.
Email string `json:"email,omitempty"` // Required: no The address email.
}
// Options struct
type OptionsStruct struct {
PackageType int `json:"package_type"` // Required: yes. The package type. For international shipment only package type 1 (package) is allowed.
OnlyRecipient int `json:"only_recipient,omitempty"` // Required: No. Deliver the package to the recipient only.
DeliveryType int `json:"delivery_type,omitempty"` // Required: Yes if delivery_date has been specified. The delivery type for the package.
DeliveryDate JSONTime `json:"delivery_date,omitempty"` // Required: Yes if delivery type has been specified. The delivery date time for this shipment.
Signature int `json:"signature,omitempty"` // Required: No. Package must be signed for.
Return int `json:"return,omitempty"` // Required: No. Return the package if the recipient is not home.
Insurance InsuranceStruct `json:"insurance"` // Required: No. Insurance price for the package.
LargeFormat int `json:"large_format"` // Required: No. Large format package.
LabelDescription string `json:"label_description"` // Required: No. This description will appear on the shipment label. Note: This will be overridden for return shipment by the following: Retour – 3SMYPAMYPAXXXXXX
AgeCheck int `json:"age_check"` // Required: No. The recipient must sign for the package and must be at least 18 years old.
}
// Insurance struct
type InsuranceStruct struct {
Amount int `json:"amount"` // Required: yes. The amount is without decimal separators (in cents).
Currency string `json:"currency"` // Required: yes. The insurance currency code. Must be one of the following: EUR.
}
// Sender struct
type SenderStruct struct {
Cc string `json:"cc,omitempty"` // Required: yes. The address country code.
Region string `json:"region,omitempty"` // Required: no. The region, department, state or province of the address.
City string `json:"city,omitempty"` // Required: yes. The address city.
Street string `json:"street,omitempty"` // Required: yes. The address street name. When shipping to an international destination, you may include street number in this field.
Number string `json:"number,omitempty"` // Required: yes for domestic shipments in NL and BE. Street number.
PostalCode string `json:"postal_code,omitempty"` // Required: yes for NL and EU destinations except for IE. The address postal code.
Person string `json:"person,omitempty"` // Required: yes. The person at this address. Up to 40 characters long.
Phone string `json:"phone,omitempty"` // Required: no. The address phone.
Email string `json:"email,omitempty"` // Required: no The address email.
}
// ShipmentRequest to io.Reader
func (s ShipmentRequest) toReader() (io.Reader, error) {
// convert to json
b, err := json.Marshal(s)
if err != nil {
return nil, err
}
// return the reader
return bytes.NewReader(b), nil
}
type ShipmentCreatedResponseStruct struct {
Data struct {
Ids []struct {
ID int `json:"id"`
ReferenceIdentifier string `json:"reference_identifier"`
} `json:"ids"`
} `json:"data"`
}
type ShipmentResponseStruct struct {
Data struct {
Shipments []ShipmentRequestStruct `json:"shipments"`
Results int `json:"results"`
} `json:"data"`
}
// ShipmentRequest struct with data and with multiple shipments
type ShipmentRequest struct {
Data struct {
Shipments []ShipmentCreateStruct `json:"shipments"`
} `json:"data"`
}
// NewClient returns a new MyParcel client.
// The API key is required to use the MyParcel API.
func NewClient(apiKey string) *Client {
// base64 encode the api key
apiKey = base64.StdEncoding.EncodeToString([]byte(apiKey))
// return the client
return &Client{
apiBaseURL: "https://api.myparcel.nl",
httpClient: &http.Client{},
UserAgent: "MyParcelGoClient/0.0.1",
ApiKey: apiKey,
}
}
// CreateShipment creates a new shipment.
func (c *Client) CreateShipment(shipment ShipmentCreateStruct) (int, error) {
// create the request
request := ShipmentRequest{
Data: struct {
Shipments []ShipmentCreateStruct `json:"shipments"`
}{
Shipments: []ShipmentCreateStruct{shipment},
},
}
// convert the request to io.Reader
reader, err := request.toReader()
if err != nil {
return 0, err
}
// create the http request
req, err := http.NewRequest("POST", c.apiBaseURL+"/shipments", reader)
if err != nil {
return 0, err
}
// set the headers
req.Header.Set("Content-Type", "application/vnd.shipment+json;version=1.1;charset=utf-8")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+c.ApiKey)
req.Header.Set("User-Agent", c.UserAgent)
// send the request
resp, err := c.httpClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
// check the response status code
if resp.StatusCode != 200 {
// echo the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, err
}
return 0, fmt.Errorf("MyParcel API returned status code %d: %s", resp.StatusCode, body)
}
// decode the response
var response ShipmentCreatedResponseStruct
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return 0, err
}
// return the shipment id
return response.Data.Ids[0].ID, nil
}
// GetShipment returns a shipment by ID.
func (c *Client) GetShipment(id int) (ShipmentResponseStruct, error) {
// create the http request
req, err := http.NewRequest("GET", c.apiBaseURL+"/shipments/"+strconv.Itoa(id), nil)
if err != nil {
return ShipmentResponseStruct{}, err
}
// set the headers
req.Header.Set("Content-Type", "application/vnd.shipment+json;version=1.1;charset=utf-8")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+c.ApiKey)
req.Header.Set("User-Agent", c.UserAgent)
// send the request
resp, err := c.httpClient.Do(req)
if err != nil {
return ShipmentResponseStruct{}, err
}
defer resp.Body.Close()
// check the response status code
if resp.StatusCode != 200 {
// echo the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return ShipmentResponseStruct{}, err
}
return ShipmentResponseStruct{}, fmt.Errorf("MyParcel API returned status code %d: %s", resp.StatusCode, body)
}
// decode the response
var shipmentResponseStruct ShipmentResponseStruct
err = json.NewDecoder(resp.Body).Decode(&shipmentResponseStruct)
if err != nil {
return ShipmentResponseStruct{}, err
}
// return the response
return shipmentResponseStruct, nil
}