we_ship_client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +7 -0
  3. data/.github/workflows/gem-push.yml +33 -0
  4. data/.github/workflows/specs.yml +35 -0
  5. data/.gitignore +12 -0
  6. data/.rspec +3 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +9 -0
  9. data/Gemfile.lock +118 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +111 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/we_ship_client/client.rb +33 -0
  16. data/lib/we_ship_client/entities/address.rb +19 -0
  17. data/lib/we_ship_client/entities/base.rb +23 -0
  18. data/lib/we_ship_client/entities/order.rb +20 -0
  19. data/lib/we_ship_client/entities/order_item.rb +16 -0
  20. data/lib/we_ship_client/entities/order_items.rb +14 -0
  21. data/lib/we_ship_client/entities/process_orders_request.rb +24 -0
  22. data/lib/we_ship_client/entities/responses/order_accepted.rb +18 -0
  23. data/lib/we_ship_client/entities/responses/order_rejected.rb +17 -0
  24. data/lib/we_ship_client/entities/responses/process_orders.rb +17 -0
  25. data/lib/we_ship_client/entities/responses/proof_of_delivery.rb +18 -0
  26. data/lib/we_ship_client/entities/responses/rejected_orders.rb +17 -0
  27. data/lib/we_ship_client/entities/responses/track_order.rb +32 -0
  28. data/lib/we_ship_client/entities/responses/track_response.rb +16 -0
  29. data/lib/we_ship_client/entities/responses/tracking_item.rb +18 -0
  30. data/lib/we_ship_client/entities/track_request.rb +21 -0
  31. data/lib/we_ship_client/entities/types.rb +15 -0
  32. data/lib/we_ship_client/entities.rb +17 -0
  33. data/lib/we_ship_client/exceptions.rb +17 -0
  34. data/lib/we_ship_client/interactors/get_tracking.rb +119 -0
  35. data/lib/we_ship_client/interactors/process_orders.rb +63 -0
  36. data/lib/we_ship_client/token_client.rb +36 -0
  37. data/lib/we_ship_client/transforms/tracking_item.rb +136 -0
  38. data/lib/we_ship_client/version.rb +5 -0
  39. data/lib/we_ship_client.rb +15 -0
  40. data/we_ship_client.gemspec +33 -0
  41. metadata +186 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+ require 'we_ship_client/entities/responses/order_accepted'
