instant_quote 1.1.8 → 1.5.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: 2c7f021aa42714cc517f85e9828e2c34a20ba4044c4f024570338117218e68c3
4
- data.tar.gz: ba11fe1ed79bd940c613587f85481583885dd8311dd021a5cb83e3c7d9178281
3
+ metadata.gz: cfe1f8cd80aa58f3cf222f4e1c98dab3e6a50b2b846a95eadf13585c5c5e1760
4
+ data.tar.gz: 0defb7c7587d624e0a83ca0ee879ab455aa0520b4c6111f10f7b6f3156d9f70e
5
5
  SHA512:
6
- metadata.gz: 78426747755312c4618ebc70b42b51f19435b04bbc26d2385881b82a3b9113d26d6fa13af7ad97dcdb83540a20bc0a6113d854a469c501029edbd34a5c4d9c33
7
- data.tar.gz: 76448f87df25db208b0d5c25d7e68df33f0c6c78ff7fcf58fa5cd041b50d21ae378ba5b328023155c5c95609412d4bfec98b352be79576f735f393b101626b20
6
+ metadata.gz: 2d152de9006e7c94ab6ebb1ae3d5b818fe79f42118c63b12890d659472d718bde2c8e5e86d4f08ed0d16379acd603398ff4f4d22327aaab3621d0fde78a0a8f8
7
+ data.tar.gz: 46162c30f56ebceabe2ffc035a837ea46f7a169816249024abd2f38e29d228a51c63af70b280b43909aad7f874375a3d8c7a237c9216c858fab716646f6c916e
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ source 'https://rubygems.org'
6
6
  #
7
7
  # gem 'capital_on_tap', path: '../capital-on-tap'
8
8
  # gem 'iwoca', path: '../iwoca'
9
+ # gem 'optimum', path: '../optimum'
9
10
 
10
11
  # Specify your gem's dependencies in instant-quotes.gemspec
11
12
  gemspec
@@ -1,17 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- instant_quote (1.1.8)
4
+ instant_quote (1.5.1)
5
5
  activesupport
6
6
  capital_on_tap
7
7
  iwoca
8
8
  money
9
+ optimum
9
10
  phonelib
10
11
 
11
12
  GEM
12
13
  remote: https://rubygems.org/
13
14
  specs:
14
- activesupport (6.0.3.2)
15
+ activesupport (6.0.3.4)
15
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
17
  i18n (>= 0.7, < 2)
17
18
  minitest (~> 5.1)
@@ -28,14 +29,15 @@ GEM
28
29
  multi_json
29
30
  rainbow
30
31
  coderay (1.1.2)
31
- concurrent-ruby (1.1.6)
32
+ concurrent-ruby (1.1.7)
32
33
  crack (0.4.3)
33
34
  safe_yaml (~> 1.0.0)
34
35
  diff-lcs (1.3)
35
36
  docile (1.3.2)
36
37
  dotenv (2.7.5)
37
- faraday (1.0.1)
38
+ faraday (1.1.0)
38
39
  multipart-post (>= 1.2, < 3)
40
+ ruby2_keywords
39
41
  faraday_middleware (1.0.0)
40
42
  faraday (~> 1.0)
41
43
  hashdiff (1.0.1)
@@ -49,15 +51,21 @@ GEM
49
51
  rainbow
50
52
  jaro_winkler (1.5.4)
51
53
  method_source (1.0.0)
52
- minitest (5.14.1)
54
+ minitest (5.14.2)
53
55
  money (6.13.8)
54
56
  i18n (>= 0.6.4, <= 2)
55
57
  multi_json (1.15.0)
56
58
  multipart-post (2.1.1)
59
+ optimum (1.1.0)
60
+ addressable
61
+ faraday
62
+ faraday_middleware
63
+ multi_json
64
+ rainbow
57
65
  parallel (1.19.1)
58
66
  parser (2.7.0.5)
59
67
  ast (~> 2.4.0)
60
- phonelib (0.6.44)
68
+ phonelib (0.6.46)
61
69
  pry (0.13.0)
62
70
  coderay (~> 1.1)
63
71
  method_source (~> 1.0)
@@ -90,6 +98,7 @@ GEM
90
98
  ruby-progressbar (~> 1.7)
91
99
  unicode-display_width (>= 1.4.0, < 1.7)
92
100
  ruby-progressbar (1.10.1)
