omniship 0.3.2.2 → 0.4.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.
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
- @@config = YAML.load_file(@config) rescue false
36
- if @@config != false
37
- raise "Invalid fedex 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"]
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
- @@config[Rails.env].freeze
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'
@@ -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 = 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]
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
- :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]
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
- :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
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 Jimmy Baker
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" => "FDXG",
18
+ "fedex_ground" => "FDXG",
20
19
  "fedex_express" => "FDXE"
21
20
  }
22
-
21
+
23
22
  ServiceTypes = {
24
- "PRIORITY_OVERNIGHT" => "FedEx Priority Overnight",
25
- "PRIORITY_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx Priority Overnight Saturday Delivery",
26
- "FEDEX_2_DAY" => "FedEx 2 Day",
27
- "FEDEX_2_DAY_SATURDAY_DELIVERY" => "FedEx 2 Day Saturday Delivery",
28
- "STANDARD_OVERNIGHT" => "FedEx Standard Overnight",
29
- "FIRST_OVERNIGHT" => "FedEx First Overnight",
30
- "FIRST_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx First Overnight Saturday Delivery",
31
- "FEDEX_EXPRESS_SAVER" => "FedEx Express Saver",
32
- "FEDEX_1_DAY_FREIGHT" => "FedEx 1 Day Freight",
33
- "FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 1 Day Freight Saturday Delivery",
34
- "FEDEX_2_DAY_FREIGHT" => "FedEx 2 Day Freight",
35
- "FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 2 Day Freight Saturday Delivery",
36
- "FEDEX_3_DAY_FREIGHT" => "FedEx 3 Day Freight",
37
- "FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 3 Day Freight Saturday Delivery",
38
- "INTERNATIONAL_PRIORITY" => "FedEx 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" => "FedEx International Economy",
41
- "INTERNATIONAL_FIRST" => "FedEx International First",
42
- "INTERNATIONAL_PRIORITY_FREIGHT" => "FedEx International Priority Freight",
43
- "INTERNATIONAL_ECONOMY_FREIGHT" => "FedEx International Economy Freight",
44
- "GROUND_HOME_DELIVERY" => "FedEx Ground Home Delivery",
45
- "FEDEX_GROUND" => "FedEx Ground",
46
- "INTERNATIONAL_GROUND" => "FedEx 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" => "FEDEX_ENVELOPE",
51
- "fedex_pak" => "FEDEX_PAK",
52
- "fedex_box" => "FEDEX_BOX",
53
- "fedex_tube" => "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" => "YOUR_PACKAGING"
55
+ "your_packaging" => "YOUR_PACKAGING"
57
56
  }
58
57
 
59
58
  DropoffTypes = {
60
- 'regular_pickup' => 'REGULAR_PICKUP',
61
- 'request_courier' => 'REQUEST_COURIER',
62
- 'dropbox' => 'DROP_BOX',
59
+ 'regular_pickup' => 'REGULAR_PICKUP',
60
+ 'request_courier' => 'REQUEST_COURIER',
61
+ 'dropbox' => 'DROP_BOX',
63
62
  'business_service_center' => 'BUSINESS_SERVICE_CENTER',
64
- 'station' => 'STATION'
63
+ 'station' => 'STATION'
65
64
  }
66
65
 
67
66
  PaymentTypes = {
68
- 'sender' => 'SENDER',
69
- 'recipient' => 'RECIPIENT',
67
+ 'sender' => 'SENDER',
68
+ 'recipient' => 'RECIPIENT',
70
69
  'third_party' => 'THIRDPARTY',
71
- 'collect' => 'COLLECT'
70
+ 'collect' => 'COLLECT'
72
71
  }
