Verify business accounts with progressive KYC rules
StripeSoftware Engineer · Technical ScreenLast updated
Implement a multi-tier KYC verification function that validates business identity inputs against format requirements and an official registry using token-based normalization.
Problem Statement
Financial platforms like Stripe must verify business accounts before enabling payouts. Verification follows a progressive tier system: basic format validation (Tier 1), official registry record matching (Tier 2), and beneficial ownership verification (Tier 3). If an account fails at any tier, it cannot qualify for subsequent tiers.
Implement a function verify_kyc(submitted: dict, registry: dict) -> int that evaluates the submitted data against the official registry record and returns the highest tier achieved (0, 1, 2, or 3).
Tier Rules
Tier 1: Format Validation
Returns 1 if all of the following hold; otherwise returns 0:
submitted["legal_name"]: Must be a string containing at least one non-whitespace character.submitted["country"]: Must be a 2-letter alphabetic string (case-insensitive).submitted["tax_id"]: When hyphens (-) and spaces are removed, it must consist only of alphanumeric characters and have a length between 8 and 10 characters (inclusive).
Tier 2: Registry Match
Must pass Tier 1. Returns 2 if all of the following match the registry record; otherwise returns 1:
country: Matchesregistry["country"](case-insensitive).tax_id: Matchesregistry["tax_id"]after stripping hyphens, spaces, and converting to uppercase.legal_name: Both submitted and registry names are normalized as follows:- Convert to lowercase.
- Remove all characters that are not ASCII letters, digits, or whitespace.
- Tokenize by whitespace.
- Remove standard legal suffix tokens:
{"inc", "llc", "corp", "corporation", "ltd", "co", "company"}. - Both normalized names must have at least one token remaining, and their remaining sets of tokens must be identical.
Tier 3: Beneficial Ownership Match
Must pass Tier 2. Returns 3 if:
submitted.get("owners", [])is non-empty.- Every owner name listed in
submitted["owners"]has a matching record inregistry.get("owners", []).- Two owner names match if, after lowercasing, removing non-alphanumeric/non-whitespace characters, and splitting into tokens, their sets of tokens are identical (e.g.,
"Jane Doe"matches"Doe, Jane").
- Two owner names match if, after lowercasing, removing non-alphanumeric/non-whitespace characters, and splitting into tokens, their sets of tokens are identical (e.g.,
If any condition in Tier 3 fails, the return value is 2.
Interface
def verify_kyc(submitted: dict, registry: dict) -> int:
pass
Examples
Example 1
submitted = {
"legal_name": "Acme Payments, Inc.",
"country": "us",
"tax_id": "12-345678",
"owners": ["Jane Doe", "Bob Smith"]
}
registry = {
"legal_name": "ACME PAYMENTS LLC",
"country": "US",
"tax_id": "12345678",
"owners": ["Doe, Jane", "Smith Bob", "Alice Brown"]
}
# Output: 3
Explanation: Passes Tier 1. In Tier 2, tax IDs match ("12345678"), country codes match ("US"), and both normalized legal names reduce to the token set {"acme", "payments"}. In Tier 3, all submitted owners ("Jane Doe" and "Bob Smith") match registry entries.
Example 2
submitted = {
"legal_name": "Global Freight Ltd",
"country": "CA",
"tax_id": "AB-998877",
"owners": []
}
registry = {
"legal_name": "Global Freight Company",
"country": "CA",
"tax_id": "AB998877",
"owners": ["Carol Danvers"]
}
# Output: 2
Explanation: Passes Tier 1 and Tier 2 ({"global", "freight"} match). Fails Tier 3 because submitted["owners"] is empty.
Hints
Hint 1
Modularize your code into helper functions for each tier and text normalization routine to keep edge-case handling isolated and testable.
Hint 2
Be careful with whitespace and punctuation stripping when tokenizing: replace non-alphanumeric characters with spaces before splitting to avoid merging adjacent words.
Hint 3
Check for edge cases where stripping suffix words leaves an empty token set, in which case the business name match must fail.
Follow-up questions
- How would you handle typo tolerance or edit distance (e.g., Levenshtein distance) for business names while avoiding false positives for short names?
- If the registry contained millions of records and you had to search for matching businesses instead of verifying against a known record, how would you index the tokenized names?
- How would you adapt this rule engine to support country-specific tax ID and suffix rules dynamically (e.g., via configuration instead of hardcoded rules)?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.