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 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