friendly_shipping 0.3.0 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 821bec044ee86ff1e468dd392ae55c475510af6c
4
- data.tar.gz: 153b7226c2e01afe5b6d7fdc260e39b712986ae5
3
+ metadata.gz: 2eff4626b4d3f7d779b8837725bd6c585fc3bd47
4
+ data.tar.gz: 3818a4de3a68cb8f9a7cf5d62650e7da66604250
5
5
  SHA512:
6
- metadata.gz: a1ad8abee5b1a56f33cc8c5b8a4d55d4bce1f056e74a0719ea237ab577587921b43ada0ca50f35ab52bba261b2fc18598e612a82185a9d172c3d45918cf07f8e
7
- data.tar.gz: 29a07b0165a570d6cbf5c008e2ff1a67aa6ce2e245be10357cbaaeb13b88c888e74419dec65ca9df7ce147e38524eb1b42a55393dba6f17c8cf2d2e4cef61338
6
+ metadata.gz: d8a23d14f73876ca9f47b6e5f6272df7d958c0c9f7a754438c9208d8c654e7ba5ca79ee01bdc9d06ed105045335f6a75016007dbdf9166a27fd55b6096c63d8f
7
+ data.tar.gz: 54458eb117ed1d541af4208979e73f2d1f5ce7e7b8afcbaea702e024165da7c6320e67ffc4d8f82913a8e1e6efb08d29ef1ca44114bef3b09c3f2d1d7d734893
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.3] - 2017-10-25
10
+
11
+ ### Changed
12
+ - Fix: ShipEngine#labels test mode works again.
13
+
14
+ ## [0.3.2] - 2017-10-25
15
+
16
+ ### Changed
17
+ - Fix: ShipEngine#labels now works as expected.
18
+
19
+ ## [0.3.1] - 2017-06-20
20
+ ### Added
21
+ - Endpoint for UPS address classification
22
+
23
+ ### Changed
24
+ - `ShipEngine#labels` now needs a second argument, the shipping method.
data/README.md CHANGED
@@ -48,7 +48,7 @@ The following methods are supported:
48
48
 
49
49
  - `#carriers` - List all configured carriers
50
50
  - `#rate_estimates(physical_shipment, carriers: [friendly_shipping_carrier])` - Get rate estimates for a shipment
51
- - `#labels(physical_shipment)` - Get labels for a shipments. Currently only supports USPS labels, other services are untested. The API of this method is still subject to change.
51
+ - `#labels(physical_shipment, shipping_method:)` - Get labels for a shipments. Currently only supports USPS labels, other services are untested.
52
52
  - `#void(physical_label)` - Void a label and get the cost refunded
53
53
 
54
54
  #### UPS (United Parcel Service)
@@ -68,6 +68,7 @@ The following methods are supported:
68
68
 
69
69
  - `#carriers` - List all configured carriers (always returns UPS)
70
70
  - `#rate_estimates(physical_shipment)` - Get rate estimates for a shipment
71
+ - `#address_classification(physical_location)` - Determine whether an address is commercial or residential.
71
72
  - `#address_validation(physical_location)` - Perform a detailed address validation and determine whether an address is commercial or residential.
72
73
  - `#city_state_lookup(physical_location)` - Lookup City and State for a given ZIP code.
