shippinglogic 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGELOG.rdoc +55 -0
- data/LICENSE +20 -0
- data/README.rdoc +175 -0
- data/Rakefile +37 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/shippinglogic.rb +3 -0
- data/lib/shippinglogic/attributes.rb +121 -0
- data/lib/shippinglogic/error.rb +22 -0
- data/lib/shippinglogic/fedex.rb +84 -0
- data/lib/shippinglogic/fedex/cancel.rb +47 -0
- data/lib/shippinglogic/fedex/enumerations.rb +348 -0
- data/lib/shippinglogic/fedex/error.rb +47 -0
- data/lib/shippinglogic/fedex/rate.rb +229 -0
- data/lib/shippinglogic/fedex/request.rb +134 -0
- data/lib/shippinglogic/fedex/response.rb +72 -0
- data/lib/shippinglogic/fedex/service.rb +11 -0
- data/lib/shippinglogic/fedex/ship.rb +238 -0
- data/lib/shippinglogic/fedex/signature.rb +68 -0
- data/lib/shippinglogic/fedex/track.rb +124 -0
- data/lib/shippinglogic/proxy.rb +23 -0
- data/lib/shippinglogic/service.rb +42 -0
- data/lib/shippinglogic/ups.rb +83 -0
- data/lib/shippinglogic/ups/cancel.rb +52 -0
- data/lib/shippinglogic/ups/enumerations.rb +56 -0
- data/lib/shippinglogic/ups/error.rb +42 -0
- data/lib/shippinglogic/ups/label.rb +50 -0
- data/lib/shippinglogic/ups/rate.rb +228 -0
- data/lib/shippinglogic/ups/request.rb +49 -0
- data/lib/shippinglogic/ups/response.rb +58 -0
- data/lib/shippinglogic/ups/service.rb +11 -0
- data/lib/shippinglogic/ups/ship_accept.rb +53 -0
- data/lib/shippinglogic/ups/ship_confirm.rb +170 -0
- data/lib/shippinglogic/ups/track.rb +118 -0
- data/lib/shippinglogic/validation.rb +32 -0
- data/shippinglogic.gemspec +120 -0
- data/spec/attributes_spec.rb +67 -0
- data/spec/config/fedex_credentials.example.yml +4 -0
- data/spec/config/ups_credentials.example.yml +3 -0
- data/spec/error_spec.rb +43 -0
- data/spec/fedex/cancel_spec.rb +10 -0
- data/spec/fedex/error_spec.rb +26 -0
- data/spec/fedex/rate_spec.rb +87 -0
- data/spec/fedex/request_spec.rb +15 -0
- data/spec/fedex/responses/blank.xml +0 -0
- data/spec/fedex/responses/cancel_not_found.xml +7 -0
- data/spec/fedex/responses/failed_authentication.xml +7 -0
- data/spec/fedex/responses/malformed.xml +8 -0
- data/spec/fedex/responses/rate_defaults.xml +7 -0
- data/spec/fedex/responses/rate_insurance.xml +9 -0
- data/spec/fedex/responses/rate_no_services.xml +7 -0
- data/spec/fedex/responses/rate_non_custom_packaging.xml +7 -0
- data/spec/fedex/responses/ship_defaults.xml +7 -0
- data/spec/fedex/responses/ship_with_no_signature.xml +7 -0
- data/spec/fedex/responses/signature_defaults.xml +7 -0
- data/spec/fedex/responses/track_defaults.xml +7 -0
- data/spec/fedex/responses/unexpected.xml +1 -0
- data/spec/fedex/service_spec.rb +19 -0
- data/spec/fedex/ship_spec.rb +37 -0
- data/spec/fedex/signature_spec.rb +11 -0
- data/spec/fedex/spec_helper.rb +84 -0
- data/spec/fedex/track_spec.rb +37 -0
- data/spec/fedex_spec.rb +16 -0
- data/spec/lib/interceptor.rb +17 -0
- data/spec/proxy_spec.rb +42 -0
- data/spec/service_spec.rb +23 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/ups/responses/blank.xml +0 -0
- data/spec/ups/responses/track_defaults.xml +2 -0
- data/spec/ups/spec_helper.rb +43 -0
- data/spec/ups_spec.rb +16 -0
- data/spec/validation_spec.rb +49 -0
- metadata +163 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require "builder"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class UPS
|
5
|
+
# Methods relating to building and sending a request to UPS's web services.
|
6
|
+
module Request
|
7
|
+
private
|
8
|
+
# Convenience method for sending requests to UPS
|
9
|
+
def request(body)
|
10
|
+
real_class.post(base.url + real_class.path, :body => body)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Convenience method to create a builder object so that our builder options are consistent across
|
14
|
+
# the various services.
|
15
|
+
#
|
16
|
+
# Ex: if I want to change the indent level to 3 it should change for all requests built.
|
17
|
+
def builder
|
18
|
+
b = Builder::XmlMarkup.new(:indent => 2)
|
19
|
+
b.instruct!
|
20
|
+
b
|
21
|
+
end
|
22
|
+
|
23
|
+
# A convenience method for building the authentication block in your XML request
|
24
|
+
def build_authentication(b)
|
25
|
+
b.AccessRequest(:"xml:lang" => "en-US") do
|
26
|
+
b.AccessLicenseNumber base.key
|
27
|
+
b.UserId base.account
|
28
|
+
b.Password base.password
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# A convenience method for building the address block in your XML request
|
33
|
+
def build_address(b, type)
|
34
|
+
address_lines = send("#{type}_streets").to_s.split(/(?:\s*\n\s*)+/m, 3)
|
35
|
+
|
36
|
+
b.Address do
|
37
|
+
b.AddressLine1 address_lines[0] if address_lines[0]
|
38
|
+
b.AddressLine2 address_lines[1] if address_lines[1]
|
39
|
+
b.AddressLine3 address_lines[2] if address_lines[2]
|
40
|
+
b.City send("#{type}_city") if send("#{type}_city")
|
41
|
+
b.StateProvinceCode send("#{type}_state") if send("#{type}_state")
|
42
|
+
b.PostalCode send("#{type}_postal_code") if send("#{type}_postal_code")
|
43
|
+
b.CountryCode send("#{type}_country") if send("#{type}_country")
|
44
|
+
b.ResidentialAddressIndicator attribute_names.include?("#{type}_residential") && send("#{type}_residential")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "shippinglogic/ups/error"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class UPS
|
5
|
+
# Methods relating to receiving a response from UPS and cleaning it up.
|
6
|
+
module Response
|
7
|
+
SUCCESSFUL_SEVERITIES = ["Warning"]
|
8
|
+
|
9
|
+
private
|
10
|
+
# Overwriting the request method to clean the response and handle errors.
|
11
|
+
def request(body)
|
12
|
+
response = clean_response(super)
|
13
|
+
|
14
|
+
if success?(response)
|
15
|
+
response
|
16
|
+
else
|
17
|
+
raise Error.new(body, response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Was the response a success?
|
22
|
+
def success?(response)
|
23
|
+
response.is_a?(Hash) && response[:response][:response_status_code] == "1"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cleans the response and returns it in a more 'user friendly' format that is easier
|
27
|
+
# to work with.
|
28
|
+
def clean_response(response)
|
29
|
+
cut_to_the_chase(sanitize_response_keys(response))
|
30
|
+
end
|
31
|
+
|
32
|
+
# UPS likes nested XML tags, because they send quite a bit of them back in responses.
|
33
|
+
# This method just 'cuts to the chase' and get to the heart of the response.
|
34
|
+
def cut_to_the_chase(response)
|
35
|
+
response.values.first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Recursively sanitizes the response object by clenaing up any hash keys.
|
39
|
+
def sanitize_response_keys(response)
|
40
|
+
if response.is_a?(Hash)
|
41
|
+
response.inject({}) do |r, (key, value)|
|
42
|
+
r[sanitize_response_key(key)] = sanitize_response_keys(value)
|
43
|
+
r
|
44
|
+
end
|
45
|
+
elsif response.is_a?(Array)
|
46
|
+
response.collect { |r| sanitize_response_keys(r) }
|
47
|
+
else
|
48
|
+
response
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Underscores and symbolizes incoming UPS response keys.
|
53
|
+
def sanitize_response_key(key)
|
54
|
+
key.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class UPS
|
5
|
+
class ShipAccept < Service
|
6
|
+
def self.path
|
7
|
+
"/ShipAccept"
|
8
|
+
end
|
9
|
+
|
10
|
+
class Details
|
11
|
+
class Shipment; attr_accessor :tracking_number, :label; end
|
12
|
+
|
13
|
+
attr_accessor :rate, :currency, :shipments
|
14
|
+
|
15
|
+
def initialize(response)
|
16
|
+
details = response[:shipment_results]
|
17
|
+
|
18
|
+
charges = details[:shipment_charges][:total_charges]
|
19
|
+
self.rate = BigDecimal.new(charges[:monetary_value])
|
20
|
+
self.currency = charges[:currency_code]
|
21
|
+
|
22
|
+
self.shipments = [*details[:package_results]].collect do |package|
|
23
|
+
shipment = Shipment.new
|
24
|
+
shipment.tracking_number = package[:tracking_number]
|
25
|
+
shipment.label = Base64.decode64(package[:label_image][:graphic_image])
|
26
|
+
shipment
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attribute :digest, :string
|
32
|
+
|
33
|
+
private
|
34
|
+
def target
|
35
|
+
@target ||= Details.new(request(build_request))
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_request
|
39
|
+
b = builder
|
40
|
+
build_authentication(b)
|
41
|
+
b.instruct!
|
42
|
+
|
43
|
+
b.ShipmentAcceptRequest do
|
44
|
+
b.Request do
|
45
|
+
b.RequestAction "ShipAccept"
|
46
|
+
end
|
47
|
+
|
48
|
+
b.ShipmentDigest digest
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
class ShipConfirm < Service
|
4
|
+
def self.path
|
5
|
+
"/ShipConfirm"
|
6
|
+
end
|
7
|
+
|
8
|
+
class Details
|
9
|
+
attr_accessor :digest, :tracking_number, :rate, :currency
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
self.digest = response[:shipment_digest]
|
13
|
+
self.tracking_number = response[:shipment_identification_number]
|
14
|
+
|
15
|
+
charges = response[:shipment_charges][:total_charges]
|
16
|
+
self.rate = BigDecimal.new(charges[:monetary_value])
|
17
|
+
self.currency = charges[:currency_code]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# shipper options
|
22
|
+
attribute :shipper_name, :string
|
23
|
+
attribute :shipper_phone_number, :string
|
24
|
+
attribute :shipper_email, :string
|
25
|
+
attribute :shipper_streets, :string
|
26
|
+
attribute :shipper_city, :string
|
27
|
+
attribute :shipper_state, :string
|
28
|
+
attribute :shipper_postal_code, :string
|
29
|
+
attribute :shipper_country, :string
|
30
|
+
|
31
|
+
# recipient options
|
32
|
+
attribute :recipient_name, :string
|
33
|
+
attribute :recipient_phone_number, :string
|
34
|
+
attribute :recipient_email, :string
|
35
|
+
attribute :recipient_streets, :string
|
36
|
+
attribute :recipient_city, :string
|
37
|
+
attribute :recipient_state, :string
|
38
|
+
attribute :recipient_postal_code, :string
|
39
|
+
attribute :recipient_country, :string
|
40
|
+
attribute :recipient_residential, :boolean, :default => false
|
41
|
+
|
42
|
+
# label options
|
43
|
+
attribute :label_format, :string, :default => "GIF"
|
44
|
+
attribute :label_file_type, :string, :default => "GIF"
|
45
|
+
|
46
|
+
# packaging options
|
47
|
+
attribute :packaging_type, :string, :default => "00"
|
48
|
+
attribute :package_count, :integer, :default => 1
|
49
|
+
attribute :package_weight, :float
|
50
|
+
attribute :package_weight_units, :string, :default => "LBS"
|
51
|
+
attribute :package_length, :integer
|
52
|
+
attribute :package_width, :integer
|
53
|
+
attribute :package_height, :integer
|
54
|
+
attribute :package_dimension_units, :string, :default => "IN"
|
55
|
+
|
56
|
+
# monetary options
|
57
|
+
attribute :currency_type, :string
|
58
|
+
attribute :insured_value, :decimal
|
59
|
+
attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.number }
|
60
|
+
|
61
|
+
# delivery options
|
62
|
+
attribute :service_type, :string
|
63
|
+
#FIXME Setting the signature option to true raises and error. I believe this has something
|
64
|
+
# to do with UPS account-specific settings and signature service availability.
|
65
|
+
attribute :signature, :boolean, :default => false
|
66
|
+
attribute :saturday, :boolean, :default => false
|
67
|
+
|
68
|
+
private
|
69
|
+
def target
|
70
|
+
@target ||= Details.new(request(build_request))
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_request
|
74
|
+
b = builder
|
75
|
+
build_authentication(b)
|
76
|
+
b.instruct!
|
77
|
+
|
78
|
+
b.ShipmentConfirmRequest do
|
79
|
+
b.Request do
|
80
|
+
b.RequestAction "ShipConfirm"
|
81
|
+
b.RequestOption "validate"
|
82
|
+
end
|
83
|
+
|
84
|
+
b.Shipment do
|
85
|
+
b.Shipper do
|
86
|
+
b.Name shipper_name
|
87
|
+
b.ShipperNumber payor_account_number
|
88
|
+
b.PhoneNumber shipper_phone_number
|
89
|
+
b.EMailAddress shipper_email
|
90
|
+
build_address(b, :shipper)
|
91
|
+
end
|
92
|
+
|
93
|
+
b.ShipTo do
|
94
|
+
b.CompanyName recipient_name
|
95
|
+
b.PhoneNumber recipient_phone_number
|
96
|
+
b.EMailAddress recipient_email
|
97
|
+
build_address(b, :recipient)
|
98
|
+
end
|
99
|
+
|
100
|
+
b.PaymentInformation do
|
101
|
+
b.Prepaid do
|
102
|
+
b.BillShipper do
|
103
|
+
b.AccountNumber payor_account_number
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
b.Service do
|
109
|
+
b.Code service_type
|
110
|
+
end
|
111
|
+
|
112
|
+
b.ShipmentServiceOptions do
|
113
|
+
b.SaturdayDelivery if saturday
|
114
|
+
if signature
|
115
|
+
b.DeliveryConfirmation do
|
116
|
+
b.DCISType "1"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
package_count.times do |i|
|
122
|
+
b.Package do
|
123
|
+
b.PackagingType do
|
124
|
+
b.Code packaging_type
|
125
|
+
end
|
126
|
+
|
127
|
+
b.Dimensions do
|
128
|
+
b.UnitOfMeasurement do
|
129
|
+
b.Code package_dimension_units
|
130
|
+
end
|
131
|
+
|
132
|
+
b.Length "%.2f" % package_length
|
133
|
+
b.Width "%.2f" % package_width
|
134
|
+
b.Height "%.2f" % package_height
|
135
|
+
end
|
136
|
+
|
137
|
+
b.PackageWeight do
|
138
|
+
b.UnitOfMeasurement do
|
139
|
+
b.Code package_weight_units
|
140
|
+
end
|
141
|
+
|
142
|
+
b.Weight "%.1f" % package_weight
|
143
|
+
end
|
144
|
+
|
145
|
+
b.PackageServiceOptions do
|
146
|
+
if insured_value
|
147
|
+
b.InsuredValue do
|
148
|
+
b.MonetaryValue insured_value
|
149
|
+
b.CurrencyCode currency_type
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
b.LabelSpecification do
|
158
|
+
b.LabelPrintMethod do
|
159
|
+
b.Code label_file_type
|
160
|
+
end
|
161
|
+
|
162
|
+
b.LabelImageFormat do
|
163
|
+
b.Code label_format
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
# An interface to the track services provided by UPS. Allows you to get an array of events for a specific
|
4
|
+
# tracking number.
|
5
|
+
#
|
6
|
+
# == Accessor methods / options
|
7
|
+
#
|
8
|
+
# * <tt>tracking_number</tt> - the tracking number
|
9
|
+
#
|
10
|
+
# === Simple Example
|
11
|
+
#
|
12
|
+
# Here is a very simple example:
|
13
|
+
#
|
14
|
+
# ups = Shippinglogic::UPS.new(key, password, account)
|
15
|
+
# tracking_details = ups.track(:tracking_number => "my number")
|
16
|
+
#
|
17
|
+
# tracking_details.status
|
18
|
+
# # => "Delivered"
|
19
|
+
#
|
20
|
+
# tracking_details.signature_name
|
21
|
+
# # => "KKING"
|
22
|
+
#
|
23
|
+
# tracking_details.events.first
|
24
|
+
# # => #<Shippinglogic::UPS::Track::Event @postal_code="95817", @name="Delivered", @state="CA",
|
25
|
+
# # @city="Sacramento", @type="Delivered", @country="US", @occured_at=Mon Dec 08 10:43:37 -0500 2008>
|
26
|
+
#
|
27
|
+
# tracking_details.events.first.name
|
28
|
+
# # => "Delivered"
|
29
|
+
#
|
30
|
+
# === Note
|
31
|
+
#
|
32
|
+
# UPS does support locating packages through means other than a tracking number.
|
33
|
+
# These are not supported and probably won't be until someone needs them. It should
|
34
|
+
# be fairly simple to add, but I could not think of a reason why anyone would want to track
|
35
|
+
# a package with anything other than a tracking number.
|
36
|
+
class Track < Service
|
37
|
+
def self.path
|
38
|
+
"/Track"
|
39
|
+
end
|
40
|
+
|
41
|
+
class Details
|
42
|
+
# Each tracking result is an object of this class
|
43
|
+
class Event; attr_accessor :name, :type, :occured_at, :city, :state, :postal_code, :country; end
|
44
|
+
|
45
|
+
attr_accessor :origin_city, :origin_state, :origin_country,
|
46
|
+
:destination_city, :destination_state, :destination_country,
|
47
|
+
:signature_name, :service_type, :status, :delivery_at,
|
48
|
+
:events
|
49
|
+
|
50
|
+
def initialize(response)
|
51
|
+
details = response[:shipment]
|
52
|
+
|
53
|
+
if origin = details.fetch(:shipper, {})[:address]
|
54
|
+
self.origin_city = origin[:city]
|
55
|
+
self.origin_state = origin[:state_province_code]
|
56
|
+
self.origin_country = origin[:country_code]
|
57
|
+
end
|
58
|
+
|
59
|
+
if destination = details.fetch(:ship_to, {})[:address]
|
60
|
+
self.destination_city = destination[:city]
|
61
|
+
self.destination_state = destination[:state_province_code]
|
62
|
+
self.destination_country = destination[:country_code]
|
63
|
+
end
|
64
|
+
|
65
|
+
package = details[:package]
|
66
|
+
events = package[:activity].is_a?(Array) ? package[:activity] : [package[:activitiy]].compact
|
67
|
+
last_event = events.first
|
68
|
+
delivery = events.detect{|e| e[:status][:status_type][:code] == "D" }
|
69
|
+
|
70
|
+
self.signature_name = last_event && last_event[:signed_for_by_name]
|
71
|
+
self.service_type = details[:service][:description]
|
72
|
+
self.status = last_event && last_event[:status][:status_type][:description]
|
73
|
+
self.delivery_at = delivery && Time.parse(delivery[:date] + delivery[:time])
|
74
|
+
|
75
|
+
self.events = events.collect do |details|
|
76
|
+
event = Event.new
|
77
|
+
status = details[:status][:status_type]
|
78
|
+
event.name = status[:description]
|
79
|
+
event.type = status[:code]
|
80
|
+
#FIXME The proper spelling is "occurred", not "occured."
|
81
|
+
event.occured_at = Time.parse(details[:date] + details[:time])
|
82
|
+
location = details[:activity_location][:address]
|
83
|
+
event.city = location[:city]
|
84
|
+
event.state = location[:state_province_code]
|
85
|
+
event.postal_code = location[:postal_code]
|
86
|
+
event.country = location[:country_code]
|
87
|
+
event
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
attribute :tracking_number, :string
|
93
|
+
|
94
|
+
private
|
95
|
+
# The parent class Service requires that we define this method. This is our kicker. This method is only
|
96
|
+
# called when we need to deal with information from UPS. Notice the caching into the @target variable.
|
97
|
+
def target
|
98
|
+
@target ||= Details.new(request(build_request))
|
99
|
+
end
|
100
|
+
|
101
|
+
# Just building some XML to send off to UPS. UPS requires this particualar format.
|
102
|
+
def build_request
|
103
|
+
b = builder
|
104
|
+
build_authentication(b)
|
105
|
+
b.instruct!
|
106
|
+
|
107
|
+
b.TrackRequest do
|
108
|
+
b.Request do
|
109
|
+
b.RequestAction "Track"
|
110
|
+
b.RequestOption "activity"
|
111
|
+
end
|
112
|
+
|
113
|
+
b.TrackingNumber tracking_number
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|