diff --git a/app/assets/images/email/user-signup-ial2.png b/app/assets/images/email/user-signup-ial2.png new file mode 100644 index 00000000000..9d8521fc12e Binary files /dev/null and b/app/assets/images/email/user-signup-ial2.png differ diff --git a/app/assets/stylesheets/email.css.scss b/app/assets/stylesheets/email.css.scss index 0c13b1ef748..b9e1286126f 100644 --- a/app/assets/stylesheets/email.css.scss +++ b/app/assets/stylesheets/email.css.scss @@ -32,6 +32,11 @@ line-height: 10px; } +.s16 { + font-size: 16px; + line-height: 24px; +} + .s30 { font-size: 30px; line-height: 30px; diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index 9f585b6f0de..8b3b78bd7dd 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -143,6 +143,7 @@ def update_enrollment_status(enrollment, response) enrollment.profile.activate enrollment.update(status: :passed) handle_successful_status_update(enrollment) + send_verified_email(enrollment.user, enrollment) else # Unsupported ID type enrollment.update(status: :failed) @@ -151,8 +152,29 @@ def update_enrollment_status(enrollment, response) when IPP_STATUS_FAILED enrollment.update(status: :failed) handle_failed_status(enrollment, response) + send_failed_email(enrollment.user, enrollment) else handle_unsupported_status(enrollment, response['status']) end end + + def send_verified_email(user, enrollment) + user.confirmed_email_addresses.each do |email_address| + UserMailer.in_person_verified( + user, + email_address, + enrollment: enrollment, + ).deliver_now_or_later + end + end + + def send_failed_email(user, enrollment) + user.confirmed_email_addresses.each do |email_address| + UserMailer.in_person_failed( + user, + email_address, + enrollment: enrollment, + ).deliver_now_or_later + end + end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 2831a2639f7..ccb2bef93d6 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -236,6 +236,27 @@ def in_person_ready_to_verify(user, email_address, first_name:, enrollment:) end end + def in_person_verified(user, email_address, enrollment:) + with_user_locale(user) do + @hide_title = true + @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(enrollment: enrollment) + mail( + to: email_address.email, + subject: t('user_mailer.in_person_verified.subject', app_name: APP_NAME), + ) + end + end + + def in_person_failed(user, email_address, enrollment:) + with_user_locale(user) do + @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(enrollment: enrollment) + mail( + to: email_address.email, + subject: t('user_mailer.in_person_failed.subject', app_name: APP_NAME), + ) + end + end + private def email_should_receive_nonessential_notifications?(email) diff --git a/app/presenters/idv/in_person/verification_results_email_presenter.rb b/app/presenters/idv/in_person/verification_results_email_presenter.rb new file mode 100644 index 00000000000..d8970ec9388 --- /dev/null +++ b/app/presenters/idv/in_person/verification_results_email_presenter.rb @@ -0,0 +1,22 @@ +module Idv + module InPerson + class VerificationResultsEmailPresenter + # update to user's time zone when out of pilot + USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York'] + + def initialize(enrollment:) + @enrollment = enrollment + end + + def location_name + @enrollment.selected_location_details['name'] + end + + def formatted_verified_date + @enrollment.status_updated_at.in_time_zone(USPS_SERVER_TIMEZONE).strftime( + I18n.t('time.formats.event_date'), + ) + end + end + end +end diff --git a/app/views/layouts/user_mailer.html.erb b/app/views/layouts/user_mailer.html.erb index f695ac2cfe6..243a7b28278 100644 --- a/app/views/layouts/user_mailer.html.erb +++ b/app/views/layouts/user_mailer.html.erb @@ -71,8 +71,11 @@ -

<%= @header || message.subject %> -

<%= yield %> + <% unless @hide_title %> +

<%= @header || message.subject %> +

+ <% end %> + <%= yield %> diff --git a/app/views/shared/_in-person-verification-results-email-lower.html.erb b/app/views/shared/_in-person-verification-results-email-lower.html.erb new file mode 100644 index 00000000000..3ebe6dca242 --- /dev/null +++ b/app/views/shared/_in-person-verification-results-email-lower.html.erb @@ -0,0 +1,38 @@ +
+
+ + + + + + +
+ + + + + + +
+ <%= link_to t('user_mailer.in_person_verified.sign_in'), + idv_url, + target: '_blank', + class: 'float-center', + rel: 'noopener' %> +
+
+ + +

+ <%= link_to idv_url, idv_url, target: '_blank', rel: 'noopener' %> +

+ +
+
+ <%= t( + 'user_mailer.in_person_verified.warning_contact_us_html', + contact_us_url: MarketingSite.contact_url, + sign_in_url: idv_url, + ) + %> +
diff --git a/app/views/user_mailer/in_person_failed.html.erb b/app/views/user_mailer/in_person_failed.html.erb new file mode 100644 index 00000000000..f34e8718abb --- /dev/null +++ b/app/views/user_mailer/in_person_failed.html.erb @@ -0,0 +1,20 @@ +
+ <%= t('user_mailer.in_person_verified.greeting') %>
+

+ <%= t( + 'user_mailer.in_person_failed.intro', + location: @presenter.location_name, + date: @presenter.formatted_verified_date, + ) %> +

+

+ <%= t('user_mailer.in_person_failed.body', app_name: APP_NAME) %>

+

+ <%= t('user_mailer.in_person_failed.verifying_identity') %>
+ +
+ +<%= render 'shared/in-person-verification-results-email-lower' %> diff --git a/app/views/user_mailer/in_person_verified.html.erb b/app/views/user_mailer/in_person_verified.html.erb new file mode 100644 index 00000000000..8ffd02a9cce --- /dev/null +++ b/app/views/user_mailer/in_person_verified.html.erb @@ -0,0 +1,19 @@ +<%= image_tag( + 'email/user-signup-ial2.png', + width: 140, + height: 177, + alt: '', + class: 'float-center padding-bottom-4', + ) %> +

<%= message.subject %>

+

+ <%= t('user_mailer.in_person_verified.greeting') %>
+ <%= t( + 'user_mailer.in_person_verified.intro', + location: @presenter.location_name, + date: @presenter.formatted_verified_date, + ) %>

+ <%= t('user_mailer.in_person_verified.next_sign_in', app_name: APP_NAME) %> +

+ +<%= render 'shared/in-person-verification-results-email-lower' %> diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 587cc7c4aa0..634f1ad81aa 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -100,11 +100,35 @@ en: %{app_name} %{help_link} or %{contact_link}. subject: Email address deleted help_link_text: Help Center + in_person_failed: + body: Click the button or copy the link below to try verifying your identity + online again through %{app_name}. If you are still experiencing issues, + please contact the agency you are trying to access. + intro: Your identity could not be verified at the %{location} Post Office on + %{date}. + subject: Your identity could not be verified in person + verifying_identity: 'When verifying your identity:' + verifying_step_not_expired: 'Your state-issued ID or driver’s license must not + be expired. We do not currently accept any other forms of + identification, such as passports and military IDs.' + verifying_step_proof_of_address: 'If you try to verify your identity in person + again, you need to bring a valid proof of address if your current + address is different than the address on your ID.' in_person_ready_to_verify: greeting: Hi %{name}, intro: Here are the details to verify your identity in person at a United States Post Office near you. subject: You’re ready to verify your identity with %{app_name} in person + in_person_verified: + greeting: Hello, + intro: You successfully verified your identity at the %{location} Post Office on + %{date}. + next_sign_in: Next, click the button or copy the link below to sign in to %{app_name}. + sign_in: Sign in + subject: You successfully verified your identity with %{app_name} + warning_contact_us_html: If you did not attempt to verify your identity in + person, please contact us and sign in to change your password. letter_reminder: info_html: The letter you are about to receive will contain a confirmation code that helps us verify your address. You can complete the identity diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index 7ac1e5820c8..9f0634ed76a 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -106,11 +106,38 @@ es: %{app_name} %{help_link} o el %{contact_link}. subject: Dirección de correo electrónico eliminada help_link_text: Centro de Ayuda + in_person_failed: + body: Haga clic en el botón o copie el enlace siguiente para volver a intentar + verificar su identidad en línea con %{app_name}. Si sigue teniendo + problemas, póngase en contacto con la agencia a la que intenta acceder. + intro: El %{date}, no se pudo verificar su identidad en la oficina de correos de + %{location}. + subject: No se pudo verificar su identidad en persona + verifying_identity: 'Al verificar su identidad:' + verifying_step_not_expired: Su documento de identidad o permiso de conducir + emitido por el estado debe estar vigente. Por el momento, no aceptamos + otras formas de identificación, como pasaportes o cartillas militares. + verifying_step_proof_of_address: Si vuelve a intentar verificar su identidad en + persona, deberá llevar un comprobante de domicilio vigente si su + dirección actual es distinta de la que aparece en su documento de + identidad. in_person_ready_to_verify: greeting: 'Hola, %{name}:' intro: Estos son los detalles para verificar su identidad en persona en una oficina de correos de los Estados Unidos cercana a usted. subject: Está listo para verificar su identidad con %{app_name} en persona + in_person_verified: + greeting: Hola, + intro: El %{date}, verificó correctamente su identidad en la oficina de correos + de %{location}. + next_sign_in: Luego, haga clic en el botón o copie el enlace que aparece a + continuación para iniciar sesión en %{app_name}. + sign_in: Iniciar sesión + subject: Verificó correctamente su identidad con %{app_name} + warning_contact_us_html: Si usted no intentó verificar su identidad en persona, + por favor, póngase en contacto con + nosotros e inicie sesión para cambiar + su contraseña. letter_reminder: info_html: La carta que está a punto de recibir contendrá un código de confirmación que nos ayudará a verificar su dirección. Puede completar diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index f1a56ba36e1..1e72e5204ce 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -109,11 +109,40 @@ fr: veuillez visiter le %{help_link} de %{app_name} ou %{contact_link}. subject: Adresse email supprimée help_link_text: Centre d’aide + in_person_failed: + body: Cliquez sur le bouton ou copiez le lien ci-dessous pour essayer de + vérifier à nouveau votre identité en ligne par le biais de %{app_name}. + Si vous rencontrez toujours des problèmes, veuillez contacter l’agence à + laquelle vous essayez d’accéder. + intro: Votre identité n’a pas pu être vérifiée au bureau de poste de %{location} + le %{date}. + subject: Votre identité n’a pas pu être vérifiée en personne + verifying_identity: 'Lors de la vérification de votre identité :' + verifying_step_not_expired: Votre carte d’identité ou votre permis de conduire + délivré par l’État ne doit pas être périmé. Nous n’acceptons + actuellement aucune autre forme d’identification, comme les passeports + et les cartes d’identité militaires. + verifying_step_proof_of_address: Si vous tentez à nouveau de vérifier votre + identité en personne, vous devez apporter un justificatif de domicile + valable si votre adresse actuelle est différente de celle figurant sur + votre pièce d’identité. in_person_ready_to_verify: greeting: Bonjour %{name}, intro: Voici les détails pour vérifier votre identité en personne dans un bureau de poste des États-Unis près de chez vous. subject: Vous êtes prêt à vérifier votre identité avec %{app_name} en personne + in_person_verified: + greeting: Bonjour, + intro: Vous avez vérifié avec succès votre identité au bureau de poste de + %{location} le %{date}. + next_sign_in: Ensuite, cliquez sur le bouton ou copiez le lien ci-dessous pour + vous connecter à %{app_name}. + sign_in: Se connecter + subject: Vous avez vérifié avec succès votre identité avec %{app_name} + warning_contact_us_html: Si vous n’avez pas essayé de vérifier votre identité en + personne, veuillez nous contacter et + vous connecter pour changer votre mot de + passe. letter_reminder: info_html: La lettre que vous êtes sur le point de recevoir contiendra un code de confirmation nous permettant de vérifier votre adresse. Vous pouvez diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index 9ff78d25b28..5500a41e826 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -17,16 +17,36 @@ let!(:passed_enrollment) { create(:in_person_enrollment, :passed) } let!(:pending_enrollment) do - create(:in_person_enrollment, status: :pending, enrollment_code: SecureRandom.hex(16)) + create( + :in_person_enrollment, + status: :pending, + enrollment_code: SecureRandom.hex(16), + selected_location_details: { name: 'FRIENDSHIP' }, + ) end let!(:pending_enrollment_2) do - create(:in_person_enrollment, status: :pending, enrollment_code: SecureRandom.hex(16)) + create( + :in_person_enrollment, + status: :pending, + enrollment_code: SecureRandom.hex(16), + selected_location_details: { name: 'BALTIMORE' }, + ) end let!(:pending_enrollment_3) do - create(:in_person_enrollment, status: :pending, enrollment_code: SecureRandom.hex(16)) + create( + :in_person_enrollment, + status: :pending, + enrollment_code: SecureRandom.hex(16), + selected_location_details: { name: 'WASHINGTON' }, + ) end let!(:pending_enrollment_4) do - create(:in_person_enrollment, status: :pending, enrollment_code: SecureRandom.hex(16)) + create( + :in_person_enrollment, + status: :pending, + enrollment_code: SecureRandom.hex(16), + selected_location_details: { name: 'ARLINGTON' }, + ) end let(:pending_enrollments) do [ @@ -93,7 +113,7 @@ ) end - it 'logs details about failed requests' do + it 'logs details about a failed proofing' do stub_request_token stub_request_failed_proofing_results @@ -121,7 +141,29 @@ ) end - it 'updates enrollment records and activates profiles on 2xx responses with valid JSON' do + it 'sends proofing failed email on response with failed status' do + stub_request_token + stub_request_failed_proofing_results + + allow(InPersonEnrollment).to receive(:needs_usps_status_check). + and_return([pending_enrollment]) + + mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true) + user = pending_enrollment.user + user.email_addresses.each do |email_address| + expect(UserMailer).to receive(:in_person_failed). + with( + user, + email_address, + enrollment: instance_of(InPersonEnrollment), + ). + and_return(mailer) + end + + job.perform(Time.zone.now) + end + + it 'updates enrollment records and activates profiles on response with passed status' do stub_request_token stub_request_passed_proofing_results @@ -148,6 +190,28 @@ end end + it 'sends verifed email on 2xx responses with valid JSON' do + stub_request_token + stub_request_passed_proofing_results + + allow(InPersonEnrollment).to receive(:needs_usps_status_check). + and_return([pending_enrollment]) + + mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true) + user = pending_enrollment.user + user.email_addresses.each do |email_address| + expect(UserMailer).to receive(:in_person_verified). + with( + user, + email_address, + enrollment: instance_of(InPersonEnrollment), + ). + and_return(mailer) + end + + job.perform(Time.zone.now) + end + it 'receives a non-hash value' do stub_request_token stub_request_proofing_results_with_responses({}) diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 0b1dd186ba0..d4535666b41 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -143,6 +143,22 @@ def in_person_ready_to_verify ) end + def in_person_verified + UserMailer.in_person_verified( + user, + email_address_record, + enrollment: in_person_enrollment, + ) + end + + def in_person_failed + UserMailer.in_person_failed( + user, + email_address_record, + enrollment: in_person_enrollment, + ) + end + private def user @@ -163,7 +179,8 @@ def in_person_enrollment user: user, profile: unsaveable(Profile.new(user: user)), enrollment_code: '2048702198804358', - created_at: Time.zone.now, + created_at: Time.zone.now - 2.hours, + status_updated_at: Time.zone.now - 1.hour, current_address_matches_id: true, selected_location_details: { 'name' => 'BALTIMORE', diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 878879d46c9..feb9d2188fe 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -520,6 +520,72 @@ def expect_email_body_to_have_help_and_contact_links end end + describe '#in_person_ready_to_verify' do + let(:first_name) { 'Michael' } + let!(:enrollment) { + create( + :in_person_enrollment, + :pending, + selected_location_details: { name: 'FRIENDSHIP' }, + status_updated_at: Time.zone.now - 2.hours, + ) + } + + let(:mail) do + UserMailer.in_person_ready_to_verify( + user, + user.email_addresses.first, + first_name: first_name, + enrollment: enrollment, + ) + end + + it_behaves_like 'a system email' + it_behaves_like 'an email that respects user email locale preference' + end + + describe '#in_person_verified' do + let(:enrollment) { + create( + :in_person_enrollment, + selected_location_details: { name: 'FRIENDSHIP' }, + status_updated_at: Time.zone.now - 2.hours, + ) + } + + let(:mail) do + UserMailer.in_person_verified( + user, + user.email_addresses.first, + enrollment: enrollment, + ) + end + + it_behaves_like 'a system email' + it_behaves_like 'an email that respects user email locale preference' + end + + describe '#in_person_failed' do + let(:enrollment) { + create( + :in_person_enrollment, + selected_location_details: { name: 'FRIENDSHIP' }, + status_updated_at: Time.zone.now - 2.hours, + ) + } + + let(:mail) do + UserMailer.in_person_failed( + user, + user.email_addresses.first, + enrollment: enrollment, + ) + end + + it_behaves_like 'a system email' + it_behaves_like 'an email that respects user email locale preference' + end + def strip_tags(str) ActionController::Base.helpers.strip_tags(str) end diff --git a/spec/presenters/idv/in_person/verification_results_email_presenter_spec.rb b/spec/presenters/idv/in_person/verification_results_email_presenter_spec.rb new file mode 100644 index 00000000000..8d05000ea47 --- /dev/null +++ b/spec/presenters/idv/in_person/verification_results_email_presenter_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +RSpec.describe Idv::InPerson::VerificationResultsEmailPresenter do + let(:location_name) { 'FRIENDSHIP' } + let(:status_updated_at) { described_class::USPS_SERVER_TIMEZONE.parse('2022-07-14T00:00:00Z') } + let!(:enrollment) do + create( + :in_person_enrollment, + :pending, + selected_location_details: { name: location_name }, + ) + end + + subject(:presenter) { described_class.new(enrollment: enrollment) } + + describe '#location_name' do + it 'returns the enrollment location name' do + expect(presenter.location_name).to eq(location_name) + end + end + + describe '#formatted_verified_date' do + around do |example| + Time.use_zone('UTC') { example.run } + end + + it 'returns a formatted verified date' do + enrollment.update(status_updated_at: status_updated_at) + expect(presenter.formatted_verified_date).to eq 'July 13, 2022' + end + end +end