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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +16 -16
- data/lib/friendly_shipping/services/rl/parse_rate_quote_response.rb +3 -3
- data/lib/friendly_shipping/services/ups_json/parse_money_hash.rb +1 -0
- data/lib/friendly_shipping/services/usps_international/parse_rate_response.rb +3 -3
- data/lib/friendly_shipping/services/{usps → usps_international}/parse_xml_response.rb +1 -1
- data/lib/friendly_shipping/services/usps_international/serialize_rate_request.rb +1 -1
- data/lib/friendly_shipping/services/usps_ship/parse_rate_estimates_response.rb +1 -1
- data/lib/friendly_shipping/version.rb +1 -1
- metadata +3 -51
- data/lib/friendly_shipping/services/ups/label.rb +0 -20
- data/lib/friendly_shipping/services/ups/label_billing_options.rb +0 -41
- data/lib/friendly_shipping/services/ups/label_item_options.rb +0 -75
- data/lib/friendly_shipping/services/ups/label_options.rb +0 -174
- data/lib/friendly_shipping/services/ups/label_package_options.rb +0 -49
- data/lib/friendly_shipping/services/ups/parse_address_classification_response.rb +0 -29
- data/lib/friendly_shipping/services/ups/parse_address_validation_response.rb +0 -53
- data/lib/friendly_shipping/services/ups/parse_city_state_lookup_response.rb +0 -33
- data/lib/friendly_shipping/services/ups/parse_modifier_element.rb +0 -29
- data/lib/friendly_shipping/services/ups/parse_money_element.rb +0 -128
- data/lib/friendly_shipping/services/ups/parse_rate_response.rb +0 -101
- data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +0 -77
- data/lib/friendly_shipping/services/ups/parse_shipment_confirm_response.rb +0 -24
- data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +0 -56
- data/lib/friendly_shipping/services/ups/parse_void_shipment_response.rb +0 -24
- data/lib/friendly_shipping/services/ups/parse_xml_response.rb +0 -50
- data/lib/friendly_shipping/services/ups/rate_estimate_options.rb +0 -111
- data/lib/friendly_shipping/services/ups/rate_estimate_package_options.rb +0 -22
- data/lib/friendly_shipping/services/ups/serialize_access_request.rb +0 -20
- data/lib/friendly_shipping/services/ups/serialize_address_snippet.rb +0 -60
- data/lib/friendly_shipping/services/ups/serialize_address_validation_request.rb +0 -40
- data/lib/friendly_shipping/services/ups/serialize_city_state_lookup_request.rb +0 -26
- data/lib/friendly_shipping/services/ups/serialize_package_node.rb +0 -75
- data/lib/friendly_shipping/services/ups/serialize_rating_service_selection_request.rb +0 -98
- data/lib/friendly_shipping/services/ups/serialize_shipment_accept_request.rb +0 -27
- data/lib/friendly_shipping/services/ups/serialize_shipment_address_snippet.rb +0 -21
- data/lib/friendly_shipping/services/ups/serialize_shipment_confirm_request.rb +0 -285
- data/lib/friendly_shipping/services/ups/serialize_time_in_transit_request.rb +0 -56
- data/lib/friendly_shipping/services/ups/serialize_void_shipment_request.rb +0 -21
- data/lib/friendly_shipping/services/ups/shipping_methods.rb +0 -111
- data/lib/friendly_shipping/services/ups/timing_options.rb +0 -33
- data/lib/friendly_shipping/services/ups.rb +0 -218
- data/lib/friendly_shipping/services/usps/choose_package_rate.rb +0 -40
- data/lib/friendly_shipping/services/usps/machinable_package.rb +0 -50
- data/lib/friendly_shipping/services/usps/parse_address_validation_response.rb +0 -43
- data/lib/friendly_shipping/services/usps/parse_city_state_lookup_response.rb +0 -41
- data/lib/friendly_shipping/services/usps/parse_package_rate.rb +0 -159
- data/lib/friendly_shipping/services/usps/parse_rate_response.rb +0 -86
- data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +0 -240
- data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +0 -26
- data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +0 -66
- data/lib/friendly_shipping/services/usps/serialize_address_validation_request.rb +0 -25
- data/lib/friendly_shipping/services/usps/serialize_city_state_lookup_request.rb +0 -20
- data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +0 -83
- data/lib/friendly_shipping/services/usps/serialize_time_in_transit_request.rb +0 -22
- data/lib/friendly_shipping/services/usps/shipping_methods.rb +0 -66
- data/lib/friendly_shipping/services/usps/timing_options.rb +0 -19
- 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
|