instant_quote 1.5.1 → 1.7.0

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: cfe1f8cd80aa58f3cf222f4e1c98dab3e6a50b2b846a95eadf13585c5c5e1760
4
- data.tar.gz: 0defb7c7587d624e0a83ca0ee879ab455aa0520b4c6111f10f7b6f3156d9f70e
3
+ metadata.gz: 8db818dd852db347c9e654ed2b793a4e4b8674f94064c3319698fb71a969027b
4
+ data.tar.gz: f07a944bffca0e76a445adb2527d1ce74d159cd64c758c0ad8f091525c7724c0
5
5
  SHA512:
6
- metadata.gz: 2d152de9006e7c94ab6ebb1ae3d5b818fe79f42118c63b12890d659472d718bde2c8e5e86d4f08ed0d16379acd603398ff4f4d22327aaab3621d0fde78a0a8f8
7
- data.tar.gz: 46162c30f56ebceabe2ffc035a837ea46f7a169816249024abd2f38e29d228a51c63af70b280b43909aad7f874375a3d8c7a237c9216c858fab716646f6c916e
6
+ metadata.gz: 2252d3dd9d5be7b52c4866c4fd19cca78d504c54219cd116ad4f35aa642705fe78de677cbfbdfe42a84ee76ed74d3835aea4f41643b81b5b026fad8c35673c68
7
+ data.tar.gz: ef828f5ac596f3ccd9a8d2da53b4ff0ce078d7a4b46605968c0e8419451e79fd3af4c5371433bab70ff8a32678fbe5da070244b67f5839cfa4aa79bfe2d1fed8
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.1)
4
+ instant_quote (1.7.0)
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.0)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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',
@@ -54,6 +50,16 @@ module InstantQuote
54
50
  ],
55
51
  type: 'select'
56
52
  },
53
+ {
54
+ name: 'send_cash_from_credit_facility_to_bank',
55
+ label: 'Do you require the ability to send cash from your credit facility to your bank?',
56
+ required: true,
57
+ choices: {
58
+ 'Yes' => 'True',
59
+ 'No' => 'False'
60
+ },
61
+ type: 'select'
62
+ },
57
63
  {
58
64
  name: 'applicant_consent',
59
65
  label: 'Yes, I give my consent for my data to be shared',
@@ -61,15 +67,16 @@ module InstantQuote
61
67
  type: 'checkbox'
62
68
  }
63
69
  ]
70
+ # rubocop:enable Layout/LineLength
64
71
 
65
72
  # Creates the quote with the information from instant connection
66
- def get_quote(params)
73
+ def get_quote(params, _connection)
67
74
  response = ::CapitalOnTap::Application.create(params)
68
75
 
69
76
  # CoT responses can be 200 OK but still have an error!
70
77
  raise_error(response) unless response.success? && !response.error
71
78
 
72
- response.body.dig(:result)
79
+ response.body[:result]
73
80
  end
74
81
 
75
82
  # Get the status for a given application ID
@@ -87,7 +94,7 @@ module InstantQuote
87
94
  # :links=>{:self=>'/api/v1/Applications/B247T62', :parent=>nil},
88
95
  # :metaData=>{}
89
96
  # }
90
- def get_status(application_id)
97
+ def get_status(application_id, _connection)
91
98
  response = ::CapitalOnTap::Application.status(application_id)
92
99
 
93
100
  raise_error(response) unless response.success?
@@ -96,15 +103,10 @@ module InstantQuote
96
103
  end
97
104
 
98
105
  # The link is always the same for Capital On Tap
99
- def get_link(_application_id)
106
+ def get_link(_connection)
100
107
  'https://account.capitalontap.com/apps/portal/login/'
101
108
  end
102
109
 
103
- # Capital On Tap doesn't need an approval request
104
- def get_approval(_application_id)
105
- true
106
- end
107
-
108
110
  private
109
111
 
110
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 \
@@ -63,7 +58,7 @@ module InstantQuote
63
58
  ]
64
59
 
65
60
  # Creates the iwoca customer and returns the state key from the response.
66
- def get_quote(params)
61
+ def get_quote(params, _connection)
67
62
  response = ::Iwoca::Quote.create_customer(params)
68
63
 
69
64
  return response.data[:state_key] if response.success?
@@ -72,7 +67,7 @@ module InstantQuote
72
67
  end
73
68
 
74
69
  # Get the current status for the created customer.
75
- def get_status(state_key)
70
+ def get_status(state_key, _connection)
76
71
  response = ::Iwoca::Quote.credit_facility_status(state_key)
77
72
 
78
73
  raise_error(response) unless response.success?
@@ -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?
@@ -89,7 +86,7 @@ module InstantQuote
89
86
  raise_error(response)
90
87
  end
91
88
 
92
- def get_approval(state_key)
89
+ def get_approval(state_key, _connection)
93
90
  response = ::Iwoca::Quote.approval(state_key)
94
91
  response.success?
95
92
  end
@@ -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',
@@ -101,7 +96,7 @@ module InstantQuote
101
96
  }
102
97
  ]
103
98
 
104
- def get_quote(params)
99
+ def get_quote(params, _connection)
105
100
  response = ::Optimum::Decision.create(params)
106
101
 
107
102
  return response.data[:opportunityId] if response.success?
@@ -109,7 +104,7 @@ module InstantQuote
109
104
  raise_error(response)
110
105
  end
111
106
 
112
- def get_status(application_id)
107
+ def get_status(application_id, _connection)
113
108
  response = ::Optimum::Decision.update(application_id)
114
109
 
115
110
  raise_error(response) unless response.success?
@@ -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
@@ -98,7 +98,8 @@ module InstantQuote
98
98
  def additional_questions
99
99
  [
100
100
  preferred_monthly_repayment_option_question,
101
- turnover_percentage_question
101
+ turnover_percentage_question,
102
+ send_cash_from_credit_facility_to_bank_question
102
103
  ]
103
104
  end
104
105
 
@@ -110,12 +111,20 @@ module InstantQuote
110
111
  question_from_extra_info('turnover_percentage_after_feb_2020', '20')
111
112
  end
112
113
 
114
+ def send_cash_from_credit_facility_to_bank_question
115
+ question_from_extra_info('send_cash_from_credit_facility_to_bank', '23')
116
+ end
117
+
113
118
  def question_from_extra_info(name, question_type)
114
119
  field = Adapters::CapitalOnTap.additional_fields.find do |f|
115
- f[:name] == name
120
+ f[:name].to_sym == name.to_sym
116
121
  end
117
122
 
118
- { 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
+ }
119
128
  end
120
129
  end
121
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,61 @@
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 sweep
29
+ first_option[:sweep]
30
+ end
31
+
32
+ def fee
33
+ fee = first_option[:fee].to_f
34
+
35
+ Money.new(fee * 100).format
36
+ end
37
+
38
+ def loan_amount
39
+ amount = first_option[:loanAmount].to_f
40
+
41
+ Money.new(amount * 100).format
42
+ end
43
+
44
+ def funded_amount
45
+ amount = first_option[:fundedAmount].to_f
46
+
47
+ Money.new(amount * 100).format
48
+ end
49
+
50
+ private
51
+
52
+ def first_option
53
+ @first_option ||= status_hash[:loanOptions].find { |opt| opt[:fundedAmount].positive? }
54
+ end
55
+
56
+ def status_hash
57
+ data.presence || { loanOptions: [] }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InstantQuote
4
- VERSION = '1.5.1'
4
+ VERSION = '1.7.0'
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.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rikas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-22 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
  - - ">="