Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-14010 - More detailed error page for Socure errors #11560

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
20 changes: 16 additions & 4 deletions app/controllers/concerns/idv/document_capture_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def handle_stored_result(user: current_user, store_in_session: true)
successful_response
else
extra = { stored_result_present: stored_result.present? }
failure(I18n.t('doc_auth.errors.general.network_error'), extra)
failure(nil, extra)
end
end

Expand All @@ -33,13 +33,25 @@ def successful_response
end

# copied from Flow::Failure module
def failure(message, extra = nil)
flash[:error] = message
form_response_params = { success: false, errors: { message: message } }
def failure(message = nil, extra = nil)
form_response_params = { success: false }
form_response_params[:errors] = make_error_hash(message)
form_response_params[:extra] = extra unless extra.nil?
FormResponse.new(**form_response_params)
end

def make_error_hash(message)
Rails.logger.info("make_error_hash: stored_result: #{stored_result.inspect}")

error_hash = { message: message || I18n.t('doc_auth.errors.general.network_error') }

if stored_result&.errors&.has_key?(:socure)
error_hash[:socure] = stored_result.errors[:socure]
end

error_hash
end

def extract_pii_from_doc(user, store_in_session: false)
if defined?(idv_session) # hybrid mobile does not have idv_session
idv_session.had_barcode_read_failure = stored_result.attention_with_barcode?
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/concerns/idv/socure_errors_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Idv
module SocureErrorsConcern
def errors
@presenter = socure_errors_presenter(handle_stored_result)
end

def goto_in_person
InPersonEnrollment.find_or_initialize_by(
user: document_capture_session.user,
status: :establishing,
sponsor_id: IdentityConfig.store.usps_ipp_sponsor_id,
).save!

redirect_to idv_in_person_url
end

def remaining_attempts
RateLimiter.new(
user: document_capture_session.user,
rate_limit_type: :idv_doc_auth,
).remaining_count
end
end
end
3 changes: 2 additions & 1 deletion app/controllers/idv/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class DocumentCaptureController < ApplicationController
before_action :confirm_step_allowed, unless: -> { allow_direct_ipp? }
before_action :override_csp_to_allow_acuant
before_action :set_usps_form_presenter
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, false) }
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, false) },
only: :show

def show
analytics.idv_doc_auth_document_capture_visited(**analytics_arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class DocumentCaptureController < ApplicationController
before_action :override_csp_to_allow_acuant
before_action :confirm_document_capture_needed, only: :show
before_action :set_usps_form_presenter
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, true) }
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, true) },
only: :show

def show
analytics.idv_doc_auth_document_capture_visited(**analytics_arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ class DocumentCaptureController < ApplicationController
include DocumentCaptureConcern
include Idv::HybridMobile::HybridMobileConcern
include RenderConditionConcern
include SocureErrorsConcern

check_or_render_not_found -> { IdentityConfig.store.socure_docv_enabled }
before_action :check_valid_document_capture_session, except: [:update]
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, true) }
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, true) },
only: :show

def show
Funnel::DocAuth::RegisterStep.new(document_capture_user.id, sp_session[:issuer]).
Expand Down Expand Up @@ -63,12 +65,21 @@ def update
if result.success?
redirect_to idv_hybrid_mobile_capture_complete_url
else
redirect_to idv_hybrid_mobile_socure_document_capture_url
redirect_to idv_hybrid_mobile_socure_document_capture_errors_url
end
end

private

def socure_errors_presenter(result)
SocureErrorPresenter.new(
error_code: result.errors[:socure][:reason_codes]&.first,
remaining_attempts: remaining_attempts,
sp_name: decorated_sp_session&.sp_name || APP_NAME,
hybrid_mobile: true,
)
end

def wait_for_result?
return false if stored_result.present?

Expand Down
15 changes: 13 additions & 2 deletions app/controllers/idv/socure/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ class DocumentCaptureController < ApplicationController
include IdvStepConcern
include DocumentCaptureConcern
include RenderConditionConcern
include SocureErrorsConcern

