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!

Architecture Overview

How It Works
Secure payment flow without server-side private keys
  1. Backend (FastAPI): Creates payment requests and verifies transactions
  2. Frontend (HTML + MetaMask): Customer connects wallet and signs transactions
  3. 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.0

Install dependencies:

pip install -r requirements.txt
2

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 file
3

Environment Variables

Create a .env file with your merchant wallet:

MERCHANT_WALLET=0xYourMerchantWalletAddress

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.py

The 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

  1. Click "Connect MetaMask"
  2. Approve the connection in MetaMask
  3. Enter a payment amount
  4. Click "Pay with MetaMask"
  5. Approve USDC spending in MetaMask
  6. Confirm the payment transaction
  7. 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=base
POST /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