onfido 0.15.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -49
  3. data/.travis.yml +3 -10
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +2 -1
  7. data/README.md +46 -172
  8. data/lib/onfido.rb +4 -3
  9. data/lib/onfido/api.rb +21 -15
  10. data/lib/onfido/errors/connection_error.rb +2 -0
  11. data/lib/onfido/errors/onfido_error.rb +2 -0
  12. data/lib/onfido/errors/request_error.rb +2 -0
  13. data/lib/onfido/errors/server_error.rb +2 -0
  14. data/lib/onfido/options.rb +38 -0
  15. data/lib/onfido/resource.rb +44 -61
  16. data/lib/onfido/resources/address.rb +3 -4
  17. data/lib/onfido/resources/applicant.rb +8 -6
  18. data/lib/onfido/resources/check.rb +15 -19
  19. data/lib/onfido/resources/document.rb +13 -11
  20. data/lib/onfido/resources/extraction.rb +11 -0
  21. data/lib/onfido/resources/live_photo.rb +11 -14
  22. data/lib/onfido/resources/live_video.rb +7 -8
  23. data/lib/onfido/resources/report.rb +10 -9
  24. data/lib/onfido/resources/sdk_token.rb +5 -5
  25. data/lib/onfido/resources/webhook.rb +15 -11
  26. data/lib/onfido/version.rb +3 -1
  27. data/onfido.gemspec +10 -12
  28. data/spec/integrations/address_spec.rb +5 -2
  29. data/spec/integrations/applicant_spec.rb +29 -42
  30. data/spec/integrations/check_spec.rb +28 -69
  31. data/spec/integrations/document_spec.rb +22 -19
  32. data/spec/integrations/extraction_spec.rb +23 -0
  33. data/spec/integrations/live_photo_spec.rb +18 -15
  34. data/spec/integrations/live_video_spec.rb +13 -11
  35. data/spec/integrations/report_spec.rb +16 -13
  36. data/spec/integrations/resource_spec.rb +93 -0
  37. data/spec/integrations/sdk_token_spec.rb +10 -6
  38. data/spec/integrations/webhook_spec.rb +56 -37
  39. data/spec/onfido/api_spec.rb +14 -25
  40. data/spec/onfido/connection_error_spec.rb +4 -2
  41. data/spec/onfido/options_spec.rb +39 -0
  42. data/spec/onfido/request_error_spec.rb +4 -2
  43. data/spec/spec_helper.rb +3 -5
  44. data/spec/support/fake_onfido_api.rb +77 -88
  45. data/spec/support/fixtures/applicant.json +21 -42
  46. data/spec/support/fixtures/check.json +4 -4
  47. data/spec/support/fixtures/checks.json +4 -4
  48. data/spec/support/fixtures/document.json +2 -2
  49. data/spec/support/fixtures/documents.json +8 -8
  50. data/spec/support/fixtures/extraction.json +23 -0
  51. data/spec/support/fixtures/live_photo.json +3 -3
  52. data/spec/support/fixtures/live_photos.json +6 -6
  53. data/spec/support/fixtures/live_video.json +3 -3
  54. data/spec/support/fixtures/live_videos.json +4 -4
  55. data/spec/support/fixtures/report.json +4 -4
  56. data/spec/support/fixtures/reports.json +8 -8
  57. data/spec/support/fixtures/webhook.json +6 -5
  58. data/spec/support/fixtures/webhooks.json +17 -12
  59. metadata +25 -65
  60. data/Rakefile +0 -1
  61. data/lib/onfido/configuration.rb +0 -47
  62. data/lib/onfido/null_logger.rb +0 -5
  63. data/lib/onfido/resources/report_type_group.rb +0 -11
  64. data/spec/integrations/exceptions_spec.rb +0 -74
  65. data/spec/integrations/report_type_group_spec.rb +0 -19
  66. data/spec/onfido/resource_spec.rb +0 -137
  67. data/spec/onfido_spec.rb +0 -84
  68. data/spec/support/fixtures/check_with_expanded_reports.json +0 -30
  69. data/spec/support/fixtures/checks_with_expanded_reports.json +0 -34
  70. data/spec/support/fixtures/report_type_group.json +0 -25
  71. data/spec/support/fixtures/report_type_groups.json +0 -30
