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
45 changes: 45 additions & 0 deletions app/forms/idv/document_capture_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module Idv
class DocumentCaptureForm
include ActiveModel::Model

ATTRIBUTES = %i[front_image front_image_data_url back_image back_image_data_url].freeze

attr_accessor :front_image, :front_image_data_url, :back_image, :back_image_data_url

validate :front_image_or_image_data_url_presence
validate :back_image_or_image_data_url_presence

def self.model_name
ActiveModel::Name.new(self, nil, 'Image')
end

def submit(params)
consume_params(params)

FormResponse.new(success: valid?, errors: errors.messages)
end

private

def front_image_or_image_data_url_presence
return if front_image.present? || front_image_data_url.present?
errors.add(:front_image, :blank)
end

def back_image_or_image_data_url_presence
return if back_image.present? || back_image_data_url.present?
errors.add(:back_image, :blank)
end

def consume_params(params)
params.each do |key, value|
raise_invalid_image_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym)
send("#{key}=", value)
end
end

def raise_invalid_image_parameter_error(key)
raise ArgumentError, "#{key} is an invalid image attribute"
end
end
end
48 changes: 48 additions & 0 deletions app/javascript/packs/image-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,52 @@ function imagePreview() {
});
}

function frontImagePreview() {
$('#doc_auth_front_image').on('change', function(event) {
$('.simple_form .alert-error').hide();
$('.simple_form .alert-notice').hide();
const files = event.target.files;
const image = files[0];
const reader = new FileReader();
reader.onload = function(file) {
const img = new Image();
img.onload = function () {
const displayWidth = '460';
const ratio = (this.height / this.width);
img.width = displayWidth;
img.height = (displayWidth * ratio);
$('#front_target').html(img);
};
img.src = file.target.result;
$('#front_target').html(img);
};
reader.readAsDataURL(image);
});
}

function backImagePreview() {
$('#doc_auth_back_image').on('change', function(event) {
$('.simple_form .alert-error').hide();
$('.simple_form .alert-notice').hide();
const files = event.target.files;
const image = files[0];
const reader = new FileReader();
reader.onload = function(file) {
const img = new Image();
img.onload = function () {
const displayWidth = '460';
const ratio = (this.height / this.width);
img.width = displayWidth;
img.height = (displayWidth * ratio);
$('#back_target').html(img);
};
img.src = file.target.result;
$('#back_target').html(img);
};
reader.readAsDataURL(image);
});
}

document.addEventListener('DOMContentLoaded', imagePreview);
document.addEventListener('DOMContentLoaded', frontImagePreview);
document.addEventListener('DOMContentLoaded', backImagePreview);
49 changes: 49 additions & 0 deletions app/services/acuant/acuant_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ def post_back_image(image:, instance_id:)
).fetch
end

def post_images(front_image:, back_image:, instance_id: nil)
document = create_document
return failure(document.errors.first, document.to_h) unless document.success?

instance_id ||= document.instance_id
front_response = post_front_image(image: front_image, instance_id: instance_id)
back_response = post_back_image(image: back_image, instance_id: instance_id)
response = merge_post_responses(front_response, back_response)

check_results(response, instance_id)
end

def get_results(instance_id:)
Requests::GetResultsRequest.new(instance_id: instance_id).fetch
end
Expand All @@ -39,6 +51,15 @@ def post_selfie(instance_id:, image:)

private

def merge_post_responses(front_response, back_response)
Acuant::Response.new(
success: front_response.success? && back_response.success?,
errors: (front_response.errors || []) + (back_response.errors || []),
exception: front_response.exception || back_response.exception,
extra: { front_response: front_response, back_response: back_response },
)
end

def merge_facial_match_and_liveness_response(facial_match_response, liveness_response)
Acuant::Response.new(
success: facial_match_response.success? && liveness_response.success?,
Expand All @@ -50,5 +71,33 @@ def merge_facial_match_and_liveness_response(facial_match_response, liveness_res
},
)
end

