Test Cards + Tokens + 3DS + DIDs β One Desktop Tool Does It All Update 29.03.2026
Generate Luhn-valid test cards with tokens, 3DS codes, biometrics, and DIDs β all from a desktop app you can run offline.
If youβve ever tested a payment form, youβve probably hit Namso Gen. It works. But itβs online-only, limited in features, and youβre trusting someone elseβs server with your workflow. This tool runs locally, generates more data types, and gives you a proper GUI.
π§ What Even Is a Test Card Generator?
Payment forms need testing. Real cards = real charges = real problems. Test cards = fake numbers that look valid to the form but canβt actually charge anyone.
How it works:
- Every card number follows the Luhn algorithm (a.k.a. Mod 10) β a checksum that catches typos
- The first 6-8 digits are the BIN/IIN β they identify the card network (Visa, Mastercard, etc.) and issuing bank
- Generators create numbers that pass Luhn validation but arenβt linked to real accounts
What you use them for:
- Testing checkout flows without risking real money
- Validating that your payment form rejects bad numbers
- Stress-testing payment systems with bulk data
- Learning how card validation actually works
What they DONβT do:
- Generate real card numbers with real balances
- Work for actual purchases (theyβll fail at the bank level)
- Give you anything illegal β these are test numbers, not stolen data
β‘ The Tool β Test Card Generator 2030
This is a Python + PyQt6 desktop app. You run it locally. No servers, no tracking, no rate limits.
What It Does
| Feature | What You Get |
|---|---|
| BIN input | Enter any 6-8 digit IIN, generates matching card type |
| Auto-detection | Visa, Mastercard, Amex, Discover, UnionPay, Crypto β detected from BIN |
| Correct lengths | Visa: 16 or 19, Amex: 15, Mastercard: 16 β handled automatically |
| CVV generation | 3 digits (most cards) or 4 digits (Amex) β auto-selected |
| Expiry dates | 2026+ with 1-10 year validity |
| Cardholder names | Random English names for realistic test data |
| Batch generation | Up to 200 cards at once |
| Save to file | Export everything to .txt |
Advanced Features (Checkboxes)
| Checkbox | What It Adds |
|---|---|
| Add tokens | Simulates Apple Pay / Google Pay / stablecoin tokenization |
| Add 3DS codes | 6-digit codes for 3D Secure authentication testing |
| Add biometric tokens | SHA-256 hash mimicking fingerprint/face ID tokens |
| Add dynamic CVV | Changes hourly (shows seed + explanation) |
| Add DID | Decentralized identifiers (did:ethr:..., did:key:..., did:web:...) |
Screenshots
π The Full Script (Copy-Paste Ready)
Requirements: Python 3.9+ and pip install PyQt6 stripe
Save this as card_generator.py and double-click to run:
#!/usr/bin/env python3
import sys
import random
import hashlib
import os
from datetime import datetime
from typing import Optional
import stripe
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit,
QPushButton, QTextEdit, QSpinBox, QFileDialog,
QMessageBox, QCheckBox, QTabWidget, QFormLayout, QHBoxLayout
)
# ====================== CARD GENERATOR FUNCTIONS ======================
def luhn_checksum(number_str):
digits = [int(d) for d in number_str]
for i in range(len(digits) - 2, -1, -2):
digits[i] = sum(divmod(digits[i] * 2, 10))
total = sum(digits)
return (10 - (total % 10)) % 10
def determine_card_type(iin_prefix):
iin_str = str(iin_prefix)
if iin_str.startswith('4'):
return 'Visa', [16, 19], 3
elif iin_str.startswith('5') or iin_str.startswith('2'):
return 'Mastercard', 16, 3
elif iin_str.startswith('34') or iin_str.startswith('37'):
return 'American Express', 15, 4
elif iin_str.startswith('6'):
return 'Discover', [16, 19], 3
elif iin_str.startswith('62') or iin_str.startswith('81'):
return 'UnionPay', [16, 19], 3
elif iin_str.startswith('7'):
return 'Crypto/Stablecoin', [16, 19], 3
else:
return 'Unknown', 16, 3
def generate_card_number(iin_prefix):
iin_str = str(iin_prefix)
if len(iin_str) < 6 or len(iin_str) > 8:
raise ValueError("IIN must be 6-8 digits")
card_type, length_range, _ = determine_card_type(iin_prefix)
if isinstance(length_range, int):
length = length_range
else:
length = random.choice(length_range)
remaining_length = length - len(iin_str) - 1
random_digits = ''.join(str(random.randint(0, 9)) for _ in range(remaining_length))
partial_number = iin_str + random_digits
checksum = luhn_checksum(partial_number + '0')
return partial_number + str(checksum), length
def generate_expiry_date():
base_year = 2026
current_year = datetime.now().year
start_year = max(current_year, base_year)
expiry_year = start_year + random.randint(1, 10)
expiry_month = random.randint(1, 12)
return f"{expiry_month:02d}/{expiry_year % 100:02d}"
def generate_static_cvv(length=3):
return ''.join(str(random.randint(0, 9)) for _ in range(length))
def generate_dynamic_cvv(seed, length=3):
current_time = datetime.now().strftime("%Y%m%d%H")
hash_input = f"{seed}{current_time}"
hashed = hashlib.sha256(hash_input.encode()).hexdigest()
return hashed[:length].upper()
def generate_biometric_token():
bio_data = ''.join(random.choices('0123456789ABCDEF', k=32))
return hashlib.sha256(bio_data.encode()).hexdigest()
def generate_did():
method = random.choice(['ethr', 'key', 'web'])
identifier = ''.join(random.choices('0123456789abcdef', k=40))
return f"did:{method}:{identifier}"
def generate_cardholder_name():
first_names = ["John", "Jane", "Alex", "Emily", "Michael", "Sophia", "David", "Olivia", "James", "Emma"]
last_names = ["Doe", "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Martinez"]
return f"{random.choice(first_names)} {random.choice(last_names)}"
def generate_token(card_number):
token_length = random.randint(16, 19)
token_prefix = random.choice(['9', '8'])
remaining = token_length - 1 - 4
random_part = ''.join(str(random.randint(0, 9)) for _ in range(remaining))
return token_prefix + random_part + card_number[-4:]
def generate_3ds_code():
return ''.join(str(random.randint(0, 9)) for _ in range(6))
def generate_cards(iin_prefix, count, include_token=False, include_3ds=False,
include_biometric=False, include_dynamic_cvv=False, include_did=False):
card_type, length_range, default_cvv = determine_card_type(iin_prefix)
cards = []
for _ in range(count):
card_number, actual_length = generate_card_number(iin_prefix)
expiry = generate_expiry_date()
cvv_seed = random.randint(100000, 999999)
cvv_length = default_cvv
cvv = generate_dynamic_cvv(cvv_seed, cvv_length) if include_dynamic_cvv else generate_static_cvv(cvv_length)
name = generate_cardholder_name()
card = {
'type': card_type,
'number': card_number,
'length': actual_length,
'expiry': expiry,
'cvv': cvv,
'name': name
}
if include_dynamic_cvv:
card['cvv_note'] = f"Dynamic (seed: {cvv_seed}, changes hourly)"
if include_token:
card['token'] = generate_token(card_number)
if include_3ds:
card['3ds_code'] = generate_3ds_code()
if include_biometric:
card['biometric_token'] = generate_biometric_token()
if include_did:
card['did'] = generate_did()
cards.append(card)
return cards
# ====================== CARD GENERATOR GUI ======================
class CardGeneratorGUI(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Card Generator 2030")
self.resize(720, 640)
layout = QVBoxLayout(self)
self.iin_label = QLabel("IIN (6β8 digits):")
self.iin_input = QLineEdit()
self.iin_input.setPlaceholderText("Example: 411111")
self.count_label = QLabel("Number of cards:")
self.count_input = QSpinBox()
self.count_input.setMinimum(1)
self.count_input.setMaximum(200)
self.count_input.setValue(10)
self.token_cb = QCheckBox("Add tokens (digital wallets / stablecoins)")
self.threeds_cb = QCheckBox("Add 3DS codes")
self.bio_cb = QCheckBox("Add biometric tokens")
self.dyn_cvv_cb = QCheckBox("Add dynamic CVV")
self.did_cb = QCheckBox("Add DID (decentralized identity)")
self.generate_btn = QPushButton("Generate Cards")
self.generate_btn.clicked.connect(self.generate)
self.output = QTextEdit()
self.output.setReadOnly(True)
self.save_btn = QPushButton("Save to File")
self.save_btn.clicked.connect(self.save_result)
layout.addWidget(self.iin_label)
layout.addWidget(self.iin_input)
layout.addWidget(self.count_label)
layout.addWidget(self.count_input)
for cb in [self.token_cb, self.threeds_cb, self.bio_cb, self.dyn_cvv_cb, self.did_cb]:
layout.addWidget(cb)
layout.addWidget(self.generate_btn)
layout.addWidget(self.output)
layout.addWidget(self.save_btn)
def generate(self):
try:
iin_text = self.iin_input.text().strip()
if not iin_text:
QMessageBox.warning(self, "Error", "Please enter IIN")
return
if not iin_text.isdigit():
QMessageBox.warning(self, "Error", "IIN must contain only digits")
return
iin = int(iin_text)
if len(str(iin)) < 6 or len(str(iin)) > 8:
QMessageBox.warning(self, "Error", "IIN must be between 6 and 8 digits")
return
count = self.count_input.value()
cards = generate_cards(
iin, count,
include_token=self.token_cb.isChecked(),
include_3ds=self.threeds_cb.isChecked(),
include_biometric=self.bio_cb.isChecked(),
include_dynamic_cvv=self.dyn_cvv_cb.isChecked(),
include_did=self.did_cb.isChecked()
)
out = ""
for idx, card in enumerate(cards, 1):
out += f"Card {idx}\n"
out += f"Type: {card['type']}\n"
out += f"Number: {card['number']} (length: {card['length']})\n"
out += f"Expiry: {card['expiry']}\n"
out += f"CVV: {card['cvv']}"
if 'cvv_note' in card:
out += f" ({card['cvv_note']})"
out += "\n"
out += f"Name: {card['name']}\n"
if 'token' in card:
out += f"Token: {card['token']}\n"
if '3ds_code' in card:
out += f"3DS Code: {card['3ds_code']}\n"
if 'biometric_token' in card:
out += f"Biometric Token: {card['biometric_token']}\n"
if 'did' in card:
out += f"DID: {card['did']}\n"
out += "β" * 60 + "\n\n"
self.output.setPlainText(out)
except ValueError as ve:
QMessageBox.warning(self, "Input Error", str(ve))
except Exception as e:
QMessageBox.critical(self, "Error", f"{type(e).__name__}\n{str(e)}")
def save_result(self):
text = self.output.toPlainText().strip()
if not text:
QMessageBox.information(self, "Nothing to save", "Generate some cards first")
return
path, _ = QFileDialog.getSaveFileName(self, "Save As", "", "Text Files (*.txt);;All Files (*.*)")
if path:
try:
with open(path, "w", encoding="utf-8") as f:
f.write(text)
QMessageBox.information(self, "Success", "File saved successfully")
except Exception as e:
QMessageBox.critical(self, "Save Error", str(e))
# ====================== STRIPE VALIDATION TAB ======================
class StripeValidationTab(QWidget):
def __init__(self) -> None:
super().__init__()
self.secret_input = QLineEdit()
self.secret_input.setEchoMode(QLineEdit.EchoMode.Password)
self.secret_input.setPlaceholderText("sk_test_... (or use STRIPE_SECRET_KEY env)")
self.pm_input = QLineEdit()
self.pm_input.setPlaceholderText("pm_... (PaymentMethod ID)")
self.customer_input = QLineEdit()
self.customer_input.setPlaceholderText("cus_... (optional)")
self.output = QTextEdit()
self.output.setReadOnly(True)
run_button = QPushButton("Validate in Stripe")
run_button.clicked.connect(self.validate_payment_method)
form = QFormLayout()
form.addRow("Stripe Secret Key:", self.secret_input)
form.addRow("PaymentMethod ID:", self.pm_input)
form.addRow("Customer ID:", self.customer_input)
top = QHBoxLayout()
top.addLayout(form)
top.addWidget(run_button)
layout = QVBoxLayout(self)
layout.addLayout(top)
layout.addWidget(self.output)
def validate_payment_method(self) -> None:
key = self.secret_input.text().strip() or os.getenv("STRIPE_SECRET_KEY", "").strip()
pm_id = self.pm_input.text().strip()
customer_id: Optional[str] = self.customer_input.text().strip() or None
if not key:
QMessageBox.warning(self, "Stripe", "Please enter Stripe test secret key.")
return
if not pm_id.startswith("pm_"):
QMessageBox.warning(self, "Stripe", "PaymentMethod ID must start with 'pm_'")
return
try:
stripe.api_key = key
if customer_id:
stripe.PaymentMethod.attach(pm_id, customer=customer_id)
si = stripe.SetupIntent.create(
payment_method=pm_id,
customer=customer_id,
confirm=True,
payment_method_types=["card"],
usage="off_session",
)
self.output.setPlainText(
f"SetupIntent: {si.id}\n"
f"Status: {si.status}\n"
f"Customer: {si.customer or '-'}\n"
f"PaymentMethod: {si.payment_method or '-'}"
)
except Exception as exc:
QMessageBox.critical(self, "Stripe Error", str(exc))
# ====================== STRIPE CARD CHECKER TAB ======================
class StripeCardCheckerTab(QWidget):
def __init__(self) -> None:
super().__init__()
self.secret_input = QLineEdit()
self.secret_input.setEchoMode(QLineEdit.EchoMode.Password)
self.secret_input.setPlaceholderText("sk_test_...")
self.card_number = QLineEdit()
self.card_number.setPlaceholderText("4242424242424242")
self.expiry_input = QLineEdit()
self.expiry_input.setPlaceholderText("12/28")
self.cvc_input = QLineEdit()
self.cvc_input.setPlaceholderText("123")
self.cvc_input.setMaxLength(4)
self.cardholder_input = QLineEdit()
self.cardholder_input.setPlaceholderText("John Doe")
self.output = QTextEdit()
self.output.setReadOnly(True)
check_button = QPushButton("Check Card via Stripe")
check_button.clicked.connect(self.check_card)
form = QFormLayout()
form.addRow("Stripe Secret Key:", self.secret_input)
form.addRow("Card Number:", self.card_number)
form.addRow("Expiry (MM/YY):", self.expiry_input)
form.addRow("CVC:", self.cvc_input)
form.addRow("Cardholder Name:", self.cardholder_input)
layout = QVBoxLayout(self)
layout.addLayout(form)
layout.addWidget(check_button)
layout.addWidget(QLabel("Check Result:"))
layout.addWidget(self.output)
def check_card(self):
key = self.secret_input.text().strip() or os.getenv("STRIPE_SECRET_KEY", "").strip()
number = self.card_number.text().strip().replace(" ", "").replace("-", "")
expiry = self.expiry_input.text().strip()
cvc = self.cvc_input.text().strip()
name = self.cardholder_input.text().strip()
if not key:
QMessageBox.warning(self, "Error", "Please enter Stripe Secret Key")
return
if not number or len(number) < 13:
QMessageBox.warning(self, "Error", "Please enter a valid card number")
return
if not expiry or "/" not in expiry:
QMessageBox.warning(self, "Error", "Expiry must be in MM/YY format")
return
try:
mm, yy = map(int, expiry.split("/"))
if yy < 100:
yy += 2000
except ValueError:
QMessageBox.warning(self, "Error", "Invalid expiry date format")
return
try:
stripe.api_key = key
pm = stripe.PaymentMethod.create(
type="card",
card={
"number": number,
"exp_month": mm,
"exp_year": yy,
"cvc": cvc,
},
billing_details={"name": name} if name else None,
)
si = stripe.SetupIntent.create(
payment_method=pm.id,
confirm=True,
payment_method_types=["card"],
usage="off_session",
)
result = "β
Card check passed!\n\n"
result += f"PaymentMethod: {pm.id}\n"
result += f"SetupIntent: {si.id}\n"
result += f"Status: {si.status}\n"
if hasattr(pm, 'card'):
result += f"Brand: {pm.card.brand}\n"
result += f"Last 4: {pm.card.last4}\n"
result += f"CVC Check: {getattr(pm.card.checks, 'cvc_check', 'N/A')}\n"
if si.status == "succeeded":
result += "\nCard successfully verified"
elif si.status == "requires_action":
result += "\nAdditional action required (e.g. 3DS)"
self.output.setPlainText(result)
except stripe.error.CardError as e:
self.output.setPlainText(f"β Card Error:\n{e.user_message}\nCode: {e.code}")
except stripe.error.StripeError as e:
self.output.setPlainText(f"β Stripe Error:\n{str(e)}")
except Exception as e:
QMessageBox.critical(self, "Unknown Error", str(e))
self.output.setPlainText(f"Error: {str(e)}")
# ====================== MAIN UNIFIED WINDOW ======================
class UnifiedWindow(QWidget):
def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Unified App β Card Generator + Stripe Tools")
self.resize(1020, 760)
tabs = QTabWidget()
tabs.addTab(CardGeneratorGUI(), "Card Generator")
tabs.addTab(StripeValidationTab(), "Stripe Validation (pm_)")
tabs.addTab(StripeCardCheckerTab(), "Stripe Card Checker")
layout = QVBoxLayout(self)
layout.addWidget(tabs)
def main() -> None:
app = QApplication(sys.argv)
window = UnifiedWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
To run:
- Install Python 3.9+ from python.org
- Open terminal/cmd β
pip install pyqt6 - Save the script β double-click to run
π Alternatives β Online Generators
If you donβt want to run Python locally, these web tools do similar things:
| Tool | What It Does | Link |
|---|---|---|
| Namso Gen | The OG. BIN-based generation, Luhn-valid, bulk export | namso.net |
| Multi-CC Gen | Multiple BINs at once, simpler interface | multi-cc-gen.web.app |
| BinCodes Generator | Generator + validator + BIN lookup combined | bincodes.com |
| TheNamsoGen | Bulk generation, CSV/JSON export, region-specific data | thenamsogen.com |
Why the desktop tool is still better:
- Works offline (no internet required after install)
- No rate limits or captchas
- Advanced features (tokens, 3DS, biometrics, DIDs) not available elsewhere
- Your data stays on your machine
π§ GitHub Repos β Build Your Own
Want to modify or integrate this into your workflow? Here are open-source alternatives:
| Repo | Language | Stars | What It Does |
|---|---|---|---|
| python-cc-num-gen | Python | β | Simple class-based generator, Visa/MC/Amex/Discover |
| CreditCardGenerator | Swift | β | CLI tool with batch generation + file export |
| credit-card-generator (Node) | Node.js | β | npm package, Triple-DES CVV generation |
| CCGenerator | Python | β | Interactive CLI with performance modes |
| reNamso | HTML/JS | 31β | Modern rewrite of Namso, no jQuery, clean code |
π BIN Lookup APIs β Know Your Numbers
Before generating, you might want to look up what a BIN actually represents:
| API | Free Tier | What It Returns |
|---|---|---|
| binlist.net | 5/hour | Card brand, type, bank, country |
| HandyAPI BIN | Free with key | Updated weekly, 6-8 digit support |
| BinCodes API | Limited | XML/JSON, card validation |
| BINTable | 100/month | REST API, detailed issuer data |
| API-Ninjas BIN | Free tier | Brand, type, country, validity check |
β Common Questions
Q: Will these cards work for real purchases?
No. They pass Luhn validation (the checksum check) but fail at the bank level. Thereβs no account behind them.
Q: Is this legal?
Generating test numbers = yes. Using them for fraud = no. These are for testing payment forms, not stealing.
Q: Whatβs a BIN/IIN?
The first 6-8 digits of a card number. They identify the card network (Visa starts with 4, Mastercard with 5, etc.) and the issuing bank.
Q: Why do some Visa cards have 19 digits?
Visa supports both 16 and 19-digit numbers. The generator handles this automatically.
Q: Whatβs a dynamic CVV?
Some modern cards change their CVV periodically (often hourly) for security. The generator simulates this with a seed-based hash.
Q: Whatβs a DID?
Decentralized Identifier β a Web3/blockchain identity format. Useful for testing systems that use verifiable credentials.
New to BINs and how they actually work? This breaks it down from zero:
Working BIN Masterclass
One script. Offline. More features than Namso. No excuses.




!