instant_quote 1.5.2 → 1.6.1

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
  SHA256:
3
- metadata.gz: 37edc1ac5cee7a85fb58c677061f8b6aab1fedaf6dbc472cfb92801b25f4fdbc
4
- data.tar.gz: 35cad5c9bcbb00750364ed51abb3367163b949a7bbe444c76404d37602868a6c
3
+ metadata.gz: c46963e390b87e65c4e598adb90a56dcbc9c8d4e9757816b0734af085cd098f6
4
+ data.tar.gz: abfa20a481247b1ddc9aa3abbda4de1c858bbf3d50584ef9fe7ac904c1f6a948
5
5
  SHA512:
6
- metadata.gz: 71b11b24f38de7282f55cf63820d9b1091bb0873781070dc2af58747055305cbb3029afb433adc2401e95a812040743649f9b398bc0c6fa338923377bacda8ef
7
- data.tar.gz: 520a2a85e693db1002e333db97e0b5549ef7632de054d6992e9d36ca0cfb72ed5f9fdab7a580ea6b6ce1b24097880825bd70d433f4cc946997c2187d5502833d
6
+ metadata.gz: 3e42e95e2474ad1e4b04838ace2371e409ce811b4c129fe7cc65375536febd85816338b4b943f5c3133ad19d3643817d4a466fe636c843a1fb36e6542c2c63b5
7
+ data.tar.gz: 26257fb6401faf86be51b53dc7c0ebacd787f8ebb187cdf330e646318cea2a113dd7131871eac972f28272b9f363dfeb209a6f2f3a330a080c526ac36c1c0c38
data/.rubocop.yml CHANGED
@@ -1,3 +1,14 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+
5
+ require:
6
+ - rubocop-rake
7
+ - rubocop-rspec
8
+
9
+ Gemspec/RequiredRubyVersion:
10
+ Enabled: false
11
+
1
12
  Layout/LineLength:
2
13
  Max: 100
3
14
 
@@ -24,3 +35,6 @@ Style/HashTransformKeys:
24
35
 
25
36
  Style/HashTransformValues:
26
37
  Enabled: true
38
+
39
+ RSpec/MultipleExpectations:
40
+ Enabled: false
data/.travis.yml CHANGED
@@ -5,3 +5,6 @@ cache: bundler
5
5
  rvm:
6
6
  - 2.6.3
7
7
  before_install: gem install bundler -v 2.0.2
8
+ script:
9
+ - bundle exec rspec --fail-fast
10
+ - bundle exec rubocop
data/Gemfile.lock CHANGED
@@ -1,47 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- instant_quote (1.5.2)
4
+ instant_quote (1.6.1)
5
5
  activesupport
6
6
  capital_on_tap
7
7
  iwoca
8
8
  money
9
9
  optimum
10
10
  phonelib
11
+ youlend
11
12
 
12
13
  GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