73
74
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/monads/result'
4
+ require 'rest-client'
5
+
6
+ module FriendlyShipping
7
+ class HttpClient
8
+ include Dry::Monads::Result::Mixin
9
+
10
+ attr_reader :error_handler
11
+
12
+ def initialize(error_handler: method(:wrap_in_failure))
13
+ @error_handler = error_handler
14
+ end
15
+
16
+ def get(request)
17
+ http_response = ::RestClient.get(
18
+ request.url, request.headers
19
+ )
20
+
21
+ Success(convert_to_friendly_response(http_response))
22
+ rescue ::RestClient::Exception => e
23
+ error_handler.call(e)
24
+ end
25
+
26
+ def post(friendly_shipping_request)
27
+ http_response = ::RestClient.post(
28
+ friendly_shipping_request.url,
29
+ friendly_shipping_request.body,
30
+ friendly_shipping_request.headers
31
+ )
32
+
33
+ Success(convert_to_friendly_response(http_response))
34
+ rescue ::RestClient::Exception => e
35
+ error_handler.call(e)
36
+ end
37
+
38
+ def put(request)
39
+ http_response = ::RestClient.put(
40
+ request.url,
41
+ request.body,
42
+ request.headers
43
+ )
44
+
45
+ Success(convert_to_friendly_response(http_response))
46
+ rescue ::RestClient::Exception => e
47
+ error_handler.call(e)
48
+ end
49
+
50
+ private
51
+
52
+ def wrap_in_failure(error)
53
+ Failure(error)
54
+ end
55
+
56
+ def convert_to_friendly_response(http_response)
57
+ FriendlyShipping::Response.new(
58
+ status: http_response.code,
59
+ body: http_response.body,
60
+ headers: http_response.headers
61
+ )
62
+ end
63
+ end
64
+ end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/monads/result'
4
- require 'friendly_shipping/services/ship_engine/client'
4
+ require 'friendly_shipping/http_client'
5
+ require 'friendly_shipping/services/ship_engine/bad_request_handler'
5
6
  require 'friendly_shipping/services/ship_engine/parse_carrier_response'
6
7
  require 'friendly_shipping/services/ship_engine/serialize_label_shipment'
7
8
  require 'friendly_shipping/services/ship_engine/serialize_rate_estimate_request'
@@ -18,7 +19,7 @@ module FriendlyShipping
18
19
  labels: "labels"
19
20
  }.freeze
20
21
 
21
- def initialize(token:, test: true, client: Client)
22
+ def initialize(token:, test: true, client: FriendlyShipping::HttpClient.new(error_handler: BadRequestHandler))
22
23
  @token = token
23
24
  @test = test
24
25
  @client = client
@@ -27,10 +28,11 @@ module FriendlyShipping
27
28
  # Get configured carriers from USPS
28
29
  #
29
30
  # @return [Result<ApiResult<Array<Carrier>>>] Carriers configured in your shipstation account
30
- def carriers
31
+ def carriers(debug: false)
31
32
  request = FriendlyShipping::Request.new(
32
33
  url: API_BASE + API_PATHS[:carriers],
33
- headers: request_headers
34
+ headers: request_headers,
35
+ debug: debug
34
36
  )
35
37
  client.get(request).fmap do |response|
36
38
  ParseCarrierResponse.call(request: request, response: response)
@@ -47,12 +49,13 @@ module FriendlyShipping
47
49
  # @return [Result<ApiResult<Array<FriendlyShipping::Rate>>>] When successfully parsing, an array of rates in a Success Monad.
48
50
  # When the parsing is not successful or ShipEngine can't give us rates, a Failure monad containing something that
49
51
  # can be serialized into an error message using `to_s`.
50
- def rate_estimates(shipment, options = {})
51
- selected_carriers = options[:carriers] || carriers.value!.data
52
+ def rate_estimates(shipment, selected_carriers: nil, debug: false)
53
+ selected_carriers ||= carriers.value!.data
52
54
  request = FriendlyShipping::Request.new(
53
55
  url: API_BASE + 'rates/estimate',
54
56
  body: SerializeRateEstimateRequest.call(shipment: shipment, carriers: selected_carriers).to_json,
55
- headers: request_headers
57
+ headers: request_headers,
58
+ debug: debug
56
59
  )
57
60
  client.post(request).bind do |response|
58
61
  ParseRateEstimateResponse.call(response: response, request: request, carriers: selected_carriers)
@@ -64,13 +67,16 @@ module FriendlyShipping
64
67
  # @param [Physical::Shipment] shipment The shipment object we're trying to get labels for
65
68
  # Note: Some ShipEngine carriers, notably USPS, only support one package per shipment, and that's
66
69
  # all that the integration supports at this point.
70
+ # @param [FriendlyShipping::ShippingMethod] shipping_method The shipping method we want to use.
71
+ # Specifically, the "#service_code" will be serialized. If a carrier is set, it's `#id` will
72
+ # also be sent to ShipEngine.
67
73
  #
