Katkat

Verify business accounts with progressive KYC rules

StripeSoftware Engineer · Technical Screen

Last 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:

  1. submitted["legal_name"]: Must be a string containing at least one non-whitespace character.
  2. submitted["country"]: Must be a 2-letter alphabetic string (case-insensitive).
  3. 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:

  1. country: Matches registry["country"] (case-insensitive).
  2. tax_id: Matches registry["tax_id"] after stripping hyphens, spaces, and converting to uppercase.
  3. 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:

  1. submitted.get("owners", []) is non-empty.
  2. Every owner name listed in submitted["owners"] has a matching record in registry.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").

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)?