101
+ ruby2_keywords (0.0.2)
93
102
  safe_yaml (1.0.5)
94
103
  simplecov (0.18.5)
95
104
  docile (~> 1.1)
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency 'activesupport'
39
39
  spec.add_dependency 'capital_on_tap'
40
40
  spec.add_dependency 'iwoca'
41
+ spec.add_dependency 'optimum'
41
42
  spec.add_dependency 'money'
42
43
  spec.add_dependency 'phonelib'
43
44
  end
@@ -13,11 +13,6 @@ module InstantQuote
13
13
  type: 'date',
14
14
  required: true
15
15
  },
16
- {
17
- name: 'business_landline',
18
- label: 'Business landline',
19
- required: true
20
- },
21
16
  {
22
17
  name: 'applicant_phone_number',
23
18
  label: 'Applicant mobile phone',
@@ -25,7 +20,7 @@ module InstantQuote
25
20
  },
26
21
  {
27
22
  name: 'monthly_income',
28
- label: 'Applicat monthly income',
23
+ label: 'Applicant monthly income',
29
24
  type: 'number',
30
25
  required: true,
31
26
  if: ->(app) { app.borrower_organisation.sole_trader? }
@@ -37,6 +32,28 @@ module InstantQuote
37
32
  required: true,
38
33
  if: ->(app) { app.borrower_organisation.sole_trader? }
39
34
  },
