dpd_ie_api 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +3 -0
- data/README.md +160 -11
- data/Rakefile +5 -1
- data/lib/dpd_ie_api/auth.rb +44 -0
- data/lib/dpd_ie_api/client.rb +34 -0
- data/lib/dpd_ie_api/objects/auth.rb +8 -0
- data/lib/dpd_ie_api/objects/base.rb +63 -0
- data/lib/dpd_ie_api/objects/preadvice.rb +8 -0
- data/lib/dpd_ie_api/objects/track.rb +8 -0
- data/lib/dpd_ie_api/resources/preadvice.rb +41 -0
- data/lib/dpd_ie_api/resources/track.rb +61 -0
- data/lib/dpd_ie_api/version.rb +1 -1
- data/lib/dpd_ie_api/xml_builder.rb +56 -0
- data/lib/dpd_ie_api/xml_parser.rb +37 -0
- data/lib/dpd_ie_api.rb +20 -3
- metadata +44 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb11635fe7a354bb8985d5edb82b4e61ae916ed43010849df6add2ef62d6c439
|
|
4
|
+
data.tar.gz: f29f052b67496fd4558640cc53bbaf381ff8e6e8baaaa94cb1bb57079d6d3e96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11361928734bebad3eeb835f74cc7c2c3801010617f417fdb9c7187966ef12d8f25f0072c633ae2edea68580516ae160bc35b128604fba54ede9867819c735b0
|
|
7
|
+
data.tar.gz: 6c2fb2b988f85e0e0cda8fa5f5aa54209c5ad675b863361ccf647538c68571f1e17f6ead4493e8bf870122800dc1d58985293e10fcd4ec98ce6b9ed4e75725ff
|
data/.rspec
ADDED
data/README.md
CHANGED
|
@@ -1,34 +1,183 @@
|
|
|
1
1
|
# DpdIeApi
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dpd_ie_api`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
3
|
+
A Ruby gem wrapper for the DPD Ireland API.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
|
-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
-
|
|
11
7
|
Install the gem and add to the application's Gemfile by executing:
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
```bash
|
|
10
|
+
bundle add dpd_ie_api
|
|
11
|
+
```
|
|
14
12
|
|
|
15
13
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
```bash
|
|
16
|
+
gem install dpd_ie_api
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Design
|
|
20
|
+
|
|
21
|
+
The gem provides a **uniform Ruby interface** across all endpoints. You always work with Ruby hashes and objects — the gem picks the best format (JSON or XML) for each endpoint internally:
|
|
22
|
+
|
|
23
|
+
| Endpoint | Request Format | Response Format |
|
|
24
|
+
|----------|---------------|-----------------|
|
|
25
|
+
| Authorize | JSON | JSON |
|
|
26
|
+
| PreAdvice | XML | XML |
|
|
27
|
+
| Track | XML | JSON |
|
|
28
|
+
|
|
29
|
+
JSON is used wherever the DPD API supports it (simpler, no XML parsing overhead). XML is used only where the API requires it. This is entirely invisible to gem consumers.
|
|
18
30
|
|
|
19
31
|
## Usage
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
### Authentication
|
|
34
|
+
|
|
35
|
+
Obtain an access token using your static token and credentials.
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
auth = DpdIeApi::Auth.fetch_token("YOUR_STATIC_TOKEN", "YOUR_USER", "YOUR_PASSWORD")
|
|
39
|
+
access_token = auth.access_token
|
|
40
|
+
refresh_token = auth.refresh_token
|
|
41
|
+
expires_in = auth.expires_in
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> [!IMPORTANT]
|
|
45
|
+
> The access token expires after a certain period (indicated by `expires_in`). It is highly recommended to cache the `access_token` and reuse it until it expires to avoid unnecessary API calls and potential rate limiting.
|
|
46
|
+
|
|
47
|
+
For production, pass `prod: true`:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
auth = DpdIeApi::Auth.fetch_token("YOUR_STATIC_TOKEN", "YOUR_USER", "YOUR_PASSWORD", prod: true)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then, initialize the client with your access token:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
client = DpdIeApi::Client.new(
|
|
57
|
+
access_token,
|
|
58
|
+
prod: true # set to true for production, false for pre-production (default)
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Client constructor:**
|
|
63
|
+
|
|
64
|
+
- `access_token` - Bearer token from `DpdIeApi::Auth.fetch_token` (required)
|
|
65
|
+
- `prod:` - Set to `true` for production (`https://papi.dpd.ie`), `false` for pre-production (`https://pre-prod-papi.dpd.ie`). Defaults to `false`.
|
|
66
|
+
|
|
67
|
+
### Error Handling
|
|
68
|
+
|
|
69
|
+
All endpoints raise `DpdIeApi::Error` on failure. The error object exposes both HTTP status code and response body:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
begin
|
|
73
|
+
client.preadvice.create(payload)
|
|
74
|
+
rescue DpdIeApi::Error => error
|
|
75
|
+
puts error.message
|
|
76
|
+
puts error.response_http_code
|
|
77
|
+
puts error.response_body
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Preadvice
|
|
82
|
+
|
|
83
|
+
#### Create
|
|
84
|
+
|
|
85
|
+
Create a preadvice consignment. Pass a Ruby hash — the gem handles XML serialization internally.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
payload = {
|
|
89
|
+
consignment: {
|
|
90
|
+
record_id: '1',
|
|
91
|
+
customer_account: '5993L3',
|
|
92
|
+
total_parcels: '1',
|
|
93
|
+
relabel: '1',
|
|
94
|
+
service_option: '5',
|
|
95
|
+
service_type: '1',
|
|
96
|
+
weight: '5',
|
|
97
|
+
delivery_address: {
|
|
98
|
+
contact: 'Warehouse address',
|
|
99
|
+
contact_telephone: '0877777777',
|
|
100
|
+
contact_email: 'test.email@no-email.gg',
|
|
101
|
+
business_name: 'Shop name',
|
|
102
|
+
address_line1: 'Trimgate St',
|
|
103
|
+
address_line2: 'Athlone Business Park',
|
|
104
|
+
address_line3: 'Navan',
|
|
105
|
+
address_line4: 'Meath',
|
|
106
|
+
post_code: 'N37XK83',
|
|
107
|
+
country_code: 'IE',
|
|
108
|
+
},
|
|
109
|
+
collection_address: {
|
|
110
|
+
contact: 'Customer address',
|
|
111
|
+
contact_telephone: '0877777777',
|
|
112
|
+
contact_email: 'test.email@no-email.gg',
|
|
113
|
+
business_name: 'Customer name',
|
|
114
|
+
address_line1: '40 Drumcondra Rd Lower',
|
|
115
|
+
address_line2: '',
|
|
116
|
+
address_line3: 'Drumcondra',
|
|
117
|
+
address_line4: 'Dublin',
|
|
118
|
+
post_code: 'N37XK83',
|
|
119
|
+
country_code: 'IE'
|
|
120
|
+
},
|
|
121
|
+
references: [
|
|
122
|
+
{ reference_name: 'Reference1', reference_value: 'Order Name 123', parcel_number: '1' },
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
result = client.preadvice.create(payload)
|
|
128
|
+
puts result.status
|
|
129
|
+
puts result.consignment.tracking_number
|
|
130
|
+
puts result.consignment.label_image
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> [!NOTE]
|
|
134
|
+
> Hash keys can be symbols (snake_case, auto-converted to PascalCase XML elements) or strings (passed through as-is for exact control). For example, use `"RecordID" => '1'` if the API requires a specific casing that differs from the automatic conversion.
|
|
135
|
+
|
|
136
|
+
### Track
|
|
137
|
+
|
|
138
|
+
#### Find
|
|
139
|
+
|
|
140
|
+
Retrieve tracking information for a consignment.
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
tracking = client.track.find("800436802")
|
|
144
|
+
|
|
145
|
+
puts tracking.tracking_number
|
|
146
|
+
puts tracking.consignment_number
|
|
147
|
+
puts tracking.consignment_status
|
|
148
|
+
puts tracking.consignment_service
|
|
149
|
+
|
|
150
|
+
# Tracking history events
|
|
151
|
+
event = tracking.tracking_history.event_detail
|
|
152
|
+
puts event.event_date
|
|
153
|
+
puts event.event_time
|
|
154
|
+
puts event.event_type_name
|
|
155
|
+
puts event.event_depot_name
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The `tracking_type` defaults to `"consignment"` but can be overridden:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
tracking = client.track.find("800436802", tracking_type: "parcel")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Response Objects
|
|
165
|
+
|
|
166
|
+
All endpoints return response objects that provide:
|
|
167
|
+
|
|
168
|
+
- **Dot notation access** — `result.consignment.tracking_number` (keys are auto-underscored)
|
|
169
|
+
- **`#raw`** — the original parsed response hash with original PascalCase keys
|
|
170
|
+
- **`#to_hash`** — convert the response object back to a Ruby hash
|
|
22
171
|
|
|
23
172
|
## Development
|
|
24
173
|
|
|
25
|
-
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
174
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
26
175
|
|
|
27
176
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
28
177
|
|
|
29
178
|
## Contributing
|
|
30
179
|
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
180
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/postco/dpd_ie_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/postco/dpd_ie_api/blob/main/CODE_OF_CONDUCT.md).
|
|
32
181
|
|
|
33
182
|
## License
|
|
34
183
|
|
|
@@ -36,4 +185,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
36
185
|
|
|
37
186
|
## Code of Conduct
|
|
38
187
|
|
|
39
|
-
Everyone interacting in the DpdIeApi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
188
|
+
Everyone interacting in the DpdIeApi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/postco/dpd_ie_api/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module DpdIeApi
|
|
7
|
+
class Auth
|
|
8
|
+
PROD_BASE_URL = 'https://papi.dpd.ie'
|
|
9
|
+
TEST_BASE_URL = 'https://pre-prod-papi.dpd.ie'
|
|
10
|
+
AUTH_PATH = '/common/api/authorize'
|
|
11
|
+
|
|
12
|
+
def self.fetch_token(static_token, user, password, prod: false)
|
|
13
|
+
base_url = prod ? PROD_BASE_URL : TEST_BASE_URL
|
|
14
|
+
url = "#{base_url}#{AUTH_PATH}"
|
|
15
|
+
|
|
16
|
+
response = Faraday.post(url) do |req|
|
|
17
|
+
req.headers['Authorization'] = "Basic #{static_token}"
|
|
18
|
+
req.headers['Content-Type'] = 'application/json'
|
|
19
|
+
req.headers['Accept'] = 'application/json'
|
|
20
|
+
req.body = { 'User' => user, 'Password' => password, 'Type' => 'CUST' }.to_json
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
unless response.success?
|
|
24
|
+
raise DpdIeApi::Error.new(
|
|
25
|
+
"Failed to authorize: #{response.status} - #{response.body}",
|
|
26
|
+
response_http_code: response.status,
|
|
27
|
+
response_body: response.body
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
parsed = JSON.parse(response.body)
|
|
32
|
+
|
|
33
|
+
if parsed['Status'] == 'FAIL'
|
|
34
|
+
raise DpdIeApi::Error.new(
|
|
35
|
+
"Authorization error: #{parsed['Reason']}",
|
|
36
|
+
response_http_code: response.status,
|
|
37
|
+
response_body: response.body
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Objects::Auth.new(parsed)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
|
|
5
|
+
module DpdIeApi
|
|
6
|
+
class Client
|
|
7
|
+
PROD_BASE_URL = 'https://papi.dpd.ie'
|
|
8
|
+
TEST_BASE_URL = 'https://pre-prod-papi.dpd.ie'
|
|
9
|
+
|
|
10
|
+
def initialize(access_token, prod: false)
|
|
11
|
+
@access_token = access_token
|
|
12
|
+
@prod = prod
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def base_url
|
|
16
|
+
@prod ? PROD_BASE_URL : TEST_BASE_URL
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def connection
|
|
20
|
+
@connection ||= Faraday.new(url: base_url) do |f|
|
|
21
|
+
f.headers['Authorization'] = "Bearer #{@access_token}"
|
|
22
|
+
f.headers['Content-Type'] = 'application/xml'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def preadvice
|
|
27
|
+
Resources::Preadvice.new(self)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def track
|
|
31
|
+
Resources::Track.new(self)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/core_ext/string'
|
|
5
|
+
require 'ostruct'
|
|
6
|
+
|
|
7
|
+
module DpdIeApi
|
|
8
|
+
module Objects
|
|
9
|
+
class Base < OpenStruct
|
|
10
|
+
attr_reader :original_response
|
|
11
|
+
|
|
12
|
+
def initialize(attributes)
|
|
13
|
+
@original_response = deep_freeze_object(attributes)
|
|
14
|
+
super(to_ostruct(attributes))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_ostruct(obj)
|
|
18
|
+
if obj.is_a?(Hash)
|
|
19
|
+
OpenStruct.new(obj.transform_keys { |key| key.to_s.underscore }.transform_values { |val| to_ostruct(val) })
|
|
20
|
+
elsif obj.is_a?(Array)
|
|
21
|
+
obj.map { |o| to_ostruct(o) }
|
|
22
|
+
else
|
|
23
|
+
obj
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_hash
|
|
28
|
+
ostruct_to_hash(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def raw
|
|
32
|
+
@original_response
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def deep_freeze_object(obj)
|
|
38
|
+
case obj
|
|
39
|
+
when Hash
|
|
40
|
+
obj.transform_values { |value| deep_freeze_object(value) }.freeze
|
|
41
|
+
when Array
|
|
42
|
+
obj.map { |item| deep_freeze_object(item) }.freeze
|
|
43
|
+
else
|
|
44
|
+
obj.respond_to?(:freeze) ? obj.freeze : obj
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ostruct_to_hash(object)
|
|
49
|
+
case object
|
|
50
|
+
when OpenStruct
|
|
51
|
+
hash = object.to_h.except(:table)
|
|
52
|
+
hash.transform_keys(&:to_s).transform_values { |value| ostruct_to_hash(value) }
|
|
53
|
+
when Array
|
|
54
|
+
object.map { |item| ostruct_to_hash(item) }
|
|
55
|
+
when Hash
|
|
56
|
+
object.transform_keys(&:to_s).transform_values { |value| ostruct_to_hash(value) }
|
|
57
|
+
else
|
|
58
|
+
object
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DpdIeApi
|
|
4
|
+
module Resources
|
|
5
|
+
class Preadvice
|
|
6
|
+
PREADVICE_PATH = '/common/api/preadvice'
|
|
7
|
+
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create(payload)
|
|
13
|
+
xml_body = DpdIeApi::XmlBuilder.build('PreAdvice', payload)
|
|
14
|
+
|
|
15
|
+
response = @client.connection.post(PREADVICE_PATH) do |req|
|
|
16
|
+
req.body = xml_body
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless response.success?
|
|
20
|
+
raise DpdIeApi::Error.new(
|
|
21
|
+
"Failed to create preadvice: #{response.status} - #{response.body}",
|
|
22
|
+
response_http_code: response.status,
|
|
23
|
+
response_body: response.body
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
parsed = DpdIeApi::XmlParser.parse(response.body, '//PreAdviceResponse')
|
|
28
|
+
|
|
29
|
+
if parsed['Status'] == 'FAIL'
|
|
30
|
+
raise DpdIeApi::Error.new(
|
|
31
|
+
"Preadvice error: #{parsed['PreAdviceErrorDetails']}",
|
|
32
|
+
response_http_code: response.status,
|
|
33
|
+
response_body: response.body
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Objects::Preadvice.new(parsed)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module DpdIeApi
|
|
6
|
+
module Resources
|
|
7
|
+
class Track
|
|
8
|
+
TRACK_PATH = '/common/api/track'
|
|
9
|
+
|
|
10
|
+
def initialize(client)
|
|
11
|
+
@client = client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find(tracking_number, tracking_type: 'consignment')
|
|
15
|
+
xml_body = build_tracking_xml(tracking_number, tracking_type)
|
|
16
|
+
|
|
17
|
+
response = @client.connection.post(TRACK_PATH) do |req|
|
|
18
|
+
req.headers['Accept'] = 'application/json'
|
|
19
|
+
req.body = xml_body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
unless response.success?
|
|
23
|
+
raise DpdIeApi::Error.new(
|
|
24
|
+
"Failed to track consignment: #{response.status} - #{response.body}",
|
|
25
|
+
response_http_code: response.status,
|
|
26
|
+
response_body: response.body
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
parsed = JSON.parse(response.body)
|
|
31
|
+
|
|
32
|
+
if parsed['TrackingErrorInfo']
|
|
33
|
+
error_info = unwrap(parsed['TrackingErrorInfo'])
|
|
34
|
+
error_detail = unwrap(error_info['TrackingErrorDetail'])
|
|
35
|
+
raise DpdIeApi::Error.new(
|
|
36
|
+
"Tracking error: #{error_detail['ErrorDetailCodeDesc']}",
|
|
37
|
+
response_http_code: response.status,
|
|
38
|
+
response_body: response.body
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Objects::Track.new(unwrap(parsed['TrackingInfo']))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def unwrap(value)
|
|
48
|
+
value.is_a?(Array) ? value.first : value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_tracking_xml(tracking_number, tracking_type)
|
|
52
|
+
DpdIeApi::XmlBuilder.build('TrackingRequest', {
|
|
53
|
+
request_line: {
|
|
54
|
+
tracking_number: tracking_number,
|
|
55
|
+
tracking_type: tracking_type
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/dpd_ie_api/version.rb
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
|
5
|
+
|
|
6
|
+
module DpdIeApi
|
|
7
|
+
class XmlBuilder
|
|
8
|
+
def self.build(root_name, data)
|
|
9
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'iso-8859-1') do |xml|
|
|
10
|
+
xml.send(root_name) do
|
|
11
|
+
hash_to_xml(xml, data)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
builder.to_xml
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def hash_to_xml(xml, data)
|
|
21
|
+
data.each do |key, value|
|
|
22
|
+
element_name = normalize_key(key)
|
|
23
|
+
|
|
24
|
+
case value
|
|
25
|
+
when Hash
|
|
26
|
+
xml.send(element_name) do
|
|
27
|
+
hash_to_xml(xml, value)
|
|
28
|
+
end
|
|
29
|
+
when Array
|
|
30
|
+
xml.send(element_name) do
|
|
31
|
+
child_name = element_name.singularize
|
|
32
|
+
value.each do |item|
|
|
33
|
+
if item.is_a?(Hash)
|
|
34
|
+
xml.send(child_name) do
|
|
35
|
+
hash_to_xml(xml, item)
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
xml.send(child_name, item.to_s)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
xml.send(element_name, value.to_s)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def normalize_key(key)
|
|
49
|
+
case key
|
|
50
|
+
when Symbol then key.to_s.camelize
|
|
51
|
+
when String then key
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
module DpdIeApi
|
|
6
|
+
class XmlParser
|
|
7
|
+
def self.parse(body, root_xpath)
|
|
8
|
+
doc = Nokogiri::XML(body)
|
|
9
|
+
root = doc.at_xpath(root_xpath)
|
|
10
|
+
xml_node_to_hash(root)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def xml_node_to_hash(node)
|
|
17
|
+
return nil if node.nil?
|
|
18
|
+
|
|
19
|
+
children = node.element_children
|
|
20
|
+
|
|
21
|
+
return node.text if children.empty?
|
|
22
|
+
|
|
23
|
+
result = {}
|
|
24
|
+
children.each do |child|
|
|
25
|
+
key = child.name
|
|
26
|
+
if result.key?(key)
|
|
27
|
+
result[key] = [result[key]] unless result[key].is_a?(Array)
|
|
28
|
+
result[key] << xml_node_to_hash(child)
|
|
29
|
+
else
|
|
30
|
+
result[key] = xml_node_to_hash(child)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/dpd_ie_api.rb
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'dpd_ie_api/version'
|
|
4
|
+
require_relative 'dpd_ie_api/objects/base'
|
|
5
|
+
require_relative 'dpd_ie_api/objects/auth'
|
|
6
|
+
require_relative 'dpd_ie_api/objects/preadvice'
|
|
7
|
+
require_relative 'dpd_ie_api/objects/track'
|
|
8
|
+
require_relative 'dpd_ie_api/xml_builder'
|
|
9
|
+
require_relative 'dpd_ie_api/xml_parser'
|
|
10
|
+
require_relative 'dpd_ie_api/auth'
|
|
11
|
+
require_relative 'dpd_ie_api/client'
|
|
12
|
+
require_relative 'dpd_ie_api/resources/preadvice'
|
|
13
|
+
require_relative 'dpd_ie_api/resources/track'
|
|
4
14
|
|
|
5
15
|
module DpdIeApi
|
|
6
|
-
class Error < StandardError
|
|
7
|
-
|
|
16
|
+
class Error < StandardError
|
|
17
|
+
attr_reader :response_http_code, :response_body
|
|
18
|
+
|
|
19
|
+
def initialize(message = nil, response_http_code: nil, response_body: nil)
|
|
20
|
+
@response_http_code = response_http_code
|
|
21
|
+
@response_body = response_body
|
|
22
|
+
super(message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
8
25
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dpd_ie_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Linh Ho
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: nokogiri
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: activesupport
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,6 +66,20 @@ dependencies:
|
|
|
52
66
|
- - "~>"
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
68
|
version: '3.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
55
83
|
description: Provides a Ruby interface for interacting with the DPD Ireland API, including
|
|
56
84
|
authorization, label generation, and tracking.
|
|
57
85
|
email:
|
|
@@ -60,22 +88,33 @@ executables: []
|
|
|
60
88
|
extensions: []
|
|
61
89
|
extra_rdoc_files: []
|
|
62
90
|
files:
|
|
91
|
+
- ".rspec"
|
|
63
92
|
- CHANGELOG.md
|
|
64
93
|
- CODE_OF_CONDUCT.md
|
|
65
94
|
- LICENSE.txt
|
|
66
95
|
- README.md
|
|
67
96
|
- Rakefile
|
|
68
97
|
- lib/dpd_ie_api.rb
|
|
98
|
+
- lib/dpd_ie_api/auth.rb
|
|
99
|
+
- lib/dpd_ie_api/client.rb
|
|
100
|
+
- lib/dpd_ie_api/objects/auth.rb
|
|
101
|
+
- lib/dpd_ie_api/objects/base.rb
|
|
102
|
+
- lib/dpd_ie_api/objects/preadvice.rb
|
|
103
|
+
- lib/dpd_ie_api/objects/track.rb
|
|
104
|
+
- lib/dpd_ie_api/resources/preadvice.rb
|
|
105
|
+
- lib/dpd_ie_api/resources/track.rb
|
|
69
106
|
- lib/dpd_ie_api/version.rb
|
|
107
|
+
- lib/dpd_ie_api/xml_builder.rb
|
|
108
|
+
- lib/dpd_ie_api/xml_parser.rb
|
|
70
109
|
- sig/dpd_ie_api.rbs
|
|
71
|
-
homepage: https://github.com/
|
|
110
|
+
homepage: https://github.com/postco/dpd_ie_api
|
|
72
111
|
licenses:
|
|
73
112
|
- MIT
|
|
74
113
|
metadata:
|
|
75
114
|
allowed_push_host: https://rubygems.org
|
|
76
|
-
homepage_uri: https://github.com/
|
|
77
|
-
source_code_uri: https://github.com/
|
|
78
|
-
changelog_uri: https://github.com/
|
|
115
|
+
homepage_uri: https://github.com/postco/dpd_ie_api
|
|
116
|
+
source_code_uri: https://github.com/postco/dpd_ie_api
|
|
117
|
+
changelog_uri: https://github.com/postco/dpd_ie_api/blob/main/CHANGELOG.md
|
|
79
118
|
post_install_message:
|
|
80
119
|
rdoc_options: []
|
|
81
120
|
require_paths:
|