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.
- data/lib/fedex.rb +58 -0
- data/lib/fedex/address.rb +30 -0
- data/lib/fedex/credentials.rb +26 -0
- data/lib/fedex/helpers.rb +20 -0
- data/lib/fedex/label.rb +56 -0
- data/lib/fedex/rate.rb +36 -0
- data/lib/fedex/request/address.rb +92 -0
- data/lib/fedex/request/base.rb +309 -0
- data/lib/fedex/request/label.rb +29 -0
- data/lib/fedex/request/rate.rb +66 -0
- data/lib/fedex/request/shipment.rb +108 -0
- data/lib/fedex/request/tracking_information.rb +89 -0
- data/lib/fedex/shipment.rb +61 -0
- data/lib/fedex/tracking_information.rb +49 -0
- data/lib/fedex/tracking_information/event.rb +19 -0
- data/lib/fedex/version.rb +3 -0
- data/lib/shipping.rb +46 -0
- data/lib/ups/UpsInfo.rb +357 -0
- metadata +119 -0
@@ -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
|
data/lib/shipping.rb
ADDED
@@ -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'
|