diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb
index dff19236bd7..c57d4f55329 100644
--- a/app/controllers/idv/document_capture_controller.rb
+++ b/app/controllers/idv/document_capture_controller.rb
@@ -49,6 +49,7 @@ def extra_view_variables
sp_name: decorated_sp_session.sp_name,
failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'),
phone_with_camera: idv_session.phone_with_camera,
+ skip_doc_auth: idv_session.skip_doc_auth,
}.merge(
acuant_sdk_upgrade_a_b_testing_variables,
phone_question_ab_test_analytics_bucket,
diff --git a/app/controllers/idv/how_to_verify_controller.rb b/app/controllers/idv/how_to_verify_controller.rb
index 0be8af23274..cab3caa2a62 100644
--- a/app/controllers/idv/how_to_verify_controller.rb
+++ b/app/controllers/idv/how_to_verify_controller.rb
@@ -5,12 +5,19 @@ class HowToVerifyController < ApplicationController
include RenderConditionConcern
before_action :confirm_step_allowed
+ before_action :confirm_verify_info_step_needed
check_or_render_not_found -> { self.class.enabled? }
def show
+ @selection = if idv_session.skip_doc_auth == false
+ Idv::HowToVerifyForm::REMOTE
+ elsif idv_session.skip_doc_auth == true
+ Idv::HowToVerifyForm::IPP
+ end
+
analytics.idv_doc_auth_how_to_verify_visited(**analytics_arguments)
- @idv_how_to_verify_form = Idv::HowToVerifyForm.new
+ @idv_how_to_verify_form = Idv::HowToVerifyForm.new(selection: @selection)
end
def update
@@ -23,10 +30,14 @@ def update
if result.success?
if how_to_verify_form_params['selection'] == Idv::HowToVerifyForm::REMOTE
+ idv_session.skip_doc_auth = false
redirect_to idv_hybrid_handoff_url
else
+ idv_session.flow_path = 'standard'
+ idv_session.skip_doc_auth = true
redirect_to idv_document_capture_url
end
+
else
flash[:error] = result.first_error_message
redirect_to idv_how_to_verify_url
@@ -41,7 +52,7 @@ def self.step_info
preconditions: ->(idv_session:, user:) do
self.enabled? && idv_session.idv_consent_given
end,
- undo_step: ->(idv_session:, user:) {}, # clear any saved data
+ undo_step: ->(idv_session:, user:) { idv_session.skip_doc_auth = nil },
)
end
diff --git a/app/forms/idv/how_to_verify_form.rb b/app/forms/idv/how_to_verify_form.rb
index b2b2066fc46..f23329917b1 100644
--- a/app/forms/idv/how_to_verify_form.rb
+++ b/app/forms/idv/how_to_verify_form.rb
@@ -1,42 +1,23 @@
-# frozen_string_literal: true
-
module Idv
class HowToVerifyForm
include ActiveModel::Model
- ATTRIBUTES = [:selection].freeze
- REMOTE = 'remote'
- IPP = 'ipp'
- attr_accessor :selection
+ REMOTE = 'remote'.freeze
+ IPP = 'ipp'.freeze
+
+ attr_reader :selection
- validates :selection, inclusion: {
- in: [REMOTE, IPP],
- }
- validates :selection, presence: {
- message: proc { I18n.t('errors.doc_auth.how_to_verify_form') },
- }
+ validates :selection,
+ presence: { message: proc { I18n.t('errors.doc_auth.how_to_verify_form') } }
def initialize(selection: nil)
@selection = selection
end
def submit(params)
- consume_params(params)
+ @selection = params[:selection]
FormResponse.new(success: valid?, errors: errors)
end
-
- private
-
- def consume_params(params)
- params.each do |key, value|
- raise_invalid_how_to_verify_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym)
- send("#{key}=", value)
- end
- end
-
- def raise_invalid_how_to_verify_parameter_error(key)
- raise ArgumentError, "#{key} is an invalid how_to_verify attribute"
- end
end
end
diff --git a/app/javascript/packages/document-capture/components/document-capture.tsx b/app/javascript/packages/document-capture/components/document-capture.tsx
index 76318da6a2c..c23629f82d5 100644
--- a/app/javascript/packages/document-capture/components/document-capture.tsx
+++ b/app/javascript/packages/document-capture/components/document-capture.tsx
@@ -37,7 +37,7 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
const { t } = useI18n();
const { flowPath, phoneWithCamera } = useContext(UploadContext);
const { trackSubmitEvent, trackVisitEvent } = useContext(AnalyticsContext);
- const { inPersonFullAddressEntryEnabled, inPersonURL } = useContext(InPersonContext);
+ const { inPersonFullAddressEntryEnabled, inPersonURL, skipDocAuth } = useContext(InPersonContext);
const appName = getConfigValue('appName');
useDidUpdateEffect(onStepChange, [stepName]);
@@ -105,7 +105,7 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
},
].filter(Boolean) as FormStep[]);
- const steps: FormStep[] = submissionError
+ const defaultSteps: FormStep[] = submissionError
? (
[
{
@@ -133,8 +133,14 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
},
].filter(Boolean) as FormStep[]);
+ // If the user got here by opting-in to in-person proofing, when skipDocAuth === true,
+ // then set steps to inPersonSteps
+ const steps: FormStep[] = skipDocAuth ? inPersonSteps : defaultSteps;
+
+ // If the user got here by opting-in to in-person proofing, when skipDocAuth === true,
+ // then set stepIndicatorPath to VerifyFlowPath.IN_PERSON
const stepIndicatorPath =
- stepName && ['location', 'prepare', 'switch_back'].includes(stepName)
+ (stepName && ['location', 'prepare', 'switch_back'].includes(stepName)) || skipDocAuth
? VerifyFlowPath.IN_PERSON
: VerifyFlowPath.DEFAULT;
@@ -173,6 +179,7 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
onStepSubmit={trackSubmitEvent}
autoFocus={!!submissionError}
titleFormat={`%{step} - ${appName}`}
+ initialStep={skipDocAuth ? steps[0].name : undefined}
/>
>
)}
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 80d4ab28ebf..91ed67aad59 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
@@ -3,6 +3,7 @@ import { Link, PageHeading, ProcessList, ProcessListItem } from '@18f/identity-c
import { getConfigValue } from '@18f/identity-config';
import { useI18n } from '@18f/identity-react-i18n';
import { FormStepsButton } from '@18f/identity-form-steps';
+import { forceRedirect } from '@18f/identity-url';
import UploadContext from '../context/upload';
import MarketingSiteContext from '../context/marketing-site';
import BackButton from './back-button';
@@ -14,8 +15,21 @@ function InPersonPrepareStep({ toPreviousStep }) {
const { t } = useI18n();
const { flowPath } = useContext(UploadContext);
const { securityAndPrivacyHowItWorksURL } = useContext(MarketingSiteContext);
- const { inPersonURL, inPersonOutageMessageEnabled, inPersonOutageExpectedUpdateDate } =
- useContext(InPersonContext);
+ const {
+ inPersonURL,
+ inPersonOutageMessageEnabled,
+ inPersonOutageExpectedUpdateDate,
+ skipDocAuth,
+ howToVerifyURL,
+ } = useContext(InPersonContext);
+
+ function goBack() {
+ if (skipDocAuth && howToVerifyURL) {
+ forceRedirect(howToVerifyURL);
+ } else {
+ toPreviousStep();
+ }
+ }
return (
<>
@@ -58,7 +72,7 @@ function InPersonPrepareStep({ toPreviousStep }) {
)}
-
+
>
);
}
diff --git a/app/javascript/packages/document-capture/context/in-person.ts b/app/javascript/packages/document-capture/context/in-person.ts
index 3fcbb5ac2da..4aed409f00f 100644
--- a/app/javascript/packages/document-capture/context/in-person.ts
+++ b/app/javascript/packages/document-capture/context/in-person.ts
@@ -36,6 +36,18 @@ export interface InPersonContextProps {
* Each item is [Long name, abbreviation], e.g. ['Ohio', 'OH']
*/
usStatesTerritories: Array<[string, string]>;
+
+ /**
+ * When skipDocAuth is true and in_person_proofing_opt_in_enabled is true,
+ * users are directed to the beginning of the IPP flow. This is set to true when
+ * they choose Opt-in IPP on the new How To Verify page
+ */
+ skipDocAuth?: boolean;
+
+ /**
+ * URL for Opt-in IPP, used when in_person_proofing_opt_in_enabled is enabled
+ */
+ howToVerifyURL?: string;
}
const InPersonContext = createContext({
diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx
index 394fa8323b7..b543888bfbe 100644
--- a/app/javascript/packs/document-capture.tsx
+++ b/app/javascript/packs/document-capture.tsx
@@ -34,6 +34,8 @@ interface AppRootData {
exitUrl: string;
idvInPersonUrl?: string;
securityAndPrivacyHowItWorksUrl: string;
+ skipDocAuth: string;
+ howToVerifyURL: string;
uiNotReadySectionEnabled: string;
uiExitQuestionSectionEnabled: string;
}
@@ -103,6 +105,8 @@ const {
inPersonOutageExpectedUpdateDate,
usStatesTerritories = '',
phoneWithCamera = '',
+ skipDocAuth,
+ howToVerifyUrl,
uiNotReadySectionEnabled = '',
uiExitQuestionSectionEnabled = '',
} = appRoot.dataset as DOMStringMap & AppRootData;
@@ -126,6 +130,8 @@ const App = composeComponents(
inPersonOutageExpectedUpdateDate,
inPersonFullAddressEntryEnabled: inPersonFullAddressEntryEnabled === 'true',
usStatesTerritories: parsedUsStatesTerritories,
+ skipDocAuth: skipDocAuth === 'true',
+ howToVerifyURL: howToVerifyUrl,
},
},
],
diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb
index a6a8c7b11d6..40ecafa562e 100644
--- a/app/services/idv/session.rb
+++ b/app/services/idv/session.rb
@@ -22,6 +22,7 @@ class Session
profile_id
redo_document_capture
resolution_successful
+ skip_doc_auth
skip_hybrid_handoff
ssn
threatmetrix_review_status
diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb
index 9742a47df57..52959f33676 100644
--- a/app/views/idv/document_capture/show.html.erb
+++ b/app/views/idv/document_capture/show.html.erb
@@ -9,4 +9,5 @@
acuant_version: acuant_version,
phone_question_ab_test_bucket: phone_question_ab_test_bucket,
phone_with_camera: phone_with_camera,
+ skip_doc_auth: skip_doc_auth,
) %>
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 6a20c1c62b7..b72d905f689 100644
--- a/app/views/idv/hybrid_mobile/document_capture/show.html.erb
+++ b/app/views/idv/hybrid_mobile/document_capture/show.html.erb
@@ -9,4 +9,5 @@
acuant_version: acuant_version,
phone_question_ab_test_bucket: phone_question_ab_test_bucket,
phone_with_camera: phone_with_camera,
+ skip_doc_auth: false,
) %>
\ No newline at end of file
diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb
index e7ec2c49c33..509c1e0c10f 100644
--- a/app/views/idv/shared/_document_capture.html.erb
+++ b/app/views/idv/shared/_document_capture.html.erb
@@ -38,6 +38,8 @@
in_person_outage_expected_update_date: IdentityConfig.store.in_person_outage_expected_update_date,
us_states_territories: us_states_territories,
doc_auth_selfie_capture: IdentityConfig.store.doc_auth_selfie_capture,
+ skip_doc_auth: skip_doc_auth,
+ how_to_verify_url: idv_how_to_verify_url,
ui_not_ready_section_enabled: IdentityConfig.store.doc_auth_not_ready_section_enabled,
ui_exit_question_section_enabled: IdentityConfig.store.doc_auth_exit_question_section_enabled,
} %>
diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb
index ba3ca5b3ac1..d52d7268a53 100644
--- a/spec/controllers/idv/how_to_verify_controller_spec.rb
+++ b/spec/controllers/idv/how_to_verify_controller_spec.rb
@@ -31,6 +31,7 @@
it 'renders the show template' do
get :show
+ expect(subject.idv_session.skip_doc_auth).to be_nil
expect(response).to render_template :show
end
@@ -48,10 +49,46 @@
end
describe '#update' do
+ let(:params) do
+ {
+ idv_how_to_verify_form: { selection: selection },
+ }
+ end
+ let(:selection) { 'remote' }
+
it 'invalidates future steps' do
expect(subject).to receive(:clear_future_steps!)
put :update
end
+
+ context 'remote' do
+ it 'sets skip doc auth on idv session to false and redirects to hybrid handoff' do
+ put :update, params: params
+
+ expect(subject.idv_session.skip_doc_auth).to be false
+ expect(response).to redirect_to(idv_hybrid_handoff_url)
+ end
+ end
+
+ context 'ipp' do
+ let(:selection) { 'ipp' }
+
+ it 'sets skip doc auth on idv session to true and redirects to document capture' do
+ put :update, params: params
+
+ expect(subject.idv_session.skip_doc_auth).to be true
+ expect(response).to redirect_to(idv_document_capture_url)
+ end
+ end
+
+ context 'undo/back' do
+ it 'sets skip_doc_auth to nil and does not redirect' do
+ put :update, params: { undo_step: true }
+
+ expect(subject.idv_session.skip_doc_auth).to be_nil
+ expect(response).to redirect_to(idv_how_to_verify_url)
+ end
+ end
end
end
diff --git a/spec/features/idv/doc_auth/how_to_verify_spec.rb b/spec/features/idv/doc_auth/how_to_verify_spec.rb
index 5052197cbe6..e6ef8c4701c 100644
--- a/spec/features/idv/doc_auth/how_to_verify_spec.rb
+++ b/spec/features/idv/doc_auth/how_to_verify_spec.rb
@@ -4,30 +4,31 @@
include IdvHelper
include DocAuthHelper
- context 'opt-in ipp is turned on' do
- let(:enabled) { true }
+ let(:enabled) { true }
- before do
- allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled) { enabled }
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled) { enabled }
- sign_in_and_2fa_user
- complete_doc_auth_steps_before_agreement_step
- complete_agreement_step
- end
+ sign_in_and_2fa_user
+ complete_doc_auth_steps_before_agreement_step
+ complete_agreement_step
+ end
- context 'opt-in ipp is turned off' do
- let(:enabled) { false }
+ context 'opt-in ipp is turned off' do
+ let(:enabled) { false }
- it 'skips when disabled' do
- expect(page).to have_current_path(idv_hybrid_handoff_url)
- end
+ it 'skips when disabled' do
+ expect(page).to have_current_path(idv_hybrid_handoff_url)
end
+ end
+ context 'opt-in ipp is turned on' do
it 'displays expected content and requires a choice' do
expect(page).to have_current_path(idv_how_to_verify_path)
# Try to continue without an option
click_continue
+
expect(page).to have_current_path(idv_how_to_verify_path)
expect(page).to have_content(t('errors.doc_auth.how_to_verify_form'))
diff --git a/spec/forms/idv/how_to_verify_form_spec.rb b/spec/forms/idv/how_to_verify_form_spec.rb
index c0439a9a4c6..7e4e5e50a83 100644
--- a/spec/forms/idv/how_to_verify_form_spec.rb
+++ b/spec/forms/idv/how_to_verify_form_spec.rb
@@ -13,22 +13,6 @@
expect(result.errors).to be_empty
end
end
-
- context 'when the form is invalid' do
- it 'returns an unsuccessful form response' do
- result = subject.submit(selection: 'peanut butter')
-
- expect(result).to be_kind_of(FormResponse)
- expect(result.success?).to eq(false)
- end
- end
-
- context 'when the form has invalid attributes' do
- it 'raises an error' do
- expect { subject.submit(selection: Idv::HowToVerifyForm::REMOTE, foo: 1) }.
- to raise_error(ArgumentError, 'foo is an invalid how_to_verify attribute')
- end
- end
end
describe 'presence validations' do
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 5d55d47f462..9eabd393c24 100644
--- a/spec/views/idv/shared/_document_capture.html.erb_spec.rb
+++ b/spec/views/idv/shared/_document_capture.html.erb_spec.rb
@@ -16,6 +16,7 @@
let(:doc_auth_selfie_capture) { { enabled: false } }
let(:phone_with_camera) { false }
let(:acuant_version) { '1.3.3.7' }
+ let(:skip_doc_auth) { false }
before do
decorated_sp_session = instance_double(
@@ -47,6 +48,7 @@
phone_question_ab_test_bucket: phone_question_ab_test_bucket,
doc_auth_selfie_capture: doc_auth_selfie_capture,
phone_with_camera: phone_with_camera,
+ skip_doc_auth: skip_doc_auth,
}
end