Skip to content

Commit

Permalink
LG-15337: Socure Try again page for hybrid mobile flow (#11734)
Browse files Browse the repository at this point in the history
LG-15337: Try again page for hybrid mobile flow

If the system times out waiting for the data from Socure,
show the try again page with, if available, an option to switch to
in person proofing.

changelog: Upcoming Features, Identity Verification, Socure timeout provides hybrid users with options.

* working Try Again feature spec
* failing feature spec for opting in to IPP from socure hybrid timeout
* allows direct to IPP in hybrid flow
* fix the IPP `Back` link redirect back to Socure timeout page
* Back link from IPP correctly returns to timeout page in both standard and hybrid flows

---------

Co-authored-by: John Maxwell <[email protected]>
Co-authored-by: Gina Yamada <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2025
1 parent 41f8d41 commit 72d57a3
Show file tree
Hide file tree
Showing 25 changed files with 299 additions and 44 deletions.
2 changes: 1 addition & 1 deletion app/controllers/concerns/idv/document_capture_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def selfie_requirement_met?
stored_result.selfie_check_performed?
end

def redirect_to_correct_vendor(vendor, in_hybrid_mobile)
def redirect_to_correct_vendor(vendor, in_hybrid_mobile:)
return if IdentityConfig.store.doc_auth_redirect_to_correct_vendor_disabled

expected_doc_auth_vendor = doc_auth_vendor
Expand Down
8 changes: 6 additions & 2 deletions app/controllers/idv/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ 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) },
only: [:show], unless: -> { allow_direct_ipp? }
before_action -> do
redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, in_hybrid_mobile: false)
end, only: [:show], unless: -> { allow_direct_ipp? }

def show
analytics.idv_doc_auth_document_capture_visited(**analytics_arguments)
Expand Down Expand Up @@ -48,6 +49,7 @@ def update
def direct_in_person
attributes = {
remaining_submit_attempts: rate_limiter.remaining_count,
flow_path: :standard,
}.merge(ab_test_analytics_buckets)
analytics.idv_in_person_direct_start(**attributes)

Expand Down Expand Up @@ -90,8 +92,10 @@ def extra_view_variables
failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'),
skip_doc_auth_from_how_to_verify: idv_session.skip_doc_auth_from_how_to_verify,
skip_doc_auth_from_handoff: idv_session.skip_doc_auth_from_handoff,
skip_doc_auth_from_socure: idv_session.skip_doc_auth_from_socure,
opted_in_to_in_person_proofing: idv_session.opted_in_to_in_person_proofing,
doc_auth_selfie_capture: resolved_authn_context_result.facial_match?,
socure_errors_timeout_url: idv_socure_errors_timeout_url,
}.merge(
acuant_sdk_upgrade_a_b_testing_variables,
)
Expand Down
36 changes: 34 additions & 2 deletions app/controllers/idv/hybrid_mobile/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ 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) },
only: :show
before_action -> do
redirect_to_correct_vendor(Idp::Constants::Vendors::LEXIS_NEXIS, in_hybrid_mobile: true)
end, only: [:show], unless: -> { allow_direct_ipp? }

def show
analytics.idv_doc_auth_document_capture_visited(**analytics_arguments)
Expand Down Expand Up @@ -43,13 +44,27 @@ def update
end
end

# Given that the start of the IPP flow is in the TrueID doc_auth React app,
# we need a generic, direct way to start the IPP flow
def direct_in_person
attributes = {
remaining_submit_attempts: rate_limiter.remaining_count,
flow_path: :hybrid,
}.merge(ab_test_analytics_buckets)
analytics.idv_in_person_direct_start(**attributes)

redirect_to idv_hybrid_mobile_document_capture_url(step: :idv_doc_auth)
end

