diff --git a/app/controllers/idv/by_mail/resend_letter_controller.rb b/app/controllers/idv/by_mail/resend_letter_controller.rb new file mode 100644 index 00000000000..8b1bd002ef7 --- /dev/null +++ b/app/controllers/idv/by_mail/resend_letter_controller.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +module Idv + module ByMail + class ResendLetterController < ApplicationController + include Idv::AvailabilityConcern + include IdvSessionConcern + include Idv::StepIndicatorConcern + + before_action :confirm_two_factor_authenticated + before_action :confirm_verification_needed + before_action :confirm_mail_not_rate_limited + before_action :confirm_profile_not_too_old + + def new + analytics.idv_resend_letter_visited + end + + def create + update_tracking + + if pii_locked? + redirect_to capture_password_url + elsif resend_requested? + resend_letter + flash[:success] = t('idv.messages.gpo.another_letter_on_the_way') + redirect_to idv_letter_enqueued_url + end + end + + def gpo_mail_service + @gpo_mail_service ||= Idv::GpoMail.new(current_user) + end + + private + + def confirm_verification_needed + return if current_user.gpo_verification_pending_profile? + redirect_to account_url + end + + def confirm_profile_not_too_old + redirect_to idv_verify_by_mail_enter_code_path if gpo_mail_service.profile_too_old? + end + + def confirm_mail_not_rate_limited + redirect_to idv_verify_by_mail_enter_code_path if gpo_mail_service.rate_limited? + end + + def update_tracking + analytics.idv_gpo_address_letter_requested( + resend: true, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + ) + create_user_event(:gpo_mail_sent, current_user) + end + + def resend_requested? + current_user.gpo_verification_pending_profile? + end + + def first_letter_requested_at + current_user.gpo_verification_pending_profile&.gpo_verification_pending_at + end + + def hours_since_first_letter(first_letter_requested_at) + first_letter_requested_at ? + (Time.zone.now - first_letter_requested_at).to_i.seconds.in_hours.to_i : 0 + end + + def resend_letter + analytics.idv_gpo_address_letter_enqueued( + enqueued_at: Time.zone.now, + resend: true, + first_letter_requested_at: first_letter_requested_at, + hours_since_first_letter: + hours_since_first_letter(first_letter_requested_at), + phone_step_attempts: RateLimiter.new( + user: current_user, + rate_limit_type: :proof_address, + ).attempts, + ) + confirmation_maker = confirmation_maker_perform + send_reminder + return unless FeatureManagement.reveal_gpo_code? + session[:last_gpo_confirmation_code] = confirmation_maker.otp + end + + def confirmation_maker_perform + confirmation_maker = GpoConfirmationMaker.new( + pii: pii, + service_provider: current_sp, + profile: current_user.pending_profile, + ) + confirmation_maker.perform + confirmation_maker + end + + def pii + Pii::Cacher.new(current_user, user_session). + fetch(current_user.gpo_verification_pending_profile.id) + end + + def send_reminder + current_user.send_email_to_all_addresses(:verify_by_mail_letter_requested) + end + + def pii_locked? + !Pii::Cacher.new(current_user, user_session).exists_in_session? + end + + def step_indicator_steps + if in_person_proofing? + Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO + else + StepIndicatorConcern::STEP_INDICATOR_STEPS_GPO + end + end + end + end +end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index c3d209c6a1f..699e56d39c0 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3275,6 +3275,17 @@ def idv_request_letter_visited( ) end + # GPO "resend letter" page visited + # @identity.idp.previous_event_name IdV: request letter visited + def idv_resend_letter_visited( + **extra + ) + track_event( + :idv_resend_letter_visited, + **extra, + ) + end + # Acuant SDK errored after loading but before initialization # @param [Boolean] success # @param [String] error_message diff --git a/app/views/idv/by_mail/resend_letter/new.html.erb b/app/views/idv/by_mail/resend_letter/new.html.erb new file mode 100644 index 00000000000..57f0692c4b3 --- /dev/null +++ b/app/views/idv/by_mail/resend_letter/new.html.erb @@ -0,0 +1,35 @@ +<% self.title = t('titles.idv.get_letter') %> + +<% content_for(:pre_flash_content) do %> + <%= render StepIndicatorComponent.new( + steps: step_indicator_steps, + current_step: :verify_address, + locale_scope: 'idv', + class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4', + ) %> +<% end %> + +<%= render PageHeadingComponent.new.with_content(t('idv.gpo.request_another_letter.title')) %> +

+ <%= t('idv.gpo.request_another_letter.instructions_html') %> +

+

+ <%= new_tab_link_to( + t('idv.gpo.request_another_letter.learn_more_link'), + help_center_redirect_url( + category: 'verify-your-identity', + article: 'verify-your-address-by-mail', + flow: :idv, + step: :gpo_send_letter, + ), + ) %> +

+ +
+ <%= button_to t('idv.gpo.request_another_letter.button'), + idv_resend_letter_path, + method: 'put', + class: 'usa-button usa-button--big usa-button--wide' %> +
+ +<%= render 'idv/shared/back', fallback_path: idv_verify_by_mail_enter_code_url %> diff --git a/config/routes.rb b/config/routes.rb index 478efd2cd7e..0cbb1193a6f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -429,6 +429,8 @@ if FeatureManagement.gpo_verification_enabled? get '/by_mail/request_letter' => 'by_mail/request_letter#index', as: :request_letter put '/by_mail/request_letter' => 'by_mail/request_letter#create' + get '/by_mail/resend_letter' => 'by_mail/resend_letter#new', as: :resend_letter + put '/by_mail/resend_letter' => 'by_mail/resend_letter#create' end get '/by_mail/letter_enqueued' => 'by_mail/letter_enqueued#show', as: :letter_enqueued diff --git a/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb new file mode 100644 index 00000000000..15670d3f2f6 --- /dev/null +++ b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb @@ -0,0 +1,119 @@ +require 'rails_helper' + +RSpec.describe Idv::ByMail::ResendLetterController do + let(:user) { create(:user) } + + before do + stub_sign_in(user) + stub_analytics + end + + describe '#new' do + context 'the user has a gpo pending pending profile' do + before do + create(:profile, :verify_by_mail_pending, user: user) + end + + it 'renders the confirmation page' do + get(:new) + + expect(response).to have_http_status(200) + expect(@analytics).to have_logged_event(:idv_resend_letter_visited) + end + end + + context 'the user does not have a gpo pending profile' do + it 'redirects to the account page' do + get(:new) + + expect(response).to redirect_to(account_url) + end + end + + context 'the user has a profile that is too old to request a new letter' do + before do + create(:profile, :verify_by_mail_pending, created_at: 100.days.ago, user: user) + end + + it 'redirects to the enter OTP page' do + get(:new) + + expect(response).to redirect_to(idv_verify_by_mail_enter_code_path) + end + end + + context 'the user has sent to much mail' do + before do + profile = create(:profile, :verify_by_mail_pending, user: user) + create_list(:gpo_confirmation_code, 3, profile: profile) + end + + it 'redirects to the enter OTP page' do + get(:new) + + expect(response).to redirect_to(idv_verify_by_mail_enter_code_path) + end + end + end + + describe '#new' do + before do + create(:profile, :verify_by_mail_pending, :with_pii, user: user) + end + + it 'uses the GPO confirmation maker to send another letter and redirects', :freeze_time do + expect_to_resend_letter_and_redirect + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter requested', + hash_including( + resend: true, + first_letter_requested_at: user.pending_profile.gpo_verification_pending_at, + hours_since_first_letter: 24, + ), + ) + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter enqueued', + hash_including( + resend: true, + first_letter_requested_at: user.pending_profile.gpo_verification_pending_at, + hours_since_first_letter: 24, + enqueued_at: Time.zone.now, + proofing_components: nil, + ), + ) + end + + it 'redirects to capture password controller if the PII is locked' do + pii_cacher = instance_double(Pii::Cacher) + allow(pii_cacher).to receive(:fetch).and_return(nil) + allow(pii_cacher).to receive(:exists_in_session?).and_return(false) + allow(Pii::Cacher).to receive(:new).and_return(pii_cacher) + + put :create + + expect(response).to redirect_to capture_password_path + end + end + + def expect_to_resend_letter_and_redirect + pii = user.pending_profile.decrypt_pii(user.password).to_h + pii_cacher = instance_double(Pii::Cacher) + allow(pii_cacher).to receive(:fetch).with(user.pending_profile.id).and_return(pii) + allow(pii_cacher).to receive(:exists_in_session?).and_return(true) + allow(Pii::Cacher).to receive(:new).and_return(pii_cacher) + + service_provider = create(:service_provider, issuer: '123abc') + session[:sp] = { issuer: service_provider.issuer, vtr: ['C1'] } + + gpo_confirmation_maker = instance_double(GpoConfirmationMaker) + allow(GpoConfirmationMaker).to receive(:new). + with(pii: pii, service_provider: service_provider, profile: user.pending_profile). + and_return(gpo_confirmation_maker) + + expect(gpo_confirmation_maker).to receive(:perform) + expect { put :create }.to change { ActionMailer::Base.deliveries.count }.by(1) + expect(response).to redirect_to idv_letter_enqueued_path + end +end