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.
- data/.gitignore +4 -1
- data/Readme.md +81 -3
- data/fedex.gemspec +2 -2
- data/lib/fedex/address.rb +30 -0
- data/lib/fedex/helpers.rb +5 -4
- data/lib/fedex/label.rb +44 -3
- data/lib/fedex/request/address.rb +92 -0
- data/lib/fedex/request/base.rb +39 -26
- data/lib/fedex/request/label.rb +10 -75
- data/lib/fedex/request/rate.rb +3 -3
- data/lib/fedex/request/shipment.rb +106 -0
- data/lib/fedex/request/tracking_information.rb +89 -0
- data/lib/fedex/shipment.rb +23 -1
- data/lib/fedex/tracking_information.rb +49 -0
- data/lib/fedex/tracking_information/event.rb +19 -0
- data/lib/fedex/version.rb +1 -1
- data/spec/lib/fedex/address_spec.rb +39 -0
- data/spec/lib/fedex/label_spec.rb +33 -18
- data/spec/lib/fedex/rate_spec.rb +149 -0
- data/spec/lib/fedex/shipment_spec.rb +33 -138
- data/spec/lib/fedex/track_spec.rb +50 -0
- data/spec/spec_helper.rb +4 -2
- data/spec/support/credentials.rb +15 -0
- metadata +29 -14
data/lib/fedex/request/label.rb
CHANGED
@@ -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 <
|
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
|
-
|
39
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
data/lib/fedex/request/rate.rb
CHANGED
@@ -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
|
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
|
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
|
data/lib/fedex/shipment.rb
CHANGED
@@ -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
@@ -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
|