Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ gem 'aws-sdk-pinpoint'
gem 'aws-sdk-pinpointsmsvoice'
gem 'aws-sdk-ses', '~> 1.6'
gem 'aws-sdk-sns'
gem 'barby', '~> 0.6.8'
gem 'base32-crockford'
gem 'blueprinter', '~> 0.25.3'
gem 'bootsnap', '~> 1.9.0', require: false
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
barby (0.6.8)
base32-crockford (0.1.0)
bcrypt (3.1.16)
benchmark-ips (2.9.2)
Expand Down Expand Up @@ -706,6 +707,7 @@ DEPENDENCIES
aws-sdk-ses (~> 1.6)
aws-sdk-sns
axe-core-rspec (~> 4.2)
barby (~> 0.6.8)
base32-crockford
better_errors (>= 2.5.1)
binding_of_caller
Expand Down
1 change: 1 addition & 0 deletions app/assets/images/idv/user-in-person.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/controllers/api/verify/password_confirm_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def completion_url(result, user)

def in_person_enrollment?(user)
return false unless IdentityConfig.store.in_person_proofing_enabled
# WILLFIX: After LG-6708 and we have enrollment saved, reference enrollment instead.
# WILLFIX: After LG-6872 and we have enrollment saved, reference enrollment instead.
ProofingComponent.find_by(user: user)&.document_check == Idp::Constants::Vendors::USPS
end
end
Expand Down
40 changes: 33 additions & 7 deletions app/controllers/idv/in_person/ready_to_verify_controller.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
module Idv
module InPerson
class ReadyToVerifyController < ApplicationController
include RenderConditionConcern

check_or_render_not_found -> { IdentityConfig.store.in_person_proofing_enabled }

before_action :confirm_two_factor_authenticated
before_action :confirm_in_person_session

def show
analytics.idv_in_person_ready_to_verify_visit
@presenter = ReadyToVerifyPresenter.new(enrollment: enrollment)
end

private

def confirm_in_person_session
redirect_to account_url unless in_person_proofing_component?
end

def in_person_proofing_component?
proofing_component&.document_check == Idp::Constants::Vendors::USPS
redirect_to account_url unless enrollment.present?
end

def proofing_component
ProofingComponent.find_by(user: current_user)
def enrollment
InPersonEnrollment.new(
user: current_user,
enrollment_code: '2048702198804358',
created_at: Time.zone.now,
current_address_matches_id: true,
selected_location_details: {
'name' => 'BALTIMORE — Post Office™',
'streetAddress' => '900 E FAYETTE ST RM 118',
'city' => 'BALTIMORE',
'state' => 'MD',
'zip5' => '21233',
'zip4' => '9715',
'phone' => '555-123-6409',
'hours' => [
{
'weekdayHours' => '8:30 AM - 4:30 PM',
},
{
'saturdayHours' => '9:00 AM - 12:00 PM',
},
{
'sundayHours' => 'Closed',
},
],
},
)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/idv/personal_key_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def generate_personal_key

def in_person_enrollment?
return false unless IdentityConfig.store.in_person_proofing_enabled
# WILLFIX: After LG-6708 and we have enrollment saved, reference enrollment instead.
# WILLFIX: After LG-6872 and we have enrollment saved, reference enrollment instead.
ProofingComponent.find_by(user: current_user)&.document_check == Idp::Constants::Vendors::USPS
end

Expand Down
66 changes: 66 additions & 0 deletions app/presenters/idv/in_person/ready_to_verify_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'barby'
require 'barby/barcode/code_128'
require 'barby/outputter/png_outputter'

module Idv
module InPerson
class ReadyToVerifyPresenter
# WILLFIX: With LG-6881, confirm timezone or use deadline from enrollment response.
USPS_SERVER_TIMEZONE = ActiveSupport::TimeZone['America/New_York']

delegate :selected_location_details, to: :enrollment

def initialize(enrollment:)
@enrollment = enrollment
end

def barcode_data_url
"data:image/png;base64,#{Base64.strict_encode64(barcode_image_data)}"
end
Comment on lines +17 to +19
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I'm beginning to work on LG-6343 (email notification) which is also meant to include an image of the barcode, I may plan to revise the output, since data URLs are not well-supported in email clients ([1][2][3]). The Barby gem supports a number of "Outputters", and the HTML outputter seems to be the most promising for email compatibility, and wouldn't be a big lift to swap in to use in the web layout for consistency.

I probably won't change it here now, but will revisit with the pull request for LG-6343.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(A preview of this rework can be seen at #6585)


def formatted_due_date
due_date.in_time_zone(USPS_SERVER_TIMEZONE).strftime(I18n.t('time.formats.event_date'))
end

def formatted_enrollment_code
EnrollmentCodeFormatter.format(enrollment_code)
end

def selected_location_hours(prefix)
selected_location_details['hours'].each do |hours_candidate|
hours = hours_candidate["#{prefix}Hours"]
return localized_hours(hours) if hours
end
end

def needs_proof_of_address?
!enrollment.current_address_matches_id
end

private

attr_reader :enrollment
delegate :enrollment_code, to: :enrollment

def barcode_image_data
Barby::Code128C.new(enrollment_code).to_png(margin: 0, xdim: 2)
end

def due_date
enrollment.created_at + IdentityConfig.store.in_person_enrollment_validity_in_days.days
end

def localized_hours(hours)
case hours
when 'Closed'
I18n.t('in_person_proofing.body.barcode.retail_hours_closed')
else
hours.
split(' - '). # Hyphen
map { |time| Time.zone.parse(time).strftime(I18n.t('time.formats.event_time')) }.
join(' – ') # Endash
end
end
end
end
end
9 changes: 9 additions & 0 deletions app/services/idv/in_person/enrollment_code_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Idv
module InPerson
class EnrollmentCodeFormatter
def self.format(code)
code.gsub(/(\d{4})/, '\1-').chomp('-')
end
end
end
end
107 changes: 106 additions & 1 deletion app/views/idv/in_person/ready_to_verify/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,108 @@
<% title t('in_person_proofing.headings.barcode') %>

<%= render PageHeadingComponent.new.with_content(t('in_person_proofing.headings.barcode')) %>
<% content_for(:pre_flash_content) do %>
<%= render 'shared/step_indicator', {
steps: Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS,
current_step: :go_to_the_post_office,
locale_scope: 'idv',
class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4',
} %>
<% end %>

<%= image_tag(
asset_url('idv/user-in-person.svg'),
width: 136,
height: 136,
class: 'display-block margin-x-auto margin-bottom-4',
) %>

<%= render PageHeadingComponent.new(class: 'text-center') do %>
<%= t('in_person_proofing.headings.barcode') %>
<% end %>

<%= render AlertComponent.new(class: 'margin-y-4', text_tag: :div) do %>
<p class="font-sans-md margin-bottom-1"><strong><%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %></strong></p>
<p class="margin-bottom-0"><%= t('in_person_proofing.body.barcode.deadline_restart') %></p>
<% end %>

<section class="border-1px border-primary-light radius-lg padding-4">
<h2 class="margin-top-0 margin-bottom-2"><%= t('in_person_proofing.body.barcode.items_to_bring') %></h2>
<p><%= t('in_person_proofing.body.barcode.emailed_info') %></p>
<%= render ProcessListComponent.new(heading_level: :h3, class: 'margin-y-3') do |c| %>
<% c.item(heading: t('in_person_proofing.process.barcode.heading')) do %>
<p><%= t('in_person_proofing.process.barcode.info') %></p>
<figure class="display-inline-block margin-0">
<%= image_tag(
@presenter.barcode_data_url,
skip_pipeline: true,
alt: t('in_person_proofing.process.barcode.image_alt'),
class: 'display-block margin-bottom-1',
) %>
<figcaption class="text-center">
<span class="usa-sr-only"><%= t('in_person_proofing.process.barcode.caption_label') %>:</span>
<%= @presenter.formatted_enrollment_code %>
</figcaption>
</figure>
<% end %>
<% c.item(heading: t('in_person_proofing.process.state_id.heading')) do %>
<p class="margin-bottom-105"><%= t('in_person_proofing.process.state_id.info') %></p>
<ul class="usa-list margin-y-105">
<% t('in_person_proofing.process.state_id.acceptable_documents').each do |document| %>
<li><%= document %></li>
<% end %>
</ul>
<p><%= t('in_person_proofing.process.state_id.no_other_documents') %></p>
<% end %>
<% if @presenter.needs_proof_of_address? %>
<% c.item(heading: t('in_person_proofing.process.proof_of_address.heading')) do %>
<p class="margin-bottom-105"><%= t('in_person_proofing.process.proof_of_address.info') %></p>
<ul class="usa-list margin-y-105">
<% t('in_person_proofing.process.proof_of_address.acceptable_proof').each do |proof| %>
<li><%= proof %></li>
<% end %>
</ul>
<% end %>
<% end %>
<% end %>
<p class="margin-bottom-0">
<%= t('in_person_proofing.body.barcode.items_to_bring_questions') %>
<%= new_window_link_to(
t('in_person_proofing.body.barcode.learn_more'),
MarketingSite.help_center_article_url(
category: 'verify-your-identity',
article: 'how-to-verify-in-person',
),
) %>
</p>
</section>

<section aria-label="<%= t('in_person_proofing.body.barcode.location_details') %>" class="margin-y-4">
<address>
<h2 class="font-sans-md margin-bottom-1"><%= @presenter.selected_location_details['name'] %></h2>
<div class="margin-bottom-1">
<%= @presenter.selected_location_details['streetAddress'] %><br>
<%= @presenter.selected_location_details['city'] %>,
<%= @presenter.selected_location_details['state'] %>
<%= @presenter.selected_location_details['zip5'] %>-<%= @presenter.selected_location_details['zip4'] %>
</div>
<h3 class="font-sans-sm margin-y-0"><%= t('in_person_proofing.body.barcode.retail_hours') %></h3>
<div class="margin-bottom-2">
<%= t('date.range', from: t('date.day_names')[0], to: t('date.day_names')[4]) %>: <%= @presenter.selected_location_hours(:weekday) %><br>
<%= t('date.day_names')[5] %>: <%= @presenter.selected_location_hours(:saturday) %><br>
<%= t('date.day_names')[6] %>: <%= @presenter.selected_location_hours(:sunday) %>
</div>
<div>
<span class="usa-sr-only"><%= t('in_person_proofing.body.barcode.retail_phone_label') %>: </span>
<%= @presenter.selected_location_details['phone'] %>
</div>
</address>
</section>

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

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

