fedex 2.0.1 → 2.2.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.
@@ -1,94 +1,29 @@
1
1
  require 'fedex/request/base'
2
2
  require 'fedex/label'
3
+ require 'fedex/request/shipment'
3
4
  require 'fileutils'
4
5
 
5
6
  module Fedex
6
7
  module Request
7
- class Label < Base
8
+ class Label < Shipment
8
9
  def initialize(credentials, options={})
9
10
  super(credentials, options)
10
- requires!(options, :filename)
11
11
  @filename = options[:filename]
12
12
  end
13
13
 
14
- # Sends post request to Fedex web service and parse the response.
15
- # A Fedex::Label object is created if the response is successful and
16
- # a PDF file is created with the label at the specified location.
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
- if success?(response)
22
- label_details = response[:process_shipment_reply][:completed_shipment_detail][:completed_package_details][:label]
23
-
24
- create_pdf(label_details)
25
- Fedex::Label.new(label_details)
26
- else
27
- error_message = if response[:process_shipment_reply]
28
- [response[:process_shipment_reply][:notifications]].flatten.first[:message]
29
- else
30
- api_response["Fault"]["detail"]["fault"]["reason"]
31
- end rescue $1
32
- raise RateError, error_message
33
- end
34
- end
35
-
36
14
  private
37
15
 
38
- # Add information for shipments
39
- def add_requested_shipment(xml)
40
- xml.RequestedShipment{
41
- xml.ShipTimestamp Time.now.utc.iso8601(2)
42
- xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
43
- xml.ServiceType service_type
44
- xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
45
- add_shipper(xml)
46
- add_recipient(xml)
47
- add_shipping_charges_payment(xml)
48
- add_customs_clearance(xml) if @customs_clearance
49
- xml.LabelSpecification {
50
- xml.LabelFormatType "COMMON2D"
51
- xml.ImageType "PDF"
52
- }
53
- xml.RateRequestTypes "ACCOUNT"
54
- add_packages(xml)
55
- }
56
- end
16
+ def success_response(api_response, response)
17
+ super
57
18
 
58
- # Build xml Fedex Web Service request
59
- def build_xml
60
- builder = Nokogiri::XML::Builder.new do |xml|
61
- xml.ProcessShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v10"){
62
- add_web_authentication_detail(xml)
63
- add_client_detail(xml)
64
- add_version(xml)
65
- add_requested_shipment(xml)
66
- }
67
- end
68
- builder.doc.root.to_xml
69
- end
70
-
71
- def create_pdf(label_details)
72
- [label_details[:parts]].flatten.each do |part|
73
- if image = (Base64.decode64(part[:image]) if part[:image])
74
- FileUtils.mkdir_p File.dirname(@filename)
75
- File.open(@filename, 'w') do |file|
76
- file.write image
77
- end
78
- end
79
- end
80
- end
81
-
82
- def service_id
83
- 'ship'
84
- end
19
+ label_details = response.merge!({
20
+ :format => @label_specification[:image_type],
21
+ :file_name => @filename
22
+ })
85
23
 
86
- # Successful request
87
- def success?(response)
88
- response[:process_shipment_reply] &&
89
- %w{SUCCESS WARNING NOTE}.include?(response[:process_shipment_reply][:highest_severity])
24
+ Fedex::Label.new label_details
90
25
  end
91
26
 
92
27
  end
93
28
  end
94
- end
29
+ end
@@ -6,7 +6,7 @@ module Fedex
6
6
  # Sends post request to Fedex web service and parse the response, a Rate object is created if the response is successful
7
7
  def process_request
8
8
  api_response = self.class.post(api_url, :body => build_xml)
9
- puts api_response if @debug == true
9
+ puts api_response if @debug
10
10
  response = parse_response(api_response)
11
11
  if success?(response)
12
12
  rate_details = [response[:rate_reply][:rate_reply_details][:rated_shipment_details]].flatten.first[:shipment_rate_detail]
@@ -51,8 +51,8 @@ module Fedex
51
51
  builder.doc.root.to_xml