def check_results(post_response, instance_id)
return post_response unless post_response.success?

fetch_doc_auth_results(instance_id)
end

def fetch_doc_auth_results(instance_id)
results_response = get_results(instance_id: instance_id)
handle_document_verification_failure(results_response) unless results_response.success?

results_response
end

def handle_document_verification_failure(get_results_response)
extra = get_results_response.to_h.merge(
notice: I18n.t('errors.doc_auth.general_info'),
)
failure(get_results_response.errors.first, extra)
end

def failure(message, extra = nil)
form_response_params = { success: false, errors: { message: message } }
if extra.present?
form_response_params[:extra] = extra unless extra.nil?
end
FormResponse.new(form_response_params)
end
end
end
53 changes: 53 additions & 0 deletions app/services/doc_auth_mock/doc_auth_mock_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ def post_back_image(image:, instance_id:)
Acuant::Response.new(success: true)
end

def post_images(front_image:, back_image:)
return mocked_response_for_method(__method__) if method_mocked?(__method__)

document = create_document
return document unless document.success?

instance_id = create_document.instance_id
front_response = post_front_image(image: front_image, instance_id: instance_id)
back_response = post_back_image(image: back_image, instance_id: instance_id)
response = merge_post_responses(front_response, back_response)
check_results(response, instance_id)
end

def get_results(instance_id:)
return mocked_response_for_method(__method__) if method_mocked?(__method__)

Expand All @@ -62,6 +75,46 @@ def mocked_response_for_method(method_name)

self.class.response_mocks[method_name.to_sym]
end

def check_results(post_response, instance_id)
if post_response.success?
fetch_doc_auth_results(instance_id)
else
failure(post_response.errors.first, post_response.to_h)
end
end

def fetch_doc_auth_results(instance_id)
results_response = get_results(instance_id: instance_id)
handle_document_verification_failure(results_response) unless results_response.success?

results_response
end

def handle_document_verification_failure(get_results_response)
mark_step_incomplete(:front_image)
extra = get_results_response.to_h.merge(
notice: I18n.t('errors.doc_auth.general_info'),
)
failure(get_results_response.errors.first, extra)
end

def merge_post_responses(front_response, back_response)
Acuant::Response.new(
success: front_response.success? && back_response.success?,
errors: (front_response.errors || []) + (back_response.errors || []),
exception: front_response.exception || back_response.exception,
extra: { front_response: front_response, back_response: back_response },
)
end

def failure(message, extra = nil)
form_response_params = { success: false, errors: { message: message } }
if extra.present?
form_response_params[:extra] = extra unless extra.nil?
end
FormResponse.new(form_response_params)
end
end
end
# rubocop:enable Lint/UnusedMethodArgument
28 changes: 26 additions & 2 deletions app/services/idv/steps/doc_auth_base_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ def image
DataUrlImage.new(flow_params[:image_data_url])
end

def front_image
uploaded_image = flow_params[:front_image]
return uploaded_image if uploaded_image.present?
DataUrlImage.new(flow_params[:front_image_data_url])
end

def back_image
uploaded_image = flow_params[:back_image]
return uploaded_image if uploaded_image.present?
DataUrlImage.new(flow_params[:back_image_data_url])
end

def doc_auth_client
@doc_auth_client ||= begin
case doc_auth_vendor
Expand Down Expand Up @@ -67,10 +79,10 @@ def idv_failure(result)
end

def save_proofing_components
Db::ProofingComponent::Add.call(user_id, :document_check, 'acuant')
Db::ProofingComponent::Add.call(user_id, :document_check, doc_auth_vendor)
Db::ProofingComponent::Add.call(user_id, :document_type, 'state_id')
return unless liveness_checking_enabled?
Db::ProofingComponent::Add.call(user_id, :liveness_check, 'acuant')
Db::ProofingComponent::Add.call(user_id, :liveness_check, doc_auth_vendor)
end

