dhl-ecommerce 1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9fdcd2715365cddaefd2c83251042ead98c90bc4
4
+ data.tar.gz: cd2b1f3aa0c60ded06018d0061a62fe4cb6b0b74
5
+ SHA512:
6
+ metadata.gz: 171ad8b6de6315759b2bda5af0addc55c32d031070ad5e2067013de9136d7cf7b0594826411d9e34fac36431240cc7eea41e77983554b8b1766166d4ceba0f11
7
+ data.tar.gz: 5eb55a54b412c6e6a34129b97dc979b53051c7c2000ece228e742fc3a934e415122352532fa4dce376078dbda397daf4fbbf33b353a015dd714bf9ae47886aaa
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 meowbox Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,112 @@
1
+ # DHL eCommerce API Wrapper for Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/meowbox/dhl-ecommerce-ruby.svg)](https://travis-ci.org/meowbox/dhl-ecommerce-ruby)
4
+
5
+ ## Installation
6
+
7
+ Installation is as simple at adding the following to your `Gemfile`.
8
+
9
+ ```ruby
10
+ gem "dhl-ecommerce"
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ The only configuration necessary is your `client_id` and either a `username`
16
+ and `password` or an `access_token`.
17
+
18
+ ```ruby
19
+ # DHL e-Commerce configuration.
20
+ DHL::Ecommerce.configure do |config|
21
+ config.username = "meowbox"
22
+ config.password = "password"
23
+ config.client_id = 6369
24
+
25
+ # Label format can be either :png or :zpl.
26
+ config.label_format = :png
27
+ end
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Accounts
33
+
34
+ ```ruby
35
+ # Find all accounts.
36
+ accounts = DHL::Ecommerce::Account.all
37
+
38
+ # Find a particular account.
39
+ account = DHL::Ecommerce::Account.find 6369
40
+ ```
41
+
42
+ ### Locations
43
+
44
+ ```ruby
45
+ # Find all locations.
46
+ locations = DHL::Ecommerce::Location.all
47
+
48
+ # Find a particular location.
49
+ location = DHL::Ecommerce::Location.find 6369
50
+ ```
51
+
52
+ It's also possible to obtain a list of locations from an account by calling the
53
+ `locations` method.
54
+
55
+ ### Labels
56
+
57
+ ```ruby
58
+ # Create the consignee's address.
59
+ consignee_address = DHL::Ecommerce::StandardAddress.new firm: "meowbox Inc.",
60
+ address_1: "816 PEACE PORTAL DR",
61
+ address_2: "STE 103",
62
+ city: "BLAINE",
63
+ postal_code: "98230",
64
+ state: "WA",
65
+ country: "US"
66
+
67
+ # Create a single label.
68
+ label = DHL::Ecommerce::Label.create consignee_address: consignee_address,
69
+ facility: :auburn,
70
+ location_id: 6369,
71
+ product_id: 83,
72
+ weight: 1
73
+ ```
74
+
75
+ The `file` method of any valid label object will return a `StringIO` object
76
+ containing either the PNG or ZPL representation of the label depending on your
77
+ configuration.
78
+
79
+ It's also possible to create multiple labels at once by passing an array of
80
+ attributes to the `create` method.
81
+
82
+ ```ruby
83
+ # Create multiple labels.
84
+ labels = DHL::Ecommerce::Label.create [ label_attributes_1, label_attributes_2 ]
85
+ ```
86
+
87
+ ### Manifests
88
+
89
+ Unlike other models, the `create` method will always return an array of
90
+ `Manifest` objects – that's because the DHL e-Commerce API will return a
91
+ manifest for each location and/or facility.
92
+
93
+ ```ruby
94
+ # Create manifests
95
+ manifests = DHL::Ecommerce::Manifest.create labels
96
+ ```
97
+
98
+ ### Tracking
99
+
100
+ ```ruby
101
+ # Find events for a particular label.
102
+ events = DHL::Ecommerce::Label.find("420000000000000001").events
103
+ ```
104
+
105
+ ### Other models
106
+
107
+ A few supporting models are also available.
108
+
109
+ - `Event`
110
+ - `Imbp`
111
+ - `Product`
112
+ - `StandardAddress`
@@ -0,0 +1,89 @@
1
+ require "builder"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+ require "faraday_middleware/response/rashify"
5
+ require "hashie"
6
+ require "multi_xml"
7
+ require "rash"
8
+
9
+ # Errors
10
+ require "dhl/ecommerce/errors/base_error"
11
+ require "dhl/ecommerce/errors/authentication_error"
12
+ require "dhl/ecommerce/errors/validation_error"
13
+
14
+ # Operations
15
+ require "dhl/ecommerce/operations/find"
16
+ require "dhl/ecommerce/operations/list"
17
+
18
+ # Resources
19
+ require "dhl/ecommerce/base"
20
+ require "dhl/ecommerce/account"
21
+ require "dhl/ecommerce/event"
22
+ require "dhl/ecommerce/impb"
23
+ require "dhl/ecommerce/label"
24
+ require "dhl/ecommerce/location"
25
+ require "dhl/ecommerce/manifest"
26
+ require "dhl/ecommerce/product"
27
+ require "dhl/ecommerce/standard_address"
28
+ require "dhl/ecommerce/tracked_event"
29
+
30
+ # Version
31
+ require "dhl/ecommerce/version"
32
+
33
+ module DHL
34
+ module Ecommerce
35
+ @access_token = ENV["DHL_ECOMMERCE_ACCESS_TOKEN"]
36
+ @client_id = ENV["DHL_ECOMMERCE_CLIENT_ID"]
37
+ @password = ENV["DHL_ECOMMERCE_PASSWORD"]
38
+ @username = ENV["DHL_ECOMMERCE_USERNAME"]
39
+ @label_format = :png
40
+
41
+ class << self
42
+ attr_accessor :client_id, :label_format
43
+ attr_writer :access_token, :password, :username
44
+
45
+ def configure
46
+ yield self
47
+ end
48
+ end
49
+
50
+ def self.access_token
51
+ # TODO This needs better error handling.
52
+ @access_token ||= client.get("https://api.dhlglobalmail.com/v1/auth/access_token", username: @username, password: @password, state: Time.now.to_i).body.response.data[:access_token]
53
+ end
54
+
55
+ def self.request(method, url, &block)
56
+ client.params = {
57
+ access_token: self.access_token,
58
+ client_id: client_id
59
+ }
60
+
61
+ response = client.run_request method.downcase.to_sym, url, nil, nil, &block
62
+
63
+ puts response.to_yaml
64
+
65
+ case response.status
66
+ when 400
67
+ case response.body.response.meta.error.error_type
68
+ when "INVALID_CLIENT_ID", "INVALID_KEY", "INVALID_TOKEN", "INACTIVE_KEY"
69
+ throw Errors::AuthenticationError.new response.body.response.meta.error.error_message, response
70
+ when "VALIDATION_ERROR", "INVALID_FACILITY_CODE"
71
+ throw Errors::ValidationError.new response.body.response.meta.error.error_message, response
72
+ else
73
+ throw Errors::BaseError.new response.body.response.meta.error.error_message, response
74
+ end
75
+ end
76
+
77
+ response.body.response.data
78
+ end
79
+
80
+ private
81
+ def self.client
82
+ @client ||= Faraday.new url: "https://api.dhlglobalmail.com/v1/", headers: { accept: "application/xml", content_type: "application/xml;charset=UTF-8" } do |c|
83
+ c.response :rashify
84
+ c.response :xml, :content_type => /\bxml$/
85
+ c.adapter :net_http
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,30 @@
1
+ module DHL
2
+ module Ecommerce
3
+ class Account < Base
4
+ include DHL::Ecommerce::Operations::Find
5
+ include DHL::Ecommerce::Operations::List
6
+
7
+ attr_reader :id, :address, :email
8
+
9
+ def initialize(attributes = {})
10
+ super attributes
11
+
12
+ unless attributes.empty?
13
+ @id = attributes[:account].to_i if attributes[:account]
14
+ @address = StandardAddress.new attributes
15
+ end
16
+ end
17
+
18
+ def locations
19
+         response = DHL::Ecommerce.request :get, "https://api.dhlglobalmail.com/v1/#{self.resource_name.downcase}s/#{id}/#{DHL::Ecommerce::Location.resource_name.downcase}s"
20
+         response[self.resource_name]["#{DHL::Ecommerce::Location.resource_name}s"][DHL::Ecommerce::Location.resource_name] = [response[self.resource_name]["#{DHL::Ecommerce::Location.resource_name}s"][DHL::Ecommerce::Location.resource_name]] unless response[self.resource_name]["#{DHL::Ecommerce::Location.resource_name}s"][DHL::Ecommerce::Location.resource_name].is_a? Array
21
+
22
+         response[self.resource_name]["#{DHL::Ecommerce::Location.resource_name}s"].map do |attributes|
23
+ location = DHL::Ecommerce::Location.new attributes
24
+ location.instance_variable_set :@account, self
25
+ location
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ module DHL
2
+ module Ecommerce
3
+ class Base
4
+ def initialize(attributes = {})
5
+ attributes.each do |attribute, value|
6
+ next if attribute.to_sym == :class
7
+
8
+ if respond_to? "#{attribute}="
9
+ send "#{attribute}=", value
10
+ elsif respond_to?("#{attribute}")
11
+ instance_variable_set "@#{attribute}", value
12
+ end
13
+ end unless attributes.empty?
14
+ end
15
+
16
+ private
17
+ def self.resource_name
18
+ self.name.split("::").last
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ module DHL
2
+ module Ecommerce
3
+ module Errors
4
+ class AuthenticationError < BaseError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ module DHL
2
+ module Ecommerce
3
+ module Errors
4
+ class BaseError < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(message = nil, response = nil)
8
+ super message
9
+
10
+ @response = response
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module DHL
2
+ module Ecommerce
3
+ module Errors
4
+ class ValidationError < BaseError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module DHL
2
+ module Ecommerce
3
+ class Event < Base
4
+ include DHL::Ecommerce::Operations::Find
5
+ include DHL::Ecommerce::Operations::List
6
+
7
+ attr_reader :id, :description
8
+
9
+ def initialize(attributes = {})
10
+ super attributes
11
+
12
+ unless attributes.empty?
13
+ @description.upcase! if @description
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module DHL
2
+ module Ecommerce
3
+ class Impb < Base
4
+ attr_accessor :construct, :value
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,250 @@
1
+ module DHL
2
+ module Ecommerce
3
+ class Label < Base
4
+ attr_accessor :customer_confirmation_number, :service_endorsement, :reference, :batch, :mail_type, :facility, :expected_ship_date, :weight, :consignee_address, :return_address, :service
5
+ attr_reader :id, :location_id, :product_id, :events, :service_type, :file, :impb
6
+
7
+ FACILITIES = {
8
+ auburn: "USSEA1",
9
+ compton: "USLAX1",
10
+ denver: "USDEN1",
11
+ edgewood: "USISP1",
12
+ elkridge: "USBWI1",
13
+ forest_park: "USATL1",
14
+ franklin: "USBOS1",
15
+ grand_prairie: "USDFW1",
16
+ hebron: "USCVG1",
17
+ melrose_park: "USORD1",
18
+ memphis: "USMEM1",
19
+ orlando: "USMCO1",
20
+ phoenix: "USPHX1",
21
+ salt_lake_city: "USSLC1",
22
+ secaucus: "USEWR1",
23
+ st_louis: "USSTL1",
24
+ union_city: "USSFO1"
25
+ }
26
+
27
+ MAIL_TYPES = {
28
+ bound_printed_matter: 6,
29
+ irregular_parcel: 2,
30
+ machinable_parcel: 3,
31
+ marketing_parcel_gte_6oz: 30,
32
+ marketing_parcel_lt_6oz: 20,
33
+ media_mail: 9,
34
+ parcel_select_machinable: 7,
35
+ parcel_select_nonmachinable: 8
36
+ }
37
+
38
+ SERVICES = {
39
+ delivery_confirmation: "DELCON",
40
+ signature_confirmation: "SIGCON"
41
+ }
42
+
43
+ SERVICE_ENDORSEMENTS = {
44
+ address_service: 1,
45
+ change_service: 3,
46
+ forwarding_service: 2,
47
+ return_service: 4
48
+ }
49
+
50
+ def location_id=(location_id)
51
+ @location = nil
52
+ @location_id = location_id
53
+ end
54
+
55
+ def location
56
+ @location ||= DHL::Ecommerce::Location.find location_id
57
+ end
58
+
59
+ def location=(location)
60
+ @location = nil if @location_id != location.id
61
+ @location_id = location.id
62
+ end
63
+
64
+ def product_id=(product_id)
65
+ @product = nil
66
+ @product_id = product_id
67
+ end
68
+
69
+ def product
70
+ @product ||= DHL::Ecommerce::Product.find product_id
71
+ end
72
+
73
+ def product=(product)
74
+ @product = nil if @product_id != product.id
75
+ @product_id = product.id
76
+ end
77
+
78
+ def self.create(attributes)
79
+ array = attributes.is_a? Array
80
+ attributes = [attributes] unless array
81
+
82
+ labels = self.create_in_batches attributes
83
+
84
+ array ? labels : labels.first
85
+ end
86
+
87
+ def self.find(id)
88
+ attributes = DHL::Ecommerce.request :get, "https://api.dhlglobalmail.com/v1/mailitems/track" do |request|
89
+ request.params[:number] = id
90
+ end
91
+
92
+ attributes[:mail_items][:mail_item] = attributes[:mail_items][:mail_item].first if attributes[:mail_items][:mail_item].is_a? Array
93
+
94
+ new attributes[:mail_items][:mail_item]
95
+ end
96
+
97
+ def initialize(attributes = {})
98
+ super attributes
99
+
100
+ unless attributes.empty?
101
+ if attributes[:mail]
102
+ @id = attributes[:mail][:mailIdentifier] if attributes[:mail][:mailIdentifier]
103
+ @weight = attributes[:mail][:weight] if attributes[:mail][:weight]
104
+ @product_id = attributes[:mail][:product_id] if attributes[:mail][:product_id]
105
+ @reference = attributes[:mail][:customer_reference] if attributes[:mail][:customer_reference]
106
+ @batch = attributes[:mail][:batch_reference] if attributes[:mail][:batch_reference]
107
+ @impb = attributes[:mail][:intelligent_mail_barcode] if attributes[:mail][:intelligent_mail_barcode]
108
+ @customer_confirmation_number = attributes[:mail][:customer_confirmation_number] if attributes[:mail][:customer_confirmation_number]
109
+
110
+ @services = :delivery_confirmation if attributes[:mail][:delivery_confirmation_flag] == '1'
111
+ @services = :signature_confirmation if attributes[:mail][:signature_confirmation_flag] == '1'
112
+ end
113
+
114
+ if attributes[:pickup]
115
+ @location_id = attributes[:pickup][:pickup] if attributes[:pickup][:pickup]
116
+ end
117
+
118
+ if attributes[:recipient]
119
+ @consignee_address = StandardAddress.new attributes[:recipient]
120
+ end
121
+
122
+ if attributes[:events]
123
+ @events = []
124
+
125
+ attributes[:events][:event] = [attributes[:events][:event]] unless attributes[:events][:event].is_a? Array
126
+ attributes[:events][:event].each do |event_attributes|
127
+ event = TrackedEvent.new event_attributes
128
+ event.instance_variable_set :@event, Event.new(event_attributes)
129
+
130
+ @events << event
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def xml
139
+ xml = Builder::XmlMarkup.new
140
+ xml.Mpu do
141
+ xml.PackageId customer_confirmation_number
142
+
143
+ xml.PackageRef do
144
+ xml.PrintFlag customer_confirmation_number.present?
145
+ # xml.LabelText ""
146
+ end
147
+
148
+ xml.ConsigneeAddress do
149
+ xml.StandardAddress do
150
+ xml.Name consignee_address.name
151
+ xml.Firm consignee_address.firm
152
+ xml.Address1 consignee_address.address_1
153
+ xml.Address2 consignee_address.address_2
154
+ xml.City consignee_address.city
155
+ xml.State consignee_address.state.to_s.upcase
156
+ xml.Zip consignee_address.postal_code
157
+ xml.CountryCode consignee_address.country.to_s.upcase
158
+ end
159
+ end
160
+
161
+ xml.ReturnAddress do
162
+ xml.StandardAddress do
163
+ xml.Name return_address.name
164
+ xml.Firm return_address.firm
165
+ xml.Address1 return_address.address_1
166
+ xml.Address2 return_address.address_2
167
+ xml.City return_address.city
168
+ xml.State return_address.state.to_s.upcase
169
+ xml.Zip return_address.postal_code
170
+ xml.CountryCode return_address.country.to_s.upcase
171
+ end
172
+ end if return_address
173
+
174
+ xml.OrderedProductCode product_id
175
+ xml.Service SERVICES.fetch service.downcase.to_sym if service
176
+ xml.ServiceEndorsement SERVICE_ENDORSEMENTS.fetch service_endorsement.downcase.to_sym if service_endorsement
177
+
178
+ # xml.DgCategory ""
179
+ # xml.ContactPhoneNumber ""
180
+
181
+ xml.Weight do
182
+ xml.Value weight
183
+
184
+ # TODO Add support for other units supported by DHL e-Commerce.
185
+ xml.Unit :lb.to_s.upcase
186
+ end
187
+
188
+ xml.BillingRef1 reference
189
+ xml.BillingRef2 batch
190
+ xml.FacilityCode FACILITIES.fetch facility.downcase.to_sym if facility
191
+ xml.ExpectedShipDate (expected_ship_date || DateTime.now).strftime("%Y%m%d")
192
+ xml.MailTypeCode MAIL_TYPES.fetch mail_type ? mail_type.downcase.to_sym : :parcel_select_machinable
193
+ end
194
+ end
195
+
196
+ def self.create_in_batches(attributes)
197
+ attributes.group_by do |value| value[:location_id] end.each.collect do |location_id, location_attributes|
198
+ case DHL::Ecommerce.label_format
199
+ when :png, :image
200
+ url = "https://api.dhlglobalmail.com/v1/#{self.resource_name.downcase}/US/#{location_id}/image"
201
+ when :zpl
202
+ url = "https://api.dhlglobalmail.com/v1/#{self.resource_name.downcase}/US/#{location_id}/zpl"
203
+ end
204
+
205
+ location_attributes.each_slice(500).collect do |slice|
206
+ labels = slice.map do |slice_attributes|
207
+ new slice_attributes
208
+ end
209
+
210
+ xml = Builder::XmlMarkup.new
211
+ xml.instruct! :xml, version: "1.1", encoding: "UTF-8"
212
+ xml.EncodeRequest do
213
+ xml.CustomerId location_id
214
+ xml.BatchRef DateTime.now.strftime("%Q")
215
+ xml.HalfOnError false
216
+ xml.RejectAllOnError true
217
+ xml.MpuList do
218
+ xml << labels.map do |label| label.send :xml end.join
219
+ end
220
+ end
221
+
222
+ response = DHL::Ecommerce.request :post, url do |request|
223
+ request.body = xml.target!
224
+ end
225
+
226
+ response[:mpu_list][:mpu] = [response[:mpu_list][:mpu]] unless response[:mpu_list][:mpu].is_a? Array
227
+
228
+ labels.zip(response[:mpu_list][:mpu]).map do |label, label_response|
229
+ label.instance_variable_set :@id, label_response[:mail_item_id].to_i if label_response[:mail_item_id]
230
+
231
+ case DHL::Ecommerce.label_format
232
+ when :png, :image
233
+ label.instance_variable_set :@file, StringIO.new(Base64.decode64(label_response[:label_image])) if label_response[:label_image]
234
+ when :zpl
235
+ label.instance_variable_set :@file, StringIO.new(Base64.decode64(label_response[:label_zpl])) if label_response[:label_zpl]
236
+ end
237
+
238
+ if label_response[:label_detail]
239
+ label.instance_variable_set :@impb, Impb.new(label_response[:label_detail][:impb]) if label_response[:label_detail][:impb]
240
+ label.instance_variable_set :@service_type, label_response[:label_detail][:service_type_code].to_i if label_response[:label_detail][:service_type_code]
241
+ end
242
+
243
+ label
244
+ end
245
+ end
246
+ end.flatten
247
+ end
248
+ end
249
+ end
250
+ end