Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .reek
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ NilCheck:
LongParameterList:
exclude:
- IdentityLinker#optional_attributes
- VendorValidatorJob#perform
RepeatedConditional:
exclude:
- Users::ResetPasswordsController
Expand Down
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Metrics/ModuleLength:
- spec/**/*
- 'app/controllers/concerns/two_factor_authenticatable.rb'

Metrics/ParameterLists:
CountKeywordArgs: false

# This is a Rails 5 feature, so it should be disabled until we upgrade
Rails/HttpPositionalArguments:
Description: 'Use keyword arguments instead of positional arguments in http method calls.'
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/concerns/idv_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ def idv_vendor
def idv_attempter
@_idv_attempter ||= Idv::Attempter.new(current_user)
end

def vendor_validator_result
VendorValidatorResultStorage.new.load(idv_session.async_result_id)
end
end
11 changes: 10 additions & 1 deletion app/controllers/verify/finance_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class FinanceController < ApplicationController
before_action :confirm_step_needed
before_action :confirm_step_allowed
before_action :submit_idv_form, only: [:create]
before_action :submit_idv_job, only: [:create]

def new
@view_model = view_model
Expand Down Expand Up @@ -37,6 +38,14 @@ def submit_idv_form
render_form
end

def submit_idv_job
SubmitIdvJob.new(
vendor_validator_class: Idv::FinancialsValidator,
idv_session: idv_session,
vendor_params: vendor_params
).call
end

def step_name
:financials
end
Expand Down Expand Up @@ -66,7 +75,7 @@ def step
@_step ||= Idv::FinancialsStep.new(
idv_form_params: idv_form.idv_params,
idv_session: idv_session,
vendor_params: vendor_params
vendor_validator_result: vendor_validator_result
)
end

Expand Down
11 changes: 10 additions & 1 deletion app/controllers/verify/phone_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class PhoneController < ApplicationController
before_action :confirm_step_needed
before_action :confirm_step_allowed
before_action :submit_idv_form, only: [:create]
before_action :submit_idv_job, only: [:create]

def new
@view_model = view_model
Expand Down Expand Up @@ -37,6 +38,14 @@ def submit_idv_form
render :new
end

def submit_idv_job
SubmitIdvJob.new(
vendor_validator_class: Idv::PhoneValidator,
idv_session: idv_session,
vendor_params: idv_form.phone
).call
end

def step_name
:phone
end
Expand All @@ -45,7 +54,7 @@ def step
@_step ||= Idv::PhoneStep.new(
idv_session: idv_session,
idv_form_params: idv_form.idv_params,
vendor_params: idv_form.phone
vendor_validator_result: vendor_validator_result
)
end

Expand Down
11 changes: 10 additions & 1 deletion app/controllers/verify/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class SessionsController < ApplicationController
before_action :confirm_step_needed, except: [:destroy]
before_action :initialize_idv_session, only: [:create]
before_action :submit_idv_form, only: [:create]
before_action :submit_idv_job, only: [:create]

delegate :attempts_exceeded?, to: :step, prefix: true

Expand Down Expand Up @@ -44,6 +45,14 @@ def submit_idv_form
process_failure unless result.success?
end

def submit_idv_job
SubmitIdvJob.new(
vendor_validator_class: Idv::ProfileValidator,
idv_session: idv_session,
vendor_params: idv_session.vendor_params
).call
end

def step_name
:sessions
end
Expand All @@ -56,7 +65,7 @@ def step
@_step ||= Idv::ProfileStep.new(
idv_form_params: profile_params,
idv_session: idv_session,
vendor_params: idv_session.vendor_params
vendor_validator_result: vendor_validator_result
)
end

Expand Down
37 changes: 37 additions & 0 deletions app/jobs/vendor_validator_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class VendorValidatorJob < ActiveJob::Base
queue_as :idv

