Skip to main content

Guide · Validators

A Luhn Checksum Is Not a Payment (and Not a Card Generator)

Updated 2026-09-01 · 7 min read

The Luhn algorithm is a digit checksum Hans Peter Luhn published for detecting typos. Payment networks later used it on primary account numbers so a mistyped PAN fails before it hits the wire. That history is why a web page can print “valid checksum” and a developer can think “this card is real.” Those are not the same sentence.

DevOkk’s Credit Card Validator runs Luhn and guesses brand from the IIN prefix. Default 4111111111111111 is a well-known Visa test PAN. It is not a payment. The page is not a gateway, not a tokenizer, not a card generator, and not PCI scope magic. Numbers are not stored to complete the check. The validator tools hub says the same thing: checksum is not a charge.

If you needed the general “do not paste secrets” list, that is What not to paste into online developer tools. Syntax versus schema for JSON is the same kind of confusion: A JSON validator is not JSON Schema.

What Luhn actually does

Digits are processed from the right: double every second digit, subtract 9 if the double exceeds 9, sum, and the total must be divisible by 10. A single-digit typo often fails. Two compensating typos can pass. A random generator that is aware of Luhn can emit passing numbers that were never issued.

Passing Luhn means “this digit string is consistent with the checksum.” It does not mean:

  • The PAN was issued by a bank
  • The account is open
  • Expiry and CVV match
  • Funds exist
  • The person in front of the form is the cardholder

A failing Luhn means “this string should not be sent as a PAN.” That is the useful half. QA should fail fast on a mistype. That is not authorization.

IIN brand is a prefix heuristic

The first digits (IIN, still often called BIN) suggest Visa, Mastercard, Amex, and so on. Those ranges are allocated and reallocated. A client-side table is a UX hint. It is not a live lookup against a scheme registry. If your test card table says a number is Mastercard and a toy prefix map says Visa, believe the processor documentation for the test, not the dropdown.

Amex length (15) versus Visa (16) is another format check, still not a charge.

Why the default is 4111…

Processors publish test PANs so you never put a live card in a sandbox. 4111111111111111 is the cliché Visa success case in countless docs. Other brands publish other numbers, including cases that fail Luhn on purpose, fail AVS, or trigger 3DS. Use those lists. Do not harvest PANs from production logs to “have a realistic test.”

This article will not list a cookbook of passing numbers for every brand beyond the one default already on the tool. The job is the distinction, not a generator.

What a payment integration actually needs

A real charge goes through a processor: PAN (or a token), expiry, often CVV, amount, currency, merchant credentials, and network rules. 3-D Secure, RADAR-style fraud tools, and issuer declines happen after Luhn. Your checkout should tokenize in a hosted field so the PAN never touches your server if that is the PCI point of your architecture. A public Luhn page is not that architecture.

If you are building a form, the right test is the sandbox charge, not a screenshot of a masked “Visa, checksum OK.”

What Luhn does not see: expiry, CVV, name, AVS

The digit string is the only input to Luhn. Month/year expiry is a separate field. CVV is a separate field (and must not be stored after authorization in PCI-ish systems). Name on card is not in the PAN. AVS compares an address the issuer knows. None of that is in the checksum. A test PAN with a valid Luhn and a wrong expiry still fails a sandbox charge. That is the correct failure.

Client-side Luhn in your own checkout is a UX filter: catch a typo before a network round-trip. It is not a substitute for a processor. Implement it if you want; still tokenize; still use test PANs in QA.

Amex 15-digit versus Visa/Mastercard 16 is a length check. Length plus Luhn is still format. Discover, UnionPay, and regional schemes have their own IIN stories; a small prefix table will be incomplete. For QA, the processor’s list wins.

PCI, screenshots, and tickets

A PAN in a Jira ticket, a Slack screenshot, or a support HAR is a data incident waiting for a name. Mask in logs. Use test PANs in fixtures. If a customer already pasted a PAN into a tools site, treat it as exposed and follow your incident process - this blog is not your QSA.

DevOkk does not become a card vault because the algorithm is local. The leak is the human who pasted the live number, the shared laptop, and the screen recording.

Fraud and generation

The tool will not help you generate PANs to test stolen-card checkers, scrape bins, or “see if this dump is live.” Those jobs are out of scope. Checksum verification of published test numbers is the intended use. If that sentence is disappointing, the disappointment is the point.

