PAN Validator

easy · iso8583, payments

PAN Validator

It's 2 AM and the fraud alerts are going off. Transactions are coming through with invalid card numbers - and somehow your system approved them.

How did invalid PANs get past validation? Because someone commented out the Luhn check "temporarily" three months ago. Never again.

The Luhn Algorithm

Hans Peter Luhn invented this checksum formula in 1954. It catches accidental errors - typos, single-digit mistakes, transpositions. It won't stop deliberate fraud (anyone can compute a valid check digit), but it will catch data entry errors.

The Algorithm:

  1. Starting from the rightmost digit (the check digit), double every second digit
  2. If doubling produces a number greater than 9, subtract 9 (equivalent to summing the digits)
  3. Sum all the digits
  4. If the total is divisible by 10, the number is valid

Example: 4532015112830366

Position:  16  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1
                                                                        ↑ rightmost
Digit:      4   5   3   2   0   1   5   1   1   2   8   3   0   3   6   6
Double?:    ×2      ×2      ×2      ×2      ×2      ×2      ×2      ×2
Result:     8   5   6   2   0   1  10   1   2   2  16   3   0   3  12   6
After -9:   8   5   6   2   0   1   1   1   2   2   7   3   0   3   3   6

Sum: 8+5+6+2+0+1+1+1+2+2+7+3+0+3+3+6 = 50

50 % 10 = 0 → Valid!

The IIN (Issuer Identification Number)

The first 6-8 digits of a PAN identify the card issuer:

First Digit(s)    Network
───────────────   ───────
4                 Visa
51-55             Mastercard
2221-2720         Mastercard (newer range)
34, 37            American Express
6011, 65          Discover
62                UnionPay

For this problem, extract the first 6 digits as the IIN (the traditional BIN length - Bank Identification Number).

What You're Building

// PANInfo contains validation results and extracted information from a PAN.
type PANInfo struct {
    Valid       bool   // True if PAN passes Luhn check
    PAN         string // The original PAN (for reference)
    IIN         string // First 6 digits (Issuer Identification Number)
    CheckDigit  int    // The last digit (Luhn check digit)
    Length      int    // Total length of the PAN
}

// ValidatePAN validates a Primary Account Number and extracts key information.
// It performs Luhn validation and extracts the IIN.
//
// Rules:
// - PAN must be 13-19 digits (industry standard range)
// - All characters must be digits (0-9)
// - Must pass Luhn check
//
// Returns PANInfo with Valid=false if any rule fails.
// The other fields are populated regardless of validity.
func ValidatePAN(pan string) PANInfo

Behavior

Validation Rules

  1. Length check: PAN must be 13-19 characters
  2. Digit check: Every character must be '0'-'9'
  3. Luhn check: Must pass the Luhn algorithm

Field Extraction

Regardless of validity:

  • IIN: First 6 digits (or entire PAN if shorter than 6)
  • CheckDigit: Last digit as integer
  • Length: Total character count

Examples

ValidatePAN("4532015112830366")
// → PANInfo{
//     Valid:      true,
//     PAN:        "4532015112830366",
//     IIN:        "453201",
//     CheckDigit: 6,
//     Length:     16,
// }

ValidatePAN("4532015112830367")  // Wrong check digit
// → PANInfo{
//     Valid:      false,
//     PAN:        "4532015112830367",
//     IIN:        "453201",
//     CheckDigit: 7,
//     Length:     16,
// }

ValidatePAN("123")  // Too short
// → PANInfo{
//     Valid:      false,
//     PAN:        "123",
//     IIN:        "123",
//     CheckDigit: 3,
//     Length:     3,
// }

ValidatePAN("4532A15112830366")  // Non-digit
// → PANInfo{
//     Valid:      false,
//     PAN:        "4532A15112830366",
//     IIN:        "4532A1",  // Still extract what's there
//     CheckDigit: 6,
//     Length:     16,
// }

Test PANs

The payment industry uses specific test PAN ranges:

Visa:       4532015112830366, 4916338506082832
Mastercard: 5425233430109903, 5114496353984312
Amex:       378282246310005, 371449635398431
Discover:   6011000400000000, 6011111111111117

These are specifically designated for testing and should never be used in production.

Why This Matters

  1. Data Quality: Catch typos before they hit the network
  2. Fraud Prevention: Invalid PANs are a red flag
  3. Network Fees: Some networks charge for invalid requests
  4. Debugging: Quickly identify malformed test data

When a transaction fails with "Invalid Card Number" (DE39='14'), the first thing to check is Luhn.

Edge Cases to Consider

  1. Leading zeros: "0000123456789012" is valid (test PANs exist with leading zeros)
  2. All same digits: "1111111111111117" - valid Luhn (sum=16, but wait, we double alternating...)
  3. Minimum length: 13 digits (some older cards)
  4. Maximum length: 19 digits
  5. Empty string: Invalid

Hints (only if stuck)

<details> <summary>Hint 1: Processing direction</summary>

Process the PAN right-to-left. The check digit (rightmost) is position 1, and you double positions 2, 4, 6, etc.

for i := len(pan) - 1; i >= 0; i-- {
    // ...
}

</details>

<details> <summary>Hint 2: The doubling trick</summary>

When you double a digit and get > 9, subtract 9. This is equivalent to summing the two digits:

  • 6 × 2 = 12 → 1 + 2 = 3 → same as 12 - 9 = 3

The subtraction is faster than string manipulation.

</details>

<details> <summary>Hint 3: Alternation tracking</summary>

Use a boolean to track which position you're at:

double := false  // Start false, first digit is not doubled
for i := len(pan) - 1; i >= 0; i-- {
    // process digit
    double = !double  // Toggle for next iteration
}

</details>

Run tests to see results
No issues detected
    Join Discord