fedex 3.6.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/Readme.md CHANGED
@@ -70,6 +70,21 @@ shipping_options = {
70
70
  }
71
71
  ```
72
72
 
73
+ If you pass a non-nil `:return_reason` as part of the shipping options, you will create
74
+ a return shipment. The request to fedex will include the following additional XML.
75
+
76
+ ```xml
77
+ <SpecialServicesRequested>
78
+ <SpecialServiceTypes>RETURN_SHIPMENT</SpecialServiceTypes>
79
+ <ReturnShipmentDetail>
80
+ <ReturnType>PRINT_RETURN_LABEL</ReturnType>
81
+ <Rma>
82
+ <Reason>YOUR RETURN REASON HERE</Reason>
83
+ </Rma>
84
+ </ReturnShipmentDetail>
85
+ </SpecialServicesRequested>
86
+ ```
87
+
73
88
  By default the shipping charges will be assigned to the sender. If you may
74
89
  change this by passing an extra hash of payment options.
75
90
 
@@ -131,7 +146,7 @@ ship = fedex.ship(:shipper=>shipper,
131
146
  :packages => packages,
132
147
  :service_type => "FEDEX_GROUND",
133
148
  :shipping_options => shipping_options)
134
- puts ship[:completed_shipment_detail][:operational_detail] [:transit_time]
149
+ puts ship[:completed_shipment_detail][:operational_detail][:transit_time]
135
150
  ```
136
151
  Above code will give you the transit time.
137
152
 
@@ -189,6 +204,97 @@ shipment.save!
189
204
 