def perform(result_id:, vendor_validator_class:, vendor:, vendor_params:, applicant_json:,
vendor_session_id:)
vendor_validator = vendor_validator_class.constantize.new(
applicant: Proofer::Applicant.new(JSON.parse(applicant_json, symbolize_names: true)),
vendor: vendor,
vendor_params: indifferent_access(vendor_params),
vendor_session_id: vendor_session_id
)

VendorValidatorResultStorage.new.store(
result_id: result_id,
result: extract_result(vendor_validator.result)
)
end

private

def extract_result(result)
vendor_resp = result.vendor_resp

Idv::VendorResult.new(
success: result.success?,
errors: result.errors,
reasons: vendor_resp.reasons,
normalized_applicant: vendor_resp.try(:normalized_applicant),
session_id: result.try(:session_id)
)
end

def indifferent_access(params)
return params if params.is_a?(String)
params.with_indifferent_access
end
end
4 changes: 0 additions & 4 deletions app/services/idv/financials_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,5 @@ def submit
def complete?
vendor_validation_passed?
end

def vendor_validator_class
Idv::FinancialsValidator
end
end
end
4 changes: 0 additions & 4 deletions app/services/idv/phone_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ def complete?
vendor_validation_passed?
end

def vendor_validator_class
Idv::PhoneValidator
end

def update_idv_session
idv_session.phone_confirmation = true
idv_session.address_verification_mechanism = :phone
Expand Down
4 changes: 0 additions & 4 deletions app/services/idv/profile_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ def increment_attempts_count
attempter.increment
end

def vendor_validator_class
Idv::ProfileValidator
end

def update_idv_session
idv_session.profile_confirmation = true
idv_session.vendor_session_id = vendor_validator_result.session_id
Expand Down
1 change: 1 addition & 0 deletions app/services/idv/session.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Idv
class Session
VALID_SESSION_ATTRIBUTES = %i[
async_result_id
address_verification_mechanism
applicant
financials_confirmation
Expand Down
35 changes: 3 additions & 32 deletions app/services/idv/step.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# abstract base class for Idv Steps
module Idv
class Step
def initialize(idv_session:, idv_form_params:, vendor_params:)
def initialize(idv_session:, idv_form_params:, vendor_validator_result:)
@idv_form_params = idv_form_params
@idv_session = idv_session
@vendor_params = vendor_params
@vendor_validator_result = vendor_validator_result
end

def vendor_validation_passed?
Expand All @@ -14,23 +14,7 @@ def vendor_validation_passed?
private

attr_accessor :idv_session
attr_reader :idv_form_params, :vendor_params

def vendor_validator_result
@_vendor_validator_result ||= extract_vendor_result(vendor_validator.result)
end

def extract_vendor_result(result)
vendor_resp = result.vendor_resp

Idv::VendorResult.new(
success: result.success?,
errors: result.errors,
reasons: vendor_resp.reasons,
normalized_applicant: vendor_resp.try(:normalized_applicant),
session_id: result.try(:session_id)
)
end
attr_reader :idv_form_params, :vendor_validator_result

def errors
@_errors ||= begin
Expand All @@ -39,18 +23,5 @@ def errors
end
end
end

def idv_vendor
@_idv_vendor ||= Idv::Vendor.new
end

def vendor_validator
@_vendor_validator ||= vendor_validator_class.new(
applicant: idv_session.applicant,
vendor: (idv_session.vendor || idv_vendor.pick),
vendor_params: vendor_params,
vendor_session_id: idv_session.vendor_session_id
)
end
end
end
3 changes: 2 additions & 1 deletion app/services/idv/vendor_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ def self.new_from_json(json)
new(**parsed)
end

def initialize(success:, errors:, reasons:, session_id:, normalized_applicant:)
def initialize(success: nil, errors: nil, reasons: nil, session_id: nil,
normalized_applicant: nil)
@success = success
@errors = errors
@reasons = reasons
Expand Down
32 changes: 32 additions & 0 deletions app/services/submit_idv_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class SubmitIdvJob
def initialize(vendor_validator_class:, idv_session:, vendor_params:)
@vendor_validator_class = vendor_validator_class
@idv_session = idv_session
@vendor_params = vendor_params
end

def call
idv_session.async_result_id = result_id

VendorValidatorJob.perform_now(
result_id: result_id,
vendor_validator_class: vendor_validator_class.to_s,
vendor: vendor,
vendor_params: vendor_params,
vendor_session_id: idv_session.vendor_session_id,
applicant_json: idv_session.applicant.to_json
)
end

private

attr_reader :vendor_validator_class, :idv_session, :vendor_params

def result_id
@_result_id ||= SecureRandom.uuid
end

def vendor
idv_session.vendor || Idv::Vendor.new.pick
end
end
24 changes: 24 additions & 0 deletions app/services/vendor_validator_result_storage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class VendorValidatorResultStorage
TTL = Figaro.env.session_timeout_in_minutes.to_i.minutes.seconds.to_i
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this TTL get updated or only set once when first called? I'm thinking about the scenario where the user starts proofing at 12:00, this TTL is then set to expire at 12:08, but the user takes longer than that to finish proofing. For example, after they submit the first profile page, let's say the vendor takes a minute to respond. Then, the user stays idle for 7 minutes on the financial info page because they had to go find their credit card. It's now 12:08, but the session is still active because their last request was only 7 minutes ago. So then they enter their credit card, but now the Redis TTL has expired. Will this cause any issues?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the TTL for the result value of one job.

So for the 3 steps, they will have 3 separate result objects that will have 3 separate expirations.

When this is asynchronous, the result is created after hitting submit and hearing back from the vendor. So as long as the user's browser takes less than this time to refresh the page (which it definitely will, I think we'll set refresh interval to something like 10-15 seconds) then the controller will pick up the result from redis and use it for what it needs to be used and move on to the next step.


