diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 5977ba5aa0d..6c0b35d084f 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -283,6 +283,30 @@ def in_person_ready_to_verify(enrollment:) end end + def in_person_ready_to_verify_reminder(enrollment:) + attachments.inline['barcode.png'] = BarcodeOutputter.new( + code: enrollment.enrollment_code, + ).image_data + + with_user_locale(user) do + @presenter = Idv::InPerson::ReadyToVerifyPresenter.new( + enrollment: enrollment, + barcode_image_url: attachments['barcode.png'].url, + ) + @header = t( + 'user_mailer.in_person_ready_to_verify_reminder.heading', + days_remaining: @presenter.days_remaining, + ) + mail( + to: email_address.email, + subject: t( + 'user_mailer.in_person_ready_to_verify_reminder.subject', + days_remaining: @presenter.days_remaining, + ), + ) + end + end + def in_person_verified(enrollment:) with_user_locale(user) do @hide_title = true diff --git a/app/models/in_person_enrollment.rb b/app/models/in_person_enrollment.rb index f897f6be30a..647a72c8c96 100644 --- a/app/models/in_person_enrollment.rb +++ b/app/models/in_person_enrollment.rb @@ -65,6 +65,16 @@ def self.generate_unique_id SecureRandom.hex(9) end + def due_date + start_date = enrollment_established_at.presence || created_at + start_date + IdentityConfig.store.in_person_enrollment_validity_in_days.days + end + + def days_to_due_date + today = DateTime.now + (today...due_date).count + end + private def on_status_updated diff --git a/app/presenters/idv/in_person/ready_to_verify_presenter.rb b/app/presenters/idv/in_person/ready_to_verify_presenter.rb index c9f2dd9c911..630b597f889 100644 --- a/app/presenters/idv/in_person/ready_to_verify_presenter.rb +++ b/app/presenters/idv/in_person/ready_to_verify_presenter.rb @@ -8,13 +8,20 @@ class ReadyToVerifyPresenter delegate :selected_location_details, :enrollment_code, to: :enrollment - def initialize(enrollment:, barcode_image_url: nil) + def initialize(enrollment:, barcode_image_url: nil, sp_name: nil) @enrollment = enrollment @barcode_image_url = barcode_image_url + @sp_name = sp_name + end + + # Reminder is exclusive of the day the email is sent (1 less than days_to_due_date) + def days_remaining + enrollment.days_to_due_date - 1 end def formatted_due_date - due_date.in_time_zone(USPS_SERVER_TIMEZONE).strftime(I18n.t('time.formats.event_date')) + enrollment.due_date.in_time_zone(USPS_SERVER_TIMEZONE). + strftime(I18n.t('time.formats.event_date')) end def selected_location_hours(prefix) @@ -27,15 +34,18 @@ def needs_proof_of_address? !enrollment.current_address_matches_id end + def service_provider + enrollment.service_provider + end + + def sp_name + service_provider ? service_provider.friendly_name : '' + end + private attr_reader :enrollment - def due_date - start_date = enrollment.enrollment_established_at.presence || enrollment.created_at - start_date + IdentityConfig.store.in_person_enrollment_validity_in_days.days - end - def localized_hours(hours) case hours when 'Closed' diff --git a/app/views/user_mailer/in_person_ready_to_verify.html.erb b/app/views/user_mailer/in_person_ready_to_verify.html.erb index e3fb2920efe..915d0cc3343 100644 --- a/app/views/user_mailer/in_person_ready_to_verify.html.erb +++ b/app/views/user_mailer/in_person_ready_to_verify.html.erb @@ -3,99 +3,4 @@ <%= t('user_mailer.in_person_ready_to_verify.intro') %>

- - - - - -
- <%= image_tag('email/info.png', width: 16, height: 16, alt: '') %> - -

<%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %>

-

<%= t('in_person_proofing.body.barcode.deadline_restart') %>

-
- -
-

- <%= t('in_person_proofing.body.barcode.items_to_bring') %> -

- - - - - - - - - - <% if @presenter.needs_proof_of_address? %> - - - - - <% end %> -
1
-

<%= t('in_person_proofing.process.barcode.heading') %>

