friendly_shipping 0.8.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (217) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/.env.template +3 -0
  4. data/.env.test.local.template +6 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop-relaxed.yml +7 -23
  7. data/.rubocop.yml +17 -2
  8. data/.rubocop_todo.yml +21 -0
  9. data/.yardopts +6 -0
  10. data/CHANGELOG.md +71 -0
  11. data/Gemfile +17 -0
  12. data/README.md +1 -1
  13. data/friendly_shipping.gemspec +8 -17
  14. data/lib/friendly_shipping/access_token.rb +24 -0
  15. data/lib/friendly_shipping/api_error.rb +8 -4
  16. data/lib/friendly_shipping/api_error_handler.rb +11 -9
  17. data/lib/friendly_shipping/api_failure.rb +2 -7
  18. data/lib/friendly_shipping/api_result.rb +26 -4
  19. data/lib/friendly_shipping/carrier.rb +28 -8
  20. data/lib/friendly_shipping/http_client.rb +25 -8
  21. data/lib/friendly_shipping/inflections.rb +10 -0
  22. data/lib/friendly_shipping/item_options.rb +3 -0
  23. data/lib/friendly_shipping/label.rb +41 -22
  24. data/lib/friendly_shipping/package_options.rb +21 -2
  25. data/lib/friendly_shipping/rate.rb +50 -15
  26. data/lib/friendly_shipping/request.rb +23 -7
  27. data/lib/friendly_shipping/response.rb +21 -6
  28. data/lib/friendly_shipping/services/rl/api_error.rb +31 -0
  29. data/lib/friendly_shipping/services/rl/bol_options.rb +103 -0
  30. data/lib/friendly_shipping/services/rl/bol_packages_serializer.rb +32 -0
  31. data/lib/friendly_shipping/services/rl/bol_structures_serializer.rb +31 -0
  32. data/lib/friendly_shipping/services/rl/item_options.rb +36 -0
  33. data/lib/friendly_shipping/services/rl/package_options.rb +38 -0
  34. data/lib/friendly_shipping/services/rl/parse_create_bol_response.rb +43 -0
  35. data/lib/friendly_shipping/services/rl/parse_invoice_response.rb +47 -0
  36. data/lib/friendly_shipping/services/rl/parse_print_bol_response.rb +44 -0
  37. data/lib/friendly_shipping/services/rl/parse_print_shipping_labels_response.rb +44 -0
  38. data/lib/friendly_shipping/services/rl/parse_rate_quote_response.rb +93 -0
  39. data/lib/friendly_shipping/services/rl/parse_transit_times_response.rb +73 -0
  40. data/lib/friendly_shipping/services/rl/rate_quote_options.rb +82 -0
  41. data/lib/friendly_shipping/services/rl/rate_quote_packages_serializer.rb +54 -0
  42. data/lib/friendly_shipping/services/rl/rate_quote_structures_serializer.rb +53 -0
  43. data/lib/friendly_shipping/services/rl/serialize_create_bol_request.rb +86 -0
  44. data/lib/friendly_shipping/services/rl/serialize_location.rb +53 -0
  45. data/lib/friendly_shipping/services/rl/serialize_rate_quote_request.rb +69 -0
  46. data/lib/friendly_shipping/services/rl/serialize_transit_times_request.rb +38 -0
  47. data/lib/friendly_shipping/services/rl/shipment_document.rb +40 -0
  48. data/lib/friendly_shipping/services/rl/shipment_information.rb +41 -0
  49. data/lib/friendly_shipping/services/rl/shipment_options.rb +48 -0
  50. data/lib/friendly_shipping/services/rl/shipping_methods.rb +28 -0
  51. data/lib/friendly_shipping/services/rl/structure_options.rb +13 -0
  52. data/lib/friendly_shipping/services/rl.rb +187 -0
  53. data/lib/friendly_shipping/services/ship_engine/api_error.rb +31 -0
  54. data/lib/friendly_shipping/services/ship_engine/customs_items_serializer.rb +36 -0
  55. data/lib/friendly_shipping/services/ship_engine/label_customs_options.rb +10 -7
  56. data/lib/friendly_shipping/services/ship_engine/label_item_options.rb +10 -7
  57. data/lib/friendly_shipping/services/ship_engine/label_options.rb +30 -17
  58. data/lib/friendly_shipping/services/ship_engine/label_package_options.rb +18 -14
  59. data/lib/friendly_shipping/services/ship_engine/parse_address_validation_response.rb +75 -0
  60. data/lib/friendly_shipping/services/ship_engine/parse_carrier_response.rb +9 -2
  61. data/lib/friendly_shipping/services/ship_engine/parse_label_response.rb +4 -2
  62. data/lib/friendly_shipping/services/ship_engine/{parse_rate_estimate_response.rb → parse_rate_estimates_response.rb} +27 -9
  63. data/lib/friendly_shipping/services/ship_engine/parse_rates_response.rb +99 -0
  64. data/lib/friendly_shipping/services/ship_engine/parse_void_response.rb +5 -1
  65. data/lib/friendly_shipping/services/ship_engine/rate_estimates_options.rb +17 -6
  66. data/lib/friendly_shipping/services/ship_engine/rates_item_options.rb +26 -0
  67. data/lib/friendly_shipping/services/ship_engine/rates_options.rb +59 -0
  68. data/lib/friendly_shipping/services/ship_engine/rates_package_options.rb +18 -0
  69. data/lib/friendly_shipping/services/ship_engine/serialize_address_residential_indicator.rb +27 -0
  70. data/lib/friendly_shipping/services/ship_engine/serialize_address_validation_request.rb +31 -0
  71. data/lib/friendly_shipping/services/ship_engine/serialize_label_shipment.rb +22 -27
  72. data/lib/friendly_shipping/services/ship_engine/serialize_rate_estimate_request.rb +41 -16
  73. data/lib/friendly_shipping/services/ship_engine/serialize_rates_request.rb +126 -0
  74. data/lib/friendly_shipping/services/ship_engine.rb +89 -33
  75. data/lib/friendly_shipping/services/ship_engine_ltl/api_error.rb +12 -0
  76. data/lib/friendly_shipping/services/ship_engine_ltl/item_options.rb +24 -5
  77. data/lib/friendly_shipping/services/ship_engine_ltl/package_options.rb +39 -4
  78. data/lib/friendly_shipping/services/ship_engine_ltl/parse_carrier_response.rb +4 -2
  79. data/lib/friendly_shipping/services/ship_engine_ltl/parse_quote_response.rb +10 -2
  80. data/lib/friendly_shipping/services/ship_engine_ltl/quote_options.rb +29 -11
  81. data/lib/friendly_shipping/services/ship_engine_ltl/serialize_packages.rb +7 -2
  82. data/lib/friendly_shipping/services/ship_engine_ltl/serialize_quote_request.rb +50 -16
  83. data/lib/friendly_shipping/services/ship_engine_ltl/serialize_structures.rb +42 -0
  84. data/lib/friendly_shipping/services/ship_engine_ltl/shipment_options.rb +47 -0
  85. data/lib/friendly_shipping/services/ship_engine_ltl/structure_options.rb +17 -0
  86. data/lib/friendly_shipping/services/ship_engine_ltl.rb +47 -39
  87. data/lib/friendly_shipping/services/tforce_freight/access_token.rb +28 -0
  88. data/lib/friendly_shipping/services/tforce_freight/api_error.rb +40 -0
  89. data/lib/friendly_shipping/services/tforce_freight/bol_options.rb +180 -0
  90. data/lib/friendly_shipping/services/tforce_freight/document_options.rb +100 -0
  91. data/lib/friendly_shipping/services/tforce_freight/generate_commodity_information.rb +92 -0
  92. data/lib/friendly_shipping/services/tforce_freight/generate_create_bol_request_hash.rb +153 -0
  93. data/lib/friendly_shipping/services/tforce_freight/generate_document_options_hash.rb +36 -0
  94. data/lib/friendly_shipping/services/tforce_freight/generate_handling_units_hash.rb +51 -0
  95. data/lib/friendly_shipping/services/tforce_freight/generate_location_hash.rb +25 -0
  96. data/lib/friendly_shipping/services/tforce_freight/generate_pickup_request_hash.rb +111 -0
  97. data/lib/friendly_shipping/services/tforce_freight/generate_rates_request_hash.rb +63 -0
  98. data/lib/friendly_shipping/services/tforce_freight/generate_reference_hash.rb +28 -0
  99. data/lib/friendly_shipping/services/tforce_freight/item_options.rb +93 -0
  100. data/lib/friendly_shipping/services/tforce_freight/package_options.rb +119 -0
  101. data/lib/friendly_shipping/services/tforce_freight/parse_create_bol_response.rb +90 -0
  102. data/lib/friendly_shipping/services/tforce_freight/parse_pickup_response.rb +45 -0
  103. data/lib/friendly_shipping/services/tforce_freight/parse_rates_response.rb +50 -0
  104. data/lib/friendly_shipping/services/tforce_freight/parse_shipment_document.rb +29 -0
  105. data/lib/friendly_shipping/services/tforce_freight/pickup_options.rb +82 -0
  106. data/lib/friendly_shipping/services/tforce_freight/rates_item_options.rb +10 -0
  107. data/lib/friendly_shipping/services/tforce_freight/rates_options.rb +159 -0
  108. data/lib/friendly_shipping/services/tforce_freight/rates_package_options.rb +10 -0
  109. data/lib/friendly_shipping/services/tforce_freight/shipment_document.rb +38 -0
  110. data/lib/friendly_shipping/services/tforce_freight/shipment_information.rb +104 -0
  111. data/lib/friendly_shipping/services/tforce_freight/shipment_options.rb +47 -0
  112. data/lib/friendly_shipping/services/tforce_freight/shipping_methods.rb +25 -0
  113. data/lib/friendly_shipping/services/tforce_freight/structure_options.rb +44 -0
  114. data/lib/friendly_shipping/services/tforce_freight.rb +176 -0
  115. data/lib/friendly_shipping/services/ups/label_item_options.rb +0 -2
  116. data/lib/friendly_shipping/services/ups/label_options.rb +14 -5
  117. data/lib/friendly_shipping/services/ups/label_package_options.rb +1 -3
  118. data/lib/friendly_shipping/services/ups/parse_address_validation_response.rb +1 -1
  119. data/lib/friendly_shipping/services/ups/parse_modifier_element.rb +29 -0
  120. data/lib/friendly_shipping/services/ups/parse_rate_response.rb +11 -3
  121. data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +0 -3
  122. data/lib/friendly_shipping/services/ups/parse_shipment_confirm_response.rb +0 -2
  123. data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +1 -1
  124. data/lib/friendly_shipping/services/ups/parse_void_shipment_response.rb +0 -2
  125. data/lib/friendly_shipping/services/ups/parse_xml_response.rb +1 -1
  126. data/lib/friendly_shipping/services/ups/rate_estimate_options.rb +14 -3
  127. data/lib/friendly_shipping/services/ups/rate_estimate_package_options.rb +0 -2
  128. data/lib/friendly_shipping/services/ups/serialize_access_request.rb +0 -2
  129. data/lib/friendly_shipping/services/ups/serialize_rating_service_selection_request.rb +10 -5
  130. data/lib/friendly_shipping/services/ups/serialize_shipment_accept_request.rb +1 -1
  131. data/lib/friendly_shipping/services/ups/serialize_shipment_confirm_request.rb +2 -4
  132. data/lib/friendly_shipping/services/ups/shipping_methods.rb +1 -1
  133. data/lib/friendly_shipping/services/ups.rb +2 -24
  134. data/lib/friendly_shipping/services/ups_freight/api_error.rb +8 -7
  135. data/lib/friendly_shipping/services/ups_freight/generate_commodity_information.rb +65 -19
  136. data/lib/friendly_shipping/services/ups_freight/generate_freight_rate_request_hash.rb +2 -23
  137. data/lib/friendly_shipping/services/ups_freight/generate_freight_ship_request_hash.rb +1 -27
  138. data/lib/friendly_shipping/services/ups_freight/generate_handling_units_hash.rb +54 -0
  139. data/lib/friendly_shipping/services/ups_freight/label_options.rb +36 -10
  140. data/lib/friendly_shipping/services/ups_freight/label_structure_options.rb +13 -0
  141. data/lib/friendly_shipping/services/ups_freight/parse_freight_label_response.rb +2 -5
  142. data/lib/friendly_shipping/services/ups_freight/parse_freight_rate_response.rb +0 -4
  143. data/lib/friendly_shipping/services/ups_freight/parse_shipment_document.rb +1 -3
  144. data/lib/friendly_shipping/services/ups_freight/rates_item_options.rb +18 -9
  145. data/lib/friendly_shipping/services/ups_freight/rates_options.rb +30 -24
  146. data/lib/friendly_shipping/services/ups_freight/rates_package_options.rb +79 -10
  147. data/lib/friendly_shipping/services/ups_freight/rates_structure_options.rb +44 -0
  148. data/lib/friendly_shipping/services/ups_freight/shipment_information.rb +7 -3
  149. data/lib/friendly_shipping/services/ups_freight/shipment_options.rb +47 -0
  150. data/lib/friendly_shipping/services/ups_freight.rb +2 -20
  151. data/lib/friendly_shipping/services/ups_json/access_token.rb +20 -0
  152. data/lib/friendly_shipping/services/ups_json/api_error.rb +27 -0
  153. data/lib/friendly_shipping/services/ups_json/generate_address_classification_payload.rb +29 -0
  154. data/lib/friendly_shipping/services/ups_json/generate_address_hash.rb +30 -0
  155. data/lib/friendly_shipping/services/ups_json/generate_city_state_lookup_payload.rb +23 -0
  156. data/lib/friendly_shipping/services/ups_json/generate_labels_payload.rb +208 -0
  157. data/lib/friendly_shipping/services/ups_json/generate_package_hash.rb +76 -0
  158. data/lib/friendly_shipping/services/ups_json/generate_rates_payload.rb +84 -0
  159. data/lib/friendly_shipping/services/ups_json/generate_timings_payload.rb +44 -0
  160. data/lib/friendly_shipping/services/ups_json/label.rb +20 -0
  161. data/lib/friendly_shipping/services/ups_json/label_billing_options.rb +41 -0
  162. data/lib/friendly_shipping/services/ups_json/label_item_options.rb +75 -0
  163. data/lib/friendly_shipping/services/ups_json/label_options.rb +174 -0
  164. data/lib/friendly_shipping/services/ups_json/label_package_options.rb +49 -0
  165. data/lib/friendly_shipping/services/ups_json/parse_address_classification_response.rb +31 -0
  166. data/lib/friendly_shipping/services/ups_json/parse_city_state_lookup_response.rb +44 -0
  167. data/lib/friendly_shipping/services/ups_json/parse_json_response.rb +51 -0
  168. data/lib/friendly_shipping/services/ups_json/parse_labels_response.rb +71 -0
  169. data/lib/friendly_shipping/services/ups_json/parse_money_hash.rb +128 -0
  170. data/lib/friendly_shipping/services/ups_json/parse_rate_modifier_hash.rb +28 -0
  171. data/lib/friendly_shipping/services/ups_json/parse_rates_response.rb +107 -0
  172. data/lib/friendly_shipping/services/ups_json/parse_timings_response.rb +56 -0
  173. data/lib/friendly_shipping/services/ups_json/parse_void_response.rb +32 -0
  174. data/lib/friendly_shipping/services/ups_json/rates_item_options.rb +20 -0
  175. data/lib/friendly_shipping/services/ups_json/rates_options.rb +111 -0
  176. data/lib/friendly_shipping/services/ups_json/rates_package_options.rb +17 -0
  177. data/lib/friendly_shipping/services/ups_json/shipping_methods.rb +111 -0
  178. data/lib/friendly_shipping/services/ups_json/timings_options.rb +33 -0
  179. data/lib/friendly_shipping/services/ups_json.rb +216 -0
  180. data/lib/friendly_shipping/services/usps/choose_package_rate.rb +3 -3
  181. data/lib/friendly_shipping/services/usps/machinable_package.rb +1 -1
  182. data/lib/friendly_shipping/services/usps/parse_package_rate.rb +6 -6
  183. data/lib/friendly_shipping/services/usps/parse_rate_response.rb +0 -4
  184. data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +6 -9
  185. data/lib/friendly_shipping/services/usps/parse_xml_response.rb +1 -1
  186. data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +1 -3
  187. data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +6 -3
  188. data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +2 -4
  189. data/lib/friendly_shipping/services/usps/shipping_methods.rb +4 -3
  190. data/lib/friendly_shipping/services/usps.rb +2 -13
  191. data/lib/friendly_shipping/services/usps_international/parse_package_rate.rb +3 -3
  192. data/lib/friendly_shipping/services/usps_international/parse_rate_response.rb +0 -3
  193. data/lib/friendly_shipping/services/usps_international/rate_estimate_options.rb +1 -3
  194. data/lib/friendly_shipping/services/usps_international/rate_estimate_package_options.rb +0 -2
  195. data/lib/friendly_shipping/services/usps_international/serialize_rate_request.rb +1 -3
  196. data/lib/friendly_shipping/services/usps_international.rb +2 -6
  197. data/lib/friendly_shipping/services/usps_ship/access_token.rb +22 -0
  198. data/lib/friendly_shipping/services/usps_ship/api_error.rb +27 -0
  199. data/lib/friendly_shipping/services/usps_ship/machinable_package.rb +55 -0
  200. data/lib/friendly_shipping/services/usps_ship/parse_rate_estimates_response.rb +80 -0
  201. data/lib/friendly_shipping/services/usps_ship/parse_timings_response.rb +82 -0
  202. data/lib/friendly_shipping/services/usps_ship/rate_estimate_options.rb +45 -0
  203. data/lib/friendly_shipping/services/usps_ship/rate_estimate_package_options.rb +122 -0
  204. data/lib/friendly_shipping/services/usps_ship/serialize_rate_estimates_request.rb +53 -0
  205. data/lib/friendly_shipping/services/usps_ship/shipping_methods.rb +38 -0
  206. data/lib/friendly_shipping/services/{ship_engine_ltl/bad_request.rb → usps_ship/timing_options.rb} +2 -2
  207. data/lib/friendly_shipping/services/usps_ship.rb +185 -0
  208. data/lib/friendly_shipping/shipment_options.rb +13 -1
  209. data/lib/friendly_shipping/shipping_method.rb +38 -11
  210. data/lib/friendly_shipping/structure_options.rb +38 -0
  211. data/lib/friendly_shipping/timing.rb +42 -7
  212. data/lib/friendly_shipping/version.rb +1 -1
  213. data/lib/friendly_shipping.rb +20 -15
  214. metadata +143 -174
  215. data/lib/friendly_shipping/services/ship_engine/bad_request.rb +0 -29
  216. data/lib/friendly_shipping/services/ship_engine/bad_request_handler.rb +0 -33
  217. data/lib/friendly_shipping/services/ship_engine_ltl/bad_request_handler.rb +0 -33
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ class ParseRateModifierHash
7
+ # @param [Hash] hash the RateModifier hash from the source JSON
8
+ # @param [String] currency_code The currency code for this modifier's amount (i.e. 'USD')
9
+ # @return [Array] The label and the amount of the rate modifier
10
+ def self.call(rate_modifier, currency_code:)
11
+ return unless rate_modifier
12
+
13
+ amount = rate_modifier['Amount'].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 = rate_modifier['ModifierType']
20
+ modifier_description = rate_modifier['ModifierDesc']
21
+ label = "#{modifier_type} (#{modifier_description})"
22
+
23
+ [label, amount]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ class ParseRatesResponse
7
+ class << self
8
+ def call(request:, response:, shipment:)
9
+ parsed_response = ParseJsonResponse.call(
10
+ request: request,
11
+ response: response,
12
+ expected_root_key: 'RateResponse'
13
+ )
14
+ parsed_response.fmap do |parsing_result|
15
+ FriendlyShipping::ApiResult.new(
16
+ build_rates(parsing_result, shipment),
17
+ original_request: request,
18
+ original_response: response
19
+ )
20
+ end
21
+ end
22
+
23
+ def build_rates(rates_result, shipment)
24
+ rates = []
25
+
26
+ rated_shipments = Array.wrap(rates_result.dig('RateResponse', 'RatedShipment'))
27
+ rated_shipments.each do |rated_shipment|
28
+ service_code = rated_shipment.dig('Service', 'Code')
29
+ shipping_method = CARRIER.shipping_methods.detect do |sm|
30
+ sm.service_code == service_code && shipment.origin.country.in?(sm.origin_countries)
31
+ end
32
+ days_to_delivery = rated_shipment.dig('GuaranteedDelivery', 'BusinessDaysInTransit').to_i
33
+ total_cost = ParseMoneyHash.call(rated_shipment['TotalCharges'], 'TotalCharges').last
34
+ insurance_price = ParseMoneyHash.call(rated_shipment['ServiceOptionsCharges'], 'ServiceOptionsCharges')&.last
35
+ negotiated_rate = ParseMoneyHash.call(rated_shipment.dig('NegotiatedRateCharges', 'TotalCharge'), 'TotalCharge')&.last
36
+ negotiated_charges = extract_charges(rated_shipment.dig('NegotiatedRateCharges', 'ItemizedCharges'), 'ItemizedCharges')
37
+ itemized_charges = extract_charges(rated_shipment['ItemizedCharges'], 'ItemizedCharges')
38
+
39
+ rated_shipment_warnings = Array.wrap(rated_shipment['RatedShipmentAlert'])
40
+ rated_shipment_warnings = rated_shipment_warnings.map { |alert| alert["Description"] }
41
+ if rated_shipment_warnings.any? { |e| e.match?(/to Residential/) }
42
+ new_address_type = 'residential'
43
+ elsif rated_shipment_warnings.any? { |e| e.match?(/to Commercial/) }
44
+ new_address_type = 'commercial'
45
+ end
46
+
47
+ rates << FriendlyShipping::Rate.new(
48
+ shipping_method: shipping_method,
49
+ amounts: { total: total_cost },
50
+ warnings: rated_shipment_warnings,
51
+ errors: [],
52
+ data: {
53
+ insurance_price: insurance_price,
54
+ negotiated_rate: negotiated_rate,
55
+ negotiated_charges: negotiated_charges,
56
+ days_to_delivery: days_to_delivery,
57
+ new_address_type: new_address_type,
58
+ itemized_charges: itemized_charges,
59
+ packages: build_packages(rated_shipment)
60
+ }.compact
61
+ )
62
+ end
63
+ rates
64
+ end
65
+
66
+ private
67
+
68
+ def build_packages(rated_shipment)
69
+ package_array = Array.wrap(rated_shipment['RatedPackage'])
70
+ package_array.map do |rated_package|
71
+ currency_code = rated_package.dig('TotalCharges', 'CurrencyCode')
72
+ {
73
+ transportation_charges: ParseMoneyHash.call(rated_package['TransportationCharges'], 'TransportationCharges')&.last,
74
+ base_service_charge: ParseMoneyHash.call(rated_package['BaseServiceCharge'], 'BaseServiceCharge')&.last,
75
+ service_options_charges: ParseMoneyHash.call(rated_package['ServiceOptionsCharges'], 'ServiceOptionsCharges')&.last,
76
+ itemized_charges: extract_charges(rated_package['ItemizedCharges'], 'ItemizedCharges'),
77
+ total_charges: ParseMoneyHash.call(rated_package['TotalCharges'], 'TotalCharges')&.last,
78
+ negotiated_charges: extract_charges(rated_package.dig('NegotiatedCharges', 'ItemizedCharges'), 'ItemizedCharges'),
79
+ rate_modifiers: extract_modifiers(rated_package['RateModifier'], currency_code: currency_code),
80
+ weight: BigDecimal(rated_package['Weight']),
81
+ billing_weight: BigDecimal(rated_package.dig('BillingWeight', 'Weight'))
82
+ }.compact
83
+ end
84
+ end
85
+
86
+ def extract_charges(charges, key_name)
87
+ non_zero_charges = {}
88
+ Array.wrap(charges).map do |charge|
89
+ parsed_charge = ParseMoneyHash.call(charge, key_name)
90
+ non_zero_charges[parsed_charge[0]] = parsed_charge[1] if parsed_charge.present?
91
+ end
92
+ non_zero_charges
93
+ end
94
+
95
+ def extract_modifiers(modifiers, currency_code:)
96
+ rate_modifiers = {}
97
+ Array.wrap(modifiers).map do |modifier|
98
+ parsed_modifier = ParseRateModifierHash.call(modifier, currency_code: currency_code)
99
+ rate_modifiers[parsed_modifier[0]] = parsed_modifier[1] if parsed_modifier.present?
100
+ end
101
+ rate_modifiers
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ class ParseTimingsResponse
7
+ class << self
8
+ def call(request:, response:, shipment:)
9
+ parsed_response = ParseJsonResponse.call(
10
+ request: request,
11
+ response: response,
12
+ expected_root_key: 'emsResponse'
13
+ )
14
+ parsed_response.fmap do |parsing_result|
15
+ FriendlyShipping::ApiResult.new(
16
+ build_timings(parsing_result, shipment),
17
+ original_request: request,
18
+ original_response: response
19
+ )
20
+ end
21
+ end
22
+
23
+ def build_timings(timings_result, shipment)
24
+ service_timings = Array.wrap(timings_result.dig('emsResponse', 'services'))
25
+ service_timings.map do |timing|
26
+ service_description = timing['serviceLevelDescription']
27
+ shipping_method = SHIPPING_METHODS.detect do |potential_shipping_method|
28
+ service_description == potential_shipping_method.name &&
29
+ potential_shipping_method.origin_countries.map(&:code).include?(shipment.origin.country.code)
30
+ end
31
+ delivery_date = timing['deliveryDate']
32
+ delivery_time = timing['deliveryTime']
33
+ delivery = Time.parse("#{delivery_date} #{delivery_time}")
34
+ pickup_date = timing['pickupDate']
35
+ pickup_time = timing['pickupTime']
36
+ pickup = Time.parse("#{pickup_date} #{pickup_time}")
37
+
38
+ guaranteed = timing['guaranteeIndicator'] == '1'
39
+ business_transit_days = timing['businessTransitDays']
40
+
41
+ FriendlyShipping::Timing.new(
42
+ shipping_method: shipping_method,
43
+ pickup: pickup,
44
+ delivery: delivery,
45
+ guaranteed: guaranteed,
46
+ data: {
47
+ business_transit_days: business_transit_days
48
+ }
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ class ParseVoidResponse
7
+ extend Dry::Monads::Result::Mixin
8
+
9
+ class << self
10
+ def call(request:, response:)
11
+ parsed_response = ParseJsonResponse.call(
12
+ request: request,
13
+ response: response,
14
+ expected_root_key: "VoidShipmentResponse"
15
+ )
16
+
17
+ parsed_response.bind do |void_response|
18
+ result = void_response["VoidShipmentResponse"]
19
+ Success(
20
+ FriendlyShipping::ApiResult.new(
21
+ result,
22
+ original_request: request,
23
+ original_response: response
24
+ )
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ # Represents item options for determining rates.
7
+ # @option commodity_code [String] This item's HS or NMFC code for international shipments.
8
+ # @option country_of_origin [String] This item's country of origin for international shipments.
9
+ class RatesItemOptions < FriendlyShipping::ItemOptions
10
+ attr_reader :commodity_code, :country_of_origin
11
+
12
+ def initialize(commodity_code: nil, country_of_origin: nil, **kwargs)
13
+ @commodity_code = commodity_code
14
+ @country_of_origin = country_of_origin
15
+ super(**kwargs)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ # Option container for rating a shipment via UPS
6
+ #
7
+ # Required:
8
+ #
9
+ # @option shipper_number [String] The shipper number of the origin of the shipment.
10
+ #
11
+ # Optional:
12
+ #
13
+ # @option carbon_neutral [Boolean] Ship with UPS' carbon neutral program
14
+ # @option customer_context [String ] Optional element to identify transactions between client and server
15
+ # @option customer_classification [Symbol] Which kind of rates to request. See UPS docs for more details. Default: `shipper_number`
16
+ # @option negotiated_rates [Boolean] if truthy negotiated rates will be requested from ups. Only valid if
17
+ # shipper account has negotiated rates. Default: false
18
+ # @option pickup_type [String] UPS pickup type. See UPS docs for more details. Default: `daily_pickup`
19
+ # @option pickup_date [Time] UPS pickup date/time. Default: nil
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 sub_version [String] The UPS API sub-version to use for requests. Default: v2205
25
+ # @option with_time_in_transit [Boolean] Whether to request timing information alongside the rates
26
+ # @option package_options_class [Class] See FriendlyShipping::ShipmentOptions
27
+ #
28
+ class UpsJson
29
+ class RatesOptions < FriendlyShipping::ShipmentOptions
30
+ PICKUP_TYPE_CODES = {
31
+ daily_pickup: "01",
32
+ customer_counter: "03",
33
+ one_time_pickup: "06",
34
+ on_call_air: "07",
35
+ suggested_retail_rates: "11",
36
+ letter_center: "19",
37
+ air_service_center: "20"
38
+ }.freeze
39
+
40
+ CUSTOMER_CLASSIFICATION_CODES = {
41
+ shipper_number: "00",
42
+ daily_rates: "01",
43
+ retail_rates: "04",
44
+ regional_rates: "05",
45
+ general_rates: "06",
46
+ standard_rates: "53"
47
+ }.freeze
48
+
49
+ SUB_VERSIONS = %w[1 1601 1607 1701 1707 2108 2205].freeze
50
+
51
+ attr_reader :carbon_neutral,
52
+ :customer_context,
53
+ :destination_account,
54
+ :negotiated_rates,
55
+ :pickup_date,
56
+ :saturday_delivery,
57
+ :saturday_pickup,
58
+ :shipper,
59
+ :shipper_number,
60
+ :shipping_method,
61
+ :sub_version,
62
+ :with_time_in_transit
63
+
64
+ def initialize(
65
+ shipper_number:,
66
+ carbon_neutral: true,
67
+ customer_context: nil,
68
+ customer_classification: :daily_rates,
69
+ destination_account: nil,
70
+ negotiated_rates: false,
71
+ pickup_type: :daily_pickup,
72
+ pickup_date: nil,
73
+ saturday_delivery: false,
74
+ saturday_pickup: false,
75
+ shipper: nil,
76
+ shipping_method: nil,
77
+ sub_version: "2205",
78
+ with_time_in_transit: false,
79
+ package_options_class: RatesPackageOptions,
80
+ **kwargs
81
+ )
82
+ raise ArgumentError, "Invalid sub-version: #{sub_version}" unless sub_version.in?(SUB_VERSIONS)
83
+
84
+ @carbon_neutral = carbon_neutral
85
+ @customer_context = customer_context
86
+ @customer_classification = customer_classification
87
+ @destination_account = destination_account
88
+ @negotiated_rates = negotiated_rates
89
+ @shipper_number = shipper_number
90
+ @pickup_type = pickup_type
91
+ @pickup_date = pickup_date
92
+ @saturday_delivery = saturday_delivery
93
+ @saturday_pickup = saturday_pickup
94
+ @shipper = shipper
95
+ @shipping_method = shipping_method
96
+ @sub_version = sub_version
97
+ @with_time_in_transit = with_time_in_transit
98
+ super(**kwargs.reverse_merge(package_options_class: package_options_class))
99
+ end
100
+
101
+ def pickup_type_code
102
+ PICKUP_TYPE_CODES[@pickup_type]
103
+ end
104
+
105
+ def customer_classification_code
106
+ CUSTOMER_CLASSIFICATION_CODES[@customer_classification]
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ class RatesPackageOptions < FriendlyShipping::PackageOptions
7
+ attr_reader :transmit_dimensions
8
+
9
+ def initialize(transmit_dimensions: true,
10
+ **kwargs)
11
+ @transmit_dimensions = transmit_dimensions
12
+ super(**kwargs.reverse_merge(item_options_class: RatesItemOptions))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ EU_COUNTRIES = %w(
7
+ AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE IT LT LU LV MT NL PO PT RO SE SI SK
8
+ ).map { |country_code| Carmen::Country.coded(country_code) }
9
+
10
+ class << self
11
+ private
12
+
13
+ def countries_by_code(code)
14
+ all_countries = Carmen::Country.all
15
+ covered_countries = EU_COUNTRIES + %w(US PR CA PL MX).map do |country_code|
16
+ Carmen::Country.coded(country_code)
17
+ end
18
+ other_countries = Carmen::Country.all - covered_countries
19
+ case code
20
+ when 'EU' then EU_COUNTRIES
21
+ when 'OTHER' then other_countries
22
+ when 'ALL' then all_countries
23
+ else
24
+ [Carmen::Country.coded(code)]
25
+ end
26
+ end
27
+ end
28
+
29
+ SHIPPING_METHODS = [
30
+ ['US', 'international', 'UPS Standard', '11'],
31
+ ['US', 'international', 'UPS Worldwide Express®', '07'],
32
+ ['US', 'international', 'UPS Worldwide Expedited®', '08'],
33
+ ['US', 'international', 'UPS Worldwide Express Plus®', '54'],
34
+ ['US', 'international', 'UPS Worldwide Saver', '65'],
35
+ ['US', 'domestic', 'UPS 2nd Day Air®', '02'],
36
+ ['US', 'domestic', 'UPS 2nd Day Air A.M.', '59'],
37
+ ['US', 'domestic', 'UPS 3 Day Select®', '12'],
38
+ ['US', 'domestic', 'UPS Ground', '03'],
39
+ ['US', 'domestic', 'UPS Next Day Air®', '01'],
40
+ ['US', 'domestic', 'UPS Next Day Air® Early', '14'],
41
+ ['US', 'domestic', 'UPS Next Day Air Saver®', '13'],
42
+ ['US', 'domestic', 'UPS SurePost Less than 1LB', '92'],
43
+ ['US', 'domestic', 'UPS SurePost 1LB or greater', '93'],
44
+ ['US', 'domestic', 'UPS SurePost BPM', '94'],
45
+ ['US', 'domestic', 'UPS SurePost Media Mail', '95'],
46
+ ['CA', 'domestic', 'UPS Expedited Canadian domestic shipments', '02'],
47
+ ['CA', 'domestic', 'UPS Express Saver Canadian domestic shipments', '13'],
48
+ ['CA', 'domestic', 'UPS 3 Day Select Shipments originating in Canada to CA and US 48', '12'],
49
+ ['CA', 'international', 'UPS 3 Day Select Shipments originating in Canada to CA and US 48', '12'],
50
+ ['CA', 'domestic', 'UPS Access Point Economy Canadian domestic shipments', '70'],
51
+ ['CA', 'domestic', 'UPS Express Canadian domestic shipments', '01'],
52
+ ['CA', 'domestic', 'UPS Express Early Canadian domestic shipments', '14'],
53
+ ['CA', 'international', 'UPS Express Saver International shipments originating in Canada', '65'],
54
+ ['CA', 'international', 'UPS Standard Shipments originating in Canada (Domestic and Int’l)', '11'],
55
+ ['CA', 'domestic', 'UPS Standard Shipments originating in Canada (Domestic and Int’l)', '11'],
56
+ ['CA', 'international', 'UPS Worldwide Expedited International shipments originating in Canada', '08'],
57
+ ['CA', 'international', 'UPS Worldwide Express International shipments originating in Canada', '07'],
58
+ ['CA', 'international', 'UPS Worldwide Express Plus International shipments originating in Canada', '54'],
59
+ ['CA', 'international', 'UPS Express Early Shipments originating in Canada to CA and US 48', '54'],
60
+ ['CA', 'domestic', 'UPS Express Early Shipments originating in Canada to CA and US 48', '54'],
61
+ ['EU', 'domestic', 'UPS Access Point Economy Shipments within the European Union', '70'],
62
+ ['EU', 'international', 'UPS Expedited Shipments originating in the European Union', '08'],
63
+ ['EU', 'international', 'UPS Express Shipments originating in the European Union', '07'],
64
+ ['EU', 'international', 'UPS Standard Shipments originating in the European Union', '11'],
65
+ ['EU', 'international', 'UPS Worldwide Express Plus Shipments originating in the European Union', '54'],
66
+ ['EU', 'international', 'UPS Worldwide Saver Shipments originating in the European Union', '65'],
67
+ ['MX', 'domestic', 'UPS Access Point Economy Shipments within Mexico', '70'],
68
+ ['MX', 'international', 'UPS Expedited Shipments originating in Mexico', '08'],
69
+ ['MX', 'international', 'UPS Express Shipments originating in Mexico', '07'],
70
+ ['MX', 'international', 'UPS Standard Shipments originating in Mexico', '11'],
71
+ ['MX', 'international', 'UPS Worldwide Express Plus Shipments originating in Mexico', '54'],
72
+ ['MX', 'international', 'UPS Worldwide Saver Shipments originating in Mexico', '65'],
73
+ ['PL', 'domestic', 'UPS Access Point Economy Polish domestic shipments', '70'],
74
+ ['PL', 'domestic', 'UPS Today Dedicated Courier Polish domestic shipments', '83'],
75
+ ['PL', 'domestic', 'UPS Today Express Polish domestic shipments', '85'],
76
+ ['PL', 'domestic', 'UPS Today Express Saver Polish domestic shipments', '86'],
77
+ ['PL', 'domestic', 'UPS Today Standard Polish domestic shipments', '82'],
78
+ ['PL', 'international', 'UPS Expedited Shipments originating in Poland', '08'],
79
+ ['PL', 'international', 'UPS Express Shipments originating in Poland', '07'],
80
+ ['PL', 'international', 'UPS Express Plus Shipments originating in Poland', '54'],
81
+ ['PL', 'international', 'UPS Express Saver Shipments originating in Poland', '65'],
82
+ ['PL', 'international', 'UPS Standard Shipments originating in Poland', '11'],
83
+ ['PR', 'domestic', 'UPS 2nd Day Air', '02'],
84
+ ['PR', 'domestic', 'UPS Ground', '03'],
85
+ ['PR', 'domestic', 'UPS Next Day Air', '01'],
86
+ ['PR', 'domestic', 'UPS Next Day Air Early', '14'],
87
+ ['PR', 'international', 'UPS Worldwide Expedited', '08'],
88
+ ['PR', 'international', 'UPS Worldwide Express', '07'],
89
+ ['PR', 'international', 'UPS Worldwide Express Plus', '54'],
90
+ ['PR', 'international', 'UPS Worldwide Saver', '65'],
91
+ ['DE', 'domestic', 'UPS Express 12:00 German domestic shipments', '74'],
92
+ ['OTHER', 'domestic', 'UPS Express', '07'],
93
+ ['OTHER', 'domestic', 'UPS Standard', '11'],
94
+ ['OTHER', 'international', 'UPS Worldwide Expedited', '08'],
95
+ ['OTHER', 'international', 'UPS Worldwide Express Plus', '54'],
96
+ ['OTHER', 'international', 'UPS Worldwide Saver', '65'],
97
+ ['ALL', 'international', 'UPS Worldwide Express Freight', '96'],
98
+ ['ALL', 'international', 'UPS Worldwide Express Freight Midday', '71']
99
+ ].freeze.map do |origin_country_code, dom_or_intl, name, code|
100
+ FriendlyShipping::ShippingMethod.new(
101
+ name: name,
102
+ service_code: code,
103
+ domestic: dom_or_intl == 'domestic',
104
+ international: dom_or_intl == 'international',
105
+ origin_countries: countries_by_code(origin_country_code),
106
+ multi_package: true
107
+ ).freeze
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class UpsJson
6
+ # Options for getting timing information from UPS
7
+ # @attribute [Time] pickup When the shipment will be picked up
8
+ # @attribute [Money] invoice_total How much the items in the shipment are worth
9
+ # As this is not super important for getting timing information, we use a default
10
+ # value of 50 USD here.
11
+ # @attribute [Boolean] documents_only Does the shipment only contain documents?
12
+ # @attribute [String] customer_context A string to connect request and response in the calling code
13
+ class TimingsOptions
14
+ attr_reader :pickup,
15
+ :invoice_total,
16
+ :documents_only,
17
+ :customer_context
18
+
19
+ def initialize(
20
+ pickup: Time.now,
21
+ invoice_total: Money.new(5000, 'USD'),
22
+ documents_only: false,
23
+ customer_context: nil
24
+ )
25
+ @pickup = pickup
26
+ @invoice_total = invoice_total
27
+ @documents_only = documents_only
28
+ @customer_context = customer_context
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end