dhl-ecommerce 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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