check_or_render_not_found -> { IdentityConfig.store.socure_docv_enabled }
before_action :confirm_not_rate_limited
before_action :confirm_step_allowed
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, false) }
before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, false) },
only: :show

# reconsider and maybe remove these when implementing the real
# update handler
Expand Down Expand Up @@ -79,7 +81,7 @@ def update
if result.success?
redirect_to idv_ssn_url
else
redirect_to idv_socure_document_capture_url
redirect_to idv_socure_document_capture_errors_url
end
end

Expand Down Expand Up @@ -107,6 +109,15 @@ def self.step_info

private

def socure_errors_presenter(result)
SocureErrorPresenter.new(
error_code: result.errors[:socure][:reason_codes]&.first,
remaining_attempts: remaining_attempts,
sp_name: decorated_sp_session&.sp_name || APP_NAME,
hybrid_mobile: false,
)
end

def wait_for_result?
return false if stored_result.present?

Expand Down
2 changes: 2 additions & 0 deletions app/models/document_capture_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def store_result_from_response(doc_auth_response)
session_result.attention_with_barcode = doc_auth_response.attention_with_barcode?
session_result.doc_auth_success = doc_auth_response.doc_auth_success?
session_result.selfie_status = doc_auth_response.selfie_status
session_result.errors = doc_auth_response.errors