52
52
  end
53
53
 
54
- def service_id
55
- 'crs'
54
+ def service
55
+ { :id => 'crs', :version => 10 }
56
56
  end
57
57
 
58
58
  # Successful request
@@ -0,0 +1,106 @@
1
+ require 'fedex/request/base'
2
+
3
+ module Fedex
4
+ module Request
5
+ class Shipment < Base
6
+ attr_reader :response_details
7
+
8
+ def initialize(credentials, options={})
9
+ super
10
+ requires! options
11
+ # Label specification is required even if we're not using it.
12
+ @label_specification = {
13
+ :label_format_type => 'COMMON2D',
14
+ :image_type => 'PDF',
15
+ :label_stock_type => 'PAPER_LETTER'
16
+ }
17
+ @label_specification.merge! options[:label_specification] if options[:label_specification]
18
+ end
19
+
20
+ # Sends post request to Fedex web service and parse the response.
21
+ # A label file is created with the label at the specified location.
22
+ # The parsed Fedex response is available in #response_details
23
+ # e.g. response_details[:completed_shipment_detail][:completed_package_details][:tracking_ids][:tracking_number]
24
+ def process_request
25
+ api_response = self.class.post api_url, :body => build_xml
26
+ puts api_response if @debug
27
+ response = parse_response(api_response)
28
+ if success?(response)
29
+ success_response(api_response, response)
30
+ else
31
+ failure_response(api_response, response)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Add information for shipments
38
+ def add_requested_shipment(xml)
39
+ xml.RequestedShipment{
40
+ xml.ShipTimestamp Time.now.utc.iso8601(2)
41
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
42
+ xml.ServiceType service_type
43
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
44
+ add_shipper(xml)
45
+ add_recipient(xml)
46
+ add_shipping_charges_payment(xml)
47
+ add_customs_clearance(xml) if @customs_clearance
48
+ add_custom_components(xml)
49
+ xml.RateRequestTypes "ACCOUNT"
50
+ add_packages(xml)
51
+ }
52
+ end
53
+
54
+ # Hook that can be used to add custom parts.
55
+ def add_custom_components(xml)
56
+ add_label_specification xml
57
+ end
58
+
59
+ # Add the label specification
60
+ def add_label_specification(xml)
61
+ xml.LabelSpecification {
62
+ hash_to_xml(xml, @label_specification)
63
+ }
64
+ end
65
+
66
+ # Callback used after a failed shipment response.
67
+ def failure_response(api_response, response)
68
+ error_message = if response[:process_shipment_reply]
69
+ [response[:process_shipment_reply][:notifications]].flatten.first[:message]
70
+ else
71
+ api_response["Fault"]["detail"]["fault"]["reason"]
72
+ end rescue $1
73
+ raise RateError, error_message
74
+ end
75
+
76
+ # Callback used after a successful shipment response.
77
+ def success_response(api_response, response)
78
+ @response_details = response[:process_shipment_reply]
79
+ end
80
+
81
+ # Build xml Fedex Web Service request
82
+ def build_xml
83
+ builder = Nokogiri::XML::Builder.new do |xml|
84
+ xml.ProcessShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v10"){
85
+ add_web_authentication_detail(xml)
86
+ add_client_detail(xml)
87
+ add_version(xml)
88
+ add_requested_shipment(xml)
89
+ }
90
+ end
91
+ builder.doc.root.to_xml
92
+ end
93
+
94
+ def service
95
+ { :id => 'ship', :version => 10 }
96
+ end
97
+
98
+ # Successful request
99
+ def success?(response)
100
+ response[:process_shipment_reply] &&
101
+ %w{SUCCESS WARNING NOTE}.include?(response[:process_shipment_reply][:highest_severity])
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,89 @@
1
+ require 'fedex/request/base'
2
+ require 'fedex/tracking_information'
3
+
4
+ module Fedex
5
+ module Request
6
+ class TrackingInformation < Base
7
+
8
+ attr_reader :package_type, :package_id
9
+
10
+ def initialize(credentials, options={})
11
+ requires!(options, :package_type, :package_id) unless options.has_key?(:tracking_number)
12
+
13
+ @package_id = options[:package_id] || options.delete(:tracking_number)
14
+ @package_type = options[:package_type] || "TRACKING_NUMBER_OR_DOORTAG"
15
+ @credentials = credentials
16
+
17
+ # Optional
18
+ @include_detailed_scans = options[:include_detailed_scans]
19
+ @uuid = options[:uuid]
20
+ @paging_token = options[:paging_token]
21
+
22
+ unless package_type_valid?
23
+ raise "Unknown package type '#{package_type}'"
24
+ end
25
+ end
26
+
27
+ def process_request
28
+ api_response = self.class.post(api_url, :body => build_xml)
29
+ puts api_response if @debug == true
30
+ response = parse_response(api_response)
31
+
32
+ if success?(response)
33
+ options = response[:track_reply][:track_details]
34
+
35
+ Fedex::TrackingInformation.new(options)
36
+ else
37
+ error_message = if response[:track_reply]
38
+ response[:track_reply][:notifications][:message]
39
+ else
40
+ api_response["Fault"]["detail"]["fault"]["reason"]
41
+ end rescue $1
42
+ raise RateError, error_message
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Build xml Fedex Web Service request
49
+ def build_xml
50
+ builder = Nokogiri::XML::Builder.new do |xml|
51
+ xml.TrackRequest(:xmlns => "http://fedex.com/ws/track/v#{service[:version]}"){
52
+ add_web_authentication_detail(xml)
53
+ add_client_detail(xml)
54
+ add_version(xml)
55
+ add_package_identifier(xml)
56
+ xml.IncludeDetailedScans @include_detailed_scans
57
+
58
+ # Optional
59
+ xml.TrackingNumberUniqueIdentifier @uuid if @uuid
60
+ xml.PagingToken @paging_token if @paging_token
61
+ }
62
+ end
63
+ builder.doc.root.to_xml
64
+ end
65
+
66
+ def service
67
+ { :id => 'trck', :version => 5 }
68
+ end
69
+
70
+ def add_package_identifier(xml)
71
+ xml.PackageIdentifier{
72
+ xml.Value package_id
73
+ xml.Type package_type
74
+ }
75
+ end
76
+
77
+ # Successful request
78
+ def success?(response)
79
+ response[:track_reply] &&
80
+ %w{SUCCESS WARNING NOTE}.include?(response[:track_reply][:highest_severity])
81
+ end
82
+
83
+ def package_type_valid?
84
+ Fedex::TrackingInformation::PACKAGE_IDENTIFIER_TYPES.include? package_type
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -1,6 +1,8 @@
1
1
  require 'fedex/credentials'
