Module: EasyPost::Util

Defined in:
lib/easypost/util.rb

Overview

Client Library helper functions

Class Method Summary collapse

Class Method Details

.get_lowest_object_rate(easypost_object, carriers = [], services = [], rates_key = 'rates') ⇒ Object

Gets the lowest rate of an EasyPost object such as a Shipment, Order, or Pickup. You can exclude by having ‘’!‘` as the first element of your optional filter lists



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/easypost/util.rb', line 9

def self.get_lowest_object_rate(easypost_object, carriers = [], services = [], rates_key = 'rates')
  lowest_rate = nil

  carriers = EasyPost::InternalUtilities.normalize_string_list(carriers)
  negative_carriers = []
  carriers_copy = carriers.clone
  carriers_copy.each do |carrier|
    if carrier[0, 1] == '!'
      negative_carriers << carrier[1..]
      carriers.delete(carrier)
    end
  end

  services = EasyPost::InternalUtilities.normalize_string_list(services)
  negative_services = []
  services_copy = services.clone
  services_copy.each do |service|
    if service[0, 1] == '!'
      negative_services << service[1..]
      services.delete(service)
    end
  end

  easypost_object.send(rates_key).each do |rate|
    rate_carrier = rate.carrier.downcase
    if carriers.size.positive? && !carriers.include?(rate_carrier)
      next
    end
    if negative_carriers.size.positive? && negative_carriers.include?(rate_carrier)
      next
    end

    rate_service = rate.service.downcase
    if services.size.positive? && !services.include?(rate_service)
      next
    end
    if negative_services.size.positive? && negative_services.include?(rate_service)
      next
    end

    if lowest_rate.nil? || rate.rate.to_f < lowest_rate.rate.to_f
      lowest_rate = rate
    end
  end

  if lowest_rate.nil?
    raise EasyPost::Errors::FilteringError.new(EasyPost::Constants::NO_MATCHING_RATES)
  end

  lowest_rate
end

.get_lowest_smart_rate(smart_rates, delivery_days, delivery_accuracy) ⇒ Object

Get the lowest SmartRate from a list of SmartRate.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/easypost/util.rb', line 121

def self.get_lowest_smart_rate(smart_rates, delivery_days, delivery_accuracy)
  valid_delivery_accuracy_values = Set[
    'percentile_50',
    'percentile_75',
    'percentile_85',
    'percentile_90',
    'percentile_95',
    'percentile_97',
    'percentile_99',
  ]
  lowest_smart_rate = nil

  unless valid_delivery_accuracy_values.include?(delivery_accuracy.downcase)
    raise EasyPost::Errors::InvalidParameterError.new(
      'delivery_accuracy',
      "Must be one of: #{valid_delivery_accuracy_values}",
    )
  end

  smart_rates.each do |rate|
    next if rate['time_in_transit'][delivery_accuracy] > delivery_days.to_i

    if lowest_smart_rate.nil? || rate['rate'].to_f < lowest_smart_rate['rate'].to_f
      lowest_smart_rate = rate
    end
  end

  if lowest_smart_rate.nil?
    raise EasyPost::Errors::FilteringError.new(EasyPost::Constants::NO_MATCHING_RATES)
  end

  lowest_smart_rate
end

.get_lowest_stateless_rate(stateless_rates, carriers = [], services = []) ⇒ Object

Gets the lowest stateless rate. You can exclude by having ‘’!‘` as the first element of your optional filter lists



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/easypost/util.rb', line 63

def self.get_lowest_stateless_rate(stateless_rates, carriers = [], services = [])
  lowest_rate = nil

  carriers = EasyPost::InternalUtilities.normalize_string_list(carriers)
  negative_carriers = []
  carriers_copy = carriers.clone
  carriers_copy.each do |carrier|
    if carrier[0, 1] == '!'
      negative_carriers << carrier[1..]
      carriers.delete(carrier)
    end
  end

  services = EasyPost::InternalUtilities.normalize_string_list(services)
  negative_services = []
  services_copy = services.clone
  services_copy.each do |service|
    if service[0, 1] == '!'
      negative_services << service[1..]
      services.delete(service)
    end
  end

  stateless_rates.each do |rate|
    rate_carrier = rate.carrier.downcase
    if carriers.size.positive? && !carriers.include?(rate_carrier)
      next
    end
    if negative_carriers.size.positive? && negative_carriers.include?(rate_carrier)
      next
    end

    rate_service = rate.service.downcase
    if services.size.positive? && !services.include?(rate_service)
      next
    end
    if negative_services.size.positive? && negative_services.include?(rate_service)
      next
    end

    if lowest_rate.nil? || rate.rate.to_f < lowest_rate.rate.to_f
      lowest_rate = rate
    end
  end

  if lowest_rate.nil?
    raise EasyPost::Errors::FilteringError.new(EasyPost::Constants::NO_MATCHING_RATES)
  end

  lowest_rate
end

.receive_event(raw_input) ⇒ Object

Converts a raw webhook event into an EasyPost object.



116
117
118
# File 'lib/easypost/util.rb', line 116

def self.receive_event(raw_input)
  EasyPost::InternalUtilities::Json.convert_json_to_object(JSON.parse(raw_input), EasyPost::Models::EasyPostObject)
end

.validate_webhook(event_body, headers, webhook_secret) ⇒ Object

Validate a webhook by comparing the HMAC signature header sent from EasyPost to your shared secret. If the signatures do not match, an error will be raised signifying the webhook either did not originate from EasyPost or the secrets do not match. If the signatures do match, the ‘event_body` will be returned as JSON.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/easypost/util.rb', line 159

def self.validate_webhook(event_body, headers, webhook_secret)
  easypost_hmac_signature = headers['X-Hmac-Signature']

  if easypost_hmac_signature.nil?
    raise EasyPost::Errors::SignatureVerificationError.new(EasyPost::Constants::WEBHOOK_MISSING_SIGNATURE)
  end

  encoded_webhook_secret = webhook_secret.unicode_normalize(:nfkd).encode('utf-8')

  expected_signature = OpenSSL::HMAC.hexdigest('sha256', encoded_webhook_secret, event_body)
  digest = "hmac-sha256-hex=#{expected_signature}"
  unless digest == easypost_hmac_signature
    raise EasyPost::Errors::SignatureVerificationError.new(EasyPost::Constants::WEBHOOK_SIGNATURE_MISMATCH)
  end

  JSON.parse(event_body)
end