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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea32f26600ebacbcea2bbb802e81cfdfdbc6f3fd8a63c5114450bd757aa7bcfe
4
- data.tar.gz: 3690d3afa1a8bef07787712907ae74e68141ff948191aec6dc15515ef56733eb
3
+ metadata.gz: d31fe21346d1dcdc18f60859f3bb71f248df1ebe76bc10b1ef8e77717c522210
4
+ data.tar.gz: 16913cdcbf765609b081cc39a1dacf655dba67faf1e2cd4ffd6a93b8d9c279eb
5
5
  SHA512:
6
- metadata.gz: a6ae792406151bc13bbd707f7bcc70f1baeb45a3e3c358fed1a528be3b0099e747c976ef056307170e636fcbe0d28e09cef9b0e79480e690c9aed95889fc1672
7
- data.tar.gz: 5ed602cde2830a7a84225605a44bd68f6573fddaaf905617130b0da9d931c9a752edfab2880e07e64ba95d6dab6638160cb95679a565c5d6d2469f54d42351e0
6
+ metadata.gz: 7179adf5e3ca6dd13788e072d9f5abd4c2fd2b8ca5dfc6d38ecb1839d18cbea3d89e0ef47ad30f45166d65ad76583b86f0d881e2ed50ca6f03e6f2d38ca8597d
7
+ data.tar.gz: f0e046542bf8ee4cc71aabbba509ca270e36dc3a39c9a5f37009c0406f88d17b4fc45e8c3892d0d54da70e8779da58ad3daaaf03e8c2e99e127ecab09e67b5fc
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- instant_quote (1.7.18)
4
+ instant_quote (1.7.20)
5
5
  activesupport
6
6
  capital_on_tap (~> 1.0.1)
7
7
  iwoca (~> 1.1.0)
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&.interest_rate
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InstantQuote
4
- VERSION = '1.7.18'
4
+ VERSION = '1.7.20'
5
5
  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.18
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-24 00:00:00.000000000 Z
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