190
205
  Documentation for setting up Paperclip with Amazon S3 can be found in the Paperclip [README](https://github.com/thoughtbot/paperclip/#storage).
191
206
 
207
+ ### ** Generate shipping labels for multi-package shipments (MPS) **
208
+
209
+ Multiple packages for a single pick-up, destination and payer can be combined into a single MPS shipment. The first label will provide a master tracking number which must be used in the subsequent calls for the remaining packages in the shipment.
210
+
211
+ Parameters for the first label:
212
+ ```ruby
213
+ label = fedex.label(
214
+ :filename => file_name,
215
+ :shipper => shipper,
216
+ :recipient => recipient,
217
+ :packages => packages,
218
+ :service_type => service_type,
219
+ :shipping_details => shipping_details,
220
+ :shipping_charges_payment => shipping_charges_payment,
221
+ :customs_clearance_detail => customs_clearance_detail,
222
+ :mps => {:package_count => package_count, :total_weight => total_weight, :sequence_number => '1'}
223
+ )
224
+ ```
225
+
226
+ Parameters for labels 2 through 'n':
227
+ ```ruby
228
+ fedex.label(
229
+ :filename => file_name,
230
+ :shipper => shipper,
231
+ :recipient => recipient,
232
+ :packages => packages,
233
+ :service_type => service_type,
234
+ :shipping_details => shipping_details,
235
+ :shipping_charges_payment => shipping_charges_payment,
236
+ :customs_clearance_detail => customs_clearance_detail,
237
+ :mps => {
238
+ :master_tracking_id => {:tracking_id_type => 'FEDEX', :tracking_number =>tracking_number},
239
+ :package_count => package_count,
240
+ :total_weight => {
241
+ :value => total_weight,
242
+ :units => 'KG'
243
+ }
244
+ :sequence_number => 'n'
245
+ }
246
+ )
247
+ ```
248
+
249
+ ### ** Create COD Shipment **
250
+
251
+ To create a Cash On Delivery label for a shipment:
252
+
253
+ change "commerical_invoice = {:purpose => 'SOLD'}" in customs_clearance_detail
254
+
255
+ add shipping_options with {:cod => {:currency => "currency", :amount => "amount", :collection_type => 'PAYMENT COLLECTION TYPE'}
256
+
257
+ PAYMENT COLLECTION TYPE - CASH, CHEQUE, DEMAND DRAFT
258
+
259
+ ### ** To add multiple commodities in customs_clearance_detail
260
+
261
+ use this format commodities_1 .... commodities_N
262
+
263
+ example
264
+
265
+ ```
266
+
267
+ customs_clearance_detail['commodites_1']
268
+ customs_clearance_detail['commodites_2']
269
+
270
+ ```
271
+
272
+ ### ** Masking shipper details in label **
273
+
274
+ this allows you hide shipper details on the label
275
+
276
+ Add customer_specified_detail = {:masked_data_1 => 'SOMETHING', :masked_data_2 => 'SOMETHING'} in :label_specification key
277
+
278
+ Example
279
+
280
+ ```
281
+ customer_specified_detail = {
282
+ :masked_data_1 => "SHIPPER_ACCOUNT_NUMBER",
283
+ :masked_data_2 => "TRANSPORTATION_CHARGES_PAYOR_ACCOUNT_NUMBER",
284
+ :masked_data_3 => "DUTIES_AND_TAXES_PAYOR_ACCOUNT_NUMBER"
285
+ }
286
+
287
+ ```
288
+
289
+ ### ** Delete a shipment **
290
+
291
+ If you do not intend to use a label you should delete it. This will notify FedEx that you will not be using the label and they won't charge you.
292
+
293
+ To delete a shipment:
294
+
295
+ ```ruby
296
+ fedex.delete(:tracking_number => "1234567890123")
297
+ ```
192
298
 
193
299
  ### ** Tracking a shipment **
194
300
 
@@ -219,7 +325,7 @@ To verify an address is valid and deliverable:
219
325
  ```ruby
220
326
 
221
327
  address = {
222
- :address => "5 Elm Street",
328
+ :street => "5 Elm Street",
223
329
  :city => "Norwalk",
224
330
  :state => "CT",
225
331
  :postal_code => "06850",
@@ -238,12 +344,58 @@ address_result.postal_code
238
344
  # => "06850-3901"
239
345
  ```
240
346
 
347
+ ### ** Requesting a Pickup **
348
+
349
+ To request a pickup:
350
+
351
+ ```ruby
352
+
353
+ pickup = fedex.pickup(:carrier_code => 'FDXE',
354
+ :packages => {:weight => {:units => "LB", :value => 10}, :count => 2},
355
+ :ready_timestamp => Date.today.to_datetime + 1.375,
356
+ :close_time => Date.today.to_time + 60 * 60 * 17)
357
+ puts pickup[:pickup_confirmation_number]
358
+ ```
359
+
360
+ ### ** Getting pickup availability details **
361
+
362
+ To check for pickup availability:
363
+
364
+ ```ruby
365
+
366
+ dispatch = Date.tomorrow.strftime('%Y-%m-%d')
367
+
368
+ pickup_availability = fedex.pickup_availability(:country_code => 'IN',
369
+ :postal_code => '400061',
370
+ :request_type => 'FUTURE_DAY',
371
+ :dispatch_date => dispatch_date,
372
+ :carrier_code => 'FDXE')
373
+
374
+ puts pickup_availability[:options]
375
+ ```
376
+
377
+ ### ** Getting service availability **
378
+
379
+ To check service availability:
380
+
381
+ ```ruby
382
+
383
+ origin = {:postal_code => '400012', :country_code => 'IN'}
384
+ destination = { :postal_code => '400020', :country_code => 'IN'}
385
+ fedex_service_hash = {:origin => origin, :destination => destination, :ship_date => '2014-06-28', :carrier_code => 'FDXE'}
386
+
387
+ service = fedex.service_availability(fedex_service_hash)
388
+
389
+ puts service[:options]
390
+ ```
391
+
241
392
  # Services/Options Available
242
393
 
243
394
  ```ruby
244
395
  Fedex::Shipment::SERVICE_TYPES
245
396
  Fedex::Shipment::PACKAGING_TYPES
246
397
  Fedex::Shipment::DROP_OFF_TYPES
398
+ Fedex::Shipment::CARRIER_CODES
247
399
  ````
248
400
 
249
401
  # Contributors:
@@ -259,6 +411,7 @@ Fedex::Shipment::DROP_OFF_TYPES
259
411
  - [yevgenko] (https://github.com/yevgenko) (Yevgeniy Viktorov)
260
412
  - [smartacus] (https://github.com/smartacus) (Michael Lippold)
261
413
  - [jonathandean] (https://github.com/jonathandean) (Jonathan Dean)
414
+ - [chirag7jain] (https://github.com/chirag7jain) (Chirag Jain)
262
415
  - and more... (https://github.com/jazminschroeder/fedex/graphs/contributors)
263
416
 
264
417
  # Copyright/License:
data/fedex.gemspec CHANGED
@@ -16,14 +16,14 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.license = 'MIT'
18
18
 
19
- s.add_dependency 'httparty', '~> 0.13.0'
20
- s.add_dependency 'nokogiri', '~> 1.6.0'
19
+ s.add_dependency 'httparty', '>= 0.8.3'
20
+ s.add_dependency 'nokogiri', '>= 1.5.6'
21
21
 
22
- s.add_development_dependency "rspec", '~> 2.9.0'
22
+ s.add_development_dependency "rspec", '~> 3.0.0'
23
23
  s.add_development_dependency 'vcr', '~> 2.0.0'
24
24
  s.add_development_dependency 'webmock', '~> 1.8.0'
25
25
  s.add_development_dependency 'pry'
26
- # s.add_runtime_dependency "rest-client"
26
+ s.add_development_dependency 'rake'
27
27
 
28
28
  s.files = `git ls-files`.split("\n")
29
29
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,25 @@
1
+ require 'base64'
2
+ require 'pathname'
3
+
4
+ module Fedex
5
+ class GroundManifest
6
+ attr_reader :manifest_data, :filename
7
+
8
+ # Initialize Fedex::GroundManifest Object
9
+ # @param [Hash] options
10
+ def initialize(options = {})
11
+ puts options
12
+ @filename = options[:filename]
13
+ @manifest_data = Base64.decode64(options[:manifest][:file])
14
+ save
15
+ end
16
+
17
+ def save
18
+ return if manifest_data.nil? || filename.nil?
19
+ full_path = Pathname.new(filename)
20
+ File.open(full_path, 'wb') do |f|
21
+ f.write(manifest_data)
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/fedex/label.rb CHANGED
@@ -7,14 +7,19 @@ module Fedex
7
7
 
8
8
  # Initialize Fedex::Label Object
9
9
  # @param [Hash] options
10
- def initialize(label_details = {})
11
- @response_details = label_details[:process_shipment_reply]
12
- package_details = label_details[:process_shipment_reply][:completed_shipment_detail][:completed_package_details]
13
- @options = package_details[:label]
10
+ def initialize(label_details = {}, associated_shipments = false)
11
+ if associated_shipments
12
+ package_details = label_details
13
+ @options = package_details[:label]
14
+ @options[:tracking_number] = package_details[:tracking_id]
15
+ else
16
+ @response_details = label_details[:process_shipment_reply]
17
+ package_details = label_details[:process_shipment_reply][:completed_shipment_detail][:completed_package_details]
18
+ @options = package_details[:label]
19
+ @options[:tracking_number] = package_details[:tracking_ids][:tracking_number]
20
+ end
14
21
  @options[:format] = label_details[:format]
15
- @options[:tracking_number] = package_details[:tracking_ids][:tracking_number]
16
22
  @options[:file_name] = label_details[:file_name]
17
-
18
23
  @image = Base64.decode64(options[:parts][:image]) if has_image?
19
24
 
20
25
  if file_name = @options[:file_name]
@@ -52,5 +57,15 @@ module Fedex
52
57
  f.write(@image)
53
58
  end
54
59
  end
60
+
61
+ def associated_shipments
62
+ if (label_details = @response_details[:completed_shipment_detail][:associated_shipments])
63
+ label_details[:format] = format
64
+ label_details[:file_name] = file_name
65
+ Label.new(label_details, true)
66
+ else
67
+ nil
68
+ end
69
+ end
55
70
  end
56
71
  end
data/lib/fedex/rate.rb CHANGED
@@ -18,9 +18,10 @@ module Fedex
18
18
  # @total_net_freight #The freight charge minus dicounts
19
19
  # @total_surcharges #The total amount of all surcharges applied to this shipment
20
20
  # @total_base_charge #The total base charge
21
- attr_accessor :service_type, :rate_type, :rate_zone, :total_billing_weight, :total_freight_discounts, :total_net_charge, :total_taxes, :total_net_freight, :total_surcharges, :total_base_charge
21
+ attr_accessor :service_type, :transit_time, :rate_type, :rate_zone, :total_billing_weight, :total_freight_discounts, :total_net_charge, :total_taxes, :total_net_freight, :total_surcharges, :total_base_charge
22
22
  def initialize(options = {})
23
23
  @service_type = options[:service_type]
24
+ @transit_time = options[:transit_time]
24
25
  @rate_type = options[:rate_type]
25
26
  @rate_zone = options[:rate_zone]
26
27
  @total_billing_weight = "#{options[:total_billing_weight][:value]} #{options[:total_billing_weight][:units]}"
@@ -34,4 +35,4 @@ module Fedex
34
35
  @total_rebates = (options[:total_rebates]||{})[:amount]
35
36
  end
36
37
  end
37
- end
38
+ end
@@ -12,10 +12,10 @@ module Fedex
12
12
  # If true the rate method will return the complete response from the Fedex Web Service
13
13
  attr_accessor :debug
14
14
  # Fedex Text URL
15
- TEST_URL = "https://gatewaybeta.fedex.com:443/xml/"
15
+ TEST_URL = "https://wsbeta.fedex.com:443/xml/"
16
16
 
17
17
  # Fedex Production URL
18
- PRODUCTION_URL = "https://gateway.fedex.com:443/xml/"
18
+ PRODUCTION_URL = "https://ws.fedex.com:443/xml/"
19
19
 
20
20
  # List of available Service Types
21
21
  SERVICE_TYPES = %w(EUROPE_FIRST_INTERNATIONAL_PRIORITY FEDEX_1_DAY_FREIGHT FEDEX_2_DAY FEDEX_2_DAY_AM FEDEX_2_DAY_FREIGHT FEDEX_3_DAY_FREIGHT FEDEX_EXPRESS_SAVER FEDEX_FIRST_FREIGHT FEDEX_FREIGHT_ECONOMY FEDEX_FREIGHT_PRIORITY FEDEX_GROUND FIRST_OVERNIGHT GROUND_HOME_DELIVERY INTERNATIONAL_ECONOMY INTERNATIONAL_ECONOMY_FREIGHT INTERNATIONAL_FIRST INTERNATIONAL_PRIORITY INTERNATIONAL_PRIORITY_FREIGHT PRIORITY_OVERNIGHT SMART_POST STANDARD_OVERNIGHT)
@@ -35,6 +35,9 @@ module Fedex
35
35
  # List of available Payment Types
36
36
  PAYMENT_TYPE = %w(RECIPIENT SENDER THIRD_PARTY)
37
37
 
38
+ # List of available Carrier Codes
39
+ CARRIER_CODES = %w(FDXC FDXE FDXG FDCC FXFR FXSP)
40
+
38
41
  # In order to use Fedex rates API you must first apply for a developer(and later production keys),
39
42
  # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
40
43
  # @param [String] key - Fedex web service key
@@ -52,6 +55,13 @@ module Fedex
52
55
  @shipping_options = options[:shipping_options] ||={}
53
56
  @payment_options = options[:payment_options] ||={}
54
57
  requires!(@payment_options, :type, :account_number, :name, :company, :phone_number, :country_code) if @payment_options.length > 0
58
+ if options.has_key?(:mps)
59
+ @mps = options[:mps]
60
+ requires!(@mps, :package_count, :total_weight, :sequence_number)
61
+ requires!(@mps, :master_tracking_id) if @mps.has_key?(:sequence_number) && @mps[:sequence_number].to_i >= 2
62
+ else
63
+ @mps = {}
64
+ end
55
65
  # Expects hash with addr and port
56
66
  if options[:http_proxy]
57
67
  self.class.http_proxy options[:http_proxy][:host], options[:http_proxy][:port]
@@ -175,13 +185,32 @@ module Fedex
175
185
  }
176
186
  end
177
187
 
188
+ # Add Master Tracking Id (for MPS Shipping Labels, this is required when requesting labels 2 through n)
189
+ def add_master_tracking_id(xml)
190
+ if @mps.has_key? :master_tracking_id
191
+ xml.MasterTrackingId{
192
+ xml.TrackingIdType @mps[:master_tracking_id][:tracking_id_type]
193
+ xml.TrackingNumber @mps[:master_tracking_id][:tracking_number]
194
+ }
195
+ end
196
+ end
197
+
178
198
  # Add packages to xml request
179
199
  def add_packages(xml)
200
+ add_master_tracking_id(xml) if @mps.has_key? :master_tracking_id
180
201
  package_count = @packages.size
181
- xml.PackageCount package_count
202
+ if @mps.has_key? :package_count
203
+ xml.PackageCount @mps[:package_count]
204
+ else
205
+ xml.PackageCount package_count
206
+ end
182
207
  @packages.each do |package|
183
208
  xml.RequestedPackageLineItems{
184
- xml.GroupPackageCount 1
209
+ if @mps.has_key? :sequence_number
210
+ xml.SequenceNumber @mps[:sequence_number]
211
+ else
212
+ xml.GroupPackageCount 1
213
+ end
185
214
  if package[:insured_value]
186
215
  xml.InsuredValue{
187
216
  xml.Currency package[:insured_value][:currency]
@@ -294,7 +323,14 @@ module Fedex
294
323
  # Build xml nodes dynamically from the hash keys and values
295
324
  def hash_to_xml(xml, hash)
296
325
  hash.each do |key, value|
297
- element = camelize(key)
326
+ key_s_down = key.to_s.downcase
327
+ if key_s_down.match(/^commodities_\d{1,}$/)
328
+ element = 'Commodities'
329
+ elsif key_s_down.match(/^masked_data_\d{1,}$/)
330
+ element = 'MaskedData'
331
+ else
332
+ element = camelize(key)
333
+ end
298
334
  if value.is_a?(Hash)
299
335
  xml.send element do |x|
300
336
  hash_to_xml(x, value)
@@ -0,0 +1,62 @@
1
+ require 'fedex/request/base'
2
+
3
+ module Fedex
4
+ module Request
5
+ class Delete < Base
6
+
7
+ attr_reader :tracking_number
8
+
9
+ def initialize(credentials, options={})
10
+ requires!(options, :tracking_number)
11
+
12
+ @tracking_number = options[:tracking_number]
13
+ @deletion_control = options[:deletion_control] || 'DELETE_ALL_PACKAGES'
14
+ @credentials = credentials
15
+ end
16
+
17
+ def process_request
18
+ api_response = self.class.post(api_url, :body => build_xml)
19
+ puts api_response if @debug == true
20
+ response = parse_response(api_response)
21
+ unless success?(response)
22
+ error_message = if response[:shipment_reply]
23
+ [response[:shipment_reply][:notifications]].flatten.first[:message]
24
+ else
25
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n
26
+ --#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
27
+ end rescue $1
28
+ raise RateError, error_message
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Build xml Fedex Web Service request
35
+ def build_xml
36
+ builder = Nokogiri::XML::Builder.new do |xml|
37
+ xml.DeleteShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v#{service[:version]}"){
38
+ add_web_authentication_detail(xml)
39
+ add_client_detail(xml)
40
+ add_version(xml)
41
+ xml.TrackingId {
42
+ xml.TrackingIdType 'FEDEX'
43
+ xml.TrackingNumber @tracking_number
44
+ }
45
+ xml.DeletionControl @deletion_control
46
+ }
47
+ end
48
+ builder.doc.root.to_xml
49
+ end
50
+
51
+ def service
52
+ { :id => 'ship', :version => Fedex::API_VERSION }
53
+ end
54
+
55
+ # Successful request
56
+ def success?(response)
57
+ response[:shipment_reply] &&
58
+ %w{SUCCESS WARNING NOTE}.include?(response[:shipment_reply][:highest_severity])
59
+ end
60
+ end
61
+ end
62
+ end