5
+ require 'we_ship_client/entities/responses/rejected_orders'
6
+
7
+ module WeShipClient
8
+ module Entities
9
+ module Responses
10
+ # The response returned by `Interactors::ProcessOrders`.
11
+ class ProcessOrders < Base
12
+ attribute? :ordersHeldInGateway, Types::Strict::Array.of(OrderAccepted).optional
13
+ attribute? :rejectedorders, RejectedOrders.optional
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+
5
+ module WeShipClient
6
+ module Entities
7
+ module Responses
8
+ # The proof of delivery data of a single order.
9
+ class ProofOfDelivery < Base
10
+ attribute :additional_information, Types::Strict::String.optional.default(nil)
11
+ attribute :delivery_date, Types::Strict::String.optional.default(nil)
12
+ attribute :signature, Types::Strict::String.optional.default(nil)
13
+ attribute :signer_name, Types::Strict::String.optional.default(nil)
14
+ attribute :visual, Types::Strict::String.optional.default(nil)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+ require 'we_ship_client/entities/responses/order_rejected'
5
+
6
+ module WeShipClient
7
+ module Entities
8
+ module Responses
9
+ # Just a container for the rejected orders list.
10
+ # Note the singular "order" name that may be confusing.
11
+ class RejectedOrders < Base
12
+ attribute? :count, Types::Strict::Integer.optional
13
+ attribute :order, Types::Strict::Array.of(OrderRejected).optional
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+ require 'we_ship_client/entities/responses/proof_of_delivery'
5
+ require 'we_ship_client/entities/responses/tracking_item'
6
+
7
+ module WeShipClient
8
+ module Entities
9
+ module Responses
10
+ # The details of a single order returned by `Interactors::GetTracking`.
11
+ class TrackOrder < Base
12
+ attribute :address1, Types::Strict::String
13
+ attribute :address2, Types::Strict::String.optional
14
+ attribute :carrier, Types::Strict::String.optional
15
+ attribute :carrier_tracking_num, Types::Strict::String.optional
16
+ attribute :city, Types::Strict::String
17
+ attribute :client_ref1, Types::Strict::String
18
+ attribute :customer_code, Types::Strict::String
19
+ attribute :estimated_delivery_date, Types::Strict::String.optional
20
+ attribute :fgw_order_id, Types::Coercible::Integer.optional
21
+ attribute :internal_tracking_num, Types::Strict::String.optional
22
+ attribute :last_tracking_update, Types::Strict::String.optional
23
+ attribute :name, Types::Strict::String
24
+ attribute :postal_code, Types::Strict::String
25
+ attribute? :proof_of_delivery, ProofOfDelivery.optional
26
+ attribute :state, Types::Strict::String
27
+ attribute :tracking_items, Types::Strict::Array.of(TrackingItem)
28
+ attribute :upload_date, Types::Strict::String
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+ require 'we_ship_client/entities/responses/track_order'
5
+
6
+ module WeShipClient
7
+ module Entities
8
+ module Responses
9
+ # The response returned by `Interactors::GetTracking`.
10
+ class TrackResponse < Base
11
+ attribute? :page_num, Types::Strict::Integer
12
+ attribute :results, Types::Strict::Array.of(TrackOrder)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+
5
+ module WeShipClient
6
+ module Entities
7
+ module Responses
8
+ # A single tracking item returned by `Interactors::GetTracking`.
9
+ class TrackingItem < Base
10
+ attribute :location, Types::Strict::String.optional
11
+ attribute :message, Types::Strict::String
12
+ attribute :status_type, Types::Strict::String
13
+ attribute :tracking_item_date, Types::Strict::String.optional.default(nil)
14
+ attribute :tracking_item_id, Types::Coercible::Integer.optional.default(nil)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+
5
+ module WeShipClient
6
+ module Entities
7
+ # The request payload used by `Interactors::GetTracking`.
8
+ class TrackRequest < Base
9
+ BATCH_SIZE = 500
10
+
11
+ attribute? :order_id, Types::Strict::Array.of(Types::Strict::String)
12
+ attribute? :client_ref1, Types::Strict::Array.of(Types::Strict::String)
13
+ attribute? :carrier_tracking_num, Types::Strict::Array.of(Types::Strict::String)
14
+ attribute? :internal_tracking_num, Types::Strict::Array.of(Types::Strict::String)
15
+ attribute :customer_code, Types::Strict::Array.of(Types::Strict::String)
16
+ attribute? :status, Types::Strict::String.enum('M', 'I', 'D', 'R', 'X', 'P', 'N', 'C')
17
+ attribute :page_num, Types::Strict::Integer.default(0)
18
+ attribute :num_records, Types::Strict::Integer.default(BATCH_SIZE)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
4
+
5
+ module WeShipClient
6
+ module Entities
7
+ module Types
8
+ begin
9
+ include Dry.Types()
10
+ rescue NoMethodError # dry-types < 0.15
11
+ include Dry::Types.module
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/entities/base'
4
+ require 'we_ship_client/entities/address'
5
+ require 'we_ship_client/entities/order_item'
6
+ require 'we_ship_client/entities/order_items'
7
+ require 'we_ship_client/entities/order'
8
+ require 'we_ship_client/entities/process_orders_request'
9
+ require 'we_ship_client/entities/track_request'
10
+ require 'we_ship_client/entities/responses/order_accepted'
11
+ require 'we_ship_client/entities/responses/order_rejected'
12
+ require 'we_ship_client/entities/responses/process_orders'
13
+ require 'we_ship_client/entities/responses/proof_of_delivery'
14
+ require 'we_ship_client/entities/responses/rejected_orders'
15
+ require 'we_ship_client/entities/responses/track_order'
16
+ require 'we_ship_client/entities/responses/track_response'
17
+ require 'we_ship_client/entities/responses/tracking_item'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeShipClient
4
+ module Exceptions
5
+ class BaseError < RuntimeError
6
+ end
7
+
8
+ class AuthenticationError < BaseError
9
+ end
10
+
11
+ class NotFoundError < BaseError
12
+ end
13
+
14
+ class ServerError < BaseError
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/client'
4
+ require 'we_ship_client/exceptions'
5
+ require 'we_ship_client/entities'
6
+ require 'we_ship_client/transforms/tracking_item'
7
+
8
+ module WeShipClient
9
+ module Interactors
10
+ # Class to interact with the /track API endpoint.
11
+ class GetTracking
12
+ ERRONEOUS_EXCEPTION_MSGS = [
13
+ 'RQA Mapping',
14
+ 'OQA Mapping',
15
+ 'SML Mapping',
16
+ 'Out for Delivery'
17
+ ].freeze
18
+
19
+ # @param auth_token [String] A token generated by TokenClient
20
+ # @param track_request [Entities::TrackRequest] The request payload
21
+ # @param timeout [Integer,nil] The request timeout
22
+ def initialize(auth_token:, track_request:, timeout: nil)
23
+ @auth_token = auth_token
24
+ @request = track_request
25
+ @timeout = timeout
26
+ end
27
+
28
+ # Get the tracking response.
29
+ #
30
+ # @raise [Exceptions::AuthenticationError] If the status code is 401
31
+ # @raise [Exceptions::ServerError] If the response is blank
32
+ # @return [Entities::Responses::TrackResponse] The tracking response
33
+ def call
34
+ response = client.http_client.post(
35
+ "#{client.base_url}/track",
36
+ data: request.to_hash,
37
+ headers: { Authorization: "JWT #{auth_token}" }
38
+ )
39
+ handle_exception(response) unless response.status == 200
40
+ json_response = JSON.parse(response.body, symbolize_names: true)
41
+ handle_exception(response) if json_response[:results].nil?
42
+ filter_results(json_response)
43
+ modified_response(response_class.new(json_response))
44
+ end
45
+
46
+ private
47
+
48
+ attr_accessor :auth_token, :request, :timeout
49
+
50
+ def handle_exception(response)
51
+ WeShipClient.logger.info(
52
+ "[WeShipClient::Interactors::GetTracking] [EXCEPTION] #{response}"
53
+ )
54
+
55
+ http_errors = {
56
+ 401 => WeShipClient::Exceptions::AuthenticationError
57
+ }
58
+
59
+ raise http_errors[response.status], response.body if http_errors.key?(response.status)
60
+
61
+ raise WeShipClient::Exceptions::ServerError, response.body
62
+ end
63
+
64
+ def response_class
65
+ WeShipClient::Entities::Responses::TrackResponse
66
+ end
67
+
68
+ def client
69
+ @client ||= WeShipClient::Client.new(timeout: timeout)
70
+ end
71
+
72
+ def filter_results(response) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
73
+ # filters out tracking items with empty message
74
+ # filters out tracking items with empty tracking_item_date and tracking_item_id
75
+ response[:results].each do |track_order|
76
+ next unless track_order[:tracking_items]
77
+
78
+ track_order[:tracking_items] = track_order[:tracking_items].select do |item|
79
+ valid_tracking_item?(item)
80
+ end
81
+ end
82
+ end
83
+
84
+ def valid_tracking_item?(item)
85
+ # either date or id must be present
86
+ (item[:tracking_item_date].present? || item[:tracking_item_id].present?) &&
87
+ # message must be present
88
+ item[:message].present? &&
89
+ # does not have a erroneous exception message
90
+ !(item[:status_type] == 'X' && erroneous_exception_msg?(item[:message]))
91
+ end
92
+
93
+ def erroneous_exception_msg?(item_message)
94
+ ERRONEOUS_EXCEPTION_MSGS.any? do |msg|
95
+ item_message.downcase.start_with?(msg.downcase)
96
+ end
97
+ end
98
+
99
+ def modified_response(response)
100
+ response.results.each do |track_order|
101
+ track_order&.tracking_items&.map do |item|
102
+ next if item.tracking_item_date.nil? && item.tracking_item_id.nil?
103
+
104
+ tracking_item_transform_class.new.call(
105
+ tracking_item: item,
106
+ state: track_order.state
107
+ )
108
+ end
109
+ end
110
+
111
+ response
112
+ end
113
+
114
+ def tracking_item_transform_class
115
+ WeShipClient::Transforms::TrackingItem
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/client'
4
+ require 'we_ship_client/entities'
5
+ require 'we_ship_client/exceptions'
6
+
7
+ module WeShipClient
8
+ module Interactors
9
+ # Class to interact with the /process_orders API endpoint.
10
+ class ProcessOrders
11
+ # @param auth_token [String] A token generated by TokenClient
12
+ # @param process_orders_request [Entities::ProcessOrdersRequest] The request payload
13
+ def initialize(auth_token:, process_orders_request:)
14
+ @auth_token = auth_token
15
+ @process_orders_request = process_orders_request
16
+ end
17
+
18
+ # Sends orders to be processed.
19
+ #
20
+ # @raise [Exceptions::AuthenticationError] If the status code is 401
21
+ # @raise [Exceptions::ServerError] If the request is not successful
22
+ # @return [Entities::Responses::ProcessOrders] The response
23
+ def call
24
+ json_response = client.http_client.post(
25
+ "#{client.base_url}/process_orders",
26
+ data: process_orders_request.to_h,
27
+ headers: {
28
+ Authorization: "JWT #{auth_token}"
29
+ }
30
+ )
31
+
32
+ handle_exception(json_response) unless json_response.status == 200
33
+ response_hash = JSON.parse(json_response.body, symbolize_names: true)
34
+ response_class.new(response_hash[:response])
35
+ end
36
+
37
+ private
38
+
39
+ attr_accessor :auth_token, :process_orders_request
40
+
41
+ def handle_exception(response)
42
+ WeShipClient.logger.info(
43
+ "[WeShipClient::Interactors::ProcessOrders] [EXCEPTION] #{response}"
44
+ )
45
+
46
+ case response.status
47
+ when 401
48
+ raise WeShipClient::Exceptions::AuthenticationError, response.body
49
+ else
50
+ raise WeShipClient::Exceptions::ServerError, response.body
51
+ end
52
+ end
53
+
54
+ def client
55
+ @client ||= WeShipClient::Client.new
56
+ end
57
+
58
+ def response_class
59
+ WeShipClient::Entities::Responses::ProcessOrders
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/client'
4
+ require 'we_ship_client/exceptions'
5
+
6
+ module WeShipClient
7
+ class TokenClient < Client
8
+ # @raise [Exceptions::AuthenticationError] If status is 401
9
+ # @raise [Exceptions::ServerError] If status is not 200
10
+ # @return [String] The access token
11
+ def generate_access_token
12
+ response = http_client.post(
13
+ "#{base_url}/token",
14
+ data: {
15
+ username: ENV['WE_SHIP_USERNAME'],
16
+ password: ENV['WE_SHIP_PASSWORD']
17
+ }
18
+ )
19
+
20
+ raise_token_generation_exception(response) unless response.status == 200
21
+
22
+ JSON.parse(response.body)['access_token']
23
+ end
24
+
25
+ private
26
+
27
+ def raise_token_generation_exception(response)
28
+ case response.status
29
+ when 401
30
+ raise WeShipClient::Exceptions::AuthenticationError, response.body
31
+ else
32
+ raise WeShipClient::Exceptions::ServerError, response.body
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/time_with_zone'
4
+
5
+ module WeShipClient
6
+ module Transforms
7
+ # A class that modifies/converts/removes some tracking items data to match our needs.
8
+ # If you want to use your custom logic, create another class and set it on
9
+ # `Interactors::GetTracking#tracking_item_transform_class`
10
+ class TrackingItem
11
+ # Following array of messages mean the status is a delivery exception regardless
12
+ # of what the actual status_type is received from weship
13
+ OVERRIDE_DELIVERED = [
14
+ 'Delivered, Signed by 85',
15
+ 'Delivered, Signed by no answer at door',
16
+ 'Delivered, Signed by LOST'
17
+ ].freeze
18
+ OVERRIDE_DELIVERY_EXCEPTION = ['48'].freeze
19
+ OVERRIDE_STATUS_TO_DELIVERY_EXCEPTION = ['No One Avail Sig Required'].freeze
20
+ OVERRIDE_STATUS_TO_RETURNED = [
21
+ 'Delivered, Signed by RETURN',
22
+ 'Delivered, Signed by 3 att',
23
+ 'Delivered, Signed by RTS'
24
+ ].freeze
25
+ OVERRIDE_STATUS_TO_MANIFEST = ['Electronically Transmitted'].freeze
26
+ DUPLICATE_DELIMITER = '----'
27
+ DELIVERY_ATTEMPTED_MESSAGE = 'Delivery attempt was made'
28
+ RETURNED_MESSAGE = 'Return to Sender'
29
+ UNDELIVERABLE_MESSAGE = 'undeliverable'
30
+ OUT_FOR_DELIVERY_MESSAGE = 'Out for Delivery'
31
+
32
+ ET_STATES = %w[CT DE FL GA IN ME MD MA MI NH NJ NY NC OH PA RI SC VT VA WV].freeze
33
+ CT_STATES = %w[AL AR IL IA KS KY LA MN MS MO NE ND OK SD TN TX WI].freeze
34
+ MT_STATES = %w[AZ CO ID MT NM UT WY].freeze
35
+ PT_STATES = %w[CA NV OR WA].freeze
36
+
37
+ def call(tracking_item:, state:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
38
+ if should_override_delivered_to_exception?(tracking_item)
39
+ tracking_item.status_type.replace('X')
40
+ tracking_item.message.replace(DELIVERY_ATTEMPTED_MESSAGE)
41
+ elsif should_override_delivered_to_returned?(tracking_item)
42
+ tracking_item.status_type.replace('R')
43
+ tracking_item.message.replace(RETURNED_MESSAGE)
44
+ elsif should_override_delivery_exception_message?(tracking_item)
45
+ tracking_item.message.replace(DELIVERY_ATTEMPTED_MESSAGE)
46
+ elsif should_override_status_to_exception?(tracking_item)
47
+ tracking_item.status_type.replace('X')
48
+ elsif should_override_out_for_delivery_status?(tracking_item)
49
+ tracking_item.status_type.replace('I')
50
+ elsif should_override_exception_to_manifest?(tracking_item)
51
+ tracking_item.status_type.replace('M')
52
+ end
53
+
54
+ # if there is a extraneous driver related message text, override to simpler message
55
+ tracking_item.message.replace('With driver') if should_override_driver_details?(tracking_item)
56
+ if should_remove_delimeters?(tracking_item)
57
+ # remove duplicates from message
58
+ tracking_item.message.replace(tracking_item.message.split(DUPLICATE_DELIMITER)[0])
59
+ end
60
+ # weship datetime is local carrier time, so convert it to UTC
61
+ convert_date_to_utc(tracking_item, state)
62
+ end
63
+
64
+ def should_override_exception_to_manifest?(tracking_item)
65
+ tracking_item.status_type == 'X' &&
66
+ OVERRIDE_STATUS_TO_MANIFEST.any? do |message|
67
+ tracking_item.message.downcase.start_with?(message.downcase)
68
+ end
69
+ end
70
+
71
+ def should_override_delivered_to_exception?(tracking_item)
72
+ tracking_item.status_type == 'D' &&
73
+ [
74
+ OVERRIDE_DELIVERED.any? { |message| tracking_item.message.downcase.start_with?(message.downcase) },
75
+ tracking_item.message.downcase.include?(UNDELIVERABLE_MESSAGE.downcase)
76
+ ].any?
77
+ end
78
+
79
+ def should_override_delivered_to_returned?(tracking_item)
80
+ tracking_item.status_type == 'D' &&
81
+ OVERRIDE_STATUS_TO_RETURNED.any? do |message|
82
+ tracking_item.message.downcase.start_with?(message.downcase)
83
+ end
84
+ end
85
+
86
+ def should_override_delivery_exception_message?(tracking_item)
87
+ tracking_item.status_type == 'X' &&
88
+ OVERRIDE_DELIVERY_EXCEPTION.any? do |message|
89
+ tracking_item.message.downcase.start_with?(message.downcase)
90
+ end
91
+ end
92
+
93
+ def should_override_status_to_exception?(tracking_item)
94
+ OVERRIDE_STATUS_TO_DELIVERY_EXCEPTION.any? do |message|
95
+ tracking_item.message.downcase.start_with?(message.downcase)
96
+ end
97
+ end
98
+
99
+ def should_remove_delimeters?(tracking_item)
100
+ tracking_item.message.index(DUPLICATE_DELIMITER)&.positive?
101
+ end
102
+
103
+ def should_override_out_for_delivery_status?(tracking_item)
104
+ tracking_item.status_type == 'X' &&
105
+ tracking_item.message.downcase.include?(OUT_FOR_DELIVERY_MESSAGE.downcase)
106
+ end
107
+
108
+ def should_override_driver_details?(tracking_item)
109
+ tracking_item.message.downcase.index('driver:')
110
+ end
111
+
112
+ def convert_date_to_utc(tracking_item, state)
113
+ date = tracking_item.tracking_item_date
114
+ if date.present?
115
+ timezone = ActiveSupport::TimeZone.new(us_timezone_from(state))
116
+ utc_date = timezone.local_to_utc(DateTime.parse(date)).to_s
117
+ tracking_item.tracking_item_date.replace(utc_date)
118
+ end
119
+ end
120
+
121
+ def us_timezone_from(state)
122
+ if ET_STATES.include?(state)
123
+ 'Eastern Time (US & Canada)'
124
+ elsif CT_STATES.include?(state)
125
+ 'Central Time (US & Canada)'
126
+ elsif MT_STATES.include?(state)
127
+ 'Mountain Time (US & Canada)'
128
+ elsif PT_STATES.include?(state)
129
+ 'Pacific Time (US & Canada)'
130
+ else # default to est
131
+ 'Eastern Time (US & Canada)'
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeShipClient
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'we_ship_client/client'
4
+ require 'we_ship_client/entities'
5
+ require 'we_ship_client/exceptions'
6
+ require 'we_ship_client/token_client'
7
+ require 'we_ship_client/interactors/get_tracking'
8
+ require 'we_ship_client/interactors/process_orders'
9
+ require 'we_ship_client/transforms/tracking_item'
10
+
11
+ module WeShipClient
12
+ def self.logger
13
+ defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'lib/we_ship_client/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.required_ruby_version = '>= 2.6'
5
+ spec.name = 'we_ship_client'
6
+ spec.version = WeShipClient::VERSION
7
+ spec.authors = ['Juul Labs, Inc.']
8
+ spec.email = ['opensource@juul.com']
9
+
10
+ spec.summary = "API client for We Ship Express V2."
11
+ spec.description = spec.summary
12
+ spec.homepage = 'https://github.com/JuulLabs-OSS/we_ship_client'
13
+
14
+ spec.metadata['homepage_uri'] = spec.homepage
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'activesupport'
26
+ spec.add_dependency 'dry-struct', '>= 0.6', '< 1.5'
27
+ spec.add_dependency 'dry-types'
28
+ spec.add_dependency 'loogi_http', '~> 1.0'
29
+
30
+ spec.add_development_dependency 'stub_env'
31
+ spec.add_development_dependency 'vcr'
32
+ spec.add_development_dependency 'webmock'
33
+ end