35
+ {
36
+ name: 'turnover_percentage_after_feb_2020',
37
+ label: 'What is your current turnover as a percentage of your turnover prior to February 2020?',
38
+ required: true,
39
+ choices: [
40
+ 'My turnover had increased since covid',
41
+ 'My turnover is about the same as it was pre-covid (within 20%)',
42
+ 'My turnover has fallen materially as a result of covid (20%-60% reduction in turnover)',
43
+ 'My turnover has fallen significantly as a result of covid (more than a 60% reduction in turnover)'
44
+ ],
45
+ type: 'select'
46
+ },
47
+ {
48
+ name: 'preferred_monthly_repayment_option',
49
+ label: 'How do you want to set up your direct debit for monthly repayment?',
50
+ required: true,
51
+ choices: [
52
+ 'Minimum Amount (10% of balance)',
53
+ 'Full Balance'
54
+ ],
55
+ type: 'select'
56
+ },
40
57
  {
41
58
  name: 'applicant_consent',
42
59
  label: 'Yes, I give my consent for my data to be shared',
@@ -57,17 +74,17 @@ module InstantQuote
57
74
 
58
75
  # Get the status for a given application ID
59
76
  # {:applicationStatus=>
60
- # {:locatorId=>"B247T62",
61
- # :applicationStage=>"AppSubmitted",
77
+ # {:locatorId=>'B247T62',
78
+ # :applicationStage=>'AppSubmitted',
62
79
  # :creditDecision=>
63
- # {:status=>"Pending",
80
+ # {:status=>'Pending',
64
81
  # :starRating=>0.0,
65
82
  # :approvalAmount=>0.0,
66
83
  # :monthlyInterestRate=>0.0,
67
84
  # :monthlyCardInterestRate=>0.0,
68
- # :decisionDate=>"2020-01-28T01:44:29.241",
69
- # :creditDecisionReason=>"None"}},
70
- # :links=>{:self=>"/api/v1/Applications/B247T62", :parent=>nil},
85
+ # :decisionDate=>'2020-01-28T01:44:29.241',
86
+ # :creditDecisionReason=>'None'}},
87
+ # :links=>{:self=>'/api/v1/Applications/B247T62', :parent=>nil},
71
88
  # :metaData=>{}
72
89
  # }
73
90
  def get_status(application_id)
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optimum'
4
+ require 'instant_quote/decision_parsers/optimum'
5
+
6
+ module InstantQuote
7
+ module Adapters
8
+ class Optimum < Adapter
9
+ additional_fields [
10
+ {
11
+ name: 'applicant_phone_number',
12
+ label: 'Applicant mobile phone',
13
+ required: true
14
+ },
15
+ {
16
+ name: 'expected_turnover_next_12_months',
17
+ label: 'Expected turnover next 12 months',
18
+ required: true,
19
+ type: 'money'
20
+ },
21
+ {
22
+ name: 'expected_customers',
23
+ label: 'Number of expected customers with a balance in 6 months',
24
+ required: true,
25
+ type: 'number'
26
+ },
27
+ {
28
+ name: 'industry_sector',
29
+ label: 'Industry sector',
30
+ required: true,
31
+ type: 'select',
32
+ choices: [
33
+ 'Advertising / Marketing',
34
+ 'Beer / Wine Merchant',
35
+ 'Builders Merchant',
36
+ 'Clothing & Fashion',
37
+ 'Computer Hardware',
38
+ 'Computer Software',
39
+ 'Construction',
40
+ 'Consultancy',
41
+ 'Contract Cleaning',
42
+ 'Courier',
43
+ 'Distribution',
44
+ 'Driving Agency',
45
+ 'Electrical Contracting',
46
+ 'Electrical Wholesaler',
47
+ 'Engineering - Heavy',
48
+ 'Engineering - Light',
49
+ 'Engineering - Precision',
50
+ 'Fabrication',
51
+ 'Flooring Contractor',
52
+ 'Food Manufacture',
53
+ 'Franchise',
54
+ 'Free Newspaper',
55
+ 'Freight Forwarding',
56
+ 'Fruit Importer',
57
+ 'Furniture Manufacture',
58
+ 'Garden Equipment',
59
+ 'Haulage',
60
+ 'IT Contracting',
61
+ 'Jewellery',
62
+ 'Maintenance',
63
+ 'Manufacturing',
64
+ 'Meat Trade',
65
+ 'Other Services',
66
+ 'Permanent Recruitment',
67
+ 'Plant Hire',
68
+ 'Plastic Injection Moulding',
69
+ 'Plumbers Merchant',
70
+ 'Printing',
71
+ 'Publishing',
72
+ 'Recycling',
73
+ 'Security Guards',
74
+ 'Shopfitting',
75
+ 'Stationery',
76
+ 'Steel Stockholding',
77
+ 'Telecoms',
78
+ 'Temporary Recruitment',
79
+ 'Timber Merchant',
80
+ 'Tooling Manufacture',
81
+ 'Toys & Fancy Goods',
82
+ 'Warehousing & Storage',
83
+ 'Wholesale'
84
+ ]
85
+ },
86
+ {
87
+ name: 'credit_control',
88
+ label: 'Do you require credit control?',
89
+ required: false,
90
+ choices: {
91
+ 'Yes' => '1',
92
+ 'No' => '0'
93
+ },
94
+ type: 'radio'
95
+ },
96
+ {
97
+ name: 'applicant_consent',
98
+ label: 'Yes, I give my consent for my data to be shared',
99
+ required: true,
100
+ type: 'checkbox'
101
+ }
102
+ ]
103
+
104
+ def get_quote(params)
105
+ response = ::Optimum::Decision.create(params)
106
+
107
+ return response.data[:opportunityId] if response.success?
108
+
109
+ raise_error(response)
110
+ end
111
+
112
+ def get_status(application_id)
113
+ response = ::Optimum::Decision.update(application_id)
114
+
115
+ raise_error(response) unless response.success?
116
+
117
+ decision_parser.new(response.body)
118
+ end
119
+
120
+ # Gets the URL for the given state key.
121
+ def get_link(_)
122
+ ''
123
+ end
124
+
125
+ def get_approval(_)
126
+ true
127
+ end
128
+
129
+ private
130
+
131
+ def decision_parser
132
+ DecisionParsers::Optimum
133
+ end
134
+
135
+ def raise_error(response)
136
+ raise ApiError.new(
137
+ adapter: 'optimum',
138
+ data: response.body,
139
+ error: response.error || 'Unknown API error'
140
+ )
141
+ end
142
+ end
143
+ end
144
+ end
@@ -14,7 +14,7 @@ module InstantQuote
14
14
  @code = params[:code]
15
15
 
16
16
  # Delete the metaData key, because it's irrelevant and has long strings
17
- @data.delete(:metaData) if @data.key?(:metaData)
17
+ @data.delete(:metaData) if @data.respond_to?(:key?) && @data.key?(:metaData)
18
18
  end
19
19
  end
20
20
  end
@@ -19,12 +19,13 @@ module InstantQuote
19
19
  PersonalAddress: translate_address,
20
20
  TradingName: app_org.name,
21
21
  BusinessLegalName: app_org.name,
22
- BusinessLandline: format_phone(extra_info['business_landline']),
22
+ BusinessLandline: format_phone(extra_info['applicant_phone_number']),
23
23
  YearsTrading: app_org.years_in_business,
24
24
  MonthlyTurnOver: translate_turnover,
25
25
  BusinessType: translate_business_type,
26
26
  BusinessAddress: translate_address,
27
- RegistrationNumber: app_org.company_number
27
+ RegistrationNumber: app_org.company_number,
28
+ additionalQuestions: additional_questions
28
29
  }