15
- activesupport (6.0.3.4)
16
+ activesupport (6.1.1)
16
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
- i18n (>= 0.7, < 2)
18
- minitest (~> 5.1)
19
- tzinfo (~> 1.1)
20
- zeitwerk (~> 2.2, >= 2.2.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
21
22
  addressable (2.7.0)
22
23
  public_suffix (>= 2.0.2, < 5.0)
23
- ast (2.4.0)
24
- byebug (11.1.1)
24
+ ast (2.4.2)
25
+ byebug (11.1.3)
25
26
  capital_on_tap (1.0.0)
26
27
  addressable
27
28
  faraday
28
29
  faraday_middleware
29
30
  multi_json
30
31
  rainbow
31
- coderay (1.1.2)
32
- concurrent-ruby (1.1.7)
33
- crack (0.4.3)
34
- safe_yaml (~> 1.0.0)
35
- diff-lcs (1.3)
36
- docile (1.3.2)
37
- dotenv (2.7.5)
38
- faraday (1.1.0)
32
+ coderay (1.1.3)
33
+ concurrent-ruby (1.1.8)
34
+ crack (0.4.5)
35
+ rexml
36
+ diff-lcs (1.4.4)
37
+ docile (1.3.5)
38
+ dotenv (2.7.6)
39
+ faraday (1.3.0)
40
+ faraday-net_http (~> 1.0)
39
41
  multipart-post (>= 1.2, < 3)
40
42
  ruby2_keywords
43
+ faraday-net_http (1.0.1)
41
44
  faraday_middleware (1.0.0)
42
45
  faraday (~> 1.0)
43
46
  hashdiff (1.0.1)
44
- i18n (1.8.5)
47
+ i18n (1.8.8)
45
48
  concurrent-ruby (~> 1.0)
46
49
  iwoca (1.0.0)
47
50
  addressable
@@ -49,10 +52,9 @@ GEM
49
52
  faraday_middleware
50
53
  multi_json
51
54
  rainbow
52
- jaro_winkler (1.5.4)
53
55
  method_source (1.0.0)
54
- minitest (5.14.2)
55
- money (6.13.8)
56
+ minitest (5.14.3)
57
+ money (6.14.0)
56
58
  i18n (>= 0.6.4, <= 2)
57
59
  multi_json (1.15.0)
58
60
  multipart-post (2.1.1)
@@ -62,57 +64,72 @@ GEM
62
64
  faraday_middleware
63
65
  multi_json
64
66
  rainbow
65
- parallel (1.19.1)
66
- parser (2.7.0.5)
67
- ast (~> 2.4.0)
68
- phonelib (0.6.46)
69
- pry (0.13.0)
67
+ parallel (1.20.1)
68
+ parser (3.0.0.0)
69
+ ast (~> 2.4.1)
70
+ phonelib (0.6.48)
71
+ pry (0.13.1)
70
72
  coderay (~> 1.1)
71
73
  method_source (~> 1.0)
72
74
  pry-byebug (3.9.0)
73
75
  byebug (~> 11.0)
74
76
  pry (~> 0.13.0)
75
- public_suffix (4.0.3)
77
+ public_suffix (4.0.6)
76
78
  rainbow (3.0.0)
77
79
  rake (10.5.0)
80
+ regexp_parser (2.0.3)
78
81
  rexml (3.2.4)
79
- rspec (3.9.0)
80
- rspec-core (~> 3.9.0)
81
- rspec-expectations (~> 3.9.0)
82
- rspec-mocks (~> 3.9.0)
83
- rspec-core (3.9.1)
84
- rspec-support (~> 3.9.1)
85
- rspec-expectations (3.9.1)
82
+ rspec (3.10.0)
83
+ rspec-core (~> 3.10.0)
84
+ rspec-expectations (~> 3.10.0)
85
+ rspec-mocks (~> 3.10.0)
86
+ rspec-core (3.10.1)
87
+ rspec-support (~> 3.10.0)
88
+ rspec-expectations (3.10.1)
86
89
  diff-lcs (>= 1.2.0, < 2.0)
87
- rspec-support (~> 3.9.0)
88
- rspec-mocks (3.9.1)
90
+ rspec-support (~> 3.10.0)
91
+ rspec-mocks (3.10.2)
89
92
  diff-lcs (>= 1.2.0, < 2.0)
90
- rspec-support (~> 3.9.0)
91
- rspec-support (3.9.2)
92
- rubocop (0.80.1)
93
- jaro_winkler (~> 1.5.1)
93
+ rspec-support (~> 3.10.0)
94
+ rspec-support (3.10.2)
95
+ rubocop (1.9.1)
94
96
  parallel (~> 1.10)
95
- parser (>= 2.7.0.1)
97
+ parser (>= 3.0.0.0)
96
98
  rainbow (>= 2.2.2, < 4.0)
99
+ regexp_parser (>= 1.8, < 3.0)
97
100
  rexml
101
+ rubocop-ast (>= 1.2.0, < 2.0)
98
102
  ruby-progressbar (~> 1.7)
99
- unicode-display_width (>= 1.4.0, < 1.7)
100
- ruby-progressbar (1.10.1)
101
- ruby2_keywords (0.0.2)
102
- safe_yaml (1.0.5)
103
- simplecov (0.18.5)
103
+ unicode-display_width (>= 1.4.0, < 3.0)
104
+ rubocop-ast (1.4.1)
105
+ parser (>= 2.7.1.5)
106
+ rubocop-rake (0.5.1)
107
+ rubocop
108
+ rubocop-rspec (2.2.0)
109
+ rubocop (~> 1.0)
110
+ rubocop-ast (>= 1.1.0)
111
+ ruby-progressbar (1.11.0)
112
+ ruby2_keywords (0.0.4)
113
+ simplecov (0.21.2)
104
114
  docile (~> 1.1)
105
115
  simplecov-html (~> 0.11)
106
- simplecov-html (0.12.2)
107
- thread_safe (0.3.6)
108
- tzinfo (1.2.7)
109
- thread_safe (~> 0.1)
110
- unicode-display_width (1.6.1)
111
- webmock (3.8.3)
116
+ simplecov_json_formatter (~> 0.1)
117
+ simplecov-html (0.12.3)
118
+ simplecov_json_formatter (0.1.2)
119
+ tzinfo (2.0.4)
120
+ concurrent-ruby (~> 1.0)
121
+ unicode-display_width (2.0.0)
122
+ webmock (3.11.2)
112
123
  addressable (>= 2.3.6)
113
124
  crack (>= 0.3.2)
114
125
  hashdiff (>= 0.4.0, < 2.0.0)
115
- zeitwerk (2.4.1)
126
+ youlend (1.1.3)
127
+ addressable
128
+ faraday
129
+ faraday_middleware
130
+ multi_json
131
+ rainbow
132
+ zeitwerk (2.4.2)
116
133
 
117
134
  PLATFORMS
118
135
  ruby
@@ -125,8 +142,10 @@ DEPENDENCIES
125
142
  rake (~> 10.0)
126
143
  rspec (~> 3.0)
127
144
  rubocop
145
+ rubocop-rake
146
+ rubocop-rspec
128
147
  simplecov
129
148
  webmock
130
149
 
131
150
  BUNDLED WITH
132
- 2.1.4
151
+ 2.2.5
data/bin/console CHANGED
@@ -24,6 +24,12 @@ Iwoca.configure do |config|
24
24
  config.webhook_signature = ENV['IWOCA_WEBHOOK_SIGNATURE']
25
25
  end
26
26
 
27
+ Youlend.configure do |config|
28
+ config.client_id = ENV['YOULEND_CLIENT_ID']
29
+ config.client_secret = ENV['YOULEND_CLIENT_SECRET']
30
+ config.debug = true
31
+ end
32
+
27
33
  send(:include, InstantQuote)
28
34
 
29
35
  require 'pry'
@@ -6,6 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
  require 'instant_quote/version'
7
7
 
8
8
  Gem::Specification.new do |spec|
9
+ spec.required_ruby_version = '>= 2.5.0'
9
10
  spec.name = 'instant_quote'
10
11
  spec.version = InstantQuote::VERSION
11
12
  spec.authors = ['rikas']
@@ -32,13 +33,16 @@ Gem::Specification.new do |spec|
32
33
  spec.add_development_dependency 'rake', '~> 10.0'
33
34
  spec.add_development_dependency 'rspec', '~> 3.0'
34
35
  spec.add_development_dependency 'rubocop'
36
+ spec.add_development_dependency 'rubocop-rake'
37
+ spec.add_development_dependency 'rubocop-rspec'
35
38
  spec.add_development_dependency 'simplecov'
36
39
  spec.add_development_dependency 'webmock'
37
40
 
38
41
  spec.add_dependency 'activesupport'
39
42
  spec.add_dependency 'capital_on_tap'
40
43
  spec.add_dependency 'iwoca'
41
- spec.add_dependency 'optimum'
42
44
  spec.add_dependency 'money'
45
+ spec.add_dependency 'optimum'
43
46
  spec.add_dependency 'phonelib'
47
+ spec.add_dependency 'youlend'
44
48
  end
data/lib/instant_quote.rb CHANGED
@@ -17,6 +17,7 @@ module InstantQuote
17
17
  get_link
18
18
  get_quote
19
19
  get_status
20
+ get_preapproval
20
21
  get_approval
21
22
  additional_fields
22
23
  parse_decision
@@ -26,6 +27,7 @@ module InstantQuote
26
27
  # InstantQuote.get_link(connection)
27
28
  # InstantQuote.get_quote(connection)
28
29
  # InstantQuote.get_status(connection)
30
+ # InstantQuote.get_preapproval(connection)
29
31
  # InstantQuote.get_approval(connection)
30
32
  # InstantQuote.additional_fields(connection)
31
33
  # InstantQuote.parse_decision(connection)
@@ -2,42 +2,63 @@
2
2
 
3
3
  module InstantQuote
4
4
  class Adapter
5
+ # This is one additional field that we always want to ask, since we're using it for phone
6
+ # validating the applicant.
7
+ DEFAULT_ADDIONAL_FIELDS = [
8
+ {
9
+ name: 'applicant_phone_number',
10
+ label: 'Applicant mobile phone',
11
+ required: true
12
+ }
13
+ ].freeze
14
+
5
15
  def self.additional_fields(fields = [])
6
- return (@additional_fields || []) if fields.empty?
16
+ return (@additional_fields || DEFAULT_ADDIONAL_FIELDS) if fields.empty?
17
+
18
+ @additional_fields ||= DEFAULT_ADDIONAL_FIELDS
19
+ @additional_fields += fields
20
+ end
7
21
 
8
- @additional_fields = fields
22
+ def self.get_link(connection)
23
+ new.get_link(connection)
9
24
  end
10
25
 
11
- def self.get_link(application_id)
12
- new.get_link(application_id)
26
+ def self.get_quote(params, connection = nil)
27
+ new.get_quote(params, connection)
13
28
  end
14
29
 
15
- def self.get_quote(params)
16
- new.get_quote(params)
30
+ def self.get_status(application_id, connection = nil)
31
+ new.get_status(application_id, connection)
17
32
  end
18
33
 
19
- def self.get_status(application_id)
20
- new.get_status(application_id)
34
+ def self.get_preapproval(params, connection = nil)
35
+ new.get_preapproval(params, connection)
21
36
  end
22
37
 
23
- def self.get_approval(application_id)
24
- new.get_approval(application_id)
38
+ def self.get_approval(application_id, connection = nil)
39
+ new.get_approval(application_id, connection)
25
40
  end
26
41
 
27
- def get_link(_application_id)
42
+ def get_link(_application_id, _connection = nil)
28
43
  raise NotImplementedError, "You must implement '#{self.class.name}#get_link'"
29
44
  end
30
45
 
31
- def get_quote(_params)
46
+ def get_quote(_params, _connection = nil)
32
47
  raise NotImplementedError, "You must implement '#{self.class.name}#get_quote'"
33
48
  end
34
49
 
35
- def get_status(_application_id)
50
+ def get_status(_application_id, _connection = nil)
36
51
  raise NotImplementedError, "You must implement '#{self.class.name}#get_status'"
37
52
  end
38
53
 
39
- def get_approval(_application_id)
40
- raise NotImplementedError, "You must implement '#{self.class.name}#get_approval"
54
+ # Default for pre approval will be true, override it in the adapter class if needed.
55
+ def get_preapproval(_params, _connection = nil)
56
+ true
57
+ end
58
+
59
+ # Default for approval will be true, override it in the adapter class if needed.
60
+ def get_approval(_application_id, _connection = nil)
61
+ true
41
62
  end
42
63
  end
43
64
  end
@@ -36,17 +36,23 @@ module InstantQuote
36
36
  end
37
37
 
38
38
  def get_link
39
- adapter.get_link(connection.external_id)
39
+ adapter.get_link(connection)
40
40
  end
41
41
 
42
42
  def get_quote
43
43
  params = translator.translate(connection)
44
44
 
45
- adapter.get_quote(params)
45
+ adapter.get_quote(params, connection)
46
46
  end
47
47
 
48
48
  def get_status
49
- adapter.get_status(connection.external_id)
49
+ adapter.get_status(connection.external_id, connection)
50
+ end
51
+
52
+ def get_preapproval
53
+ params = translator.translate(connection)
54
+
55
+ adapter.get_preapproval(params, connection)
50
56
  end
51
57
 
52
58
  def get_approval
@@ -6,6 +6,7 @@ require 'instant_quote/decision_parsers/capital_on_tap'
6
6
  module InstantQuote
7
7
  module Adapters
8
8
  class CapitalOnTap < Adapter
9
+ # rubocop:disable Layout/LineLength
9
10
  additional_fields [
10
11
  {
11
12
  name: 'date_of_birth',
@@ -13,11 +14,6 @@ module InstantQuote
13
14
  type: 'date',
14
15
  required: true
15
16
  },
16
- {
17
- name: 'applicant_phone_number',
18
- label: 'Applicant mobile phone',
19
- required: true
20
- },
21
17
  {
22
18
  name: 'monthly_income',
23
19
  label: 'Applicant monthly income',
@@ -71,6 +67,7 @@ module InstantQuote
71
67
  type: 'checkbox'
72
68
  }
73
69
  ]
70
+ # rubocop:enable Layout/LineLength
74
71
 
75
72
  # Creates the quote with the information from instant connection
76
73
  def get_quote(params)
@@ -79,7 +76,7 @@ module InstantQuote
79
76
  # CoT responses can be 200 OK but still have an error!
80
77
  raise_error(response) unless response.success? && !response.error
81
78
 
82
- response.body.dig(:result)
79
+ response.body[:result]
83
80
  end
84
81
 
85
82
  # Get the status for a given application ID
@@ -106,15 +103,10 @@ module InstantQuote
106
103
  end
107
104
 
108
105
  # The link is always the same for Capital On Tap
109
- def get_link(_application_id)
106
+ def get_link(_connection)
110
107
  'https://account.capitalontap.com/apps/portal/login/'
111
108
  end
112
109
 
113
- # Capital On Tap doesn't need an approval request
114
- def get_approval(_application_id)
115
- true
116
- end
117
-
118
110
  private
119
111
 
120
112
  def raise_error(response)
@@ -44,11 +44,6 @@ module InstantQuote
44
44
  label: 'Country',
45
45
  type: 'country'
46
46
  },