data/lib/onfido/api.rb CHANGED
@@ -1,47 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class API
3
- def initialize(options = {})
4
- @api_key = options[:api_key]
5
+ def initialize(api_key:, region:, **extra_options)
6
+ @options = Onfido::Options.new(api_key: api_key, region: region, **extra_options)
5
7
  end
6
8
 
7
9
  def applicant
8
- Onfido::Applicant.new(@api_key)
10
+ @applicant ||= Onfido::Applicant.new(options)
9
11
  end
10
12
 
11
13
  def check
12
- Onfido::Check.new(@api_key)
14
+ @check ||= Onfido::Check.new(options)
13
15
  end
14
16
 
15
17
  def document
16
- Onfido::Document.new(@api_key)
18
+ @document ||= Onfido::Document.new(options)
17
19
  end
18
20
 
19
21
  def live_photo
20
- Onfido::LivePhoto.new(@api_key)
22
+ @live_photo ||= Onfido::LivePhoto.new(options)
21
23
  end
22
24
 
23
25
  def live_video
24
- Onfido::LiveVideo.new(@api_key)
26
+ @live_video ||= Onfido::LiveVideo.new(options)
25
27
  end
26
28
 
27
29
  def report
28
- Onfido::Report.new(@api_key)
29
- end
30
-
31
- def report_type_group
32
- Onfido::ReportTypeGroup.new(@api_key)
30
+ @report ||= Onfido::Report.new(options)
33
31
  end
34
32
 
35
33
  def sdk_token
36
- Onfido::SdkToken.new(@api_key)
34
+ @sdk_token ||= Onfido::SdkToken.new(options)
37
35
  end
38
36
 
39
37
  def webhook
40
- Onfido::Webhook.new(@api_key)
38
+ @webhook ||= Onfido::Webhook.new(options)
41
39
  end
42
40
 
43
41
  def address
44
- Onfido::Address.new(@api_key)
42
+ @address ||= Onfido::Address.new(options)
45
43
  end
44
+
45
+ def extraction
46
+ @extraction ||= Onfido::Extraction.new(options)
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :options
46
52
  end
47
53
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class ConnectionError < OnfidoError
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class OnfidoError < StandardError
3
5
  attr_accessor :response_code, :response_body
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class RequestError < OnfidoError
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class ServerError < OnfidoError
3
5
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onfido
4
+ class Options
5
+ REGIONS = %w[eu us ca].freeze
6
+
7
+ def initialize(api_key:, region:, open_timeout: 10, read_timeout: 30, unknown_api_url: nil)
8
+ @api_key = api_key
9
+ @region = region.to_s.downcase
10
+ @open_timeout = open_timeout
11
+ @read_timeout = read_timeout
12
+ @unknown_api_url = unknown_api_url
13
+
14
+ raise "Unknown region #{@region}" unless REGIONS.include?(@region)
15
+ end
16
+
17
+ def rest_client
18
+ @rest_client ||= RestClient::Resource.new(
19
+ base_url,
20
+ read_timeout: read_timeout,
21
+ open_timeout: open_timeout,
22
+ headers: {
23
+ 'Authorization' => "Token token=#{api_key}",
24
+ 'Accept' => 'application/json',
25
+ 'User-Agent' => "onfido-ruby/#{Onfido::VERSION}"
26
+ }
27
+ )
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :api_key, :open_timeout, :read_timeout
33
+
34
+ def base_url
35
+ @unknown_api_url || "https://api.#{@region}.onfido.com/v3.1/"
36
+ end
37
+ end
38
+ end
@@ -1,59 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
- class Resource
3
- VALID_HTTP_METHODS = %i(get post put delete).freeze
4
+ class Resource # rubocop:todo Metrics/ClassLength
5
+ VALID_HTTP_METHODS = %i[get post put delete].freeze
4
6
  REQUEST_TIMEOUT_HTTP_CODE = 408