68
74
  # @return [Result<ApiResult<Array<FriendlyShipping::Label>>>] The label returned.
69
75
  #
70
- def labels(shipment)
76
+ def labels(shipment, shipping_method:)
71
77
  request = FriendlyShipping::Request.new(
72
78
  url: API_BASE + API_PATHS[:labels],
73
- body: SerializeLabelShipment.new(shipment: shipment).call.merge(test_label: test).to_json,
79
+ body: SerializeLabelShipment.call(shipment: shipment, shipping_method: shipping_method, test: test).to_json,
74
80
  headers: request_headers
75
81
  )
76
82
  client.post(request).fmap do |response|
@@ -78,11 +84,12 @@ module FriendlyShipping
78
84
  end
79
85
  end
80
86
 
81
- def void(label)
87
+ def void(label, debug: false)
82
88
  request = FriendlyShipping::Request.new(
83
89
  url: "#{API_BASE}labels/#{label.id}/void",
84
90
  body: '',
85
- headers: request_headers
91
+ headers: request_headers,
92
+ debug: debug
86
93
  )
87
94
  client.put(request).bind do |response|
88
95
  ParseVoidResponse.call(request: request, response: response)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ class ShipEngine
8
+ class BadRequest < StandardError
9
+ attr_reader :rest_error, :response
10
+
11
+ def initialize(rest_error)
12
+ @rest_error = rest_error
13
+ @response = rest_error.response
14
+ super parse_json_errors || rest_error
15
+ end
16
+
17
+ private
18
+
19
+ def parse_json_errors
20
+ parsed_body = JSON.parse(response.body)
21
+ messages = parsed_body.fetch('errors')&.map { |e| e.fetch('message') }
22
+ messages&.join(', ')
23
+ rescue JSON::ParserError, KeyError => _e
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'friendly_shipping/services/ship_engine/bad_request'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ class ShipEngine
8
+ class BadRequestHandler
9
+ extend Dry::Monads::Result::Mixin
10
+
11
+ def self.call(error)
12
+ if error.http_code == 400
13
+ Failure(BadRequest.new(error))
14
+ else
15
+ Failure(error)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'friendly_shipping/bad_request'
4
-
5
3
  module FriendlyShipping
6
4
  module Services
7
5
  class ShipEngine
@@ -4,76 +4,78 @@ module FriendlyShipping
4
4
  module Services
5
5
  class ShipEngine
6
6
  class SerializeLabelShipment
7
- attr_reader :shipment
7
+ class << self
8
+ def call(shipment:, shipping_method:, test:)
9
+ shipment_hash = {
10
+ label_format: shipment.options[:label_format].presence || "pdf",
11
+ label_download_type: shipment.options[:label_download_type].presence || "url",
12
+ shipment: {
13
+ service_code: shipping_method.service_code,
14
+ ship_to: serialize_address(shipment.destination),
15
+ ship_from: serialize_address(shipment.origin),
16
+ packages: serialize_packages(shipment.packages)
17
+ }
18
+ }
19
+ # A carrier might not be necessary if the service code is unique within ShipEngine.
20
+ if shipping_method.carrier
21
+ shipment_hash[:shipment][:carrier_id] = shipping_method.carrier.id
22
+ end
8
23
 
9
- def initialize(shipment:)
10
- @shipment = shipment
11
- end
24
+ if test
25
+ shipment_hash[:test_label] = true
26
+ end
12
27
 
13
- def call
14
- shipment_hash = {
15
- label_format: shipment.options[:label_format].presence || "pdf",
16
- label_download_type: shipment.options[:label_download_type].presence || "url",
17
- shipment: {
18
- service_code: shipment.service_code,
19
- ship_to: serialize_address(shipment.destination),
20
- ship_from: serialize_address(shipment.origin),
21
- packages: serialize_packages(shipment.packages)
22
- }
23
- }
24
- if shipment.options[:carrier_id]
25
- shipment_hash[:shipment][:carrier_id] = shipment.options[:carrier_id]
28
+ shipment_hash
26
29
  end
