instant_quote 1.7.33 → 1.7.34

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: 961b0814c5895349859b111b9154e56582aade793a7e7623c68e53b142742b86
4
- data.tar.gz: 7e88f40122d9bc7442618b3fc4aa3994f6b8f988887cfb240de853d47094bd95
3
+ metadata.gz: 97b97adb2fb250be1f7b7a46b44cbb082500cb0dae53cb350e31123d506a1efe
4
+ data.tar.gz: 66ca99708c03bfbea6587abf097196998d366c1842026f43ce02b04705a779ae
5
5
  SHA512:
6
- metadata.gz: ccf3b9a9eb9d9e38c3c9ad2a9341432f683dd0dc6ca14e59b31b80b701563560c4796869f8202a84c45461798387419dbf90b1db943635ad6b513e417b6971f8
7
- data.tar.gz: aaa6b9dcd4fc6dfebf247e443911af337f77a0cd0f1f713547fc2aa51840e73d6069f68b194b8504ae997598f26c7d9556dff0a665b72457f41d6723629b7947
6
+ metadata.gz: 9f94bf675fa6e919ece437296cad1684401ee435f9e2e54c1d86be65b94469f00d4e155187644c1e93ba75cc745a09554a9e988e45738d28d3b3ced25fa3b5a4
7
+ data.tar.gz: d095be67aa6e9b7f0d07d3bdebef1e89e95140b7435445b90287b8275814b071cceff5c657d8dd74bb97f7ac84c01cec156844fc8a6fb0a878daac1638ef279f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- instant_quote (1.7.33)
4
+ instant_quote (1.7.34)
5
5
  activesupport
6
6
  capital_on_tap (~> 1.0.1)
7
7
  iwoca (~> 1.1.0)
@@ -22,6 +22,7 @@ GEM
22
22
  addressable (2.8.5)
23
23
  public_suffix (>= 2.0.2, < 6.0)
24
24
  ast (2.4.2)
25
+ base64 (0.1.1)
25
26
  byebug (11.1.3)
26
27
  capital_on_tap (1.0.1)
27
28
  addressable
@@ -35,7 +36,8 @@ GEM
35
36
  diff-lcs (1.5.0)
36
37
  docile (1.4.0)
37
38
  dotenv (2.8.1)
38
- faraday (2.7.10)
39
+ faraday (2.7.11)
40
+ base64
39
41
  faraday-net_http (>= 2.0, < 3.1)
40
42
  ruby2_keywords (>= 0.0.4)
41
43
  faraday-net_http (3.0.2)
@@ -51,7 +53,7 @@ GEM
51
53
  json-schema (4.0.0)
52
54
  addressable (>= 2.8)
53
55
  method_source (1.0.0)
54
- minitest (5.19.0)
56
+ minitest (5.20.0)
55
57
  money (6.16.0)
56
58
  i18n (>= 0.6.4, <= 2)
57
59
  multi_json (1.15.0)
@@ -123,7 +125,7 @@ GEM
123
125
  addressable (>= 2.8.0)
124
126
  crack (>= 0.3.2)
125
127
  hashdiff (>= 0.4.0, < 2.0.0)
126
- youlend (1.1.4)
128
+ youlend (1.2.0)
127
129
  addressable
128
130
  faraday (~> 2.7)
129
131
  rainbow
@@ -146,4 +148,4 @@ DEPENDENCIES
146
148
  webmock
147
149
 
148
150
  BUNDLED WITH
149
- 2.4.7
151
+ 2.3.10
@@ -40,6 +40,10 @@ module InstantQuote
40
40
  new.get_approval(application_id, connection)
41
41
  end
42
42
 
43
+ def self.accept_offer(application_id, connection, offer_id)
44
+ new.accept_offer(application_id, connection, offer_id)
45
+ end
46
+
43
47
  def get_link(_connection)
44
48
  raise NotImplementedError, "You must implement '#{self.class.name}#get_link'"
45
49
  end
@@ -61,5 +65,9 @@ module InstantQuote
61
65
  def get_approval(_application_id, _connection)
62
66
  OpenStruct.new(success?: true)
63
67
  end
68
+
69
+ def accept_offer(_application_id, _connection, _offer_id)
70
+ true
71
+ end
64
72
  end
65
73
  end
@@ -59,6 +59,10 @@ module InstantQuote
59
59
  adapter.get_approval(connection.external_id, connection)
60
60
  end
