fedex 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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