5
7
 
6
- def initialize(api_key = nil)
7
- @api_key = api_key || Onfido.api_key
8
+ def initialize(options)
9
+ @rest_client = options.rest_client
8
10
  end
9
11
 
10
- def url_for(path)
11
- Onfido.endpoint + path
12
- end
12
+ private
13
13
 
14
- VALID_HTTP_METHODS.each do |method|
15
- define_method method do |*args|
16
- make_request(
17
- method: method.to_sym,
18
- url: args.first.fetch(:url),
19
- payload: build_query(args.first.fetch(:payload, {}))
20
- )
21
- end
14
+ attr_reader :rest_client
15
+
16
+ def get(path:)
17
+ handle_request { rest_client[path].get }
22
18
  end
23
19
 
24
- private
20
+ def post(path:, payload: nil)
21
+ handle_request { rest_client[path].post(payload) }
22
+ end
25
23
 
26
- def make_request(options)
27
- url = options.fetch(:url)
28
- payload = options.fetch(:payload)
29
- method = options.fetch(:method)
24
+ def put(path:, payload: nil)
25
+ handle_request { rest_client[path].put(payload) }
26
+ end
30
27
 
31
- request_options = {
32
- url: url,
33
- payload: payload,
34
- method: method,
35
- headers: headers,
36
- open_timeout: Onfido.open_timeout,
37
- timeout: Onfido.read_timeout
38
- }
28
+ def delete(path:)
29
+ handle_request { rest_client[path].delete }
30
+ end
39
31
 
40
- response = RestClient::Request.execute(request_options)
32
+ def handle_request
33
+ response = yield
41
34
 
42
35
  # response should be parsed only when there is a response expected
43
36
  parse(response) unless response.code == 204 # no_content
44
- rescue RestClient::ExceptionWithResponse => error
45
- if error.response && !timeout_response?(error.response)
46
- handle_api_error(error.response)
37
+ rescue RestClient::ExceptionWithResponse => e
38
+ if e.response && !timeout_response?(e.response)
39
+ handle_api_error(e.response)
47
40
  else
48
- handle_restclient_error(error, url)
41
+ handle_restclient_error(e)
49
42
  end
50
- rescue RestClient::Exception, Errno::ECONNREFUSED => error
51
- handle_restclient_error(error, url)
43
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
44
+ handle_restclient_error(e)
52
45
  end
53
46
 
54
47
  def parse(response)
55
48
  content_type = response.headers[:content_type]
56
- if content_type && content_type.include?("application/json")
49
+ if content_type&.include?('application/json')
57
50
  JSON.parse(response.body.to_s)
58
51
  else
59
52
  response.body
@@ -66,13 +59,6 @@ module Onfido
66
59
  response.code.to_i == REQUEST_TIMEOUT_HTTP_CODE
67
60
  end
68
61
 
69
- def headers
70
- {
71
- 'Authorization' => "Token token=#{@api_key}",
72
- 'Accept' => "application/json"
73
- }
74
- end
75
-
76
62
  # There seems to be a serialization issue with the HTTP client
77
63
  # which does not serialize the payload properly.
78
64
  # Have a look here https://gist.github.com/PericlesTheo/cb35139c57107ab3c84a
@@ -88,12 +74,12 @@ module Onfido
88
74
  def handle_api_error(response)
89
75
  parsed_response = parse(response)
90
76
 
91
- general_api_error(response.code, response.body) unless parsed_response["error"]
77
+ general_api_error(response.code, response.body) unless parsed_response['error']
92
78
 
93
79
  error_class = response.code.to_i >= 500 ? ServerError : RequestError
94
80
 
