dhl-bcs 0.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: 0edf9d48f49c8c00bd677e058a1b9a81728dbbf1
4
+ data.tar.gz: ae7cd220247f3b37aae0604d30ce3bb01acdbd16
5
+ SHA512:
6
+ metadata.gz: 726e19231bda1db3dc47835ac20c9d95bc68f3fac5a29140fdc456f741c76ee767a156b8dde6b705f25510f534159f1fb2bf16d04435962c372ae73b1c48c3b7
7
+ data.tar.gz: d358004816955f652956f13756b46fca01dd73e4217dd38f1362f9e04e6b825f2fd04839a6bf90e23071cd7b0c04532a7d1904832b539d8706946de0c979b289
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ /TAGS
12
+ /tags
13
+ /.tags*
14
+ /.idea
15
+ .directory
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at wagner@webit.de. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dhl-bcs.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Christoph Wagner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,217 @@
1
+ # Dhl::Bcs
2
+
3
+ This is a client for the DHL Business Customer Shipping (BCS) API version 2.0.
4
+ It is inspired by the [DHL intraship gem](https://github.com/waldher/dhl-intraship) which implements API version 1.0 which is expired.
5
+ The Dhl::Bcs gem uses [Savon 2](https://github.com/savonrb/savon) to communicate via SOAP with the DHL API.
6
+ The DHL BCS API is just for standard parcels. If you are looking for shipping of express parcels this gem is not for you.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'dhl-bcs'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install dhl-bcs
23
+
24
+ ## Usage
25
+
26
+ Initialize a new API client using
27
+
28
+ ```ruby
29
+ client = Dhl::Bcs.client(config, options)
30
+ ```
31
+
32
+ Config is the following hash:
33
+
34
+ ```ruby
35
+ config = {
36
+ api_user: 'The user for API BasicAuth', #mandatory
37
+ api_pwd: 'The password for API BasicAuth', #mandatory
38
+ user: 'your BCS user name', #mandatory
39
+ signature: 'Your BCS user password', #mandatory
40
+ ekp: 'Your DHL EKP (first part of your DHL Account number)', #mandatory
41
+ participation_number: 'Your DHL EKP (last two characters of your DHL Account number)' #mandatory
42
+ }
43
+ ```
44
+
45
+ Options is an optional parameter and can contain the following parameters:
46
+
47
+ ```ruby
48
+ options = {
49
+ test: true, # If test is set, all API calls go against the DHL test system (defaults to false)
50
+ log: false # If log is set, you get all logging (with request and response XML) to your standard logger. (defaults to true)
51
+ }
52
+ ```
53
+
54
+ ### Create shipments
55
+
56
+ To create a shipment at DHL you need a sender_address, a receiver_address, and informations about the parcel.
57
+
58
+ ```ruby
59
+ shipment = Dhl::Bcs.build_shipment(
60
+ shipper: {
61
+ name: 'Christoph Wagner',
62
+ company: 'webit! Gesellschaft für neue Medien mbH',
63
+ street_name: 'Schandauer Straße',
64
+ street_number: '34',
65
+ zip: '01309',
66
+ city: 'Dresden',
67
+ country_code: 'DE',
68
+ email: 'wagner@webit.de'
69
+ },
70
+ receiver: {
71
+ name: 'Jane Doe',
72
+ street_name: 'Willy-Brandt-Straße',
73
+ street_number: '1',
74
+ zip: '10557',
75
+ city: 'Berlin',
76
+ country_code: 'DE',
77
+ email: 'jane.doe@example.com'
78
+ },
79
+ weight: 3.5,
80
+ length: 10,
81
+ width: 20,
82
+ height: 30,
83
+ shipment_date: Date.new(2016, 7, 13)
84
+ )
85
+
86
+ client.create_shipment_order(shipment)
87
+ ```
88
+
89
+ You will get a result that looks like this:
90
+
91
+ ```ruby
92
+ [
93
+ {
94
+ status: { status_code: '0', status_text: 'ok', status_message: 'Der Webservice wurde ohne Fehler ausgeführt.' },
95
+ shipment_number: '22222222201019582121',
96
+ label_url: 'https://cig.dhl.de/gkvlabel/SANDBOX/dhl-vls/gw/shpmntws/printShipment?token=JD7HKktuvugIFEkhSvCfbEz4J8Ah0dkcVuw4PzBGRyRnW%2FwEPAwfytLtb31e7gMDsSX32%2BEB5exp8nNPs%2FhJSQ%3D%3D',
97
+ }
98
+ ]
99
+ ```
100
+
101
+ This is an Array of Hashes, cause it is possible to send up to 30 parcels at the same time, with the API.
102
+ Say you want to send 3 parcels to the same address you can also do something like that:
103
+
104
+ ```ruby
105
+ shipper = Dhl::Bcs.build_shipper(
106
+ name: 'Christoph Wagner',
107
+ company: 'webit! Gesellschaft für neue Medien mbH',
108
+ street_name: 'Schandauer Straße',
109
+ street_number: '34',
110
+ zip: '01309',
111
+ city: 'Dresden',
112
+ country_code: 'DE',
113
+ email: 'wagner@webit.de'
114
+ )
115
+
116
+ receiver = Dhl::Bcs.build_receiver(
117
+ name: 'Jane Doe',
118
+ street_name: 'Willy-Brandt-Straße',
119
+ street_number: '1',
120
+ zip: '10557',
121
+ city: 'Berlin',
122
+ country_code: 'DE',
123
+ email: 'jane.doe@example.com'
124
+ )
125
+
126
+ shipment1 = Dhl::Bcs.build_shipment(shipper: shipper, receiver: receiver, weight: 3)
127
+ shipment2 = Dhl::Bcs.build_shipment(shipper: shipper, receiver: receiver, weight: 3.5)
128
+ shipment3 = Dhl::Bcs.build_shipment(shipper: shipper, receiver: receiver, weight: 4)
129
+
130
+ client.create_shipment_order(shipment1, shipment2, shipment3)
131
+ ```
132
+
133
+ ### Validate shipments
134
+
135
+ ```ruby
136
+ client.validate_shipment(shipment1, shipment2, shipment3)
137
+ ```
138
+
139
+ ### Update shipment
140
+
141
+ You can update a shipment at DHL. It is the same like deleting it and creating a new one just with one request.
142
+ So you will get a new shipping number for it and so on.
143
+ To update a shipment you need the shipment number of the old one and give a complete new shipment that is created instead.
144
+ This works for one shipment at a time.
145
+ ```ruby
146
+ client.update_shipment_order('22222222901010000944', shipment)
147
+ ```
148
+
149
+ ### Other methods
150
+
151
+ The methods `delete_shipment_order`, `get_label`, `get_export_doc` and `do_manifest` works technically the same.
152
+ They took one or many (up to 30) shipment numbers and do something with these shipments at DHL.
153
+
154
+ To delete a shipment you can use:
155
+ ```ruby
156
+ client.delete_shipment_order('22222222901010000944')
157
+ ```
158
+
159
+ As result you will get one Hash like:
160
+ ```ruby
161
+ {
162
+ '22222222901010000944' => {
163
+ status: { status_code: '0', status_text: 'ok', status_message: nil }
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### Services
169
+
170
+ There is a basic support to add Services to a shipment in this gem.
171
+
172
+ ```ruby
173
+ shipment.services << Dhl::Bcs.build_service(name: 'IndividualSenderRequirement', attributes: { active: '1', details: 'Test' })
174
+ ```
175
+
176
+ A service has a name and attributes.
177
+ Sometimes a service has children, for example the 'IdentCheck'-Service.
178
+
179
+ ```ruby
180
+ shipment.services << Dhl::Bcs.build_service(name: 'IdentCheck', attributes: { active: '1' }, children: { 'Ident' => { surname: 'Doe', given_name: 'Jon Doe', date_of_birth: '1980-12-24', minimum_age: '18' } })
181
+ ```
182
+
183
+ Check out the DHL developer documentation to configure the services you need.
184
+
185
+ ### Get API version
186
+
187
+ ```ruby
188
+ client.get_version
189
+ ```
190
+ You don't need a shipment for that.
191
+
192
+ ### Logging
193
+
194
+ If you need the last made request and its response from the client you can use:
195
+ ```ruby
196
+ client.last_log
197
+ ```
198
+ This works even if you used the option `log: false` at the client. This option controlls just the output in the log file or console.
199
+
200
+ ### Everything else
201
+ Have a deeper look at the code of this gem and find out how things work.
202
+ You can help to implement missing things or extend this documentation.
203
+
204
+ ## Development
205
+
206
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
207
+
208
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
209
+
210
+ ## Contributing
211
+
212
+ Bug reports and pull requests are welcome on GitHub at https://github.com/webit-de/dhl-bcs. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
213
+
214
+
215
+ ## License
216
+
217
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dhl/client"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dhl/bcs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dhl-bcs"
8
+ spec.version = Dhl::Bcs::VERSION
9
+ spec.authors = ["Christoph Wagner"]
10
+ spec.email = ["wagner@webit.de"]
11
+
12
+ spec.summary = %q{Client for DHL Business-Customer-Shipping SOAP API 2.0}
13
+ spec.description = %q{This is inspired by the dhl-intraship gem that is a little bit outdated and doesn't support the new DHL API. If you need DHL Express Services this is not for you.}
14
+ spec.homepage = "https://github.com/webit-de/dhl-bcs"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "savon", "~> 2.11"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.12"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "webmock", "~> 2.1"
28
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
29
+ end
@@ -0,0 +1,41 @@
1
+ require 'dhl/bcs/version'
2
+ require 'dhl/bcs/errors'
3
+ require 'dhl/bcs/v2/client'
4
+ require 'dhl/bcs/v2/buildable'
5
+ require 'dhl/bcs/v2/shipment'
6
+ require 'dhl/bcs/v2/shipper'
7
+ require 'dhl/bcs/v2/receiver'
8
+ require 'dhl/bcs/v2/communication'
9
+ require 'dhl/bcs/v2/location'
10
+ require 'dhl/bcs/v2/address'
11
+ require 'dhl/bcs/v2/packstation'
12
+ require 'dhl/bcs/v2/parcel_shop'
13
+ require 'dhl/bcs/v2/postfiliale'
14
+ require 'dhl/bcs/v2/bank_data'
15
+ require 'dhl/bcs/v2/service'
16
+
17
+ module Dhl
18
+ module Bcs
19
+
20
+ def self.client(config, options = {})
21
+ V2::Client.new(config, options)
22
+ end
23
+
24
+ def self.build_shipment(*args)
25
+ V2::Shipment.build(*args)
26
+ end
27
+
28
+ def self.build_shipper(*args)
29
+ V2::Shipper.build(*args)
30
+ end
31
+
32
+ def self.build_receiver(*args)
33
+ V2::Receiver.build(*args)
34
+ end
35
+
36
+ def self.build_service(*args)
37
+ V2::Service.new(*args)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ module Dhl
2
+ module Bcs
3
+ class Error < StandardError; end
4
+
5
+ class RequestError < StandardError; end
6
+
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Dhl::Bcs::V2
2
+ class Address < Location
3
+
4
+ ADD_PROPS = %i(company company_addition street_name street_number address_addition dispatching_information).freeze
5
+ PROPERTIES = Location::PROPERTIES + ADD_PROPS
6
+ attr_accessor(*ADD_PROPS)
7
+
8
+ def to_soap_hash
9
+ h = {}
10
+ h['cis:name2'] = company if company
11
+ h['cis:name3'] = company_addition if company_addition
12
+ h['cis:streetName'] = street_name
13
+ h['cis:streetNumber'] = street_number
14
+ h['cis:addressAddition'] = address_addition if address_addition
15
+ h['cis:dispatchingInformation'] = dispatching_information if dispatching_information
16
+ h.merge(super)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module Dhl::Bcs::V2
2
+ class BankData
3
+
4
+ include Buildable
5
+
6
+ PROPERTIES = %i(account_owner bank_name iban note1 note2 bic accountreference).freeze
7
+ attr_accessor(*PROPERTIES)
8
+
9
+ def to_soap_hash
10
+ {
11
+ 'cis:accountOwner' => account_owner,
12
+ 'cis:bank_name' => bank_name,
13
+ 'cis:iban' => iban
14
+ }.tap do |h|
15
+ h['cis:note1'] = note1 if note1
16
+ h['cis:note2'] = note2 if note2
17
+ h['cis:bic'] = bic if bic
18
+ h['cis:accountreference'] = accountreference if accountreference
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ module Dhl::Bcs::V2
2
+ module Buildable
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def build(attributes)
10
+ attrib = attributes[underscore(name.split('::').last).to_sym]
11
+ if attrib.is_a?(self)
12
+ attrib
13
+ elsif attrib.nil?
14
+ klass_attributes = {}
15
+ self::PROPERTIES.each do |prop|
16
+ value = attributes.delete(prop)
17
+ klass_attributes[prop] = value if value
18
+ end
19
+ new(klass_attributes)
20
+ elsif attrib.is_a?(Hash)
21
+ new(attrib)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # borrowed from rails without acronyms
28
+ def underscore(camel_cased_word)
29
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
30
+ word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
31
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
32
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
33
+ word.tr!('-'.freeze, '_'.freeze)
34
+ word.downcase!
35
+ word
36
+ end
37
+
38
+ end
39
+
40
+ def initialize(**attributes)
41
+ attributes.each do |property, value|
42
+ send("#{property}=", value) if self.class::PROPERTIES.include?(property)
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,139 @@
1
+ require 'savon'
2
+ require 'stringio'
3
+ require 'logger'
4
+
5
+ module Dhl::Bcs::V2
6
+ class Client
7
+
8
+ WSDL = 'https://cig.dhl.de/cig-wsdls/com/dpdhl/wsdl/geschaeftskundenversand-api/2.0/geschaeftskundenversand-api-2.0.wsdl'
9
+
10
+ def initialize(config, log: true, test: false, **options)
11
+ raise "User must be specified" if config[:user].nil?
12
+ raise "Signature (password) must be specified" if config[:signature].nil?
13
+ raise "EKP (first part of the DHL account number) must be specified" if config[:ekp].nil?
14
+ raise "Participation Number (last two characters of account number) must be specified" if config[:participation_number].nil?
15
+ raise "Api User must be specified" if config[:api_user].nil?
16
+ raise "Api Password must be specified" if config[:api_pwd].nil?
17
+
18
+ @ekp = config[:ekp]
19
+ @participation_number = config[:participation_number]
20
+
21
+ @logIO = StringIO.new
22
+ @logger = Logger.new($stdout) if log
23
+
24
+ @client = Savon.client({
25
+ endpoint: (test ? 'https://cig.dhl.de/services/sandbox/soap' : 'https://cig.dhl.de/services/production/soap'),
26
+ wsdl: WSDL,
27
+ basic_auth: [config[:api_user], config[:api_pwd]],
28
+ logger: Logger.new(@logIO),
29
+ log: true,
30
+ soap_header: {
31
+ 'cis:Authentification' => {
32
+ 'cis:user' => config[:user],
33
+ 'cis:signature' => config[:signature],
34
+ 'cis:type' => 0
35
+ }
36
+ },
37
+ namespaces: { 'xmlns:cis' => 'http://dhl.de/webservice/cisbase' }
38
+ })
39
+ end
40
+
41
+ def get_version(major: 2, minor: 0, build: nil)
42
+ request(:get_version,
43
+ 'bcs:Version' => {
44
+ 'majorRelease' => major,
45
+ 'minorRelease' => minor
46
+ }.tap { |h| h['build'] = build if build }
47
+ ) do |response|
48
+ response.body[:get_version_response][:version]
49
+ end
50
+ end
51
+
52
+ def validate_shipment(*shipments, **options)
53
+ request(:validate_shipment, build_shipment_orders(shipments, options)) do |response|
54
+ [response.body[:validate_shipment_response][:validation_state]].flatten.map do |validation_state|
55
+ validation_state[:status]
56
+ end
57
+ end
58
+ end
59
+
60
+ def create_shipment_order(*shipments, **options)
61
+ request(:create_shipment_order, build_shipment_orders(shipments, options)) do |response|
62
+ [response.body[:create_shipment_order_response][:creation_state]].flatten.map do |creation_state|
63
+ creation_state[:label_data]
64
+ end
65
+ end
66
+ end
67
+
68
+ def update_shipment_order(shipment_number, shipment, **options)
69
+ request(:update_shipment_order, { 'cis:shipmentNumber' => shipment_number }.merge(build_shipment_orders([shipment], options))) do |response|
70
+ clean_response_data(response.body[:update_shipment_order_response][:label_data])
71
+ end
72
+ end
73
+
74
+ {
75
+ delete_shipment_order: :deletion_state,
76
+ get_label: :label_data,
77
+ get_export_doc: :export_doc_data,
78
+ do_manifest: :manifest_state
79
+ }.each do |api_method, response_key|
80
+ define_method api_method do |*shipment_numbers|
81
+ raise Dhl::Bcs::DataError, 'No more than 30 shipment_numbers allowed per request!' if shipment_numbers.size > 30
82
+ request(api_method, 'cis:shipmentNumber' => shipment_numbers) do |response|
83
+ h = {}
84
+ [response.body[:"#{api_method}_response"][response_key]].flatten.each do |data|
85
+ h[data.delete(:shipment_number)] = clean_response_data(data)
86
+ end
87
+ h
88
+ end
89
+ end
90
+ end
91
+
92
+ # returns base64 encoded PDF Dokument
93
+ def get_manifest(date)
94
+ request(:get_manifest, 'manifestDate' => date) do |response|
95
+ response.body[:get_manifest_response][:manifest_data]
96
+ end
97
+ end
98
+
99
+ def last_log
100
+ @logIO.string
101
+ end
102
+
103
+ protected
104
+
105
+ def build_shipment_orders(shipments, label_response_type: 'URL')
106
+ raise Dhl::Bcs::DataError, 'No more than 30 shipments allowed per request!' if shipments.size > 30
107
+ {
108
+ 'ShipmentOrder' =>
109
+ shipments.map.with_index(1) do |shipment, index|
110
+ {
111
+ 'sequenceNumber' => format('%02i', index.to_s),
112
+ 'Shipment' => shipment.to_soap_hash(@ekp, @participation_number),
113
+ 'LabelResponseType' => label_response_type.to_s.upcase
114
+ }
115
+ end
116
+ }
117
+ end
118
+
119
+ def request(action, message = {})
120
+ @logIO.string = ''
121
+ response = @client.call(action, message: {
122
+ 'bcs:Version' => {
123
+ 'majorRelease' => 2,
124
+ 'minorRelease' => 0
125
+ }
126
+ }.merge(message))
127
+ @logger << @logIO.string if @logger
128
+ yield response
129
+ rescue
130
+ raise Dhl::Bcs::RequestError, @logIO.string
131
+ end
132
+
133
+ def clean_response_data(data)
134
+ data.delete(:@xmlns)
135
+ data
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,18 @@
1
+ module Dhl::Bcs::V2
2
+ class Communication
3
+
4
+ include Buildable
5
+
6
+ PROPERTIES = %i(phone email contact_person).freeze
7
+ attr_accessor(*PROPERTIES)
8
+
9
+ def to_soap_hash
10
+ {}.tap do |h|
11
+ h['cis:phone'] = phone if phone
12
+ h['cis:email'] = email if email
13
+ h['cis:contactPerson'] = contact_person if contact_person
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Dhl::Bcs::V2
2
+ # a location is one of Address, Packstation, Postfiliale or ParcelShop
3
+ # they have some properties in common
4
+ class Location
5
+
6
+ include Buildable
7
+
8
+ PROPERTIES = %i(zip city country country_code state).freeze
9
+ attr_accessor(*PROPERTIES)
10
+
11
+ def to_soap_hash
12
+ {
13
+ 'cis:zip' => zip,
14
+ 'cis:city' => city,
15
+ 'cis:Origin' => {}.tap { |h|
16
+ h['cis:country'] = country if country
17
+ h['cis:countryISOCode'] = country_code if country_code
18
+ h['cis:state'] = state if state
19
+ }
20
+ }
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Dhl::Bcs::V2
2
+ class Packstation < Location
3
+
4
+ ADD_PROPS = %i(post_number packstation_number).freeze
5
+ PROPERTIES = Location::PROPERTIES + ADD_PROPS
6
+ attr_accessor(*ADD_PROPS)
7
+
8
+ def to_soap_hash
9
+ h = {}
10
+ h['postNumber'] = post_number if post_number
11
+ h['packstationNumber'] = packstation_number
12
+ h.merge(super)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Dhl::Bcs::V2
2
+ class ParcelShop < Location
3
+
4
+ ADD_PROPS = %i(parcel_shop_number street_name street_number).freeze
5
+ PROPERTIES = Location::PROPERTIES + ADD_PROPS
6
+ attr_accessor(*ADD_PROPS)
7
+
8
+ def to_soap_hash
9
+ h = {}
10
+ h['parcelShopNumber'] = parcel_shop_number
11
+ h['cis:streetName'] = street_name if street_name
12
+ h['cis:streetNumber'] = street_number if street_number
13
+ h.merge(super)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Dhl::Bcs::V2
2
+ class Postfiliale < Location
3
+
4
+ ADD_PROPS = %i(postfilial_number post_number).freeze
5
+ PROPERTIES = Location::PROPERTIES + ADD_PROPS
6
+ attr_accessor(*ADD_PROPS)
7
+
8
+ def to_soap_hash
9
+ h = {}
10
+ h['postfilialNumber'] = postfilial_number
11
+ h['postNumber'] = post_number
12
+ h.merge(super)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Dhl::Bcs::V2
2
+ class Receiver
3
+
4
+ # a location is one of Address, Packstation, Postfiliale or ParcelShop
5
+ PROPERTIES = %i(name communication location).freeze
6
+
7
+ attr_accessor(*PROPERTIES)
8
+
9
+ def self.build(name: nil, **attributes)
10
+ communication = Communication.build(attributes)
11
+ location =
12
+ if attributes.key?(:packstation_number)
13
+ Packstation
14
+ elsif attributes.key?(:postfilial_number)
15
+ Postfiliale
16
+ elsif attributes.key?(:parcel_shop_number)
17
+ ParcelShop
18
+ else
19
+ Address
20
+ end.build(attributes)
21
+ new(attributes.merge(name: name, communication: communication, location: location))
22
+ end
23
+
24
+ def initialize(**attributes)
25
+ attributes.each do |property, value|
26
+ send("#{property}=", value) if PROPERTIES.include?(property)
27
+ end
28
+ end
29
+
30
+ def to_soap_hash
31
+ {
32
+ 'cis:name1' => name,
33
+ 'Communication' => communication.to_soap_hash,
34
+ location.class.name.split('::').last => location.to_soap_hash
35
+ }
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ module Dhl::Bcs::V2
2
+ class Service
3
+
4
+ include Buildable
5
+
6
+ PROPERTIES = %i(name attributes children).freeze
7
+ attr_accessor(*PROPERTIES)
8
+
9
+ def to_soap_hash
10
+ # make a self closing tag by appending "/" if no children
11
+ tag_name = children ? name : "#{name}/"
12
+ { tag_name => children, attributes!: { tag_name => attributes } }
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,83 @@
1
+ module Dhl::Bcs::V2
2
+ class Shipment
3
+
4
+ attr_accessor :shipper, :receiver, :bank_data, :shipment_date, :product,
5
+ :customer_reference, :services, :weight, :length, :height, :width,
6
+ :notification_email
7
+
8
+ PRODUCT_PROCEDURE_NUMBERS = {
9
+ 'V01PAK' => '01', # Paket National
10
+ 'V53WPAK' => '53', # Weltpaket
11
+ 'V54EPAK' => '54', # Europaket
12
+ 'V06TG' => '01', # Kurier Taggleich
13
+ 'V06WZ' => '01', # Kurier Wunschzeit
14
+ 'V86PARCEL' => '86', # DHL Paket Austria
15
+ 'V87PARCEL' => '87', # DHL PAKET Connect
16
+ 'V82PARCEL' => '82' # DHL PAKET International
17
+ }.freeze
18
+ PRODUCTS = PRODUCT_PROCEDURE_NUMBERS.keys.freeze
19
+
20
+ # build a shipment from hash data
21
+ def self.build(shipper:, receiver:, bank_data: nil, **shipment_attributes)
22
+ shipper = Shipper.build(shipper) if shipper.is_a?(Hash)
23
+ receiver = Receiver.build(receiver) if receiver.is_a?(Hash)
24
+ bank_data = BankData.build(bank_data) if bank_data.is_a?(Hash)
25
+ new({ shipper: shipper, receiver: receiver, bank_data: bank_data }.merge(shipment_attributes))
26
+ end
27
+
28
+ def initialize(attributes = {})
29
+ assign_attributes(
30
+ {
31
+ product: 'V01PAK',
32
+ shipment_date: Date.today,
33
+ services: []
34
+ }.merge(attributes)
35
+ )
36
+ end
37
+
38
+ def assign_attributes(attributes)
39
+ attributes.each do |key, value|
40
+ setter = :"#{key}="
41
+ send(setter, value) if respond_to?(setter)
42
+ end
43
+ end
44
+
45
+ def product=(product)
46
+ raise Error, "No valid product code #{product}. Please use one of these: #{PRODUCTS.join(', ')}" unless PRODUCTS.include?(product)
47
+ @product = product
48
+ end
49
+
50
+ def to_soap_hash(ekp, participation_number)
51
+ raise Error, 'Packing weight in kilo must be set!' unless weight
52
+ raise Error, 'Sender address must be set!' unless shipper
53
+ raise Error, 'Receiver address must be set!' unless receiver
54
+ raise Error, 'Product must be set!' unless product
55
+
56
+ account_number = "#{ekp}#{PRODUCT_PROCEDURE_NUMBERS[product]}#{participation_number}"
57
+ raise Error, 'Need a 14 character long account number. Check EKP and participation_number' if account_number.size != 14
58
+ {
59
+ 'ShipmentDetails' => {}.tap { |h|
60
+ h['product'] = product
61
+ h['shipmentDate'] = shipment_date.strftime("%Y-%m-%d")
62
+ h['cis:accountNumber'] = account_number
63
+ h['customerReference'] = customer_reference if !customer_reference.blank?
64
+
65
+ # just one ShipmentItem possible
66
+ h['ShipmentItem'] = { 'weightInKG' => weight }.tap { |si|
67
+ si['lengthInCM'] = length if length
68
+ si['widthInCM'] = width if width
69
+ si['heightInCM'] = height if height
70
+ }
71
+ h['Service'] = services.map(&:to_soap_hash) unless services.empty?
72
+ h['Notification'] = { 'recipientEmailAddress' => notification_email } if notification_email
73
+ h['BankData'] = bank_data.to_soap_hash if bank_data
74
+ },
75
+ # Shipper information
76
+ 'Shipper' => shipper.to_soap_hash,
77
+ # Receiver information
78
+ 'Receiver' => receiver.to_soap_hash
79
+ }
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,32 @@
1
+ module Dhl::Bcs::V2
2
+ class Shipper
3
+
4
+ PROPERTIES = %i(name company company_addition address communication).freeze
5
+
6
+ attr_accessor(*PROPERTIES)
7
+
8
+ def self.build(company: nil, **attributes)
9
+ address = Address.build(attributes)
10
+ communication = Communication.build(attributes)
11
+ new(attributes.merge(address: address, communication: communication, company: company))
12
+ end
13
+
14
+ def initialize(**attributes)
15
+ attributes.each do |property, value|
16
+ send("#{property}=", value) if PROPERTIES.include?(property)
17
+ end
18
+ end
19
+
20
+ def to_soap_hash
21
+ {
22
+ 'Name' => { 'cis:name1' => name }.tap { |h|
23
+ h['cis:name2'] = company if company
24
+ h['cis:name3'] = company_addition if company_addition
25
+ },
26
+ 'Address' => address.to_soap_hash,
27
+ 'Communication' => communication.to_soap_hash
28
+ }
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module Dhl
2
+ module Bcs
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dhl-bcs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Christoph Wagner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: savon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.4'
97
+ description: This is inspired by the dhl-intraship gem that is a little bit outdated
98
+ and doesn't support the new DHL API. If you need DHL Express Services this is not
99
+ for you.
100
+ email:
101
+ - wagner@webit.de
102
+ executables:
103
+ - console
104
+ - setup
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - ".gitignore"
109
+ - ".travis.yml"
110
+ - CODE_OF_CONDUCT.md
111
+ - Gemfile
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - bin/console
116
+ - bin/setup
117
+ - dhl-bcs.gemspec
118
+ - lib/dhl/bcs.rb
119
+ - lib/dhl/bcs/errors.rb
120
+ - lib/dhl/bcs/v2/address.rb
121
+ - lib/dhl/bcs/v2/bank_data.rb
122
+ - lib/dhl/bcs/v2/buildable.rb
123
+ - lib/dhl/bcs/v2/client.rb
124
+ - lib/dhl/bcs/v2/communication.rb
125
+ - lib/dhl/bcs/v2/location.rb
126
+ - lib/dhl/bcs/v2/packstation.rb
127
+ - lib/dhl/bcs/v2/parcel_shop.rb
128
+ - lib/dhl/bcs/v2/postfiliale.rb
129
+ - lib/dhl/bcs/v2/receiver.rb
130
+ - lib/dhl/bcs/v2/service.rb
131
+ - lib/dhl/bcs/v2/shipment.rb
132
+ - lib/dhl/bcs/v2/shipper.rb
133
+ - lib/dhl/bcs/version.rb
134
+ homepage: https://github.com/webit-de/dhl-bcs
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.5.1
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Client for DHL Business-Customer-Shipping SOAP API 2.0
158
+ test_files: []