61
61
 
62
+ def accept_offer(offer_id)
63
+ adapter.accept_offer(connection.external_id, connection, offer_id)
64
+ end
65
+
62
66
  def additional_fields
63
67
  adapter.additional_fields
64
68
  end
@@ -2,24 +2,12 @@
2
2
 
3
3
  require 'youlend'
4
4
  require 'instant_quote/decision_parsers/youlend'
5
+ require 'instant_quote/connection_translators/youlend_persons'
5
6
 
6
7
  module InstantQuote
7
8
  module Adapters
8
9
  class Youlend < Adapter
9
10
  additional_fields [
10
- {
11
- name: 'monthly_card_revenue',
12
- label: 'Avg. monthly card revenue',
13
- required: true,
14
- type: 'money',
15
- minimum: 1_000
16
- },
17
- {
18
- name: 'monthly_revenue_from_invoices',
19
- label: 'Avg. monthly revenue from invoices',
20
- required: true,
21
- type: 'money'
22
- },
23
11
  {
24
12
  name: 'applicant_consent',
25
13
  label: 'Yes, I give my consent for my data to be shared with YouLend',
@@ -28,48 +16,44 @@ module InstantQuote
28
16
  }
29
17
  ]
30
18
 
31
- # For Youlend we first need to do the pre_qualification and then, if we get a positive
32
- # response we can create the lead. We need to call both endpoints here, so it might take a
33
- # while.
19
+ # For Youlend we first need to create a lead and then update lead
20
+ # since some of the fields are not available in create
34
21
  def get_quote(params, connection)
35
- response = ::Youlend::Lead.create(params)
22
+ data = create_lead(params)
36
23
 
37
- if response.success?
38
- connection.update(link: response.data[:signUpURL])
24
+ update_lead(data[:leadId], params)
25
+ submit_persons(data[:leadId], connection)
39
26
 
40
- return response.data[:leadId]
41
- end
27
+ connection.update(link: data[:signUpURL])
42
28
 
43
- raise_error(response)
29
+ return data[:leadId]
44
30
  end
45
31
 
46
- def get_status(_lead_id, connection)
32
+ def get_status(lead_id, connection)
33
+ details = ::Youlend::Lead.details(lead_id)
34
+ raise_error(details) unless details.success?
35
+
36
+ offers = ::Youlend::Lead.offers(lead_id) if details.body[:onboardingState] == 'offersProvided'
37
+ raise_error(offers) if offers && !offers.success?
38
+
39
+ decision = connection.approval_decision_info.merge(details: details.body)
40
+ decision.merge!(offers: offers&.body) if offers
41
+
42
+ connection.update(approval_decision_info: decision)
47
43
  DecisionParsers::Youlend.new(connection.approval_decision_info)
48
44
  end
49
45
 
46
+ def accept_offer(application_id, _connection, offer_id)
47
+ response = ::Youlend::Lead.accept_offer(application_id, offer_id)
48
+
49
+ response.success?
50
+ end
51
+
50
52
  # Gets the URL for the given state key.
51
53
  def get_link(connection)
52
54
  connection.link
53
55
  end
54
56
 
55
- # Checks if the application is eligible for a loan. If it is we need to update the
56
- # connection's approval_decision_info right away, since it's the only place where we get it.
57
- def get_preapproval(params, connection)
58
- # The preapproval endpoint doesn't work with all params, we need to remove some
59
- params.delete(:companyName)
60
- params.delete(:monthsTrading)
61
- params.delete(:keyContactName)
62
- params.delete(:registeredAddress)
63
- params.delete(:thirdPartyLeadId)
64
- params.delete(:contactPhoneNumber)
65
-
66
- response = ::Youlend::Prequalification.verify(params)
67
-
68
- connection.update(approval_decision_info: response.data) if response.success?
69
-
70
- response
71
- end
72
-
73
57
  private
74
58
 
75
59
  def raise_error(response)
@@ -79,6 +63,26 @@ module InstantQuote
79
63
  error: response.errors.first || '[Youlend] API error'
80
64
  )
81
65
  end