29
30
 
30
31
  # If the borrower organisation is Sole Trader we need to merge two additional fields.
@@ -71,7 +72,7 @@ module InstantQuote
71
72
  }
72
73
  end
73
74
 
74
- # Since in Finpoint the user selects an intervel (i.e. "500,000 - 999,999"). We get the avg
75
+ # Since in Finpoint the user selects an intervel (i.e. '500,000 - 999,999'). We get the avg
75
76
  # on this interval and use that number as the monthly turnover.
76
77
  def translate_turnover
77
78
  turnover = app_org.turnover_range
@@ -93,6 +94,29 @@ module InstantQuote
93
94
  'Partnership'
94
95
  end
95
96
  end
97
+
98
+ def additional_questions
99
+ [
100
+ preferred_monthly_repayment_option_question,
101
+ turnover_percentage_question
102
+ ]
103
+ end
104
+
105
+ def preferred_monthly_repayment_option_question
106
+ question_from_extra_info('preferred_monthly_repayment_option', '17')
107
+ end
108
+
109
+ def turnover_percentage_question
110
+ question_from_extra_info('turnover_percentage_after_feb_2020', '20')
111
+ end
112
+
113
+ def question_from_extra_info(name, question_type)
114
+ field = Adapters::CapitalOnTap.additional_fields.find do |f|
115
+ f[:name] == name
116
+ end
117
+
118
+ { question: field[:label], answer: extra_info[field[:name]], questionType: question_type }
119
+ end
96
120
  end
97
121
  end