-

<%= t('in_person_proofing.process.barcode.info') %>

- <%= render BarcodeComponent.new( - barcode_data: @presenter.enrollment_code, - barcode_image_url: @presenter.barcode_image_url, - label: nil, - label_formatter: Idv::InPerson::EnrollmentCodeFormatter.method(:format), - ) %> -
2
-

<%= t('in_person_proofing.process.state_id.heading') %>

-

<%= t('in_person_proofing.process.state_id.info') %>

-
    - <% t('in_person_proofing.process.state_id.acceptable_documents').each do |document| %> -
  • <%= document %>
  • - <% end %> -
-

<%= t('in_person_proofing.process.state_id.no_other_documents') %>

-
3
-

<%= t('in_person_proofing.process.proof_of_address.heading') %>

-

<%= t('in_person_proofing.process.proof_of_address.info') %>

-
    - <% t('in_person_proofing.process.proof_of_address.acceptable_proof').each do |proof| %> -
  • <%= proof %>
  • - <% end %> -
-

<%= t('in_person_proofing.process.proof_of_address.physical_or_digital_copy') %>

-
-

- <%= t('in_person_proofing.body.barcode.items_to_bring_questions') %> - <%= link_to( - t('in_person_proofing.body.barcode.learn_more'), - MarketingSite.help_center_article_url( - category: 'verify-your-identity', - article: 'verify-your-identity-in-person', - ), - ) %> -

-
- -<% if @presenter.selected_location_details.present? %> -
-

<%= @presenter.selected_location_details['name'] %>

-
- <%= @presenter.selected_location_details['street_address'] %>
- <%= @presenter.selected_location_details['formatted_city_state_zip'] %> -
-
<%= t('in_person_proofing.body.barcode.retail_hours') %>
-
- <%= t('date.range', from: t('date.day_names')[0], to: t('date.day_names')[4]) %>: <%= @presenter.selected_location_hours(:weekday) %>
- <%= t('date.day_names')[5] %>: <%= @presenter.selected_location_hours(:saturday) %>
- <%= t('date.day_names')[6] %>: <%= @presenter.selected_location_hours(:sunday) %> -
-
- <%= @presenter.selected_location_details[:phone] %> -
-
-<% end %> - -

<%= t('in_person_proofing.body.barcode.speak_to_associate') %>

- -

- <%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %> - <%= t('in_person_proofing.body.barcode.no_appointment_required') %> -

+<%= render 'user_mailer/shared/in_person_ready_to_verify' %> diff --git a/app/views/user_mailer/in_person_ready_to_verify_reminder.html.erb b/app/views/user_mailer/in_person_ready_to_verify_reminder.html.erb new file mode 100644 index 00000000000..7b2e6060999 --- /dev/null +++ b/app/views/user_mailer/in_person_ready_to_verify_reminder.html.erb @@ -0,0 +1,6 @@ +

+ <%= t('user_mailer.in_person_ready_to_verify_reminder.greeting') %>
+ <%= t('user_mailer.in_person_ready_to_verify_reminder.intro', sp_name: @presenter.sp_name) %> +

+ +<%= render 'user_mailer/shared/in_person_ready_to_verify' %> diff --git a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb new file mode 100644 index 00000000000..34aacaa53d9 --- /dev/null +++ b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb @@ -0,0 +1,96 @@ + + + + + +
+ <%= image_tag('email/info.png', width: 16, height: 16, alt: '') %> + +

<%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %>

+

<%= t('in_person_proofing.body.barcode.deadline_restart') %>

+
+ +
+

+ <%= t('in_person_proofing.body.barcode.items_to_bring') %> +

+ + + + + + + + + + <% if @presenter.needs_proof_of_address? %> + + + + + <% end %> +
1
+

<%= t('in_person_proofing.process.barcode.heading') %>

+

<%= t('in_person_proofing.process.barcode.info') %>

+ <%= render BarcodeComponent.new( + barcode_data: @presenter.enrollment_code, + barcode_image_url: @presenter.barcode_image_url, + label: nil, + label_formatter: Idv::InPerson::EnrollmentCodeFormatter.method(:format), + ) %> +
2
+