95
81
  raise error_class.new(
96
- parsed_response["error"]['message'],
82
+ parsed_response['error']['message'],
97
83
  response_code: response.code,
98
84
  response_body: response.body
99
85
  )
@@ -110,46 +96,43 @@ module Onfido
110
96
  )
111
97
  end
112
98
 
113
- def handle_restclient_error(error, url)
99
+ def handle_restclient_error(error) # rubocop:todo Metrics/MethodLength
114
100
  connection_message =
115
- "Please check your internet connection and try again. " \
116
- "If this problem persists, you should let us know at info@onfido.com."
101
+ 'Please check your internet connection and try again. ' \
102
+ 'If this problem persists, you should let us know at info@onfido.com.'
117
103
 
118
104
  message =
119
105
  case error
120
106
  when RestClient::RequestTimeout
121
- "Could not connect to Onfido (#{url}). #{connection_message}"
107
+ "Could not connect to Onfido . #{connection_message}"
122
108
 
123
109
  when RestClient::ServerBrokeConnection
124
- "The connection to the server (#{url}) broke before the " \
125
- "request completed. #{connection_message}"
110
+ "The connection to the server broke before the request completed. #{connection_message}"
126
111
 
127
112
  when RestClient::SSLCertificateNotVerified
128
113
  "Could not verify Onfido's SSL certificate. Please make sure " \
129
- "that your network is not intercepting certificates. " \
130
- "(Try going to #{Onfido.endpoint} in your browser.) " \
131
- "If this problem persists, let us know at info@onfido.com."
114
+ 'that your network is not intercepting certificates. '
132
115
 
133
116
  when SocketError
134
- "Unexpected error when trying to connect to Onfido. " \
135
- "You may be seeing this message because your DNS is not working. " \
117
+ 'Unexpected error when trying to connect to Onfido. ' \
118
+ 'You may be seeing this message because your DNS is not working. ' \
136
119
  "To check, try running 'host onfido.com' from the command line."
137
120
 
138
121
  else
139
- "Unexpected error communicating with Onfido. " \
140
- "If this problem persists, let us know at info@onfido.com."
122
+ 'Unexpected error communicating with Onfido. ' \
123
+ 'If this problem persists, let us know at info@onfido.com.'
141
124
  end
142
125
 
143
126
  full_message = message + "\n\n(Network error: #{error.message})"
144
127
 
145
- raise ConnectionError.new(full_message)
128
+ raise ConnectionError, full_message
146
129
  end
147
130
 
148
131
  def validate_file!(file)
149
132
  return if file.respond_to?(:read) && file.respond_to?(:path)
150
133
 
151
- raise ArgumentError, "File must be a `File`-like object which responds to " \
152
- "`#read` and `#path`"
134
+ raise ArgumentError, 'File must be a `File`-like object which responds to ' \
135
+ '`#read` and `#path`'
153
136
  end
154
137
  end
155
138
  end
@@ -1,10 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class Address < Resource
3
5
  def all(postcode)
4
- get(
5
- url: url_for('addresses/pick'),
6
- payload: { postcode: postcode.delete(' ') }
7
- )
6
+ get(path: "addresses/pick?postcode=#{postcode.delete(' ')}")
8
7
  end
9
8
  end
10
9
  end
@@ -1,27 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class Applicant < Resource
3
5
  def create(payload)
4
- post(url: url_for('applicants'), payload: payload)
6
+ post(path: 'applicants', payload: payload)
5
7
  end
6
8
 
7
9
  def update(applicant_id, payload)
8
- put(url: url_for("applicants/#{applicant_id}"), payload: payload)
10
+ put(path: "applicants/#{applicant_id}", payload: payload)
9
11
  end
10
12
 
11
13
  def destroy(applicant_id)
12
- delete(url: url_for("applicants/#{applicant_id}"))
14
+ delete(path: "applicants/#{applicant_id}")
13
15
  end
14
16
 
