shippinglogic 1.2.3
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/.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,42 @@
|
|
1
|
+
require "httparty"
|
2
|
+
require "shippinglogic/proxy"
|
3
|
+
require "shippinglogic/attributes"
|
4
|
+
require "shippinglogic/validation"
|
5
|
+
|
6
|
+
module Shippinglogic
|
7
|
+
class Service < Proxy
|
8
|
+
include Attributes
|
9
|
+
include HTTParty
|
10
|
+
include Validation
|
11
|
+
|
12
|
+
attr_accessor :base
|
13
|
+
|
14
|
+
# Accepts the base service object as a single parameter so that we can access
|
15
|
+
# authentication credentials and options.
|
16
|
+
def initialize(base, attributes = {})
|
17
|
+
self.base = base
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
# Allows the cached response to be reset, specifically when an attribute changes
|
23
|
+
def reset_target
|
24
|
+
@target = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Resets the target before deferring to the +write_attribute+ method as defined by the
|
28
|
+
# +Attributes+ module. This keeps +Attributes+ dissociated from any proxy or service specific
|
29
|
+
# code, making testing simpler.
|
30
|
+
def write_attribute(*args)
|
31
|
+
reset_target
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# For each service you need to overwrite this method. This is where you make the call to the API
|
36
|
+
# and do your magic. See the child classes for examples on how to define this method. It is very
|
37
|
+
# important that you cache the result into a variable to avoid uneccessary requests.
|
38
|
+
def target
|
39
|
+
raise NotImplementedError.new("You need to implement a target method that the proxy class can delegate method calls to")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "shippinglogic/ups/service"
|
2
|
+
require "shippinglogic/ups/cancel"
|
3
|
+
require "shippinglogic/ups/rate"
|
4
|
+
require "shippinglogic/ups/track"
|
5
|
+
|
6
|
+
module Shippinglogic
|
7
|
+
class UPS
|
8
|
+
# A hash representing default the options. If you are using this in a Rails app the best place
|
9
|
+
# to modify or change these options is either in an initializer or your specific environment file. Keep
|
10
|
+
# in mind that these options can be modified on the instance level when creating an object. See #initialize
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# === Options
|
14
|
+
#
|
15
|
+
# * <tt>:test</tt> - this basically tells us which url to use. If set to true we will use the UPS test URL, if false we
|
16
|
+
# will use the production URL. If you are using this in a rails app, unless you are in your production environment, this
|
17
|
+
# will default to true automatically.
|
18
|
+
# * <tt>:test_url</tt> - the test URL for UPS's webservices. (default: https://wwwcie.ups.com:443/ups.app/xml)
|
19
|
+
# * <tt>:production_url</tt> - the production URL for UPS's webservices. (default: https://www.ups.com:443/ups.app/xml)
|
20
|
+
def self.options
|
21
|
+
@options ||= {
|
22
|
+
:test => !!(defined?(Rails) && !Rails.env.production?),
|
23
|
+
:production_url => "https://www.ups.com:443/ups.app/xml",
|
24
|
+
:test_url => "https://wwwcie.ups.com:443/ups.app/xml"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :key, :password, :account, :number, :options
|
29
|
+
|
30
|
+
# Before you can use the UPS web services you need to provide 4 credentials:
|
31
|
+
#
|
32
|
+
# 1. Your UPS access key
|
33
|
+
# 2. Your UPS password
|
34
|
+
# 3. Your UPS user ID
|
35
|
+
# 4. Your 6-character UPS account number
|
36
|
+
#
|
37
|
+
#TODO Explain how to acquire those 4 credentials.
|
38
|
+
#
|
39
|
+
# The last parameter allows you to modify the class options on an instance level. It accepts the
|
40
|
+
# same options that the class level method #options accepts. If you don't want to change any of
|
41
|
+
# them, don't supply this parameter.
|
42
|
+
def initialize(key, password, account, number, options = {})
|
43
|
+
self.key = key
|
44
|
+
self.password = password
|
45
|
+
self.account = account
|
46
|
+
self.number = number
|
47
|
+
self.options = self.class.options.merge(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# A convenience method for accessing the endpoint URL for the UPS API.
|
51
|
+
def url
|
52
|
+
options[:test] ? options[:test_url] : options[:production_url]
|
53
|
+
end
|
54
|
+
|
55
|
+
def cancel(attributes = {})
|
56
|
+
@cancel ||= Cancel.new(self, attributes)
|
57
|
+
end
|
58
|
+
|
59
|
+
def rate(attributes = {})
|
60
|
+
@rate ||= Rate.new(self, attributes)
|
61
|
+
end
|
62
|
+
|
63
|
+
def ship_confirm(attributes = {})
|
64
|
+
@ship_confirm ||= ShipConfirm.new(self, attributes)
|
65
|
+
end
|
66
|
+
|
67
|
+
def ship_accept(attributes = {})
|
68
|
+
@ship_accept ||= ShipAccept.new(self, attributes)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ship(attributes = {})
|
72
|
+
@ship ||= ship_accept(:digest => ship_confirm(attributes).digest)
|
73
|
+
end
|
74
|
+
|
75
|
+
def track(attributes = {})
|
76
|
+
@track ||= Track.new(self, attributes)
|
77
|
+
end
|
78
|
+
|
79
|
+
def label(attributes = {})
|
80
|
+
@label ||= Label.new(self, attributes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
# An interface to the shipment canceling service provided by UPS. Allows you to cancel a shipment
|
4
|
+
#
|
5
|
+
# == Accessor methods / options
|
6
|
+
#
|
7
|
+
# * <tt>tracking_number</tt> - the tracking number
|
8
|
+
#
|
9
|
+
# === Simple Example
|
10
|
+
#
|
11
|
+
# ups = Shippinglogic::UPS.new(key, password, account)
|
12
|
+
# cancel = ups.cancel(:tracking_number => "my number")
|
13
|
+
# cancel.perform
|
14
|
+
# # => true
|
15
|
+
class Cancel < Service
|
16
|
+
def self.path
|
17
|
+
"/Void"
|
18
|
+
end
|
19
|
+
|
20
|
+
attribute :tracking_number, :string
|
21
|
+
|
22
|
+
# Our services are set up as a proxy. We need to access the underlying object, to trigger the request
|
23
|
+
# to UPS. So calling this method is a way to do that since there really is no underlying object
|
24
|
+
def perform
|
25
|
+
target && true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
# The parent class Service requires that we define this method. This is our kicker. This method is only
|
30
|
+
# called when we need to deal with information from FedEx. Notice the caching into the @target variable.
|
31
|
+
def target
|
32
|
+
@target ||= request(build_request)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Just building some XML to send off to FedEx. FedEx require this particualr format.
|
36
|
+
def build_request
|
37
|
+
b = builder
|
38
|
+
build_authentication(b)
|
39
|
+
b.instruct!
|
40
|
+
|
41
|
+
b.VoidShipmentRequest do
|
42
|
+
b.Request do
|
43
|
+
b.RequestAction "1"
|
44
|
+
end
|
45
|
+
|
46
|
+
#TODO Determine whether tracking numbers are valid shipment identification numbers.
|
47
|
+
b.ShipmentIdentificationNumber tracking_number
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
# This module contains the various enumerations that UPS uses for its various options. When describing
|
4
|
+
# service options sometimes the docs will specify that the option must be an item in one of these arrays.
|
5
|
+
# You can also use these to build drop down options.
|
6
|
+
#
|
7
|
+
# Lastly, if you want to make these user friendly use a string inflector (humanize or titlize).
|
8
|
+
module Enumerations
|
9
|
+
# packaging options
|
10
|
+
PACKAGING_TYPES = {
|
11
|
+
"00" => "UNKNOWN",
|
12
|
+
"01" => "UPS Letter",
|
13
|
+
"02" => "Package",
|
14
|
+
"03" => "Tube",
|
15
|
+
"04" => "Pak",
|
16
|
+
"21" => "Express Box",
|
17
|
+
"24" => "25KG Box",
|
18
|
+
"25" => "10KG Box",
|
19
|
+
"30" => "Pallet",
|
20
|
+
"2a" => "Small Express Box",
|
21
|
+
"2b" => "Medium Express Box",
|
22
|
+
"2c" => "Large Express Box"
|
23
|
+
}
|
24
|
+
|
25
|
+
# delivery options
|
26
|
+
DROPOFF_TYPES = {
|
27
|
+
"01" => "Daily Pickup",
|
28
|
+
"03" => "Customer Counter",
|
29
|
+
"06" => "One Time Pickup",
|
30
|
+
"07" => "On Call Air",
|
31
|
+
"11" => "Suggested Retail Rates",
|
32
|
+
"19" => "Letter Center",
|
33
|
+
"20" => "Air Service Center"
|
34
|
+
}
|
35
|
+
SERVICE_TYPES = {
|
36
|
+
"01" => "Next Day Air",
|
37
|
+
"02" => "2nd Day Air",
|
38
|
+
"03" => "Ground",
|
39
|
+
"07" => "Worldwide Express",
|
40
|
+
"08" => "Worldwide Expedited",
|
41
|
+
"11" => "Standard",
|
42
|
+
"12" => "3 Day Select",
|
43
|
+
"13" => "Next Day Air Saver",
|
44
|
+
"14" => "Next Day Air Early AM",
|
45
|
+
"54" => "Worldwide Express Plus",
|
46
|
+
"59" => "2nd Day Air AM",
|
47
|
+
"65" => "Saver",
|
48
|
+
"82" => "UPS Today Standard",
|
49
|
+
"83" => "UPS Today Dedicated Courier",
|
50
|
+
"84" => "UPS Today Intercity",
|
51
|
+
"85" => "UPS Today Express",
|
52
|
+
"86" => "UPS Today Express Saver"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
# If UPS responds with an error, we try our best to pull the pertinent information out of that
|
4
|
+
# response and raise it with this object. Any time UPS says there is a problem an object of this
|
5
|
+
# class will be raised.
|
6
|
+
#
|
7
|
+
# === Tip
|
8
|
+
#
|
9
|
+
# If you want to see the raw request / respose catch the error object and call the request / response method. Ex:
|
10
|
+
#
|
11
|
+
# begin
|
12
|
+
# # my UPS code
|
13
|
+
# rescue Shippinglogic::UPS::Error => e
|
14
|
+
# # do whatever you want here, just do:
|
15
|
+
# # e.request
|
16
|
+
# # e.response
|
17
|
+
# # to get the raw response from UPS
|
18
|
+
# end
|
19
|
+
class Error < Shippinglogic::Error
|
20
|
+
def initialize(request, response)
|
21
|
+
super
|
22
|
+
|
23
|
+
if response.blank?
|
24
|
+
add_error("The response from UPS was blank.")
|
25
|
+
elsif !response.is_a?(Hash)
|
26
|
+
add_error("The response from UPS was malformed and was not in a valid XML format.")
|
27
|
+
elsif errors = response.fetch(:response, {})[:error]
|
28
|
+
errors = errors.is_a?(Array) ? errors : [errors]
|
29
|
+
errors.delete_if { |error| Response::SUCCESSFUL_SEVERITIES.include?(error[:error_severity]) }
|
30
|
+
errors.each { |error| add_error(error[:error_description], error[:error_code]) }
|
31
|
+
else
|
32
|
+
add_error(
|
33
|
+
"There was a problem with your UPS request, and we couldn't locate a specific error message. This means your response " +
|
34
|
+
"was in an unexpected format. You might try glancing at the raw response by using the 'response' method on this error object."
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
super(errors.collect { |error| error[:message] }.join(", "))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module Shippinglogic
|
4
|
+
class UPS
|
5
|
+
class Label < Service
|
6
|
+
def self.path
|
7
|
+
"/LabelRecovery"
|
8
|
+
end
|
9
|
+
|
10
|
+
attribute :tracking_number, :string
|
11
|
+
|
12
|
+
private
|
13
|
+
def target
|
14
|
+
@target ||= parse_response(request(build_request))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Just building some XML to send off to USP using our various options
|
18
|
+
def build_request
|
19
|
+
b = builder
|
20
|
+
build_authentication(b)
|
21
|
+
b.instruct!
|
22
|
+
|
23
|
+
b.LabelRecoveryRequest do
|
24
|
+
b.Request do
|
25
|
+
b.RequestAction "LabelRecovery"
|
26
|
+
end
|
27
|
+
|
28
|
+
b.LabelSpecification do
|
29
|
+
b.LabelImageFormat do
|
30
|
+
b.Code "GIF"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
b.Translate do
|
35
|
+
b.LanguageCode "eng"
|
36
|
+
b.DialectCode "US"
|
37
|
+
b.Code "01"
|
38
|
+
end
|
39
|
+
|
40
|
+
b.TrackingNumber tracking_number
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_response(response)
|
45
|
+
return unless details = response.fetch(:label_results, {})[:label_image]
|
46
|
+
Base64.decode64(details[:graphic_image])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class UPS
|
3
|
+
# An interface to the rate services provided by UPS. Allows you to get an array of rates from UPS for a shipment,
|
4
|
+
# or a single rate for a specific service.
|
5
|
+
#
|
6
|
+
# == Options
|
7
|
+
# === Shipper options
|
8
|
+
#
|
9
|
+
# * <tt>shipper_name</tt> - name of the shipper.
|
10
|
+
# * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
11
|
+
# * <tt>shipper_city</tt> - city part of the address.
|
12
|
+
# * <tt>shipper_state_</tt> - state part of the address, use state abreviations.
|
13
|
+
# * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
14
|
+
# * <tt>shipper_country</tt> - country code part of the address. UPS expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
|
15
|
+
#
|
16
|
+
# === Recipient options
|
17
|
+
#
|
18
|
+
# * <tt>recipient_name</tt> - name of the recipient.
|
19
|
+
# * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
20
|
+
# * <tt>recipient_city</tt> - city part of the address.
|
21
|
+
# * <tt>recipient_state</tt> - state part of the address, use state abreviations.
|
22
|
+
# * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
23
|
+
# * <tt>recipient_country</tt> - country code part of the address. UPS expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
|
24
|
+
# * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
25
|
+
#
|
26
|
+
# === Packaging options
|
27
|
+
#
|
28
|
+
# One thing to note is that UPS does support multiple package shipments. The problem is that all of the packages must be identical.
|
29
|
+
# UPS specifically notes in their documentation that mutiple package specifications are not allowed. So your only option for a
|
30
|
+
# multi package shipment is to increase the package_count option and keep the dimensions and weight the same for all packages. Then again,
|
31
|
+
# the documentation for the UPS web services is terrible, so I could be wrong. Any tests I tried resulted in an error though.
|
32
|
+
#
|
33
|
+
# * <tt>packaging_type</tt> - one of PACKAGE_TYPES. (default: YOUR_PACKAGING)
|
34
|
+
# * <tt>package_count</tt> - the number of packages in your shipment. (default: 1)
|
35
|
+
# * <tt>package_weight</tt> - a single packages weight.
|
36
|
+
# * <tt>package_weight_units</tt> - either LB or KG. (default: LB)
|
37
|
+
# * <tt>package_length</tt> - a single packages length, only required if using YOUR_PACKAGING for packaging_type.
|
38
|
+
# * <tt>package_width</tt> - a single packages width, only required if using YOUR_PACKAGING for packaging_type.
|
39
|
+
# * <tt>package_height</tt> - a single packages height, only required if using YOUR_PACKAGING for packaging_type.
|
40
|
+
# * <tt>package_dimension_units</tt> - either IN or CM. (default: IN)
|
41
|
+
#
|
42
|
+
# === Monetary options
|
43
|
+
#
|
44
|
+
# * <tt>currency_type</tt> - the type of currency. (default: nil, because UPS will default to your account preferences)
|
45
|
+
# * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil)
|
46
|
+
# * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then
|
47
|
+
# you can specify that here. (default: your account number)
|
48
|
+
#
|
49
|
+
# === Delivery options
|
50
|
+
#
|
51
|
+
# * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all
|
52
|
+
# available services. (default: nil)
|
53
|
+
# * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
|
54
|
+
# * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP)
|
55
|
+
#
|
56
|
+
# === Misc options
|
57
|
+
#
|
58
|
+
# * <tt>documents_only</tt> - whether the package consists of only documents (default: false)
|
59
|
+
#
|
60
|
+
# == Simple Example
|
61
|
+
#
|
62
|
+
# Here is a very simple example. Mix and match the options above to get more accurate rates:
|
63
|
+
#
|
64
|
+
# ups = Shippinglogic::UPS.new(key, password, account)
|
65
|
+
# rates = ups.rate(
|
66
|
+
# :shipper_postal_code => "10007",
|
67
|
+
# :shipper_country => "US",
|
68
|
+
# :recipient_postal_code => "75201",
|
69
|
+
# :recipient_country_code => "US",
|
70
|
+
# :package_weight => 24,
|
71
|
+
# :package_length => 12,
|
72
|
+
# :package_width => 12,
|
73
|
+
# :package_height => 12
|
74
|
+
# )
|
75
|
+
#
|
76
|
+
# rates.first
|
77
|
+
# #<Shippinglogic::UPS::Rate::Service:0x10354d108 @currency="USD", @speed=nil,
|
78
|
+
# @rate=#<BigDecimal:10353ac10,'0.1885E2',18(18)>, @type="Ground", @name="Ground">
|
79
|
+
#
|
80
|
+
# # to show accessor methods
|
81
|
+
# rates.first.name
|
82
|
+
# # => "Ground"
|
83
|
+
class Rate < Service
|
84
|
+
def self.path
|
85
|
+
"/Rate"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Each rate result is an object of this class
|
89
|
+
class Service; attr_accessor :name, :type, :speed, :rate, :currency; end
|
90
|
+
|
91
|
+
# shipper options
|
92
|
+
attribute :shipper_name, :string
|
93
|
+
attribute :shipper_streets, :string
|
94
|
+
attribute :shipper_city, :string
|
95
|
+
attribute :shipper_state, :string
|
96
|
+
attribute :shipper_postal_code, :string
|
97
|
+
attribute :shipper_country, :string, :modifier => :country_code
|
98
|
+
|
99
|
+
# recipient options
|
100
|
+
attribute :recipient_name, :string
|
101
|
+
attribute :recipient_streets, :string
|
102
|
+
attribute :recipient_city, :string
|
103
|
+
attribute :recipient_state, :string
|
104
|
+
attribute :recipient_postal_code, :string
|
105
|
+
attribute :recipient_country, :string, :modifier => :country_code
|
106
|
+
attribute :recipient_residential, :boolean, :default => false
|
107
|
+
|
108
|
+
# packaging options
|
109
|
+
attribute :packaging_type, :string, :default => "00"
|
110
|
+
attribute :package_count, :integer, :default => 1
|
111
|
+
attribute :package_weight, :float
|
112
|
+
attribute :package_weight_units, :string, :default => "LBS"
|
113
|
+
attribute :package_length, :float
|
114
|
+
attribute :package_width, :float
|
115
|
+
attribute :package_height, :float
|
116
|
+
attribute :package_dimension_units, :string, :default => "IN"
|
117
|
+
|
118
|
+
# monetary options
|
119
|
+
attribute :currency_type, :string
|
120
|
+
attribute :insured_value, :decimal
|
121
|
+
attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.account }
|
122
|
+
|
123
|
+
# delivery options
|
124
|
+
attribute :service_type, :string
|
125
|
+
attribute :dropoff_type, :string, :default => "01"
|
126
|
+
attribute :saturday, :boolean, :default => false
|
127
|
+
|
128
|
+
# misc options
|
129
|
+
attribute :documents_only, :boolean, :default => false
|
130
|
+
|
131
|
+
private
|
132
|
+
def target
|
133
|
+
@target ||= parse_response(request(build_request))
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_request
|
137
|
+
b = builder
|
138
|
+
build_authentication(b)
|
139
|
+
b.instruct!
|
140
|
+
|
141
|
+
b.RatingServiceSelectionRequest do
|
142
|
+
b.Request do
|
143
|
+
b.RequestAction "Rate"
|
144
|
+
b.RequestOption service_type ? "Rate" : "Shop"
|
145
|
+
end
|
146
|
+
|
147
|
+
b.PickupType do
|
148
|
+
b.Code dropoff_type
|
149
|
+
end
|
150
|
+
|
151
|
+
b.Shipment do
|
152
|
+
b.Shipper do
|
153
|
+
b.Name shipper_name
|
154
|
+
b.ShipperNumber payor_account_number
|
155
|
+
build_address(b, :shipper)
|
156
|
+
end
|
157
|
+
|
158
|
+
b.ShipTo do
|
159
|
+
b.CompanyName recipient_name
|
160
|
+
build_address(b, :recipient)
|
161
|
+
end
|
162
|
+
|
163
|
+
if service_type
|
164
|
+
b.Service do
|
165
|
+
b.Code service_type
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
b.DocumentsOnly if documents_only
|
170
|
+
|
171
|
+
package_count.times do |i|
|
172
|
+
b.Package do
|
173
|
+
b.PackagingType do
|
174
|
+
b.Code packaging_type
|
175
|
+
end
|
176
|
+
|
177
|
+
b.Dimensions do
|
178
|
+
b.UnitOfMeasurement do
|
179
|
+
b.Code package_dimension_units
|
180
|
+
end
|
181
|
+
|
182
|
+
b.Length "%.2f" % package_length
|
183
|
+
b.Width "%.2f" % package_width
|
184
|
+
b.Height "%.2f" % package_height
|
185
|
+
end
|
186
|
+
|
187
|
+
b.PackageWeight do
|
188
|
+
b.UnitOfMeasurement do
|
189
|
+
b.Code package_weight_units
|
190
|
+
end
|
191
|
+
|
192
|
+
b.Weight "%.1f" % package_weight
|
193
|
+
end
|
194
|
+
|
195
|
+
b.PackageServiceOptions do
|
196
|
+
if insured_value
|
197
|
+
b.InsuredValue do
|
198
|
+
b.MonetaryValue insured_value
|
199
|
+
b.CurrencyCode currency_type
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
b.ShipmentServiceOptions do
|
207
|
+
b.SaturdayDelivery if saturday
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_response(response)
|
214
|
+
return [] if !response[:rated_shipment]
|
215
|
+
|
216
|
+
response[:rated_shipment].collect do |details|
|
217
|
+
service = Service.new
|
218
|
+
service.name = Enumerations::SERVICE_TYPES[details[:service][:code]]
|
219
|
+
service.type = service.name
|
220
|
+
service.speed = (days = details[:guaranteed_days_to_delivery]) && (days.to_i * 86400)
|
221
|
+
service.rate = BigDecimal.new(details[:total_charges][:monetary_value])
|
222
|
+
service.currency = details[:total_charges][:currency_code]
|
223
|
+
service
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|