66
+
67
+ def create_lead(params)
68
+ response = ::Youlend::Lead.create(params)
69
+ raise_error(response) unless response.success?
70
+
71
+ response.data
72
+ end
73
+
74
+ def update_lead(lead_id, params)
75
+ response = ::Youlend::Lead.update(lead_id, params)
76
+ raise_error(response) unless response.success?
77
+ end
78
+
79
+ def submit_persons(lead_id, connection)
80
+ people_translator = ConnectionTranslators::YoulendPersons.new(connection)
81
+ people_params = people_translator.translate
82
+
83
+ response = ::Youlend::SignificantPersons.create(lead_id, people_params)
84
+ raise_error(response) unless response.success?
85
+ end
82
86
  end
83
87
  end
84
88
  end
@@ -5,58 +5,31 @@ module InstantQuote
5
5
  CURRENCY = 'GBP'
6
6
 
7
7
  class Youlend < ConnectionTranslator
8
- # rubocop:disable Metrics/AbcSize
9
- def translate
8
+ def translate # rubocop:disable Metrics/AbcSize
10
9
  {
11
10
  companyName: company.name,
12
11
  companyType: company_type_translated,
12
+ companyNumber: company.company_number,
13
13
  monthsTrading: company.total_months_in_business,
14
14
  keyContactName: primary_user_name,
15
15
  thirdPartyLeadId: application.code,
16
- financialData: financial_data,
17
16
  countryISOCode: 'GBR',
18
17
  loanCurrencyISOCode: CURRENCY,
19
- thirdPartyMerchantId: company.id,
20
- thirdPartyCustomerId: application&.primary_user&.id,
18
+ loanAmount: application.amount.to_f,
19
+ thirdPartyCustomerId: application&.primary_user&.id.to_s,
21
20
  registeredAddress: company_address,
22
21
  contactPhoneNumber: extra_info[:applicant_phone_number],
23
- monthlyCardRevenue: extra_info[:monthly_card_revenue].to_i,
24
- contactEmailAddress: application&.primary_user&.email # optional
22
+ contactEmailAddress: application&.primary_user&.email,
23
+ # we need country and description on update
24
+ # but they are ignored in create
25
+ country: 'GBR',
26
+ description: application.description,
27
+ confirmedCreditSearch: true
25
28
  }
26
29
  end
27
- # rubocop:enable Metrics/AbcSize
28
30
 
29
31
  private
30
32
 
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
33
  def company_address
61
34
  address = company.address
62
35
 
@@ -81,13 +54,13 @@ module InstantQuote
81
54
  def company_type_translated
82
55
  case company.company_type.name
83
56
  when /LTD/
84
- 'ltd'
57
+ 'Ltd'
85
58
  when /LLP/
86
- 'llp'
59
+ 'Llp'
87
60
  when /PLC/
88
- 'plc'
61
+ 'Plc'
89
62
  when 'Sole Trader'
90
- 'soleTrader'
63
+ 'SoleTrader'
91
64
  else
92
65
  'other'
93
66
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstantQuote
4
+ module ConnectionTranslators
5
+ class YoulendPersons < ConnectionTranslator
6
+ def translate
7
+ people = application.borrower_organisation.company_people.map do |person|
8
+ translate_person(person)
9
+ end
10
+
11
+ {
12
+ significantPersons: people.select { |p| valid?(p) }
13
+ }
14
+ end
15
+
16
+ private
17
+
18
+ def valid?(person_hash)
19
+ person_hash.values_at(:address, :typeOfPerson, :dateOfBirth, :firstName, :surname)
20
+ .none?(&:nil?)
21
+ end
22
+
23
+ def translate_person(person)
24
+ {
25
+ address: address(person),
26
+ dateOfBirth: date_of_birth(person),
27
+ firstName: person.first_name || person.name,
28
+ surname: person.last_name || person.name,
29
+ typeOfPerson: type_of_person(person),
30
+ emailAddress: application.primary_user.email,
31
+ mobilePhoneNumber: extra_info[:applicant_phone_number],
32
+ percentageOwned: person.shares_percent || 0,
33
+ nationality: person.nationality
34
+ }
35
+ end
36
+
37
+ def address(person)
38
+ address = person.address
39
+ return unless address
40
+
41
+ {
42
+ line1: address.line_1,
43
+ line2: address.line_2,
44
+ city: address.town,
45
+ region: address.town,
46
+ areaCode: address.postcode,
47
+ country: address.country
48
+ }
49
+ end
50
+
51
+ def type_of_person(person)
52
+ if person.director? && person.shareholder?
53
+ 'DirectorAndBeneficialOwner'
54
+ elsif person.director?
55
+ 'Director'
56
+ elsif person.shareholder?
57
+ 'BeneficialOwner'
58
+ end
59
+ end
60
+
61
+ def date_of_birth(person)
62
+ dob = person.birthdate || application.primary_user.birthdate
63
+ return unless dob
64
+
65
+ {
66
+ year: dob.year,
67
+ month: dob.month,
68
+ day: dob.day
69
+ }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,71 +6,145 @@ module InstantQuote
6
6
  module DecisionParsers
