diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index 5cd9eb2ea57..2e11e0c985e 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -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 diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index 47fdc45dc19..9924beaf852 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -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) @@ -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) @@ -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, ) diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb index 7190c4ee291..873285b3d21 100644 --- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb @@ -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) @@ -43,6 +44,18 @@ 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', @@ -50,6 +63,8 @@ def extra_view_variables 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, ) @@ -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? @@ -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 diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index a63a11a208f..46ebaf0230d 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -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 @@ -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 diff --git a/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb b/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb new file mode 100644 index 00000000000..c216de5f9ad --- /dev/null +++ b/app/controllers/idv/hybrid_mobile/socure/errors_controller.rb @@ -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 diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index f84854fe9df..df0111f7606 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -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 diff --git a/app/controllers/idv/socure/errors_controller.rb b/app/controllers/idv/socure/errors_controller.rb index 61bbb8e06fc..3d6c9d3c21c 100644 --- a/app/controllers/idv/socure/errors_controller.rb +++ b/app/controllers/idv/socure/errors_controller.rb @@ -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:) {}, @@ -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, diff --git a/app/javascript/packages/document-capture/components/document-capture.tsx b/app/javascript/packages/document-capture/components/document-capture.tsx index 2416230db1b..45afc887982 100644 --- a/app/javascript/packages/document-capture/components/document-capture.tsx +++ b/app/javascript/packages/document-capture/components/document-capture.tsx @@ -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(() => { @@ -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 ? [] @@ -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 ( <> diff --git a/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx b/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx index 04a12982665..cb126245075 100644 --- a/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx @@ -21,7 +21,9 @@ function InPersonPrepareStep({ toPreviousStep }) { inPersonOutageExpectedUpdateDate, skipDocAuthFromHowToVerify, skipDocAuthFromHandoff, + skipDocAuthFromSocure, howToVerifyURL, + socureErrorsTimeoutURL, previousStepURL, } = useContext(InPersonContext); @@ -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(); } diff --git a/app/javascript/packages/document-capture/context/in-person.ts b/app/javascript/packages/document-capture/context/in-person.ts index 1a24a8a5941..bea2c64eab3 100644 --- a/app/javascript/packages/document-capture/context/in-person.ts +++ b/app/javascript/packages/document-capture/context/in-person.ts @@ -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 */ diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx index f089d96154c..ff8034bd62d 100644 --- a/app/javascript/packs/document-capture.tsx +++ b/app/javascript/packs/document-capture.tsx @@ -35,7 +35,9 @@ interface AppRootData { securityAndPrivacyHowItWorksUrl: string; skipDocAuthFromHowToVerify: string; skipDocAuthFromHandoff: string; + skipDocAuthFromSocure: string; howToVerifyURL: string; + socureErrorsTimeoutURL: string; previousStepUrl: string; docAuthSelfieDesktopTestMode: string; accountUrl: string; @@ -106,7 +108,9 @@ const { usStatesTerritories = '', skipDocAuthFromHowToVerify, skipDocAuthFromHandoff, + skipDocAuthFromSocure, howToVerifyUrl, + socureErrorsTimeoutUrl, previousStepUrl, docAuthSelfieDesktopTestMode, locationsUrl: locationsURL, @@ -136,7 +140,9 @@ render( usStatesTerritories: parsedUsStatesTerritories, skipDocAuthFromHowToVerify: skipDocAuthFromHowToVerify === 'true', skipDocAuthFromHandoff: skipDocAuthFromHandoff === 'true', + skipDocAuthFromSocure: skipDocAuthFromSocure === 'true', howToVerifyURL: howToVerifyUrl, + socureErrorsTimeoutURL: socureErrorsTimeoutUrl, previousStepURL: previousStepUrl, }} > diff --git a/app/presenters/socure_error_presenter.rb b/app/presenters/socure_error_presenter.rb index 6f96c1b5257..e5f7c26f25b 100644 --- a/app/presenters/socure_error_presenter.rb +++ b/app/presenters/socure_error_presenter.rb @@ -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 @@ -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 diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index cccdd0f6c96..377a3f4b74d 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2829,10 +2829,12 @@ 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 @@ -2840,6 +2842,7 @@ def idv_in_person_direct_start( track_event( :idv_in_person_direct_start, remaining_submit_attempts:, + flow_path:, skip_hybrid_handoff:, opted_in_to_in_person_proofing:, **extra, diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index b4ce639e580..6d6b2ea6ed9 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -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 diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb index e7fb85d687f..540a78434c8 100644 --- a/app/views/idv/document_capture/show.html.erb +++ b/app/views/idv/document_capture/show.html.erb @@ -10,6 +10,8 @@ opted_in_to_in_person_proofing: opted_in_to_in_person_proofing, skip_doc_auth_from_how_to_verify: skip_doc_auth_from_how_to_verify, skip_doc_auth_from_handoff: skip_doc_auth_from_handoff, + skip_doc_auth_from_socure: skip_doc_auth_from_socure, + socure_errors_timeout_url: socure_errors_timeout_url, doc_auth_selfie_capture: doc_auth_selfie_capture, mock_client: mock_client, ) %> diff --git a/app/views/idv/hybrid_mobile/document_capture/show.html.erb b/app/views/idv/hybrid_mobile/document_capture/show.html.erb index 049858e346b..16eb206b424 100644 --- a/app/views/idv/hybrid_mobile/document_capture/show.html.erb +++ b/app/views/idv/hybrid_mobile/document_capture/show.html.erb @@ -10,6 +10,8 @@ opted_in_to_in_person_proofing: false, skip_doc_auth_from_how_to_verify: false, skip_doc_auth_from_handoff: nil, + skip_doc_auth_from_socure: skip_doc_auth_from_socure, + socure_errors_timeout_url: socure_errors_timeout_url, doc_auth_selfie_capture: doc_auth_selfie_capture, mock_client: mock_client, ) %> diff --git a/app/views/idv/hybrid_mobile/socure/errors/show.html.erb b/app/views/idv/hybrid_mobile/socure/errors/show.html.erb new file mode 100644 index 00000000000..a7210f8471f --- /dev/null +++ b/app/views/idv/hybrid_mobile/socure/errors/show.html.erb @@ -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 +%> +