Test cards that are supposed to fail

Processor docs include PANs that fail Luhn, fail with a specific decline code, or require 3DS. Those failures are the test. A Luhn-only page that says “invalid checksum” on a documented fail-Luhn test number is working. Do not “fix” the PAN until the checksum passes if the point of the test was the failure. Do not invent a passing number to skip the case.

CVV 123 on a test Visa is a sandbox convention, not a secret worth pasting into a second website. Keep test data in the processor’s table, in your own fixtures, not in a public validator as a habit.

Brand-new IIN ranges will confuse a stale prefix map. That is why brand on this page is a guess. Sandbox charges still go through the network mock.

XML well-formedness is not XSD. JSON syntax is not schema. YAML parse is not “this is the config we meant.” Luhn sits in that family: a cheap local filter. The expensive questions stay with the system of record.

Spaces, hyphens, and the string you actually checksum

Luhn runs on digits. Spaces and hyphens in 4111-1111-1111-1111 must be stripped first. If a form sends the punctuation into the algorithm, it will fail a good test PAN. DevOkk’s field is a number check, not a pretty-printer for PAN layout. Do not add check digits by hand to “make it valid” on a production-looking string you found in a log.

Last-four display is not Luhn. Showing •••• 1111 in a UI does not prove the full PAN checksummed. That is a display choice.

Unicode digits from a PDF copy-paste can look like ASCII 4s and not be. If a published test PAN fails, inspect the character codes before you assume the algorithm is wrong.

What “valid” on this page is allowed to mean

Valid here means: digits pass Luhn, and a prefix table guessed a brand. Invalid means: the digit string is not Luhn-consistent (after stripping spaces). It does not mean the sandbox will approve. It does not mean Visa issued the PAN. It does not mean you may store the number. The masked UI is there so a shoulder-surfer gets less of the string - still do not use a live PAN.

If you need to verify a tokenizer, charge $0 in the processor sandbox with a documented test card.

Tokenization is not Luhn either

A hosted card field that returns a tok_… string never needed your checkout to run Luhn on the full PAN in JavaScript. The processor tokenized after its own checks. Client-side Luhn on a raw <input> is optional UX before you send to the tokenizer. It is not a substitute for Elements, Checkout, or whatever your integration guide names. DevOkk’s page is a standalone checksum demo, not a PCI SAQ replacement. Storing PANs in your database because Luhn passed is still a breach waiting for a name.

If the sandbox declines a documented test PAN, debug amount, currency, and test-mode keys - not the checksum of 4111111111111111. Expiry 12/34 on a test card is sandbox theater, not a reason to type a live expiry beside a live PAN on a public page.

A checksum is not a charge

Open Credit Card Validator when you want to see Luhn and a prefix brand on a test PAN, in the browser, with a masked result. Then run the actual payment in the processor sandbox. Never paste a live production card number into this textarea, or any other. The algorithm is old, small, and useful. It is not the card network.

Frequently asked questions

What does DevOkk’s credit card validator actually check?

The Luhn checksum and a brand guess from the IIN prefix. Default 4111111111111111 is a published Visa test number with a valid checksum. Credit Card Validator is not a payment gateway and does not store PANs.

Does a passing Luhn check mean the card will charge?

No. Luhn is a transcription check. It does not talk to a network, does not verify the PAN is issued, and does not check expiry, CVV, AVS, or 3-D Secure. A made-up number can still pass Luhn.

Should I paste a real customer card number here?

No. Use published test PANs from your processor’s docs (Stripe, Braintree, and card brands publish them). Live PANs are payment data. Do not put them in a public textarea, a ticket, or a screenshot.

Is this a card number generator?

No. The tool will not help you invent PANs for fraud, scraping, or ‘testing without test cards.’ Checksums on published test numbers are the intended demo.

Are numbers stored?

No. The check runs in the browser. The result is masked. That still does not make a live PAN a good paste. Clear the field and localStorage on a shared device.

Why did brand say Visa but the BIN table in my head said otherwise?

IIN/BIN ranges are leased and change. A prefix heuristic is a guess for UX, not a network registration lookup. Trust your processor’s test-card table for brand in QA.

More reading that links back to the same tools and workflows.