73
-
72
+
74
73
  PackageIdentifierTypes = {
75
- 'tracking_number' => 'TRACKING_NUMBER_OR_DOORTAG',
76
- 'door_tag' => 'TRACKING_NUMBER_OR_DOORTAG',
77
- 'rma' => 'RMA',
78
- 'ground_shipment_id' => 'GROUND_SHIPMENT_ID',
79
- 'ground_invoice_number' => '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' => 'GROUND_PO',
82
- 'express_reference' => 'EXPRESS_REFERENCE',
83
- 'express_mps_master' => '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, :password, :account, :login]
93
+ [:key, :account, :meter, :password]
95
94
  end
96
-
95
+
97
96
  def find_rates(origin, destination, packages, options = {})
98
- options = @options.update(options)
99
- packages = Array(packages)
100
-
101
- rate_request = build_rate_request(origin, destination, packages, options)
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 = @options.update(options)
110
-
122
+ options = @options.update(options)
111
123
  tracking_request = build_tracking_request(tracking_number, options)
112
- response = commit(save_request(tracking_request), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
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
- xml_request = XmlNode.new('RateRequest', 'xmlns' => 'http://fedex.com/ws/rate/v6') do |root_node|
121
- root_node << build_request_header
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
- # Version
124
- root_node << XmlNode.new('Version') do |version_node|
125
- version_node << XmlNode.new('ServiceId', 'crs')
126
- version_node << XmlNode.new('Major', '6')
127
- version_node << XmlNode.new('Intermediate', '0')
128
- version_node << XmlNode.new('Minor', '0')
129
- end
130
-
131
- # Returns delivery dates
132
- root_node << XmlNode.new('ReturnTransitAndCommit', true)
133
- # Returns saturday delivery shipping options when available
134
- root_node << XmlNode.new('VariableOptions', 'SATURDAY_DELIVERY')
135
-
136
- root_node << XmlNode.new('RequestedShipment') do |rs|
137
- rs << XmlNode.new('ShipTimestamp', Time.now)
138
- rs << XmlNode.new('DropoffType', options[:dropoff_type] || 'REGULAR_PICKUP')
139
- rs << XmlNode.new('PackagingType', options[:packaging_type] || 'YOUR_PACKAGING')
140
-
141
- rs << build_location_node('Shipper', (options[:shipper] || origin))
142
- rs << build_location_node('Recipient', destination)
143
- if options[:shipper] and options[:shipper] != origin
144
- rs << build_location_node('Origin', origin)
145
- end
146
-
147
- rs << XmlNode.new('RateRequestTypes', 'ACCOUNT')
148
- rs << XmlNode.new('PackageCount', packages.size)
149
- packages.each do |pkg|
150
- rs << XmlNode.new('RequestedPackages') do |rps|
151
- rps << XmlNode.new('Weight') do |tw|
152
- tw << XmlNode.new('Units', imperial ? 'LB' : 'KG')
153
- tw << XmlNode.new('Value', [((imperial ? pkg.lbs : pkg.kgs).to_f*1000).round/1000.0, 0.1].max)
154
- end
155
- rps << XmlNode.new('Dimensions') do |dimensions|
156
- [:length,:width,:height].each do |axis|
157
- value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
158
- dimensions << XmlNode.new(axis.to_s.capitalize, value.ceil)
159
- end
160
- dimensions << XmlNode.new('Units', imperial ? 'IN' : 'CM')
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
- end
164
-
165
- end
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
- xml_request.to_s
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 build_request_header
195
- web_authentication_detail = XmlNode.new('WebAuthenticationDetail') do |wad|
196
- wad << XmlNode.new('UserCredential') do |uc|
197
- uc << XmlNode.new('Key', @options[:key])
198
- uc << XmlNode.new('Password', @options[:password])
199
- end
200
- end
201
-
202
- client_detail = XmlNode.new('ClientDetail') do |cd|
203
- cd << XmlNode.new('AccountNumber', @options[:account])
204
- cd << XmlNode.new('MeterNumber', @options[:login])
205
- end
206
-
207
- trasaction_detail = XmlNode.new('TransactionDetail') do |td|
208
- td << XmlNode.new('CustomerTransactionId', 'ActiveShipping') # TODO: Need to do something better with this..
209
- end
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
- location_node = XmlNode.new(name) do |xml_node|
216
- xml_node << XmlNode.new('Address') do |address_node|
217
- address_node << XmlNode.new('PostalCode', location.postal_code)
218
- address_node << XmlNode.new("CountryCode", location.country_code(:alpha2))
219
-
220
- address_node << XmlNode.new("Residential", true) unless location.commercial?
221
- end
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 = REXML::Document.new(response)
230
- root_node = xml.elements['RateReply']
231
-
361
+
362
+ xml = Nokogiri::XML(response).remove_namespaces!
363
+
232
364
  success = response_success?(xml)
233
365
  message = response_message(xml)
234
-
235
- root_node.elements.each('RateReplyDetails') do |rated_shipment|
236
- service_code = rated_shipment.get_text('ServiceType').to_s
237
- is_saturday_delivery = rated_shipment.get_text('AppliedOptions').to_s == 'SATURDAY_DELIVERY'
238
- service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
239
-
240
- currency = handle_uk_currency(rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').to_s)
241
- rate_estimates << RateEstimate.new(origin, destination, @@name,
242
- self.class.service_name_for_code(service_type),
243
- :service_code => service_code,
244
- :total_price => rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').to_s.to_f,
245
- :currency => currency,
246
- :packages => packages,
247
- :delivery_range => [rated_shipment.get_text('DeliveryTimestamp').to_s] * 2)
248
- end
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 => response,
302
- :request => last_request,
457
+ :xml => response,
458
+ :request => last_request,
303
459
  :shipment_events => shipment_events,
304
- :destination => destination,
460
+ :destination => destination,
305
461
  :tracking_number => tracking_number
306
462
  )
307
463
  end
308
-
309
- def response_status_node(document)
310
- document.elements['/*/Notifications/']
464
+
465
+ def response_status_node(xml)
466
+ xml.ProcessShipmentReply
311
467
  end
312
-
313
- def response_success?(document)
314
- %w{SUCCESS WARNING NOTE}.include? response_status_node(document).get_text('Severity').to_s
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(document)
318
- response_node = response_status_node(document)
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.gsub("\n",''))
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