friendly_shipping 0.10.1 → 0.10.2

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.md +16 -16
  4. data/lib/friendly_shipping/services/rl/parse_rate_quote_response.rb +3 -3
  5. data/lib/friendly_shipping/services/ups_json/parse_money_hash.rb +1 -0
  6. data/lib/friendly_shipping/services/usps_international/parse_rate_response.rb +3 -3
  7. data/lib/friendly_shipping/services/{usps → usps_international}/parse_xml_response.rb +1 -1
  8. data/lib/friendly_shipping/services/usps_international/serialize_rate_request.rb +1 -1
  9. data/lib/friendly_shipping/services/usps_ship/parse_rate_estimates_response.rb +1 -1
  10. data/lib/friendly_shipping/version.rb +1 -1
  11. metadata +3 -51
  12. data/lib/friendly_shipping/services/ups/label.rb +0 -20
  13. data/lib/friendly_shipping/services/ups/label_billing_options.rb +0 -41
  14. data/lib/friendly_shipping/services/ups/label_item_options.rb +0 -75
  15. data/lib/friendly_shipping/services/ups/label_options.rb +0 -174
  16. data/lib/friendly_shipping/services/ups/label_package_options.rb +0 -49
  17. data/lib/friendly_shipping/services/ups/parse_address_classification_response.rb +0 -29
  18. data/lib/friendly_shipping/services/ups/parse_address_validation_response.rb +0 -53
  19. data/lib/friendly_shipping/services/ups/parse_city_state_lookup_response.rb +0 -33
  20. data/lib/friendly_shipping/services/ups/parse_modifier_element.rb +0 -29
  21. data/lib/friendly_shipping/services/ups/parse_money_element.rb +0 -128
  22. data/lib/friendly_shipping/services/ups/parse_rate_response.rb +0 -101
  23. data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +0 -77
  24. data/lib/friendly_shipping/services/ups/parse_shipment_confirm_response.rb +0 -24
  25. data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +0 -56
  26. data/lib/friendly_shipping/services/ups/parse_void_shipment_response.rb +0 -24
  27. data/lib/friendly_shipping/services/ups/parse_xml_response.rb +0 -50
  28. data/lib/friendly_shipping/services/ups/rate_estimate_options.rb +0 -111
  29. data/lib/friendly_shipping/services/ups/rate_estimate_package_options.rb +0 -22
  30. data/lib/friendly_shipping/services/ups/serialize_access_request.rb +0 -20
  31. data/lib/friendly_shipping/services/ups/serialize_address_snippet.rb +0 -60
  32. data/lib/friendly_shipping/services/ups/serialize_address_validation_request.rb +0 -40
  33. data/lib/friendly_shipping/services/ups/serialize_city_state_lookup_request.rb +0 -26
  34. data/lib/friendly_shipping/services/ups/serialize_package_node.rb +0 -75
  35. data/lib/friendly_shipping/services/ups/serialize_rating_service_selection_request.rb +0 -98
  36. data/lib/friendly_shipping/services/ups/serialize_shipment_accept_request.rb +0 -27
  37. data/lib/friendly_shipping/services/ups/serialize_shipment_address_snippet.rb +0 -21
  38. data/lib/friendly_shipping/services/ups/serialize_shipment_confirm_request.rb +0 -285
  39. data/lib/friendly_shipping/services/ups/serialize_time_in_transit_request.rb +0 -56
  40. data/lib/friendly_shipping/services/ups/serialize_void_shipment_request.rb +0 -21
  41. data/lib/friendly_shipping/services/ups/shipping_methods.rb +0 -111
  42. data/lib/friendly_shipping/services/ups/timing_options.rb +0 -33
  43. data/lib/friendly_shipping/services/ups.rb +0 -218
  44. data/lib/friendly_shipping/services/usps/choose_package_rate.rb +0 -40
  45. data/lib/friendly_shipping/services/usps/machinable_package.rb +0 -50
  46. data/lib/friendly_shipping/services/usps/parse_address_validation_response.rb +0 -43
  47. data/lib/friendly_shipping/services/usps/parse_city_state_lookup_response.rb +0 -41
  48. data/lib/friendly_shipping/services/usps/parse_package_rate.rb +0 -159
  49. data/lib/friendly_shipping/services/usps/parse_rate_response.rb +0 -86
  50. data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +0 -240
  51. data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +0 -26
  52. data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +0 -66
  53. data/lib/friendly_shipping/services/usps/serialize_address_validation_request.rb +0 -25
  54. data/lib/friendly_shipping/services/usps/serialize_city_state_lookup_request.rb +0 -20
  55. data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +0 -83
  56. data/lib/friendly_shipping/services/usps/serialize_time_in_transit_request.rb +0 -22
  57. data/lib/friendly_shipping/services/usps/shipping_methods.rb +0 -66
  58. data/lib/friendly_shipping/services/usps/timing_options.rb +0 -19
  59. data/lib/friendly_shipping/services/usps.rb +0 -115
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseAddressValidationResponse
7
- extend Dry::Monads::Result::Mixin
8
-
9
- def self.call(request:, response:)
10
- parsing_result = ParseXMLResponse.call(
11
- request: request,
12
- response: response,
13
- expected_root_tag: 'AddressValidationResponse'
14
- )
15
- parsing_result.bind do |xml|
16
- if xml.at('NoCandidatesIndicator')
17
- Failure(
18
- FriendlyShipping::ApiResult.new(
19
- 'Address is probably invalid. No similar valid addresses found.',
20
- original_request: request,
21
- original_response: response
22
- )
23
- )
24
- else
25
- Success(
26
- FriendlyShipping::ApiResult.new(
27
- build_suggestions(xml),
28
- original_request: request,
29
- original_response: response
30
- )
31
- )
32
- end
33
- end
34
- end
35
-
36
- def self.build_suggestions(xml)
37
- xml.xpath('//AddressKeyFormat').map do |address_fragment|
38
- Physical::Location.new(
39
- address1: address_fragment.xpath('AddressLine[1]')[0]&.text,
40
- address2: address_fragment.xpath('AddressLine[2]')[0]&.text,
41
- company_name: address_fragment.at('ConsigneeName')&.text,
42
- city: address_fragment.at('PoliticalDivision2')&.text,
43
- region: address_fragment.at('PoliticalDivision1')&.text,
44
- country: address_fragment.at('CountryCode')&.text,
45
- zip: "#{address_fragment.at('PostcodePrimaryLow')&.text}-#{address_fragment.at('PostcodeExtendedLow')&.text}",
46
- address_type: address_fragment.at('AddressClassification/Description')&.text&.downcase
47
- )
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseCityStateLookupResponse
7
- extend Dry::Monads::Result::Mixin
8
-
9
- def self.call(request:, response:, location:)
10
- parsing_result = ParseXMLResponse.call(
11
- request: request,
12
- response: response,
13
- expected_root_tag: 'AddressValidationResponse'
14
- )
15
- parsing_result.fmap do |xml|
16
- FriendlyShipping::ApiResult.new(
17
- [
18
- Physical::Location.new(
19
- city: xml.at('AddressValidationResult/Address/City')&.text,
20
- region: xml.at('AddressValidationResult/Address/StateProvinceCode')&.text,
21
- country: location.country,
22
- zip: xml.at('AddressValidationResult/Address/PostcodePrimaryLow')&.text,
23
- )
24
- ],
25
- original_request: request,
26
- original_response: response
27
- )
28
- end
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseModifierElement
7
- # @param [Nokogiri::XML::Element] element The modifier element from the source XML
8
- # @param [String] currency_code The currency code for this modifier's amount (i.e. 'USD')
9
- # @return [Array<String, Money>]
10
- def self.call(element, currency_code:)
11
- return unless element
12
-
13
- amount = element.at('Amount').text.to_d
14
- return if amount.zero?
15
-
16
- currency = Money::Currency.new(currency_code)
17
- amount = Money.new(amount * currency.subunit_to_unit, currency)
18
-
19
- modifier_type = element.at('ModifierType').text
20
- modifier_description = element.at('ModifierDesc').text
21
-
22
- label = "#{modifier_type} (#{modifier_description})"
23
-
24
- [label, amount]
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseMoneyElement
7
- def self.call(element)
8
- return unless element
9
-
10
- monetary_value = element.at('MonetaryValue').text.to_d
11
- return if monetary_value.zero?
12
-
13
- currency_code = element.at('CurrencyCode').text
14
- currency = Money::Currency.new(currency_code)
15
- amount = Money.new(monetary_value * currency.subunit_to_unit, currency)
16
-
17
- surcharge_code = element.at('Code')&.text
18
- label = surcharge_code ? UPS_SURCHARGE_CODES[surcharge_code] : element.name
19
-
20
- [label, amount]
21
- end
22
-
23
- UPS_SURCHARGE_CODES = {
24
- "100" => "ADDITIONAL HANDLING",
25
- "110" => "COD",
26
- "120" => "DELIVERY CONFIRMATION",
27
- "121" => "SHIP DELIVERY CONFIRMATION",
28
- "153" => "PKG EMAIL SHIP NOTIFICATION",
29
- "154" => "PKG EMAIL RETURN NOTIFICATION",
30
- "155" => "PKG EMAIL INBOUND RETURN NOTIFICATION",
31
- "156" => "PKG EMAIL QUANTUM VIEW SHIP NOTIFICATION",
32
- "157" => "PKG EMAIL QUANTUM VIEW EXCEPTION NOTIFICATION",
33
- "158" => "PKG EMAIL QUANTUM VIEW DELIVERY NOTIFICATION",
34
- "165" => "PKG FAX INBOUND RETURN NOTIFICATION",
35
- "166" => "PKG FAX QUANTUM VIEW SHIP NOTIFICATION",
36
- "171" => "SHIP EMAIL ERL NOTIFICATION",
37
- "173" => "SHIP EMAIL SHIP NOTIFICATION",
38
- "174" => "SHIP EMAIL RETURN NOTIFICATION",
39
- "175" => "SHIP EMAIL INBOUND RETURN NOTIFICATION",
40
- "176" => "SHIP EMAIL QUANTUM VIEW SHIP NOTIFICATION",
41
- "177" => "SHIP EMAIL QUANTUM VIEW EXCEPTION NOTIFICATION",
42
- "178" => "SHIP EMAIL QUANTUM VIEW DELIVERY NOTIFICATION",
43
- "179" => "SHIP EMAIL QUANTUM VIEW NOTIFY",
44
- "187" => "SHIP UPS ACCESS POINT NOTIFICATION",
45
- "188" => "SHIP EEI FILING NOTIFICATION",
46
- "189" => "SHIP UAP SHIPPER NOTIFICATION",
47
- "190" => "EXTENDED AREA",
48
- "200" => "DRY ICE",
49
- "220" => "HOLD FOR PICKUP",
50
- "240" => "ORIGIN CERTIFICATE",
51
- "250" => "PRINT RETURN LABEL",
52
- "258" => "EXPORT LICENSE VERIFICATION",
53
- "260" => "PRINT N MAIL",
54
- "270" => "RESIDENTIAL ADDRESS",
55
- "280" => "RETURN SERVICE 1ATTEMPT",
56
- "290" => "RETURN SERVICE 3ATTEMPT",
57
- "300" => "SATURDAY DELIVERY",
58
- "310" => "SATURDAY PICKUP",
59
- "330" => "PKG VERBAL CONFIRMATION",
60
- "350" => "ELECTRONIC RETURN LABEL",
61
- "372" => "QUANTUM VIEW NOTIFY DELIVERY",
62
- "374" => "UPS PREPARED SED FORM",
63
- "375" => "FUEL SURCHARGE",
64
- "376" => "DELIVERY AREA",
65
- "377" => "LARGE PACKAGE",
66
- "378" => "SHIPPER PAYS DUTY TAX",
67
- "379" => "SHIPPER PAYS DUTY TAX UNPAID",
68
- "400" => "INSURANCE",
69
- "401" => "SHIP ADDITIONAL HANDLING",
70
- "402" => "SHIPPER RELEASE",
71
- "403" => "CHECK TO SHIPPER",
72
- "404" => "UPS PROACTIVE RESPONSE",
73
- "405" => "GERMAN PICKUP",
74
- "406" => "GERMAN ROAD TAX",
75
- "407" => "EXTENDED AREA PICKUP",
76
- "410" => "RETURN OF DOCUMENT",
77
- "430" => "PEAK SEASON",
78
- "431" => "LARGE PACKAGE SEASONAL SURCHARGE",
79
- "432" => "ADDITIONAL HANDLING SEASONAL SURCHARGE",
80
- "440" => "SHIP LARGE PACKAGE",
81
- "441" => "CARBON NEUTRAL",
82
- "442" => "PKG QV IN TRANSIT NOTIFICATION",
83
- "443" => "SHIP QV IN TRANSIT NOTIFICATION",
84
- "444" => "IMPORT CONTROL",
85
- "445" => "COMMERCIAL INVOICE REMOVAL",
86
- "446" => "IMPORT CONTROL ELECTRONIC LABEL",
87
- "447" => "IMPORT CONTROL PRINT LABEL",
88
- "448" => "IMPORT CONTROL PRINT AND MAIL LABEL",
89
- "449" => "IMPORT CONTROL ONE PICK UP ATTEMPT LABEL",
90
- "450" => "IMPORT CONTROL THREE PICK UP ATTEMPT LABEL",
91
- "452" => "REFRIGERATION",
92
- "454" => "PAC 1A BOX1",
93
- "455" => "PAC 3A BOX1",
94
- "456" => "PAC 1A BOX2",
95
- "457" => "PAC 3A BOX2",
96
- "458" => "PAC 1A BOX3",
97
- "459" => "PAC 3A BOX3",
98
- "460" => "PAC 1A BOX4",
99
- "461" => "PAC 3A BOX4",
100
- "462" => "PAC 1A BOX5",
101
- "463" => "PAC 3A BOX5",
102
- "464" => "EXCHANGE PRINT RETURN LABEL",
103
- "465" => "EXCHANGE FORWARD",
104
- "466" => "SHIP PREALERT NOTIFICATION",
105
- "470" => "COMMITTED DELIVERY WINDOW",
106
- "480" => "SECURITY SURCHARGE",
107
- "492" => "CUSTOMER TRANSACTION FEE",
108
- "500" => "SHIPMENT COD",
109
- "510" => "LIFT GATE FOR PICKUP",
110
- "511" => "LIFT GATE FOR DELIVERY",
111
- "512" => "DROP OFF AT UPS FACILITY",
112
- "515" => "UPS PREMIUM CARE",
113
- "520" => "OVERSIZE PALLET",
114
- "530" => "FREIGHT DELIVERY SURCHARGE",
115
- "531" => "FREIGHT PICKUP SURCHARGE",
116
- "540" => "DIRECT TO RETAIL",
117
- "541" => "DIRECT DELIVERY ONLY",
118
- "542" => "DELIVER TO ADDRESSEE ONLY",
119
- "543" => "DIRECT TO RETAIL COD",
120
- "544" => "RETAIL ACCESS POINT545 SHIPPING TICKET NOTIFICATION",
121
- "546" => "ELECTRONIC PACKAGE RELEASE AUTHENTICATION",
122
- "547" => "PAY AT STORE"
123
- }.freeze
124
- private_constant :UPS_SURCHARGE_CODES
125
- end
126
- end
127
- end
128
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseRateResponse
7
- class << self
8
- def call(request:, response:, shipment:)
9
- parsing_result = ParseXMLResponse.call(
10
- request: request,
11
- response: response,
12
- expected_root_tag: 'RatingServiceSelectionResponse'
13
- )
14
- parsing_result.fmap do |xml|
15
- FriendlyShipping::ApiResult.new(
16
- build_rates(xml, shipment),
17
- original_request: request,
18
- original_response: response
19
- )
20
- end
21
- end
22
-
23
- def build_rates(xml, shipment)
24
- xml.root.css('> RatedShipment').map do |rated_shipment|
25
- service_code = rated_shipment.at('Service/Code').text
26
- shipping_method = CARRIER.shipping_methods.detect do |sm|
27
- sm.service_code == service_code && shipment.origin.country.in?(sm.origin_countries)
28
- end
29
- days_to_delivery = rated_shipment.at('GuaranteedDaysToDelivery').text.to_i
30
-
31
- total = ParseMoneyElement.call(rated_shipment.at('TotalCharges')).last
32
- insurance_price = ParseMoneyElement.call(rated_shipment.at('ServiceOptionsCharges'))&.last
33
- negotiated_rate = ParseMoneyElement.call(
34
- rated_shipment.at('NegotiatedRates/NetSummaryCharges/GrandTotal')
35
- )&.last
36
- negotiated_charges = extract_charges(rated_shipment.xpath('NegotiatedRates/ItemizedCharges'))
37
- itemized_charges = extract_charges(rated_shipment.xpath('ItemizedCharges'))
38
-
39
- rated_shipment_warnings = rated_shipment.css('RatedShipmentWarning').map { |e| e.text.strip }
40
- if rated_shipment_warnings.any? { |e| e.match?(/to Residential/) }
41
- new_address_type = 'residential'
42
- elsif rated_shipment_warnings.any? { |e| e.match?(/to Commercial/) }
43
- new_address_type = 'commercial'
44
- end
45
-
46
- FriendlyShipping::Rate.new(
47
- shipping_method: shipping_method,
48
- amounts: { total: total },
49
- warnings: rated_shipment_warnings,
50
- errors: [],
51
- data: {
52
- insurance_price: insurance_price,
53
- negotiated_rate: negotiated_rate,
54
- negotiated_charges: negotiated_charges,
55
- days_to_delivery: days_to_delivery,
56
- new_address_type: new_address_type,
57
- itemized_charges: itemized_charges,
58
- packages: build_packages(rated_shipment)
59
- }.compact
60
- )
61
- end
62
- end
63
-
64
- private
65
-
66
- def build_packages(rated_shipment)
67
- rated_shipment.css('RatedPackage').map do |rated_package|
68
- currency_code = rated_package.at('TotalCharges/CurrencyCode').text
69
- {
70
- transportation_charges: ParseMoneyElement.call(rated_package.at('TransportationCharges'))&.last,
71
- base_service_charge: ParseMoneyElement.call(rated_package.at('BaseServiceCharge'))&.last,
72
- service_options_charges: ParseMoneyElement.call(rated_package.at('ServiceOptionsCharges'))&.last,
73
- itemized_charges: extract_charges(rated_package.xpath('ItemizedCharges')),
74
- total_charges: ParseMoneyElement.call(rated_package.at('TotalCharges'))&.last,
75
- negotiated_charges: extract_charges(rated_package.xpath('NegotiatedCharges/ItemizedCharges')),
76
- rate_modifiers: extract_modifiers(rated_package.xpath('RateModifier'), currency_code: currency_code),
77
- weight: BigDecimal(rated_package.at('Weight').text),
78
- billing_weight: BigDecimal(rated_package.at('BillingWeight/Weight').text)
79
- }.compact
80
- end
81
- end
82
-
83
- def extract_charges(node)
84
- node.map do |element|
85
- ParseMoneyElement.call(element)
86
- end.compact.to_h
87
- end
88
-
89
- # @param [Nokogiri::XML::NodeSet] node The RateModifier node set from the source XML
90
- # @param [String] currency_code The currency code for the modifier amounts (i.e. 'USD')
91
- # @return [Array<Hash>]
92
- def extract_modifiers(node, currency_code:)
93
- node.map do |element|
94
- ParseModifierElement.call(element, currency_code: currency_code)
95
- end.compact.to_h
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseShipmentAcceptResponse
7
- extend Dry::Monads::Result::Mixin
8
-
9
- class << self
10
- def call(request:, response:)
11
- parsing_result = ParseXMLResponse.call(
12
- request: request,
13
- response: response,
14
- expected_root_tag: 'ShipmentAcceptResponse'
15
- )
16
- parsing_result.fmap do |xml|
17
- FriendlyShipping::ApiResult.new(
18
- build_labels(xml),
19
- original_request: request,
20
- original_response: response
21
- )
22
- end
23
- end
24
-
25
- private
26
-
27
- def build_labels(xml)
28
- packages = xml.xpath('//ShipmentAcceptResponse/ShipmentResults/PackageResults')
29
- form_format = xml.at('Form/Image/ImageFormat/Code')&.text
30
- encoded_form = xml.at('Form/Image/GraphicImage')&.text
31
- decoded_form = encoded_form ? Base64.decode64(encoded_form) : nil
32
- packages.map do |package|
33
- cost_breakdown = build_cost_breakdown(package)
34
- package_cost = cost_breakdown.values.any? ? cost_breakdown.values.sum : nil
35
- encoded_label_data = package.at('LabelImage/GraphicImage')&.text
36
- FriendlyShipping::Services::Ups::Label.new(
37
- tracking_number: package.at('TrackingNumber').text,
38
- usps_tracking_number: package.at('USPSPICNumber')&.text,
39
- label_data: encoded_label_data ? Base64.decode64(encoded_label_data) : nil,
40
- label_format: package.at('LabelImage/LabelImageFormat/Code')&.text,
41
- cost: package_cost,
42
- shipment_cost: get_shipment_cost(xml),
43
- data: {
44
- cost_breakdown: cost_breakdown,
45
- negotiated_rate: get_negotiated_rate(xml),
46
- form_format: form_format,
47
- form: decoded_form,
48
- customer_context: xml.xpath('//TransactionReference/CustomerContext')&.text
49
- }.compact
50
- )
51
- end
52
- end
53
-
54
- def build_cost_breakdown(package)
55
- cost_elements = [
56
- package.at('BaseServiceCharge'),
57
- package.at('ServiceOptionsCharges'),
58
- package.xpath('ItemizedCharges')
59
- ].flatten
60
-
61
- cost_elements.map { |element| ParseMoneyElement.call(element) }.compact.to_h
62
- end
63
-
64
- def get_shipment_cost(shipment_xml)
65
- total_charges_element = shipment_xml.at('ShipmentResults/ShipmentCharges/TotalCharges')
66
- ParseMoneyElement.call(total_charges_element)&.last
67
- end
68
-
69
- def get_negotiated_rate(shipment_xml)
70
- negotiated_total_element = shipment_xml.at('NegotiatedRates/NetSummaryCharges/GrandTotal')
71
- ParseMoneyElement.call(negotiated_total_element)&.last
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseShipmentConfirmResponse
7
- def self.call(request:, response:)
8
- parsing_result = ParseXMLResponse.call(
9
- request: request,
10
- response: response,
11
- expected_root_tag: 'ShipmentConfirmResponse'
12
- )
13
- parsing_result.fmap do |xml|
14
- FriendlyShipping::ApiResult.new(
15
- xml.root.at('ShipmentDigest').text,
16
- original_request: request,
17
- original_response: response
18
- )
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseTimeInTransitResponse
7
- def self.call(request:, response:)
8
- parsing_result = ParseXMLResponse.call(
9
- request: request,
10
- response: response,
11
- expected_root_tag: 'TimeInTransitResponse'
12
- )
13
- parsing_result.fmap do |xml|
14
- origin_country_code = xml.at('TransitResponse/TransitFrom/AddressArtifactFormat/CountryCode').text
15
- timings = xml.root.xpath('//TransitResponse/ServiceSummary').map do |service_summary|
16
- # First find the shipping method
17
- service_description = service_summary.at('Service/Description').text.gsub(/\s+/, ' ').strip
18
- shipping_method = SHIPPING_METHODS.detect do |potential_shipping_method|
19
- potential_shipping_method.name.starts_with?(service_description) &&
20
- potential_shipping_method.origin_countries.map(&:code).include?(origin_country_code)
21
- end
22
-
23
- # Figure out the dates and times
24
- delivery_date = service_summary.at('EstimatedArrival/Date').text
25
- delivery_time = service_summary.at('EstimatedArrival/Time').text
26
- delivery = Time.parse("#{delivery_date} #{delivery_time}")
27
- pickup_date = service_summary.at('EstimatedArrival/PickupDate').text
28
- pickup_time = service_summary.at('EstimatedArrival/PickupTime')&.text
29
- pickup = Time.parse("#{pickup_date} #{pickup_time}")
30
-
31
- # Some additional data
32
- guaranteed = service_summary.at('Guaranteed/Code').text == 'Y'
33
- business_transit_days = service_summary.at('EstimatedArrival/BusinessTransitDays').text
34
-
35
- FriendlyShipping::Timing.new(
36
- shipping_method: shipping_method,
37
- pickup: pickup,
38
- delivery: delivery,
39
- guaranteed: guaranteed,
40
- data: {
41
- business_transit_days: business_transit_days
42
- }
43
- )
44
- end
45
-
46
- FriendlyShipping::ApiResult.new(
47
- timings,
48
- original_request: request,
49
- original_response: response
50
- )
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseVoidShipmentResponse
7
- def self.call(request:, response:)
8
- parsing_result = ParseXMLResponse.call(
9
- request: request,
10
- response: response,
11
- expected_root_tag: 'VoidShipmentResponse'
12
- )
13
- parsing_result.fmap do |xml|
14
- FriendlyShipping::ApiResult.new(
15
- xml.root.at('ResponseStatusDescription').text,
16
- original_request: request,
17
- original_response: response
18
- )
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FriendlyShipping
4
- module Services
5
- class Ups
6
- class ParseXMLResponse
7
- extend Dry::Monads::Result::Mixin
8
- SUCCESSFUL_RESPONSE_STATUS_CODE = '1'
9
-
10
- class << self
11
- def call(request:, response:, expected_root_tag:)
12
- xml = Nokogiri.XML(response.body, &:strict)
13
-
14
- if xml.root.nil? || xml.root.name != expected_root_tag
15
- wrap_failure('Invalid document', request, response)
16
- elsif request_successful?(xml)
17
- Success(xml)
18
- else
19
- wrap_failure(error_message(xml), request, response)
20
- end
21
- rescue Nokogiri::XML::SyntaxError => e
22
- wrap_failure(e, request, response)
23
- end
24
-
25
- private
26
-
27
- def request_successful?(xml)
28
- xml.root.at('Response/ResponseStatusCode').text == SUCCESSFUL_RESPONSE_STATUS_CODE
29
- end
30
-
31
- def error_message(xml)
32
- status = xml.root.at_xpath('Response/ResponseStatusDescription')&.text
33
- desc = xml.root.at_xpath('Response/Error/ErrorDescription')&.text
34
- [status, desc].compact.join(": ").presence || 'UPS could not process the request.'
35
- end
36
-
37
- def wrap_failure(failure, request, response)
38
- Failure(
39
- FriendlyShipping::ApiResult.new(
40
- failure,
41
- original_request: request,
42
- original_response: response
43
- )
44
- )
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end