dpd_shipping 1.0.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: ab251ad9ae5730c25632dfc242e5f306ba6b6c4c
4
+ data.tar.gz: a7833a13b3de6d0ea9a13814f6a1cbea100debbf
5
+ SHA512:
6
+ metadata.gz: d4badac2ba1819b6842ad019fd56c02f4423399973ab54d4d0980a0c855a1701cbe70580fd740770d39ee950a58bc03c80bbd1d460b9308b83cf100dc8e29764
7
+ data.tar.gz: 9f0c4b8ea8500a644d29a16f8f468dadde4fa0081d05352cb137ede5da80b98667275a87885885ef30299fe8b2cc723ef7159785521921cbd07e2292f01e1103
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ --backtrace
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dpd_shipping.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Maximilian Richt (robbi5)
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,84 @@
1
+ # Dpd::Shipping
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/dpd_shipping.svg)](http://rubygems.org/gems/dpd_shipping) [![Build Status](https://travis-ci.org/robbi5/dpd_shipping.svg)](https://travis-ci.org/robbi5/dpd_shipping)
4
+
5
+ This gem is a wrapper around the SOAP-based API provided by [iloxx](http://iloxx.de). It works both for DPD and iloxx customers. It allows you to build and send a shipment request and return a shipping label and the parcel numbers. The gem was formerly known as iloxx_shipping.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'dpd_shipping'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install dpd_shipping
20
+
21
+ ## Usage
22
+
23
+ 1. Create a new receiver address:
24
+
25
+ receiver = Dpd::Shipping::Address.new({
26
+ :name => "Lilly Lox",
27
+ :street => "Gutenstetter Str. 8b",
28
+ :zip => "90449",
29
+ :city => "Nürnberg",
30
+ :country_code => "DE"
31
+ })
32
+
33
+ 2. Create a new parcel
34
+
35
+ parcel = Dpd::Shipping::Parcel.new(
36
+ weight: 1.25,
37
+ content: "Stones",
38
+ address: receiver,
39
+ service: Dpd::Shipping::NormalpaketService.new,
40
+ # ^- or use ExpressService or RetoureService (with parameters) instead
41
+ reference: "Order #1234",
42
+ )
43
+
44
+ 3. Submit your parcel
45
+
46
+ config = {
47
+ user: "Your DPD/iloxx User ID",
48
+ token: "Your DPD/iloxx User Token",
49
+ }
50
+ api = Dpd::Shipping::API.new(config, {
51
+ test: true # If test is set, all API calls go against the test system
52
+ })
53
+ shipment_date = Date.today
54
+ result = api.add_order(parcel, shipment_date)
55
+
56
+
57
+ 4. Receive the parcel number and label pdf
58
+
59
+ result[:shipments].each do |shipment|
60
+ p shipment[:parcel_number]
61
+ end
62
+
63
+ # base64 encoded label.pdf in result[:label_data] - lets save it to disk:
64
+
65
+ f = File.open('label.pdf', 'w')
66
+ begin
67
+ f.write Base64.decode64(result[:label_data])
68
+ ensure
69
+ f.close unless f.nil?
70
+ end
71
+
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
80
+
81
+ ## Thanks
82
+
83
+ * iloxx AG for providing the documentation for their SOAP API
84
+ * [savon](http://github.com/savonrb) for a nice soap abstraction library
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ spec.pattern = 'spec/dpd_shipping/*_spec.rb'
6
+ spec.rspec_opts = ['--backtrace']
7
+ end
8
+
9
+ task :default => :spec
10
+ task :test => :spec
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dpd_shipping/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dpd_shipping"
8
+ gem.version = Dpd::Shipping::VERSION
9
+ gem.authors = ["Maximilian Richt"]
10
+ gem.email = ["maxi@richt.name"]
11
+ gem.description = %q{A simple way to access the dpd/iloxx shipping API}
12
+ gem.summary = %q{A wrapper around the SOAP-based dpd/iloxx shipping web service. Generate a shipping request and get a label back. Formerly known as iloxx_shipping.}
13
+ gem.homepage = "https://github.com/robbi5/dpd_shipping"
14
+ gem.license = 'MIT'
15
+
16
+ gem.add_dependency "savon", "~> 2.2.0"
17
+ gem.add_development_dependency "rake"
18
+ gem.add_development_dependency "rspec", "~> 2.11.0"
19
+ gem.add_development_dependency "webmock", "~> 1.13.0"
20
+
21
+ gem.files = `git ls-files`.split($/)
22
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
+ gem.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,17 @@
1
+ require 'date'
2
+ require 'savon'
3
+
4
+ require 'dpd_shipping/version'
5
+ require 'dpd_shipping/address'
6
+ require 'dpd_shipping/request'
7
+ # requests
8
+ require 'dpd_shipping/order_request'
9
+ require 'dpd_shipping/daily_transaction_request'
10
+ require 'dpd_shipping/parcel'
11
+ require 'dpd_shipping/service'
12
+ # services
13
+ require 'dpd_shipping/service/express'
14
+ require 'dpd_shipping/service/normalpaket'
15
+ require 'dpd_shipping/service/retoure'
16
+ # and the last one:
17
+ require 'dpd_shipping/api'
@@ -0,0 +1,48 @@
1
+ module Dpd
2
+ module Shipping
3
+ class Address
4
+ attr_accessor :company, :sex, :name, :street, :zip, :city, :state, :country_code, :email, :phone
5
+
6
+ VALID_SEX = [:NoSexCode, :Male, :Female]
7
+
8
+ def initialize(attributes = {})
9
+ attributes.each do |key, value|
10
+ setter = :"#{key.to_s}="
11
+ if self.respond_to?(setter)
12
+ self.send(setter, value)
13
+ end
14
+ end
15
+ end
16
+
17
+ def company?
18
+ !self.company.blank?
19
+ end
20
+
21
+ def sex=(sex)
22
+ raise "Sex must be one of the following: #{VALID_SEX.to_s}" unless VALID_SEX.include? sex
23
+ @sex = sex
24
+ end
25
+
26
+ def country_code=(country_code)
27
+ raise "Country code must be an ISO-3166 two digit code" unless country_code.length == 2
28
+ @country_code = country_code
29
+ end
30
+
31
+ def append_to_xml(xml)
32
+ xml.tns :ShipAddress do |xml|
33
+ xml.tns :Company, company if company?
34
+ xml.tns :SexCode, sex || :NoSexCode
35
+ xml.tns :Name, name
36
+ xml.tns :Street, street
37
+ xml.tns :ZipCode, zip
38
+ xml.tns :City, city
39
+ xml.tns :State, state || ""
40
+ xml.tns :Country, country_code
41
+ xml.tns :Phone, phone || ""
42
+ xml.tns :Mail, email || ""
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,146 @@
1
+ # encoding: UTF-8
2
+ module Dpd
3
+ module Shipping
4
+ class PartnerAuthenticationError < StandardError; end
5
+ class AuthenticationError < StandardError; end
6
+ class ShippingDateError < StandardError; end
7
+
8
+ class APIErrorCodes
9
+ PARTNER_AUTH_ERROR = 3 # Ungültige Zugangsdaten (PartnerCredentials).
10
+ AUTH_ERROR = 4 # Ungültige Zugangsdaten oder Nutzung des Services ist nicht freigegeben.
11
+ end
12
+
13
+ class API
14
+ DEFAULT_NAMESPACES = {
15
+ "xmlns:iloxx" => "http://iloxx.de/"
16
+ }
17
+
18
+ PPVAPI_WSDL = "https://www.iloxx.de/iloxxapi/ppvapi.asmx?WSDL"
19
+ PPVAPI_ENDPOINT = "https://www.iloxx.de/iloxxapi/ppvapi.asmx"
20
+
21
+ PPVAPI_TEST_WSDL = "http://qa.www.iloxx.de/iloxxapi/ppvapi.asmx?WSDL"
22
+ PPVAPI_TEST_ENDPOINT = "http://qa.www.iloxx.de/iloxxapi/ppvapi.asmx"
23
+
24
+ API_VERSION = 102
25
+
26
+ def initialize(config, options = {})
27
+ raise "User ID must be specified" if config[:user].nil?
28
+ raise "User Token must be specified" if config[:token].nil?
29
+
30
+ if options[:test]
31
+ wsdl_url = PPVAPI_TEST_WSDL
32
+ endpoint = PPVAPI_TEST_ENDPOINT
33
+ else
34
+ wsdl_url = PPVAPI_WSDL
35
+ endpoint = PPVAPI_ENDPOINT
36
+ end
37
+
38
+ @user = config[:user]
39
+ @token = config[:token]
40
+ @partner_name = config[:partner_name] || 'iloxx.24'
41
+ @partner_key = config[:partner_key] || '554F346F592B42757131367A64477A7A676362767A413D3D'
42
+
43
+ @options = options
44
+ @client = ::Savon::Client.new do |sc|
45
+ sc.wsdl wsdl_url
46
+ sc.endpoint endpoint
47
+ sc.namespace DEFAULT_NAMESPACES['xmlns:iloxx']
48
+ sc.soap_version 2
49
+ sc.filters [:LabelPDFStream, :UserToken]
50
+ end
51
+ end
52
+
53
+ def add_order(parcels, date = nil)
54
+ shipping_date = date || Date.today
55
+
56
+ if shipping_date > Date.today + 7
57
+ raise ShippingDateError.new('Invalid shipping date. Min: today, max: +7 days')
58
+ end
59
+
60
+ if !parcels.is_a? Array
61
+ parcels = [parcels]
62
+ end
63
+ parcels.each do |p|
64
+ p.internal_reference = parcels.index p
65
+ end
66
+
67
+ request = OrderRequest.new(
68
+ :version => API_VERSION,
69
+ :auth => auth_hash,
70
+ :shipping_date => shipping_date,
71
+ :parcels => parcels
72
+ )
73
+ response = @client.call(:ppv_add_order, message: request.build!)
74
+
75
+ result = response.body[:ppv_add_order_response][:ppv_add_order_result]
76
+ if result[:ack] == 'Success'
77
+ shipments = []
78
+ if !result[:response_data_array].is_a? Array
79
+ result[:response_data_array] = [result[:response_data_array][:response_data]]
80
+ end
81
+ result[:response_data_array].each { |rdata| shipments << rdata }
82
+ {
83
+ :label_data => result[:label_pdf_stream],
84
+ :shipments => shipments
85
+ }
86
+ else
87
+ handle_errors result
88
+ end
89
+ end
90
+
91
+ def get_daily_transaction_list(date = nil)
92
+ shipping_date = date || Date.today
93
+
94
+ request = DailyTransactionRequest.new(
95
+ :version => API_VERSION,
96
+ :auth => auth_hash,
97
+ :date => shipping_date,
98
+ :type => :DPD
99
+ )
100
+
101
+ response = @client.call(:ppv_get_daily_transaction_list, message: request.build!)
102
+ result = response.body[:ppv_get_daily_transaction_list_response][:ppv_add_order_result]
103
+ if result[:DailyTransactionResponse] == 'Success'
104
+ {
105
+ :transaction_list => result[:transaction_list_pdf_stream]
106
+ }
107
+ else
108
+ handle_errors result
109
+ end
110
+ end
111
+
112
+ protected
113
+
114
+ def auth_hash
115
+ {
116
+ :partner => {
117
+ :name => @partner_name,
118
+ :key => @partner_key
119
+ },
120
+ :user => {
121
+ :id => @user,
122
+ :token => @token
123
+ }
124
+ }
125
+ end
126
+
127
+ def handle_errors(result)
128
+ errors = result[:error_data_array][:error_data]
129
+ errors = [errors] unless errors.is_a? Array
130
+
131
+ errors.each do |err|
132
+ id = err[:error_id].to_i
133
+ msg = " " + (err[:error_msg] || "")
134
+ case id
135
+ when APIErrorCodes::PARTNER_AUTH_ERROR
136
+ raise PartnerAuthenticationError.new('Failed to authenticate partner.' + msg)
137
+ when APIErrorCodes::AUTH_ERROR
138
+ raise AuthenticationError.new('Failed to authenticate.' + msg)
139
+ else
140
+ raise "Iloxx API call failed: ##{id}: #{msg}"
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,19 @@
1
+ module Dpd
2
+ module Shipping
3
+ class DailyTransactionRequest < Request
4
+
5
+ REQUEST_TYPE = :ppvDailyTransactionRequest
6
+
7
+ def initialize(attributes = {})
8
+ super(attributes)
9
+ @date = attributes[:date]
10
+ @type = attributes[:type]
11
+ end
12
+
13
+ def body(xml)
14
+ xml.tns :TransactionListDate, @date.strftime("%d.%m.%Y")
15
+ xml.tns :TransactionListType, @type
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module Dpd
2
+ module Shipping
3
+ class OrderRequest < Request
4
+
5
+ REQUEST_TYPE = :ppvOrderRequest
6
+
7
+ def initialize(attributes = {})
8
+ super(attributes)
9
+ @shipping_date = attributes[:shipping_date]
10
+ @parcels = attributes[:parcels]
11
+ end
12
+
13
+ def body(xml)
14
+ xml.tns :OrderAction, "addOrder"
15
+ xml.tns :ServiceSettings do |xml|
16
+ xml.tns :ErrorLanguage, "German" # TODO: make configurable
17
+ xml.tns :CountrySettings, "ISO3166"
18
+ xml.tns :ZipCodeSetting, "ZipCodeAsSingleValue"
19
+ end
20
+ xml.tns :OrderLabel do |xml|
21
+ xml.tns :LabelSize, "MultiLabel_A4" # TODO: make configurable
22
+ xml.tns :LabelStartPosition, "UpperLeft"
23
+ end
24
+ xml.tns :ShipDate, @shipping_date.strftime("%d.%m.%Y")
25
+ xml.tns :ppvOrderDataArray do |xml|
26
+ @parcels.each do |parcel|
27
+ parcel.append_to_xml(xml)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end