instant_quote 1.7.18 → 1.7.20
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/Gemfile.lock +1 -1
- data/bin/console +48 -0
- data/lib/instant_quote/decision_parsers/iwoca.rb +11 -1
- data/lib/instant_quote/decision_parsers/iwoca_v1.rb +141 -0
- data/lib/instant_quote/decision_parsers/iwoca_v2_translator.rb +69 -0
- data/lib/instant_quote/version.rb +1 -1
- data/lib/instant_quote/webhooks/iwoca.rb +18 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d31fe21346d1dcdc18f60859f3bb71f248df1ebe76bc10b1ef8e77717c522210
|
|
4
|
+
data.tar.gz: 16913cdcbf765609b081cc39a1dacf655dba67faf1e2cd4ffd6a93b8d9c279eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7179adf5e3ca6dd13788e072d9f5abd4c2fd2b8ca5dfc6d38ecb1839d18cbea3d89e0ef47ad30f45166d65ad76583b86f0d881e2ed50ca6f03e6f2d38ca8597d
|
|
7
|
+
data.tar.gz: f0e046542bf8ee4cc71aabbba509ca270e36dc3a39c9a5f37009c0406f88d17b4fc45e8c3892d0d54da70e8779da58ad3daaaf03e8c2e99e127ecab09e67b5fc
|
data/Gemfile.lock
CHANGED
data/bin/console
CHANGED
|
@@ -32,5 +32,53 @@ end
|
|
|
32
32
|
|
|
33
33
|
send(:include, InstantQuote)
|
|
34
34
|
|
|
35
|
+
# Configuration needed for the Money gem
|
|
36
|
+
Money.default_currency = :gbp
|
|
37
|
+
I18n.available_locales = [:en]
|
|
38
|
+
I18n.locale = :en
|
|
39
|
+
Money.locale_backend = :i18n
|
|
40
|
+
Money.rounding_mode = BigDecimal::ROUND_HALF_EVEN
|
|
41
|
+
|
|
42
|
+
def declined_v1_json
|
|
43
|
+
json = '{
|
|
44
|
+
"data": {
|
|
45
|
+
"state_key": "[FILTERED]",
|
|
46
|
+
"approval_requirements": [
|
|
47
|
+
{ "type": "cc", "status": "required" },
|
|
48
|
+
{ "type": "enhanced_security_check", "status": "pending" }
|
|
49
|
+
],
|
|
50
|
+
"approval_status": { "status": "declined" },
|
|
51
|
+
"latest_approval_request": { "status": "decision_made" }
|
|
52
|
+
}
|
|
53
|
+
}'
|
|
54
|
+
|
|
55
|
+
JSON.parse(json)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def approved_v1_json
|
|
59
|
+
json = '{
|
|
60
|
+
"data": {
|
|
61
|
+
"state_key": "[FILTERED]",
|
|
62
|
+
"approval_status": {
|
|
63
|
+
"approved": {
|
|
64
|
+
"duration": 24,
|
|
65
|
+
"interval": "1m",
|
|
66
|
+
"max_credit": 21000.0,
|
|
67
|
+
"min_credit": 1.0,
|
|
68
|
+
"rate_structures": [
|
|
69
|
+
{ "effective_on_day": 0, "rates": [{ "max_principal": 21000.0, "rate": 0.0515 }] }
|
|
70
|
+
],
|
|
71
|
+
"time_out": "2023-09-24T00:00:00Z",
|
|
72
|
+
"top_up_allowed": false
|
|
73
|
+
},
|
|
74
|
+
"status": "approved"
|
|
75
|
+
},
|
|
76
|
+
"latest_approval_request": { "status": "decision_made" }
|
|
77
|
+
}
|
|
78
|
+
}'
|
|
79
|
+
|
|
80
|
+
JSON.parse(json)
|
|
81
|
+
end
|
|
82
|
+
|
|
35
83
|
require 'pry'
|
|
36
84
|
Pry.start
|
|
@@ -98,6 +98,16 @@ module InstantQuote
|
|
|
98
98
|
# offers - the response of the available offers
|
|
99
99
|
def initialize(data = {})
|
|
100
100
|
@data = super
|
|
101
|
+
|
|
102
|
+
# Just in case (v1 has a lot of key 'data' nesting)
|
|
103
|
+
@data = @data.dig(:data) if @data.key?(:data)
|
|
104
|
+
|
|
105
|
+
# It's a v1 JSON response, so we need to translate it to v2 first!
|
|
106
|
+
if @data.key?(:state_key)
|
|
107
|
+
puts "Translating v1 JSON to v2..."
|
|
108
|
+
@data = DecisionParsers::IwocaV2Translator.translate(@data)
|
|
109
|
+
end
|
|
110
|
+
|
|
101
111
|
@status = @data[:status] if @data.key?(:status)
|
|
102
112
|
@offers = @data[:offers] if @data.key?(:offers)
|
|
103
113
|
end
|
|
@@ -153,7 +163,7 @@ module InstantQuote
|
|
|
153
163
|
end
|
|
154
164
|
|
|
155
165
|
def monthly_interest_rate
|
|
156
|
-
first_offer
|
|
166
|
+
first_offer[:interest_rate]
|
|
157
167
|
end
|
|
158
168
|
|
|
159
169
|
def status
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InstantQuote
|
|
4
|
+
module DecisionParsers
|
|
5
|
+
# Example responses:
|
|
6
|
+
#
|
|
7
|
+
# {
|
|
8
|
+
# "data": {
|
|
9
|
+
# "state_key": "[FILTERED]",
|
|
10
|
+
# "approval_requirements": [
|
|
11
|
+
# { "type": "enhanced_security_check", "status": "pending" }
|
|
12
|
+
# ],
|
|
13
|
+
# "approval_status": { "status": "declined" },
|
|
14
|
+
# "latest_approval_request": { "status": "decision_made" }
|
|
15
|
+
# }
|
|
16
|
+
# }
|
|
17
|
+
#
|
|
18
|
+
# {
|
|
19
|
+
# "data": {
|
|
20
|
+
# "state_key": "[FILTERED]",
|
|
21
|
+
# "approval_requirements": [
|
|
22
|
+
# { "type": "cc", "status": "required" },
|
|
23
|
+
# { "type": "enhanced_security_check", "status": "pending" }
|
|
24
|
+
# ],
|
|
25
|
+
# "approval_status": { "status": "declined" },
|
|
26
|
+
# "latest_approval_request": { "status": "decision_made" }
|
|
27
|
+
# }
|
|
28
|
+
# }
|
|
29
|
+
#
|
|
30
|
+
# {
|
|
31
|
+
# "data": {
|
|
32
|
+
# "state_key": "[FILTERED]",
|
|
33
|
+
# "approval_status": {
|
|
34
|
+
# "approved": {
|
|
35
|
+
# "duration": 24,
|
|
36
|
+
# "interval": "1m",
|
|
37
|
+
# "max_credit": 21000.0,
|
|
38
|
+
# "min_credit": 1.0,
|
|
39
|
+
# "rate_structures": [
|
|
40
|
+
# { "effective_on_day": 0, "rates": [{ "max_principal": 21000.0, "rate": 0.0515 }] }
|
|
41
|
+
# ],
|
|
42
|
+
# "time_out": "2023-09-24T00:00:00Z",
|
|
43
|
+
# "top_up_allowed": false
|
|
44
|
+
# },
|
|
45
|
+
# "status": "approved"
|
|
46
|
+
# },
|
|
47
|
+
# "latest_approval_request": { "status": "decision_made" }
|
|
48
|
+
# }
|
|
49
|
+
# }
|
|
50
|
+
class IwocaV1 < DecisionParser
|
|
51
|
+
STATUSES = {
|
|
52
|
+
pending: 'pending',
|
|
53
|
+
approved: 'approved',
|
|
54
|
+
declined: 'declined',
|
|
55
|
+
no_decision: 'no_decision_possible',
|
|
56
|
+
not_defined: 'not_defined'
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
LOAN_STATES = {
|
|
60
|
+
ongoing: 'ongoing',
|
|
61
|
+
ended: 'ended',
|
|
62
|
+
topup: 'topup',
|
|
63
|
+
pending: 'pending',
|
|
64
|
+
overdue: 'overdue',
|
|
65
|
+
duetoday: 'duetoday'
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
# iwoca sends an extra "data" key, that is removed here, to avoid having to do data[:data]
|
|
69
|
+
# because it's silly :)
|
|
70
|
+
def initialize(data = {})
|
|
71
|
+
@data = super
|
|
72
|
+
@data = @data[:data] if @data.key?(:data)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pending?
|
|
76
|
+
status == STATUSES[:not_defined]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def approved?
|
|
80
|
+
status == STATUSES[:approved]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def declined?
|
|
84
|
+
status == STATUSES[:declined]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def manual_review?
|
|
88
|
+
status == STATUSES[:pending]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def no_decision_possible?
|
|
92
|
+
status == STATUSES[:no_decision]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def loan_started?
|
|
96
|
+
data[:loan_status] == LOAN_STATES[:ongoing]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def amount
|
|
100
|
+
amount_approved = status_hash.dig(:approved, :max_credit) || 0
|
|
101
|
+
|
|
102
|
+
Money.new(amount_approved * 100).format
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Only used by the new iwoca2 translator
|
|
106
|
+
def amount_unformatted
|
|
107
|
+
status_hash.dig(:approved, :max_credit) || 0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def credit_duration
|
|
111
|
+
status_hash.dig(:approved, :duration)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def credit_interval
|
|
115
|
+
status_hash.dig(:approved, :interval)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def monthly_interest_rate
|
|
119
|
+
rates = status_hash.dig(:approved, :rate_structures)
|
|
120
|
+
|
|
121
|
+
return 0 unless rates&.any?
|
|
122
|
+
|
|
123
|
+
rate_info = rates.first[:rates]&.first
|
|
124
|
+
|
|
125
|
+
return 0 unless rate_info
|
|
126
|
+
|
|
127
|
+
rate_info[:rate]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def status
|
|
131
|
+
status_hash[:status] || STATUSES[:not_defined]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def status_hash
|
|
137
|
+
data[:approval_status] || data[:latest_approval_request] || {}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'instant_quote/decision_parsers/iwoca'
|
|
4
|
+
|
|
5
|
+
# This class is used to translate iwoca's v1 decision format to v2. In the app we might have some
|
|
6
|
+
# decisions stored with the v1 format that need translation but the webhook at the moment is also
|
|
7
|
+
# using the old format (with state_key instead of customer_id).
|
|
8
|
+
module InstantQuote
|
|
9
|
+
module DecisionParsers
|
|
10
|
+
class IwocaV2Translator
|
|
11
|
+
def initialize(v1_json)
|
|
12
|
+
@decision = InstantQuote::DecisionParsers::IwocaV1.new(v1_json)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.translate(decision)
|
|
16
|
+
new(decision).translate
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Translates the v1 JSON into v2 format.
|
|
20
|
+
def translate
|
|
21
|
+
{
|
|
22
|
+
status: {
|
|
23
|
+
status: translate_status(@decision.status),
|
|
24
|
+
requests: [
|
|
25
|
+
{
|
|
26
|
+
suggested_product: {
|
|
27
|
+
amount: @decision.amount_unformatted,
|
|
28
|
+
duration: {
|
|
29
|
+
unit: translate_interval(@decision.credit_interval),
|
|
30
|
+
amount: @decision.credit_duration
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
offers: [
|
|
37
|
+
{
|
|
38
|
+
interest_rate: @decision.monthly_interest_rate
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def translate_status(v1_status)
|
|
47
|
+
case v1_status
|
|
48
|
+
when 'approved'
|
|
49
|
+
'funded'
|
|
50
|
+
else
|
|
51
|
+
v1_status
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def translate_interval(v1_interval)
|
|
56
|
+
case v1_interval
|
|
57
|
+
when '1m'
|
|
58
|
+
'months'
|
|
59
|
+
when '1w'
|
|
60
|
+
'weeks'
|
|
61
|
+
when '1d'
|
|
62
|
+
'days'
|
|
63
|
+
else
|
|
64
|
+
'months'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -42,6 +42,24 @@ module InstantQuote
|
|
|
42
42
|
request.headers[SIGNATURE_HEADER]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# The list of possible event types is:
|
|
46
|
+
# approval_status_changed
|
|
47
|
+
# customer_funded
|
|
48
|
+
# application_offered
|
|
49
|
+
# application_declined
|
|
50
|
+
# application_deferred
|
|
51
|
+
# cashflow_added
|
|
52
|
+
# mca_context_changed
|
|
53
|
+
# bank_account_setup
|
|
54
|
+
# application_status_changed
|
|
55
|
+
# application_attributed
|
|
56
|
+
# indicative_offer_created
|
|
57
|
+
# shopify_payment_resolve
|
|
58
|
+
# shopify_payment_reject
|
|
59
|
+
# shopify_refund_resolve
|
|
60
|
+
#
|
|
61
|
+
# More info:
|
|
62
|
+
# https://iwoca.stoplight.io/docs/lapi-notifications/b59ca8c181611-get-available-webhook-event-types
|
|
45
63
|
def iwoca_event_type
|
|
46
64
|
# Allowed Values: approval_status_changed, instruct_payments, customer_funded
|
|
47
65
|
request.headers[EVENT_TYPE_HEADER]
|
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.7.
|
|
4
|
+
version: 1.7.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rikas
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-08-
|
|
11
|
+
date: 2023-08-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -158,6 +158,8 @@ files:
|
|
|
158
158
|
- lib/instant_quote/decision_parsers/iwoca_flexi24.rb
|
|
159
159
|
- lib/instant_quote/decision_parsers/iwoca_recovery_loans.rb
|
|
160
160
|
- lib/instant_quote/decision_parsers/iwoca_revenue_based_loans.rb
|
|
161
|
+
- lib/instant_quote/decision_parsers/iwoca_v1.rb
|
|
162
|
+
- lib/instant_quote/decision_parsers/iwoca_v2_translator.rb
|
|
161
163
|
- lib/instant_quote/decision_parsers/optimum.rb
|
|
162
164
|
- lib/instant_quote/decision_parsers/youlend.rb
|
|
163
165
|
- lib/instant_quote/version.rb
|