47
- {
48
- name: 'applicant_phone_number',
49
- label: 'Applicant mobile number',
50
- required: true
51
- },
52
47
  {
53
48
  name: 'applicant_consent',
54
49
  label: "I agree to iwoca running soft credit checks on my personal information with \
@@ -81,7 +76,9 @@ module InstantQuote
81
76
  end
82
77
 
83
78
  # Gets the URL for the given state key.
84
- def get_link(state_key)
79
+ def get_link(connection)
80
+ state_key = connection.external_id
81
+
85
82
  response = ::Iwoca::Quote.login_link(state_key)
86
83
 
87
84
  return response.data[:login_link] if response.success?
@@ -7,11 +7,6 @@ module InstantQuote
7
7
  module Adapters
8
8
  class Optimum < Adapter
9
9
  additional_fields [
10
- {
11
- name: 'applicant_phone_number',
12
- label: 'Applicant mobile phone',
13
- required: true
14
- },
15
10
  {
16
11
  name: 'expected_turnover_next_12_months',
17
12
  label: 'Expected turnover next 12 months',
@@ -118,14 +113,10 @@ module InstantQuote
118
113
  end
119
114
 
120
115
  # Gets the URL for the given state key.
121
- def get_link(_)
116
+ def get_link(_connection)
122
117
  ''
123
118
  end
124
119
 
125
- def get_approval(_)
126
- true
127
- end
128
-
129
120
  private
130
121
 
131
122
  def decision_parser
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'youlend'
4
+ require 'instant_quote/decision_parsers/youlend'
5
+
6
+ module InstantQuote
7
+ module Adapters
8
+ class Youlend < Adapter
9
+ additional_fields [
10
+ {
11
+ name: 'monthly_card_revenue',
12
+ label: 'Avg. monthly revenue from card payments',
13
+ required: true,
14
+ type: 'money'
15
+ },
16
+ {
17
+ name: 'monthly_revenue_from_invoices',
18
+ label: 'Avg. monthly revenue from invoices',
19
+ required: true,
20
+ type: 'money'
21
+ },
22
+ {
23
+ name: 'applicant_consent',
24
+ label: 'Yes, I give my consent for my data to be shared',
25
+ required: true,
26
+ type: 'checkbox'
27
+ }
28
+ ]
29
+
30
+ # For Youlend we first need to do the pre_qualification and then, if we get a positive
31
+ # response we can create the lead. We need to call both endpoints here, so it might take a
32
+ # while.
33
+ def get_quote(params, connection)
34
+ response = ::Youlend::Lead.create(params)
35
+
36
+ if response.success?
37
+ connection.update(link: response.data[:signUpURL])
38
+
39
+ return response.data[:leadId]
40
+ end
41
+
42
+ raise_error(response)
43
+ end
44
+
45
+ def get_status(_lead_id, connection)
46
+ DecisionParsers::Youlend.new(connection.approval_decision_info)
47
+ end
48
+
49
+ # Gets the URL for the given state key.
50
+ def get_link(connection)
51
+ connection.link
52
+ end
53
+
54
+ # Checks if the application is eligible for a loan. If it is we need to update the
55
+ # connection's approval_decision_info right away, since it's the only place where we get it.
56
+ def get_preapproval(params, connection)
57
+ # The preapproval endpoint doesn't work with all params, we need to remove some
58
+ params.delete(:companyName)
59
+ params.delete(:monthsTrading)
60
+ params.delete(:keyContactName)
61
+ params.delete(:registeredAddress)
62
+ params.delete(:thirdPartyLeadId)
63
+ params.delete(:contactPhoneNumber)
64
+
65
+ response = ::Youlend::Prequalification.verify(params)
66
+
67
+ connection.update(approval_decision_info: response.data) if response.success?
68
+
69
+ response
70
+ end
71
+
72
+ private
73
+
74
+ def raise_error(response)
75
+ raise ApiError.new(
76
+ adapter: 'youlend',
77
+ data: response.body,
78
+ error: response.error || '[Youlend] API error'
79
+ )
80
+ end
81
+ end
82
+ end
83
+ end
@@ -8,6 +8,8 @@ module InstantQuote
8
8
  alias to_s error
9
9
 
10
10
  def initialize(params = {})
11
+ super
12
+
11
13
  @adapter = params[:adapter]
12
14
  @error = params[:error]
13
15
  @data = params[:data] || {}
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+
3
5
  module InstantQuote
6
+ # Inherit from this class to create the translation from Finpoint's app to the JSON that needs to
7
+ # be sent to the service you're integrating. You'll have access to `application` and `extra_info`
8
+ # from the application's record.
4
9
  class ConnectionTranslator
5
10
  attr_accessor :application, :extra_info
6
11
 
@@ -10,7 +15,7 @@ module InstantQuote
10
15
 
11
16
  def initialize(connection)
12
17
  @application = connection.finance_application
13
- @extra_info = connection.extra_info
18
+ @extra_info = ActiveSupport::HashWithIndifferentAccess.new(connection.extra_info)
14
19
  end
15
20
 
16
21
  def translate
@@ -117,10 +117,14 @@ module InstantQuote
117
117
 
118
118
  def question_from_extra_info(name, question_type)
119
119
  field = Adapters::CapitalOnTap.additional_fields.find do |f|
120
- f[:name] == name
120
+ f[:name].to_sym == name.to_sym
121
121
  end
122
122
 
123
- { question: field[:label], answer: extra_info[field[:name]], questionType: question_type }
123
+ {
124
+ question: field[:label],
125
+ answer: extra_info[field[:name]],
126
+ questionType: question_type
127
+ }
124
128
  end
125
129
  end
126
130
  end
@@ -78,7 +78,7 @@ module InstantQuote
78
78
  application.borrower_organisation
79
79
  end
80
80
 
81
- # rubocop:disable Metrics/AbcSize
81
+ # rubocop:disable Naming/VariableNumber
82
82
  def company_address
83
83
  {
84
84
  postcode: company.address.postcode,
@@ -88,7 +88,7 @@ module InstantQuote
88
88
  country: company.address.country
89
89
  }
90
90
  end
91
- # rubocop:enable Metrics/AbcSize
91
+ # rubocop:enable Naming/VariableNumber
92
92
 
93
93
  def person_information
94
94
  {
@@ -123,6 +123,7 @@ module InstantQuote
123
123
  }
124
124
  end
125
125
 
126
+ # rubocop:disable Naming/VariableNumber
126
127
  def person_residential_address
127
128
  {
128
129
  uid: SecureRandom.uuid,
@@ -135,6 +136,7 @@ module InstantQuote
135
136
  residential_status: home_owning_translated
136
137
  }
137
138
  end
139
+ # rubocop:enable Naming/VariableNumber
138
140
 
139
141
  def home_owning_translated
140
142
  case company.directors_home_owning.name
@@ -3,6 +3,7 @@
3
3
  module InstantQuote
4
4
  module ConnectionTranslators
5
5
  class Optimum < ConnectionTranslator
6
+ # rubocop:disable Metrics/AbcSize
6
7
  def translate
7
8
  {
8
9
  companyNumber: company&.company_number.to_s,
@@ -19,6 +20,7 @@ module InstantQuote
19
20
  quotationOnly: false
20
21
  }
21
22
  end
23
+ # rubocop:enable Metrics/AbcSize
22
24
 
23
25
  private
24
26
 
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstantQuote
4
+ module ConnectionTranslators
5
+ CURRENCY = 'GBP'
6
+
7
+ class Youlend < ConnectionTranslator
8
+ # rubocop:disable Metrics/AbcSize
9
+ def translate
10
+ {
11
+ companyName: company.name,
12
+ companyType: company_type_translated,
13
+ monthsTrading: company.total_months_in_business,
14
+ keyContactName: primary_user_name,
15
+ thirdPartyLeadId: application.code,
16
+ financialData: financial_data,
17
+ countryISOCode: 'GBR',
18
+ loanCurrencyISOCode: CURRENCY,
19
+ thirdPartyMerchantId: company.id,
20
+ thirdPartyCustomerId: application&.primary_user&.id,
21
+ registeredAddress: company_address,
22
+ contactPhoneNumber: extra_info[:applicant_phone_number],
23
+ monthlyCardRevenue: extra_info[:monthly_card_revenue].to_i,
24
+ contactEmailAddress: application&.primary_user&.email # optional
25
+ }
26
+ end
27
+ # rubocop:enable Metrics/AbcSize
28
+
29
+ private
30
+
31
+ def financial_data
32
+ {
33
+ monthlyCardRevenue: extra_info[:monthly_card_revenue].to_i,
34
+ monthlyRevenueFromInvoices: extra_info[:monthly_revenue_from_invoices].to_i,
35
+ paymentData: payment_data
36
+ }
37
+ end
38
+
39
+ def payment_data
40
+ (1..12).to_a.map do |month|
41
+ formatted_month = format('%02<month>d', month: month)
42
+
43
+ {
44
+ paymentDate: "#{Date.today.year - 1}-#{formatted_month}-01",
45
+ amount: total_amount,
46
+ currencyISOCode: CURRENCY
47
+ }
48
+ end
49
+ end
50
+
51
+ def total_amount
52
+ @total_amount ||= begin
53
+ card = extra_info[:monthly_card_revenue].to_i || 0
54
+ invoices = extra_info[:monthly_revenue_from_invoices].to_i || 0
55
+
56
+ card + invoices
57
+ end
58
+ end
59
+
60
+ def company_address
61
+ address = company.address
62
+
63
+ {
64
+ line1: address.line_1,
65
+ line2: address.line_2,
66
+ city: address.town,
67
+ region: address.town,
68
+ areaCode: address.postcode,
69
+ country: address.country
70
+ }
71
+ end
72
+
73
+ def company
74
+ @company ||= application.borrower_organisation
75
+ end
76
+
77
+ def primary_user_name
78
+ application.primary_user.name
79
+ end
80
+
81
+ def company_type_translated
82
+ case company.company_type.name
83
+ when /LTD/
84
+ 'ltd'
85
+ when /LLP/
86
+ 'llp'
87
+ when /PLC/
88
+ 'plc'
89
+ when 'Sole Trader'
90
+ 'soleTrader'
91
+ else
92
+ 'other'
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -42,7 +42,7 @@ module InstantQuote
42
42
  end
43
43
 
44
44
  def status
45
- credit_decision.dig(:status) || STATUSES[:could_not_submit]
45
+ credit_decision[:status] || STATUSES[:could_not_submit]
46
46
  end
47
47
 
48
48
  # The applicationStage can be one of the following:
@@ -52,13 +52,13 @@ module InstantQuote
52
52
  # ApprovedCredit, ApprovedPrepaid, DdSetup, AddOnSelection, DoneHavFailed,
53
53
  # DoneIdVerificationRequired, Done, Portal
54
54
  def stage
55
- data.dig(:applicationStage) || {}
55
+ data[:applicationStage] || {}
56
56
  end
57
57
 
58
58
  private
59
59
 
60
60
  def credit_decision
61
- data.dig(:creditDecision) || {}
61
+ data[:creditDecision] || {}
62
62
  end
63
63
  end
64
64
  end
@@ -91,21 +91,21 @@ module InstantQuote
91
91
 
92
92
  return 0 unless rates&.any?
93
93
 
94
- rate_info = rates.first.dig(:rates)&.first
94
+ rate_info = rates.first[:rates]&.first
95
95
 
96
96
  return 0 unless rate_info
97
97
 
98
- rate_info.dig(:rate)
98
+ rate_info[:rate]
99
99
  end
100
100
 
101
101
  def status
102
- status_hash.dig(:status) || STATUSES[:not_defined]
102
+ status_hash[:status] || STATUSES[:not_defined]
103
103
  end
104
104
 
105
105
  private
106
106
 
107
107
  def status_hash
108
- data.dig(:approval_status) || data.dig(:latest_approval_request) || {}
108
+ data[:approval_status] || data[:latest_approval_request] || {}
109
109
  end
110
110
  end
111
111
  end
@@ -6,26 +6,29 @@ module InstantQuote
6
6
  #
7
7
  # {
8
8
  # decision: {
9
- # decision: "approved",
10
- # trialPeriodMonths: 4, # trial period length
11
- # fundingLimit: 745000,
12
- # annualRenewalFee: 0,
13
- # badDebtProtection: 0.85,
14
- # cashAvailableWithinHours: 48,
15
- # discountFee: 3.35,
16
- # facility: "Factoring", # Factoring or Invoice Discounting or Confidential Invoice Discounting
17
- # prepayment: 80,
18
- # serviceFee: 1.14
9
+ # decision: "approved",
10
+ # trialPeriodMonths: 4, # trial period length
11
+ # fundingLimit: 745000,
12
+ # annualRenewalFee: 0,
13
+ # badDebtProtection: 0.85,
14
+ # cashAvailableWithinHours: 48,
15
+ # discountFee: 3.35,
16
+ # facility: "Factoring", # Factoring / Invoice Discounting / Confidential Invoice Discounting
17
+ # prepayment: 80,
18
+ # serviceFee: 1.14
19
19
  # },
20
- # opportunityId: "01234567ABCDEFGH", # the Optimum Finance opportunity ID (only supplied for successful applications that are not 'quotation only')
21
- # companyName: "COMPANY NAME LTD" # company name from the given company number - use this to verify correct company
20
+ # opportunityId: "01234567ABCDEFGH", # the Optimum Finance opportunity ID (only supplied for
21
+ # successful applications that are not 'quotation only')
22
+ #
23
+ # companyName: "COMPANY NAME LTD" # company name from the given company number - use this to
24
+ # verify correct company
22
25
  # }
23
26
 
24
27
  class Optimum < DecisionParser
25
28
  STATUSES = {
26
29
  approved: 'approved',
27
30
  declined: 'declined',
28
- referred: 'referred',
31
+ referred: 'referred'
29
32
  }.freeze
30
33
 
31
34
  LOAN_STATES = {
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstantQuote
4
+ module DecisionParsers
5
+ # Example response:
6
+ #
7
+ # {
8
+ # thirdPartyMerchantId: "b97b0834-74e5-4da6-a3df-c8dbe017fe09",
9
+ # mid: nil,
10
+ # companyName: "Test Company",
11
+ # overallCreditRiskScore: 3.5,
12
+ # overrideCreditRiskScore: 1.0,
13
+ # loanOptions: [
14
+ # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" },
15
+ # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" },
16
+ # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" }
17
+ # ]
18
+ # }
19
+ class Youlend < DecisionParser
20
+ def approved?
21
+ status_hash[:loanOptions].any?
22
+ end
23
+
24
+ def declined?
25
+ status_hash[:loanOptions].empty?
26
+ end
27
+
28
+ def amount
29
+ option = status_hash[:loanOptions].first
30
+
31
+ amount_approved = option[:fundedAmount].to_f + option[:fee].to_f
32
+
33
+ Money.new(amount_approved * 100).format
34
+ end
35
+
36
+ private
37
+
38
+ def status_hash
39
+ data.presence || { loanOptions: [] }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InstantQuote
4
- VERSION = '1.5.2'
4
+ VERSION = '1.6.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instant_quote
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - rikas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-02 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: simplecov
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -165,7 +193,7 @@ dependencies:
165
193
  - !ruby/object:Gem::Version
166
194
  version: '0'
167
195
  - !ruby/object:Gem::Dependency
168
- name: optimum
196
+ name: money
169
197
  requirement: !ruby/object:Gem::Requirement
170
198
  requirements:
171
199
  - - ">="
@@ -179,7 +207,7 @@ dependencies:
179
207
  - !ruby/object:Gem::Version
180
208
  version: '0'
181
209
  - !ruby/object:Gem::Dependency
182
- name: money
210
+ name: optimum
183
211
  requirement: !ruby/object:Gem::Requirement
184
212
  requirements:
185
213
  - - ">="
@@ -206,6 +234,20 @@ dependencies:
206
234
  - - ">="
207
235
  - !ruby/object:Gem::Version
208
236
  version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: youlend
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :runtime
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
209
251
  description: Instant quote providers gem.
210
252
  email:
211
253
  - oterosantos@gmail.com
@@ -232,17 +274,20 @@ files:
232
274
  - lib/instant_quote/adapters/iwoca.rb
233
275
  - lib/instant_quote/adapters/iwoca_cbils.rb
234
276
  - lib/instant_quote/adapters/optimum.rb
277
+ - lib/instant_quote/adapters/youlend.rb
235
278
  - lib/instant_quote/api_error.rb
236
279
  - lib/instant_quote/connection_translator.rb
237
280
  - lib/instant_quote/connection_translators/capital_on_tap.rb
238
281
  - lib/instant_quote/connection_translators/iwoca.rb
239
282
  - lib/instant_quote/connection_translators/iwoca_cbils.rb
240
283
  - lib/instant_quote/connection_translators/optimum.rb
284
+ - lib/instant_quote/connection_translators/youlend.rb
241
285
  - lib/instant_quote/decision_parser.rb
242
286
  - lib/instant_quote/decision_parsers/capital_on_tap.rb
243
287
  - lib/instant_quote/decision_parsers/iwoca.rb
244
288
  - lib/instant_quote/decision_parsers/iwoca_cbils.rb
245
289
  - lib/instant_quote/decision_parsers/optimum.rb
290
+ - lib/instant_quote/decision_parsers/youlend.rb
246
291
  - lib/instant_quote/version.rb
247
292
  - lib/instant_quote/webhooks/iwoca.rb
248
293
  homepage: https://finpoint.co.uk
@@ -257,7 +302,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
257
302
  requirements:
258
303
  - - ">="
259
304
  - !ruby/object:Gem::Version
260
- version: '0'
305
+ version: 2.5.0
261
306
  required_rubygems_version: !ruby/object:Gem::Requirement
262
307
  requirements:
263
308
  - - ">="