def extra_view_variables
{
flow_path: 'hybrid',
mock_client: doc_auth_vendor == 'mock',
document_capture_session_uuid: document_capture_session_uuid,
failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'),
doc_auth_selfie_capture: resolved_authn_context_result.facial_match?,
skip_doc_auth_from_socure: @skip_doc_auth_from_socure,
socure_errors_timeout_url: idv_hybrid_mobile_socure_errors_timeout_url,
}.merge(
acuant_sdk_upgrade_a_b_testing_variables,
)
Expand All @@ -69,6 +84,19 @@ def analytics_arguments
)
end

def allow_direct_ipp?
return false if params[:step].blank?
return false if params[:action].to_s != 'show' && params[:action] != 'direct_in_person'
# Only allow direct access to document capture if IPP available
return false unless IdentityConfig.store.in_person_doc_auth_button_enabled &&
Idv::InPersonConfig.enabled_for_issuer?(sp_session[:issuer])

# allow
@previous_step_url = params[:step] == 'hybrid_handoff' ? idv_hybrid_handoff_path : nil
@skip_doc_auth_from_socure = true
true
end

def confirm_document_capture_needed
return unless stored_result&.success?
return if redo_document_capture_pending?
Expand All @@ -86,6 +114,10 @@ def redo_document_capture_pending?
def set_usps_form_presenter
@presenter = Idv::InPerson::UspsFormPresenter.new
end

def rate_limiter
RateLimiter.new(user: document_capture_user, rate_limit_type: :idv_doc_auth)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class DocumentCaptureController < ApplicationController

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) },
only: :show
before_action -> do
redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, in_hybrid_mobile: true)
end, only: :show
before_action :fetch_test_verification_data, only: [:update]

def show
Expand Down Expand Up @@ -97,9 +98,7 @@ def wait_for_result?
# If the stored_result is nil, the job fetching the results has not completed.
analytics.idv_doc_auth_document_capture_polling_wait_visited(**analytics_arguments)
if wait_timed_out?
# flash[:error] = I18n.t('errors.doc_auth.polling_timeout')
# TODO: redirect to try again page LG-14873/14952/15059
render plain: 'Technical difficulties!!!', status: :ok
redirect_to idv_hybrid_mobile_socure_errors_timeout_path
else
@refresh_interval =
IdentityConfig.store.doc_auth_socure_wait_polling_refresh_max_seconds
Expand Down
73 changes: 73 additions & 0 deletions app/controllers/idv/hybrid_mobile/socure/errors_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

module Idv
module HybridMobile
module Socure
class ErrorsController < ApplicationController
include DocumentCaptureConcern
include HybridMobileConcern
include AvailabilityConcern
include StepIndicatorConcern
include SocureErrorsConcern

def show
error_code = error_code_for(handle_stored_result)
track_event(error_code: error_code)
@presenter = socure_errors_presenter(error_code)
end

def timeout
track_event(error_code: :timeout)
@presenter = socure_errors_presenter(:timeout)
render :show
end

def self.step_info
Idv::StepInfo.new(
key: :hybrid_socure_errors,
controller: self,
action: :timeout,
next_steps: [FlowPolicy::FINAL],
preconditions: ->(idv_session:, user:) do
true
end,
undo_step: ->(idv_session:, user:) {},
)
end

private

def rate_limiter
RateLimiter.new(user: document_capture_session&.user, rate_limit_type: :idv_doc_auth)
end

def remaining_submit_attempts
@remaining_submit_attempts ||= rate_limiter.remaining_count
end

def track_event(error_code:)
attributes = {
error_code:,
remaining_submit_attempts:,
}

analytics.idv_doc_auth_socure_error_visited(**attributes)
end

def socure_errors_presenter(error_code)
SocureErrorPresenter.new(
error_code:,
remaining_attempts: remaining_submit_attempts,
sp_name: service_provider&.friendly_name || APP_NAME,
issuer: service_provider&.issuer,
flow_path: :hybrid,
)
end

def service_provider
@service_provider ||= ServiceProvider.find_by(issuer: document_capture_session&.issuer)
end
end
end
end
end
5 changes: 3 additions & 2 deletions app/controllers/idv/socure/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class DocumentCaptureController < ApplicationController
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) },
only: :show
before_action -> do
redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, in_hybrid_mobile: false)
end, only: :show
before_action :fetch_test_verification_data, only: [:update]