def store(result_id:, result:)
Sidekiq.redis do |redis|
redis.setex(redis_key(result_id), TTL, result.to_json)
end
end

def load(result_id)
result_json = Sidekiq.redis do |redis|
redis.get(redis_key(result_id))
end

return unless result_json

Idv::VendorResult.new_from_json(result_json)
end

# @api private
def redis_key(result_id)
"vendor-validator-result-#{result_id}"
end
end
1 change: 1 addition & 0 deletions config/sidekiq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
- voice
- mailers
- analytics
- idv
:logfile: 'log/sidekiq.log'
44 changes: 44 additions & 0 deletions spec/jobs/vendor_validator_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'rails_helper'

RSpec.describe VendorValidatorJob do
let(:result_id) { SecureRandom.uuid }
let(:vendor_validator_class) { 'Idv::PhoneValidator' }
let(:vendor) { :mock }
let(:vendor_params) { '+1 (888) 123-4567' }
let(:applicant) { Proofer::Applicant.new(first_name: 'Test') }
let(:applicant_json) { applicant.to_json }
let(:vendor_session_id) { SecureRandom.uuid }

subject(:job) { VendorValidatorJob.new }

describe '#perform' do
subject(:perform) do
job.perform(
result_id: result_id,
vendor_validator_class: vendor_validator_class,
vendor: vendor,
vendor_params: vendor_params,
applicant_json: applicant_json,
vendor_session_id: vendor_session_id
)
end

it 'calls out to a vendor and serializes the result' do
expect(Idv::PhoneValidator).to receive(:new).
with(
applicant: kind_of(Proofer::Applicant),
vendor: vendor,
vendor_params: vendor_params,
vendor_session_id: vendor_session_id
).and_call_original

before_result = VendorValidatorResultStorage.new.load(result_id)
expect(before_result).to be_nil

perform

after_result = VendorValidatorResultStorage.new.load(result_id)
expect(after_result).to be_a(Idv::VendorResult)
end
end
end
Loading