2
2
  require 'fedex/request/label'
3
3
  require 'fedex/request/rate'
4
+ require 'fedex/request/tracking_information'
5
+ require 'fedex/request/address'
4
6
 
5
7
  module Fedex
6
8
  class Shipment
@@ -23,6 +25,7 @@ module Fedex
23
25
  # @param [Array] packages, An arrary including a hash for each package being shipped
24
26
  # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
25
27
  # @param [String] filename, A location where the label will be saved
28
+ # @param [Hash] label_specification, A hash containing the label printer settings
26
29
  def label(options = {})
27
30
  Request::Label.new(@credentials, options).process_request
28
31
  end
@@ -35,5 +38,24 @@ module Fedex
35
38
  Request::Rate.new(@credentials, options).process_request
36
39
  end
37
40
 
41
+ # @param [Hash] address, A hash containing the address information
42
+ def validate_address(options = {})
43
+ Request::Address.new(@credentials, options).process_request
44
+ end
45
+
46
+ # @param [Hash] shipper, A hash containing the shipper information
47
+ # @param [Hash] recipient, A hash containing the recipient information
48
+ # @param [Array] packages, An arrary including a hash for each package being shipped
49
+ # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
50
+ def ship(options = {})
51
+ Request::Shipment.new(@credentials, options).process_request
52
+ end
53
+
54
+ # @param [Hash] package_id, A string with the requested tracking number
55
+ # @param [Hash] package_type, A string identifitying the type of tracking number used. Full list Fedex::Track::PACKAGE_IDENTIFIER_TYPES
56
+ def track(options = {})
57
+ Request::TrackingInformation.new(@credentials, options).process_request
58
+ end
59
+
38
60
  end
