ShippingInfo 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.
@@ -0,0 +1,29 @@
1
+ require 'fedex/request/base'
2
+ require 'fedex/label'
3
+ require 'fedex/request/shipment'
4
+ require 'fileutils'
5
+
6
+ module Fedex
7
+ module Request
8
+ class Label < Shipment
9
+ def initialize(credentials, options={})
10
+ super(credentials, options)
11
+ @filename = options[:filename]
12
+ end
13
+
14
+ private
15
+
16
+ def success_response(api_response, response)
17
+ super
18
+
19
+ label_details = response.merge!({
20
+ :format => @label_specification[:image_type],
21
+ :file_name => @filename
22
+ })
23
+
24
+ Fedex::Label.new label_details
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,66 @@
1
+ require 'fedex/request/base'
2
+
3
+ module Fedex
4
+ module Request
5
+ class Rate < Base
6
+ # Sends post request to Fedex web service and parse the response, a Rate object is created if the response is successful
7
+ def process_request
8
+ api_response = self.class.post(api_url, :body => build_xml)
9
+ puts api_response if @debug
10
+ response = parse_response(api_response)
11
+ if success?(response)
12
+ rate_details = [response[:rate_reply][:rate_reply_details][:rated_shipment_details]].flatten.first[:shipment_rate_detail]
13
+ Fedex::Rate.new(rate_details)
14
+ else
15
+ error_message = if response[:rate_reply]
16
+ [response[:rate_reply][:notifications]].flatten.first[:message]
17
+ else
18
+ api_response["Fault"]["detail"]["fault"]["reason"]
19
+ end rescue $1
20
+ raise RateError, error_message
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # Add information for shipments
27
+ def add_requested_shipment(xml)
28
+ xml.RequestedShipment{
29
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
30
+ xml.ServiceType service_type
31
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
32
+ add_shipper(xml)
33
+ add_recipient(xml)
34
+ add_shipping_charges_payment(xml)
35
+ add_customs_clearance(xml) if @customs_clearance
36
+ xml.RateRequestTypes "ACCOUNT"
37
+ add_packages(xml)
38
+ }
39
+ end
40
+
41
+ # Build xml Fedex Web Service request
42
+ def build_xml
43
+ builder = Nokogiri::XML::Builder.new do |xml|
44
+ xml.RateRequest(:xmlns => "http://fedex.com/ws/rate/v10"){
45
+ add_web_authentication_detail(xml)
46
+ add_client_detail(xml)
47
+ add_version(xml)
48
+ add_requested_shipment(xml)
49
+ }
50
+ end
51
+ builder.doc.root.to_xml
52
+ end
53
+
54
+ def service
55
+ { :id => 'crs', :version => 10 }
56
+ end
57
+
58
+ # Successful request
59
+ def success?(response)
60
+ response[:rate_reply] &&
61
+ %w{SUCCESS WARNING NOTE}.include?(response[:rate_reply][:highest_severity])
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,108 @@
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
+ xml.LabelFormatType @label_specification[:label_format_type]
63
+ xml.ImageType @label_specification[:image_type]
64
+ xml.LabelStockType @label_specification[:label_stock_type]
65
+ }
66
+ end
67
+
68
+ # Callback used after a failed shipment response.
69
+ def failure_response(api_response, response)
70
+ error_message = if response[:process_shipment_reply]
71
+ [response[:process_shipment_reply][:notifications]].flatten.first[:message]
72
+ else
73
+ api_response["Fault"]["detail"]["fault"]["reason"]
74
+ end rescue $1
75
+ raise RateError, error_message
76
+ end
77
+
78
+ # Callback used after a successful shipment response.
79
+ def success_response(api_response, response)
80
+ @response_details = response[:process_shipment_reply]
81
+ end
82
+
83
+ # Build xml Fedex Web Service request
84
+ def build_xml
85
+ builder = Nokogiri::XML::Builder.new do |xml|
86
+ xml.ProcessShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v10"){
87
+ add_web_authentication_detail(xml)
88
+ add_client_detail(xml)
89
+ add_version(xml)
90
+ add_requested_shipment(xml)
91
+ }
92
+ end
93
+ builder.doc.root.to_xml
94
+ end
95
+
96
+ def service
97
+ { :id => 'ship', :version => 10 }
98
+ end
99
+
100
+ # Successful request
101
+ def success?(response)
102
+ response[:process_shipment_reply] &&
103
+ %w{SUCCESS WARNING NOTE}.include?(response[:process_shipment_reply][:highest_severity])
104
+ end
105
+
106
+ end
107
+ end
108
+ 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
@@ -0,0 +1,61 @@
1
+ require 'fedex/credentials'
2
+ require 'fedex/request/label'
3
+ require 'fedex/request/rate'
4
+ require 'fedex/request/tracking_information'
5
+ require 'fedex/request/address'
6
+
7
+ module Fedex
8
+ class Shipment
9
+
10
+ # In order to use Fedex rates API you must first apply for a developer(and later production keys),
11
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
12
+ # @param [String] key - Fedex web service key
13
+ # @param [String] password - Fedex password
14
+ # @param [String] account_number - Fedex account_number
15
+ # @param [String] meter - Fedex meter number
16
+ # @param [String] mode - [development/production]
17
+ #
18
+ # return a Fedex::Shipment object
19
+ def initialize(options={})
20
+ @credentials = Credentials.new(options)
21
+ end
22
+
23
+ # @param [Hash] shipper, A hash containing the shipper information
24
+ # @param [Hash] recipient, A hash containing the recipient information
25
+ # @param [Array] packages, An arrary including a hash for each package being shipped
26
+ # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
27
+ # @param [String] filename, A location where the label will be saved
28
+ # @param [Hash] label_specification, A hash containing the label printer settings
29
+ def label(options = {})
30
+ Request::Label.new(@credentials, options).process_request
31
+ end
32
+
33
+ # @param [Hash] shipper, A hash containing the shipper information
34
+ # @param [Hash] recipient, A hash containing the recipient information
35
+ # @param [Array] packages, An arrary including a hash for each package being shipped
36
+ # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
37
+ def rate(options = {})
38
+ Request::Rate.new(@credentials, options).process_request
39
+ end
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
+
60
+ 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
@@ -0,0 +1,3 @@
1
+ module Fedex
2
+ VERSION = "2.2.1"
3
+ end
@@ -0,0 +1,46 @@
1
+ #--
2
+ # Copyright (c) 2005 Lucas Carlson
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+ # Author:: Lucas Carlson (mailto:lucas@rufy.com)
24
+ # Copyright:: Copyright (c) 2005 Lucas Carlson
25
+ # License:: LGPL
26
+
27
+ $:.unshift(File.dirname(__FILE__))
28
+
29
+ begin
30
+ require 'rubygems'
31
+ rescue LoadError
32
+ nil
33
+ end
34
+
35
+ require 'builder'
36
+ require 'yaml'
37
+ require 'rexml/document'
38
+ require 'net/http'
39
+ require 'net/https'
40
+ require 'base64'
41
+ require 'fileutils'
42
+ require 'tempfile'
43
+
44
+ require 'extensions'
45
+ require 'Shipping/base'
46
+ require 'Shipping/ups'