diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index 52dd1630ba2..f54c706427d 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -145,6 +145,7 @@ def set_idv_form allowed_countries: PhoneNumberCapabilities::ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES, failed_phone_numbers: idv_session.failed_phone_step_numbers, + hybrid_handoff_phone_number: idv_session.phone_for_mobile_flow, ) end @@ -171,6 +172,7 @@ def async_state_done(async_state) [:context, :stages, :address], ], new_phone_added: new_phone_added?, + hybrid_handoff_phone_used: hybrid_handoff_phone_used?, ), ) @@ -198,8 +200,18 @@ def new_phone_added? configured_phones = context.phone_configurations.map(&:phone).map do |number| PhoneFormatter.format(number) end - applicant_phone = PhoneFormatter.format(idv_session.applicant['phone']) - !configured_phones.include?(applicant_phone) + !configured_phones.include?(formatted_previous_phone_step_params_phone) + end + + def hybrid_handoff_phone_used? + formatted_previous_phone_step_params_phone == + PhoneFormatter.format(idv_session.phone_for_mobile_flow) + end + + def formatted_previous_phone_step_params_phone + PhoneFormatter.format( + idv_session.previous_phone_step_params&.fetch('phone'), + ) end def gpo_letter_available diff --git a/app/forms/idv/phone_form.rb b/app/forms/idv/phone_form.rb index b279d80d037..8e90ed47f79 100644 --- a/app/forms/idv/phone_form.rb +++ b/app/forms/idv/phone_form.rb @@ -5,7 +5,7 @@ class PhoneForm ALL_DELIVERY_METHODS = [:sms, :voice].freeze attr_reader :user, :phone, :allowed_countries, :delivery_methods, :international_code, - :otp_delivery_preference, :failed_phone_numbers + :otp_delivery_preference, :failed_phone_numbers, :hybrid_handoff_phone_number validate :validate_valid_phone_for_allowed_countries validate :validate_phone_delivery_methods @@ -20,13 +20,15 @@ def initialize( previous_params:, allowed_countries: nil, delivery_methods: ALL_DELIVERY_METHODS, - failed_phone_numbers: [] + failed_phone_numbers: [], + hybrid_handoff_phone_number: nil ) previous_params ||= {} @user = user @allowed_countries = allowed_countries @delivery_methods = delivery_methods @failed_phone_numbers = failed_phone_numbers + @hybrid_handoff_phone_number = hybrid_handoff_phone_number @international_code, @phone = determine_initial_values( **previous_params. @@ -52,7 +54,7 @@ def submit(params) # @return [Array] The international_code and phone values to use. def determine_initial_values(international_code: nil, phone: nil) if phone.nil? && international_code.nil? - default_phone = user.default_phone_configuration&.phone + default_phone = user.default_phone_configuration&.phone || hybrid_handoff_phone_number if valid_phone?(default_phone, phone_confirmed: true) phone = default_phone international_code = country_code_for(default_phone) diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index 7651a97648d..1542df3f4c4 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -478,6 +478,7 @@ result = { success: true, new_phone_added: true, + hybrid_handoff_phone_used: false, errors: {}, phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), country_code: proofing_phone.country, @@ -506,6 +507,27 @@ end end + it 'tracks that the hybrid handoff phone was used' do + user = build(:user) + stub_verify_steps_one_and_two(user) + + stub_analytics + allow(@analytics).to receive(:track_event) + + expect(@analytics).to receive(:track_event).ordered.with( + 'IdV: phone confirmation form', hash_including(:success) + ) + expect(@analytics).to receive(:track_event).ordered.with( + 'IdV: phone confirmation vendor', hash_including(hybrid_handoff_phone_used: true) + ) + + subject.idv_session.phone_for_mobile_flow = good_phone + + put :create, params: { idv_phone_form: { phone: good_phone } } + expect(response).to redirect_to idv_phone_path + get :new + end + context 'when verification fails' do it 'renders failure page and does not set phone confirmation' do user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now }) @@ -548,6 +570,7 @@ result = { success: false, new_phone_added: true, + hybrid_handoff_phone_used: false, phone_fingerprint: Pii::Fingerprinter.fingerprint(proofing_phone.e164), country_code: proofing_phone.country, area_code: proofing_phone.area_code, diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index bbc74c4923a..fdfe2960ad6 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -102,7 +102,7 @@ proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' } }, 'IdV: phone confirmation otp sent' => { @@ -312,7 +312,7 @@ proofing_components: { document_check: 'usps', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'aamva' } }, 'IdV: phone confirmation vendor' => { - success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, + success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, hybrid_handoff_phone_used: false, area_code: '202', country_code: 'US', phone_fingerprint: anything, proofing_components: { address_check: 'lexis_nexis_address', document_check: 'usps', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'aamva' } }, 'IdV: phone confirmation otp sent' => { diff --git a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb index c336ef8f9ef..899e34486a4 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb @@ -81,6 +81,14 @@ expect(page).to have_content(t('headings.verify')) click_idv_continue + prefilled_phone = page.find(id: 'idv_phone_form_phone').value + + expect( + PhoneFormatter.format(prefilled_phone), + ).to eq( + PhoneFormatter.format(user.default_phone_configuration.phone), + ) + fill_out_phone_form_ok verify_phone_otp @@ -304,4 +312,45 @@ end end end + + it 'prefils the phone number used on the phone step if the user has no MFA phone', :js do + user = create(:user, :with_authentication_app) + + perform_in_browser(:desktop) do + start_idv_from_sp + sign_in_and_2fa_user(user) + + complete_doc_auth_steps_before_hybrid_handoff_step + clear_and_fill_in(:doc_auth_phone, phone_number) + click_send_link + end + + expect(@sms_link).to be_present + + perform_in_browser(:mobile) do + visit @sms_link + attach_and_submit_images + + expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_url) + expect(page).to have_text(t('doc_auth.instructions.switch_back')) + end + + perform_in_browser(:desktop) do + expect(page).to have_current_path(idv_ssn_path, wait: 10) + + fill_out_ssn_form_ok + click_idv_continue + + expect(page).to have_content(t('headings.verify')) + click_idv_continue + + prefilled_phone = page.find(id: 'idv_phone_form_phone').value + + expect( + PhoneFormatter.format(prefilled_phone), + ).to eq( + PhoneFormatter.format(phone_number), + ) + end + end end diff --git a/spec/forms/idv/phone_form_spec.rb b/spec/forms/idv/phone_form_spec.rb index 5084fc1c702..356d44dc48d 100644 --- a/spec/forms/idv/phone_form_spec.rb +++ b/spec/forms/idv/phone_form_spec.rb @@ -130,6 +130,29 @@ end end + context 'with a number submitted on the hybrid handoff step' do + context 'with a phone number on the user record' do + let(:user_phone) { '2025555000' } + let(:hybrid_handoff_phone_number) { '2025551234' } + let(:user) { build_stubbed(:user, :fully_registered, with: { phone: user_phone }) } + let(:optional_params) { { hybrid_handoff_phone_number: hybrid_handoff_phone_number } } + + it 'uses the user phone as the initial value' do + expect(subject.phone).to eq(PhoneFormatter.format(user_phone)) + end + end + + context 'without a phone number on the user record' do + let(:hybrid_handoff_phone_number) { '2025551234' } + let(:user) { build_stubbed(:user) } + let(:optional_params) { { hybrid_handoff_phone_number: hybrid_handoff_phone_number } } + + it 'uses the hybrid handoff phone as the initial value' do + expect(subject.phone).to eq(PhoneFormatter.format(hybrid_handoff_phone_number)) + end + end + end + context 'with previously submitted value' do let(:user) { build_stubbed(:user, :fully_registered, with: { phone: '7035551234' }) } let(:previous_params) { { phone: '2255555000' } }