<%= t('in_person_proofing.process.state_id.heading') %>

+

<%= t('in_person_proofing.process.state_id.info') %>

+
    + <% t('in_person_proofing.process.state_id.acceptable_documents').each do |document| %> +
  • <%= document %>
  • + <% end %> +
+

<%= t('in_person_proofing.process.state_id.no_other_documents') %>

+
3
+

<%= t('in_person_proofing.process.proof_of_address.heading') %>

+

<%= t('in_person_proofing.process.proof_of_address.info') %>

+
    + <% t('in_person_proofing.process.proof_of_address.acceptable_proof').each do |proof| %> +
  • <%= proof %>
  • + <% end %> +
+

<%= t('in_person_proofing.process.proof_of_address.physical_or_digital_copy') %>

+
+

+ <%= t('in_person_proofing.body.barcode.items_to_bring_questions') %> + <%= link_to( + t('in_person_proofing.body.barcode.learn_more'), + MarketingSite.help_center_article_url( + category: 'verify-your-identity', + article: 'verify-your-identity-in-person', + ), + ) %> +

+
+ +<% if @presenter.selected_location_details.present? %> +
+

<%= @presenter.selected_location_details['name'] %>

+
+ <%= @presenter.selected_location_details['street_address'] %>
+ <%= @presenter.selected_location_details['formatted_city_state_zip'] %> +
+
<%= t('in_person_proofing.body.barcode.retail_hours') %>
+
+ <%= t('date.range', from: t('date.day_names')[0], to: t('date.day_names')[4]) %>: <%= @presenter.selected_location_hours(:weekday) %>
+ <%= t('date.day_names')[5] %>: <%= @presenter.selected_location_hours(:saturday) %>
+ <%= t('date.day_names')[6] %>: <%= @presenter.selected_location_hours(:sunday) %> +
+
+ <%= @presenter.selected_location_details[:phone] %> +
+
+<% end %> + +

<%= t('in_person_proofing.body.barcode.speak_to_associate') %>

+ +

+ <%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %> + <%= t('in_person_proofing.body.barcode.no_appointment_required') %> +

diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 270586b2ef7..aeab8e7c233 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -136,6 +136,12 @@ en: 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_ready_to_verify_reminder: + greeting: Hello, + heading: You have %{days_remaining} days left to verify your identity in person + intro: Don’t miss the chance to verify your identity at your local Post Office. + Complete this step to access %{sp_name}. + subject: Verify your identity at a Post Office in the next %{days_remaining} days in_person_verified: greeting: Hello, intro: You successfully verified your identity at the %{location} Post Office on diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index 76a2fc813a0..195afc26db9 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -144,6 +144,13 @@ es: 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_ready_to_verify_reminder: + greeting: Hola, + heading: Tiene %{days_remaining} días para verificar su identidad en persona + intro: No pierda la oportunidad de verificar su identidad en su oficina de + correos local. Complete este paso para acceder %{sp_name}. + subject: Verifique su identidad en una oficina de correos en los próximos + %{days_remaining} días in_person_verified: greeting: Hola, intro: El %{date}, verificó correctamente su identidad en la oficina de correos diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index 332c52f5c9f..21266a3c8b3 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -147,6 +147,14 @@ fr: 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_ready_to_verify_reminder: + greeting: Bonjour, + heading: Il vous reste %{days_remaining} jours pour vérifier votre identité en + personne + intro: Ne manquez pas l’occasion de vérifier votre identité dans votre bureau de + poste de proximité. Complétez cette étape pour accéder à %{sp_name}. + subject: Vérifiez votre identité auprès d’un bureau de poste dans les + %{days_remaining} prochains jours. in_person_verified: greeting: Bonjour, intro: Vous avez vérifié avec succès votre identité au bureau de poste de diff --git a/spec/factories/in_person_enrollments.rb b/spec/factories/in_person_enrollments.rb index 36e5da3ee1d..51449e3296f 100644 --- a/spec/factories/in_person_enrollments.rb +++ b/spec/factories/in_person_enrollments.rb @@ -31,5 +31,9 @@ status_check_attempted_at { Time.zone.now } status_updated_at { Time.zone.now } end + + trait :with_service_provider do + service_provider { association :service_provider } + end end end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 3fe82dbb0f6..17228f898c1 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -127,6 +127,15 @@ def in_person_ready_to_verify ) end + def in_person_ready_to_verify_reminder + UserMailer.with( + user: user, + email_address: email_address_record, + ).in_person_ready_to_verify_reminder( + enrollment: in_person_enrollment, + ) + end + def in_person_verified UserMailer.with(user: user, email_address: email_address_record).in_person_verified( enrollment: in_person_enrollment, @@ -166,6 +175,10 @@ def in_person_enrollment profile: unsaveable(Profile.new(user: user)), enrollment_code: '2048702198804358', created_at: Time.zone.now - 2.hours, + service_provider: ServiceProvider.new( + friendly_name: 'Test Service Provider', + issuer: SecureRandom.uuid, + ), status_updated_at: Time.zone.now - 1.hour, current_address_matches_id: params['current_address_matches_id'] == 'true', selected_location_details: { @@ -177,7 +190,6 @@ def in_person_enrollment 'saturday_hours' => '9:00 AM - 12:00 PM', 'sunday_hours' => 'Closed', }, - service_provider: params[:issuer] ? ServiceProvider.find_by(issuer: params[:issuer]) : nil, ), ) end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 0879123fa0c..c31c0471968 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -576,6 +576,26 @@ def expect_email_body_to_have_help_and_contact_links it_behaves_like 'an email that respects user email locale preference' end + describe '#in_person_ready_to_verify_reminder' do + let!(:enrollment) do + create( + :in_person_enrollment, + :pending, + selected_location_details: { name: 'FRIENDSHIP' }, + status_updated_at: Time.zone.now - 2.hours, + ) + end + + let(:mail) do + UserMailer.with(user: user, email_address: email_address).in_person_ready_to_verify_reminder( + 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) do create( diff --git a/spec/presenters/idv/in_person/ready_to_verify_presenter_spec.rb b/spec/presenters/idv/in_person/ready_to_verify_presenter_spec.rb index 4f19ff2ac6f..81674542e20 100644 --- a/spec/presenters/idv/in_person/ready_to_verify_presenter_spec.rb +++ b/spec/presenters/idv/in_person/ready_to_verify_presenter_spec.rb @@ -3,7 +3,6 @@ RSpec.describe Idv::InPerson::ReadyToVerifyPresenter do let(:user) { build(:user) } let(:profile) { build(:profile, user: user) } - let(:enrollment_code) { '2048702198804358' } let(:current_address_matches_id) { true } let(:created_at) { described_class::USPS_SERVER_TIMEZONE.parse('2022-07-14T00:00:00Z') } let(:enrollment_established_at) do @@ -13,15 +12,14 @@ JSON.parse(UspsInPersonProofing::Mock::Fixtures.enrollment_selected_location_details) end let(:enrollment) do - InPersonEnrollment.new( + create( + :in_person_enrollment, :with_service_provider, :pending, user: user, profile: profile, - enrollment_code: enrollment_code, - unique_id: InPersonEnrollment.generate_unique_id, created_at: created_at, enrollment_established_at: enrollment_established_at, current_address_matches_id: current_address_matches_id, - selected_location_details: enrollment_selected_location_details, + selected_location_details: enrollment_selected_location_details ) end subject(:presenter) { described_class.new(enrollment: enrollment) } @@ -120,6 +118,27 @@ end end + describe '#sp_name' do + subject(:sp_name) { presenter.sp_name } + + it 'returns friendly service provider name' do + expect(sp_name).to eq('Test Service Provider') + end + end + + describe '#days_remaining' do + subject(:days_remaining) { presenter.days_remaining } + let(:config) { IdentityConfig.store.in_person_enrollment_validity_in_days } + + context '4 days until due date' do + it 'returns 3 days' do + travel_to(enrollment_established_at + (config - 4).days) do + expect(days_remaining).to eq(3) + end + end + end + end + describe '#barcode_image_url' do subject(:barcode_image_url) { presenter.barcode_image_url }