fedex_ship 0.1.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/fedex_ship-0.1.0.iml +22 -0
  5. data/.idea/misc.xml +7 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.idea/workspace.xml +56 -0
  9. data/.rspec +2 -0
  10. data/Gemfile +5 -0
  11. data/Rakefile +7 -0
  12. data/Readme.md +496 -0
  13. data/fedex_ship.gemspec +28 -0
  14. data/lib/fedex_ship.rb +55 -0
  15. data/lib/fedex_ship/address.rb +31 -0
  16. data/lib/fedex_ship/credentials.rb +26 -0
  17. data/lib/fedex_ship/document.rb +51 -0
  18. data/lib/fedex_ship/ground_manifest.rb +25 -0
  19. data/lib/fedex_ship/helpers.rb +20 -0
  20. data/lib/fedex_ship/label.rb +71 -0
  21. data/lib/fedex_ship/rate.rb +38 -0
  22. data/lib/fedex_ship/request/address.rb +97 -0
  23. data/lib/fedex_ship/request/base.rb +443 -0
  24. data/lib/fedex_ship/request/delete.rb +76 -0
  25. data/lib/fedex_ship/request/document.rb +45 -0
  26. data/lib/fedex_ship/request/ground_close.rb +73 -0
  27. data/lib/fedex_ship/request/label.rb +29 -0
  28. data/lib/fedex_ship/request/logs_fedex.rb +74 -0
  29. data/lib/fedex_ship/request/pickup.rb +135 -0
  30. data/lib/fedex_ship/request/pickup_availability.rb +102 -0
  31. data/lib/fedex_ship/request/rate.rb +94 -0
  32. data/lib/fedex_ship/request/service_availability.rb +86 -0
  33. data/lib/fedex_ship/request/shipment.rb +249 -0
  34. data/lib/fedex_ship/request/tracking_information.rb +119 -0
  35. data/lib/fedex_ship/shipment.rb +115 -0
  36. data/lib/fedex_ship/tracking_information.rb +54 -0
  37. data/lib/fedex_ship/tracking_information/event.rb +24 -0
  38. data/lib/fedex_ship/version.rb +6 -0
  39. data/spec/config/fedex_credentials.example.yml +13 -0
  40. data/spec/lib/fedex_ship/address_spec.rb +59 -0
  41. data/spec/lib/fedex_ship/delete_spec.rb +26 -0
  42. data/spec/lib/fedex_ship/document_spec.rb +177 -0
  43. data/spec/lib/fedex_ship/ground_close_spec.rb +42 -0
  44. data/spec/lib/fedex_ship/label_spec.rb +73 -0
  45. data/spec/lib/fedex_ship/pickup_availability_spec.rb +19 -0
  46. data/spec/lib/fedex_ship/pickup_spec.rb +32 -0
  47. data/spec/lib/fedex_ship/rate_spec.rb +216 -0
  48. data/spec/lib/fedex_ship/service_availability_spec.rb +20 -0
  49. data/spec/lib/fedex_ship/shipment_spec.rb +86 -0
  50. data/spec/lib/fedex_ship/track_spec.rb +67 -0
  51. data/spec/spec_helper.rb +12 -0
  52. data/spec/support/credentials.rb +15 -0
  53. data/spec/support/vcr.rb +14 -0
  54. metadata +193 -0
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'fedex_ship/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'fedex_ship'
7
+ s.version = FedexShip::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Jazmin Schroeder','Shubham Sharma']
10
+ s.email = ['shubhamsharma4587@gmail.com']
11
+ s.homepage = 'https://github.com/shubhamsharma4587/fedex_ship'
12
+ s.summary = %q{Fedex Web Services}
13
+ s.description = %q{Provides an interface to Upgraded Fedex Web Services}
14
+
15
+ s.add_dependency 'httparty', '>= 0.13.7'
16
+ s.add_dependency 'nokogiri', '>= 1.5.6'
17
+
18
+ s.add_development_dependency "rspec", '~> 3.1'
19
+ s.add_development_dependency 'vcr', '~> 2.0'
20
+ s.add_development_dependency 'webmock', '~> 1.8.0'
21
+ s.add_development_dependency 'pry'
22
+ s.add_development_dependency 'rake'
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ['lib']
28
+ end
@@ -0,0 +1,55 @@
1
+ require 'fedex_ship/shipment'
2
+
3
+ # Get shipping rates trough Fedex Web Services
4
+ #
5
+ # In order to use the API you will need to apply for developer/production credentials,
6
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
7
+ #
8
+ # ===Usage example
9
+ # #Use your own Fedex Keys
10
+ # fedex = FedexShip::Shipment.new(:key => 'xxx',
11
+ # :password => 'xxxx',
12
+ # :account_number => 'xxxx',
13
+ # :meter => 'xxx',
14
+ # :mode=>['production'|'development'])
15
+ # shipper = {:name => "Sender",
16
+ # :company => "Company",
17
+ # :phone_number => "555-555-5555",
18
+ # :address => "Main Street",
19
+ # :city => "Harrison",
20
+ # :state => "AR",
21
+ # :postal_code => "72601",
22
+ # :country_code => "US" }
23
+ #
24
+ # recipient = { :name => "Recipient",
25
+ # :company => "Company",
26
+ # :phone_number => "555-555-5555",
27
+ # :address => "Main Street",
28
+ # :city => "City",
29
+ # :state => "ST",
30
+ # :postal_code => "55555",
31
+ # :country_code => "US",
32
+ # :residential => "false" }
33
+ # packages = []
34
+ # packages << { :weight => {:units => "LB", :value => 2},
35
+ # :dimensions => {:length => 10, :width => 5, :height => 4, :units => "IN" } }
36
+ # packages << { :weight => {:units => "LB", :value => 6},
37
+ # :dimensions => {:length => 5, :width => 5, :height => 4, :units => "IN" } }
38
+ # # "YOUR PACKAGING" and "REGULAR PICKUP" are the default options for all shipments but you can easily change them by passing an extra hash for # shipping_options
39
+ # shipping_options = { :packaging_type => "YOUR_PACKAGING", :drop_off_type => "REGULAR_PICKUP" }
40
+ # rate = fedex.rate({:shipper=>shipper, :recipient => recipient, :packages => packages, :service_type => "FEDEX_GROUND", :shipping_options => #shipping_options})
41
+ #
42
+ # $ <FedexShip::Rate:0x1019ba5f8 @total_net_charge="34.03",
43
+ # @total_surcharges="1.93",
44
+ # @total_billing_weight="8.0 LB",
45
+ # @total_taxes="0.0",
46
+ # @rate_type="PAYOR_ACCOUNT_PACKAGE",
47
+ # @total_base_charge="32.1",
48
+ # @total_freight_discounts=nil,
49
+ # @total_net_freight="32.1",
50
+ # @rate_zone="51">
51
+ module FedexShip
52
+ require 'fedex_ship/version'
53
+ #Exceptions: FedexShip::RateError
54
+ class RateError < StandardError; end
55
+ end
@@ -0,0 +1,31 @@
1
+ module FedexShip
2
+ class Address
3
+
4
+ attr_reader :changes, :score, :confirmed, :available, :status, :residential,
5
+ :business, :company, :street_lines, :city, :state,
6
+ :province_code, :postal_code, :country_code
7
+
8
+ def initialize(options)
9
+ @changes = options[:changes]
10
+ @score = options[:score].to_i
11
+ @confirmed = options[:delivery_point_validation] == "CONFIRMED"
12
+ @available = options[:delivery_point_validation] != "UNAVAILABLE"
13
+
14
+ @status = options[:residential_status]
15
+ @residential = status == "RESIDENTIAL"
16
+ @business = status == "BUSINESS"
17
+
18
+ address = options[:address]
19
+
20
+ @company = options[:company_name]
21
+ @street_lines = address[:street_lines]
22
+ @city = address[:city]
23
+ @state = address[:state_or_province_code]
24
+ @province_code = address[:state_or_province_code]
25
+ @postal_code = address[:postal_code]
26
+ @country_code = address[:country_code]
27
+
28
+ @options = options
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ require 'fedex_ship/helpers'
2
+
3
+ module FedexShip
4
+ class Credentials
5
+ include Helpers
6
+ attr_reader :key, :password, :account_number, :meter, :mode
7
+
8
+ # In order to use Fedex rates API you must first apply for a developer(and later production keys),
9
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
10
+ # @param [String] key - Fedex web service key
11
+ # @param [String] password - Fedex password
12
+ # @param [String] account_number - Fedex account_number
13
+ # @param [String] meter - Fedex meter number
14
+ # @param [String] mode - [development/production]
15
+ #
16
+ # return a FedexShip::Credentials object
17
+ def initialize(options={})
18
+ requires!(options, :key, :password, :account_number, :meter, :mode)
19
+ @key = options[:key]
20
+ @password = options[:password]
21
+ @account_number = options[:account_number]
22
+ @meter = options[:meter]
23
+ @mode = options[:mode]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ module FedexShip
2
+ class Document
3
+ attr_reader :tracking_number, :filenames, :response_details
4
+
5
+ # Initialize FedexShip::Document Object
6
+ # @param [Hash] options
7
+ def initialize(shipment_details = {})
8
+ @response_details = shipment_details[:process_shipment_reply]
9
+ @filenames = shipment_details[:filenames]
10
+
11
+ # extract label and tracking number
12
+ package_details = @response_details[:completed_shipment_detail][:completed_package_details]
13
+ label = package_details[:label]
14
+ @tracking_number = package_details[:tracking_ids][:tracking_number]
15
+
16
+ # extract shipment documents
17
+ shipment_documents = @response_details[:completed_shipment_detail][:shipment_documents] || []
18
+
19
+ # unify iteration interface
20
+ unless shipment_documents.kind_of?(Array)
21
+ shipment_documents = [shipment_documents]
22
+ end
23
+
24
+ # keeps the filenames which actually saved
25
+ save(@filenames[:label], label)
26
+
27
+ # save shipment documents
28
+ shipment_documents.each do |doc|
29
+ doc_type = doc[:type].downcase.to_sym
30
+ save(@filenames[doc_type], doc)
31
+ end
32
+ end
33
+
34
+ def save(path, content)
35
+ return unless path && has_image?(content)
36
+
37
+ image = Base64.decode64(content[:parts][:image])
38
+ full_path = Pathname.new(path)
39
+ File.open(full_path, 'wb') do|f|
40
+ f.write(image)
41
+ end
42
+
43
+ full_path
44
+ end
45
+
46
+ def has_image?(content)
47
+ content[:parts] && content[:parts][:image]
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ require 'base64'
2
+ require 'pathname'
3
+
4
+ module FedexShip
5
+ class GroundManifest
6
+ attr_reader :manifest_data, :filename
7
+
8
+ # Initialize FedexShip::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
@@ -0,0 +1,20 @@
1
+ module FedexShip
2
+ module Helpers
3
+
4
+ private
5
+ # String or :symbol to CamelCase
6
+ def camelize(s)
7
+ # s.to_s.split('_').map { |e| e.capitalize }.join('')
8
+ s.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
9
+ end
10
+
11
+ # Helper method to validate required fields
12
+ def requires!(hash, *params)
13
+ params.each { |param| raise RateError, "Missing Required Parameter #{param}" if hash[param].nil? }
14
+ end
15
+
16
+ def underscorize(key) #:nodoc:
17
+ key.to_s.sub(/^(v[0-9]+|ns):/, "").gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,71 @@
1
+ require 'base64'
2
+ require 'pathname'
3
+
4
+ module FedexShip
5
+ class Label
6
+ attr_accessor :options, :image, :response_details
7
+
8
+ # Initialize FedexShip::Label Object
9
+ # @param [Hash] options
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
21
+ @options[:format] = label_details[:format]
22
+ @options[:file_name] = label_details[:file_name]
23
+ @image = Base64.decode64(options[:parts][:image]) if has_image?
24
+
25
+ if file_name = @options[:file_name]
26
+ save(file_name, false)
27
+ end
28
+ end
29
+
30
+ def name
31
+ [tracking_number, format].join('.')
32
+ end
33
+
34
+ def format
35
+ options[:format]
36
+ end
37
+
38
+ def file_name
39
+ options[:file_name]
40
+ end
41
+
42
+ def tracking_number
43
+ options[:tracking_number]
44
+ end
45
+
46
+ def has_image?
47
+ options[:parts] && options[:parts][:image]
48
+ end
49
+
50
+ def save(path, append_name = true)
51
+ return unless has_image?
52
+
53
+ full_path = Pathname.new(path)
54
+ full_path = full_path.join(name) if append_name
55
+
56
+ File.open(full_path, 'wb') do|f|
57
+ f.write(@image)
58
+ end
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
70
+ end
71
+ end
@@ -0,0 +1,38 @@
1
+ module FedexShip
2
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for a complete list of values returned from the API
3
+ #
4
+ # Rate totals are contained in the node
5
+ # response[:rate_reply][:rate_reply_details][:rated_shipment_details]
6
+ class Rate
7
+ # Initialize FedexShip::Rate Object
8
+ # @param [Hash] options
9
+ #
10
+ #
11
+ # return [FedexShip::Rate Object]
12
+ # @rate_type #Type used for this specific set of rate data
13
+ # @rate_zone #Indicates the rate zone used(based on origin and destination)
14
+ # @total_billing_weight #The weight used to calculate these rates
15
+ # @total_freight_discounts #The toal discounts used in the rate calculation
16
+ # @total_net_charge #The net charge after applying all discounts and surcharges
17
+ # @total_taxes #Total of the transportation-based taxes
18
+ # @total_net_freight #The freight charge minus dicounts
19
+ # @total_surcharges #The total amount of all surcharges applied to this shipment
20
+ # @total_base_charge #The 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
+ def initialize(options = {})
23
+ @service_type = options[:service_type]
24
+ @transit_time = options[:transit_time]
25
+ @rate_type = options[:rate_type]
26
+ @rate_zone = options[:rate_zone]
27
+ @total_billing_weight = "#{options[:total_billing_weight][:value]} #{options[:total_billing_weight][:units]}"
28
+ @total_freight_discounts = options[:total_freight_discounts]
29
+ @total_net_charge = options[:total_net_charge][:amount]
30
+ @total_taxes = options[:total_taxes][:amount]
31
+ @total_net_freight = options[:total_net_freight][:amount]
32
+ @total_surcharges = options[:total_surcharges][:amount]
33
+ @total_base_charge = options[:total_base_charge][:amount]
34
+ @total_net_fedex_charge = (options[:total_net_fe_dex_charge]||{})[:amount]
35
+ @total_rebates = (options[:total_rebates]||{})[:amount]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,97 @@
1
+ require 'fedex_ship/request/base'
2
+ require 'fedex_ship/address'
3
+ require 'fileutils'
4
+
5
+ module FedexShip
6
+ module Request
7
+ class Address < Base
8
+ def initialize(credentials, options={})
9
+ requires!(options, :address)
10
+ @credentials = credentials
11
+ @address = options[:address]
12
+
13
+ @address[:address] ||= @address[:street]
14
+ end
15
+
16
+ def process_request
17
+ api_response = self.class.post(api_url, :body => build_xml)
18
+ puts api_response if @debug == true
19
+ response = parse_response(api_response)
20
+ if success?(response)
21
+ options = response[:address_validation_reply][:address_results][:proposed_address_details]
22
+ options = options.first if options.is_a? Array
23
+ FedexShip::Address.new(options)
24
+ else
25
+ error_message = if response[:address_validation_reply]
26
+ [response[:address_validation_reply][:notifications]].flatten.first[:message]
27
+ else
28
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n--#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
29
+ end rescue $1
30
+ raise RateError, error_message
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Build xml Fedex Web Service request
37
+ def build_xml
38
+ builder = Nokogiri::XML::Builder.new do |xml|
39
+ xml.AddressValidationRequest(:xmlns => "http://fedex.com/ws/addressvalidation/v#{service[:version]}"){
40
+ add_web_authentication_detail(xml)
41
+ add_client_detail(xml)
42
+ add_version(xml)
43
+ add_request_timestamp(xml)
44
+ add_address_validation_options(xml)
45
+ add_address_to_validate(xml)
46
+ }
47
+ end
48
+ builder.doc.root.to_xml
49
+ end
50
+
51
+ def add_request_timestamp(xml)
52
+ timestamp = Time.now
53
+
54
+ # Calculate current timezone offset manually.
55
+ # Ruby <= 1.9.2 does not support this in Time#strftime
56
+ #
57
+ utc_offest = "#{timestamp.gmt_offset < 0 ? "-" : "+"}%02d:%02d" %
58
+ (timestamp.gmt_offset / 60).abs.divmod(60)
59
+ timestamp = timestamp.strftime("%Y-%m-%dT%H:%M:%S") + utc_offest
60
+
61
+ xml.RequestTimestamp timestamp
62
+ end
63
+
64
+ def add_address_validation_options(xml)
65
+ xml.Options{
66
+ xml.CheckResidentialStatus true
67
+ }
68
+ end
69
+
70
+ def add_address_to_validate(xml)
71
+ xml.AddressesToValidate{
72
+ xml.CompanyName @address[:company] unless @address[:company].nil? or @address[:company].empty?
73
+ xml.Address{
74
+ Array(@address[:address]).take(2).each do |address_line|
75
+ xml.StreetLines address_line
76
+ end
77
+ xml.City @address[:city]
78
+ xml.StateOrProvinceCode @address[:state]
79
+ xml.PostalCode @address[:postal_code]
80
+ xml.CountryCode @address[:country]
81
+ }
82
+ }
83
+ end
84
+
85
+ def service
86
+ { :id => 'aval', :version => 2 }
87
+ end
88
+
89
+ # Successful request
90
+ def success?(response)
91
+ response[:address_validation_reply] &&
92
+ %w{SUCCESS WARNING NOTE}.include?(response[:address_validation_reply][:highest_severity])
93
+ end
94
+
95
+ end
96
+ end
97
+ end