27
- shipment_hash
28
- end
29
30
 
30
- private
31
+ private
31
32
 
32
- def serialize_address(address)
33
- {
34
- name: address.name,
35
- phone: address.phone,
36
- company_name: address.company_name,
37
- address_line1: address.address1,
38
- address_line2: address.address2,
39
- city_locality: address.city,
40
- state_province: address.region.code,
41
- postal_code: address.zip,
42
- country_code: address.country.code,
43
- address_residential_indicator: "No"
44
- }
45
- end
33
+ def serialize_address(address)
34
+ {
35
+ name: address.name,
36
+ phone: address.phone,
37
+ company_name: address.company_name,
38
+ address_line1: address.address1,
39
+ address_line2: address.address2,
40
+ city_locality: address.city,
41
+ state_province: address.region.code,
42
+ postal_code: address.zip,
43
+ country_code: address.country.code,
44
+ address_residential_indicator: "No"
45
+ }
46
+ end
46
47
 
47
- def serialize_packages(packages)
48
- packages.map do |package|
49
- package_hash = serialize_weight(package.weight)
50
- if package.container.properties[:usps_label_messages]
51
- package_hash[:label_messages] = package.container.properties[:usps_label_messages]
52
- end
53
- package_code = package.container.properties[:usps_package_code]
54
- if package_code
55
- package_hash[:package_code] = package_code
56
- else
57
- package_hash[:dimensions] = {
58
- unit: 'inch',
59
- width: package.container.width.convert_to(:inches).value.to_f.round(2),
60
- length: package.container.length.convert_to(:inches).value.to_f.round(2),
61
- height: package.container.height.convert_to(:inches).value.to_f.round(2)
62
- }
48
+ def serialize_packages(packages)
49
+ packages.map do |package|
50
+ package_hash = serialize_weight(package.weight)
51
+ if package.container.properties[:usps_label_messages]
52
+ package_hash[:label_messages] = package.container.properties[:usps_label_messages]
53
+ end
54
+ package_code = package.container.properties[:usps_package_code]
55
+ if package_code
56
+ package_hash[:package_code] = package_code
57
+ else
58
+ package_hash[:dimensions] = {
59
+ unit: 'inch',
60
+ width: package.container.width.convert_to(:inches).value.to_f.round(2),
61
+ length: package.container.length.convert_to(:inches).value.to_f.round(2),
62
+ height: package.container.height.convert_to(:inches).value.to_f.round(2)
63
+ }
64
+ end
65
+ package_hash
63
66
  end
64
- package_hash
65
67
  end
66
- end
67
68
 
68
- def serialize_weight(weight)
69
- ounces = weight.convert_to(:ounce).value.to_f
70
- {
71
- weight: {
72
- # Max weight for USPS First Class is 15.9 oz, not 16 oz
73
- value: ounces.between?(15.9, 16) ? 15.9 : ounces,
74
- unit: "ounce"
69
+ def serialize_weight(weight)
70
+ ounces = weight.convert_to(:ounce).value.to_f
71
+ {
72
+ weight: {
73
+ # Max weight for USPS First Class is 15.9 oz, not 16 oz
74
+ value: ounces.between?(15.9, 16) ? 15.9 : ounces,
75
+ unit: "ounce"
76
+ }
75
77
  }
76
- }
78
+ end
77
79
  end
78
80
  end
79
81
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/monads/result'
4
- require 'friendly_shipping/services/ups/client'
4
+ require 'friendly_shipping/http_client'
5
5
  require 'friendly_shipping/services/ups/serialize_access_request'
6
6
  require 'friendly_shipping/services/ups/serialize_city_state_lookup_request'
7
7
  require 'friendly_shipping/services/ups/serialize_address_validation_request'
8
8
  require 'friendly_shipping/services/ups/serialize_rating_service_selection_request'
9
+ require 'friendly_shipping/services/ups/parse_address_classification_response'
9
10
  require 'friendly_shipping/services/ups/parse_address_validation_response'
10
11
  require 'friendly_shipping/services/ups/parse_city_state_lookup_response'
11
12
  require 'friendly_shipping/services/ups/parse_rate_response'
@@ -34,7 +35,7 @@ module FriendlyShipping
34
35
  rates: '/ups.app/xml/Rate'
35
36
  }.freeze
36
37
 
37
- def initialize(key:, login:, password:, test: true, client: Client)
38
+ def initialize(key:, login:, password:, test: true, client: HttpClient.new)
38
39
  @key = key
39
40
  @login = login
40
41
  @password = password
@@ -50,12 +51,13 @@ module FriendlyShipping
50
51
  # @param [Physical::Shipment] location The shipment we want to get rates for
51
52
  # @return [Result<ApiResult<Array<Rate>>>] The rates returned from UPS encoded in a
52
53
  # `FriendlyShipping::ApiResult` object.
53
- def rate_estimates(shipment, _options = {})
54
+ def rate_estimates(shipment, debug: false)
54
55
  rate_request_xml = SerializeRatingServiceSelectionRequest.call(shipment: shipment)
55
56
  url = base_url + RESOURCES[:rates]
56
57
  request = FriendlyShipping::Request.new(
57
58
  url: url,
58
- body: access_request_xml + rate_request_xml
59
+ body: access_request_xml + rate_request_xml,
60
+ debug: debug
59
61
  )
60
62
 
61
63
  client.post(request).bind do |response|
@@ -69,12 +71,13 @@ module FriendlyShipping
69
71
  # `Physical::Location` object. Name and Company name are always nil, the
70
72
  # address lines will be made conformant to what UPS considers right. The returned location will
71
73
  # have the address_type set if possible.
72
- def address_validation(location)
74
+ def address_validation(location, debug: false)
73
75
  address_validation_request_xml = SerializeAddressValidationRequest.call(location: location)
74
76
  url = base_url + RESOURCES[:address_validation]
75
77
  request = FriendlyShipping::Request.new(
76
78
  url: url,
77
- body: access_request_xml + address_validation_request_xml
79
+ body: access_request_xml + address_validation_request_xml,
80
+ debug: debug
78
81
  )
79
82
 
80
83
  client.post(request).bind do |response|
@@ -82,16 +85,34 @@ module FriendlyShipping
82
85
  end
83
86
  end
84
87
 
88
+ # Classify an address.
89
+ # @param [Physical::Location] location The address we want to classify
90
+ # @return [Result<ApiResult<String>>] Either `"commercial"`, `"residential"`, or `"unknown"`
91
+ def address_classification(location, debug: false)
92
+ address_validation_request_xml = SerializeAddressValidationRequest.call(location: location)
93
+ url = base_url + RESOURCES[:address_validation]
94
+ request = FriendlyShipping::Request.new(
95
+ url: url,
96
+ body: access_request_xml + address_validation_request_xml,
97
+ debug: debug
98
+ )
99
+
100
+ client.post(request).bind do |response|
101
+ ParseAddressClassificationResponse.call(response: response, request: request)
102
+ end
103
+ end
104
+
85
105
  # Find city and state for a given ZIP code
86
106
  # @param [Physical::Location] location A location object with country and ZIP code set
87
107
  # @return [Result<ApiResult<Array<Physical::Location>>>] The response data from UPS encoded in a
88
108
  # `Physical::Location` object. Country, City and ZIP code will be set, everything else nil.
89
- def city_state_lookup(location)
109
+ def city_state_lookup(location, debug: false)
90
110
  city_state_lookup_request_xml = SerializeCityStateLookupRequest.call(location: location)
91
111
  url = base_url + RESOURCES[:city_state_lookup]
92
112
  request = FriendlyShipping::Request.new(
93
113
  url: url,
94
- body: access_request_xml + city_state_lookup_request_xml
114
+ body: access_request_xml + city_state_lookup_request_xml,
115
+ debug: debug
95
116
  )
96
117
 
