Credit Card Validator — C++ source
Validate credit card numbers with the Luhn checksum and detect the issuing brand (Visa, Mastercard, Amex, Discover, Diners, JCB, UnionPay, Maestro). 100% client-side - nothing is transmitted.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// credit-card-validator — Luhn checksum + brand detection for card numbers.
//
// Language: C++ (C++17, standard library only)
// Source: CosmoDev polyglot showcase port of the Credit Card Validator tool,
// ported from src/lib/creditCard.ts (the canonical TypeScript lib) and
// held in lock-step with cli/credit-card-validator/credit-card-validator.go.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Pure credit-card validation with no I/O beyond the demo main: sanitize to
// digits, run the Luhn checksum, detect the issuing brand by prefix + length,
// and group the number for display. Never throws on malformed input; mirrors
// the TypeScript contract.
//
// The brand table mirrors the TS/Python regexes with std::regex (ECMAScript
// grammar). Every pattern is "^"-anchored and matched with regex_search, so
// only the prefix is tested — exactly like Python's pattern.search().
#include <algorithm>
#include <iostream>
#include <optional>
#include <regex>
#include <string>
#include <utility>
#include <vector>
// Full validation result. `brand` is std::nullopt when the issuer is unknown
// (the C++ equivalent of TypeScript's `string | null`).
struct CardInfo {
std::string input;
std::string sanitized; // digits only
bool valid = false; // digits present, sane length, and Luhn passes
bool luhn_valid = false; // Luhn checksum passes (requires >= 12 digits)
std::optional<std::string> brand; // issuing brand, or nullopt when unknown
std::string formatted; // grouped per brand spacing rules
};
// Luhn (mod-10) checksum. Empty or non-digit input returns false.
bool luhn_check(const std::string &digits) {
static const std::regex digits_only("[0-9]*");
if (digits.empty() || !std::regex_match(digits, digits_only)) {
return false;
}
int total = 0;
bool should_double = false;
// Walk right-to-left; double every second digit and collapse >9 to its digit-sum
// (e.g. 7*2=14 -> 14-9=5, identical to summing 1+4).
for (auto it = digits.rbegin(); it != digits.rend(); ++it) {
int d = *it - '0';
if (should_double) {
d *= 2;
if (d > 9) {
d -= 9;
}
}
total += d;
should_double = !should_double;
}
return total % 10 == 0;
}
// Brand detection rule: name, prefix pattern, allowed lengths.
struct BrandRule {
const char *name;
std::regex pattern;
std::vector<size_t> lengths;
};
// Brand detection table. ORDER MATTERS: the first prefix+length match wins.
// Maestro is last because its broad "6" / 5x ranges overlap narrower issuers.
const std::vector<BrandRule> &brand_rules() {
static const std::vector<BrandRule> rules = {
{"Visa", std::regex("^4"), {13, 16, 19}},
{"Mastercard", std::regex("^(5[1-5]|2(2[2-9]|[3-6][0-9]|7[01]|720))"), {16}},
{"American Express", std::regex("^3[47]"), {15}},
{"Discover",
std::regex("^(6011|65|64[4-9]|622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9([01][0-9]|2[0-5])))"),
{16, 19}},
{"Diners Club", std::regex("^(30[0-5]|3095|36|38|39|54|55)"), {14, 16}},
{"JCB", std::regex("^35(2[89]|[3-8][0-9])"), {16, 17, 18, 19}},
{"UnionPay", std::regex("^62"), {16, 17, 18, 19}},
{"Maestro", std::regex("^(50|56|57|58|6)"), {12, 13, 14, 15, 16, 17, 18, 19}},
};
return rules;
}
// Detect the card brand by prefix + length. Returns nullopt when unknown.
std::optional<std::string> detect_brand(const std::string &digits) {
if (digits.empty()) {
return std::nullopt;
}
for (const auto &rule : brand_rules()) {
if (std::regex_search(digits, rule.pattern) &&
std::find(rule.lengths.begin(), rule.lengths.end(), digits.size()) != rule.lengths.end()) {
return rule.name;
}
}
return std::nullopt;
}
// Slice digits into runs of the given widths, joining with spaces.
std::string group(const std::string &digits, std::initializer_list<size_t> widths) {
std::vector<std::string> parts;
size_t i = 0;
const size_t n = digits.size();
for (size_t w : widths) {
if (i >= n) {
break;
}
parts.push_back(digits.substr(i, w)); // substr clamps the run at the end
i += w;
}
// Any trailing remainder (e.g. a partially typed number) is appended as-is.
if (i < n) {
parts.push_back(digits.substr(i));
}
std::string out;
for (size_t k = 0; k < parts.size(); ++k) {
if (k > 0) {
out += ' ';
}
out += parts[k];
}
return out;
}
// Group the number per brand spacing rules.
std::string format_card(const std::string &digits, const std::optional<std::string> &brand) {
if (digits.empty()) {
return "";
}
// Amex uses a 4-6-5 grouping; Diners Club's 14-digit variant uses 4-6-4.
if (brand && *brand == "American Express") {
return group(digits, {4, 6, 5});
}
if (brand && *brand == "Diners Club" && digits.size() == 14) {
return group(digits, {4, 6, 4});
}
// Default: groups of four separated by spaces, no trailing space
// (equivalent to the TS regex `(.{4})(?=.)` replacement).
std::string out;
for (size_t i = 0; i < digits.size(); ++i) {
if (i > 0 && i % 4 == 0) {
out += ' ';
}
out += digits[i];
}
return out;
}
// Keep ASCII digit bytes only; multibyte UTF-8 bytes are all >= 0x80, so excluded.
std::string sanitize_digits(const std::string &input) {
std::string out;
for (char ch : input) {
if (ch >= '0' && ch <= '9') {
out += ch;
}
}
return out;
}
// Validate a card number. Always returns a CardInfo; never throws.
CardInfo validate_card(const std::string &input) {
CardInfo info;
info.input = input;
info.sanitized = sanitize_digits(input);
info.brand = detect_brand(info.sanitized);
// Luhn is only meaningful once we have a plausible card length (>= 12 digits).
info.luhn_valid = info.sanitized.size() >= 12 && luhn_check(info.sanitized);
info.valid = info.luhn_valid;
info.formatted = format_card(info.sanitized, info.brand);
return info;
}
int main() {
// Tiny showcase: validate a well-known test number (Visa).
CardInfo info = validate_card("4111 1111 1111 1111");
std::cout << "brand=" << (info.brand ? *info.brand : "None")
<< " valid=" << (info.valid ? "true" : "false")
<< " formatted=\"" << info.formatted << "\"\n";
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →