friendly_shipping 0.3.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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