97
118
  client.post(request).bind do |response|
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FriendlyShipping
4
+ module Services
5
+ class Ups
6
+ class ParseAddressClassificationResponse
7
+ extend Dry::Monads::Result::Mixin
8
+
9
+ def self.call(request:, response:)
10
+ parsing_result = ParseXMLResponse.call(response.body, 'AddressValidationResponse')
11
+
12
+ parsing_result.bind do |xml|
13
+ address_type = xml.at('AddressClassification/Description')&.text&.downcase
14
+ Success(
15
+ FriendlyShipping::ApiResult.new(
16
+ address_type,
17
+ original_request: request,
18
+ original_response: response
19
+ )
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -40,9 +40,9 @@ module FriendlyShipping
40
40
 
41
41
  # Quote residential rates by default. If UPS doesn't know if the address is residential or
42
42
  # commercial, it will quote a residential rate by default. Even with this flag being set,
43
- # if UPS knows the address is commercial it will quote a commercial rate.
43
+ # if UPS knows the address is commercial it will often quote a commercial rate.
44
44
  #
45
- xml.ResidentialAddressIndicator
45
+ xml.ResidentialAddressIndicator unless location.commercial?
46
46
  end
47
47
  end
48
48
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'friendly_shipping/services/usps/client'
3
+ require 'friendly_shipping/http_client'
4
4
  require 'friendly_shipping/services/usps/shipping_methods'
5
5
  require 'friendly_shipping/services/usps/serialize_address_validation_request'
6
6
  require 'friendly_shipping/services/usps/serialize_city_state_lookup_request'
@@ -32,7 +32,7 @@ module FriendlyShipping
32
32
  rates: 'RateV4'
33
33
  }.freeze
34
34
 
35
- def initialize(login:, test: true, client: Client)
35
+ def initialize(login:, test: true, client: HttpClient.new)
36
36
  @login = login
37
37
  @test = test
38
38
  @client = client
@@ -57,9 +57,9 @@ module FriendlyShipping
57
57
  # @return [Result<Array<FriendlyShipping::Rate>>] When successfully parsing, an array of rates in a Success Monad.
58
58
  # When the parsing is not successful or USPS can't give us rates, a Failure monad containing something that
59
59
  # can be serialized into an error message using `to_s`.
60
- def rate_estimates(shipment, options = {})
61
- rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login, shipping_method: options[:shipping_method])
62
- request = FriendlyShipping::Request.new(url: base_url, body: "API=#{RESOURCES[:rates]}&XML=#{CGI.escape rate_request_xml}")
60
+ def rate_estimates(shipment, shipping_method: nil, debug: false)
61
+ rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login, shipping_method: shipping_method)
62
+ request = build_request(api: :rates_request, xml: rate_request_xml, debug: debug)
63
63
 
64
64
  client.post(request).bind do |response|
65
65
  ParseRateResponse.call(response: response, request: request, shipment: shipment)
@@ -72,12 +72,9 @@ module FriendlyShipping
72
72
  # `Physical::Location` object. Name and Company name are always nil, the
73
73
  # address lines will be made conformant to what USPS considers right. The returned location will
74
74
  # have the address_type set if possible.
75
- def address_validation(location)
75
+ def address_validation(location, debug: false)
76
76
  address_validation_request_xml = SerializeAddressValidationRequest.call(location: location, login: login)
77
- request = FriendlyShipping::Request.new(
78
- url: base_url,
79
- body: "API=#{RESOURCES[:address_validation]}&XML=#{CGI.escape address_validation_request_xml}"
80
- )
77
+ request = build_request(api: :address_validation, xml: address_validation_request_xml, debug: debug)
81
78
 
82
79
  client.post(request).bind do |response|
83
80
  ParseAddressValidationResponse.call(response: response, request: request)
@@ -88,12 +85,9 @@ module FriendlyShipping
88
85
  # @param [Physical::Location] location A location object with country and ZIP code set
89
86
  # @return [Result<ApiResult<Array<Physical::Location>>>] The response data from USPS encoded in a
90
87
  # `Physical::Location` object. Country, City and ZIP code will be set, everything else nil.
