Skip to content

LG-13706: Socure KYC Proofer#11093

Merged
matthinz merged 10 commits intomainfrom
matthinz/13706-socure-kyc-proofer
Aug 21, 2024
Merged

LG-13706: Socure KYC Proofer#11093
matthinz merged 10 commits intomainfrom
matthinz/13706-socure-kyc-proofer

Conversation

@matthinz
Copy link
Contributor

@matthinz matthinz commented Aug 15, 2024

🎫 Ticket

Link to the relevant ticket:
LG-13706

🛠 Summary of changes

This PR adds an implementation of a Proofer for Socure's ID+ service (specifically the KYC module). It does not actually wire the proofer in anywhere--that will come in a future update.

📜 Testing Plan

You can test this manually!

DO NOT USE REAL PII

  1. Log into the Socure dashboard and grab an Id+ API key in the "Sandbox" environment
  2. Make it available in your shell: export SOCURE_IDPLUS_API_KEY=your-secret-api-key
  3. Run this super-advanced test script in the Rails console:
config = Proofing::Socure::IdPlus::Config.new(api_key: ENV['SOCURE_IDPLUS_API_KEY'], base_url: 'https://sandbox.socure.us')

proofer = Proofing::Socure::IdPlus::Proofer.new(config)

result = proofer.proof(
  first_name: 'Fakey',
  last_name: 'McFakerson',
  address1: '1234 Fake St.',
  city: 'Fakeville',
  state: 'CA',
  zipcode: '90210',
  phone: '360-555-1234',
  email: 'testy@example.org',
  dob: '1999-12-31',
  ssn: '999-22-3344',
)

- Basic Proofer implementation for Socure KYC.
- Request / Response classes, error handling, etc.

changelog: Upcoming Features, Identity verification, Implement proofer for Socure KYC
def kyc_field_validations
@field_validations ||= kyc('fieldValidations').
each_with_object({}) do |(field, valid), obj|
obj[field.to_sym] = valid.round == 1
Copy link
Contributor

Choose a reason for hiding this comment

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

would we ever want this logic to change per-field? or are we set with anything above 0.5 is a pass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They will literally only send either 0.99 or 0.01, I just figured .round would avoid any weirdness with == and floating points

@matthinz matthinz requested a review from a team August 16, 2024 00:10
VERIFIED_ATTRIBUTE_MAP = {
address: %i[streetAddress city state zip].freeze,
first_name: :firstName,
last_name: :surName,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is offtopic and unimportant, but is camelcase surName our pattern or Socure's? My brain keeps either reading it as surfName, or as two words (since camelCase) and wondering what a sur name is, like it's a foreign word. (I mean, it is, but who knows what a "south name" is?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is Socure's field naming. My thinking is that request/response class speak in terms of Socure's API and their naming, and the proofer class translates that to our conventions / case style

dob: input.dob&.to_date&.to_s,

userConsent: true,
consentTimestamp: 5.minutes.ago.iso8601,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems weird? Do they actually require a timestamp here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They do, and it needs to be recent enough or they will return an error


# > The country or jurisdiction from where the transaction originates,
# > specified in ISO-2 country codes format
countryOfOrigin: 'US',
Copy link
Contributor

Choose a reason for hiding this comment

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

Idle curiosity: does the transaction "originate" from our servers, or the end user? I.e., we know CBP has a lot of users from Mexico; do we need to try to make this dynamic, or is it where we are?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is where we are--on Socure's end they are using it for compliance stuff (like, "does GDPR apply to this transaction?")

# XXX: This should be set to the time the user submitted agreement,
# which we are not currently tracking. The "5.minutes.ago" is
# because Socure will reject times "in the future", so we avoid
# our clocks being out of sync with theirs.
Copy link
Contributor

Choose a reason for hiding this comment

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

Unimportant but IMHO this comment makes more sense in the code. I wondered why it was set this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are a couple of tickets in the epic to follow up on this:

stub_request(:post, 'https://example.org/api/3.0/EmailAuthScore').
to_return(
status: 500,
body: 'It works!',
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
body: 'It works!',
body: 'It does not work!',

(Only half-joking?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my attempt at humor--Apache will display an "It works!" page by default, meaning that Apache is working, but this page is almost never what you actually want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, ha! I am all for Apache jokes. I somehow missed this one, but I am 200 OK with this now.

Copy link
Contributor

@n1zyy n1zyy left a comment

Choose a reason for hiding this comment

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

This looks reasonable to me. I was able to test it in the console successfully.

Definitely out of scope, if we ever even do it, but with @errors={:reason_codes=>["I919"]} I can't help but think it'd be neat to map those to descriptions. But it's possible these are proprietary and/or there's not really a need to have them in the code? Just a musing for another time.

@matthinz
Copy link
Contributor Author

This looks reasonable to me. I was able to test it in the console successfully.

Definitely out of scope, if we ever even do it, but with @errors={:reason_codes=>["I919"]} I can't help but think it'd be neat to map those to descriptions. But it's possible these are proprietary and/or there's not really a need to have them in the code? Just a musing for another time.

I think there is an API endpoint we can hit to get the descriptions, but they don't change too much over time, so we could possibly have a static mapping

@matthinz matthinz merged commit e863fe9 into main Aug 21, 2024
@matthinz matthinz deleted the matthinz/13706-socure-kyc-proofer branch August 21, 2024 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants