Files
HyperNode-AI/app/services/withdrawals.py
T
dekun 2bbdb00a4d 初始化 HyperNode-AI 控制面。
包含节点账本、每日清算,以及 Ubuntu 交互式部署脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:53:20 +08:00

100 lines
3.3 KiB
Python

"""提现申请。可用余额 = 已入账积分 - 待处理提现 - 已完成提现。"""
from __future__ import annotations
from decimal import Decimal
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.clock import utcnow
from app.errors import AppError
from app.models import NodeYield, WithdrawalLog, WithdrawalStatus, YieldStatus
from app.money import money, to_decimal
from app.services.common import get_user_by_wallet
def available_balance(session: Session, user_id: int) -> Decimal:
credited_rows = session.scalars(
select(NodeYield.points_delta).where(
NodeYield.user_id == user_id,
NodeYield.status == YieldStatus.SETTLED,
)
).all()
reserved_rows = session.scalars(
select(WithdrawalLog.amount).where(
WithdrawalLog.user_id == user_id,
WithdrawalLog.status.in_([WithdrawalStatus.PENDING, WithdrawalStatus.COMPLETED]),
)
).all()
credited = sum((to_decimal(item) for item in credited_rows), Decimal("0"))
reserved = sum((to_decimal(item) for item in reserved_rows), Decimal("0"))
balance = credited - reserved
if balance <= 0:
return Decimal("0")
return money(balance)
def request_withdrawal(
session: Session,
wallet_address: str,
amount: Decimal,
asset_symbol: str = "POINT",
) -> tuple[WithdrawalLog, Decimal]:
user = get_user_by_wallet(session, wallet_address.strip())
value = money(amount)
if value <= 0:
raise AppError("提现金额必须大于 0")
balance = available_balance(session, user.id)
if value > balance:
raise AppError("可用积分不足")
row = WithdrawalLog(
user_id=user.id,
amount=value,
asset_symbol=asset_symbol.strip().upper() or "POINT",
status=WithdrawalStatus.PENDING,
created_at=utcnow(),
updated_at=utcnow(),
)
session.add(row)
session.flush()
return row, money(balance - value)
def confirm_withdrawal(session: Session, withdrawal_id: int, tx_hash: str) -> WithdrawalLog:
row = _get_withdrawal(session, withdrawal_id)
if row.status != WithdrawalStatus.PENDING:
raise AppError("仅待处理的提现可以确认", 409)
digest = tx_hash.strip()
occupied = session.scalar(select(WithdrawalLog.id).where(WithdrawalLog.tx_hash == digest))
if occupied is not None:
raise AppError("交易哈希已存在", 409)
row.tx_hash = digest
row.status = WithdrawalStatus.COMPLETED
row.updated_at = utcnow()
try:
session.flush()
except IntegrityError as exc:
session.rollback()
raise AppError("交易哈希已存在", 409) from exc
return row
def reject_withdrawal(session: Session, withdrawal_id: int, note: str | None = None) -> WithdrawalLog:
row = _get_withdrawal(session, withdrawal_id)
if row.status != WithdrawalStatus.PENDING:
raise AppError("仅待处理的提现可以拒绝", 409)
row.status = WithdrawalStatus.REJECTED
row.note = note
row.updated_at = utcnow()
session.flush()
return row
def _get_withdrawal(session: Session, withdrawal_id: int) -> WithdrawalLog:
row = session.get(WithdrawalLog, withdrawal_id)
if row is None:
raise AppError("提现记录不存在", 404)
return row