91
- def city_state_lookup(location)
88
+ def city_state_lookup(location, debug: false)
92
89
  city_state_lookup_request_xml = SerializeCityStateLookupRequest.call(location: location, login: login)
93
- request = FriendlyShipping::Request.new(
94
- url: base_url,
95
- body: "API=#{RESOURCES[:city_state_lookup]}&XML=#{CGI.escape city_state_lookup_request_xml}"
96
- )
90
+ request = build_request(api: :city_state_lookup, xml: city_state_lookup_request_xml, debug: debug)
97
91
 
98
92
  client.post(request).bind do |response|
99
93
  ParseCityStateLookupResponse.call(response: response, request: request)
@@ -102,6 +96,14 @@ module FriendlyShipping
102
96
 
103
97
  private
104
98
 
99
+ def build_request(api:, xml:, debug:)
100
+ FriendlyShipping::Request.new(
101
+ url: base_url,
102
+ body: "API=#{RESOURCES[api]}&XML=#{CGI.escape xml}",
103
+ debug: debug
104
+ )
105
+ end
106
+
105
107
  def base_url
106
108
  test ? TEST_URL : LIVE_URL
107
109
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FriendlyShipping
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_shipping
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Meyerhoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-14 00:00:00.000000000 Z
11
+ date: 2019-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: data_uri
@@ -149,6 +149,7 @@ files:
149
149
  - ".rubocop-relaxed.yml"
150
150
  - ".rubocop.yml"
151
151
  - ".travis.yml"
152
+ - CHANGELOG.md
152
153
  - CODE_OF_CONDUCT.md
153
154
  - Gemfile
154
155
  - LICENSE.txt
@@ -160,14 +161,15 @@ files:
160
161
  - lib/friendly_shipping.rb
161
162
  - lib/friendly_shipping/api_failure.rb
162
163
  - lib/friendly_shipping/api_result.rb
163
- - lib/friendly_shipping/bad_request.rb
164
164
  - lib/friendly_shipping/carrier.rb
165
+ - lib/friendly_shipping/http_client.rb
165
166
  - lib/friendly_shipping/label.rb
166
167
  - lib/friendly_shipping/rate.rb
167
168
  - lib/friendly_shipping/request.rb
168
169
  - lib/friendly_shipping/response.rb
169
170
  - lib/friendly_shipping/services/ship_engine.rb
170
- - lib/friendly_shipping/services/ship_engine/client.rb
171
+ - lib/friendly_shipping/services/ship_engine/bad_request.rb
172
+ - lib/friendly_shipping/services/ship_engine/bad_request_handler.rb
171
173
  - lib/friendly_shipping/services/ship_engine/parse_carrier_response.rb
172
174
  - lib/friendly_shipping/services/ship_engine/parse_label_response.rb
173
175
  - lib/friendly_shipping/services/ship_engine/parse_rate_estimate_response.rb
@@ -175,7 +177,7 @@ files:
175
177
  - lib/friendly_shipping/services/ship_engine/serialize_label_shipment.rb
176
178
  - lib/friendly_shipping/services/ship_engine/serialize_rate_estimate_request.rb
177
179
  - lib/friendly_shipping/services/ups.rb
178
- - lib/friendly_shipping/services/ups/client.rb
180
+ - lib/friendly_shipping/services/ups/parse_address_classification_response.rb
179
181
  - lib/friendly_shipping/services/ups/parse_address_validation_response.rb
180
182
  - lib/friendly_shipping/services/ups/parse_city_state_lookup_response.rb
181
183
  - lib/friendly_shipping/services/ups/parse_rate_response.rb
@@ -189,7 +191,6 @@ files:
189
191
  - lib/friendly_shipping/services/ups/shipping_methods.rb
190
192
  - lib/friendly_shipping/services/usps.rb
191
193
  - lib/friendly_shipping/services/usps/choose_package_rate.rb
192
- - lib/friendly_shipping/services/usps/client.rb
193
194
  - lib/friendly_shipping/services/usps/machinable_package.rb
194
195
  - lib/friendly_shipping/services/usps/parse_address_validation_response.rb
