diff --git a/app/services/proofing/aamva/applicant.rb b/app/services/proofing/aamva/applicant.rb
index a567329ca1e..d704cfe8708 100644
--- a/app/services/proofing/aamva/applicant.rb
+++ b/app/services/proofing/aamva/applicant.rb
@@ -21,6 +21,8 @@ module Aamva
:state_id_number,
:state_id_jurisdiction,
:state_id_type,
+ :state_id_issued,
+ :state_id_expiration,
keyword_init: true,
).freeze
@@ -64,6 +66,8 @@ def self.from_proofer_applicant(applicant)
state_id_number: applicant.dig(:state_id_number)&.gsub(/[^\w\d]/, ''),
state_id_jurisdiction: applicant[:state_id_jurisdiction],
state_id_type: applicant[:state_id_type],
+ state_id_issued: applicant[:state_id_issued],
+ state_id_expiration: applicant[:state_id_expiration],
)
end
end.freeze
diff --git a/app/services/proofing/aamva/proofer.rb b/app/services/proofing/aamva/proofer.rb
index c73841282d8..48616af80d0 100644
--- a/app/services/proofing/aamva/proofer.rb
+++ b/app/services/proofing/aamva/proofer.rb
@@ -21,6 +21,18 @@ class Proofer
],
).freeze
+ ADDRESS_ATTRIBUTES = [
+ :address1,
+ :address2,
+ :city,
+ :state,
+ :zipcode,
+ ].to_set.freeze
+
+ OPTIONAL_ADDRESS_ATTRIBUTES = [:address2].freeze
+
+ REQUIRED_ADDRESS_ATTRIBUTES = (ADDRESS_ATTRIBUTES - OPTIONAL_ADDRESS_ATTRIBUTES).freeze
+
attr_reader :config
# Instance methods
@@ -31,6 +43,7 @@ def initialize(config)
def proof(applicant)
aamva_applicant =
Aamva::Applicant.from_proofer_applicant(OpenStruct.new(applicant))
+
response = Aamva::VerificationClient.new(
config,
).send_verification_request(
@@ -55,6 +68,7 @@ def build_result_from_response(verification_response)
exception: nil,
vendor_name: 'aamva:state_id',
transaction_id: verification_response.transaction_locator_id,
+ requested_attributes: requested_attributes(verification_response).index_with(1),
verified_attributes: verified_attributes(verification_response),
)
end
@@ -73,30 +87,30 @@ def parse_verification_errors(verification_response)
errors
end
- def verified_attributes(verification_response)
- attributes = Set.new
- results = verification_response.verification_results
-
- attributes.add :address if address_verified?(results)
+ def requested_attributes(verification_response)
+ attributes = verification_response.
+ verification_results.filter { |_, verified| !verified.nil? }.
+ keys.
+ to_set
- results.delete :address1
- results.delete :address2
- results.delete :city
- results.delete :state
- results.delete :zipcode
+ normalize_address_attributes(attributes)
+ end
- results.each do |attribute, verified|
- attributes.add attribute if verified
- end
+ def verified_attributes(verification_response)
+ attributes = verification_response.
+ verification_results.filter { |_, verified| verified }.
+ keys.
+ to_set
- attributes
+ normalize_address_attributes(attributes)
end
- def address_verified?(results)
- results[:address1] &&
- results[:city] &&
- results[:state] &&
- results[:zipcode]
+ def normalize_address_attributes(attribute_set)
+ all_present = REQUIRED_ADDRESS_ATTRIBUTES & attribute_set == REQUIRED_ADDRESS_ATTRIBUTES
+
+ (attribute_set - ADDRESS_ATTRIBUTES).tap do |result|
+ result.add(:address) if all_present
+ end
end
def send_to_new_relic(result)
diff --git a/app/services/proofing/aamva/request/verification_request.rb b/app/services/proofing/aamva/request/verification_request.rb
index df38c8b2d3f..69e45d65dd8 100644
--- a/app/services/proofing/aamva/request/verification_request.rb
+++ b/app/services/proofing/aamva/request/verification_request.rb
@@ -62,34 +62,43 @@ def add_user_provided_data_to_body
user_provided_data_map.each do |xpath, data|
REXML::XPath.first(document, xpath).add_text(data)
end
- add_street_address_line_2_to_rexml_document(document) if applicant.address2.present?
+
+ add_optional_element(
+ 'ns2:AddressDeliveryPointText',
+ value: applicant.address2,
+ document:,
+ after: '//ns1:Address/ns2:AddressDeliveryPointText',
+ )
+
+ add_optional_element(
+ 'ns2:DriverLicenseIssueDate',
+ value: applicant.state_id_data.state_id_issued,
+ document:,
+ inside: '//ns:verifyDriverLicenseDataRequest',
+ )
+
+ add_optional_element(
+ 'ns2:DriverLicenseExpirationDate',
+ value: applicant.state_id_data.state_id_expiration,
+ document:,
+ inside: '//ns:verifyDriverLicenseDataRequest',
+ )
+
@body = document.to_s
end
- def add_street_address_line_2_to_rexml_document(document)
- old_address_node = document.delete_element('//ns1:Address')
- new_address_node = old_address_node.clone
- old_address_node.children.each do |child_node|
- next unless child_node.node_type == :element
+ def add_optional_element(name, value:, document:, inside: nil, after: nil)
+ return if value.blank?
- new_element = child_node.clone
- new_element.add_text(child_node.text)
- new_address_node.add_element(new_element)
+ el = REXML::Element.new(name)
+ el.text = value
- if child_node.name == 'AddressDeliveryPointText'
- new_address_node.add_element(address_line_2_element)
- end
+ if inside
+ REXML::XPath.first(document, inside).add_element(el)
+ elsif after
+ sibling = REXML::XPath.first(document, after)
+ sibling.parent.insert_after(sibling, el)
end
- REXML::XPath.first(
- document,
- '//ns:verifyDriverLicenseDataRequest',
- ).add_element(new_address_node)
- end
-
- def address_line_2_element
- element = REXML::Element.new('ns2:AddressDeliveryPointText')
- element.add_text(applicant.address2)
- element
end
def build_request_body
diff --git a/app/services/proofing/aamva/response/verification_response.rb b/app/services/proofing/aamva/response/verification_response.rb
index b26c6e20f69..adcbdadfb7b 100644
--- a/app/services/proofing/aamva/response/verification_response.rb
+++ b/app/services/proofing/aamva/response/verification_response.rb
@@ -8,6 +8,8 @@ module Aamva
module Response
class VerificationResponse
VERIFICATION_ATTRIBUTES_MAP = {
+ 'DriverLicenseExpirationDateMatchIndicator' => :state_id_expiration,
+ 'DriverLicenseIssueDateMatchIndicator' => :state_id_issued,
'DriverLicenseNumberMatchIndicator' => :state_id_number,
'DocumentCategoryMatchIndicator' => :state_id_type,
'PersonBirthDateMatchIndicator' => :dob,
@@ -62,6 +64,7 @@ def success?
REQUIRED_VERIFICATION_ATTRIBUTES.each do |verification_attribute|
return false unless verification_results[verification_attribute]
end
+
true
end
diff --git a/app/services/proofing/state_id_result.rb b/app/services/proofing/state_id_result.rb
index 459603b67d5..275303357d9 100644
--- a/app/services/proofing/state_id_result.rb
+++ b/app/services/proofing/state_id_result.rb
@@ -11,6 +11,7 @@ class StateIdResult
:success,
:vendor_name,
:transaction_id,
+ :requested_attributes,
:verified_attributes
def initialize(
@@ -19,6 +20,7 @@ def initialize(
exception: nil,
vendor_name: nil,
transaction_id: '',
+ requested_attributes: [],
verified_attributes: []
)
@success = success
@@ -26,6 +28,7 @@ def initialize(
@exception = exception
@vendor_name = vendor_name
@transaction_id = transaction_id
+ @requested_attributes = requested_attributes
@verified_attributes = verified_attributes
end
diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb
index 639d69011fd..c41649e4b7c 100644
--- a/spec/jobs/resolution_proofing_job_spec.rb
+++ b/spec/jobs/resolution_proofing_job_spec.rb
@@ -88,7 +88,7 @@
expect(result_context_stages_state_id[:success]).to eq(true)
expect(result_context_stages_state_id[:timed_out]).to eq(false)
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
- expect(result_context_stages_state_id[:verified_attributes]).to eq(
+ expect(result_context_stages_state_id[:verified_attributes]).to match_array(
%w[address state_id_number state_id_type dob last_name first_name],
)
@@ -165,7 +165,7 @@
expect(result_context_stages_state_id[:success]).to eq(true)
expect(result_context_stages_state_id[:timed_out]).to eq(false)
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
- expect(result_context_stages_state_id[:verified_attributes]).to eq(
+ expect(result_context_stages_state_id[:verified_attributes]).to match_array(
%w[address state_id_number state_id_type dob last_name first_name],
)
@@ -249,7 +249,7 @@
# result[:context][:stages][:state_id]
expect(result_context_stages_state_id[:vendor_name]).to eq('aamva:state_id')
expect(result_context_stages_state_id[:success]).to eq(true)
- expect(result_context_stages_state_id[:verified_attributes]).to eq(
+ expect(result_context_stages_state_id[:verified_attributes]).to match_array(
%w[address state_id_number state_id_type dob last_name first_name],
)
end
@@ -490,7 +490,7 @@
expect(result_context_stages_state_id[:success]).to eq(true)
expect(result_context_stages_state_id[:timed_out]).to eq(false)
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
- expect(result_context_stages_state_id[:verified_attributes]).to eq(
+ expect(result_context_stages_state_id[:verified_attributes]).to match_array(
%w[address state_id_number state_id_type dob last_name first_name],
)
diff --git a/spec/services/proofing/aamva/proofer_spec.rb b/spec/services/proofing/aamva/proofer_spec.rb
index eee30ed3cb1..6afb19420a8 100644
--- a/spec/services/proofing/aamva/proofer_spec.rb
+++ b/spec/services/proofing/aamva/proofer_spec.rb
@@ -67,6 +67,20 @@
].to_set,
)
end
+
+ it 'includes requested_attributes' do
+ result = subject.proof(state_id_data)
+ expect(result.requested_attributes).to eq(
+ {
+ dob: 1,
+ state_id_number: 1,
+ state_id_type: 1,
+ last_name: 1,
+ first_name: 1,
+ address: 1,
+ },
+ )
+ end
end
context 'when verification is unsuccessful' do
@@ -98,6 +112,20 @@
].to_set,
)
end
+
+ it 'includes requested_attributes' do
+ result = subject.proof(state_id_data)
+ expect(result.requested_attributes).to eq(
+ {
+ dob: 1,
+ state_id_number: 1,
+ state_id_type: 1,
+ last_name: 1,
+ first_name: 1,
+ address: 1,
+ },
+ )
+ end
end
context 'when verification attributes are missing' do
@@ -128,6 +156,43 @@
].to_set,
)
end
+
+ it 'includes requested_attributes' do
+ result = subject.proof(state_id_data)
+ expect(result.requested_attributes).to eq(
+ {
+ state_id_number: 1,
+ state_id_type: 1,
+ last_name: 1,
+ first_name: 1,
+ address: 1,
+ },
+ )
+ end
+ end
+
+ context 'when issue / expiration present' do
+ let(:state_id_data) do
+ {
+ state_id_number: '1234567890',
+ state_id_jurisdiction: 'VA',
+ state_id_type: 'drivers_license',
+ state_id_issued: '2023-04-05',
+ state_id_expiration: '2030-01-02',
+ }
+ end
+
+ it 'includes them' do
+ expect(Proofing::Aamva::Request::VerificationRequest).to receive(:new).with(
+ hash_including(
+ applicant: satisfy do |a|
+ expect(a.state_id_data.state_id_issued).to eql('2023-04-05')
+ expect(a.state_id_data.state_id_expiration).to eql('2030-01-02')
+ end,
+ ),
+ )
+ subject.proof(state_id_data)
+ end
end
context 'when AAMVA throws an exception' do
diff --git a/spec/services/proofing/aamva/request/verification_request_spec.rb b/spec/services/proofing/aamva/request/verification_request_spec.rb
index 2f379d5a911..19279b21218 100644
--- a/spec/services/proofing/aamva/request/verification_request_spec.rb
+++ b/spec/services/proofing/aamva/request/verification_request_spec.rb
@@ -71,6 +71,20 @@
],
)
end
+
+ it 'includes issue date if present' do
+ applicant.state_id_data.state_id_issued = '2024-05-06'
+ expect(subject.body).to include(
+ '2024-05-06',
+ )
+ end
+
+ it 'includes expiration date if present' do
+ applicant.state_id_data.state_id_expiration = '2030-01-02'
+ expect(subject.body).to include(
+ '2030-01-02',
+ )
+ end
end
describe '#headers' do
diff --git a/spec/services/proofing/aamva/response/verification_response_spec.rb b/spec/services/proofing/aamva/response/verification_response_spec.rb
index 47d57cba506..2bd0287eb63 100644
--- a/spec/services/proofing/aamva/response/verification_response_spec.rb
+++ b/spec/services/proofing/aamva/response/verification_response_spec.rb
@@ -13,6 +13,8 @@
end
let(:verification_results) do
{
+ state_id_expiration: nil,
+ state_id_issued: nil,
state_id_number: true,
state_id_type: true,
dob: true,