<%= @presenter.body_text %>

+

<%= @presenter.rate_limit_text %>

+<% end %> diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb index bd02e0b4cb7..23fde096821 100644 --- a/app/views/idv/shared/_document_capture.html.erb +++ b/app/views/idv/shared/_document_capture.html.erb @@ -38,7 +38,9 @@ doc_auth_selfie_desktop_test_mode: IdentityConfig.store.doc_auth_selfie_desktop_test_mode, skip_doc_auth_from_how_to_verify: skip_doc_auth_from_how_to_verify, skip_doc_auth_from_handoff: skip_doc_auth_from_handoff, + skip_doc_auth_from_socure: skip_doc_auth_from_socure, how_to_verify_url: idv_how_to_verify_url, + socure_errors_timeout_url: socure_errors_timeout_url, previous_step_url: @previous_step_url, locations_url: idv_in_person_usps_locations_url, sessions_url: api_internal_sessions_path, diff --git a/config/routes.rb b/config/routes.rb index 0cf6cf6027e..1b9940e00c3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -373,10 +373,12 @@ get '/documents' => 'hybrid_mobile/entry#show', as: :hybrid_mobile_entry get '/hybrid_mobile/document_capture' => 'hybrid_mobile/document_capture#show' put '/hybrid_mobile/document_capture' => 'hybrid_mobile/document_capture#update' + get '/hybrid_mobile/in_person/direct' => 'hybrid_mobile/document_capture#direct_in_person' get '/hybrid_mobile/capture_complete' => 'hybrid_mobile/capture_complete#show' get '/hybrid_mobile/socure/document_capture' => 'hybrid_mobile/socure/document_capture#show' get '/hybrid_mobile/socure/document_capture_update' => 'hybrid_mobile/socure/document_capture#update', as: :hybrid_mobile_socure_document_capture_update - get '/hybrid_mobile/socure/document_capture_errors' => 'hybrid_mobile/socure/document_capture#errors', as: :hybrid_mobile_socure_document_capture_errors + get '/hybrid_mobile/socure/document_capture_errors' => 'hybrid_mobile/socure/errors#show', as: :hybrid_mobile_socure_document_capture_errors + get '/hybrid_mobile/socure/errors/timeout' => 'hybrid_mobile/socure/errors#timeout' get '/hybrid_handoff' => 'hybrid_handoff#show' put '/hybrid_handoff' => 'hybrid_handoff#update' get '/link_sent' => 'link_sent#show' diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 4a0d945b92e..51ac1b725ca 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -434,6 +434,7 @@ let(:analytics_args) do { remaining_submit_attempts: 4, + flow_path: :standard, skip_hybrid_handoff: nil, opted_in_to_in_person_proofing: nil, } diff --git a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb index c05ef0408b3..81519ae409a 100644 --- a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb @@ -425,10 +425,9 @@ allow(subject).to receive(:wait_timed_out?).and_return(true) end - it 'renders a technical difficulties message' do + it 'redirects to the hybrid mobile socure errors timeout page' do get(:update) - expect(response).to have_http_status(:ok) - expect(response.body).to eq('Technical difficulties!!!') + expect(response).to redirect_to(idv_hybrid_mobile_socure_errors_timeout_url) end end end diff --git a/spec/features/idv/doc_auth/socure_document_capture_spec.rb b/spec/features/idv/doc_auth/socure_document_capture_spec.rb index ede7b13c5f5..62d0a815d4c 100644 --- a/spec/features/idv/doc_auth/socure_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/socure_document_capture_spec.rb @@ -63,14 +63,15 @@ expect(page).to have_current_path(fake_socure_document_capture_app_url) visit idv_socure_document_capture_path expect(page).to have_current_path(idv_socure_document_capture_path) - %w[ - WAITING_FOR_USER_TO_REDIRECT, - APP_OPENED, - DOCUMENT_FRONT_UPLOADED, - DOCUMENT_BACK_UPLOADED, - ].each do |event_type| - socure_docv_send_webhook(docv_transaction_token: @docv_transaction_token, event_type:) - end + socure_docv_upload_documents( + docv_transaction_token: @docv_transaction_token, + webhooks: %w[ + WAITING_FOR_USER_TO_REDIRECT, + APP_OPENED, + DOCUMENT_FRONT_UPLOADED, + DOCUMENT_BACK_UPLOADED, + ], + ) # Go to the wait page visit idv_socure_document_capture_update_path @@ -87,7 +88,7 @@ expect(page).to have_content(t('in_person_proofing.headings.prepare')) # Go back - visit idv_socure_document_capture_update_path + click_on t('forms.buttons.back') expect(page).to have_current_path(idv_socure_errors_timeout_path) # Try Socure again diff --git a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb index 94e870126ee..3e14419a6eb 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb @@ -169,6 +169,74 @@ end end + context 'user times out waiting for result' do + before do + DocAuth::Mock::DocAuthMockClient.reset! + allow(IdentityConfig.store) + .to receive(:in_person_proofing_enabled).and_return(true) + allow(IdentityConfig.store) + .to receive(:in_person_doc_auth_button_enabled).and_return(true) + allow(Idv::InPersonConfig).to receive(:enabled_for_issuer?).and_return(true) + allow(IdentityConfig.store).to receive(:doc_auth_socure_wait_polling_timeout_minutes) + .and_return(0) + end + + it 'presents options to try again or try in person', js: true do + perform_in_browser(:desktop) do + visit_idp_from_sp_with_ial2(sp) + sign_up_and_2fa_ial1_user + + complete_doc_auth_steps_before_hybrid_handoff_step + clear_and_fill_in(:doc_auth_phone, phone_number) + click_send_link + end + + perform_in_browser(:mobile) do + visit @sms_link + expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_url) + + click_idv_continue + expect(page).to have_current_path(fake_socure_document_capture_app_url) + + # Fail to receive the DOCUMENTS_UPLOADED and SESSION_COMPLETE webhooks + socure_docv_upload_documents( + docv_transaction_token: @docv_transaction_token, + webhooks: %w[ + WAITING_FOR_USER_TO_REDIRECT, + APP_OPENED, + DOCUMENT_FRONT_UPLOADED, + DOCUMENT_BACK_UPLOADED, + ], + ) + + # Go to the wait page + visit idv_hybrid_mobile_socure_document_capture_update_url + expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_update_path) + + # Timeout + visit idv_hybrid_mobile_socure_document_capture_update_url + expect(page).to have_current_path(idv_hybrid_mobile_socure_errors_timeout_path) + expect(page).to have_content(I18n.t('idv.errors.try_again_later')) + + # Try in person + click_on t('in_person_proofing.body.cta.button') + expect(page).to have_current_path( + idv_hybrid_mobile_document_capture_path(step: :idv_doc_auth), + ) + expect(page).to have_content(t('in_person_proofing.headings.prepare')) + + # Go back + click_on t('forms.buttons.back') + expect(page).to have_current_path(idv_hybrid_mobile_socure_errors_timeout_path) + + # Try Socure again + click_on t('idv.failure.button.warning') + expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_path) + expect(page).to have_content(t('doc_auth.headings.verify_with_phone')) + end + end + end + context 'user is rate limited on mobile' do let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } let(:socure_docv_webhook_repeat_endpoints) do # repeat webhooks @@ -296,7 +364,7 @@ end context 'when a valid test token is used' do - it 'fetches verificationdata using override docvToken in request', + it 'fetches verification data using override docvToken in request', js: true, allow_browser_log: true do user = nil @@ -341,7 +409,7 @@ .to receive(:send_http_post_request).and_raise('doh') end - it 'waits to fetch verificationdata using docv capture session token', js: true do + it 'waits to fetch verification data using docv capture session token', js: true do expect(SocureDocvRepeatWebhookJob).to receive(:perform_later) .exactly(6 * socure_docv_webhook_repeat_endpoints.length).times.and_call_original diff --git a/spec/support/features/document_capture_step_helper.rb b/spec/support/features/document_capture_step_helper.rb index d92eb073a1a..5e22d060e17 100644 --- a/spec/support/features/document_capture_step_helper.rb +++ b/spec/support/features/document_capture_step_helper.rb @@ -72,15 +72,19 @@ def click_try_again click_spinner_button_and_wait t('idv.failure.button.warning') end - def socure_docv_upload_documents(docv_transaction_token:) - [ - 'WAITING_FOR_USER_TO_REDIRECT', - 'APP_OPENED', - 'DOCUMENT_FRONT_UPLOADED', - 'DOCUMENT_BACK_UPLOADED', - 'DOCUMENTS_UPLOADED', - 'SESSION_COMPLETE', - ].each { |event_type| socure_docv_send_webhook(docv_transaction_token:, event_type:) } + def socure_docv_upload_documents(docv_transaction_token:, webhooks: default_webhook_list) + webhooks.each { |event_type| socure_docv_send_webhook(docv_transaction_token:, event_type:) } + end + + def default_webhook_list + %w[ + WAITING_FOR_USER_TO_REDIRECT + APP_OPENED + DOCUMENT_FRONT_UPLOADED + DOCUMENT_BACK_UPLOADED + DOCUMENTS_UPLOADED + SESSION_COMPLETE + ] end def socure_docv_send_webhook( diff --git a/spec/views/idv/shared/_document_capture.html.erb_spec.rb b/spec/views/idv/shared/_document_capture.html.erb_spec.rb index d15ab4aa988..17a5e1fa3dd 100644 --- a/spec/views/idv/shared/_document_capture.html.erb_spec.rb +++ b/spec/views/idv/shared/_document_capture.html.erb_spec.rb @@ -17,6 +17,8 @@ let(:acuant_version) { '1.3.3.7' } let(:skip_doc_auth_from_how_to_verify) { false } let(:skip_doc_auth_from_handoff) { false } + let(:skip_doc_auth_from_socure) { false } + let(:socure_errors_timeout_url) { idv_socure_errors_timeout_url } let(:opted_in_to_in_person_proofing) { false } let(:presenter) { Idv::InPerson::UspsFormPresenter.new } let(:mock_client) { false } @@ -53,6 +55,8 @@ doc_auth_selfie_capture: selfie_capture_enabled, skip_doc_auth_from_how_to_verify: skip_doc_auth_from_how_to_verify, skip_doc_auth_from_handoff: skip_doc_auth_from_handoff, + skip_doc_auth_from_socure: skip_doc_auth_from_socure, + socure_errors_timeout_url: socure_errors_timeout_url, opted_in_to_in_person_proofing: opted_in_to_in_person_proofing, mock_client: mock_client, } @@ -112,6 +116,13 @@ ) end + it 'sends skip_doc_auth_from_how_to_verify to in the frontend' do + render_partial + expect(rendered).to have_css( + "#document-capture-form[data-skip-doc-auth-from-how-to-verify='false']", + ) + end + it 'sends skip_doc_auth_from_handoff to in the frontend' do render_partial expect(rendered).to have_css( @@ -119,6 +130,13 @@ ) end + it 'sends skip_doc_auth_from_socure to in the frontend' do + render_partial + expect(rendered).to have_css( + "#document-capture-form[data-skip-doc-auth-from-socure='false']", + ) + end + context 'when doc_auth_selfie_capture is false' do let(:selfie_capture_enabled) { false } it 'does not send doc_auth_selfie_capture to the FE' do