def extract_pii_from_doc(response)
Expand Down Expand Up @@ -106,6 +118,18 @@ def post_back_image
result
end

def post_images
return throttled_response if throttled_else_increment

result = doc_auth_client.post_images(
front_image: front_image.read,
back_image: back_image.read,
)
add_cost(:acuant_front_image)
add_cost(:acuant_back_image)
result
end

def throttled
redirect_to throttled_url
[false, I18n.t('errors.doc_auth.acuant_throttle')]
Expand Down
25 changes: 23 additions & 2 deletions app/services/idv/steps/document_capture_step.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
module Idv
module Steps
class DocumentCaptureStep < DocAuthBaseStep
def call; end
def call
response = post_images
if response.success?
save_proofing_components
extract_pii_from_doc(response)
else
handle_document_verification_failure(response)
end
end

def form_submit; end
private

def handle_document_verification_failure(response)
mark_step_incomplete(:document_capture)
extra = response.to_h.merge(
notice: I18n.t('errors.doc_auth.general_info'),
)
failure(response.errors.first, extra)
end

def form_submit
Idv::DocumentCaptureForm.new.submit(permit(:front_image, :front_image_data_url,
:back_image, :back_image_data_url))
end
end
end
end
69 changes: 68 additions & 1 deletion app/views/idv/doc_auth/document_capture.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,71 @@
<div id='document-capture-form'>
Form goes here
</div>

<div>
<% title t('doc_auth.titles.doc_auth') %>

<%= render 'idv/doc_auth/error_messages', flow_session: flow_session %>

<%= simple_form_for(
:doc_auth,
url: url_for,
method: 'PUT',
html: { autocomplete: 'off', role: 'form', class: 'mt2' }
) do |f| %>
<%# ---- Front Image ----- %>
<%= render 'idv/doc_auth/front_of_state_id_image' %>

<h1 class='h3 my0'>
<%= t('doc_auth.headings.upload_front') %>
</h1>

<%= accordion('totp-info', t('doc_auth.tips.title_html'),
wrapper_css: 'my2 col-12 fs-16p') do %>
<%= render 'idv/doc_auth/tips_and_sample' %>
<div class='center'>
<%= image_tag(asset_url('state-id-sample-front.jpg'), height: 338, width: 450) %>
</div>
<% end %>

<%= f.input :front_image_data_url, as: :hidden %>
<%= render 'idv/doc_auth/notices', flow_session: flow_session %>
<%= f.input :front_image, label: false, as: :file, required: true, wrapper_class: 'mt3 sm-col-8' %>
<div class='my2' id='front_target'></div>


<%# ---- Back Image ----- %>
<%= render 'idv/doc_auth/back_of_state_id_image' %>

<h1 class='h3 my0'>
<%= t('doc_auth.headings.upload_back') %>
</h1>

<%= accordion('totp-info', t('doc_auth.tips.title_html'),
wrapper_css: 'my2 col-12 fs-16p') do %>
<%= render 'idv/doc_auth/tips_and_sample' %>
<div class='center'>
<%= image_tag(asset_url('state-id-sample-back.jpg'), height: 338, width: 450) %>
</div>
<% end %>

<%= f.input :back_image_data_url, as: :hidden %>
<%= render 'idv/doc_auth/notices', flow_session: flow_session %>
<%= f.input :back_image, label: false, as: :file, required: true, wrapper_class: 'mt3 sm-col-8' %>
<div class='my2' id='back_target'></div>

<%# ---- Selfie ----- %>


<%# ---- Selfie ----- %>

<div class='mt3'>
<%= render 'idv/doc_auth/submit_with_spinner' %>
</div>
<% end %>

<p class='mt3 mb0'><%= t('doc_auth.info.upload_image') %></p>

<%= render 'idv/doc_auth/start_over_or_cancel' %>
<%= javascript_pack_tag 'image-preview' %>
</div>
<%= javascript_pack_tag 'document-capture' %>
Loading