98
122
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstantQuote
4
+ module ConnectionTranslators
5
+ class Optimum < ConnectionTranslator
6
+ def translate
7
+ {
8
+ companyNumber: company&.company_number.to_s,
9
+ sector: extra_info['industry_sector'],
10
+ turnover: extra_info['expected_turnover_next_12_months'].to_f,
11
+ homeowners: homeowners?,
12
+ yearsTrading: company&.years_in_business,
13
+ expectedCustomers: extra_info['expected_customers'].to_i,
14
+ contactName: application&.primary_user&.name,
15
+ contactNumber: extra_info['applicant_phone_number'],
16
+ contactEmail: application&.primary_user&.email,
17
+ creditControl: extra_info['credit_control'] == '1',
18
+ phoenix: false,
19
+ quotationOnly: false
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def company
26
+ application.borrower_organisation
27
+ end
28
+
29
+ def homeowners?
30
+ case company&.directors_home_owning&.name
31
+ when 'Home owner without mortgage', 'Home owner with mortgage'
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstantQuote
4
+ module DecisionParsers
5
+ # Example response:
6
+ #
7
+ # {
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
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
22
+ # }
23
+
24
+ class Optimum < DecisionParser
25
+ STATUSES = {
26
+ approved: 'approved',
27
+ declined: 'declined',
28
+ referred: 'referred',
29
+ }.freeze
30
+
31
+ LOAN_STATES = {
32
+ started: 'Closed Won'
33
+ }.freeze
34
+
35
+ def initialize(data = {})
36
+ super
37
+ end
38
+
39
+ def pending?
40
+ !STATUSES.value?(status)
41
+ end
42
+
43
+ def approved?
44
+ status == STATUSES[:approved]
45
+ end
46
+
47
+ def declined?
48
+ status == STATUSES[:declined]
49
+ end
50
+
51
+ def manual_review?
52
+ status == STATUSES[:referred]
53
+ end
54
+
55
+ def loan_started?
56
+ data[:status] == LOAN_STATES[:started]
57
+ end
58
+
59
+ def amount
60
+ amount_approved = decision[:fundingLimit] || 0
61
+
62
+ Money.new(amount_approved * 100).format
63
+ end
64
+
65
+ def credit_duration
66
+ 0
67
+ end
68
+
69
+ def credit_interval
70
+ decision[:trialPeriodMonths]
71
+ end
72
+
73
+ def monthly_interest_rate
74
+ 0
75
+ end
76
+
77
+ def status
78
+ decision[:decision]
79
+ end
80
+
81
+ private
82
+
83
+ def decision
84
+ data[:decision] || data[:originalDecision] || {}
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InstantQuote
4
- VERSION = '1.1.8'
4
+ VERSION = '1.5.1'
5
5
  end
@@ -16,31 +16,15 @@ module InstantQuote
16
16
  class SignatureVerifyError < StandardError
17
17
  end
18
18
 
19
- def iwoca_event
20
- # Dynamically calls the following methods:
21
- # * handle_approval_status_changed
22
- # * handle_instruct_payments
23
- # * handle_customer_funded
24
- if iwoca_verify_signature
25
- return send("handle_#{iwoca_event_type}") if respond_to?("handle_#{iwoca_event_type}")
26
-
27
- render json: iwoca_payload, status: 200
28
- else
29
- iwoca_verify_signature_error
30
- end
31
- rescue SignatureVerifyError => e
32
- iwoca_verify_signature_error(e.message)
33
- end
34
-
35
19
  private
36
20
 
37
21
  def iwoca_verify_signature
38
- unless webhook_signature && iwoca_payload && iwoca_signature
22
+ unless webhook_signature && payload && iwoca_signature
39
23
  raise SignatureVerifyError, "#{SIGNATURE_HEADER} header missing"
40
24
  end
41
25
 
42
26
  digest = OpenSSL::Digest.new('sha1')
43
- hmac_digest = OpenSSL::HMAC.digest(digest, webhook_signature, iwoca_payload)
27
+ hmac_digest = OpenSSL::HMAC.digest(digest, webhook_signature, payload)
44
28
  signature = "sha1=#{Base64.strict_encode64(hmac_digest)}"
45
29
 
46
30
  Rack::Utils.secure_compare(signature, iwoca_signature)
@@ -50,8 +34,8 @@ module InstantQuote
50
34
  @webhook_signature ||= ::Iwoca.configuration.webhook_signature
51
35
  end
52
36
 
53
- def iwoca_payload
54
- @iwoca_payload ||= request.body.read
37
+ def payload
38
+ @payload ||= request.body.read
55
39
  end
56
40
 
57
41
  def iwoca_signature
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.1.8
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - rikas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-07 00:00:00.000000000 Z
11
+ date: 2020-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: optimum
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: money
169
183
  requirement: !ruby/object:Gem::Requirement
@@ -217,22 +231,25 @@ files:
217
231
  - lib/instant_quote/adapters/capital_on_tap.rb
218
232
  - lib/instant_quote/adapters/iwoca.rb
219
233
  - lib/instant_quote/adapters/iwoca_cbils.rb
234
+ - lib/instant_quote/adapters/optimum.rb
220
235
  - lib/instant_quote/api_error.rb
221
236
  - lib/instant_quote/connection_translator.rb
222
237
  - lib/instant_quote/connection_translators/capital_on_tap.rb
223
238
  - lib/instant_quote/connection_translators/iwoca.rb
224
239
  - lib/instant_quote/connection_translators/iwoca_cbils.rb
240
+ - lib/instant_quote/connection_translators/optimum.rb
225
241
  - lib/instant_quote/decision_parser.rb
226
242
  - lib/instant_quote/decision_parsers/capital_on_tap.rb
227
243
  - lib/instant_quote/decision_parsers/iwoca.rb
228
244
  - lib/instant_quote/decision_parsers/iwoca_cbils.rb
245
+ - lib/instant_quote/decision_parsers/optimum.rb
229
246
  - lib/instant_quote/version.rb
230
247
  - lib/instant_quote/webhooks/iwoca.rb
231
248
  homepage: https://finpoint.co.uk
232
249
  licenses: []
233
250
  metadata:
234
251
  homepage_uri: https://finpoint.co.uk
235
- post_install_message:
252
+ post_install_message:
236
253
  rdoc_options: []
237
254
  require_paths:
238
255
  - lib
@@ -248,7 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
265
  version: '0'
249
266
  requirements: []
250
267
  rubygems_version: 3.0.3
251
- signing_key:
268
+ signing_key:
252
269
  specification_version: 4
253
270
  summary: Instant quote providers gem.
254
271
  test_files: []