Files
AutonetSellCar/backend/app/api/cc.py
AutonetSellCar Deploy 1f0dcb1ddb Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript
- Backend: FastAPI with SQLAlchemy
- Agent: Carmodoo sync agent
- Deployment: Docker Compose based staging/production setup
- Scripts: Automated deployment with rollback support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:24:39 +09:00

887 lines
30 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Request, Header
from sqlalchemy.orm import Session
from sqlalchemy import desc
from typing import List, Optional
from pydantic import BaseModel
from datetime import datetime
import stripe
import logging
from ..database import get_db
from ..models import User, Car, CarView, PerformanceCheckView, ChargeHistory, CarPerformanceCheck, CCPackage, DEFAULT_CC_PACKAGES
from ..models.settings import SystemSettings
from ..models.user import PaymentSettings
from ..schemas import UserResponse, CarViewResponse, PurchaseViewRequest
from .auth import get_current_user, get_current_admin_user, get_current_user_optional
from .referral import create_referral_reward
from .carmodoo import CarmodooClient, capture_performance_check_pdf
from .notification import notify_system
from ..config import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
# Configure Stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
router = APIRouter(prefix="/cc", tags=["cc"])
class ChargeRequest(BaseModel):
amount: int
currency: str = "USD"
payment_method: str = "card"
transaction_id: Optional[str] = None # For crypto payments
wallet_address: Optional[str] = None # User's wallet for refunds
class USDCChargeRequest(BaseModel):
amount_usdc: int
transaction_hash: str
wallet_address: str
network: str = "Polygon"
class ChargeHistoryResponse(BaseModel):
id: int
amount_usd: int
cc_amount: int
payment_method: str
status: str
created_at: str
class Config:
from_attributes = True
@router.get("/balance")
def get_cc_balance(current_user: User = Depends(get_current_user)):
"""Get current user's CC balance"""
return {"cc_balance": current_user.cc_balance or 0}
@router.get("/views", response_model=List[CarViewResponse])
def get_purchased_views(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get list of cars the user has paid to view"""
views = db.query(CarView).filter(CarView.user_id == current_user.id).all()
return views
@router.get("/views/car-ids")
def get_purchased_car_ids(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get list of car IDs the user has paid to view (for quick lookup)"""
views = db.query(CarView.car_id).filter(CarView.user_id == current_user.id).all()
return {"car_ids": [v[0] for v in views]}
@router.post("/purchase-view")
def purchase_car_view(
request: PurchaseViewRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Purchase access to view full car details (costs 1 CC)"""
car_id = request.car_id
# Check if car exists
car = db.query(Car).filter(Car.id == car_id).first()
if not car:
raise HTTPException(status_code=404, detail="Car not found")
# Check if already purchased
existing_view = db.query(CarView).filter(
CarView.user_id == current_user.id,
CarView.car_id == car_id
).first()
if existing_view:
return {"message": "Already purchased", "cc_balance": current_user.cc_balance}
# Check if user has enough CC
if (current_user.cc_balance or 0) < 1:
raise HTTPException(
status_code=400,
detail="Insufficient CC balance. You need 1 CC to view full car details."
)
# Deduct CC and create view record
current_user.cc_balance = (current_user.cc_balance or 0) - 1
car_view = CarView(
user_id=current_user.id,
car_id=car_id,
cc_paid=1
)
db.add(car_view)
db.commit()
return {
"message": "Purchase successful",
"cc_balance": current_user.cc_balance,
"car_id": car_id
}
@router.get("/check-view/{car_id}")
def check_car_view(
car_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Check if user has purchased view access for a specific car"""
existing_view = db.query(CarView).filter(
CarView.user_id == current_user.id,
CarView.car_id == car_id
).first()
return {
"has_access": existing_view is not None,
"cc_balance": current_user.cc_balance or 0
}
PERFORMANCE_CHECK_COST = 0.1 # 0.1 CC for performance check view
@router.post("/purchase-performance-check")
async def purchase_performance_check_view(
request: PurchaseViewRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Purchase access to view performance check (costs 0.1 CC)"""
car_id = request.car_id
# Check if car exists
car = db.query(Car).filter(Car.id == car_id).first()
if not car:
raise HTTPException(status_code=404, detail="Car not found")
# Check if performance check record exists
perf_check = db.query(CarPerformanceCheck).filter(
CarPerformanceCheck.car_id == car_id
).first()
# If no performance check record, try to fetch from Carmodoo
if not perf_check:
try:
carmodoo_client = CarmodooClient()
check_num = car.check_num or ""
# Try to get check_num if not available
if not check_num:
check_num = await carmodoo_client.get_car_check_num(car.source_id, car.source_key or "")
if check_num:
# Fetch performance check data
perf_result = await carmodoo_client.get_performance_check(car.source_id, car.source_key or "", check_num)
if perf_result.get("found") and perf_result.get("data"):
perf_data = perf_result["data"]
# Create CarPerformanceCheck record
perf_check = CarPerformanceCheck(
car_id=car.id,
check_number=perf_data.get("check_number") or check_num,
check_date=perf_data.get("check_date"),
valid_until=perf_data.get("valid_until"),
first_registration=perf_data.get("first_registration"),
mileage=perf_data.get("mileage"),
mileage_status=perf_data.get("mileage_status"),
seize_count=perf_data.get("seize_count", 0),
collateral_count=perf_data.get("collateral_count", 0),
is_flood_damaged=perf_data.get("is_flood_damaged", False),
is_fire_damaged=perf_data.get("is_fire_damaged", False),
is_total_loss=perf_data.get("is_total_loss", False),
engine_status=perf_data.get("engine_status"),
transmission_status=perf_data.get("transmission_status"),
power_delivery_status=perf_data.get("power_delivery_status"),
raw_data=perf_data,
raw_html=perf_result.get("raw_html", "")[:50000],
)
db.add(perf_check)
db.flush()
# Capture PDF
try:
pdf_path = await capture_performance_check_pdf(perf_check.check_number, car.id)
if pdf_path:
perf_check.pdf_path = pdf_path
except Exception as pdf_error:
logger.warning(f"PDF capture failed: {pdf_error}")
db.commit()
db.refresh(perf_check)
except Exception as e:
logger.error(f"Failed to fetch performance check: {e}")
if not perf_check:
raise HTTPException(status_code=404, detail="Performance check not available for this car")
# Check if already purchased
existing_view = db.query(PerformanceCheckView).filter(
PerformanceCheckView.user_id == current_user.id,
PerformanceCheckView.car_id == car_id
).first()
if existing_view:
return {"message": "Already purchased", "cc_balance": current_user.cc_balance}
# Check if user has enough CC
if (current_user.cc_balance or 0) < PERFORMANCE_CHECK_COST:
raise HTTPException(
status_code=400,
detail=f"Insufficient CC balance. You need {PERFORMANCE_CHECK_COST} CC to view performance check."
)
# Deduct CC and create view record
current_user.cc_balance = (current_user.cc_balance or 0) - PERFORMANCE_CHECK_COST
perf_view = PerformanceCheckView(
user_id=current_user.id,
car_id=car_id,
cc_paid=PERFORMANCE_CHECK_COST
)
db.add(perf_view)
db.commit()
return {
"message": "Purchase successful",
"cc_balance": current_user.cc_balance,
"car_id": car_id
}
@router.get("/check-performance-check/{car_id}")
def check_performance_check_view(
car_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Check if user has purchased performance check view for a specific car"""
# Check if performance check exists for this car
perf_check = db.query(CarPerformanceCheck).filter(
CarPerformanceCheck.car_id == car_id
).first()
# Check 1: Purchased performance check (0.1 CC)
existing_perf_view = db.query(PerformanceCheckView).filter(
PerformanceCheckView.user_id == current_user.id,
PerformanceCheckView.car_id == car_id
).first()
# Check 2: Purchased full car view (1 CC) -> performance check included free
existing_car_view = db.query(CarView).filter(
CarView.user_id == current_user.id,
CarView.car_id == car_id
).first()
has_access = (existing_perf_view is not None) or (existing_car_view is not None)
return {
"has_access": has_access,
"has_performance_check": perf_check is not None,
"cc_balance": current_user.cc_balance or 0,
"cost": PERFORMANCE_CHECK_COST,
"included_in_car_view": existing_car_view is not None # True if already purchased car view
}
@router.get("/payment-info")
def get_payment_info():
"""Get payment information including USDC wallet address"""
return {
"usdc_wallet_address": PaymentSettings.USDC_WALLET_ADDRESS,
"usdc_network": PaymentSettings.USDC_NETWORK,
"min_charge_usd": PaymentSettings.MIN_CHARGE_USD,
"max_charge_usd": PaymentSettings.MAX_CHARGE_USD,
"supported_currencies": PaymentSettings.SUPPORTED_CURRENCIES,
"supported_methods": PaymentSettings.SUPPORTED_METHODS,
"rate": "1 USD = 1 CC",
}
@router.post("/charge")
def charge_cc(
request: ChargeRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create a charge request (for card or bank transfer - requires admin verification)"""
# Validate amount
if request.amount < PaymentSettings.MIN_CHARGE_USD:
raise HTTPException(status_code=400, detail=f"Minimum charge amount is ${PaymentSettings.MIN_CHARGE_USD}")
if request.amount > PaymentSettings.MAX_CHARGE_USD:
raise HTTPException(status_code=400, detail=f"Maximum charge amount is ${PaymentSettings.MAX_CHARGE_USD}")
# Calculate CC amount (1 USD = 1 CC)
cc_amount = request.amount
# Determine status based on payment method
# Card payments would go through a payment gateway (not implemented yet)
# USDC and bank transfers require manual verification
status = "pending" if request.payment_method in ["usdc", "bank_transfer"] else "pending"
# Create charge history record
charge_record = ChargeHistory(
user_id=current_user.id,
amount=request.amount,
amount_usd=request.amount, # Backwards compatibility
cc_amount=cc_amount,
currency=request.currency,
payment_method=request.payment_method,
transaction_id=request.transaction_id,
wallet_address=request.wallet_address,
status=status
)
db.add(charge_record)
db.commit()
db.refresh(charge_record)
return {
"message": "Charge request created" if status == "pending" else "Charge successful",
"charge_id": charge_record.id,
"amount": request.amount,
"currency": request.currency,
"cc_amount": cc_amount,
"status": status,
"payment_info": {
"usdc_wallet": PaymentSettings.USDC_WALLET_ADDRESS if request.payment_method == "usdc" else None,
"network": PaymentSettings.USDC_NETWORK if request.payment_method == "usdc" else None,
}
}
@router.post("/charge/usdc")
def charge_cc_usdc(
request: USDCChargeRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create USDC charge request with transaction hash"""
# Validate amount
if request.amount_usdc < PaymentSettings.MIN_CHARGE_USD:
raise HTTPException(status_code=400, detail=f"Minimum charge amount is {PaymentSettings.MIN_CHARGE_USD} USDC")
if request.amount_usdc > PaymentSettings.MAX_CHARGE_USD:
raise HTTPException(status_code=400, detail=f"Maximum charge amount is {PaymentSettings.MAX_CHARGE_USD} USDC")
# Check for duplicate transaction
existing = db.query(ChargeHistory).filter(
ChargeHistory.transaction_id == request.transaction_hash
).first()
if existing:
raise HTTPException(status_code=400, detail="This transaction has already been submitted")
# Create pending charge record
charge_record = ChargeHistory(
user_id=current_user.id,
amount=request.amount_usdc,
amount_usd=request.amount_usdc,
cc_amount=request.amount_usdc,
currency="USDC",
payment_method="usdc",
transaction_id=request.transaction_hash,
wallet_address=request.wallet_address,
status="pending"
)
db.add(charge_record)
db.commit()
db.refresh(charge_record)
return {
"message": "USDC payment submitted for verification",
"charge_id": charge_record.id,
"amount_usdc": request.amount_usdc,
"cc_amount": request.amount_usdc,
"status": "pending",
"transaction_hash": request.transaction_hash
}
@router.get("/charge-history")
def get_charge_history(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get user's charge history"""
history = db.query(ChargeHistory).filter(
ChargeHistory.user_id == current_user.id
).order_by(desc(ChargeHistory.created_at)).limit(50).all()
return [
{
"id": h.id,
"amount": h.amount or h.amount_usd,
"amount_usd": h.amount_usd,
"currency": h.currency or "USD",
"cc_amount": h.cc_amount,
"payment_method": h.payment_method,
"transaction_id": h.transaction_id,
"status": h.status,
"created_at": h.created_at.isoformat() if h.created_at else None
}
for h in history
]
# Admin endpoints for payment verification
@router.get("/admin/pending")
def admin_get_pending_payments(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_user)
):
"""Get all pending payment requests (Admin only)"""
pending = db.query(ChargeHistory).filter(
ChargeHistory.status == "pending"
).order_by(desc(ChargeHistory.created_at)).all()
return [
{
"id": h.id,
"user_id": h.user_id,
"user_email": h.user.email if h.user else None,
"user_name": h.user.name if h.user else None,
"amount": h.amount or h.amount_usd,
"currency": h.currency or "USD",
"cc_amount": h.cc_amount,
"payment_method": h.payment_method,
"transaction_id": h.transaction_id,
"wallet_address": h.wallet_address,
"status": h.status,
"created_at": h.created_at.isoformat() if h.created_at else None
}
for h in pending
]
@router.get("/admin/all")
def admin_get_all_payments(
status: str = None,
page: int = 1,
page_size: int = 20,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_user)
):
"""Get all payment records with optional status filter (Admin only)"""
query = db.query(ChargeHistory)
if status:
query = query.filter(ChargeHistory.status == status)
total = query.count()
payments = query.order_by(desc(ChargeHistory.created_at)).offset((page - 1) * page_size).limit(page_size).all()
return {
"payments": [
{
"id": h.id,
"user_id": h.user_id,
"user_email": h.user.email if h.user else None,
"user_name": h.user.name if h.user else None,
"amount": h.amount or h.amount_usd,
"currency": h.currency or "USD",
"cc_amount": h.cc_amount,
"payment_method": h.payment_method,
"transaction_id": h.transaction_id,
"wallet_address": h.wallet_address,
"admin_note": h.admin_note,
"status": h.status,
"verified_at": h.verified_at.isoformat() if h.verified_at else None,
"created_at": h.created_at.isoformat() if h.created_at else None
}
for h in payments
],
"total": total,
"page": page,
"page_size": page_size
}
@router.put("/admin/{charge_id}/verify")
def admin_verify_payment(
charge_id: int,
approved: bool,
admin_note: str = None,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_user)
):
"""Verify and approve/reject a pending payment (Admin only)"""
charge = db.query(ChargeHistory).filter(ChargeHistory.id == charge_id).first()
if not charge:
raise HTTPException(status_code=404, detail="Charge record not found")
if charge.status != "pending":
raise HTTPException(status_code=400, detail=f"Charge is already {charge.status}")
if approved:
charge.status = "completed"
charge.verified_at = datetime.utcnow()
charge.verified_by = current_user.id
charge.admin_note = admin_note
# Credit CC to user
user = db.query(User).filter(User.id == charge.user_id).first()
if user:
user.cc_balance = (user.cc_balance or 0) + charge.cc_amount
# Trigger referral reward if applicable
if user.referred_by:
referrer = db.query(User).filter(
User.referral_code == user.referred_by
).first()
if referrer:
create_referral_reward(
referrer_id=referrer.id,
referred_user_id=user.id,
payment_amount=charge.amount_usd or charge.amount,
db=db
)
# Send notification to user
notify_system(
db,
user.id,
"Payment Confirmed",
f"Your payment of {charge.amount} {charge.currency or 'USD'} has been confirmed. {charge.cc_amount} CC has been added to your balance.",
"/profile"
)
else:
charge.status = "rejected"
charge.verified_at = datetime.utcnow()
charge.verified_by = current_user.id
charge.admin_note = admin_note
# Send notification to user
user = db.query(User).filter(User.id == charge.user_id).first()
if user:
notify_system(
db,
user.id,
"Payment Rejected",
f"Your payment request for {charge.amount} {charge.currency or 'USD'} was rejected. Reason: {admin_note or 'No reason provided'}",
"/profile"
)
db.commit()
return {
"message": f"Payment {'approved' if approved else 'rejected'}",
"charge_id": charge_id,
"new_status": charge.status
}
# ============================================
# Stripe Payment Endpoints
# ============================================
class CreateCheckoutRequest(BaseModel):
package_id: int
@router.get("/stripe-key")
def get_stripe_publishable_key():
"""Get Stripe publishable key for frontend"""
return {"publishable_key": settings.STRIPE_PUBLISHABLE_KEY}
@router.get("/packages")
def get_cc_packages(db: Session = Depends(get_db)):
"""Get available CC packages"""
# Get system settings for cars_per_cc
system_settings = db.query(SystemSettings).first()
cars_per_cc = system_settings.cars_per_cc if system_settings and system_settings.cars_per_cc else 3
# First try to get from database
packages = db.query(CCPackage).filter(CCPackage.is_active == True).order_by(CCPackage.sort_order).all()
# If no packages in DB, initialize with defaults
if not packages:
for pkg_data in DEFAULT_CC_PACKAGES:
pkg = CCPackage(**pkg_data)
db.add(pkg)
db.commit()
packages = db.query(CCPackage).filter(CCPackage.is_active == True).order_by(CCPackage.sort_order).all()
return [
{
"id": pkg.id,
"name": pkg.name,
"price_usd": pkg.price_usd,
"cc_amount": pkg.cc_amount,
"bonus_cc": pkg.bonus_cc,
"total_cc": pkg.cc_amount + pkg.bonus_cc,
"discount_percent": pkg.discount_percent,
"recommendations": (pkg.cc_amount + pkg.bonus_cc) * cars_per_cc,
"cars_per_cc": cars_per_cc, # 프론트엔드에서 표시용
}
for pkg in packages
]
@router.post("/create-checkout-session")
def create_checkout_session(
request: CreateCheckoutRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create Stripe checkout session for CC purchase"""
if not settings.STRIPE_SECRET_KEY:
raise HTTPException(status_code=500, detail="Stripe is not configured")
# Get package
package = db.query(CCPackage).filter(CCPackage.id == request.package_id).first()
if not package:
raise HTTPException(status_code=404, detail="Package not found")
if not package.is_active:
raise HTTPException(status_code=400, detail="This package is no longer available")
try:
# Create Stripe Checkout Session
checkout_session = stripe.checkout.Session.create(
payment_method_types=["card"],
line_items=[
{
"price_data": {
"currency": "usd",
"unit_amount": package.price_usd * 100, # Stripe uses cents
"product_data": {
"name": f"AutonetSellCar CC - {package.name}",
"description": f"{package.cc_amount + package.bonus_cc} CC ({package.cc_amount} + {package.bonus_cc} bonus)",
},
},
"quantity": 1,
}
],
mode="payment",
success_url=f"{settings.STRIPE_SUCCESS_URL}?session_id={{CHECKOUT_SESSION_ID}}",
cancel_url=settings.STRIPE_CANCEL_URL,
client_reference_id=str(current_user.id),
metadata={
"user_id": str(current_user.id),
"package_id": str(package.id),
"cc_amount": str(package.cc_amount),
"bonus_cc": str(package.bonus_cc),
},
customer_email=current_user.email,
)
# Create pending charge record
charge_record = ChargeHistory(
user_id=current_user.id,
package_id=package.id,
amount=package.price_usd,
amount_usd=package.price_usd,
cc_amount=package.cc_amount,
bonus_cc=package.bonus_cc,
currency="USD",
payment_method="stripe",
stripe_session_id=checkout_session.id,
status="pending"
)
db.add(charge_record)
db.commit()
return {
"checkout_url": checkout_session.url,
"session_id": checkout_session.id
}
except stripe.error.StripeError as e:
logger.error(f"Stripe error: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/webhook")
async def stripe_webhook(
request: Request,
db: Session = Depends(get_db)
):
"""Handle Stripe webhook events"""
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
if not settings.STRIPE_WEBHOOK_SECRET:
logger.warning("Stripe webhook secret not configured")
raise HTTPException(status_code=500, detail="Webhook not configured")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError as e:
logger.error(f"Invalid payload: {e}")
raise HTTPException(status_code=400, detail="Invalid payload")
except stripe.error.SignatureVerificationError as e:
logger.error(f"Invalid signature: {e}")
raise HTTPException(status_code=400, detail="Invalid signature")
# Handle the checkout.session.completed event
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Get charge record by session ID
charge = db.query(ChargeHistory).filter(
ChargeHistory.stripe_session_id == session["id"]
).first()
if charge and charge.status == "pending":
# Update charge record
charge.status = "completed"
charge.stripe_payment_intent_id = session.get("payment_intent")
charge.verified_at = datetime.utcnow()
# Credit CC to user
user = db.query(User).filter(User.id == charge.user_id).first()
if user:
total_cc = charge.cc_amount + (charge.bonus_cc or 0)
user.cc_balance = (user.cc_balance or 0) + total_cc
# Trigger referral reward if applicable
if user.referred_by:
referrer = db.query(User).filter(
User.referral_code == user.referred_by
).first()
if referrer:
create_referral_reward(
referrer_id=referrer.id,
referred_user_id=user.id,
payment_amount=charge.amount_usd or charge.amount,
db=db
)
# Send notification
notify_system(
db,
user.id,
"CC Purchase Successful",
f"Your purchase of {total_cc} CC has been completed. Your new balance is {user.cc_balance} CC.",
"/cc"
)
logger.info(f"CC credited: user={user.id}, amount={total_cc}")
db.commit()
elif event["type"] == "checkout.session.expired":
session = event["data"]["object"]
# Update charge record to cancelled
charge = db.query(ChargeHistory).filter(
ChargeHistory.stripe_session_id == session["id"]
).first()
if charge and charge.status == "pending":
charge.status = "cancelled"
db.commit()
return {"status": "success"}
@router.get("/checkout-success")
def checkout_success(
session_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Verify checkout session and return result"""
# Find charge record
charge = db.query(ChargeHistory).filter(
ChargeHistory.stripe_session_id == session_id,
ChargeHistory.user_id == current_user.id
).first()
if not charge:
raise HTTPException(status_code=404, detail="Payment record not found")
# If still pending, try to verify with Stripe
if charge.status == "pending":
try:
session = stripe.checkout.Session.retrieve(session_id)
if session.payment_status == "paid":
charge.status = "completed"
charge.stripe_payment_intent_id = session.payment_intent
charge.verified_at = datetime.utcnow()
# Credit CC
total_cc = charge.cc_amount + (charge.bonus_cc or 0)
current_user.cc_balance = (current_user.cc_balance or 0) + total_cc
db.commit()
except stripe.error.StripeError as e:
logger.error(f"Error verifying session: {e}")
return {
"status": charge.status,
"cc_amount": charge.cc_amount,
"bonus_cc": charge.bonus_cc or 0,
"total_cc": charge.cc_amount + (charge.bonus_cc or 0),
"cc_balance": current_user.cc_balance or 0
}
# Manual CC charge request (for Russian users via Mongolian partner)
class ManualChargeRequest(BaseModel):
package_id: int
payment_note: Optional[str] = None # e.g., "Paid via Mongolian partner bank"
@router.post("/manual-request")
def create_manual_charge_request(
request: ManualChargeRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create manual CC charge request (for Russian users)"""
# Get package
package = db.query(CCPackage).filter(CCPackage.id == request.package_id).first()
if not package:
raise HTTPException(status_code=404, detail="Package not found")
# Create pending charge record
charge_record = ChargeHistory(
user_id=current_user.id,
package_id=package.id,
amount=package.price_usd,
amount_usd=package.price_usd,
cc_amount=package.cc_amount,
bonus_cc=package.bonus_cc,
currency="USD",
payment_method="manual",
admin_note=request.payment_note,
status="pending"
)
db.add(charge_record)
db.commit()
db.refresh(charge_record)
# Notify admins
admins = db.query(User).filter(User.is_admin == True).all()
for admin in admins:
notify_system(
db,
admin.id,
"New Manual CC Request",
f"User {current_user.email} requested {package.cc_amount} CC (${package.price_usd}). Payment method: manual.",
"/admin/cc"
)
return {
"message": "Manual charge request created. An admin will verify your payment.",
"charge_id": charge_record.id,
"package": {
"name": package.name,
"price_usd": package.price_usd,
"cc_amount": package.cc_amount + package.bonus_cc
},
"status": "pending"
}