FastAPI Backend Integration
Build a complete payment system with FastAPI backend and MetaMask frontend. Customers pay directly from their wallet - no private keys on your server!
🔐 Security First: This architecture ensures private keys NEVER leave the customer's wallet. All transactions are signed by MetaMask on the client side.
Architecture Overview
How It Works
Secure payment flow without server-side private keys
- Backend (FastAPI): Creates payment requests and verifies transactions
- Frontend (HTML + MetaMask): Customer connects wallet and signs transactions
- Blockchain: Smart contract processes payment and splits funds automatically
Key Benefits:
- ✅ No private keys on your server
- ✅ Customers control their own wallets
- ✅ Works with hardware wallets (Ledger, Trezor)
- ✅ Simple HTML frontend - no React/Vue needed
Project Setup
1
Install Dependencies
Create a requirements.txt file:
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
zynpay>=0.1.0
python-dotenv>=1.0.0Install dependencies:
pip install -r requirements.txt2
Project Structure
Create the following directory structure:
your-project/
├── app.py # FastAPI backend
├── requirements.txt # Python dependencies
├── .env # Environment variables
└── static/
└── index.html # Frontend HTML file3
Environment Variables
Create a .env file with your merchant wallet:
MERCHANT_WALLET=0xYourMerchantWalletAddress⚠️ Never commit .env to git! Add it to .gitignore
Backend Implementation
FastAPI Backend (app.py)
Complete backend implementation
#!/usr/bin/env python3
"""
FastAPI Web App Example with MetaMask Integration
This example shows how to:
1. Create a backend API to generate payment requests
2. Frontend uses MetaMask to sign transactions (no private keys on server!)
3. Verify payments on-chain
SECURITY: Private keys NEVER leave the customer's wallet!
"""
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import os
from dotenv import load_dotenv
from zynpay import ZynPayClient, Environment
load_dotenv()
app = FastAPI(title="ZynPay Payment API", version="1.0.0")
# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize ZynPay client
MERCHANT_WALLET = os.getenv('MERCHANT_WALLET')
client = ZynPayClient(
merchant_wallet=MERCHANT_WALLET,
environment=Environment.TESTNET,
default_chain='base'
)
# Request/Response models
class CreatePaymentRequest(BaseModel):
amount: float
chain: str = 'base'
class VerifyPaymentRequest(BaseModel):
tx_hash: str
payment_id: str
# Serve static files
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def index():
"""Serve the frontend"""
return FileResponse('static/index.html')
@app.post("/api/payment/create")
async def create_payment(request: CreatePaymentRequest):
"""Create a payment request"""
try:
if request.amount <= 0:
raise HTTPException(status_code=400, detail="Invalid amount")
# Create payment
payment = client.payments.create(amount=request.amount, chain=request.chain)
return {
'success': True,
'payment': {
'id': payment.id,
'amount': payment.amount,
'chain': payment.chain,
'router_address': payment.router_address,
'merchant_wallet': client.merchant_wallet,
'usdc_address': payment.usdc_address,
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/balance/{address}")
async def check_balance(address: str, chain: str = 'base'):
"""Check wallet balance"""
try:
balance = client.web3.get_balance(address, chain=chain)
return {
'success': True,
'balance': {
'usdc': balance.usdc,
'eth': balance.native,
'has_usdc': balance.has_usdc,
'has_gas': balance.has_gas,
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/payment/verify")
async def verify_payment(request: VerifyPaymentRequest):
"""Verify a payment transaction"""
try:
if not request.tx_hash:
raise HTTPException(status_code=400, detail="Transaction hash required")
# In a real app, you'd verify the transaction on-chain
# and update your database
return {
'success': True,
'verified': True,
'message': 'Payment verified on blockchain'
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/config")
async def get_config():
"""Get network configuration"""
return {
'merchant_wallet': client.merchant_wallet,
'environment': client.environment,
'supported_chains': client.get_supported_chains(),
}
if __name__ == '__main__':
import uvicorn
print("🚀 Starting ZynPay FastAPI App...")
print(f"📝 Merchant: {MERCHANT_WALLET}")
print(f"🌐 Environment: {client.environment}")
print("\n✅ Server running at: http://localhost:8080")
print("💡 Open in browser to test with MetaMask!")
uvicorn.run(app, host="0.0.0.0", port=8080)Frontend Implementation
HTML Frontend (static/index.html)
Complete frontend with MetaMask integration
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ZynPay - MetaMask Payment Demo</title>
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
<div class="container">
<h1>💳 ZynPay Payment</h1>
<p class="subtitle">Pay with MetaMask - No account needed!</p>
<div id="status" class="status info">
📱 Connect MetaMask to get started
</div>
<div class="wallet-info">
<strong>💼 Merchant:</strong>
<span id="merchantAddress">Loading...</span>
</div>
<div id="walletInfo" style="display: none" class="wallet-info">
<strong>🔗 Your Wallet:</strong> <span id="walletAddress"></span>
</div>
<div id="balanceSection" style="display: none" class="balance">
<div class="balance-item">
<span>USDC Balance:</span>
<strong id="usdcBalance">--</strong>
</div>
<div class="balance-item">
<span>ETH Balance:</span>
<strong id="ethBalance">--</strong>
</div>
</div>
<div class="form-group">
<label for="amount">Payment Amount (USDC)</label>
<input type="number" id="amount" value="5.00" min="0.01" step="0.01" />
</div>
<button id="connectBtn" onclick="connectWallet()">
Connect MetaMask
</button>
<button id="payBtn" style="display: none" onclick="makePayment()" disabled>
Pay with MetaMask
</button>
<div class="steps">
<div class="step" id="step1">Connect wallet</div>
<div class="step" id="step2">Create payment</div>
<div class="step" id="step3">Approve USDC</div>
<div class="step" id="step4">Execute payment</div>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>Running the Application
1
Start the Backend
# Make sure you have .env file with MERCHANT_WALLET
python app.pyThe server will start on http://localhost:8080
2
Open in Browser
Navigate to http://localhost:8080 in a browser with MetaMask installed
3
Test the Payment Flow
- Click "Connect MetaMask"
- Approve the connection in MetaMask
- Enter a payment amount
- Click "Pay with MetaMask"
- Approve USDC spending in MetaMask
- Confirm the payment transaction
- Wait for confirmation - payment complete! ✅
API Endpoints
POST /api/payment/create
Create a new payment request
{
"amount": 10.00,
"chain": "base"
}GET /api/balance/{address}
Check wallet balance
Query parameters:
?chain=basePOST /api/payment/verify
Verify a payment transaction
{
"tx_hash": "0x...",
"payment_id": "..."
}GET /api/config
Get network configuration
Returns merchant wallet, environment, and supported chains
Security Best Practices
✅ What This Architecture Does Right
- Private keys never leave the customer's wallet
- All transactions are signed client-side by MetaMask
- Backend only creates payment requests, doesn't execute them
- No sensitive data stored on server
- Works with hardware wallets (Ledger, Trezor)
⚠️ Production Considerations
- Add rate limiting to API endpoints
- Implement proper CORS policies (don't use allow_origins=["*"])
- Add authentication/authorization if needed
- Store payment records in a database
- Add proper error handling and logging
- Use environment-specific configurations