omniship 0.3.2.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/omniship.rb +13 -12
- data/lib/omniship/address.rb +36 -36
- data/lib/omniship/carriers/fedex.rb +343 -188
- data/lib/omniship/carriers/ups.rb +156 -95
- data/lib/omniship/notification.rb +18 -0
- data/lib/omniship/response.rb +7 -7
- data/lib/omniship/ship_response.rb +12 -0
- data/lib/omniship/version.rb +2 -2
- data/lib/rails/generators/omniship/setup/setup_generator.rb +19 -0
- data/lib/rails/generators/omniship/setup/templates/initializer.rb +2 -0
- data/lib/rails/generators/omniship/setup/templates/omniship.yml +33 -0
- metadata +58 -73
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9f5ce46f050f67d8b750e29ae85c457dc30bde0
|
4
|
+
data.tar.gz: fedf5e0c3e0408636c1ba156781eb9088df4f3ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 473d38ea1ef885e530317a3b68ae013d0717406dd176ee8bca0e9ff2abfcfbc45780c211da29a62567ac2c462a8290ea6c5e9ff34de8eadc247bcdf27650b068
|
7
|
+
data.tar.gz: 999466fd9b82fb309c5327af767df136a364066b2a64c7755abb99f440d9535d31a156772fcb1844ee800bad661d41837ad5cc0abdd1de3ee4a61bafed925631
|
data/lib/omniship.rb
CHANGED
@@ -26,23 +26,23 @@
|
|
26
26
|
require 'omniship/base'
|
27
27
|
|
28
28
|
def Omniship.setup
|
29
|
-
@root = Rails.root
|
30
|
-
if @root
|
29
|
+
@root = Rails.root
|
30
|
+
if @root
|
31
31
|
@boot = File.join(@root, "config", "boot.rb").freeze
|
32
32
|
@config = File.join(@root, "config", "omniship.yml").freeze
|
33
|
-
@keys = %w{ username password key }.map { |v| v.freeze }.freeze
|
33
|
+
@keys = %w{ username password key account meter }.map { |v| v.freeze }.freeze
|
34
34
|
require boot unless defined? Rails.env
|
35
|
-
|
36
|
-
|
37
|
-
raise "Invalid
|
38
|
-
if (
|
39
|
-
|
40
|
-
"ups" =>
|
41
|
-
"fedex" =>
|
42
|
-
"usps" =>
|
35
|
+
if File.exists? @config
|
36
|
+
@config = YAML.load_file(@config)
|
37
|
+
raise "Invalid omniship configuration file: #{@config}" unless @config.is_a?(Hash)
|
38
|
+
if (@config.keys & @keys).sort == @keys.sort and !@config.has_key?(Rails.env)
|
39
|
+
@config[Rails.env] = {
|
40
|
+
"ups" => @config["ups"],
|
41
|
+
"fedex" => @config["fedex"],
|
42
|
+
"usps" => @config["usps"]
|
43
43
|
}
|
44
44
|
end
|
45
|
-
|
45
|
+
@config[Rails.env].freeze
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -75,3 +75,4 @@ require 'omniship/rate_estimate'
|
|
75
75
|
require 'omniship/carrier'
|
76
76
|
require 'omniship/carriers'
|
77
77
|
require 'omniship/shipment_event'
|
78
|
+
require 'omniship/ship_response'
|
data/lib/omniship/address.rb
CHANGED
@@ -28,16 +28,16 @@ module Omniship #:nodoc:
|
|
28
28
|
@country = (options[:country].nil? or options[:country].is_a?(ActiveMerchant::Country)) ?
|
29
29
|
options[:country] :
|
30
30
|
ActiveMerchant::Country.find(options[:country])
|
31
|
-
@postal_code
|
32
|
-
@province
|
33
|
-
@city
|
34
|
-
@name
|
35
|
-
@address1
|
36
|
-
@address2
|
37
|
-
@address3
|
38
|
-
@phone
|
39
|
-
@fax
|
40
|
-
@company_name
|
31
|
+
@postal_code = options[:postal_code] || options[:postal] || options[:zip]
|
32
|
+
@province = options[:province] || options[:state] || options[:territory] || options[:region]
|
33
|
+
@city = options[:city]
|
34
|
+
@name = options[:name]
|
35
|
+
@address1 = options[:address1]
|
36
|
+
@address2 = options[:address2]
|
37
|
+
@address3 = options[:address3]
|
38
|
+
@phone = options[:phone]
|
39
|
+
@fax = options[:fax]
|
40
|
+
@company_name = options[:company_name] || options[:company]
|
41
41
|
@attention_name = options[:attention_name]
|
42
42
|
|
43
43
|
self.address_type = options[:address_type]
|
@@ -46,19 +46,19 @@ module Omniship #:nodoc:
|
|
46
46
|
def self.from(object, options={})
|
47
47
|
return object if object.is_a? Omniship::Address
|
48
48
|
attr_mappings = {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
49
|
+
:name => [:name],
|
50
|
+
:attention_name => [:attention_name],
|
51
|
+
:country => [:country_code, :country],
|
52
|
+
:postal_code => [:postal_code, :zip, :postal],
|
53
|
+
:province => [:province_code, :state_code, :territory_code, :region_code, :province, :state, :territory, :region],
|
54
|
+
:city => [:city, :town],
|
55
|
+
:address1 => [:address1, :address, :street],
|
56
|
+
:address2 => [:address2],
|
57
|
+
:address3 => [:address3],
|
58
|
+
:phone => [:phone, :phone_number],
|
59
|
+
:fax => [:fax, :fax_number],
|
60
|
+
:address_type => [:address_type],
|
61
|
+
:company_name => [:company, :company_name]
|
62
62
|
}
|
63
63
|
attributes = {}
|
64
64
|
hash_access = begin
|
@@ -95,19 +95,19 @@ module Omniship #:nodoc:
|
|
95
95
|
|
96
96
|
def to_hash
|
97
97
|
{
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
98
|
+
:country => country_code,
|
99
|
+
:postal_code => postal_code,
|
100
|
+
:province => province,
|
101
|
+
:city => city,
|
102
|
+
:name => name,
|
103
|
+
:address1 => address1,
|
104
|
+
:address2 => address2,
|
105
|
+
:address3 => address3,
|
106
|
+
:phone => phone,
|
107
|
+
:fax => fax,
|
108
|
+
:address_type => address_type,
|
109
|
+
:company_name => company_name,
|
110
|
+
:attention_name => attention_name
|
111
111
|
}
|
112
112
|
end
|
113
113
|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
# FedEx module by
|
2
|
-
# http://github.com/jimmyebaker
|
1
|
+
# FedEx module by Donavan White
|
3
2
|
|
4
3
|
module Omniship
|
5
4
|
# :key is your developer API key
|
@@ -8,79 +7,79 @@ module Omniship
|
|
8
7
|
# :login is your meter number
|
9
8
|
class FedEx < Carrier
|
10
9
|
self.retry_safe = true
|
11
|
-
|
10
|
+
|
12
11
|
cattr_reader :name
|
13
12
|
@@name = "FedEx"
|
14
|
-
|
13
|
+
|
15
14
|
TEST_URL = 'https://gatewaybeta.fedex.com:443/xml'
|
16
15
|
LIVE_URL = 'https://gateway.fedex.com:443/xml'
|
17
|
-
|
16
|
+
|
18
17
|
CarrierCodes = {
|
19
|
-
"fedex_ground"
|
18
|
+
"fedex_ground" => "FDXG",
|
20
19
|
"fedex_express" => "FDXE"
|
21
20
|
}
|
22
|
-
|
21
|
+
|
23
22
|
ServiceTypes = {
|
24
|
-
"PRIORITY_OVERNIGHT"
|
25
|
-
"PRIORITY_OVERNIGHT_SATURDAY_DELIVERY"
|
26
|
-
"FEDEX_2_DAY"
|
27
|
-
"FEDEX_2_DAY_SATURDAY_DELIVERY"
|
28
|
-
"STANDARD_OVERNIGHT"
|
29
|
-
"FIRST_OVERNIGHT"
|
30
|
-
"FIRST_OVERNIGHT_SATURDAY_DELIVERY"
|
31
|
-
"FEDEX_EXPRESS_SAVER"
|
32
|
-
"FEDEX_1_DAY_FREIGHT"
|
33
|
-
"FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY"
|
34
|
-
"FEDEX_2_DAY_FREIGHT"
|
35
|
-
"FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY"
|
36
|
-
"FEDEX_3_DAY_FREIGHT"
|
37
|
-
"FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY"
|
38
|
-
"INTERNATIONAL_PRIORITY"
|
23
|
+
"PRIORITY_OVERNIGHT" => "FedEx Priority Overnight",
|
24
|
+
"PRIORITY_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx Priority Overnight Saturday Delivery",
|
25
|
+
"FEDEX_2_DAY" => "FedEx 2 Day",
|
26
|
+
"FEDEX_2_DAY_SATURDAY_DELIVERY" => "FedEx 2 Day Saturday Delivery",
|
27
|
+
"STANDARD_OVERNIGHT" => "FedEx Standard Overnight",
|
28
|
+
"FIRST_OVERNIGHT" => "FedEx First Overnight",
|
29
|
+
"FIRST_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx First Overnight Saturday Delivery",
|
30
|
+
"FEDEX_EXPRESS_SAVER" => "FedEx Express Saver",
|
31
|
+
"FEDEX_1_DAY_FREIGHT" => "FedEx 1 Day Freight",
|
32
|
+
"FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 1 Day Freight Saturday Delivery",
|
33
|
+
"FEDEX_2_DAY_FREIGHT" => "FedEx 2 Day Freight",
|
34
|
+
"FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 2 Day Freight Saturday Delivery",
|
35
|
+
"FEDEX_3_DAY_FREIGHT" => "FedEx 3 Day Freight",
|
36
|
+
"FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 3 Day Freight Saturday Delivery",
|
37
|
+
"INTERNATIONAL_PRIORITY" => "FedEx International Priority",
|
39
38
|
"INTERNATIONAL_PRIORITY_SATURDAY_DELIVERY" => "FedEx International Priority Saturday Delivery",
|
40
|
-
"INTERNATIONAL_ECONOMY"
|
41
|
-
"INTERNATIONAL_FIRST"
|
42
|
-
"INTERNATIONAL_PRIORITY_FREIGHT"
|
43
|
-
"INTERNATIONAL_ECONOMY_FREIGHT"
|
44
|
-
"GROUND_HOME_DELIVERY"
|
45
|
-
"FEDEX_GROUND"
|
46
|
-
"INTERNATIONAL_GROUND"
|
39
|
+
"INTERNATIONAL_ECONOMY" => "FedEx International Economy",
|
40
|
+
"INTERNATIONAL_FIRST" => "FedEx International First",
|
41
|
+
"INTERNATIONAL_PRIORITY_FREIGHT" => "FedEx International Priority Freight",
|
42
|
+
"INTERNATIONAL_ECONOMY_FREIGHT" => "FedEx International Economy Freight",
|
43
|
+
"GROUND_HOME_DELIVERY" => "FedEx Ground Home Delivery",
|
44
|
+
"FEDEX_GROUND" => "FedEx Ground",
|
45
|
+
"INTERNATIONAL_GROUND" => "FedEx International Ground"
|
47
46
|
}
|
48
47
|
|
49
48
|
PackageTypes = {
|
50
|
-
"fedex_envelope"
|
51
|
-
"fedex_pak"
|
52
|
-
"fedex_box"
|
53
|
-
"fedex_tube"
|
49
|
+
"fedex_envelope" => "FEDEX_ENVELOPE",
|
50
|
+
"fedex_pak" => "FEDEX_PAK",
|
51
|
+
"fedex_box" => "FEDEX_BOX",
|
52
|
+
"fedex_tube" => "FEDEX_TUBE",
|
54
53
|
"fedex_10_kg_box" => "FEDEX_10KG_BOX",
|
55
54
|
"fedex_25_kg_box" => "FEDEX_25KG_BOX",
|
56
|
-
"your_packaging"
|
55
|
+
"your_packaging" => "YOUR_PACKAGING"
|
57
56
|
}
|
58
57
|
|
59
58
|
DropoffTypes = {
|
60
|
-
'regular_pickup'
|
61
|
-
'request_courier'
|
62
|
-
'dropbox'
|
59
|
+
'regular_pickup' => 'REGULAR_PICKUP',
|
60
|
+
'request_courier' => 'REQUEST_COURIER',
|
61
|
+
'dropbox' => 'DROP_BOX',
|
63
62
|
'business_service_center' => 'BUSINESS_SERVICE_CENTER',
|
64
|
-
'station'
|
63
|
+
'station' => 'STATION'
|
65
64
|
}
|
66
65
|
|
67
66
|
PaymentTypes = {
|
68
|
-
'sender'
|
69
|
-
'recipient'
|
67
|
+
'sender' => 'SENDER',
|
68
|
+
'recipient' => 'RECIPIENT',
|
70
69
|
'third_party' => 'THIRDPARTY',
|
71
|
-
'collect'
|
70
|
+
'collect' => 'COLLECT'
|
72
71
|
}
|
73
|
-
|
72
|
+
|
74
73
|
PackageIdentifierTypes = {
|
75
|
-
'tracking_number'
|
76
|
-
'door_tag'
|
77
|
-
'rma'
|
78
|
-
'ground_shipment_id'
|
79
|
-
'ground_invoice_number'
|
74
|
+
'tracking_number' => 'TRACKING_NUMBER_OR_DOORTAG',
|
75
|
+
'door_tag' => 'TRACKING_NUMBER_OR_DOORTAG',
|
76
|
+
'rma' => 'RMA',
|
77
|
+
'ground_shipment_id' => 'GROUND_SHIPMENT_ID',
|
78
|
+
'ground_invoice_number' => 'GROUND_INVOICE_NUMBER',
|
80
79
|
'ground_customer_reference' => 'GROUND_CUSTOMER_REFERENCE',
|
81
|
-
'ground_po'
|
82
|
-
'express_reference'
|
83
|
-
'express_mps_master'
|
80
|
+
'ground_po' => 'GROUND_PO',
|
81
|
+
'express_reference' => 'EXPRESS_REFERENCE',
|
82
|
+
'express_mps_master' => 'EXPRESS_MPS_MASTER'
|
84
83
|
}
|
85
84
|
|
86
85
|
def self.service_name_for_code(service_code)
|
@@ -89,88 +88,214 @@ module Omniship
|
|
89
88
|
"FedEx #{name.sub(/Fedex /, '')}"
|
90
89
|
end
|
91
90
|
end
|
92
|
-
|
91
|
+
|
93
92
|
def requirements
|
94
|
-
[:key, :
|
93
|
+
[:key, :account, :meter, :password]
|
95
94
|
end
|
96
|
-
|
95
|
+
|
97
96
|
def find_rates(origin, destination, packages, options = {})
|
98
|
-
options
|
99
|
-
|
100
|
-
|
101
|
-
rate_request
|
102
|
-
|
103
|
-
response = commit(save_request(rate_request), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
|
104
|
-
|
97
|
+
options = @options.merge(options)
|
98
|
+
options[:test] = options[:test].nil? ? true : options[:test]
|
99
|
+
packages = Array(packages)
|
100
|
+
rate_request = build_rate_request(origin, destination, packages, options)
|
101
|
+
response = commit(save_request(rate_request.gsub("\n", "")), options[:test])
|
105
102
|
parse_rate_response(origin, destination, packages, response, options)
|
106
103
|
end
|
107
|
-
|
104
|
+
|
105
|
+
def create_shipment(origin, destination, packages, options={})
|
106
|
+
options = @options.merge(options)
|
107
|
+
options[:test] = options[:test].nil? ? true : options[:test]
|
108
|
+
packages = Array(packages)
|
109
|
+
ship_request = build_ship_request(origin, destination, packages, options)
|
110
|
+
response = commit(save_request(ship_request.gsub("\n", "")), options[:test])
|
111
|
+
parse_ship_response(response, options)
|
112
|
+
end
|
113
|
+
|
114
|
+
def delete_shipment(tracking_number, shipment_type, options={})
|
115
|
+
options = @options.merge(options)
|
116
|
+
delete_shipment_request = build_delete_request(tracking_number, shipment_type, options)
|
117
|
+
response = commit(save_request(delete_shipment_request.gsub("\n", "")), options[:test])
|
118
|
+
parse_delete_response(response, options)
|
119
|
+
end
|
120
|
+
|
108
121
|
def find_tracking_info(tracking_number, options={})
|
109
|
-
options
|
110
|
-
|
122
|
+
options = @options.update(options)
|
111
123
|
tracking_request = build_tracking_request(tracking_number, options)
|
112
|
-
response
|
124
|
+
response = commit(save_request(tracking_request), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
|
113
125
|
parse_tracking_response(response, options)
|
114
126
|
end
|
115
|
-
|
127
|
+
|
116
128
|
protected
|
117
129
|
def build_rate_request(origin, destination, packages, options={})
|
118
130
|
imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
|
119
131
|
|
120
|
-
|
121
|
-
|
132
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
133
|
+
xml.RateRequest('xmlns' => 'http://fedex.com/ws/rate/v12') {
|
134
|
+
build_access_request(xml)
|
135
|
+
xml.Version {
|
136
|
+
xml.ServiceId "crs"
|
137
|
+
xml.Major "12"
|
138
|
+
xml.Intermediate "0"
|
139
|
+
xml.Minor "0"
|
140
|
+
}
|
141
|
+
xml.RequestedShipment {
|
142
|
+
xml.ShipTimestamp options[:ship_date] || DateTime.now.strftime
|
143
|
+
xml.DropoffType options[:dropoff_type] || 'REGULAR_PICKUP'
|
144
|
+
xml.PackagingType options[:packaging_type] || 'YOUR_PACKAGING'
|
145
|
+
build_location_node(['Shipper'], (options[:shipper] || origin), xml)
|
146
|
+
build_location_node(['Recipient'], destination, xml)
|
147
|
+
if options[:shipper] && options[:shipper] != origin
|
148
|
+
build_location_node(['Origin'], origin, xml)
|
149
|
+
end
|
150
|
+
xml.RateRequestTypes 'ACCOUNT'
|
151
|
+
xml.PackageCount packages.size
|
152
|
+
packages.each do |pkg|
|
153
|
+
xml.RequestedPackageLineItems {
|
154
|
+
xml.SequenceNumber 1
|
155
|
+
xml.GroupPackageCount 1
|
156
|
+
xml.Weight {
|
157
|
+
xml.Units (imperial ? 'LB' : 'KG')
|
158
|
+
xml.Value ((imperial ? pkg.weight : pkg.weight/2.2).to_f)
|
159
|
+
}
|
160
|
+
# xml.Dimensions {
|
161
|
+
# [:length, :width, :height].each do |axis|
|
162
|
+
# name = axis.to_s.capitalize
|
163
|
+
# value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f*1000).round/1000.0
|
164
|
+
# xml.name value
|
165
|
+
# end
|
166
|
+
# xml.Units (imperial ? 'IN' : 'CM')
|
167
|
+
# }
|
168
|
+
}
|
169
|
+
end
|
170
|
+
}
|
171
|
+
}
|
172
|
+
end
|
173
|
+
builder.to_xml
|
174
|
+
end
|
122
175
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
176
|
+
def build_ship_request(origin, destination, packages, options={})
|
177
|
+
imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
|
178
|
+
|
179
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
180
|
+
xml.ProcessShipmentRequest('xmlns' => 'http://fedex.com/ws/ship/v12') {
|
181
|
+
build_access_request(xml)
|
182
|
+
xml.Version {
|
183
|
+
xml.ServiceId "ship"
|
184
|
+
xml.Major "12"
|
185
|
+
xml.Intermediate "0"
|
186
|
+
xml.Minor "0"
|
187
|
+
}
|
188
|
+
xml.RequestedShipment {
|
189
|
+
xml.ShipTimestamp options[:ship_date] || DateTime.now.strftime
|
190
|
+
xml.DropoffType options[:dropoff_type] || 'REGULAR_PICKUP'
|
191
|
+
xml.ServiceType options[:service_type] || 'GROUND_HOME_DELIVERY'
|
192
|
+
xml.PackagingType options[:package_type] || 'YOUR_PACKAGING'
|
193
|
+
build_location_node(["Shipper"], (options[:shipper] || origin), xml)
|
194
|
+
build_location_node(["Recipient"], destination, xml)
|
195
|
+
if options[:shipper] && options[:shipper] != origin
|
196
|
+
build_location_node(["Origin"], origin, xml)
|
197
|
+
end
|
198
|
+
xml.ShippingChargesPayment {
|
199
|
+
xml.PaymentType "SENDER"
|
200
|
+
xml.Payor {
|
201
|
+
xml.ResponsibleParty {
|
202
|
+
xml.AccountNumber @options[:account]
|
203
|
+
xml.Contact nil
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
xml.SpecialServicesRequested {
|
208
|
+
xml.SpecialServiceTypes "SATURDAY_DELIVERY" if options[:saturday_delivery]
|
209
|
+
if options[:return_shipment]
|
210
|
+
xml.SpecialServiceTypes "RETURN_SHIPMENT"
|
211
|
+
xml.ReturnShipmentDetail {
|
212
|
+
xml.ReturnType "PRINT_RETURN_LABEL"
|
213
|
+
}
|
161
214
|
end
|
215
|
+
}
|
216
|
+
xml.LabelSpecification {
|
217
|
+
xml.LabelFormatType 'COMMON2D'
|
218
|
+
xml.ImageType 'PDF'
|
219
|
+
xml.LabelStockType 'PAPER_7X4.75'
|
220
|
+
}
|
221
|
+
xml.RateRequestTypes 'ACCOUNT'
|
222
|
+
xml.PackageCount packages.size
|
223
|
+
packages.each do |pkg|
|
224
|
+
xml.RequestedPackageLineItems {
|
225
|
+
xml.SequenceNumber 1
|
226
|
+
xml.Weight {
|
227
|
+
xml.Units (imperial ? 'LB' : 'KG')
|
228
|
+
xml.Value ((imperial ? pkg.weight : pkg.weight/2.2).to_f)
|
229
|
+
}
|
230
|
+
# xml.Dimensions {
|
231
|
+
# [:length, :width, :height].each do |axis|
|
232
|
+
# name = axis.to_s.capitalize
|
233
|
+
# value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f*1000).round/1000.0
|
234
|
+
# xml.send name, value.to_s
|
235
|
+
# end
|
236
|
+
# xml.Units (imperial ? 'IN' : 'CM')
|
237
|
+
# }
|
238
|
+
}
|
162
239
|
end
|
163
|
-
|
164
|
-
|
165
|
-
|
240
|
+
|
241
|
+
if !!@options[:notifications]
|
242
|
+
xml.SpecialServicesRequested {
|
243
|
+
xml.SpecialServiceTypes "EMAIL_NOTIFICATION"
|
244
|
+
xml.EmailNotificationDetail {
|
245
|
+
xml.PersonalMessage # Personal Message to be sent to all recipients
|
246
|
+
@options[:notifications].each do |email|
|
247
|
+
xml.Recipients {
|
248
|
+
xml.EmailAddress email.address
|
249
|
+
xml.NotificationEventsRequested {
|
250
|
+
xml.EmailNotificationEventType{
|
251
|
+
xml.ON_DELIVERY if email.on_delivery
|
252
|
+
xml.ON_EXCEPTION if email.on_exception
|
253
|
+
xml.ON_SHIPMENT if email.on_shipment
|
254
|
+
xml.ON_TENDER if email.on_tender
|
255
|
+
}
|
256
|
+
}
|
257
|
+
xml.Format email.format || "HTML" # options are "HTML" "Text" "Wireless"
|
258
|
+
xml.Localization {
|
259
|
+
xml.Language email.language || "EN" # Default to EN (English)
|
260
|
+
xml.LocaleCode email.locale_code if !email.locale_code.nil?
|
261
|
+
}
|
262
|
+
}
|
263
|
+
end
|
264
|
+
xml.EMailNotificationAggregationType @options[:notification_aggregation_type] if @options.has_key?(:notification_aggregation_type)
|
265
|
+
}
|
266
|
+
}
|
267
|
+
end
|
268
|
+
}
|
269
|
+
}
|
166
270
|
end
|
167
|
-
|
271
|
+
builder.to_xml
|
168
272
|
end
|
169
|
-
|
273
|
+
|
274
|
+
def build_delete_request(tracking_number, shipment_type, options={})
|
275
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
276
|
+
xml.DeleteShipmentRequest('xmlns' => 'http://fedex.com/ws/ship/v12') {
|
277
|
+
build_access_request(xml)
|
278
|
+
xml.Version {
|
279
|
+
xml.ServiceId "ship"
|
280
|
+
xml.Major "12"
|
281
|
+
xml.Intermediate "0"
|
282
|
+
xml.Minor "0"
|
283
|
+
}
|
284
|
+
xml.ShipTimestamp options[:ship_timestamp] if options[:ship_timestamp]
|
285
|
+
xml.TrackingId {
|
286
|
+
xml.TrackingIdType shipment_type
|
287
|
+
xml.TrackingNumber tracking_number
|
288
|
+
}
|
289
|
+
xml.DeletionControl options[:deletion_type] || "DELETE_ALL_PACKAGES"
|
290
|
+
}
|
291
|
+
end
|
292
|
+
builder.to_xml
|
293
|
+
end
|
294
|
+
|
170
295
|
def build_tracking_request(tracking_number, options={})
|
171
296
|
xml_request = XmlNode.new('TrackRequest', 'xmlns' => 'http://fedex.com/ws/track/v3') do |root_node|
|
172
297
|
root_node << build_request_header
|
173
|
-
|
298
|
+
|
174
299
|
# Version
|
175
300
|
root_node << XmlNode.new('Version') do |version_node|
|
176
301
|
version_node << XmlNode.new('ServiceId', 'trck')
|
@@ -178,74 +303,81 @@ module Omniship
|
|
178
303
|
version_node << XmlNode.new('Intermediate', '0')
|
179
304
|
version_node << XmlNode.new('Minor', '0')
|
180
305
|
end
|
181
|
-
|
306
|
+
|
182
307
|
root_node << XmlNode.new('PackageIdentifier') do |package_node|
|
183
308
|
package_node << XmlNode.new('Value', tracking_number)
|
184
309
|
package_node << XmlNode.new('Type', PackageIdentifierTypes[options['package_identifier_type'] || 'tracking_number'])
|
185
310
|
end
|
186
|
-
|
311
|
+
|
187
312
|
root_node << XmlNode.new('ShipDateRangeBegin', options['ship_date_range_begin']) if options['ship_date_range_begin']
|
188
313
|
root_node << XmlNode.new('ShipDateRangeEnd', options['ship_date_range_end']) if options['ship_date_range_end']
|
189
314
|
root_node << XmlNode.new('IncludeDetailedScans', 1)
|
190
315
|
end
|
191
316
|
xml_request.to_s
|
192
317
|
end
|
193
|
-
|
194
|
-
def
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
[web_authentication_detail, client_detail, trasaction_detail]
|
318
|
+
|
319
|
+
def build_access_request(xml)
|
320
|
+
xml.WebAuthenticationDetail {
|
321
|
+
xml.UserCredential {
|
322
|
+
xml.Key @options[:key]
|
323
|
+
xml.Password @options[:password]
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
xml.ClientDetail {
|
328
|
+
xml.AccountNumber @options[:account]
|
329
|
+
xml.MeterNumber @options[:meter]
|
330
|
+
}
|
331
|
+
|
332
|
+
xml.TransactionDetail {
|
333
|
+
xml.CustomerTransactionId 'Omniship' # TODO: Need to do something better with this...
|
334
|
+
}
|
212
335
|
end
|
213
|
-
|
214
|
-
def build_location_node(name, location)
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
336
|
+
|
337
|
+
def build_location_node(name, location, xml)
|
338
|
+
for name in name
|
339
|
+
xml.send(name) {
|
340
|
+
xml.Contact {
|
341
|
+
xml.PersonName location.name unless location.name == "" || location.name == nil
|
342
|
+
xml.CompanyName location.company unless location.company == "" || location.name == nil
|
343
|
+
xml.PhoneNumber location.phone
|
344
|
+
}
|
345
|
+
xml.Address {
|
346
|
+
xml.StreetLines location.address1
|
347
|
+
xml.StreetLines location.address2 unless location.address2.nil?
|
348
|
+
xml.City location.city
|
349
|
+
xml.StateOrProvinceCode location.state
|
350
|
+
xml.PostalCode location.postal_code
|
351
|
+
xml.CountryCode location.country_code(:alpha2)
|
352
|
+
xml.Residential true unless location.commercial?
|
353
|
+
}
|
354
|
+
}
|
222
355
|
end
|
223
356
|
end
|
224
|
-
|
357
|
+
|
225
358
|
def parse_rate_response(origin, destination, packages, response, options)
|
226
|
-
rate_estimates
|
359
|
+
rate_estimates = []
|
227
360
|
success, message = nil
|
228
|
-
|
229
|
-
xml =
|
230
|
-
|
231
|
-
|
361
|
+
|
362
|
+
xml = Nokogiri::XML(response).remove_namespaces!
|
363
|
+
|
232
364
|
success = response_success?(xml)
|
233
365
|
message = response_message(xml)
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
366
|
+
|
367
|
+
|
368
|
+
service_code = xml.xpath('//ServiceType').text == options[:service_type]
|
369
|
+
is_saturday_delivery = xml.xpath('//AppliedOptions').text == 'SATURDAY_DELIVERY'
|
370
|
+
service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
|
371
|
+
|
372
|
+
currency = handle_uk_currency(xml.xpath('//RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').text)
|
373
|
+
rate_estimates << RateEstimate.new(origin, destination, @@name,
|
374
|
+
self.class.service_name_for_code(service_type),
|
375
|
+
:service_code => service_code,
|
376
|
+
:total_price => xml.xpath('//RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').text.to_f,
|
377
|
+
:currency => currency,
|
378
|
+
:packages => packages,
|
379
|
+
:delivery_range => [xml.xpath('//DeliveryTimestamp').text] * 2)
|
380
|
+
|
249
381
|
|
250
382
|
if rate_estimates.empty?
|
251
383
|
success = false
|
@@ -254,28 +386,52 @@ module Omniship
|
|
254
386
|
|
255
387
|
RateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :request => last_request, :log_xml => options[:log_xml])
|
256
388
|
end
|
257
|
-
|
389
|
+
|
390
|
+
def parse_ship_response(response, options)
|
391
|
+
xml = Nokogiri::XML(response).remove_namespaces!
|
392
|
+
success = response_success?(xml)
|
393
|
+
message = response_message(xml)
|
394
|
+
label = nil
|
395
|
+
tracking_number = nil
|
396
|
+
|
397
|
+
if success
|
398
|
+
label = xml.xpath("//Image").text
|
399
|
+
tracking_number = xml.xpath("//TrackingNumber").text
|
400
|
+
else
|
401
|
+
success = false
|
402
|
+
message = "Shipment was not succcessful." if message.blank?
|
403
|
+
end
|
404
|
+
ShipResponse.new(success, message, :tracking_number => tracking_number, :label_encoded => label)
|
405
|
+
end
|
406
|
+
|
407
|
+
def parse_delete_response(response, options={})
|
408
|
+
xml = Nokogiri::XML(response).remove_namespaces!
|
409
|
+
success = response_success?(xml)
|
410
|
+
message = response_message(xml)
|
411
|
+
return [success, message]
|
412
|
+
end
|
413
|
+
|
258
414
|
def parse_tracking_response(response, options)
|
259
415
|
xml = REXML::Document.new(response)
|
260
416
|
root_node = xml.elements['TrackReply']
|
261
|
-
|
417
|
+
|
262
418
|
success = response_success?(xml)
|
263
419
|
message = response_message(xml)
|
264
|
-
|
420
|
+
|
265
421
|
if success
|
266
422
|
tracking_number, origin, destination = nil
|
267
423
|
shipment_events = []
|
268
|
-
|
424
|
+
|
269
425
|
tracking_details = root_node.elements['TrackDetails']
|
270
426
|
tracking_number = tracking_details.get_text('TrackingNumber').to_s
|
271
|
-
|
427
|
+
|
272
428
|
destination_node = tracking_details.elements['DestinationAddress']
|
273
429
|
destination = Address.new(
|
274
430
|
:country => destination_node.get_text('CountryCode').to_s,
|
275
431
|
:province => destination_node.get_text('StateOrProvinceCode').to_s,
|
276
432
|
:city => destination_node.get_text('City').to_s
|
277
433
|
)
|
278
|
-
|
434
|
+
|
279
435
|
tracking_details.elements.each('Events') do |event|
|
280
436
|
address = event.elements['Address']
|
281
437
|
|
@@ -284,45 +440,44 @@ module Omniship
|
|
284
440
|
zip_code = address.get_text('PostalCode').to_s
|
285
441
|
country = address.get_text('CountryCode').to_s
|
286
442
|
next if country.blank?
|
287
|
-
|
443
|
+
|
288
444
|
location = Address.new(:city => city, :state => state, :postal_code => zip_code, :country => country)
|
289
445
|
description = event.get_text('EventDescription').to_s
|
290
|
-
|
446
|
+
|
291
447
|
# for now, just assume UTC, even though it probably isn't
|
292
448
|
time = Time.parse("#{event.get_text('Timestamp').to_s}")
|
293
449
|
zoneless_time = Time.utc(time.year, time.month, time.mday, time.hour, time.min, time.sec)
|
294
|
-
|
450
|
+
|
295
451
|
shipment_events << ShipmentEvent.new(description, zoneless_time, location)
|
296
452
|
end
|
297
453
|
shipment_events = shipment_events.sort_by(&:time)
|
298
454
|
end
|
299
|
-
|
455
|
+
|
300
456
|
TrackingResponse.new(success, message, Hash.from_xml(response),
|
301
|
-
:xml
|
302
|
-
:request
|
457
|
+
:xml => response,
|
458
|
+
:request => last_request,
|
303
459
|
:shipment_events => shipment_events,
|
304
|
-
:destination
|
460
|
+
:destination => destination,
|
305
461
|
:tracking_number => tracking_number
|
306
462
|
)
|
307
463
|
end
|
308
|
-
|
309
|
-
def response_status_node(
|
310
|
-
|
464
|
+
|
465
|
+
def response_status_node(xml)
|
466
|
+
xml.ProcessShipmentReply
|
311
467
|
end
|
312
|
-
|
313
|
-
def response_success?(
|
314
|
-
%w{SUCCESS WARNING NOTE}.include?
|
468
|
+
|
469
|
+
def response_success?(xml)
|
470
|
+
%w{SUCCESS WARNING NOTE}.include? xml.xpath('//Notifications/Severity').text
|
315
471
|
end
|
316
|
-
|
317
|
-
def response_message(
|
318
|
-
|
319
|
-
"#{response_status_node(document).get_text('Severity').to_s} - #{response_node.get_text('Code').to_s}: #{response_node.get_text('Message').to_s}"
|
472
|
+
|
473
|
+
def response_message(xml)
|
474
|
+
"#{xml.xpath('//Notifications/Severity').text} - #{xml.xpath('//Notifications/Code').text}: #{xml.xpath('//Notifications/Message').text}"
|
320
475
|
end
|
321
|
-
|
476
|
+
|
322
477
|
def commit(request, test = false)
|
323
|
-
ssl_post(test ? TEST_URL : LIVE_URL, request
|
478
|
+
ssl_post(test ? TEST_URL : LIVE_URL, request)
|
324
479
|
end
|
325
|
-
|
480
|
+
|
326
481
|
def handle_uk_currency(currency)
|
327
482
|
currency =~ /UKL/i ? 'GBP' : currency
|
328
483
|
end
|