7
7
  # Example response:
8
8
  #
9
+ # Details response merged with optional offers
9
10
  # {
10
- # thirdPartyMerchantId: "b97b0834-74e5-4da6-a3df-c8dbe017fe09",
11
- # mid: nil,
12
- # companyName: "Test Company",
13
- # overallCreditRiskScore: 3.5,
14
- # overrideCreditRiskScore: 1.0,
15
- # loanOptions: [
16
- # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" },
17
- # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" },
18
- # { fundedAmount: 0.0, fee: 0.0, loanAmount: 0.0, sweep: 0.0, currencyISOCode: "GBP" }
19
- # ]
11
+ # "details": {
12
+ # "leadSourceLendingPartnerId": "2487faad-24c3-40ed-8e63-f4551555116a",
13
+ # "onboardingState": "offersProvided",
14
+ # "onboardingStateReason": "",
15
+ # "organisationDetailsStepComplete": true,
16
+ # "paymentDataStepComplete": true,
17
+ # "offerStepComplete": false,
18
+ # "kycDocumentsStepComplete": true,
19
+ # "companyName": "SATURDAY LTD",
20
+ # "leadType": "Api",
21
+ # "signingDetails": {
22
+ # "eligibleForInstantSigning": false,
23
+ # "instantSigningDetails": null
24
+ # }
25
+ # },
26
+ # "offers": {
27
+ # "leadId": "5c35a9f4-436e-47b9-a31c-b55f5881f8be",
28
+ # "previousAcceptedOffer": null,
29
+ # "providedOffers": [
30
+ # {
31
+ # "offerId": "12cfda2b-cfb6-4d79-bd16-2979ed6a069a",
32
+ # "youWillGet": 6000,
33
+ # "youWillRepay": 7020,
34
+ # "currencyISOCode": "GBP",
35
+ # "sweep": 20,
36
+ # "minimumWeeklyRepayment": 150.28,
37
+ # "daysUntilRepayment": 218,
38
+ # "maturityDate": "2024-06-26T16:42:15.038Z"
39
+ # },
40
+ # {
41
+ # "offerId": "2d93cb8b-7e3b-4c87-87cf-40e2f7e226e3",
42
+ # "youWillGet": 6500,
43
+ # "youWillRepay": 7735,
44
+ # "currencyISOCode": "GBP",
45
+ # "sweep": 20,
46
+ # "minimumWeeklyRepayment": 150.4,
47
+ # "daysUntilRepayment": 240,
48
+ # "maturityDate": "2024-07-29T16:42:15.038Z"
49
+ # },
50
+ # {
51
+ # "offerId": "f2ef06ef-0c29-418c-ad93-d7abdee5994e",
52
+ # "youWillGet": 7000,
53
+ # "youWillRepay": 8470,
54
+ # "currencyISOCode": "GBP",
55
+ # "sweep": 20,
56
+ # "minimumWeeklyRepayment": 150.1,
57
+ # "daysUntilRepayment": 263,
58
+ # "maturityDate": "2024-09-02T16:42:15.038Z"
59
+ # }
60
+ # ],
61
+ # "acceptedOffer": null,
62
+ # "unsignedLoanDocument": null,
63
+ # "unsignedLoanDocumentDownloaded": false,
64
+ # "documentSigningId": "00000000-0000-0000-0000-000000000000",
65
+ # "sortCode": null,
66
+ # "accountNumber": null,
67
+ # "swift": null,
68
+ # "iban": null,
69
+ # "bankName": null,
70
+ # "confirmedCompanyAccount": false,
71
+ # "minFundingAmount": 0,
72
+ # "maxFundingAmount": 0
73
+ # }
20
74
  # }
75
+ #
21
76
  class Youlend < DecisionParser
77
+ APPROVED_STATES = %w[offersProvided offerAccepted contractgenerated contractsigned]
78
+
22
79
  def approved?
23
- status_hash[:loanOptions].any? && !funded_option.empty?
80
+ APPROVED_STATES.include?(status) && loan_offer.present?
24
81
  end
