Good Carder
Professional
- Messages
- 753
- Reaction score
- 493
- Points
- 63
Introduction: Why does a carder need its own payment gateway?
In this article, I'll show you how to set up a simple proxy gateway using Python and FastAPI that logs all incoming requests and redirects them to a real processor if necessary. The code is intended for use in isolated test networks only.Part 1. Architecture of a Fake Payment Gateway
1.1 Two approaches: emulation vs. proxying
| Approach | Description | Pros | Cons |
|---|---|---|---|
| Full emulation | The gateway automatically generates responses, simulating a real processor (always returns "success" or "failure" according to a template) | Simplicity, does not require access to a real API | Does not work with real anti-fraud behavior scenarios, the answers may not match the real ones |
| Proxying (MITM) | The gateway receives the request, logs it, then forwards it to the real processor (Stripe, Adyen) and returns a response to the client. | Complete realism, transactions actually go through processing | Requires valid API keys, can be expensive, and may violate terms of service. |
For training and testing purposes, full emulation with predefined responses is usually sufficient. For behavioral research, it's better to use MITM proxies, but only on your own test cards.
1.2. System Components
Code:
[Client (browser/script)]
│
▼
┌──────────────────────────────────────┐
Fake gateway (FastAPI)
- Request acceptance
- Logging (IP, headers, request body)
- Format validation
- Mode selection (emulation/proxy)
└─────────────────────────────────┘
│
├──► [Logs to DB/File]
│
└──► (optional) [Real Stripe/Adyen Gateway]
│
▼
Real Answer
1.3. Selection of technologies
- FastAPI (Python) - asynchronous, easy to use, automatic documentation.
- SQLite/PostgreSQL – for storing logs.
- Docker - for isolated deployment.
- ngrok / localtunnel - for temporary public access during training (for testing purposes only).
Part 2. Creating an Emulated Gateway (Stripe-like)
2.1. Stripe API Basic Structure
Stripe uses a REST API with endpoints:- POST /v1/payment_intents — creating a payment intent.
- POST /v1/payment_intents/:id/confirm — confirmation.
- POST /v1/setup_intents - creating a card to save.
The emulator will simulate only the required endpoints with predefined responses.
2.2. FastAPI implementation
Python:
# fake_gateway.py
import uuid
import json
import logging
from datetime import datetime
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import uvicorn
# Logging setup
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="Fake Payment Gateway (Educational Purposes Only)")
# Storage of created PaymentIntents (in memory, for emulation)
payment_intents = {}
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Logging all incoming requests"""
body = await request.body()
logger.info(f"=== Incoming request ===")
logger.info(f"Method: {request.method}")
logger.info(f"URL: {request.url}")
logger.info(f"Headers: {dict(request.headers)}")
logger.info(f"Body: {body.decode('utf-8')[:1000]}") # first 1000 characters only
# Save to a file or database if necessary
with open("gateway_logs.json", "a") as f:
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"method": request.method,
"url": str(request.url),
"headers": dict(request.headers),
"body": body.decode('utf-8', errors='ignore')
}
f.write(json.dumps(log_entry) + "\n")
response = await call_next(request)
return response
@app.post("/v1/payment_intents")
async def create_payment_intent(request: Request):
"""Emulate PaymentIntent creation (Stripe API)"""
data = await request.form()
# or JSON, depending on how it arrives
amount = data.get("amount") # in cents
currency = data.get("currency", "usd")
payment_method_types = data.getlist("payment_method_types[]") or ["card"]
# Generate ID
intent_id = f"pi_{uuid.uuid4().hex[:16]}"
client_secret = f"{intent_id}_secret_{uuid.uuid4().hex[:16]}"
# Emulate a successful response (or you can add logic for specific cards)
payment_intents[intent_id] = {
"id": intent_id,
"amount": int(amount) if amount else None,
"currency": currency,
"status": "requires_payment_method",
"client_secret": client_secret
}
response_data = {
"id": intent_id,
"object": "payment_intent",
"amount": int(amount) if amount else None,
"currency": currency,
"status": "requires_payment_method",
"client_secret": client_secret,
"payment_method_types": payment_method_types
}
return JSONResponse(content=response_data, status_code=200)
@app.post("/v1/payment_intents/{intent_id}/confirm")
async def confirm_payment_intent(intent_id: str, request: Request):
"""Payment confirmation emulation"""
data = await request.form()
payment_method_data = data.get("payment_method_data")
# Simulate card validation
# In a real emulator, you can add a check for the "test" card
if intent_id not in payment_intents:
raise HTTPException(status_code=404, detail="PaymentIntent not found")
# Always successful for the demo, but you can add error logic
payment_intents[intent_id]["status"] = "succeeded"
response_data = {
"id": intent_id,
"object": "payment_intent",
"status": "succeeded",
"charges": {
"data": [{
"id": f"ch_{uuid.uuid4().hex[:16]}",
"amount": payment_intents[intent_id]["amount"],
"status": "succeeded"
}]
}
}
return JSONResponse(content=response_data, status_code=200)
@app.post("/v1/setup_intents")
async def create_setup_intent(request: Request):
"""SetupIntent emulation (to save the card without payment)"""
data = await request.form()
intent_id = f"seti_{uuid.uuid4().hex[:16]}"
client_secret = f"{intent_id}_secret_{uuid.uuid4().hex[:16]}"
response_data = {
"id": intent_id,
"object": "setup_intent",
"status": "requires_payment_method",
"client_secret": client_secret
}
return JSONResponse(content=response_data, status_code=200)
@app.post("/v1/setup_intents/{intent_id}/confirm")
async def confirm_setup_intent(intent_id: str, request: Request):
"""SetupIntent Confirmation"""
# Always successful
response_data = {
"id": intent_id,
"object": "setup_intent",
"status": "succeeded",
"payment_method": f"pm_{uuid.uuid4().hex[:16]}"
}
return JSONResponse(content=response_data, status_code=200)
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
2.3. Customizing the Appearance: Simulating a Payment Page
For training purposes, you can set up a simple HTML page that replicates the Stripe Checkout design:
HTML:
<!-- fake_checkout.html -->
<!DOCTYPE html>
<html>
<head>
<title>Secure Payment</title>
<style>
body { font-family: Arial; max-width: 500px; margin: auto; padding: 20px; }
.card-field { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
button { background: #635bff; color: white; border: none; padding: 12px; width: 100%; }
</style>
</head>
<body>
<h2>Complete your payment</h2>
<form id="payment-form">
<div class="card-field">
<label>Card number</label>
<input type="text" id="card-number" placeholder="4242 4242 4242 4242">
</div>
<div class="card-field">
<label>Expiry (MM/YY)</label>
<input type="text" id="expiry" placeholder="12/28">
</div>
<div class="card-field">
<label>CVC</label>
<input type="text" id="cvc" placeholder="123">
</div>
<button type="submit">Pay $49.99</button>
</form>
<div id="result"></div>
<script>
// The script sends data to /api/charge
document.getElementById('payment-form').addEventListener('submit', async (e) => {
e.preventDefault();
const cardNumber = document.getElementById('card-number').value;
const expiry = document.getElementById('expiry').value;
const cvc = document.getElementById('cvc').value;
const response = await fetch('/api/charge', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({cardNumber, expiry, cvc, amount: 4999})
});
const result = await response.json();
document.getElementById('result').innerText = result.message;
});
</script>
</body>
</html>
Part 3. Proxying requests to a real gateway (MITM)
3.1. Proxy architecture
Code:
Client → Fake Gateway (logging) → Forward to the real Stripe API → Response back to the client
3.2. Implementing a Proxy Using httpx
Python:
# proxy_gateway.py
import httpx
from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
REAL_STRIPE_API = "https://api.stripe.com/v1"
STRIPE_SECRET_KEY = "sk_test_..." # For testing purposes ONLY!
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(request: Request, path: str):
# Generate a URL to the real API
url = f"{REAL_STRIPE_API}/{path}"
# Get the request body
body = await request.body()
# Log the incoming request
logger.info(f"Proxying {request.method} {url}")
logger.info(f"Headers: {dict(request.headers)}")
logger.info(f"Body: {body.decode('utf-8')[:500]}")
# Prepare headers (remove host, add authorization)
headers = dict(request.headers)
headers["Authorization"] = f"Bearer {STRIPE_SECRET_KEY}"
# Remove potentially conflicting headers
headers.pop("host", None)
headers.pop("content-length", None)
# Send request to the real Stripe
async with httpx.AsyncClient() as client:
resp = await client.request(
method=request.method,
url=url,
headers=headers,
content=body,
timeout=30.0
)
# Log the response
logger.info(f"Response status: {resp.status_code}")
logger.info(f"Response body: {resp.text[:500]}")
# Return the response to the client
return Response(
content=resp.content,
status_code=resp.status_code,
headers=dict(resp.headers)
)
3.3. Secure Proxying: Limitations
- Use real (live) Stripe keys in proxies. Even for tests, use test keys (sk_test_...), which don't process real money.
- Limit the IP addresses from which requests are allowed (for example, localhost only).
- Add authentication (API key or Basic Auth) to access the proxy to prevent random carders from using it.
Part 4. Ethical aspects
4.1. Data protection
- Never keep logs longer than necessary for analysis.
- Anonymize logs before saving (remove IP addresses if they are not needed).
- Use log encryption.
Part 5: Comprehensive Deployment Checklist
- Define the goal: training, research, security testing.
- Select the mode: full emulation or proxy. Emulation is safer for learning.
- Obtain the necessary permissions. For proxying to a real API, you must obtain written confirmation from the key owner.
- Set up logging with rotation and protection.
- Protect the gateway from unauthorized access (firewall, VPN, authentication).
- Conduct a test using test cards (e.g. 4242 4242 4242 4242).
- Once completed, delete all logs and close access.
Conclusion: Knowing your enemy is the foundation of defense
Creating a fake payment gateway is a powerful tool in the hands of a carder. It allows you to understand how data is intercepted, which fields are most attractive to attackers, and how to improve phishing detection.However, this tool carries enormous risks if used improperly. Responsibility for compliance with the law rests solely with you. Use this knowledge to successfully card.
A quick one-line reminder:
"Set up a gateway — obtain written permission. Log — encrypt. Do not proxy to real keys without a test mode. Security is built on knowledge of attacks, but knowledge does not give the right to attack."
