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.
Files changed (74) hide show
  1. data/.document +5 -0
  2. data/CHANGELOG.rdoc +55 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +175 -0
  5. data/Rakefile +37 -0
  6. data/VERSION.yml +5 -0
  7. data/init.rb +1 -0
  8. data/lib/shippinglogic.rb +3 -0
  9. data/lib/shippinglogic/attributes.rb +121 -0
  10. data/lib/shippinglogic/error.rb +22 -0
  11. data/lib/shippinglogic/fedex.rb +84 -0
  12. data/lib/shippinglogic/fedex/cancel.rb +47 -0
  13. data/lib/shippinglogic/fedex/enumerations.rb +348 -0
  14. data/lib/shippinglogic/fedex/error.rb +47 -0
  15. data/lib/shippinglogic/fedex/rate.rb +229 -0
  16. data/lib/shippinglogic/fedex/request.rb +134 -0
  17. data/lib/shippinglogic/fedex/response.rb +72 -0
  18. data/lib/shippinglogic/fedex/service.rb +11 -0
  19. data/lib/shippinglogic/fedex/ship.rb +238 -0
  20. data/lib/shippinglogic/fedex/signature.rb +68 -0
  21. data/lib/shippinglogic/fedex/track.rb +124 -0
  22. data/lib/shippinglogic/proxy.rb +23 -0
  23. data/lib/shippinglogic/service.rb +42 -0
  24. data/lib/shippinglogic/ups.rb +83 -0
  25. data/lib/shippinglogic/ups/cancel.rb +52 -0
  26. data/lib/shippinglogic/ups/enumerations.rb +56 -0
  27. data/lib/shippinglogic/ups/error.rb +42 -0
  28. data/lib/shippinglogic/ups/label.rb +50 -0
  29. data/lib/shippinglogic/ups/rate.rb +228 -0
  30. data/lib/shippinglogic/ups/request.rb +49 -0
  31. data/lib/shippinglogic/ups/response.rb +58 -0
  32. data/lib/shippinglogic/ups/service.rb +11 -0
  33. data/lib/shippinglogic/ups/ship_accept.rb +53 -0
  34. data/lib/shippinglogic/ups/ship_confirm.rb +170 -0
  35. data/lib/shippinglogic/ups/track.rb +118 -0
  36. data/lib/shippinglogic/validation.rb +32 -0
  37. data/shippinglogic.gemspec +120 -0
  38. data/spec/attributes_spec.rb +67 -0
  39. data/spec/config/fedex_credentials.example.yml +4 -0
  40. data/spec/config/ups_credentials.example.yml +3 -0
  41. data/spec/error_spec.rb +43 -0
  42. data/spec/fedex/cancel_spec.rb +10 -0
  43. data/spec/fedex/error_spec.rb +26 -0
  44. data/spec/fedex/rate_spec.rb +87 -0
  45. data/spec/fedex/request_spec.rb +15 -0
  46. data/spec/fedex/responses/blank.xml +0 -0
  47. data/spec/fedex/responses/cancel_not_found.xml +7 -0
  48. data/spec/fedex/responses/failed_authentication.xml +7 -0
  49. data/spec/fedex/responses/malformed.xml +8 -0
  50. data/spec/fedex/responses/rate_defaults.xml +7 -0
  51. data/spec/fedex/responses/rate_insurance.xml +9 -0
  52. data/spec/fedex/responses/rate_no_services.xml +7 -0
  53. data/spec/fedex/responses/rate_non_custom_packaging.xml +7 -0
  54. data/spec/fedex/responses/ship_defaults.xml +7 -0
  55. data/spec/fedex/responses/ship_with_no_signature.xml +7 -0
  56. data/spec/fedex/responses/signature_defaults.xml +7 -0
  57. data/spec/fedex/responses/track_defaults.xml +7 -0
  58. data/spec/fedex/responses/unexpected.xml +1 -0
  59. data/spec/fedex/service_spec.rb +19 -0
  60. data/spec/fedex/ship_spec.rb +37 -0
  61. data/spec/fedex/signature_spec.rb +11 -0
  62. data/spec/fedex/spec_helper.rb +84 -0
  63. data/spec/fedex/track_spec.rb +37 -0
  64. data/spec/fedex_spec.rb +16 -0
  65. data/spec/lib/interceptor.rb +17 -0
  66. data/spec/proxy_spec.rb +42 -0
  67. data/spec/service_spec.rb +23 -0
  68. data/spec/spec_helper.rb +12 -0
  69. data/spec/ups/responses/blank.xml +0 -0
  70. data/spec/ups/responses/track_defaults.xml +2 -0
  71. data/spec/ups/spec_helper.rb +43 -0
  72. data/spec/ups_spec.rb +16 -0
  73. data/spec/validation_spec.rb +49 -0
  74. 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