25
82
 
26
83
  def declined?
27
- !approved?
84
+ !(approved? || incomplete? || submitted? || pending?)
85
+ end
86
+
87
+ def pending?
88
+ data.nil? || data.empty?
89
+ end
90
+
91
+ def submitted?
92
+ status == 'stage1submitted'
93
+ end
94
+
95
+ def incomplete?
96
+ status == 'stage1incomplete'
28
97
  end
29
98
 
30
99
  def sweep
31
100
  return '-' unless approved?
32
101
 
33
- funded_option[:sweep]
102
+ loan_offer[:sweep]
34
103
  end
35
104
 
36
105
  def status
37
106
  approved? ? 'approved' : 'declined'
38
107
  end
39
108
 
40
- def fee
41
- fee = funded_option[:fee].to_f || 0
42
-
43
- Money.new(fee * 100).format
44
- end
45
-
46
109
  def loan_amount
47
- amount = funded_option[:loanAmount].to_f || 0
48
-
49
- Money.new(amount * 100).format
110
+ Money.new(raw_loan_amount * 100).format
50
111
  end
51
112
 
52
113
  def funded_amount
53
- amount = funded_option[:fundedAmount].to_f || 0
114
+ Money.new(raw_funded_amount * 100).format
115
+ end
116
+
117
+ def fee
118
+ Money.new(raw_fee_amount * 100).format
119
+ end
54
120
 
55
- Money.new(amount * 100).format
121
+ def offer_id
122
+ loan_offer[:offerId]
56
123
  end
57
124
 
58
125
  alias amount funded_amount
59
126
 
60
127
  private
61
128
 
62
- def funded_option
63
- @funded_option ||= status_hash[:loanOptions].find(-> { {} }) do |opt|
64
- opt[:fundedAmount].positive?
65
- end
129
+ # Ideally we want to give the borrower the highest amount on offer.
130
+ def loan_offer
131
+ @loan_offer ||= (data.dig(:offers, :providedOffers) || []).max_by { |offer| offer[:youWillGet].to_f }
66
132
  end
67
133
 
68
- def status_hash
69
- default = ActiveSupport::HashWithIndifferentAccess.new({ loanOptions: [] })
134
+ def raw_fee_amount
135
+ raw_loan_amount - raw_funded_amount
136
+ end
70
137
 
71
- return default unless data && !data.empty? && !data[:error]
138
+ def raw_funded_amount
139
+ loan_offer&.dig(:youWillGet)&.to_f || 0
140
+ end
72
141
 
73
- default.merge(data)
142
+ def raw_loan_amount
143
+ loan_offer&.dig(:youWillRepay)&.to_f || 0
144
+ end
145
+
146
+ def status
147
+ data.dig(:details, :onboardingState)
74
148
  end
75
149
  end
76
150
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InstantQuote
4
- VERSION = '1.7.33'
4
+ VERSION = '1.7.34'
5
5
  end
data/lib/instant_quote.rb CHANGED
@@ -37,6 +37,10 @@ module InstantQuote
37
37
  end
38
38
  end
39
39
 
40
+ def accept_offer(connection, offer_id)
41
+ AdapterFinder.new(connection).accept_offer(offer_id)
42
+ end
43
+
40
44
  def available_adapters
41
45
  AdapterFinder.available_adapters
42
46
  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.7.33
4
+ version: 1.7.34
5
5
  platform: ruby
6
6
  authors:
7
7
  - rikas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-13 00:00:00.000000000 Z
11
+ date: 2023-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -150,6 +150,7 @@ files:
150
150
  - lib/instant_quote/connection_translators/iwoca_revenue_based_loans.rb
151
151
  - lib/instant_quote/connection_translators/optimum.rb
152
152
  - lib/instant_quote/connection_translators/youlend.rb
153
+ - lib/instant_quote/connection_translators/youlend_persons.rb
153
154
  - lib/instant_quote/decision_parser.rb
154
155
  - lib/instant_quote/decision_parsers/capital_on_tap.rb
155
156
  - lib/instant_quote/decision_parsers/fake.rb
@@ -183,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
184
  - !ruby/object:Gem::Version
184
185
  version: '0'
185
186
  requirements: []
186
- rubygems_version: 3.2.3
187
+ rubygems_version: 3.0.8
187
188
  signing_key:
188
189
  specification_version: 4
189
190
  summary: Instant quote providers gem.