active_shipping 0.1.4 → 0.9.1
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/CHANGELOG +2 -0
- data/README.markdown +0 -3
- data/lib/active_merchant/common.rb +14 -0
- data/lib/{active_shipping/lib → active_merchant/common}/connection.rb +2 -0
- data/lib/{active_shipping/lib → active_merchant/common}/country.rb +0 -0
- data/lib/active_merchant/common/error.rb +26 -0
- data/lib/active_merchant/common/post_data.rb +24 -0
- data/lib/{active_shipping/lib → active_merchant/common}/posts_data.rb +0 -0
- data/lib/{active_shipping/lib → active_merchant/common}/requires_parameters.rb +0 -0
- data/lib/{active_shipping/lib → active_merchant/common}/utils.rb +0 -0
- data/lib/{active_shipping/lib → active_merchant/common}/validateable.rb +0 -0
- data/lib/active_shipping.rb +10 -22
- data/lib/active_shipping/shipping/base.rb +2 -1
- data/lib/active_shipping/shipping/carrier.rb +0 -5
- data/lib/active_shipping/shipping/carriers.rb +2 -1
- data/lib/active_shipping/shipping/carriers/fedex.rb +7 -191
- data/lib/active_shipping/shipping/carriers/kunaki.rb +165 -0
- data/lib/active_shipping/shipping/carriers/ups.rb +2 -1
- data/lib/active_shipping/shipping/carriers/usps.rb +0 -85
- data/lib/active_shipping/shipping/location.rb +1 -4
- data/lib/active_shipping/shipping/response.rb +1 -4
- data/lib/active_shipping/version.rb +3 -0
- data/lib/vendor/quantified/lib/quantified.rb +5 -3
- metadata +68 -102
- data/.gitignore +0 -8
- data/Rakefile +0 -51
- data/VERSION +0 -1
- data/active_shipping.gemspec +0 -152
- data/init.rb +0 -1
- data/lib/active_shipping/lib/error.rb +0 -4
- data/lib/active_shipping/lib/post_data.rb +0 -22
- data/lib/active_shipping/shipping/contact.rb +0 -18
- data/lib/active_shipping/shipping/label.rb +0 -31
- data/lib/active_shipping/shipping/location_response.rb +0 -14
- data/lib/active_shipping/shipping/party.rb +0 -15
- data/lib/active_shipping/shipping/return_label_response.rb +0 -14
- data/lib/active_shipping/shipping/return_shipment.rb +0 -14
- data/lib/active_shipping/shipping/shipment.rb +0 -73
- data/test/fixtures.example.yml +0 -13
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_request.xml +0 -67
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_response.xml +0 -213
- data/test/fixtures/xml/fedex/tracking_request.xml +0 -27
- data/test/fixtures/xml/fedex/tracking_response.xml +0 -153
- data/test/fixtures/xml/shipwire/international_rates_response.xml +0 -17
- data/test/fixtures/xml/shipwire/invalid_credentials_response.xml +0 -4
- data/test/fixtures/xml/shipwire/new_carrier_rate_response.xml +0 -18
- data/test/fixtures/xml/shipwire/no_rates_response.xml +0 -7
- data/test/fixtures/xml/shipwire/rates_response.xml +0 -36
- data/test/fixtures/xml/ups/example_tracking_response.xml +0 -53
- data/test/fixtures/xml/ups/shipment_from_tiger_direct.xml +0 -222
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +0 -1
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_book_rate_response.xml +0 -85
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_book_wii_rate_response.xml +0 -168
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_wii_rate_response.xml +0 -85
- data/test/fixtures/xml/usps/example_tracking_response.xml +0 -104
- data/test/fixtures/xml/usps/multi_tracking_example.xml +0 -105
- data/test/party_factory.rb +0 -29
- data/test/remote/fedex_test.rb +0 -160
- data/test/remote/shipwire_test.rb +0 -88
- data/test/remote/ups_test.rb +0 -207
- data/test/remote/usps_test.rb +0 -184
- data/test/shipment_factory.rb +0 -27
- data/test/test_helper.rb +0 -171
- data/test/unit/base_test.rb +0 -18
- data/test/unit/carriers/fedex_test.rb +0 -78
- data/test/unit/carriers/shipwire_test.rb +0 -130
- data/test/unit/carriers/ups_test.rb +0 -81
- data/test/unit/carriers/usps_test.rb +0 -206
- data/test/unit/location_test.rb +0 -46
- data/test/unit/package_test.rb +0 -65
- data/test/unit/party_test.rb +0 -20
- data/test/unit/response_test.rb +0 -10
- data/test/unit/shipment_test.rb +0 -43
data/CHANGELOG
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
* Assume test_helper is in load path when running tests [cody]
|
2
|
+
* Add support Kunaki rating service [cody]
|
1
3
|
* Require active_support instead of activesupport to avoid deprecation warning in Rails 2.3.5 [cody]
|
2
4
|
* Remove ftools for Rails 1.9 compatibility and remove xml logging, as logging is now included in the connection [cody]
|
3
5
|
* Update connection code from ActiveMerchant [cody]
|
data/README.markdown
CHANGED
@@ -38,8 +38,6 @@ You will need to get [Git][] if you don't have it. Then:
|
|
38
38
|
|
39
39
|
Active Shipping includes an init.rb file. This means that Rails will automatically load it on startup. Check out [git-archive][] for exporting the file tree from your repository to your vendor directory.
|
40
40
|
|
41
|
-
Gem and tarball forthcoming on rubyforge.
|
42
|
-
|
43
41
|
[Git]:http://git.or.cz/
|
44
42
|
[git-archive]:http://www.kernel.org/pub/software/scm/git/docs/git-archive.html
|
45
43
|
|
@@ -144,7 +142,6 @@ Gem and tarball forthcoming on rubyforge.
|
|
144
142
|
|
145
143
|
* proper documentation
|
146
144
|
* proper offline testing for carriers in addition to the remote tests
|
147
|
-
* package into a gem
|
148
145
|
* carrier code template generator
|
149
146
|
* more carriers
|
150
147
|
* integrate with ActiveMerchant
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
autoload :Connection, 'active_merchant/common/connection'
|
3
|
+
autoload :Country, 'active_merchant/common/country'
|
4
|
+
autoload :ActiveMerchantError, 'active_merchant/common/error'
|
5
|
+
autoload :ConnectionError, 'active_merchant/common/error'
|
6
|
+
autoload :RetriableConnectionError, 'active_merchant/common/error'
|
7
|
+
autoload :ResponseError, 'active_merchant/common/error'
|
8
|
+
autoload :ClientCertificateError, 'active_merchant/common/error'
|
9
|
+
autoload :PostData, 'active_merchant/common/post_data'
|
10
|
+
autoload :PostsData, 'active_merchant/common/posts_data'
|
11
|
+
autoload :RequiresParameters, 'active_merchant/common/requires_parameters'
|
12
|
+
autoload :Utils, 'active_merchant/common/utils'
|
13
|
+
autoload :Validateable, 'active_merchant/common/validateable'
|
14
|
+
end
|
@@ -80,6 +80,8 @@ module ActiveMerchant
|
|
80
80
|
raise ConnectionError, "The remote server reset the connection"
|
81
81
|
rescue Errno::ECONNREFUSED => e
|
82
82
|
raise RetriableConnectionError, "The remote server refused the connection"
|
83
|
+
rescue OpenSSL::X509::CertificateError => e
|
84
|
+
raise ClientCertificateError, "The remote server did not accept the provided SSL certificate"
|
83
85
|
rescue Timeout::Error, Errno::ETIMEDOUT => e
|
84
86
|
raise ConnectionError, "The connection to the remote server timed out"
|
85
87
|
end
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
class ActiveMerchantError < StandardError #:nodoc:
|
3
|
+
end
|
4
|
+
|
5
|
+
class ConnectionError < ActiveMerchantError # :nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
class RetriableConnectionError < ConnectionError # :nodoc:
|
9
|
+
end
|
10
|
+
|
11
|
+
class ResponseError < ActiveMerchantError # :nodoc:
|
12
|
+
attr_reader :response
|
13
|
+
|
14
|
+
def initialize(response, message = nil)
|
15
|
+
@response = response
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ClientCertificateError < ActiveMerchantError # :nodoc
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module ActiveMerchant
|
4
|
+
class PostData < Hash
|
5
|
+
class_inheritable_accessor :required_fields, :instance_writer => false
|
6
|
+
self.required_fields = []
|
7
|
+
|
8
|
+
def []=(key, value)
|
9
|
+
return if value.blank? && !required?(key)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_post_data
|
14
|
+
collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :to_s, :to_post_data
|
18
|
+
|
19
|
+
private
|
20
|
+
def required?(key)
|
21
|
+
required_fields.include?(key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/active_shipping.rb
CHANGED
@@ -23,20 +23,19 @@
|
|
23
23
|
|
24
24
|
$:.unshift File.dirname(__FILE__)
|
25
25
|
|
26
|
-
|
27
|
-
require 'active_support'
|
26
|
+
begin
|
27
|
+
require 'active_support/all'
|
28
|
+
rescue LoadError => e
|
29
|
+
require 'rubygems'
|
30
|
+
gem "activesupport", ">= 2.3.5"
|
31
|
+
require "active_support/all"
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
require 'quantified/mass'
|
32
|
-
require 'quantified/length'
|
34
|
+
autoload :XmlNode, 'vendor/xml_node/lib/xml_node'
|
35
|
+
autoload :Quantified, 'vendor/quantified/lib/quantified'
|
33
36
|
|
34
37
|
require 'net/https'
|
35
|
-
require '
|
36
|
-
require 'active_shipping/lib/requires_parameters'
|
37
|
-
require 'active_shipping/lib/connection'
|
38
|
-
require 'active_shipping/lib/posts_data'
|
39
|
-
require 'active_shipping/lib/country'
|
38
|
+
require 'active_merchant/common'
|
40
39
|
|
41
40
|
require 'active_shipping/shipping/base'
|
42
41
|
require 'active_shipping/shipping/response'
|
@@ -45,17 +44,6 @@ require 'active_shipping/shipping/tracking_response'
|
|
45
44
|
require 'active_shipping/shipping/package'
|
46
45
|
require 'active_shipping/shipping/location'
|
47
46
|
require 'active_shipping/shipping/rate_estimate'
|
48
|
-
require 'active_shipping/shipping/location_response'
|
49
|
-
require 'active_shipping/shipping/return_label_response'
|
50
|
-
|
51
|
-
require 'active_shipping/shipping/contact'
|
52
|
-
require 'active_shipping/shipping/party'
|
53
|
-
require 'active_shipping/shipping/label'
|
54
|
-
|
55
|
-
require 'active_shipping/shipping/shipment'
|
56
|
-
require 'active_shipping/shipping/return_shipment'
|
57
|
-
|
58
47
|
require 'active_shipping/shipping/shipment_event'
|
59
48
|
require 'active_shipping/shipping/carrier'
|
60
49
|
require 'active_shipping/shipping/carriers'
|
61
|
-
|
@@ -5,7 +5,8 @@ module ActiveMerchant
|
|
5
5
|
self.mode = :production
|
6
6
|
|
7
7
|
def self.carrier(name)
|
8
|
-
ActiveMerchant::Shipping::Carriers.all.find {|c| c.name.downcase == name}
|
8
|
+
ActiveMerchant::Shipping::Carriers.all.find {|c| c.name.downcase == name.to_s.downcase} ||
|
9
|
+
raise(NameError, "unknown carrier #{name}")
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -3,13 +3,14 @@ require 'active_shipping/shipping/carriers/ups'
|
|
3
3
|
require 'active_shipping/shipping/carriers/usps'
|
4
4
|
require 'active_shipping/shipping/carriers/fedex'
|
5
5
|
require 'active_shipping/shipping/carriers/shipwire'
|
6
|
+
require 'active_shipping/shipping/carriers/kunaki'
|
6
7
|
|
7
8
|
module ActiveMerchant
|
8
9
|
module Shipping
|
9
10
|
module Carriers
|
10
11
|
class <<self
|
11
12
|
def all
|
12
|
-
[BogusCarrier, UPS, USPS, FedEx, Shipwire]
|
13
|
+
[BogusCarrier, UPS, USPS, FedEx, Shipwire, Kunaki]
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
@@ -106,152 +106,8 @@ module ActiveMerchant
|
|
106
106
|
response = commit(save_request(tracking_request), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
|
107
107
|
parse_tracking_response(response, options)
|
108
108
|
end
|
109
|
-
|
110
|
-
def get_return_label(shipment, options = {})
|
111
|
-
req = build_return_label_request(shipment)
|
112
|
-
response = commit(save_request(req), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
|
113
|
-
|
114
|
-
parse_return_label_response(response, shipment)
|
115
|
-
end
|
116
|
-
|
117
|
-
def validate_location(location, options = {})
|
118
|
-
req = build_location_validation_request(location)
|
119
|
-
response = commit(save_request(req), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
|
120
|
-
|
121
|
-
parse_location_validation_request(response, location)
|
122
|
-
end
|
123
|
-
|
109
|
+
|
124
110
|
protected
|
125
|
-
def build_location_validation_request(location)
|
126
|
-
xml_request = XmlNode.new('AddressValidationRequest',
|
127
|
-
'xmlns' => 'http://fedex.com/ws/addressvalidation/v2') do |root_node|
|
128
|
-
|
129
|
-
root_node << build_request_header
|
130
|
-
root_node << XmlNode.new('Version') do |version_node|
|
131
|
-
version_node << XmlNode.new('ServiceId', 'aval')
|
132
|
-
version_node << XmlNode.new('Major', '2')
|
133
|
-
version_node << XmlNode.new('Intermediate', '0')
|
134
|
-
version_node << XmlNode.new('Minor', '0')
|
135
|
-
end
|
136
|
-
|
137
|
-
root_node << XmlNode.new('RequestTimestamp', Time.now)
|
138
|
-
root_node << XmlNode.new('Options', '')
|
139
|
-
root_node << XmlNode.new('AddressesToValidate') do |v|
|
140
|
-
v << build_party_location_node('Address', location)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
xml_request.to_s
|
145
|
-
end
|
146
|
-
|
147
|
-
def parse_location_validation_request(response, location)
|
148
|
-
xml = REXML::Document.new(response)
|
149
|
-
success = response_success?(xml)
|
150
|
-
message = response_message(xml)
|
151
|
-
|
152
|
-
if success
|
153
|
-
parent = xml.elements.first
|
154
|
-
location.valid = parent.elements['//DeliveryPointValidation'][0] == 'CONFIRMED'
|
155
|
-
location.score = parent.elements['//Score'][0].to_s.to_i
|
156
|
-
else
|
157
|
-
location.valid = false
|
158
|
-
location.score = 0
|
159
|
-
message
|
160
|
-
end
|
161
|
-
LocationResponse.new(success, message, Hash.from_xml(response),
|
162
|
-
:xml => response,
|
163
|
-
:request => last_request,
|
164
|
-
:location => location
|
165
|
-
)
|
166
|
-
end
|
167
|
-
|
168
|
-
def parse_return_label_response(response, shipment)
|
169
|
-
xml = REXML::Document.new(response)
|
170
|
-
success = response_success?(xml)
|
171
|
-
message = response_message(xml)
|
172
|
-
|
173
|
-
if success
|
174
|
-
parent = xml.elements.first
|
175
|
-
shipment.label.image = Base64.decode64(parent.elements['//Image'][0].to_s)
|
176
|
-
shipment.tracking_number = parent.elements['//TrackingNumber'][0].to_s
|
177
|
-
shipment.transit_time = parent.elements['//TransitTime'][0].to_s
|
178
|
-
|
179
|
-
shipment
|
180
|
-
end
|
181
|
-
ReturnLabelResponse.new(success, message, Hash.from_xml(response),
|
182
|
-
:xml => response,
|
183
|
-
:request => last_request,
|
184
|
-
:shipment => shipment
|
185
|
-
)
|
186
|
-
end
|
187
|
-
|
188
|
-
def build_return_label_request(shipment)
|
189
|
-
xml_request = XmlNode.new('ProcessShipmentRequest',
|
190
|
-
'xmlns' => 'http://fedex.com/ws/ship/v7') do |root_node|
|
191
|
-
root_node << build_request_header
|
192
|
-
root_node << XmlNode.new('Version') do |version_node|
|
193
|
-
version_node << XmlNode.new('ServiceId', 'ship')
|
194
|
-
version_node << XmlNode.new('Major', '7')
|
195
|
-
version_node << XmlNode.new('Intermediate', '0')
|
196
|
-
version_node << XmlNode.new('Minor', '0')
|
197
|
-
end
|
198
|
-
|
199
|
-
root_node << XmlNode.new('RequestedShipment') do |rs|
|
200
|
-
rs << XmlNode.new('ShipTimestamp', shipment.ship_at || Time.now)
|
201
|
-
rs << XmlNode.new('DropoffType', shipment.dropoff_type)
|
202
|
-
rs << XmlNode.new('ServiceType', shipment.service)
|
203
|
-
rs << XmlNode.new('PackagingType', shipment.packaging_type)
|
204
|
-
rs << XmlNode.new('TotalWeight') do |t|
|
205
|
-
t << XmlNode.new('Units', shipment.total_weight_units)
|
206
|
-
t << XmlNode.new('Value', shipment.total_weight_value)
|
207
|
-
end
|
208
|
-
if shipment.total_insured_amount
|
209
|
-
rs << XmlNode.new('TotalInsuredValue') do |ins|
|
210
|
-
ins << XmlNode.new('Currency', shipment.total_insured_currency)
|
211
|
-
ins << XmlNode.new('Amount', shipment.total_insured_amount)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
rs << build_party_node('Shipper', shipment.shipper)
|
215
|
-
rs << build_party_node('Recipient', shipment.recipient)
|
216
|
-
rs << XmlNode.new('ShippingChargesPayment') do |payment|
|
217
|
-
payment << XmlNode.new('PaymentType', shipment.payment_type)
|
218
|
-
payment << XmlNode.new('Payor') do |payor|
|
219
|
-
payor << XmlNode.new('AccountNumber', shipment.payor_account_number)
|
220
|
-
payor << XmlNode.new('CountryCode', shipment.payor_account_country.code(:alpha2))
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
rs << XmlNode.new('SpecialServicesRequested') do |s|
|
225
|
-
s << XmlNode.new('SpecialServiceTypes', 'RETURN_SHIPMENT')
|
226
|
-
s << XmlNode.new('ReturnShipmentDetail') do |d|
|
227
|
-
d << XmlNode.new('ReturnType', shipment.return_type)
|
228
|
-
d << XmlNode.new('Rma') do |rma|
|
229
|
-
rma << XmlNode.new('Number', shipment.rma_number)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
rs << XmlNode.new('LabelSpecification') do |spec|
|
235
|
-
spec << XmlNode.new('LabelFormatType',
|
236
|
-
shipment.label.format_type || 'COMMON2D')
|
237
|
-
spec << XmlNode.new('ImageType', shipment.label.image_type)
|
238
|
-
spec << XmlNode.new('LabelStockType', 'PAPER_4X6')
|
239
|
-
end
|
240
|
-
rs << XmlNode.new('RateRequestTypes', shipment.rate_request_type)
|
241
|
-
rs << XmlNode.new('PackageCount', shipment.package_count)
|
242
|
-
rs << XmlNode.new('PackageDetail', 'PACKAGE_SUMMARY')
|
243
|
-
(1..shipment.package_count).each do |package_index|
|
244
|
-
rs << XmlNode.new('RequestedPackageLineItems') do |p|
|
245
|
-
p << XmlNode.new('SequenceNumber', package_index)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
xml_request.to_s
|
252
|
-
end
|
253
|
-
|
254
|
-
|
255
111
|
def build_rate_request(origin, destination, packages, options={})
|
256
112
|
imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
|
257
113
|
|
@@ -349,47 +205,6 @@ module ActiveMerchant
|
|
349
205
|
[web_authentication_detail, client_detail, trasaction_detail]
|
350
206
|
end
|
351
207
|
|
352
|
-
def build_party_node(name, party)
|
353
|
-
XmlNode.new(name) do |xml_node|
|
354
|
-
if party.account_number
|
355
|
-
xml_node << XmlNode.new('AccountNumber', party.account_number)
|
356
|
-
end
|
357
|
-
|
358
|
-
if party.contact
|
359
|
-
xml_node << XmlNode.new('Contact') do |c|
|
360
|
-
c << XmlNode.new('PersonName', party.contact.name)
|
361
|
-
c << XmlNode.new('Title', party.contact.title)
|
362
|
-
c << XmlNode.new('CompanyName', party.contact.company_name)
|
363
|
-
c << XmlNode.new('PhoneNumber', party.contact.phone_number)
|
364
|
-
c << XmlNode.new('FaxNumber', party.contact.fax_number)
|
365
|
-
c << XmlNode.new('EMailAddress', party.contact.email_address)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
|
370
|
-
if party.location
|
371
|
-
xml_node << build_party_location_node('Address', party.location)
|
372
|
-
end
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def build_party_location_node(name, location)
|
377
|
-
XmlNode.new(name) do |a|
|
378
|
-
if location.address1
|
379
|
-
a << XmlNode.new('StreetLines', location.address1)
|
380
|
-
end
|
381
|
-
|
382
|
-
if location.address2
|
383
|
-
a << XmlNode.new('StreetLines', location.address2)
|
384
|
-
end
|
385
|
-
|
386
|
-
a << XmlNode.new('City', location.city)
|
387
|
-
a << XmlNode.new('StateOrProvinceCode', location.state)
|
388
|
-
a << XmlNode.new('PostalCode', location.zip)
|
389
|
-
a << XmlNode.new('CountryCode', location.country.code(:alpha2))
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
208
|
def build_location_node(name, location)
|
394
209
|
location_node = XmlNode.new(name) do |xml_node|
|
395
210
|
xml_node << XmlNode.new('Address') do |address_node|
|
@@ -446,9 +261,8 @@ module ActiveMerchant
|
|
446
261
|
tracking_number = tracking_details.get_text('TrackingNumber').to_s
|
447
262
|
|
448
263
|
destination_node = tracking_details.elements['DestinationAddress']
|
449
|
-
|
450
264
|
destination = Location.new(
|
451
|
-
:country => destination_node.get_text('CountryCode').to_s
|
265
|
+
:country => destination_node.get_text('CountryCode').to_s,
|
452
266
|
:province => destination_node.get_text('StateOrProvinceCode').to_s,
|
453
267
|
:city => destination_node.get_text('City').to_s
|
454
268
|
)
|
@@ -458,12 +272,14 @@ module ActiveMerchant
|
|
458
272
|
:city => event.elements['Address'].get_text('City').to_s,
|
459
273
|
:state => event.elements['Address'].get_text('StateOrProvinceCode').to_s,
|
460
274
|
:postal_code => event.elements['Address'].get_text('PostalCode').to_s,
|
461
|
-
:country => event.elements['Address'].get_text('CountryCode').to_s
|
275
|
+
:country => event.elements['Address'].get_text('CountryCode').to_s)
|
462
276
|
description = event.get_text('EventDescription').to_s
|
463
277
|
|
464
|
-
|
278
|
+
# for now, just assume UTC, even though it probably isn't
|
279
|
+
time = Time.parse("#{event.get_text('Timestamp').to_s}")
|
280
|
+
zoneless_time = Time.utc(time.year, time.month, time.mday, time.hour, time.min, time.sec)
|
465
281
|
|
466
|
-
shipment_events << ShipmentEvent.new(description,
|
282
|
+
shipment_events << ShipmentEvent.new(description, zoneless_time, location)
|
467
283
|
end
|
468
284
|
shipment_events = shipment_events.sort_by(&:time)
|
469
285
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
module ActiveMerchant
|
4
|
+
module Shipping
|
5
|
+
class Kunaki < Carrier
|
6
|
+
self.retry_safe = true
|
7
|
+
|
8
|
+
cattr_reader :name
|
9
|
+
@@name = "Kunaki"
|
10
|
+
|
11
|
+
URL = 'https://Kunaki.com/XMLService.ASP'
|
12
|
+
|
13
|
+
CARRIERS = [ "UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL" ]
|
14
|
+
|
15
|
+
COUNTRIES = {
|
16
|
+
'AR' => 'Argentina',
|
17
|
+
'AU' => 'Australia',
|
18
|
+
'AT' => 'Austria',
|
19
|
+
'BE' => 'Belgium',
|
20
|
+
'BR' => 'Brazil',
|
21
|
+
'BG' => 'Bulgaria',
|
22
|
+
'CA' => 'Canada',
|
23
|
+
'CN' => 'China',
|
24
|
+
'CY' => 'Cyprus',
|
25
|
+
'CZ' => 'Czech Republic',
|
26
|
+
'DK' => 'Denmark',
|
27
|
+
'EE' => 'Estonia',
|
28
|
+
'FI' => 'Finland',
|
29
|
+
'FR' => 'France',
|
30
|
+
'DE' => 'Germany',
|
31
|
+
'GI' => 'Gibraltar',
|
32
|
+
'GR' => 'Greece',
|
33
|
+
'GL' => 'Greenland',
|
34
|
+
'HK' => 'Hong Kong',
|
35
|
+
'HU' => 'Hungary',
|
36
|
+
'IS' => 'Iceland',
|
37
|
+
'IE' => 'Ireland',
|
38
|
+
'IL' => 'Israel',
|
39
|
+
'IT' => 'Italy',
|
40
|
+
'JP' => 'Japan',
|
41
|
+
'LV' => 'Latvia',
|
42
|
+
'LI' => 'Liechtenstein',
|
43
|
+
'LT' => 'Lithuania',
|
44
|
+
'LU' => 'Luxembourg',
|
45
|
+
'MX' => 'Mexico',
|
46
|
+
'NL' => 'Netherlands',
|
47
|
+
'NZ' => 'New Zealand',
|
48
|
+
'NO' => 'Norway',
|
49
|
+
'PL' => 'Poland',
|
50
|
+
'PT' => 'Portugal',
|
51
|
+
'RO' => 'Romania',
|
52
|
+
'RU' => 'Russia',
|
53
|
+
'SG' => 'Singapore',
|
54
|
+
'SK' => 'Slovakia',
|
55
|
+
'SI' => 'Slovenia',
|
56
|
+
'ES' => 'Spain',
|
57
|
+
'SE' => 'Sweden',
|
58
|
+
'CH' => 'Switzerland',
|
59
|
+
'TW' => 'Taiwan',
|
60
|
+
'TR' => 'Turkey',
|
61
|
+
'UA' => 'Ukraine',
|
62
|
+
'GB' => 'United Kingdom',
|
63
|
+
'US' => 'United States',
|
64
|
+
'VA' => 'Vatican City',
|
65
|
+
'RS' => 'Yugoslavia',
|
66
|
+
'ME' => 'Yugoslavia'
|
67
|
+
}
|
68
|
+
|
69
|
+
def find_rates(origin, destination, packages, options = {})
|
70
|
+
requires!(options, :items)
|
71
|
+
commit(origin, destination, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def valid_credentials?
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def build_request(destination, options)
|
80
|
+
xml = Builder::XmlMarkup.new
|
81
|
+
xml.instruct!
|
82
|
+
xml.tag! 'ShippingOptions' do
|
83
|
+
xml.tag! 'AddressInfo' do
|
84
|
+
xml.tag! 'Country', COUNTRIES[destination.country_code]
|
85
|
+
|
86
|
+
state = ['US', 'CA'].include?(destination.country_code.to_s) ? destination.state : ''
|
87
|
+
|
88
|
+
xml.tag! 'State_Province', state
|
89
|
+
xml.tag! 'PostalCode', destination.zip
|
90
|
+
end
|
91
|
+
|
92
|
+
options[:items].each do |item|
|
93
|
+
xml.tag! 'Product' do
|
94
|
+
xml.tag! 'ProductId', item[:sku]
|
95
|
+
xml.tag! 'Quantity', item[:quantity]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
xml.target!
|
100
|
+
end
|
101
|
+
|
102
|
+
def commit(origin, destination, options)
|
103
|
+
request = build_request(destination, options)
|
104
|
+
|
105
|
+
response = parse( ssl_post(URL, request, "Content-Type" => "text/xml") )
|
106
|
+
|
107
|
+
RateResponse.new(success?(response), message_from(response), response,
|
108
|
+
:rates => build_rate_estimates(response, origin, destination)
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_rate_estimates(response, origin, destination)
|
113
|
+
response["Options"].collect do |quote|
|
114
|
+
RateEstimate.new(origin, destination, carrier_for(quote["Description"]), quote["Description"],
|
115
|
+
:total_price => quote["Price"],
|
116
|
+
:currency => "USD"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def carrier_for(service)
|
122
|
+
CARRIERS.dup.find{ |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse(xml)
|
126
|
+
response = {}
|
127
|
+
response["Options"] = []
|
128
|
+
|
129
|
+
document = REXML::Document.new(sanitize(xml))
|
130
|
+
|
131
|
+
response["ErrorCode"] = parse_child_text(document.root, "ErrorCode")
|
132
|
+
response["ErrorText"] = parse_child_text(document.root, "ErrorText")
|
133
|
+
|
134
|
+
document.root.elements.each("Option") do |e|
|
135
|
+
rate = {}
|
136
|
+
rate["Description"] = parse_child_text(e, "Description")
|
137
|
+
rate["Price"] = parse_child_text(e, "Price")
|
138
|
+
response["Options"] << rate
|
139
|
+
end
|
140
|
+
response
|
141
|
+
end
|
142
|
+
|
143
|
+
def sanitize(response)
|
144
|
+
result = response.to_s
|
145
|
+
result.gsub!("\r\n", "")
|
146
|
+
result.gsub!(/<(\/)?(BODY|HTML)>/, '')
|
147
|
+
result
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_child_text(parent, name)
|
151
|
+
if element = parent.elements[name]
|
152
|
+
element.text
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def success?(response)
|
157
|
+
response["ErrorCode"] == "0"
|
158
|
+
end
|
159
|
+
|
160
|
+
def message_from(response)
|
161
|
+
response["ErrorText"]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|