<%= render 'idv/doc_auth/cancel', step: 'in_person_ready_to_verify' %>
1 change: 1 addition & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ idv_send_link_max_attempts: 5
idv_sp_required: false
in_person_proofing_enabled: false
in_person_proofing_enabled_issuers: '[]'
in_person_enrollment_validity_in_days: 30
include_slo_in_saml_metadata: false
irs_attempt_api_audience: 'https://irs.gov'
irs_attempt_api_auth_tokens: ''
Expand Down
38 changes: 38 additions & 0 deletions config/locales/in_person_proofing/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ en:
info: 'If your current address does not match the address on your ID, you will
need to bring proof of your current address to the Post Office.'
learn_more: Learn more
barcode:
deadline: Your deadline to verify your identity in person is %{deadline}.
deadline_restart: If you go after the deadline, your information will not be
saved and you will need to restart the process.
emailed_info: We have emailed this information to the email you used to log in.
items_to_bring: 'Bring these items with you to the Post Office:'
items_to_bring_questions: Questions about what to bring?
learn_more: Learn more
location_details: Location details
no_appointment_required: No appointment is required.
retail_hours: Retail hours
retail_hours_closed: Closed
retail_phone_label: Phone number
speak_to_associate: You can speak with any retail associate at this Post Office
to verify your identity.
prepare:
alert_selected_post_office: 'You’ve selected the %{name} Post Office.'
bring_barcode_header: A copy of your barcode
Expand Down Expand Up @@ -64,3 +79,26 @@ en:
state_id: Enter the information on your ID
update_address: Update your current address
update_state_id: Update the information on your ID
process:
barcode:
caption_label: Enrollment code
heading: A copy of your barcode
image_alt: Barcode
info: Print or scan from your mobile device.
proof_of_address:
acceptable_proof:
- Lease, Mortgage, or Deed of Trust
- Voter Registration
- Vehicle Registration Card
- Home or Vehicle Insurance Policy
heading: Proof of your current address
info: 'You need a proof of address if your current address is different than the
address on your ID. Acceptable forms of proof of address are:'
state_id:
acceptable_documents:
- State Driver’s License
- State Non-Driver’s Identification Card
heading: Your state-issued ID
info: 'Your ID must not be expired. Acceptable forms of ID are:'
no_other_documents: We do not currently accept any other forms of
identification, such as passports and military IDs.
41 changes: 41 additions & 0 deletions config/locales/in_person_proofing/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ es:
identidad, deberá llevar a la oficina de correos un comprobante de su
dirección actual.'
learn_more: Aprende más
barcode:
deadline: El plazo para verificar su identidad en persona es el %{deadline}.
deadline_restart: Si supera la fecha límite, su información no se guardará y
tendrá que reiniciar el proceso.
emailed_info: Hemos enviado esta información al correo electrónico que utilizó
para iniciar la sesión.
items_to_bring: 'Lleve estos artículos a la oficina de correos:'
items_to_bring_questions: ¿Preguntas sobre qué llevar?
learn_more: Más información
location_details: Detalles de la ubicación
no_appointment_required: No es necesario pedir cita.
retail_hours: Heures de vente au détail
retail_hours_closed: Cerrado
retail_phone_label: Número de teléfono
speak_to_associate: Puedes hablar con cualquier socio de ventas en esta oficina
de correos para verificar tu identidad.
prepare:
alert_selected_post_office: 'Ha seleccionado la oficina de correos %{name}.'
bring_barcode_header: Una copia de su código de barras
Expand Down Expand Up @@ -67,3 +83,28 @@ es:
state_id: Ingrese la información de su cédula
update_address: Actualizar su dirección actual
update_state_id: Actualizar la información de su cédula de identidad
process:
barcode:
caption_label: Código de registro
heading: Una copia de su código de barras
image_alt: Código de barras
info: Imprima o escanee desde su dispositivo móvil.
proof_of_address:
acceptable_proof:
- Arrendamiento, hipoteca o escritura de fideicomiso
- Registro de votantes
- Tarjeta de registro del vehículo
- Póliza de seguro del hogar o del vehículo
heading: Prueba de su dirección actual
info: 'Necesita un justificante de domicilio si su dirección actual es diferente
a la que figura en su cédula de identidad. Los comprobantes de
domicilio aceptables son:'
state_id:
acceptable_documents:
- Licencia de conducir estatal
- Tarjeta de identificación estatal para no conductores.
heading: Su cédula emitida por el estado
info: 'Su cédula de identidad no debe estar caducada. Las formas de
identificación aceptables son:'
no_other_documents: Actualmente no aceptamos otras formas de identificación,
como pasaportes y cartillas militares.
Loading