39
- end
61
+ end
@@ -0,0 +1,49 @@
1
+ require 'fedex/tracking_information/event'
2
+
3
+ module Fedex
4
+ class TrackingInformation
5
+ PACKAGE_IDENTIFIER_TYPES = %w{
6
+ BILL_OF_LADING
7
+ COD_RETURN_TRACKING_NUMBER
8
+ CUSTOMER_AUTHORIZATION_NUMBER
9
+ CUSTOMER_REFERENCE
10
+ DEPARTMENT
11
+ FREE_FORM_REFERENCE
12
+ GROUND_INTERNATIONAL
13
+ GROUND_SHIPMENT_ID
14
+ GROUP_MPS
15
+ INVOICE
16
+ JOB_GLOBAL_TRACKING_NUMBER
17
+ ORDER_GLOBAL_TRACKING_NUMBER
18
+ ORDER_TO_PAY_NUMBER
19
+ PARTNER_CARRIER_NUMBER
20
+ PART_NUMBER
21
+ PURCHASE_ORDER
22
+ RETURN_MATERIALS_AUTHORIZATION
23
+ RETURNED_TO_SHIPPER_TRACKING_NUMBER
24
+ TRACKING_CONTROL_NUMBER
25
+ TRACKING_NUMBER_OR_DOORTAG
26
+ TRANSPORTATION_CONTROL_NUMBER
27
+ SHIPPER_REFERENCE
28
+ STANDARD_MPS
29
+ }
30
+
31
+ attr_reader :tracking_number, :signature_name, :service_type, :status,
32
+ :delivery_at, :events
33
+
34
+ def initialize(details = {})
35
+ @details = details
36
+
37
+ @tracking_number = details[:tracking_number]
38
+ @signature_name = details[:delivery_signature_name]
39
+ @service_type = details[:service_type]
40
+ @status = details[:status_description]
41
+ @delivery_at = Time.parse(details[:actual_delivery_timestamp])
42
+
43
+ @events = details[:events].map do |event_details|
44
+ Event.new(event_details)
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ module Fedex
2
+ class TrackingInformation
3
+ class Event
4
+ attr_reader :description, :type, :occured_at, :city, :state, :postal_code,
5
+ :country, :residential
6
+
7
+ def initialize(details = {})
8
+ @description = details[:event_description]
9
+ @type = details[:event_type]
10
+ @occured_at = Time.parse(details[:timestamp])
11
+ @city = details[:address][:city]
12
+ @state = details[:address][:state_or_province_code]
13
+ @postal_code = details[:address][:postal_code]
14
+ @country = details[:address][:country_code]
15
+ @residential = details[:address][:residential] == "true"
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/fedex/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fedex
2
- VERSION = "2.0.1"
2
+ VERSION = "2.2.0"
3
3
  end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ module Fedex
4
+ describe Address, :production do
5
+ describe "validation" do
6
+
7
+ # Address Validation is only enabled in the production environment
8
+ #
9
+ let(:fedex) { Shipment.new(fedex_production_credentials) }
10
+
11
+ context "valid address", :vcr do
12
+ let(:address) do
13
+ {
14
+ :street => "5 Elm Street",
15
+ :city => "Norwalk",
16
+ :state => "CT",
17
+ :postal_code => "06850",
18
+ :country => "USA"
19
+ }
20
+ end
21
+
22
+ let(:options) do
23
+ { :address => address }
24
+ end
25
+
26
+ it "validates the address" do
27
+ address = fedex.validate_address(options)
28
+
29
+ address.residential.should be_true
30
+ address.business.should be_false
31
+ address.score.should == 100
32
+
33
+ address.postal_code.should == "06850-3901"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end