friendly_shipping 0.5 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop-relaxed.yml +3 -4
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +29 -0
- data/friendly_shipping.gemspec +12 -12
- data/lib/friendly_shipping/api_failure.rb +2 -15
- data/lib/friendly_shipping/http_client.rb +16 -9
- data/lib/friendly_shipping/request.rb +7 -1
- data/lib/friendly_shipping/services/ship_engine/bad_request_handler.rb +15 -3
- data/lib/friendly_shipping/services/ups.rb +9 -1
- data/lib/friendly_shipping/services/ups/parse_address_classification_response.rb +5 -2
- data/lib/friendly_shipping/services/ups/parse_address_validation_response.rb +5 -2
- data/lib/friendly_shipping/services/ups/parse_city_state_lookup_response.rb +5 -2
- data/lib/friendly_shipping/services/ups/parse_rate_response.rb +5 -1
- data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +5 -1
- data/lib/friendly_shipping/services/ups/parse_shipment_confirm_response.rb +5 -1
- data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +5 -1
- data/lib/friendly_shipping/services/ups/parse_void_shipment_response.rb +5 -1
- data/lib/friendly_shipping/services/ups/parse_xml_response.rb +16 -7
- data/lib/friendly_shipping/services/ups_freight.rb +44 -13
- data/lib/friendly_shipping/services/ups_freight/generate_delivery_options_hash.rb +21 -0
- data/lib/friendly_shipping/services/ups_freight/generate_document_options_hash.rb +28 -0
- data/lib/friendly_shipping/services/ups_freight/generate_email_options_hash.rb +25 -0
- data/lib/friendly_shipping/services/ups_freight/generate_freight_rate_request_hash.rb +2 -10
- data/lib/friendly_shipping/services/ups_freight/generate_freight_ship_request_hash.rb +81 -0
- data/lib/friendly_shipping/services/ups_freight/generate_location_hash.rb +5 -2
- data/lib/friendly_shipping/services/ups_freight/generate_pickup_options_hash.rb +21 -0
- data/lib/friendly_shipping/services/ups_freight/generate_pickup_request_hash.rb +31 -0
- data/lib/friendly_shipping/services/ups_freight/label_delivery_options.rb +29 -0
- data/lib/friendly_shipping/services/ups_freight/label_document_options.rb +56 -0
- data/lib/friendly_shipping/services/ups_freight/label_email_options.rb +40 -0
- data/lib/friendly_shipping/services/ups_freight/label_item_options.rb +10 -0
- data/lib/friendly_shipping/services/ups_freight/label_options.rb +37 -0
- data/lib/friendly_shipping/services/ups_freight/label_package_options.rb +10 -0
- data/lib/friendly_shipping/services/ups_freight/label_pickup_options.rb +29 -0
- data/lib/friendly_shipping/services/ups_freight/parse_freight_label_response.rb +57 -0
- data/lib/friendly_shipping/services/ups_freight/parse_freight_rate_response.rb +29 -32
- data/lib/friendly_shipping/services/ups_freight/parse_shipment_document.rb +24 -0
- data/lib/friendly_shipping/services/ups_freight/pickup_request_options.rb +29 -0
- data/lib/friendly_shipping/services/ups_freight/rates_options.rb +3 -6
- data/lib/friendly_shipping/services/ups_freight/restful_api_error_handler.rb +30 -0
- data/lib/friendly_shipping/services/ups_freight/shipment_document.rb +21 -0
- data/lib/friendly_shipping/services/ups_freight/shipment_information.rb +35 -0
- data/lib/friendly_shipping/services/usps.rb +1 -0
- data/lib/friendly_shipping/services/usps/parse_address_validation_response.rb +5 -1
- data/lib/friendly_shipping/services/usps/parse_city_state_lookup_response.rb +5 -1
- data/lib/friendly_shipping/services/usps/parse_rate_response.rb +5 -2
- data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +6 -2
- data/lib/friendly_shipping/services/usps/parse_xml_response.rb +15 -5
- data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +1 -1
- data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +4 -1
- data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +11 -5
- data/lib/friendly_shipping/services/usps/shipping_methods.rb +4 -4
- data/lib/friendly_shipping/version.rb +1 -1
- metadata +74 -39
- data/lib/friendly_shipping/services/ups_freight/generate_ups_security_hash.rb +0 -23
- data/lib/friendly_shipping/services/ups_freight/parse_json_response.rb +0 -38
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class LabelOptions < RatesOptions
|
7
|
+
attr_reader :document_options,
|
8
|
+
:email_options,
|
9
|
+
:pickup_options,
|
10
|
+
:delivery_options,
|
11
|
+
:pickup_instructions,
|
12
|
+
:delivery_instructions,
|
13
|
+
:handling_instructions
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
document_options: [],
|
17
|
+
email_options: [],
|
18
|
+
pickup_options: nil,
|
19
|
+
delivery_options: nil,
|
20
|
+
pickup_instructions: nil,
|
21
|
+
delivery_instructions: nil,
|
22
|
+
handling_instructions: nil,
|
23
|
+
**kwargs
|
24
|
+
)
|
25
|
+
@pickup_options = pickup_options
|
26
|
+
@delivery_options = delivery_options
|
27
|
+
@document_options = document_options
|
28
|
+
@email_options = email_options
|
29
|
+
@pickup_instructions = pickup_instructions
|
30
|
+
@delivery_instructions = delivery_instructions
|
31
|
+
@handling_instructions = handling_instructions
|
32
|
+
super kwargs
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class LabelPickupOptions
|
7
|
+
attr_reader :holiday_pickup,
|
8
|
+
:inside_pickup,
|
9
|
+
:weekend_pickup,
|
10
|
+
:lift_gate_required,
|
11
|
+
:limited_access_pickup
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
holiday_pickup: nil,
|
15
|
+
inside_pickup: nil,
|
16
|
+
weekend_pickup: nil,
|
17
|
+
lift_gate_required: nil,
|
18
|
+
limited_access_pickup: nil
|
19
|
+
)
|
20
|
+
@holiday_pickup = holiday_pickup
|
21
|
+
@inside_pickup = inside_pickup
|
22
|
+
@weekend_pickup = weekend_pickup
|
23
|
+
@lift_gate_required = lift_gate_required
|
24
|
+
@limited_access_pickup = limited_access_pickup
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups_freight/parse_shipment_document'
|
4
|
+
require 'friendly_shipping/services/ups_freight/shipment_information'
|
5
|
+
|
6
|
+
module FriendlyShipping
|
7
|
+
module Services
|
8
|
+
class UpsFreight
|
9
|
+
class ParseFreightLabelResponse
|
10
|
+
class << self
|
11
|
+
def call(request:, response:)
|
12
|
+
json = JSON.parse(response.body)
|
13
|
+
|
14
|
+
warnings_json = Array.wrap(json.dig("FreightShipResponse", "Response", "Alert"))
|
15
|
+
warnings = warnings_json.map do |detailed_warning|
|
16
|
+
status = detailed_warning['Code']
|
17
|
+
desc = detailed_warning['Description']
|
18
|
+
[status, desc].compact.join(": ")
|
19
|
+
end.join("\n")
|
20
|
+
|
21
|
+
shipment_results = json.dig("FreightShipResponse", "ShipmentResults")
|
22
|
+
|
23
|
+
service_code = shipment_results.dig("Service", "Code")
|
24
|
+
shipping_method = SHIPPING_METHODS.detect { |sm| sm.service_code == service_code }
|
25
|
+
|
26
|
+
total_shipment_charge = shipment_results.dig("TotalShipmentCharge")
|
27
|
+
currency = Money::Currency.new(total_shipment_charge['CurrencyCode'])
|
28
|
+
amount = total_shipment_charge['MonetaryValue'].to_f
|
29
|
+
total_money = Money.new(amount * currency.subunit_to_unit, currency)
|
30
|
+
|
31
|
+
images_data = Array.wrap(shipment_results.dig("Documents", "Image"))
|
32
|
+
|
33
|
+
bol_id = shipment_results.dig("BOLID")
|
34
|
+
shipment_number = shipment_results.dig("ShipmentNumber")
|
35
|
+
pickup_request_number = shipment_results.dig("PickupRequestConfirmationNumber")
|
36
|
+
|
37
|
+
documents = images_data.map { |image_data| ParseShipmentDocument.call(image_data: image_data) }
|
38
|
+
|
39
|
+
FriendlyShipping::ApiResult.new(
|
40
|
+
ShipmentInformation.new(
|
41
|
+
total: total_money,
|
42
|
+
bol_id: bol_id,
|
43
|
+
number: shipment_number,
|
44
|
+
pickup_request_number: pickup_request_number,
|
45
|
+
shipping_method: shipping_method,
|
46
|
+
warnings: warnings,
|
47
|
+
documents: documents
|
48
|
+
),
|
49
|
+
original_request: request,
|
50
|
+
original_response: response
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'friendly_shipping/rate'
|
4
4
|
require 'friendly_shipping/api_result'
|
5
|
-
require 'friendly_shipping/services/ups_freight/parse_json_response'
|
6
5
|
require 'friendly_shipping/services/ups_freight/shipping_methods'
|
7
6
|
|
8
7
|
module FriendlyShipping
|
@@ -11,40 +10,38 @@ module FriendlyShipping
|
|
11
10
|
class ParseFreightRateResponse
|
12
11
|
class << self
|
13
12
|
def call(request:, response:)
|
14
|
-
|
13
|
+
json = JSON.parse(response.body)
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
15
|
+
service_code = json.dig("FreightRateResponse", "Service", "Code")
|
16
|
+
shipping_method = SHIPPING_METHODS.detect { |sm| sm.service_code == service_code }
|
17
|
+
total_shipment_charge = json.dig("FreightRateResponse", "TotalShipmentCharge")
|
18
|
+
currency = Money::Currency.new(total_shipment_charge['CurrencyCode'])
|
19
|
+
amount = total_shipment_charge['MonetaryValue'].to_f
|
20
|
+
total_money = Money.new(amount * currency.subunit_to_unit, currency)
|
21
|
+
data = {
|
22
|
+
customer_context: json.dig("FreightRateResponse", "TransactionReference", "TransactionIdentifier"),
|
23
|
+
commodities: Array.wrap(json.dig("FreightRateResponse", "Commodity")),
|
24
|
+
response_body: json
|
25
|
+
}
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
FriendlyShipping::ApiResult.new(
|
35
|
-
[
|
36
|
-
FriendlyShipping::Rate.new(
|
37
|
-
amounts: {
|
38
|
-
total: total_money
|
39
|
-
},
|
40
|
-
shipping_method: shipping_method,
|
41
|
-
data: data
|
42
|
-
)
|
43
|
-
],
|
44
|
-
original_request: request,
|
45
|
-
original_response: response
|
46
|
-
)
|
27
|
+
days_in_transit = json.dig("FreightRateResponse", "TimeInTransit", "DaysInTransit")
|
28
|
+
if days_in_transit
|
29
|
+
data[:days_in_transit] = days_in_transit.to_i
|
47
30
|
end
|
31
|
+
|
32
|
+
FriendlyShipping::ApiResult.new(
|
33
|
+
[
|
34
|
+
FriendlyShipping::Rate.new(
|
35
|
+
amounts: {
|
36
|
+
total: total_money
|
37
|
+
},
|
38
|
+
shipping_method: shipping_method,
|
39
|
+
data: data
|
40
|
+
)
|
41
|
+
],
|
42
|
+
original_request: request,
|
43
|
+
original_response: response
|
44
|
+
)
|
48
45
|
end
|
49
46
|
end
|
50
47
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups_freight/shipment_document'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class UpsFreight
|
8
|
+
class ParseShipmentDocument
|
9
|
+
REVERSE_DOCUMENT_TYPES = LabelDocumentOptions::DOCUMENT_TYPES.map(&:reverse_each).map(&:to_a).to_h
|
10
|
+
|
11
|
+
def self.call(image_data:)
|
12
|
+
format_code = image_data.dig("Type", "Code")
|
13
|
+
graphic_image_b64 = image_data.dig("GraphicImage")
|
14
|
+
|
15
|
+
ShipmentDocument.new(
|
16
|
+
format: image_data.dig("Format", "Code").downcase.to_sym,
|
17
|
+
binary: Base64.decode64(graphic_image_b64),
|
18
|
+
document_type: REVERSE_DOCUMENT_TYPES.fetch(format_code)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class PickupRequestOptions
|
7
|
+
attr_reader :pickup_time_window,
|
8
|
+
:requester,
|
9
|
+
:third_party_requester,
|
10
|
+
:requester_email,
|
11
|
+
:comments
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
pickup_time_window:,
|
15
|
+
requester:,
|
16
|
+
requester_email:,
|
17
|
+
comments: nil,
|
18
|
+
third_party_requester: false
|
19
|
+
)
|
20
|
+
@pickup_time_window = pickup_time_window
|
21
|
+
@requester = requester
|
22
|
+
@third_party_requester = third_party_requester
|
23
|
+
@requester_email = requester_email
|
24
|
+
@comments = comments
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -31,8 +31,7 @@ module FriendlyShipping
|
|
31
31
|
:billing_code,
|
32
32
|
:customer_context,
|
33
33
|
:shipping_method,
|
34
|
-
:
|
35
|
-
:pickup_comments,
|
34
|
+
:pickup_request_options,
|
36
35
|
:commodity_information_generator
|
37
36
|
|
38
37
|
def initialize(
|
@@ -41,8 +40,7 @@ module FriendlyShipping
|
|
41
40
|
shipping_method:,
|
42
41
|
billing: :prepaid,
|
43
42
|
customer_context: nil,
|
44
|
-
|
45
|
-
pickup_comments: nil,
|
43
|
+
pickup_request_options: nil,
|
46
44
|
commodity_information_generator: GenerateCommodityInformation,
|
47
45
|
**kwargs
|
48
46
|
)
|
@@ -51,8 +49,7 @@ module FriendlyShipping
|
|
51
49
|
@shipping_method = shipping_method
|
52
50
|
@billing_code = BILLING_CODES.fetch(billing)
|
53
51
|
@customer_context = customer_context
|
54
|
-
@
|
55
|
-
@pickup_comments = pickup_comments
|
52
|
+
@pickup_request_options = pickup_request_options
|
56
53
|
@commodity_information_generator = commodity_information_generator
|
57
54
|
super(**kwargs.merge(package_options_class: RatesPackageOptions))
|
58
55
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class RestfulApiErrorHandler
|
7
|
+
extend Dry::Monads::Result::Mixin
|
8
|
+
|
9
|
+
def self.call(error, original_request: nil, original_response: nil)
|
10
|
+
parsed_json = JSON.parse(error.response.body)
|
11
|
+
errors = parsed_json.dig('response', 'errors')
|
12
|
+
|
13
|
+
failure_string = errors.map do |err|
|
14
|
+
status = err['code']
|
15
|
+
desc = err['message']
|
16
|
+
[status, desc].compact.join(": ").presence || 'UPS could not process the request.'
|
17
|
+
end.join("\n")
|
18
|
+
|
19
|
+
Failure(
|
20
|
+
ApiFailure.new(
|
21
|
+
failure_string,
|
22
|
+
original_request: original_request,
|
23
|
+
original_response: original_response
|
24
|
+
)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class ShipmentDocument
|
7
|
+
attr_reader :format, :document_type, :binary
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
format:,
|
11
|
+
document_type:,
|
12
|
+
binary:
|
13
|
+
)
|
14
|
+
@format = format
|
15
|
+
@document_type = document_type
|
16
|
+
@binary = binary
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class UpsFreight
|
6
|
+
class ShipmentInformation
|
7
|
+
attr_reader :documents,
|
8
|
+
:number,
|
9
|
+
:pickup_request_number,
|
10
|
+
:total,
|
11
|
+
:bol_id,
|
12
|
+
:shipping_method,
|
13
|
+
:warnings
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
total:,
|
17
|
+
bol_id:,
|
18
|
+
number:,
|
19
|
+
pickup_request_number: nil,
|
20
|
+
documents: [],
|
21
|
+
shipping_method: nil,
|
22
|
+
warnings: nil
|
23
|
+
)
|
24
|
+
@total = total
|
25
|
+
@bol_id = bol_id
|
26
|
+
@number = number
|
27
|
+
@pickup_request_number = pickup_request_number
|
28
|
+
@documents = documents
|
29
|
+
@shipping_method = shipping_method
|
30
|
+
@warnings = warnings
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -12,7 +12,11 @@ module FriendlyShipping
|
|
12
12
|
# @return [Result<FriendlyShipping::AddressValidationResult>]
|
13
13
|
def call(request:, response:)
|
14
14
|
# Filter out error responses and directly return a failure
|
15
|
-
parsing_result = ParseXMLResponse.call(
|
15
|
+
parsing_result = ParseXMLResponse.call(
|
16
|
+
request: request,
|
17
|
+
response: response,
|
18
|
+
expected_root_tag: 'AddressValidateResponse'
|
19
|
+
)
|
16
20
|
parsing_result.fmap do |xml|
|
17
21
|
address = xml.root.at('Address')
|
18
22
|
suggestions = [
|
@@ -12,7 +12,11 @@ module FriendlyShipping
|
|
12
12
|
# @return [Result<FriendlyShipping::AddressValidationResult>]
|
13
13
|
def call(request:, response:)
|
14
14
|
# Filter out error responses and directly return a failure
|
15
|
-
parsing_result = ParseXMLResponse.call(
|
15
|
+
parsing_result = ParseXMLResponse.call(
|
16
|
+
request: request,
|
17
|
+
response: response,
|
18
|
+
expected_root_tag: 'CityStateLookupResponse'
|
19
|
+
)
|
16
20
|
parsing_result.fmap do |xml|
|
17
21
|
address = xml.root.at('ZipCode')
|
18
22
|
suggestions = [
|
@@ -20,8 +20,11 @@ module FriendlyShipping
|
|
20
20
|
# @return [Result<ApiResult<Array<FriendlyShipping::Rate>>>] When successfully parsing, an array of rates in a Success Monad.
|
21
21
|
def call(request:, response:, shipment:, options:)
|
22
22
|
# Filter out error responses and directly return a failure
|
23
|
-
parsing_result = ParseXMLResponse.call(
|
24
|
-
|
23
|
+
parsing_result = ParseXMLResponse.call(
|
24
|
+
request: request,
|
25
|
+
response: response,
|
26
|
+
expected_root_tag: 'RateV4Response'
|
27
|
+
)
|
25
28
|
parsing_result.fmap do |xml|
|
26
29
|
# Get all the possible rates for each package
|
27
30
|
rates_by_package = rates_from_response_node(xml, shipment, options)
|