friendly_shipping 0.4.14 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/friendly_shipping.gemspec +1 -1
- data/lib/friendly_shipping/services/ups.rb +5 -2
- data/lib/friendly_shipping/services/ups/rate_estimate_options.rb +100 -0
- data/lib/friendly_shipping/services/ups/rate_estimate_package_options.rb +24 -0
- data/lib/friendly_shipping/services/ups/serialize_package_node.rb +3 -2
- data/lib/friendly_shipping/services/ups/serialize_rating_service_selection_request.rb +40 -34
- data/lib/friendly_shipping/services/usps.rb +7 -12
- data/lib/friendly_shipping/services/usps/choose_package_rate.rb +3 -4
- data/lib/friendly_shipping/services/usps/parse_package_rate.rb +3 -5
- data/lib/friendly_shipping/services/usps/parse_rate_response.rb +10 -6
- data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +28 -0
- data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +58 -0
- data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +7 -17
- data/lib/friendly_shipping/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7033f8bf845a41a0cefa6899a465fd78a85077ff565c7c30f67d04f20708ff6
|
4
|
+
data.tar.gz: 2c5207e0139608d78b7c65b6f273b9dc222bc750bc47e587147e8f486c908b43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00d7ce8fe1d737b482d71a6ff96195a82dc70b94c7784291c9cc18eb2f71ca529dadf33900a7f5aef223a4f89a3b1297403d7bc352dd9e8255c139b11b7351c1
|
7
|
+
data.tar.gz: 7a7746f0c59dfcd03d2a330142f106cfe3c0fc7b82628859691d28c0dbd27e3d7584670c44f3509780271aaaf44fbd83551d0e1415ac14807d9ec2578891ab95
|
data/.circleci/config.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.5] - 2020-01-24
|
8
|
+
|
9
|
+
### Removed
|
10
|
+
- Drop support for Ruby 2.4 (#83)
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- UPS/USPS Services: Use options classes for rate estimates (#82)
|
14
|
+
|
7
15
|
## [0.4.14] - 2020-01-21
|
8
16
|
|
9
17
|
### Changed
|
data/friendly_shipping.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_runtime_dependency "nokogiri", ">= 1.6"
|
29
29
|
spec.add_runtime_dependency "physical", ">= 0.4.4"
|
30
30
|
spec.add_runtime_dependency "rest-client", "~> 2.0"
|
31
|
-
spec.required_ruby_version = '>= 2.
|
31
|
+
spec.required_ruby_version = '>= 2.5'
|
32
32
|
|
33
33
|
spec.add_development_dependency "bundler"
|
34
34
|
spec.add_development_dependency "dotenv"
|
@@ -20,6 +20,7 @@ require 'friendly_shipping/services/ups/parse_time_in_transit_response'
|
|
20
20
|
require 'friendly_shipping/services/ups/parse_void_shipment_response'
|
21
21
|
require 'friendly_shipping/services/ups/shipping_methods'
|
22
22
|
require 'friendly_shipping/services/ups/label_options'
|
23
|
+
require 'friendly_shipping/services/ups/rate_estimate_options'
|
23
24
|
require 'friendly_shipping/services/ups/timing_options'
|
24
25
|
|
25
26
|
module FriendlyShipping
|
@@ -63,10 +64,12 @@ module FriendlyShipping
|
|
63
64
|
|
64
65
|
# Get rates for a shipment
|
65
66
|
# @param [Physical::Shipment] shipment The shipment we want to get rates for
|
67
|
+
# @param [FriendlyShipping::Services::Ups::RateEstimateOptions] options What options
|
68
|
+
# to use for this rate estimate call
|
66
69
|
# @return [Result<ApiResult<Array<Rate>>>] The rates returned from UPS encoded in a
|
67
70
|
# `FriendlyShipping::ApiResult` object.
|
68
|
-
def rate_estimates(shipment, debug: false)
|
69
|
-
rate_request_xml = SerializeRatingServiceSelectionRequest.call(shipment: shipment)
|
71
|
+
def rate_estimates(shipment, options:, debug: false)
|
72
|
+
rate_request_xml = SerializeRatingServiceSelectionRequest.call(shipment: shipment, options: options)
|
70
73
|
url = base_url + RESOURCES[:rates]
|
71
74
|
request = FriendlyShipping::Request.new(
|
72
75
|
url: url,
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups/rate_estimate_package_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
# Option container for rating a shipment via UPS
|
8
|
+
#
|
9
|
+
# Required:
|
10
|
+
#
|
11
|
+
# @option shipper_number [String] The shipper number of the origin of the shipment.
|
12
|
+
#
|
13
|
+
# Optional:
|
14
|
+
#
|
15
|
+
# @option carbon_neutral [Boolean] Ship with UPS' carbon neutral program
|
16
|
+
# @option customer_context [String ] Optional element to identify transactions between client and server
|
17
|
+
# @option customer_classification [Symbol] Which kind of rates to request. See UPS docs for more details. Default: `shipper_number`
|
18
|
+
# @option negotiated_rates [Boolean] if truthy negotiated rates will be requested from ups. Only valid if
|
19
|
+
# shipper account has negotiated rates. Default: false
|
20
|
+
# @option saturday_delivery [Boolean] should we request Saturday delivery?. Default: false
|
21
|
+
# @option saturday_pickup [Boolean] should we request Saturday pickup?. Default: false
|
22
|
+
# @option shipping_method [FriendlyShipping::ShippingMethod] Request rates for a particular shipping method only?
|
23
|
+
# Default is `nil`, which translates to 'All shipping methods' (The "Shop" option in UPS parlance)
|
24
|
+
# @option with_time_in_transit [Boolean] Whether to request timing information alongside the rates
|
25
|
+
# @option package_options_class [Class] See FriendlyShipping::ShipmentOptions
|
26
|
+
#
|
27
|
+
class Ups
|
28
|
+
class RateEstimateOptions < FriendlyShipping::ShipmentOptions
|
29
|
+
PICKUP_TYPE_CODES = {
|
30
|
+
daily_pickup: "01",
|
31
|
+
customer_counter: "03",
|
32
|
+
one_time_pickup: "06",
|
33
|
+
on_call_air: "07",
|
34
|
+
suggested_retail_rates: "11",
|
35
|
+
letter_center: "19",
|
36
|
+
air_service_center: "20"
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
CUSTOMER_CLASSIFICATION_CODES = {
|
40
|
+
shipper_number: "00",
|
41
|
+
daily_rates: "01",
|
42
|
+
retail_rates: "04",
|
43
|
+
regional_rates: "05",
|
44
|
+
general_rates: "06",
|
45
|
+
standard_rates: "53"
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
attr_reader :carbon_neutral,
|
49
|
+
:customer_context,
|
50
|
+
:destination_account,
|
51
|
+
:negotiated_rates,
|
52
|
+
:saturday_delivery,
|
53
|
+
:saturday_pickup,
|
54
|
+
:shipper,
|
55
|
+
:shipper_number,
|
56
|
+
:shipping_method,
|
57
|
+
:with_time_in_transit
|
58
|
+
|
59
|
+
def initialize(
|
60
|
+
shipper_number:,
|
61
|
+
carbon_neutral: true,
|
62
|
+
customer_context: nil,
|
63
|
+
customer_classification: :daily_rates,
|
64
|
+
destination_account: nil,
|
65
|
+
negotiated_rates: false,
|
66
|
+
pickup_type: :daily_pickup,
|
67
|
+
saturday_delivery: false,
|
68
|
+
saturday_pickup: false,
|
69
|
+
shipper: nil,
|
70
|
+
shipping_method: nil,
|
71
|
+
with_time_in_transit: false,
|
72
|
+
package_options_class: FriendlyShipping::Services::Ups::RateEstimatePackageOptions,
|
73
|
+
**kwargs
|
74
|
+
)
|
75
|
+
@carbon_neutral = carbon_neutral
|
76
|
+
@customer_context = customer_context
|
77
|
+
@customer_classification = customer_classification
|
78
|
+
@destination_account = destination_account
|
79
|
+
@negotiated_rates = negotiated_rates
|
80
|
+
@shipper_number = shipper_number
|
81
|
+
@pickup_type = pickup_type
|
82
|
+
@saturday_delivery = saturday_delivery
|
83
|
+
@saturday_pickup = saturday_pickup
|
84
|
+
@shipper = shipper
|
85
|
+
@shipping_method = shipping_method
|
86
|
+
@with_time_in_transit = with_time_in_transit
|
87
|
+
super kwargs.merge(package_options_class: package_options_class)
|
88
|
+
end
|
89
|
+
|
90
|
+
def pickup_type_code
|
91
|
+
PICKUP_TYPE_CODES[@pickup_type]
|
92
|
+
end
|
93
|
+
|
94
|
+
def customer_classification_code
|
95
|
+
CUSTOMER_CLASSIFICATION_CODES[@customer_classification]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups/label_item_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class Ups
|
8
|
+
# Package properties relevant for quoting a UPS package
|
9
|
+
#
|
10
|
+
# @option transmit_dimensions [Boolean] Whether to transmit the dimensions of this package, or quote only based on weight.
|
11
|
+
class RateEstimatePackageOptions < FriendlyShipping::PackageOptions
|
12
|
+
attr_reader :transmit_dimensions
|
13
|
+
|
14
|
+
def initialize(
|
15
|
+
transmit_dimensions: true,
|
16
|
+
**kwargs
|
17
|
+
)
|
18
|
+
@transmit_dimensions = transmit_dimensions
|
19
|
+
super kwargs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -9,7 +9,8 @@ module FriendlyShipping
|
|
9
9
|
package:,
|
10
10
|
reference_numbers: {},
|
11
11
|
delivery_confirmation_code: nil,
|
12
|
-
shipper_release: false
|
12
|
+
shipper_release: false,
|
13
|
+
transmit_dimensions: true
|
13
14
|
)
|
14
15
|
xml.Package do
|
15
16
|
xml.PackagingType do
|
@@ -20,7 +21,7 @@ module FriendlyShipping
|
|
20
21
|
xml.Description(package.items.map(&:description).compact.join(', '))
|
21
22
|
end
|
22
23
|
|
23
|
-
if package.dimensions.all? { |dim| !dim.value.zero? && !dim.value.infinite? }
|
24
|
+
if transmit_dimensions && package.dimensions.all? { |dim| !dim.value.zero? && !dim.value.infinite? }
|
24
25
|
xml.Dimensions do
|
25
26
|
xml.UnitOfMeasurement do
|
26
27
|
xml.Code('IN')
|
@@ -8,47 +8,30 @@ module FriendlyShipping
|
|
8
8
|
module Services
|
9
9
|
class Ups
|
10
10
|
class SerializeRatingServiceSelectionRequest
|
11
|
-
|
12
|
-
|
13
|
-
customer_counter: "03",
|
14
|
-
one_time_pickup: "06",
|
15
|
-
on_call_air: "07",
|
16
|
-
suggested_retail_rates: "11",
|
17
|
-
letter_center: "19",
|
18
|
-
air_service_center: "20"
|
19
|
-
)
|
20
|
-
|
21
|
-
CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new(
|
22
|
-
shipper_number: "00",
|
23
|
-
daily_rates: "01",
|
24
|
-
retail_rates: "04",
|
25
|
-
regional_rates: "05",
|
26
|
-
general_rates: "06",
|
27
|
-
standard_rates: "53"
|
28
|
-
)
|
29
|
-
|
30
|
-
def self.call(shipment:)
|
31
|
-
shipper = shipment.options[:shipper] || shipment.origin
|
32
|
-
pickup_type = PICKUP_CODES[shipment.options[:pickup_type] || :daily_pickup]
|
33
|
-
customer_classification = CUSTOMER_CLASSIFICATIONS[
|
34
|
-
shipment.options[:customer_classification] || :daily_rates
|
35
|
-
]
|
36
|
-
origin_account = shipment.options[:origin_account]
|
37
|
-
destination_account = shipment.options[:destination_account]
|
11
|
+
def self.call(shipment:, options:)
|
12
|
+
shipper = options.shipper || shipment.origin
|
38
13
|
|
39
14
|
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
40
15
|
xml.RatingServiceSelectionRequest do
|
41
16
|
xml.Request do
|
42
17
|
xml.RequestAction('Rate')
|
43
|
-
|
18
|
+
# If no shipping method is given, request all of them
|
19
|
+
# I one is given, omit the request option. It then becomes "Rate", the default.
|
20
|
+
xml.RequestOption('Shop') unless options.shipping_method
|
44
21
|
xml.SubVersion('1707')
|
22
|
+
# Optional element to identify transactions between client and server.
|
23
|
+
if options.customer_context
|
24
|
+
xml.TransactionReference do
|
25
|
+
xml.CustomerContext(options.customer_context)
|
26
|
+
end
|
27
|
+
end
|
45
28
|
end
|
46
29
|
|
47
30
|
xml.PickupType do
|
48
|
-
xml.Code(
|
31
|
+
xml.Code(options.pickup_type_code)
|
49
32
|
end
|
50
33
|
xml.CustomerClassification do
|
51
|
-
xml.Code(
|
34
|
+
xml.Code(options.customer_classification_code)
|
52
35
|
end
|
53
36
|
|
54
37
|
xml.Shipment do
|
@@ -56,14 +39,14 @@ module FriendlyShipping
|
|
56
39
|
xml.Shipper do
|
57
40
|
SerializeAddressSnippet.call(xml: xml, location: shipper)
|
58
41
|
|
59
|
-
xml.ShipperNumber(
|
42
|
+
xml.ShipperNumber(options.shipper_number)
|
60
43
|
end
|
61
44
|
|
62
45
|
xml.ShipTo do
|
63
46
|
SerializeAddressSnippet.call(xml: xml, location: shipment.destination)
|
64
47
|
|
65
|
-
if destination_account
|
66
|
-
xml.ShipperAssignedIdentificationNumber(destination_account)
|
48
|
+
if options.destination_account
|
49
|
+
xml.ShipperAssignedIdentificationNumber(options.destination_account)
|
67
50
|
end
|
68
51
|
end
|
69
52
|
|
@@ -74,7 +57,30 @@ module FriendlyShipping
|
|
74
57
|
end
|
75
58
|
|
76
59
|
shipment.packages.each do |package|
|
77
|
-
|
60
|
+
package_options = options.options_for_package(package)
|
61
|
+
SerializePackageNode.call(
|
62
|
+
xml: xml,
|
63
|
+
package: package,
|
64
|
+
transmit_dimensions: package_options.transmit_dimensions
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
if options.shipping_method
|
69
|
+
xml.Service do
|
70
|
+
xml.Code options.shipping_method.service_code
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
xml.ShipmentServiceOptions do
|
75
|
+
xml.UPScarbonneutralIndicator if options.carbon_neutral
|
76
|
+
xml.SaturdayDelivery if options.saturday_delivery
|
77
|
+
xml.SaturdayPickup if options.saturday_pickup
|
78
|
+
end
|
79
|
+
|
80
|
+
if options.negotiated_rates
|
81
|
+
xml.RateInformation do
|
82
|
+
xml.NegotiatedRatesIndicator if options.negotiated_rates
|
83
|
+
end
|
78
84
|
end
|
79
85
|
end
|
80
86
|
end
|
@@ -11,6 +11,7 @@ require 'friendly_shipping/services/usps/parse_city_state_lookup_response'
|
|
11
11
|
require 'friendly_shipping/services/usps/parse_rate_response'
|
12
12
|
require 'friendly_shipping/services/usps/parse_time_in_transit_response'
|
13
13
|
require 'friendly_shipping/services/usps/timing_options'
|
14
|
+
require 'friendly_shipping/services/usps/rate_estimate_options'
|
14
15
|
|
15
16
|
module FriendlyShipping
|
16
17
|
module Services
|
@@ -48,25 +49,19 @@ module FriendlyShipping
|
|
48
49
|
|
49
50
|
# Get rate estimates from USPS
|
50
51
|
#
|
51
|
-
# @param [Physical::Shipment] shipment
|
52
|
-
#
|
53
|
-
#
|
54
|
-
# @property [Symbol] box_name The type of box we want to get rates for. Has to be one of the keys
|
55
|
-
# of FriendlyShipping::Services::Usps::CONTAINERS.
|
56
|
-
# @property [Boolean] commercial_pricing Whether we prefer commercial pricing results or retail results
|
57
|
-
# @property [Boolean] hold_for_pickup Whether we want a rate with Hold For Pickup Service
|
58
|
-
# @param [Physical::ShippingMethod] shipping_method The shipping method ("service" in USPS parlance) we want
|
59
|
-
# to get rates for.
|
52
|
+
# @param [Physical::Shipment] shipment
|
53
|
+
# @param [FriendlyShipping::Services::Usps::RateEstimateOptions] options What options
|
54
|
+
# to use for this rate estimate call
|
60
55
|
#
|
61
56
|
# @return [Result<Array<FriendlyShipping::Rate>>] When successfully parsing, an array of rates in a Success Monad.
|
62
57
|
# When the parsing is not successful or USPS can't give us rates, a Failure monad containing something that
|
63
58
|
# can be serialized into an error message using `to_s`.
|
64
|
-
def rate_estimates(shipment,
|
65
|
-
rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login,
|
59
|
+
def rate_estimates(shipment, options: RateEstimateOptions.new, debug: false)
|
60
|
+
rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login, options: options)
|
66
61
|
request = build_request(api: :rates, xml: rate_request_xml, debug: debug)
|
67
62
|
|
68
63
|
client.post(request).bind do |response|
|
69
|
-
ParseRateResponse.call(response: response, request: request, shipment: shipment)
|
64
|
+
ParseRateResponse.call(response: response, request: request, shipment: shipment, options: options)
|
70
65
|
end
|
71
66
|
end
|
72
67
|
|
@@ -16,19 +16,18 @@ module FriendlyShipping
|
|
16
16
|
# @param [Array<FriendlyShipping::Rate>] The rates we select from
|
17
17
|
#
|
18
18
|
# @return [FriendlyShipping::Rate] The rate that most closely matches our package
|
19
|
-
def self.call(shipping_method,
|
19
|
+
def self.call(shipping_method, rates, package_options)
|
20
20
|
# Keep all rates with the requested shipping method
|
21
21
|
rates_with_this_shipping_method = rates.select { |r| r.shipping_method == shipping_method }
|
22
22
|
|
23
23
|
# Keep only rates with the package type of this package
|
24
24
|
rates_with_this_package_type = rates_with_this_shipping_method.select do |r|
|
25
|
-
r.data[:box_name] ==
|
26
|
-
r.data[:box_name] == :flat_rate_boxes && package.properties[:box_name]&.match?(FLAT_RATE_BOX)
|
25
|
+
r.data[:box_name] == package_options.box_name
|
27
26
|
end
|
28
27
|
|
29
28
|
# Filter by our package's `hold_for_pickup` option
|
30
29
|
rates_with_this_hold_for_pickup_option = rates_with_this_package_type.select do |r|
|
31
|
-
r.data[:hold_for_pickup] ==
|
30
|
+
r.data[:hold_for_pickup] == package_options.hold_for_pickup
|
32
31
|
end
|
33
32
|
|
34
33
|
# At this point, we have one or two rates left, and they're similar enough.
|
@@ -57,7 +57,7 @@ module FriendlyShipping
|
|
57
57
|
CURRENCY = Money::Currency.new('USD').freeze
|
58
58
|
|
59
59
|
class << self
|
60
|
-
def call(rate_node, package)
|
60
|
+
def call(rate_node, package, package_options)
|
61
61
|
# "A mail class identifier for the postage returned. Not necessarily unique within a <Package/>."
|
62
62
|
# (from the USPS docs). We save this on the data Hash, but do not use it for identifying shipping methods.
|
63
63
|
service_code = rate_node.attributes[SERVICE_CODE_TAG].value
|
@@ -84,7 +84,7 @@ module FriendlyShipping
|
|
84
84
|
# In these cases, return the commercial rate instead of the normal rate.
|
85
85
|
# Some rates are available in both commercial and retail pricing - if we want the commercial pricing here,
|
86
86
|
# we need to specify the commercial_pricing property on the `Physical::Package`.
|
87
|
-
rate_value = if (
|
87
|
+
rate_value = if (package_options.commercial_pricing || rate_node.at(RATE_TAG).text.to_d.zero?) && rate_node.at(COMMERCIAL_RATE_TAG)
|
88
88
|
rate_node.at(COMMERCIAL_RATE_TAG).text.to_d
|
89
89
|
else
|
90
90
|
rate_node.at(RATE_TAG).text.to_d
|
@@ -107,9 +107,7 @@ module FriendlyShipping
|
|
107
107
|
# We find out the box name using a bit of Regex magic using named captures. See the `BOX_REGEX`
|
108
108
|
# constant above.
|
109
109
|
box_name_match = service_name.match(/#{BOX_REGEX}/)
|
110
|
-
box_name =
|
111
|
-
box_name_match.named_captures.compact.keys.last.to_sym
|
112
|
-
end
|
110
|
+
box_name = box_name_match ? box_name_match.named_captures.compact.keys.last.to_sym : :variable
|
113
111
|
|
114
112
|
# Combine all the gathered information in a FriendlyShipping::Rate object.
|
115
113
|
# Careful: This rate is only for one package within the shipment, and we get multiple
|
@@ -16,20 +16,24 @@ module FriendlyShipping
|
|
16
16
|
# @param [FriendlyShipping::Request] request The request that was used to obtain this Response
|
17
17
|
# @param [FriendlyShipping::Response] response The response that USPS returned
|
18
18
|
# @param [Physical::Shipment] shipment The shipment object we're trying to get results for
|
19
|
+
# @param [FriendlyShipping::Services::Usps::RateEstimateOptions] options The options we sent with this request
|
19
20
|
# @return [Result<ApiResult<Array<FriendlyShipping::Rate>>>] When successfully parsing, an array of rates in a Success Monad.
|
20
|
-
def call(request:, response:, shipment:)
|
21
|
+
def call(request:, response:, shipment:, options:)
|
21
22
|
# Filter out error responses and directly return a failure
|
22
23
|
parsing_result = ParseXMLResponse.call(response.body, 'RateV4Response')
|
23
24
|
rates = []
|
24
25
|
parsing_result.fmap do |xml|
|
25
26
|
# Get all the possible rates for each package
|
26
|
-
rates_by_package = rates_from_response_node(xml, shipment)
|
27
|
+
rates_by_package = rates_from_response_node(xml, shipment, options)
|
27
28
|
|
28
29
|
rates = SHIPPING_METHODS.map do |shipping_method|
|
29
30
|
# For every package ...
|
30
31
|
matching_rates = rates_by_package.map do |package, package_rates|
|
31
32
|
# ... choose the rate that fits this package best.
|
32
|
-
|
33
|
+
|
34
|
+
package_options = options.options_for_package(package)
|
35
|
+
|
36
|
+
ChoosePackageRate.call(shipping_method, package_rates, package_options)
|
33
37
|
end.compact # Some shipping rates are not available for every shipping method.
|
34
38
|
|
35
39
|
# in this case, go to the next shipping method.
|
@@ -62,17 +66,17 @@ module FriendlyShipping
|
|
62
66
|
# @param [Physical::Shipment] shipment The shipment we're trying to get rates for
|
63
67
|
#
|
64
68
|
# @return [Hash<Physical::Package => Array<FriendlyShipping::Rate>>]
|
65
|
-
def rates_from_response_node(xml, shipment)
|
69
|
+
def rates_from_response_node(xml, shipment, options)
|
66
70
|
xml.xpath(PACKAGE_NODE_XPATH).each_with_object({}) do |package_node, result|
|
67
71
|
package_id = package_node['ID']
|
68
72
|
corresponding_package = shipment.packages[package_id.to_i]
|
69
|
-
|
73
|
+
package_options = options.options_for_package(corresponding_package)
|
70
74
|
# There should always be a package in the original shipment that corresponds to the package ID
|
71
75
|
# in the USPS response.
|
72
76
|
raise BoxNotFoundError if corresponding_package.nil?
|
73
77
|
|
74
78
|
result[corresponding_package] = package_node.xpath(SERVICE_NODE_NAME).map do |service_node|
|
75
|
-
ParsePackageRate.call(service_node, corresponding_package)
|
79
|
+
ParsePackageRate.call(service_node, corresponding_package, package_options)
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/usps/rate_estimate_package_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
# Option container for rating a shipment via USPS
|
8
|
+
#
|
9
|
+
# Context: The shipment object we're trying to get results for
|
10
|
+
# USPS returns rates on a package-by-package basis, so the options for obtaining rates are
|
11
|
+
# set on the [FriendlyShipping/RateEstimateobect] hash. The possible options are:
|
12
|
+
|
13
|
+
# @param [Physical::ShippingMethod] shipping_method The shipping method ("service" in USPS parlance) we want
|
14
|
+
# to get rates for.
|
15
|
+
# @param [Boolean] commercial_pricing Whether we prefer commercial pricing results or retail results
|
16
|
+
# @param [Boolean] hold_for_pickup Whether we want a rate with Hold For Pickup Service
|
17
|
+
class Usps
|
18
|
+
class RateEstimateOptions < FriendlyShipping::ShipmentOptions
|
19
|
+
def initialize(
|
20
|
+
package_options_class: FriendlyShipping::Services::Usps::RateEstimatePackageOptions,
|
21
|
+
**kwargs
|
22
|
+
)
|
23
|
+
super kwargs.merge(package_options_class: package_options_class)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups/rate_estimate_package_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
# Options for one package when rating
|
8
|
+
#
|
9
|
+
# @param [Symbol] box_name The type of box we want to get rates for. Has to be one of the keys
|
10
|
+
# of FriendlyShipping::Services::Usps::CONTAINERS.
|
11
|
+
class Usps
|
12
|
+
class RateEstimatePackageOptions < FriendlyShipping::PackageOptions
|
13
|
+
attr_reader :box_name,
|
14
|
+
:commercial_pricing,
|
15
|
+
:first_class_mail_type,
|
16
|
+
:hold_for_pickup,
|
17
|
+
:shipping_method,
|
18
|
+
:transmit_dimensions
|
19
|
+
|
20
|
+
def initialize(
|
21
|
+
box_name: :variable,
|
22
|
+
commercial_pricing: false,
|
23
|
+
first_class_mail_type: nil,
|
24
|
+
hold_for_pickup: false,
|
25
|
+
shipping_method: nil,
|
26
|
+
transmit_dimensions: true,
|
27
|
+
**kwargs
|
28
|
+
)
|
29
|
+
@box_name = CONTAINERS.key?(box_name) ? box_name : :variable
|
30
|
+
@commercial_pricing = commercial_pricing
|
31
|
+
@first_class_mail_type = first_class_mail_type
|
32
|
+
@hold_for_pickup = hold_for_pickup
|
33
|
+
@shipping_method = shipping_method
|
34
|
+
@transmit_dimensions = transmit_dimensions
|
35
|
+
super kwargs
|
36
|
+
end
|
37
|
+
|
38
|
+
def container_code
|
39
|
+
CONTAINERS.fetch(box_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def first_class_mail_type_code
|
43
|
+
FIRST_CLASS_MAIL_TYPES[first_class_mail_type]
|
44
|
+
end
|
45
|
+
|
46
|
+
def service_code
|
47
|
+
return 'ALL' unless shipping_method
|
48
|
+
|
49
|
+
if commercial_pricing
|
50
|
+
"#{shipping_method.service_code} COMMERCIAL"
|
51
|
+
else
|
52
|
+
shipping_method.service_code
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -16,24 +16,24 @@ module FriendlyShipping
|
|
16
16
|
# @param [FriendlyShipping::ShippingMethod] shipping_method The shipping method we want to get rates
|
17
17
|
# for. If empty, we get all of them
|
18
18
|
# @return Array<[FriendlyShipping::Rate]> A set of Rates that this package may be sent with
|
19
|
-
def call(shipment:, login:,
|
19
|
+
def call(shipment:, login:, options:)
|
20
20
|
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
21
21
|
xml.RateV4Request('USERID' => login) do
|
22
22
|
shipment.packages.each_with_index do |package, index|
|
23
|
+
package_options = options.options_for_package(package)
|
23
24
|
xml.Package('ID' => index) do
|
24
|
-
xml.Service(
|
25
|
-
if
|
26
|
-
xml.FirstClassMailType(
|
25
|
+
xml.Service(package_options.service_code)
|
26
|
+
if package_options.first_class_mail_type
|
27
|
+
xml.FirstClassMailType(package_options.first_class_mail_type_code)
|
27
28
|
end
|
28
29
|
xml.ZipOrigination(shipment.origin.zip)
|
29
30
|
xml.ZipDestination(shipment.destination.zip)
|
30
31
|
xml.Pounds(0)
|
31
32
|
xml.Ounces(ounces_for(package))
|
32
33
|
size_code = size_code_for(package)
|
33
|
-
|
34
|
-
xml.Container(container)
|
34
|
+
xml.Container(package_options.container_code)
|
35
35
|
xml.Size(size_code)
|
36
|
-
if
|
36
|
+
if package_options.transmit_dimensions && package_options.container_code == 'VARIABLE'
|
37
37
|
xml.Width("%<width>0.2f" % { width: package.width.convert_to(:inches).value.to_f })
|
38
38
|
xml.Length("%<length>0.2f" % { length: package.length.convert_to(:inches).value.to_f })
|
39
39
|
xml.Height("%<height>0.2f" % { height: package.height.convert_to(:inches).value.to_f })
|
@@ -61,16 +61,6 @@ module FriendlyShipping
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
def service_code_by(shipping_method, package)
|
65
|
-
return 'ALL' unless shipping_method
|
66
|
-
|
67
|
-
if package.properties[:commercial_pricing]
|
68
|
-
"#{shipping_method.service_code} COMMERCIAL"
|
69
|
-
else
|
70
|
-
shipping_method.service_code
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
64
|
def ounces_for(package)
|
75
65
|
ounces = package.weight.convert_to(:ounces).value.to_f.round(2).ceil
|
76
66
|
ounces == 16 ? 15.999 : [ounces, 1].max
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly_shipping
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Meyerhoff
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: data_uri
|
@@ -309,6 +309,8 @@ files:
|
|
309
309
|
- lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb
|
310
310
|
- lib/friendly_shipping/services/ups/parse_void_shipment_response.rb
|
311
311
|
- lib/friendly_shipping/services/ups/parse_xml_response.rb
|
312
|
+
- lib/friendly_shipping/services/ups/rate_estimate_options.rb
|
313
|
+
- lib/friendly_shipping/services/ups/rate_estimate_package_options.rb
|
312
314
|
- lib/friendly_shipping/services/ups/serialize_access_request.rb
|
313
315
|
- lib/friendly_shipping/services/ups/serialize_address_snippet.rb
|
314
316
|
- lib/friendly_shipping/services/ups/serialize_address_validation_request.rb
|
@@ -342,6 +344,8 @@ files:
|
|
342
344
|
- lib/friendly_shipping/services/usps/parse_rate_response.rb
|
343
345
|
- lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb
|
344
346
|
- lib/friendly_shipping/services/usps/parse_xml_response.rb
|
347
|
+
- lib/friendly_shipping/services/usps/rate_estimate_options.rb
|
348
|
+
- lib/friendly_shipping/services/usps/rate_estimate_package_options.rb
|
345
349
|
- lib/friendly_shipping/services/usps/serialize_address_validation_request.rb
|
346
350
|
- lib/friendly_shipping/services/usps/serialize_city_state_lookup_request.rb
|
347
351
|
- lib/friendly_shipping/services/usps/serialize_rate_request.rb
|
@@ -365,7 +369,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
365
369
|
requirements:
|
366
370
|
- - ">="
|
367
371
|
- !ruby/object:Gem::Version
|
368
|
-
version: '2.
|
372
|
+
version: '2.5'
|
369
373
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
370
374
|
requirements:
|
371
375
|
- - ">="
|