15
17
  def find(applicant_id)
16
- get(url: url_for("applicants/#{applicant_id}"))
18
+ get(path: "applicants/#{applicant_id}")
17
19
  end
18
20
 
19
21
  def all(page: 1, per_page: 20)
20
- get(url: url_for("applicants?page=#{page}&per_page=#{per_page}"))
22
+ get(path: "applicants?page=#{page}&per_page=#{per_page}")
21
23
  end
22
24
 
23
25
  def restore(applicant_id)
24
- post(url: url_for("applicants/#{applicant_id}/restore"))
26
+ post(path: "applicants/#{applicant_id}/restore")
25
27
  end
26
28
  end
27
29
  end
@@ -1,32 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class Check < Resource
3
- def create(applicant_id, payload)
4
- post(
5
- url: url_for("applicants/#{applicant_id}/checks"),
6
- payload: payload
7
- )
8
- end
5
+ def create(applicant_id:, report_names:, **payload)
6
+ payload[:applicant_id] = applicant_id
7
+ payload[:report_names] = report_names
9
8
 
10
- def find(applicant_id, check_id, expand: nil)
11
- querystring = "&expand=#{expand}" if expand
12
- get(url: url_for("applicants/#{applicant_id}/checks/#{check_id}?" \
13
- "#{querystring}"))
9
+ post(path: 'checks', payload: payload)
14
10
  end
15
11
 
16
- def find_by_url(url, expand: nil)
17
- url_path = url.sub(%r/^https\:\/\/api\.onfido\.com\/v2\//, '')
18
- querystring = "&expand=#{expand}" if expand
19
- get(url: url_for("#{url_path}?#{querystring}"))
12
+ def find(check_id)
13
+ get(path: "checks/#{check_id}")
20
14
  end
21
15
 
22
- def all(applicant_id, page: 1, per_page: 20, expand: nil)
23
- querystring = "page=#{page}&per_page=#{per_page}"
24
- querystring += "&expand=#{expand}" if expand
25
- get(url: url_for("applicants/#{applicant_id}/checks?#{querystring}"))
16
+ def all(applicant_id)
17
+ get(path: "checks?applicant_id=#{applicant_id}")
26
18
  end
27
19
 
28
20
  def resume(check_id)
29
- post(url: url_for("checks/#{check_id}/resume"))
21
+ post(path: "checks/#{check_id}/resume")
22
+ end
23
+
24
+ def download(check_id)
25
+ get(path: "checks/#{check_id}/download")
30
26
  end
31
27
  end
32
28
  end
@@ -1,26 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Onfido
2
4
  class Document < Resource
3
5
  # with open-uri the file can be a link or an actual file
4
6
 
5
- def create(applicant_id, payload)
6
- validate_file!(payload.fetch(:file))
7
+ def create(applicant_id:, file:, type:, **payload)
8
+ validate_file!(file)
9
+ payload[:applicant_id] = applicant_id
10
+ payload[:file] = file
11
+ payload[:type] = type
7
12
 
8
- post(
9
- url: url_for("applicants/#{applicant_id}/documents"),
10
- payload: payload
11
- )
13
+ post(path: 'documents', payload: payload)
12
14
  end
13
15
 
14
- def find(applicant_id, document_id)
15
- get(url: url_for("applicants/#{applicant_id}/documents/#{document_id}"))
16
+ def find(document_id)
17
+ get(path: "documents/#{document_id}")
16
18
  end
17
19
 
18
- def download(applicant_id, document_id)
19
- get(url: url_for("applicants/#{applicant_id}/documents/#{document_id}/download"))
20
+ def download(document_id)
21
+ get(path: "documents/#{document_id}/download")
20
22
  end
21
23
 
22
24
  def all(applicant_id)
23
- get(url: url_for("applicants/#{applicant_id}/documents"))
25
+ get(path: "documents?applicant_id=#{applicant_id}")
24
26
  end
25
27
  end
26
28
  end