diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 783d8f67c62..ddf84940e57 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -31,7 +31,10 @@ def shared_update idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid - Idv::Agent.new(pii).proof_resolution( + user_pii = pii + user_pii[:best_effort_phone_number_for_socure] = best_effort_phone + + Idv::Agent.new(user_pii).proof_resolution( document_capture_session, trace_id: amzn_trace_id, user_id: current_user.id, @@ -48,6 +51,14 @@ def log_event_for_missing_threatmetrix_session_id analytics.idv_verify_info_missing_threatmetrix_session_id if idv_session.ssn_step_complete? end + def best_effort_phone + if idv_session.phone_for_mobile_flow + { source: :hybrid_handoff, phone: idv_session.phone_for_mobile_flow } + elsif current_user.default_phone_configuration + { source: :mfa, phone: current_user.default_phone_configuration.formatted_phone } + end + end + private def ipp_enrollment_in_progress? diff --git a/app/jobs/socure_shadow_mode_proofing_job.rb b/app/jobs/socure_shadow_mode_proofing_job.rb index 83b8aa15bf1..d9bbe33faae 100644 --- a/app/jobs/socure_shadow_mode_proofing_job.rb +++ b/app/jobs/socure_shadow_mode_proofing_job.rb @@ -42,6 +42,7 @@ def perform( analytics.idv_socure_shadow_mode_proofing_result( resolution_result: format_proofing_result_for_logs(proofing_result), socure_result: socure_result.to_h, + phone_source: applicant[:phone_source], user_id: user.uuid, pii_like_keypaths: [ [:errors, :ssn], @@ -91,6 +92,10 @@ def build_applicant( ) applicant_pii = decrypted_arguments[:applicant_pii] + if applicant_pii[:phone].nil? && applicant_pii[:best_effort_phone_number_for_socure] + applicant_pii[:phone] = applicant_pii[:best_effort_phone_number_for_socure][:phone] + applicant_pii[:phone_source] = applicant_pii[:best_effort_phone_number_for_socure][:source] + end { **applicant_pii.slice( @@ -102,6 +107,7 @@ def build_applicant( :state, :zipcode, :phone, + :phone_source, :dob, :ssn, :consent_given_at, diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index ea5ef9b3ddb..2626d798f47 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -4591,14 +4591,17 @@ def idv_session_error_visited( # Logs a Socure KYC result alongside a resolution result for later comparison. # @param [Hash] socure_result Result from Socure KYC API call # @param [Hash] resolution_result Result from resolution proofing + # @param [String,nil] phone_source Whether the phone number is from MFA or hybrid handoff def idv_socure_shadow_mode_proofing_result( socure_result:, resolution_result:, + phone_source:, **extra ) track_event( :idv_socure_shadow_mode_proofing_result, resolution_result: resolution_result.to_h, + phone_source:, socure_result: socure_result.to_h, **extra, ) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 5dc2be668d4..edb8a6f07f4 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -32,7 +32,7 @@ def proof( ipp_enrollment_in_progress:, current_sp: ) - @applicant_pii = applicant_pii + @applicant_pii = applicant_pii.except(:best_effort_phone_number_for_socure) @request_ip = request_ip @threatmetrix_session_id = threatmetrix_session_id @timer = timer diff --git a/app/services/proofing/socure/id_plus/proofer.rb b/app/services/proofing/socure/id_plus/proofer.rb index 91edb4c4300..09461aedf43 100644 --- a/app/services/proofing/socure/id_plus/proofer.rb +++ b/app/services/proofing/socure/id_plus/proofer.rb @@ -33,7 +33,7 @@ def initialize(config) # @param [Hash] applicant # @return [Proofing::Resolution::Result] def proof(applicant) - input = Input.new(applicant) + input = Input.new(applicant.except(:phone_source)) request = Request.new(config:, input:) diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 22cb5cbe811..470578f81e6 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -268,6 +268,10 @@ expect(Idv::Agent).to receive(:new).with( Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge( consent_given_at: subject.idv_session.idv_consent_given_at, + best_effort_phone_number_for_socure: { + source: :mfa, + phone: '+1 415-555-0130', + }, ), ).and_call_original put :update diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 0062867e8fe..56e1e52f3df 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -393,7 +393,7 @@ end end - context 'when the reolution proofing job result is missing' do + context 'when the resolution proofing job result is missing' do let(:async_state) do ProofingSessionAsyncResult.new(status: ProofingSessionAsyncResult::MISSING) end @@ -491,4 +491,36 @@ end end end + + describe '#best_effort_phone' do + it 'returns nil when there is no number available' do + expect(subject.best_effort_phone).to eq(nil) + end + + context 'when there is a hybrid handoff number' do + before(:each) do + allow(subject.idv_session).to receive(:phone_for_mobile_flow).and_return('202-555-1234') + end + + it 'returns the phone number from hybrid handoff' do + expect(subject.best_effort_phone[:phone]).to eq('202-555-1234') + end + + it 'sets type to :hybrid_handoff' do + expect(subject.best_effort_phone[:source]).to eq(:hybrid_handoff) + end + end + + context 'when there was an MFA phone number provided' do + let(:user) { create(:user, :with_phone) } + + it 'returns the MFA phone number' do + expect(subject.best_effort_phone[:phone]).to eq('+1 202-555-1212') + end + + it 'sets the phone source to :mfa' do + expect(subject.best_effort_phone[:source]).to eq(:mfa) + end + end + end end diff --git a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb index 220ecb938fc..acc02e7e63d 100644 --- a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb +++ b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb @@ -18,7 +18,7 @@ end let(:applicant_pii) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE + Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN end let(:encrypted_arguments) do @@ -182,20 +182,8 @@ end context 'when document capture session result is present in redis' do - it 'makes a proofing call' do - expect(job.proofer).to receive(:proof).and_call_original - perform - end - - it 'does not log an idv_socure_shadow_mode_proofing_result_missing event' do - perform - expect(analytics).not_to have_logged_event(:idv_socure_shadow_mode_proofing_result_missing) - end - - it 'logs an event' do - perform - expect(analytics).to have_logged_event( - :idv_socure_shadow_mode_proofing_result, + let(:expected_event_body) do + { user_id: user.uuid, resolution_result: { success: true, @@ -278,9 +266,52 @@ vendor_workflow: nil, verified_attributes: %i[address first_name last_name phone ssn dob].to_set, }, + } + end + + it 'makes a proofing call' do + expect(job.proofer).to receive(:proof).and_call_original + perform + end + + it 'does not log an idv_socure_shadow_mode_proofing_result_missing event' do + perform + expect(analytics).not_to have_logged_event(:idv_socure_shadow_mode_proofing_result_missing) + end + + it 'logs an event' do + perform + expect(analytics).to have_logged_event( + :idv_socure_shadow_mode_proofing_result, + expected_event_body, ) end + context 'when the user has an MFA phone number' do + let(:applicant_pii) do + Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( + best_effort_phone_number_for_socure: { + source: :mfa, + phone: '1 202-555-0000', + }, + ) + end + + let(:encrypted_arguments) do + Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( + JSON.generate({ applicant_pii: applicant_pii }), + ) + end + + it 'logs an event with the phone number' do + perform + expect(analytics).to have_logged_event( + :idv_socure_shadow_mode_proofing_result, + expected_event_body.merge(phone_source: 'mfa'), + ) + end + end + context 'when socure proofer raises an error' do before do allow(job.proofer).to receive(:proof).and_raise @@ -349,22 +380,71 @@ job.build_applicant(encrypted_arguments:, user_email:) end - it 'builds an applicant structure that looks right' do - expect(build_applicant).to eql( - { - first_name: 'FAKEY', - last_name: 'MCFAKERSON', - address1: '1 FAKE RD', - address2: nil, - city: 'GREAT FALLS', - state: 'MT', - zipcode: '59010-1234', + let(:expected_attributes) do + { + first_name: 'FAKEY', + last_name: 'MCFAKERSON', + address1: '1 FAKE RD', + address2: nil, + city: 'GREAT FALLS', + state: 'MT', + zipcode: '59010-1234', + dob: '1938-10-06', + ssn: '900-66-1234', + email: user.email, + } + end + + context 'when the user has a phone directly passed in' do + let(:applicant_pii) do + Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( phone: '12025551212', - dob: '1938-10-06', - ssn: '900-66-1234', - email: user.email, - }, - ) + ) + end + + let(:encrypted_arguments) do + Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( + JSON.generate({ applicant_pii: }), + ) + end + + it 'builds an applicant structure with that phone number' do + expect(build_applicant).to eql( + expected_attributes.merge(phone: '12025551212'), + ) + end + end + + context 'when the user has a hybrid-handoff phone' do + let(:applicant_pii_no_phone) do + Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( + best_effort_phone_number_for_socure: { + source: :hybrid_handoff, + phone: '12025556789', + }, + ) + end + + let(:encrypted_arguments) do + Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( + JSON.generate({ applicant_pii: applicant_pii_no_phone }), + ) + end + + it 'builds an applicant using the hybrid handoff number' do + expect(build_applicant).to eql( + expected_attributes.merge( + phone: '12025556789', + phone_source: 'hybrid_handoff', + ), + ) + end + end + + context 'when no phone is available for the user' do + it 'does not set phone at all' do + expect(build_applicant).to eql(expected_attributes) + end end end