195
196
  - lib/friendly_shipping/services/usps/parse_city_state_lookup_response.rb
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module FriendlyShipping
6
- class BadRequest < StandardError
7
- attr_reader :rest_error, :response
8
-
9
- def initialize(rest_error)
10
- @rest_error = rest_error
11
- @response = rest_error.response
12
- super parse_json_errors || rest_error
13
- end
14
-
15
- private
16
-
17
- def parse_json_errors
18
- parsed_body = JSON.parse(response.body)
19
- messages = parsed_body.fetch('errors')&.map { |e| e.fetch('message') }
20
- messages&.join(', ')
21
- rescue JSON::ParserError, KeyError => _e
22
- nil
23
- end
24
- end
25
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/monads/result'
4
- require 'friendly_shipping/bad_request'
5
- require 'rest-client'
6
-
7
- module FriendlyShipping
8
- module Services
9
- class ShipEngine
10
- class Client
11
- extend Dry::Monads::Result::Mixin
12
- class <<self
13
- def get(request)
14
- http_response = ::RestClient.get(
15
- request.url, request.headers
16
- )
17
-
18
- Success(convert_to_friendly_response(http_response))
19
- rescue ::RestClient::Exception => e
20
- Failure(e)
21
- end
22
-
23
- def post(friendly_shipping_request)
24
- http_response = ::RestClient.post(
25
- friendly_shipping_request.url,
26
- friendly_shipping_request.body,
27
- friendly_shipping_request.headers
28
- )
29
-
30
- Success(convert_to_friendly_response(http_response))
31
- rescue ::RestClient::Exception => e
32
- if e.http_code == 400
33
- Failure(BadRequest.new(e))
34
- else
35
- Failure(e)
36
- end
37
- end
38
-
39
- def put(request)
40
- http_response = ::RestClient.put(
41
- request.url,
42
- request.body,
43
- request.headers
44
- )
45
-
46
- Success(convert_to_friendly_response(http_response))
47
- rescue ::RestClient::Exception => e
48
- if e.http_code == 400
49
- Failure(BadRequest.new(e))
50
- else
51
- Failure(e)
52
- end
53
- end
54
-
55
- private
56
-
57
- def convert_to_friendly_response(http_response)
58
- FriendlyShipping::Response.new(
59
- status: http_response.code,
60
- body: http_response.body,
61
- headers: http_response.headers
62
- )
63
- end
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/monads/result'
4
- require 'friendly_shipping/bad_request'
5
- require 'rest-client'
6
-
7
- module FriendlyShipping
8
- module Services
9
- class Ups
10
- class Client
11
- extend Dry::Monads::Result::Mixin
12
- class << self
13
- def post(friendly_shipping_request)
14
- http_response = ::RestClient.post(
15
- friendly_shipping_request.url,
16
- friendly_shipping_request.body,
17
- friendly_shipping_request.headers
18
- )
19
-
20
- Success(convert_to_friendly_response(http_response))
21
- rescue ::RestClient::Exception => e
22
- Failure(e)
23
- end
24
-
25
- private
26
-
27
- def convert_to_friendly_response(http_response)
28
- FriendlyShipping::Response.new(
29
- status: http_response.code,
30
- body: http_response.body,
31
- headers: http_response.headers
32
- )
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/monads/result'
4
- require 'friendly_shipping/bad_request'
5
- require 'rest-client'
6
-
7
- module FriendlyShipping
8
- module Services
9
- class Usps
10
- class Client
11
- extend Dry::Monads::Result::Mixin
12
- class <<self
13
- # USPS allows both GET and POST request. We're using POST here as those request
14
- # are less limited in size.
15
- def post(request)
16
- http_response = ::RestClient.post(request.url, request.body)
17
-
18
- Success(convert_to_friendly_response(http_response))
19
- rescue ::RestClient::Exception => e
20
- Failure(e)
21
- end
22
-
23
- private
24
-
25
- def convert_to_friendly_response(http_response)
26
- FriendlyShipping::Response.new(
27
- status: http_response.code,
28
- body: http_response.body,
29
- headers: http_response.headers
30
- )
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end