初始化 HyperNode-AI 控制面。
包含节点账本、每日清算,以及 Ubuntu 交互式部署脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""提现申请。可用余额 = 已入账积分 - 待处理提现 - 已完成提现。"""
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user