EncryptedRedisStructStorage.store(
session_result,
expires_in: IdentityConfig.store.doc_capture_request_valid_for_minutes.minutes.in_seconds,
Expand Down
160 changes: 160 additions & 0 deletions app/presenters/socure_error_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# frozen_string_literal: true

class SocureErrorPresenter
include Rails.application.routes.url_helpers
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TranslationHelper
include LinkHelper

attr_reader :url_options

def initialize(error_code:, remaining_attempts:, sp_name:, hybrid_mobile:)
@error_code = error_code
@remaining_attempts = remaining_attempts
@sp_name = sp_name
@hybrid_mobile = hybrid_mobile
@url_options = {}
end

def heading
heading_string_for(error_code)
end

def body_text
error_string_for(error_code)
end

def rate_limit_text
if remaining_attempts == 1
t('doc_auth.rate_limit_warning.singular_html')
else
t('doc_auth.rate_limit_warning.plural_html', remaining_attempts: remaining_attempts)
end
end

def action
url = hybrid_mobile ? idv_hybrid_mobile_socure_document_capture_path
: idv_socure_document_capture_path
{
text: I18n.t('idv.failure.button.warning'),
url: url,
}
end

def secondary_action_heading
I18n.t('in_person_proofing.headings.cta')
end

def secondary_action_text
I18n.t('in_person_proofing.body.cta.prompt_detail')
end

def secondary_action
url = hybrid_mobile ? idv_hybrid_mobile_socure_document_capture_goto_in_person_path
: idv_socure_document_capture_goto_in_person_path

{
text: I18n.t('in_person_proofing.body.cta.button'),
url: url,
}
end

def troubleshooting_heading
I18n.t('components.troubleshooting_options.ipp_heading')
end

def options
[
{
url: help_center_redirect_path(
category: 'verify-your-identity',
article: 'how-to-add-images-of-your-state-issued-id',
),
text: I18n.t('idv.troubleshooting.options.doc_capture_tips'),
isExternal: true,
},
{
url: help_center_redirect_path(
category: 'verify-your-identity',
article: 'accepted-identification-documents',
),
text: I18n.t('idv.troubleshooting.options.supported_documents'),
isExternal: true,
},
{
url: return_to_sp_failure_to_proof_url(step: 'document_capture'),
text: t(
'idv.failure.verify.fail_link_html',
sp_name: sp_name,
),
isExternal: true,
},
]
end

def step_indicator_steps
Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS
end

private

attr_reader :error_code, :remaining_attempts, :sp_name, :hybrid_mobile

SOCURE_ERROR_MAP = {
'I848' => 'unreadable_id',
'I854' => 'unreadable_id',
'R810' => 'unreadable_id',
'R820' => 'unreadable_id',
'R822' => 'unreadable_id',
'R823' => 'unreadable_id',
'R824' => 'unreadable_id',
'R825' => 'unreadable_id',
'R826' => 'unreadable_id',
'R831' => 'unreadable_id',
'R833' => 'unreadable_id',
'R838' => 'unreadable_id',
'R859' => 'unreadable_id',
'R861' => 'unreadable_id',
'R863' => 'unreadable_id',

'I849' => 'unaccepted_id_type',
'R853' => 'unaccepted_id_type',
'R862' => 'unaccepted_id_type',

'R827' => 'expired_id',

'I808' => 'low_resolution',

'R845' => 'underage',

'I856' => 'id_not_found',
'R819' => 'id_not_found',
}.freeze

def remapped_error(error_code)
SOCURE_ERROR_MAP[error_code] || 'unreadable_id'
end

def heading_string_for(error_code)
# i18n-tasks-use t('doc_auth.headers.unreadable_id')
# i18n-tasks-use t('doc_auth.headers.unaccepted_id_type')
# i18n-tasks-use t('doc_auth.headers.expired_id')
# i18n-tasks-use t('doc_auth.headers.low_resolution')
# i18n-tasks-use t('doc_auth.headers.underage')
# i18n-tasks-use t('doc_auth.headers.id_not_found')
I18n.t("doc_auth.headers.#{remapped_error(error_code)}")
end

def error_string_for(error_code)
if remapped_error(error_code) == 'underage' # special handling because it says 'Login.gov'
I18n.t('doc_auth.errors.underage', app_name: APP_NAME)
else
# i18n-tasks-use t('doc_auth.errors.unreadable_id')
# i18n-tasks-use t('doc_auth.errors.unaccepted_id_type')
# i18n-tasks-use t('doc_auth.errors.expired_id')
# i18n-tasks-use t('doc_auth.errors.low_resolution')
# i18n-tasks-use t('doc_auth.errors.id_not_found')
I18n.t("doc_auth.errors.#{remapped_error(error_code)}")
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DocvResultResponse < DocAuth::Response

def initialize(http_response:, biometric_comparison_required: false)
@http_response = http_response

@biometric_comparison_required = biometric_comparison_required
@pii_from_doc = read_pii

Expand Down Expand Up @@ -73,7 +74,7 @@ def error_messages
return {} if successful_result?

{
reason_codes: get_data(DATA_PATHS[:reason_codes]),
socure: { reason_codes: get_data(DATA_PATHS[:reason_codes]) },
}
end

Expand Down
6 changes: 4 additions & 2 deletions app/services/document_capture_session_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
:failed_back_image_fingerprints,
:failed_selfie_image_fingerprints,
:captured_at,
:doc_auth_success, :selfie_status,
:doc_auth_success,
:selfie_status,
:errors,
keyword_init: true,
allowed_members: [:id, :success, :attention_with_barcode, :failed_front_image_fingerprints,
:failed_back_image_fingerprints, :failed_selfie_image_fingerprints,
:captured_at, :doc_auth_success, :selfie_status]
:captured_at, :doc_auth_success, :selfie_status, :errors],
) do
include DocAuth::SelfieConcern

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<%= render(
'idv/shared/error',
type: :warning,
heading: @presenter.heading,
action: @presenter.action,
secondary_action: @presenter.secondary_action,
current_step: :verify_id,
troubleshooting_heading: @presenter.troubleshooting_heading,
secondary_action_heading: @presenter.secondary_action_heading,
secondary_action_text: @presenter.secondary_action_text,
options: @presenter.options,
step_indicator_steps: @presenter.step_indicator_steps,
) do
%>
<p><%= @presenter.body_text %></p>
<p><%= @presenter.rate_limit_text %></p>
<% end %>
2 changes: 1 addition & 1 deletion app/views/idv/session_errors/state_id_warning.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
text: t('idv.warning.state_id.try_again_button'),
url: @try_again_path,
},
action_secondary: {
secondary_action: {
text: t('idv.warning.state_id.cancel_button', app_name: APP_NAME),
url: return_to_sp_failure_to_proof_url(step: 'verify_info', location: 'state_id_warning'),
},
Expand Down
Loading