# reconsider and maybe remove these when implementing the real
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/idv/socure/errors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def self.step_info
action: :timeout,
next_steps: [FlowPolicy::FINAL],
preconditions: ->(idv_session:, user:) do
# idv_session.socure_docv_wait_polling_started_at.present?
true
end,
undo_step: ->(idv_session:, user:) {},
Expand All @@ -55,6 +54,9 @@ def track_event(error_code:)
end

def socure_errors_presenter(error_code)
# There really isn't a good place to set this
idv_session.skip_doc_auth_from_socure = true if flow_path == 'standard'

SocureErrorPresenter.new(
error_code:,
remaining_attempts: remaining_submit_attempts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
const { flowPath } = useContext(UploadContext);
const { trackSubmitEvent, trackVisitEvent } = useContext(AnalyticsContext);
const { isSelfieCaptureEnabled } = useContext(SelfieCaptureContext);
const { inPersonURL, skipDocAuthFromHandoff, skipDocAuthFromHowToVerify } =
const { inPersonURL, skipDocAuthFromHandoff, skipDocAuthFromHowToVerify, skipDocAuthFromSocure } =
useContext(InPersonContext);
useDidUpdateEffect(onStepChange, [stepName]);
useEffect(() => {
Expand Down Expand Up @@ -135,7 +135,8 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
}
// If the user got here by opting-in to in-person proofing, when skipDocAuthFromHowToVerify === true
// then set steps to inPersonSteps
const isInPersonStepEnabled = skipDocAuthFromHowToVerify || skipDocAuthFromHandoff;
const isInPersonStepEnabled =
skipDocAuthFromHowToVerify || skipDocAuthFromHandoff || skipDocAuthFromSocure;
const inPersonSteps: FormStep[] =
inPersonURL === undefined
? []
Expand All @@ -150,13 +151,13 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
steps = [reviewFormStep, ...inPersonSteps];
}
// If the user got here by opting-in to in-person proofing, when skipDocAuthFromHowToVerify === true
// or opting-in ipp from handoff page, and selfie is required, when skipDocAuthFromHandoff === true
// or opting-in ipp from handoff page, and selfie is required, when skipDocAuthFromHandoff === true,
// or opting-in ipp from socure hybrid, when skipDocAuthFromSocure === true,
// then set stepIndicatorPath to VerifyFlowPath.IN_PERSON
const stepIndicatorPath =
(stepName && ['location', 'prepare', 'switch_back'].includes(stepName)) || isInPersonStepEnabled
? VerifyFlowPath.IN_PERSON
: VerifyFlowPath.DEFAULT;

return (
<>
<VerifyFlowStepIndicator currentStep="document_capture" path={stepIndicatorPath} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ function InPersonPrepareStep({ toPreviousStep }) {
inPersonOutageExpectedUpdateDate,
skipDocAuthFromHowToVerify,
skipDocAuthFromHandoff,
skipDocAuthFromSocure,
howToVerifyURL,
socureErrorsTimeoutURL,
previousStepURL,
} = useContext(InPersonContext);

Expand All @@ -31,6 +33,9 @@ function InPersonPrepareStep({ toPreviousStep }) {
forceRedirect(previousStepURL);
} else if (skipDocAuthFromHowToVerify && howToVerifyURL) {
forceRedirect(howToVerifyURL);
} else if (skipDocAuthFromSocure && socureErrorsTimeoutURL) {
// directly from Socure Hybrid page
forceRedirect(socureErrorsTimeoutURL);
} else {
toPreviousStep();
}
Expand Down
10 changes: 10 additions & 0 deletions app/javascript/packages/document-capture/context/in-person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,21 @@ export interface InPersonContextProps {
*/
skipDocAuthFromHandoff?: boolean;

/**
* Flag set when user select IPP from the Socure Hybrid page when IPP is available
*/
skipDocAuthFromSocure?: boolean;

/**
* URL for Opt-in IPP, used when in_person_proofing_opt_in_enabled is enabled
*/
howToVerifyURL?: string;

/**
* URL to return back to the Socure timeout page from Opt-in IPP
*/
socureErrorsTimeoutURL?: string;

/**
* URL for going back to previous steps in Doc Auth, like handoff and howToVerify
*/
Expand Down
6 changes: 6 additions & 0 deletions app/javascript/packs/document-capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ interface AppRootData {
securityAndPrivacyHowItWorksUrl: string;
skipDocAuthFromHowToVerify: string;
skipDocAuthFromHandoff: string;
skipDocAuthFromSocure: string;
howToVerifyURL: string;
socureErrorsTimeoutURL: string;
previousStepUrl: string;
docAuthSelfieDesktopTestMode: string;
accountUrl: string;
Expand Down Expand Up @@ -106,7 +108,9 @@ const {
usStatesTerritories = '',
skipDocAuthFromHowToVerify,
skipDocAuthFromHandoff,
skipDocAuthFromSocure,
howToVerifyUrl,
socureErrorsTimeoutUrl,
previousStepUrl,
docAuthSelfieDesktopTestMode,
locationsUrl: locationsURL,
Expand Down Expand Up @@ -136,7 +140,9 @@ render(
usStatesTerritories: parsedUsStatesTerritories,
skipDocAuthFromHowToVerify: skipDocAuthFromHowToVerify === 'true',
skipDocAuthFromHandoff: skipDocAuthFromHandoff === 'true',
skipDocAuthFromSocure: skipDocAuthFromSocure === 'true',
howToVerifyURL: howToVerifyUrl,
socureErrorsTimeoutURL: socureErrorsTimeoutUrl,
previousStepURL: previousStepUrl,
}}
>
Expand Down
8 changes: 5 additions & 3 deletions app/presenters/socure_error_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ def secondary_action_text
end

def secondary_action
url = flow_path == :hybrid ? idv_hybrid_mobile_in_person_direct_url :
idv_in_person_direct_url

if in_person_enabled?
{
text: I18n.t('in_person_proofing.body.cta.button'),
url: idv_in_person_direct_url,
url:,
}
end
end
Expand Down Expand Up @@ -176,7 +179,6 @@ def error_string_for(error_code)

def in_person_enabled?
IdentityConfig.store.in_person_doc_auth_button_enabled &&
Idv::InPersonConfig.enabled_for_issuer?(issuer) &&
flow_path.to_s == 'standard'
Idv::InPersonConfig.enabled_for_issuer?(issuer)
end
end
3 changes: 3 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2829,17 +2829,20 @@ def idv_image_capture_failed(

# User chooses to try In Person, e.g. from a doc_auth timeout error page
# @param [Integer] remaining_submit_attempts The number of remaining attempts to submit
# @param ["hybrid","standard"] flow_path Document capture user flow
# @param [Boolean] skip_hybrid_handoff Whether the user skipped the hybrid handoff A/B test
# @param [Boolean] opted_in_to_in_person_proofing Whether the user opted into in-person proofing
def idv_in_person_direct_start(
remaining_submit_attempts:,
flow_path:,
skip_hybrid_handoff: nil,
opted_in_to_in_person_proofing: nil,
**extra
)
track_event(
:idv_in_person_direct_start,
remaining_submit_attempts:,
flow_path:,
skip_hybrid_handoff:,
opted_in_to_in_person_proofing:,
**extra,
Expand Down
1 change: 1 addition & 0 deletions app/services/idv/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class Session
selfie_check_required
skip_doc_auth_from_handoff
skip_doc_auth_from_how_to_verify
skip_doc_auth_from_socure
skip_hybrid_handoff
socure_docv_wait_polling_started_at
ssn
Expand Down
Loading

0 comments on commit 72d57a3

Please sign in to comment.