iloxx_shipping 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.
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +82 -0
- data/Rakefile +10 -0
- data/iloxx_shipping.gemspec +24 -0
- data/lib/iloxx_shipping.rb +17 -0
- data/lib/iloxx_shipping/address.rb +48 -0
- data/lib/iloxx_shipping/api.rb +146 -0
- data/lib/iloxx_shipping/daily_transaction_request.rb +19 -0
- data/lib/iloxx_shipping/order_request.rb +33 -0
- data/lib/iloxx_shipping/parcel.rb +31 -0
- data/lib/iloxx_shipping/request.rb +40 -0
- data/lib/iloxx_shipping/service.rb +21 -0
- data/lib/iloxx_shipping/service/express.rb +24 -0
- data/lib/iloxx_shipping/service/normalpaket.rb +27 -0
- data/lib/iloxx_shipping/service/retoure.rb +20 -0
- data/lib/iloxx_shipping/version.rb +5 -0
- data/spec/fixtures/address/simple.xml +1 -0
- data/spec/fixtures/address/with_company.xml +1 -0
- data/spec/fixtures/api/bad_auth_request.xml +1 -0
- data/spec/fixtures/api/bad_auth_response.xml +17 -0
- data/spec/fixtures/api/request.xml +1 -0
- data/spec/fixtures/api/response.xml +17 -0
- data/spec/fixtures/order_request/simple.xml +1 -0
- data/spec/fixtures/parcel/simple.xml +1 -0
- data/spec/fixtures/wsdl_curl.txt +310 -0
- data/spec/iloxx_shipping/address_spec.rb +52 -0
- data/spec/iloxx_shipping/api_spec.rb +71 -0
- data/spec/iloxx_shipping/order_request_spec.rb +46 -0
- data/spec/iloxx_shipping/parcel_spec.rb +32 -0
- data/spec/spec_helper.rb +36 -0
- metadata +142 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Iloxx::Shipping
|
2
|
+
|
3
|
+
This gem is a wrapper around the SOAP-based API provided by [iloxx](http://iloxx.de). It allows you to build and send a shipment request and return a shipping label and the parcel numbers.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'iloxx_shipping'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install iloxx_shipping
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
1. Create a new receiver address:
|
22
|
+
|
23
|
+
receiver = Iloxx::Shipping::Address.new({
|
24
|
+
:name => "Lilly Lox",
|
25
|
+
:street => "Gutenstetter Str. 8b",
|
26
|
+
:zip => "90449",
|
27
|
+
:city => "Nürnberg",
|
28
|
+
:country_code => "DE"
|
29
|
+
})
|
30
|
+
|
31
|
+
2. Create a new parcel
|
32
|
+
|
33
|
+
parcel = Iloxx::Shipping::Parcel.new(
|
34
|
+
weight: 1.25,
|
35
|
+
content: "Stones",
|
36
|
+
address: receiver,
|
37
|
+
service: Iloxx::Shipping::NormalpaketService.new,
|
38
|
+
# ^- or use ExpressService or RetoureService (with parameters) instead
|
39
|
+
reference: "Order #1234",
|
40
|
+
)
|
41
|
+
|
42
|
+
3. Submit your parcel
|
43
|
+
|
44
|
+
config = {
|
45
|
+
user: "Your Iloxx User ID",
|
46
|
+
token: "Your Iloxx User Token",
|
47
|
+
}
|
48
|
+
api = Iloxx::Shipping::API.new(config, {
|
49
|
+
test: true # If test is set, all API calls go against the test system
|
50
|
+
})
|
51
|
+
shipment_date = Date.today
|
52
|
+
result = api.add_order(parcel, shipment_date)
|
53
|
+
|
54
|
+
|
55
|
+
4. Receive the parcel number and label pdf
|
56
|
+
|
57
|
+
result[:shipments].each do |shipment|
|
58
|
+
p shipment[:parcel_number]
|
59
|
+
end
|
60
|
+
|
61
|
+
# base64 encoded label.pdf in result[:label_data] - lets save it to disk:
|
62
|
+
|
63
|
+
f = File.open('label.pdf', 'w')
|
64
|
+
begin
|
65
|
+
f.write Base64.decode64(result[:label_data])
|
66
|
+
ensure
|
67
|
+
f.close unless f.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
77
|
+
5. Create new Pull Request
|
78
|
+
|
79
|
+
## Thanks
|
80
|
+
|
81
|
+
* iloxx AG for providing the documentation for their SOAP API
|
82
|
+
* [savon](http://github.com/savonrb) for a nice soap abstraction library
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iloxx_shipping/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "iloxx_shipping"
|
8
|
+
gem.version = Iloxx::Shipping::VERSION
|
9
|
+
gem.authors = ["Maximilian Richt"]
|
10
|
+
gem.email = ["maxi@richt.name"]
|
11
|
+
gem.description = %q{A simple way to access the iloxx shipping API}
|
12
|
+
gem.summary = %q{A wrapper around the SOAP-based iloxx shipping web service. Generate a shipping request and get a label back.}
|
13
|
+
gem.homepage = "https://github.com/robbi5/iloxx_shipping"
|
14
|
+
gem.license = 'MIT'
|
15
|
+
|
16
|
+
gem.add_dependency "savon", "~> 2.2.0"
|
17
|
+
gem.add_development_dependency "rspec", "~> 2.11.0"
|
18
|
+
gem.add_development_dependency "webmock", "~> 1.13.0"
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($/)
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
23
|
+
gem.require_paths = ["lib"]
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'savon'
|
3
|
+
|
4
|
+
require 'iloxx_shipping/version'
|
5
|
+
require 'iloxx_shipping/address'
|
6
|
+
require 'iloxx_shipping/request'
|
7
|
+
# requests
|
8
|
+
require 'iloxx_shipping/order_request'
|
9
|
+
require 'iloxx_shipping/daily_transaction_request'
|
10
|
+
require 'iloxx_shipping/parcel'
|
11
|
+
require 'iloxx_shipping/service'
|
12
|
+
# services
|
13
|
+
require 'iloxx_shipping/service/express'
|
14
|
+
require 'iloxx_shipping/service/normalpaket'
|
15
|
+
require 'iloxx_shipping/service/retoure'
|
16
|
+
# and the last one:
|
17
|
+
require 'iloxx_shipping/api'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Iloxx
|
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 Iloxx
|
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 Iloxx
|
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 Iloxx
|
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Iloxx
|
2
|
+
module Shipping
|
3
|
+
class Parcel
|
4
|
+
attr_accessor :internal_reference, :customer, :reference, :content, :weight, :service, :address, :track_url
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
attributes.each do |key, value|
|
8
|
+
setter = :"#{key.to_s}="
|
9
|
+
if self.respond_to?(setter)
|
10
|
+
self.send(setter, value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def append_to_xml(xml)
|
16
|
+
xml.tns(:ppvOrderData) do |xml|
|
17
|
+
xml.tns(:PartnerOrderReference, internal_reference || " ")
|
18
|
+
xml.tns(:Customer, customer || " ")
|
19
|
+
xml.tns(:Reference, reference || " ")
|
20
|
+
xml.tns(:Content, content)
|
21
|
+
xml.tns(:Weight, weight)
|
22
|
+
xml.tns(:ShipService, service.service_type)
|
23
|
+
xml.tns(:CODAmount, service.cod_amount || 0)
|
24
|
+
address.append_to_xml(xml)
|
25
|
+
xml.tns(:TrackURL, track_url || "")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|