diff --git a/app/controllers/idv/gpo_controller.rb b/app/controllers/idv/gpo_controller.rb index 80fb418888b..4ec812ed5dc 100644 --- a/app/controllers/idv/gpo_controller.rb +++ b/app/controllers/idv/gpo_controller.rb @@ -222,6 +222,8 @@ def enqueue_job document_capture_session, should_proof_state_id: false, trace_id: amzn_trace_id, + user_id: current_user.id, + threatmetrix_session_id: nil, ) end diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index bac62cde043..e4c12850d44 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -13,7 +13,7 @@ class ResolutionProofingJob < ApplicationJob ) def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:, - dob_year_only:) + dob_year_only:, user_id: nil, threatmetrix_session_id: nil) timer = JobHelpers::Timer.new raise_stale_job! if stale_job?(enqueued_at) @@ -25,6 +25,15 @@ def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:, applicant_pii = decrypted_args[:applicant_pii] + threatmetrix_result = nil + if use_lexisnexis_ddp_threatmetrix_before_rdp_instant_verify? + user = User.find_by(id: user_id) + threatmetrix_result = proof_lexisnexis_ddp_with_threatmetrix( + applicant_pii, user, threatmetrix_session_id + ) + log_threatmetrix_info(threatmetrix_result, user) + end + callback_log_data = if dob_year_only && should_proof_state_id proof_aamva_then_lexisnexis_dob_only( timer: timer, @@ -39,22 +48,48 @@ def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:, ) end + add_threatmetrix_result_to_callback_result(callback_log_data.result, threatmetrix_result) + document_capture_session = DocumentCaptureSession.new(result_id: result_id) document_capture_session.store_proofing_result(callback_log_data.result) ensure - logger.info( - { - name: 'ProofResolution', - trace_id: trace_id, - resolution_success: callback_log_data&.resolution_success, - state_id_success: callback_log_data&.state_id_success, - timing: timer.results, - }.to_json, + logger_info_hash( + name: 'ProofResolution', + trace_id: trace_id, + resolution_success: callback_log_data&.resolution_success, + state_id_success: callback_log_data&.state_id_success, + timing: timer.results, ) end private + def log_threatmetrix_info(threatmetrix_result, user) + logger_info_hash( + name: 'ThreatMetrix', + user_id: user&.uuid, + threatmetrix_request_id: threatmetrix_result.transaction_id, + threatmetrix_success: threatmetrix_result.success?, + ) + end + + def logger_info_hash(hash) + logger.info(hash.to_json) + end + + def add_threatmetrix_result_to_callback_result(callback_log_data_result, threatmetrix_result) + callback_log_data_result[:threatmetrix_success] = threatmetrix_result.success? + callback_log_data_result[:threatmetrix_request_id] = threatmetrix_result.transaction_id + end + + def proof_lexisnexis_ddp_with_threatmetrix(applicant_pii, user, threatmetrix_session_id) + return unless applicant_pii + ddp_pii = applicant_pii.dup + ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id + ddp_pii[:email] = user&.confirmed_email_addresses&.first&.email + lexisnexis_ddp_proofer.proof(ddp_pii) + end + # @return [CallbackLogData] def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:) proofer_result = timer.time('resolution') do @@ -202,6 +237,19 @@ def resolution_proofer end end + def lexisnexis_ddp_proofer + @lexisnexis_ddp_proofer ||= + if IdentityConfig.store.proofer_mock_fallback + Proofing::Mock::DdpMockClient.new + else + Proofing::LexisNexis::Ddp::Proofer.new( + api_key: IdentityConfig.store.lexisnexis_threatmetrix_api_key, + org_id: IdentityConfig.store.lexisnexis_threatmetrix_org_id, + base_url: IdentityConfig.store.lexisnexis_threatmetrix_base_url, + ) + end + end + def state_id_proofer @state_id_proofer ||= if IdentityConfig.store.proofer_mock_fallback @@ -218,4 +266,8 @@ def state_id_proofer ) end end + + def use_lexisnexis_ddp_threatmetrix_before_rdp_instant_verify? + IdentityConfig.store.lexisnexis_threatmetrix_enabled + end end diff --git a/app/services/idv/agent.rb b/app/services/idv/agent.rb index 40a52232c96..47c3dc06b9a 100644 --- a/app/services/idv/agent.rb +++ b/app/services/idv/agent.rb @@ -5,7 +5,11 @@ def initialize(applicant) end def proof_resolution( - document_capture_session, should_proof_state_id:, trace_id: + document_capture_session, + should_proof_state_id:, + trace_id:, + user_id:, + threatmetrix_session_id: ) document_capture_session.create_proofing_session @@ -19,6 +23,8 @@ def proof_resolution( dob_year_only: IdentityConfig.store.proofing_send_partial_dob, trace_id: trace_id, result_id: document_capture_session.result_id, + user_id: user_id, + threatmetrix_session_id: threatmetrix_session_id, } if IdentityConfig.store.ruby_workers_idv_enabled diff --git a/app/services/idv/steps/verify_base_step.rb b/app/services/idv/steps/verify_base_step.rb index 5439ffe1597..d77af214924 100644 --- a/app/services/idv/steps/verify_base_step.rb +++ b/app/services/idv/steps/verify_base_step.rb @@ -49,6 +49,8 @@ def add_proofing_costs(results) if stage == :resolution # transaction_id comes from ConversationId add_cost(:lexis_nexis_resolution, transaction_id: hash[:transaction_id]) + tmx_id = hash[:threatmetrix_request_id] + add_cost(:threatmetrix, transaction_id: tmx_id) if tmx_id elsif stage == :state_id process_aamva(hash[:transaction_id]) end @@ -176,6 +178,8 @@ def enqueue_job document_capture_session, should_proof_state_id: should_use_aamva?(pii), trace_id: amzn_trace_id, + user_id: user_id, + threatmetrix_session_id: flow_session[:threatmetrix_session_id], ) end diff --git a/app/services/proofing/lexis_nexis/ddp/proofer.rb b/app/services/proofing/lexis_nexis/ddp/proofer.rb new file mode 100644 index 00000000000..f3f31230127 --- /dev/null +++ b/app/services/proofing/lexis_nexis/ddp/proofer.rb @@ -0,0 +1,49 @@ +module Proofing + module LexisNexis + module Ddp + class Proofer < LexisNexis::Proofer + vendor_name 'lexisnexis:ddp' + + required_attributes :state_id_number, + :threatmetrix_session_id, + :state_id_number, + :first_name, + :last_name, + :dob, + :ssn, + :address1, + :city, + :state, + :zipcode + + optional_attributes :address2, :phone, :email + + stage :resolution + + proof do |applicant, result| + proof_applicant(applicant, result) + end + + def send_verification_request(applicant) + VerificationRequest.new(config: config, applicant: applicant).send + end + + def proof_applicant(applicant, result) + response = send_verification_request(applicant) + process_response(response, result) + end + + private + + def process_response(response, result) + body = response.response_body + result.transaction_id = body['request_id'] + request_result = body['request_result'] + review_status = body['review_status'] + result.add_error(:request_result, request_result) unless request_result == 'success' + result.add_error(:review_status, review_status) unless review_status == 'pass' + end + end + end + end +end diff --git a/app/services/proofing/lexis_nexis/ddp/verification_request.rb b/app/services/proofing/lexis_nexis/ddp/verification_request.rb new file mode 100644 index 00000000000..2b895b87311 --- /dev/null +++ b/app/services/proofing/lexis_nexis/ddp/verification_request.rb @@ -0,0 +1,46 @@ +module Proofing + module LexisNexis + module Ddp + class VerificationRequest < Request + private + + def build_request_body + { + api_key: config.api_key, + org_id: config.org_id, + account_address_street1: applicant[:address1], + account_address_street2: applicant[:address2] || '', + account_address_city: applicant[:city], + account_address_state: applicant[:state], + account_address_country: 'US', + account_address_zip: applicant[:zipcode], + account_date_of_birth: applicant[:dob] ? + Date.parse(applicant[:dob]).strftime('%Y%m%d') : '', + account_email: applicant[:email], + account_first_name: applicant[:first_name], + account_last_name: applicant[:last_name], + account_telephone: '', # applicant[:phone], decision was made not to send phone + drivers_license_number_hash: applicant[:state_id_number] ? + OpenSSL::Digest::SHA256.hexdigest(applicant[:state_id_number].gsub(/\W/, '')) : '', + event_type: 'ACCOUNT_CREATION', + service_type: 'all', + session_id: applicant[:threatmetrix_session_id], + ssn_hash: OpenSSL::Digest::SHA256.hexdigest(applicant[:ssn].gsub(/\D/, '')), + }.to_json + end + + def metric_name + 'lexis_nexis_ddp' + end + + def url_request_path + '/api/session-query' + end + + def timeout + IdentityConfig.store.lexisnexis_threatmetrix_timeout + end + end + end + end +end diff --git a/app/services/proofing/lexis_nexis/proofer.rb b/app/services/proofing/lexis_nexis/proofer.rb index 967610ff24c..07cba134baa 100644 --- a/app/services/proofing/lexis_nexis/proofer.rb +++ b/app/services/proofing/lexis_nexis/proofer.rb @@ -12,6 +12,8 @@ class Proofer < Proofing::Base :password, :request_mode, :request_timeout, + :org_id, + :api_key, keyword_init: true, allowed_members: [ :instant_verify_workflow, diff --git a/app/services/proofing/mock/ddp_mock_client.rb b/app/services/proofing/mock/ddp_mock_client.rb new file mode 100644 index 00000000000..acf18b3d8e2 --- /dev/null +++ b/app/services/proofing/mock/ddp_mock_client.rb @@ -0,0 +1,28 @@ +module Proofing + module Mock + class DdpMockClient < Proofing::Base + vendor_name 'DdpMock' + + required_attributes :threatmetrix_session_id, + :state_id_number, + :first_name, + :last_name, + :dob, + :ssn, + :address1, + :city, + :state, + :zipcode + + optional_attributes :address2, :phone, :email + + stage :resolution + + TRANSACTION_ID = 'ddp-mock-transaction-id-123' + + proof do |applicant, result| + result.transaction_id = TRANSACTION_ID + end + end + end +end diff --git a/config/application.yml.default b/config/application.yml.default index 9d5fadab496..670c352a38f 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -139,14 +139,12 @@ lexisnexis_trueid_liveness_nocropping_workflow: customers.gsa.trueid.workflow lexisnexis_trueid_noliveness_cropping_workflow: customers.gsa.trueid.workflow lexisnexis_trueid_noliveness_nocropping_workflow: customers.gsa.trueid.workflow ################################################################### -# LexisNexis ThreatMetrix ########################################## +# LexisNexis DDP/ThreatMetrix ##################################### +lexisnexis_threatmetrix_api_key: test_api_key lexisnexis_threatmetrix_base_url: https://www.example.com -lexisnexis_threatmetrix_request_mode: testing lexisnexis_threatmetrix_org_id: test_account -lexisnexis_threatmetrix_username: test_username -lexisnexis_threatmetrix_password: test_password -lexisnexis_threatmetrix_instant_verify_timeout: 1.0 -lexisnexis_threatmetrix_instant_verify_workflow: customers.gsa.instant.verify.workflow +lexisnexis_threatmetrix_timeout: 1.0 +lexisnexis_threatmetrix_enabled: false ################################################################### lockout_period_in_minutes: 10 log_to_stdout: false diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 17d9b9c0506..e9439310dcc 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -208,13 +208,11 @@ def self.build_store(config_map) config.add(:lexisnexis_trueid_noliveness_cropping_workflow, type: :string) config.add(:lexisnexis_trueid_noliveness_nocropping_workflow, type: :string) config.add(:lexisnexis_trueid_timeout, type: :float) + config.add(:lexisnexis_threatmetrix_api_key, type: :string) config.add(:lexisnexis_threatmetrix_base_url, type: :string) - config.add(:lexisnexis_threatmetrix_request_mode, type: :string) + config.add(:lexisnexis_threatmetrix_enabled, type: :string) config.add(:lexisnexis_threatmetrix_org_id, type: :string) - config.add(:lexisnexis_threatmetrix_username, type: :string) - config.add(:lexisnexis_threatmetrix_password, type: :string) - config.add(:lexisnexis_threatmetrix_instant_verify_timeout, type: :float) - config.add(:lexisnexis_threatmetrix_instant_verify_workflow, type: :string) + config.add(:lexisnexis_threatmetrix_timeout, type: :float) config.add(:liveness_checking_enabled, type: :boolean) config.add(:lockout_period_in_minutes, type: :integer) config.add(:log_to_stdout, type: :boolean) diff --git a/spec/features/idv/doc_auth/verify_step_spec.rb b/spec/features/idv/doc_auth/verify_step_spec.rb index 70d510d63cb..62e6d2a3056 100644 --- a/spec/features/idv/doc_auth/verify_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_step_spec.rb @@ -157,16 +157,18 @@ allow(IdentityConfig.store).to receive(:aamva_supported_jurisdictions).and_return( [Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction]], ) + user = create(:user, :signed_up) expect_any_instance_of(Idv::Agent). to receive(:proof_resolution). with( anything, should_proof_state_id: true, trace_id: anything, + threatmetrix_session_id: nil, + user_id: user.id, ). and_call_original - user = create(:user, :signed_up) sign_in_and_2fa_user(user) complete_doc_auth_steps_before_verify_step click_idv_continue @@ -181,16 +183,18 @@ IdentityConfig.store.aamva_supported_jurisdictions - [Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction]], ) + user = create(:user, :signed_up) expect_any_instance_of(Idv::Agent). to receive(:proof_resolution). with( anything, should_proof_state_id: false, trace_id: anything, + threatmetrix_session_id: nil, + user_id: user.id, ). and_call_original - user = create(:user, :signed_up) sign_in_and_2fa_user(user) complete_doc_auth_steps_before_verify_step click_idv_continue @@ -203,17 +207,19 @@ it 'does not perform the state ID check' do allow(IdentityConfig.store).to receive(:aamva_sp_banlist_issuers). and_return('["urn:gov:gsa:openidconnect:sp:server"]') + user = create(:user, :signed_up) expect_any_instance_of(Idv::Agent). to receive(:proof_resolution). with( anything, should_proof_state_id: false, trace_id: anything, + threatmetrix_session_id: nil, + user_id: user.id, ). and_call_original visit_idp_from_sp_with_ial1(:oidc) - user = create(:user, :signed_up) sign_in_and_2fa_user(user) complete_doc_auth_steps_before_verify_step click_idv_continue diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json new file mode 100644 index 00000000000..4aa2c84f848 --- /dev/null +++ b/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json @@ -0,0 +1,5 @@ +{ + "error_detail": "service_type", + "request_id":"1234-abcd", + "request_result":"fail_invalid_parameter" +} diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/request.json b/spec/fixtures/proofing/lexis_nexis/ddp/request.json new file mode 100644 index 00000000000..bf7aefc8db4 --- /dev/null +++ b/spec/fixtures/proofing/lexis_nexis/ddp/request.json @@ -0,0 +1,20 @@ +{ + "api_key": "test_api_key", + "org_id": "test_org_id", + "account_address_street1": "123 Main St", + "account_address_street2": "Ste 3", + "account_address_city": "Baton Rouge", + "account_address_state": "LA", + "account_address_country": "US", + "account_address_zip": "70802-12345", + "account_date_of_birth": "19800101", + "account_email": "test@example.com", + "account_first_name": "Testy", + "account_last_name": "McTesterson", + "account_telephone": "", + "drivers_license_number_hash": "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f", + "event_type": "ACCOUNT_CREATION", + "service_type": "all", + "session_id": "UNIQUE_SESSION_ID", + "ssn_hash": "15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225" +} diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json new file mode 100644 index 00000000000..1d95511b371 --- /dev/null +++ b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json @@ -0,0 +1,18 @@ +{ + "account_address_result": "success", + "account_address_score": "0", + "account_email_result": "success", + "account_email_score": "0", + "account_name_result": "success", + "account_name_score": "0", + "account_telephone_result": "success", + "account_telephone_score": "0", + "request_id": "1234", + "request_result": "success", + "review_status": "pass", + "risk_rating": "trusted", + "ssn_hash_result": "success", + "ssn_hash_score": "0", + "summary_risk_score": "-6", + "tmx_risk_rating": "neutral" +} diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 0980635a298..dfd4328d936 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -39,6 +39,9 @@ instance_double(Proofing::Aamva::Proofer, class: Proofing::Aamva::Proofer) end let(:trace_id) { SecureRandom.uuid } + let(:user) { build(:user, :signed_up) } + let(:threatmetrix_session_id) { SecureRandom.uuid } + let(:threatmetrix_request_id) { '1234' } describe '.perform_later' do it 'stores results' do @@ -48,6 +51,8 @@ dob_year_only: dob_year_only, encrypted_arguments: encrypted_arguments, trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: threatmetrix_session_id, ) result = document_capture_session.load_proofing_result[:result] @@ -65,17 +70,25 @@ dob_year_only: dob_year_only, encrypted_arguments: encrypted_arguments, trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: threatmetrix_session_id, ) end - context 'webmock lexisnexis' do + context 'webmock lexisnexis and threatmetrix' do before do stub_request( :post, 'https://lexisnexis.example.com/restws/identity/v2/abc123/aaa/conversation', ).to_return(body: lexisnexis_response.to_json) + stub_request( + :post, + 'https://www.example.com/api/session-query', + ).to_return(body: LexisNexisFixtures.ddp_success_response_json) allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false) + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled). + and_return(true) allow(IdentityConfig.store).to receive(:lexisnexis_account_id).and_return('abc123') allow(IdentityConfig.store).to receive(:lexisnexis_request_mode).and_return('aaa') @@ -140,6 +153,8 @@ }, transaction_id: lexisnexis_transaction_id, reference: lexisnexis_reference, + threatmetrix_success: true, + threatmetrix_request_id: threatmetrix_request_id, ) end @@ -216,6 +231,8 @@ }, transaction_id: lexisnexis_transaction_id, reference: lexisnexis_reference, + threatmetrix_request_id: threatmetrix_request_id, + threatmetrix_success: true, ) end end @@ -235,13 +252,23 @@ and_return(Proofing::Result.new) end - it 'logs the trace_id and timing info' do - expect(instance.logger).to receive(:info) do |message| - expect(JSON.parse(message, symbolize_names: true)).to include( + it 'logs the trace_id and timing info for ProofResolution and the Threatmetrix info' do + expect(instance).to receive(:logger_info_hash).ordered.with( + hash_including( + name: 'ThreatMetrix', + user_id: nil, + threatmetrix_request_id: Proofing::Mock::DdpMockClient::TRANSACTION_ID, + threatmetrix_success: true, + ), + ) + + expect(instance).to receive(:logger_info_hash).ordered.with( + hash_including( :timing, + name: 'ProofResolution', trace_id: trace_id, - ) - end + ), + ) perform end diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb index 2b941f7767e..3f11d6e6463 100644 --- a/spec/services/idv/agent_spec.rb +++ b/spec/services/idv/agent_spec.rb @@ -25,7 +25,11 @@ Idp::Constants::MOCK_IDV_APPLICANT.merge(uuid: user.uuid, ssn: '444-55-6666'), ) agent.proof_resolution( - document_capture_session, should_proof_state_id: true, trace_id: trace_id + document_capture_session, + should_proof_state_id: true, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result @@ -36,7 +40,11 @@ it 'does proof state_id if resolution succeeds' do agent = Idv::Agent.new(Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge(uuid: user.uuid)) agent.proof_resolution( - document_capture_session, should_proof_state_id: true, trace_id: trace_id + document_capture_session, + should_proof_state_id: true, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result expect(result[:context][:stages][:state_id]).to include( @@ -52,7 +60,11 @@ Idp::Constants::MOCK_IDV_APPLICANT.merge(uuid: user.uuid, ssn: '444-55-6666'), ) agent.proof_resolution( - document_capture_session, should_proof_state_id: true, trace_id: trace_id + document_capture_session, + should_proof_state_id: true, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result expect(result[:errors][:ssn]).to eq ['Unverified SSN.'] @@ -62,7 +74,11 @@ it 'does not proof state_id if resolution succeeds' do agent = Idv::Agent.new(Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge(uuid: user.uuid)) agent.proof_resolution( - document_capture_session, should_proof_state_id: false, trace_id: trace_id + document_capture_session, + should_proof_state_id: false, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result @@ -78,7 +94,11 @@ ) agent.proof_resolution( - document_capture_session, should_proof_state_id: false, trace_id: trace_id + document_capture_session, + should_proof_state_id: false, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result @@ -97,7 +117,11 @@ ) agent.proof_resolution( - document_capture_session, should_proof_state_id: true, trace_id: trace_id + document_capture_session, + should_proof_state_id: true, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: nil, ) result = document_capture_session.load_proofing_result.result @@ -117,7 +141,10 @@ it 'proofs addresses successfully with valid information' do agent = Idv::Agent.new({ phone: Faker::PhoneNumber.cell_phone }) agent.proof_address( - document_capture_session, trace_id: trace_id, user_id: user_id, issuer: issuer + document_capture_session, + trace_id: trace_id, + user_id: user_id, + issuer: issuer, ) result = document_capture_session.load_proofing_result[:result] expect(result[:context][:stages]).to include({ address: 'AddressMock' }) diff --git a/spec/services/idv/steps/ipp/verify_step_spec.rb b/spec/services/idv/steps/ipp/verify_step_spec.rb index 0987bc9c936..0cba80606e0 100644 --- a/spec/services/idv/steps/ipp/verify_step_spec.rb +++ b/spec/services/idv/steps/ipp/verify_step_spec.rb @@ -52,6 +52,8 @@ kind_of(DocumentCaptureSession), should_proof_state_id: anything, trace_id: amzn_trace_id, + threatmetrix_session_id: nil, + user_id: anything, ) step.call diff --git a/spec/services/idv/steps/verify_step_spec.rb b/spec/services/idv/steps/verify_step_spec.rb index 832e317307b..79ff6ed8616 100644 --- a/spec/services/idv/steps/verify_step_spec.rb +++ b/spec/services/idv/steps/verify_step_spec.rb @@ -60,6 +60,8 @@ kind_of(DocumentCaptureSession), should_proof_state_id: anything, trace_id: amzn_trace_id, + threatmetrix_session_id: nil, + user_id: user.id, ) step.call diff --git a/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb new file mode 100644 index 00000000000..0b5e2588cf7 --- /dev/null +++ b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb @@ -0,0 +1,74 @@ +require 'rails_helper' + +describe Proofing::LexisNexis::Ddp::Proofer do + let(:applicant) do + { + first_name: 'Testy', + last_name: 'McTesterson', + ssn: '123456789', + dob: '01/01/1980', + address1: '123 Main St', + address2: 'Ste 3', + city: 'Baton Rouge', + state: 'LA', + zipcode: '70802-12345', + state_id_number: '12345678', + threatmetrix_session_id: '123456', + phone: '5551231234', + email: 'test@example.com', + } + end + let(:verification_request) do + Proofing::LexisNexis::Ddp::VerificationRequest.new( + applicant: applicant, + config: LexisNexisFixtures.example_config, + ) + end + + describe '#send' do + context 'when the request times out' do + it 'raises a timeout error' do + stub_request(:post, verification_request.url).to_timeout + + expect { verification_request.send }.to raise_error( + Proofing::TimeoutError, + 'LexisNexis timed out waiting for verification response', + ) + end + end + + context 'when the request is made' do + it 'it looks like the right request' do + request = stub_request(:post, verification_request.url). + with(body: verification_request.body, headers: verification_request.headers). + to_return(body: LexisNexisFixtures.ddp_success_response_json, status: 200) + + verification_request.send + + expect(request).to have_been_requested.once + end + end + end + + subject(:instance) do + Proofing::LexisNexis::Ddp::Proofer.new(**LexisNexisFixtures.example_config.to_h) + end + + describe '#proof' do + subject(:result) { instance.proof(applicant) } + + before do + stub_request(:post, verification_request.url). + to_return(body: response_body, status: 200) + end + + context 'when the response is a full match' do + let(:response_body) { LexisNexisFixtures.ddp_success_response_json } + + it 'is a successful result' do + expect(result.success?).to eq(true) + expect(result.errors).to be_empty + end + end + end +end diff --git a/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb b/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb new file mode 100644 index 00000000000..ec7c7d71c91 --- /dev/null +++ b/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +describe Proofing::LexisNexis::Ddp::VerificationRequest do + let(:dob) { '1980-01-01' } + let(:applicant) do + { + first_name: 'Testy', + last_name: 'McTesterson', + ssn: '123-45-6789', + dob: dob, + address1: '123 Main St', + address2: 'Ste 3', + city: 'Baton Rouge', + state: 'LA', + zipcode: '70802-12345', + state_id_number: '12345678', + threatmetrix_session_id: 'UNIQUE_SESSION_ID', + phone: '5551231234', + email: 'test@example.com', + } + end + + let(:response_body) { LexisNexisFixtures.ddp_success_response_json } + subject do + described_class.new(applicant: applicant, config: LexisNexisFixtures.example_ddp_config) + end + + describe '#body' do + it 'returns a properly formed request body' do + expect(subject.body).to eq(LexisNexisFixtures.ddp_request_json) + end + + context 'without an address line 2' do + let(:applicant) do + hash = super() + hash.delete(:address2) + hash + end + + it 'sets StreetAddress2 to and empty string' do + parsed_body = JSON.parse(subject.body, symbolize_names: true) + expect(parsed_body[:account_address_street2]).to eq('') + end + end + end + + describe '#url' do + it 'returns a url for the DDP session query endpoint' do + expect(subject.url).to eq('https://example.com/api/session-query') + end + end +end diff --git a/spec/support/lexis_nexis_fixtures.rb b/spec/support/lexis_nexis_fixtures.rb index f58c92a6055..ab1c91b78f4 100644 --- a/spec/support/lexis_nexis_fixtures.rb +++ b/spec/support/lexis_nexis_fixtures.rb @@ -14,6 +14,34 @@ def example_config ) end + def example_ddp_config + Proofing::LexisNexis::Proofer::Config.new( + api_key: 'test_api_key', + base_url: 'https://example.com', + org_id: 'test_org_id', + ) + end + + def ddp_request_json + raw = read_fixture_file_at_path('ddp/request.json') + JSON.parse(raw).to_json + end + + def ddp_success_response_json + raw = read_fixture_file_at_path('ddp/successful_response.json') + JSON.parse(raw).to_json + end + + def ddp_failure_response_json + raw = read_fixture_file_at_path('ddp/failed_response.json') + JSON.parse(raw).to_json + end + + def ddp_error_response_json + raw = read_fixture_file_at_path('ddp/error_response.json') + JSON.parse(raw).to_json + end + def instant_verify_request_json raw = read_fixture_file_at_path('instant_verify/request.json') JSON.parse(raw).to_json