instant_quote 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +22 -15
- data/instant_quote.gemspec +1 -0
- data/lib/instant_quote/adapter_finder.rb +11 -8
- data/lib/instant_quote/adapters/capital_on_tap.rb +13 -5
- data/lib/instant_quote/adapters/iwoca.rb +11 -2
- data/lib/instant_quote/adapters/iwoca_cbils.rb +19 -0
- data/lib/instant_quote/adapters/optimum.rb +144 -0
- data/lib/instant_quote/api_error.rb +4 -2
- data/lib/instant_quote/connection_translators/capital_on_tap.rb +1 -1
- data/lib/instant_quote/connection_translators/iwoca.rb +5 -1
- data/lib/instant_quote/connection_translators/iwoca_cbils.rb +15 -0
- data/lib/instant_quote/connection_translators/optimum.rb +39 -0
- data/lib/instant_quote/decision_parsers/iwoca.rb +1 -1
- data/lib/instant_quote/decision_parsers/iwoca_cbils.rb +10 -0
- data/lib/instant_quote/decision_parsers/optimum.rb +88 -0
- data/lib/instant_quote/version.rb +1 -1
- data/lib/instant_quote/webhooks/iwoca.rb +4 -20
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec2e26453b0ed03e414feceb9e8344ff4d502d5f0962f7b509450608278c9b34
|
|
4
|
+
data.tar.gz: 830639812afaf22a2f67059a6fb757fc6988e99121deca26ede7eba041738a6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0822f784c417f72c986a44ecaad692be2a53b227cf09c14a4bb7d7afe5b5db7dd3dd3d070952cc809b38f3c7cb0fe2168e0237629d5a7d70c26b24c71cf2d7f7'
|
|
7
|
+
data.tar.gz: '069cc5595bae6a50601f73d4403be3bcaad28b51033beec08f0488a426a51d3f8911a1828c18b00e69ecfbf3d27f24f2b3e68bd00f05091af9e334f5a6bf9e26'
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.6.
|
|
1
|
+
2.6.6
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
instant_quote (1.
|
|
4
|
+
instant_quote (1.2.0)
|
|
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.
|
|
15
|
+
activesupport (6.0.3.2)
|
|
15
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
16
17
|
i18n (>= 0.7, < 2)
|
|
17
18
|
minitest (~> 5.1)
|
|
18
19
|
tzinfo (~> 1.1)
|
|
19
|
-
zeitwerk (~> 2.2)
|
|
20
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
|
20
21
|
addressable (2.7.0)
|
|
21
22
|
public_suffix (>= 2.0.2, < 5.0)
|
|
22
23
|
ast (2.4.0)
|
|
@@ -28,18 +29,18 @@ GEM
|
|
|
28
29
|
multi_json
|
|
29
30
|
rainbow
|
|
30
31
|
coderay (1.1.2)
|
|
31
|
-
concurrent-ruby (1.1.
|
|
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 (0.
|
|
38
|
+
faraday (1.0.1)
|
|
38
39
|
multipart-post (>= 1.2, < 3)
|
|
39
|
-
faraday_middleware (0.
|
|
40
|
-
faraday (
|
|
40
|
+
faraday_middleware (1.0.0)
|
|
41
|
+
faraday (~> 1.0)
|
|
41
42
|
hashdiff (1.0.1)
|
|
42
|
-
i18n (1.8.
|
|
43
|
+
i18n (1.8.5)
|
|
43
44
|
concurrent-ruby (~> 1.0)
|
|
44
45
|
iwoca (1.0.0)
|
|
45
46
|
addressable
|
|
@@ -49,15 +50,21 @@ GEM
|
|
|
49
50
|
rainbow
|
|
50
51
|
jaro_winkler (1.5.4)
|
|
51
52
|
method_source (1.0.0)
|
|
52
|
-
minitest (5.14.
|
|
53
|
-
money (6.13.
|
|
53
|
+
minitest (5.14.2)
|
|
54
|
+
money (6.13.8)
|
|
54
55
|
i18n (>= 0.6.4, <= 2)
|
|
55
|
-
multi_json (1.
|
|
56
|
+
multi_json (1.15.0)
|
|
56
57
|
multipart-post (2.1.1)
|
|
58
|
+
optimum (1.0.0)
|
|
59
|
+
addressable
|
|
60
|
+
faraday
|
|
61
|
+
faraday_middleware
|
|
62
|
+
multi_json
|
|
63
|
+
rainbow
|
|
57
64
|
parallel (1.19.1)
|
|
58
65
|
parser (2.7.0.5)
|
|
59
66
|
ast (~> 2.4.0)
|
|
60
|
-
phonelib (0.6.
|
|
67
|
+
phonelib (0.6.45)
|
|
61
68
|
pry (0.13.0)
|
|
62
69
|
coderay (~> 1.1)
|
|
63
70
|
method_source (~> 1.0)
|
|
@@ -96,14 +103,14 @@ GEM
|
|
|
96
103
|
simplecov-html (~> 0.11)
|
|
97
104
|
simplecov-html (0.12.2)
|
|
98
105
|
thread_safe (0.3.6)
|
|
99
|
-
tzinfo (1.2.
|
|
106
|
+
tzinfo (1.2.7)
|
|
100
107
|
thread_safe (~> 0.1)
|
|
101
108
|
unicode-display_width (1.6.1)
|
|
102
109
|
webmock (3.8.3)
|
|
103
110
|
addressable (>= 2.3.6)
|
|
104
111
|
crack (>= 0.3.2)
|
|
105
112
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
106
|
-
zeitwerk (2.
|
|
113
|
+
zeitwerk (2.4.0)
|
|
107
114
|
|
|
108
115
|
PLATFORMS
|
|
109
116
|
ruby
|
|
@@ -120,4 +127,4 @@ DEPENDENCIES
|
|
|
120
127
|
webmock
|
|
121
128
|
|
|
122
129
|
BUNDLED WITH
|
|
123
|
-
2.
|
|
130
|
+
2.1.4
|
data/instant_quote.gemspec
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Require all the connection translators
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Dir.glob('*.rb', base: File.expand_path('connection_translators', __dir__)) do |translator|
|
|
5
|
+
require "instant_quote/connection_translators/#{translator}"
|
|
6
|
+
end
|
|
6
7
|
|
|
7
8
|
# Require all the decision parsers
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
Dir.glob('*.rb', base: File.expand_path('decision_parsers', __dir__)) do |parser|
|
|
10
|
+
require "instant_quote/decision_parsers/#{parser}"
|
|
11
|
+
end
|
|
10
12
|
|
|
11
13
|
# Require all the adapters
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
Dir.glob('*.rb', base: File.expand_path('adapters', __dir__)) do |adapter|
|
|
15
|
+
require "instant_quote/adapters/#{adapter}"
|
|
16
|
+
end
|
|
14
17
|
|
|
15
18
|
module InstantQuote
|
|
16
19
|
class AdapterFinder
|
|
@@ -28,8 +31,8 @@ module InstantQuote
|
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def self.available_adapters
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
Dir.glob('*.rb', base: File.expand_path('adapters', __dir__))
|
|
35
|
+
.map { |adapter| adapter.sub('.rb', '') }
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def get_link
|
|
@@ -25,7 +25,7 @@ module InstantQuote
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
name: 'monthly_income',
|
|
28
|
-
label: '
|
|
28
|
+
label: 'Applicant monthly income',
|
|
29
29
|
type: 'number',
|
|
30
30
|
required: true,
|
|
31
31
|
if: ->(app) { app.borrower_organisation.sole_trader? }
|
|
@@ -50,9 +50,7 @@ module InstantQuote
|
|
|
50
50
|
response = ::CapitalOnTap::Application.create(params)
|
|
51
51
|
|
|
52
52
|
# CoT responses can be 200 OK but still have an error!
|
|
53
|
-
unless response.success? && !response.error
|
|
54
|
-
raise ApiError.new(data: response.body, error: response.error || 'Unknown API error')
|
|
55
|
-
end
|
|
53
|
+
raise_error(response) unless response.success? && !response.error
|
|
56
54
|
|
|
57
55
|
response.body.dig(:result)
|
|
58
56
|
end
|
|
@@ -75,7 +73,7 @@ module InstantQuote
|
|
|
75
73
|
def get_status(application_id)
|
|
76
74
|
response = ::CapitalOnTap::Application.status(application_id)
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
raise_error(response) unless response.success?
|
|
79
77
|
|
|
80
78
|
DecisionParsers::CapitalOnTap.new(response.body[:applicationStatus])
|
|
81
79
|
end
|
|
@@ -89,6 +87,16 @@ module InstantQuote
|
|
|
89
87
|
def get_approval(_application_id)
|
|
90
88
|
true
|
|
91
89
|
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def raise_error(response)
|
|
94
|
+
raise ApiError.new(
|
|
95
|
+
adapter: 'capital_on_tap',
|
|
96
|
+
data: response.body,
|
|
97
|
+
error: response.error || 'Unknown API error'
|
|
98
|
+
)
|
|
99
|
+
end
|
|
92
100
|
end
|
|
93
101
|
end
|
|
94
102
|
end
|
|
@@ -77,7 +77,7 @@ module InstantQuote
|
|
|
77
77
|
|
|
78
78
|
raise_error(response) unless response.success?
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
decision_parser.new(response.body)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Gets the URL for the given state key.
|
|
@@ -96,8 +96,17 @@ module InstantQuote
|
|
|
96
96
|
|
|
97
97
|
private
|
|
98
98
|
|
|
99
|
+
def decision_parser
|
|
100
|
+
DecisionParsers::Iwoca
|
|
101
|
+
end
|
|
102
|
+
|
|
99
103
|
def raise_error(response)
|
|
100
|
-
raise ApiError.new(
|
|
104
|
+
raise ApiError.new(
|
|
105
|
+
adapter: 'iwoca',
|
|
106
|
+
data: response.data,
|
|
107
|
+
code: response.errors.first[:code],
|
|
108
|
+
error: response.errors.first[:detail]
|
|
109
|
+
)
|
|
101
110
|
end
|
|
102
111
|
end
|
|
103
112
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'iwoca'
|
|
4
|
+
require 'instant_quote/adapters/iwoca'
|
|
5
|
+
require 'instant_quote/decision_parsers/iwoca_cbils'
|
|
6
|
+
|
|
7
|
+
module InstantQuote
|
|
8
|
+
module Adapters
|
|
9
|
+
class IwocaCbils < Iwoca
|
|
10
|
+
additional_fields Iwoca.additional_fields
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def decision_parser
|
|
15
|
+
DecisionParsers::IwocaCbils
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -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
|
+
'1' => 'Yes',
|
|
92
|
+
'0' => 'No'
|
|
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
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module InstantQuote
|
|
4
4
|
class ApiError < StandardError
|
|
5
|
-
attr_accessor :error, :data
|
|
5
|
+
attr_accessor :error, :data, :code, :adapter
|
|
6
6
|
|
|
7
7
|
alias message error
|
|
8
8
|
alias to_s error
|
|
9
9
|
|
|
10
10
|
def initialize(params = {})
|
|
11
|
+
@adapter = params[:adapter]
|
|
11
12
|
@error = params[:error]
|
|
12
13
|
@data = params[:data] || {}
|
|
14
|
+
@code = params[:code]
|
|
13
15
|
|
|
14
16
|
# Delete the metaData key, because it's irrelevant and has long strings
|
|
15
|
-
@data.delete(:metaData) if @data.key?(:metaData)
|
|
17
|
+
@data.delete(:metaData) if @data.respond_to?(:key?) && @data.key?(:metaData)
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -10,7 +10,7 @@ module InstantQuote
|
|
|
10
10
|
company: company_information,
|
|
11
11
|
people: [person_information],
|
|
12
12
|
requested_products: {
|
|
13
|
-
|
|
13
|
+
product_name => {
|
|
14
14
|
approval: {
|
|
15
15
|
amount: (application.amount_pennies / 100),
|
|
16
16
|
duration: 12,
|
|
@@ -25,6 +25,10 @@ module InstantQuote
|
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
+
def product_name
|
|
29
|
+
:credit_facility
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
# rubocop:disable Metrics/AbcSize
|
|
29
33
|
def company_information
|
|
30
34
|
information = {
|
|
@@ -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
|
|
@@ -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 &&
|
|
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,
|
|
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
|
|
54
|
-
@
|
|
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.
|
|
4
|
+
version: 1.2.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-
|
|
11
|
+
date: 2020-09-11 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
|
|
@@ -216,13 +230,19 @@ files:
|
|
|
216
230
|
- lib/instant_quote/adapter_finder.rb
|
|
217
231
|
- lib/instant_quote/adapters/capital_on_tap.rb
|
|
218
232
|
- lib/instant_quote/adapters/iwoca.rb
|
|
233
|
+
- lib/instant_quote/adapters/iwoca_cbils.rb
|
|
234
|
+
- lib/instant_quote/adapters/optimum.rb
|
|
219
235
|
- lib/instant_quote/api_error.rb
|
|
220
236
|
- lib/instant_quote/connection_translator.rb
|
|
221
237
|
- lib/instant_quote/connection_translators/capital_on_tap.rb
|
|
222
238
|
- lib/instant_quote/connection_translators/iwoca.rb
|
|
239
|
+
- lib/instant_quote/connection_translators/iwoca_cbils.rb
|
|
240
|
+
- lib/instant_quote/connection_translators/optimum.rb
|
|
223
241
|
- lib/instant_quote/decision_parser.rb
|
|
224
242
|
- lib/instant_quote/decision_parsers/capital_on_tap.rb
|
|
225
243
|
- lib/instant_quote/decision_parsers/iwoca.rb
|
|
244
|
+
- lib/instant_quote/decision_parsers/iwoca_cbils.rb
|
|
245
|
+
- lib/instant_quote/decision_parsers/optimum.rb
|
|
226
246
|
- lib/instant_quote/version.rb
|
|
227
247
|
- lib/instant_quote/webhooks/iwoca.rb
|
|
228
248
|
homepage: https://finpoint.co.uk
|