This commit is contained in:
2026-06-11 10:04:18 +08:00
parent 8ca6a30f34
commit 64f8be96b5
2 changed files with 55 additions and 11 deletions
+3 -2
View File
@@ -16,6 +16,9 @@ from app.services.payment import (
verify_wechat_callback,
verify_alipay_callback,
process_payment_success_by_order_no,
_get_payment_configs,
_is_mock_mode,
_check_and_expire_order,
)
router = APIRouter(prefix="/payments", tags=["payments"])
@@ -42,7 +45,6 @@ async def recharge(
raise HTTPException(status_code=400, detail="不支持的支付方式")
# Check if the selected payment method is enabled in admin config
from app.services.payment import _get_payment_configs, _is_mock_mode
configs = await _get_payment_configs(db)
if not _is_mock_mode(configs):
enabled_key = f"payment_{req.method}_enabled"
@@ -118,7 +120,6 @@ async def list_orders(
db: AsyncSession = Depends(get_db),
):
# Auto-expire stale pending orders before returning
from app.services.payment import _check_and_expire_order
result = await db.execute(
select(PaymentOrder)
.where(PaymentOrder.user_id == current_user.id)
+52 -9
View File
@@ -23,6 +23,19 @@ from app.models.system_config import SystemConfig
from app.services.credits import add_credits
from app.utils.id_gen import generate_id, generate_order_no
__all__ = [
"create_recharge_order",
"verify_alipay_callback",
"verify_wechat_callback",
"process_payment_success_by_order_no",
"get_order",
"get_user_orders",
"expire_all_pending_orders",
"_get_payment_configs",
"_is_mock_mode",
"_check_and_expire_order",
]
# ---------------------------------------------------------------------------
# Payment logger → log/payment/YYYY-MM-DD.log (one file per day, no cleanup)
# ---------------------------------------------------------------------------
@@ -135,6 +148,10 @@ async def _get_payment_configs(db: AsyncSession) -> dict[str, str]:
return {c.key: c.value for c in result.scalars().all()}
def _is_mock_mode(configs: dict[str, str]) -> bool:
return configs.get("payment_mock", "false").lower() == "true"
async def _check_and_expire_order(db: AsyncSession, order: PaymentOrder) -> bool:
"""If a pending order has passed its expiry, mark it cancelled.
Returns True if the order was expired.
@@ -237,24 +254,27 @@ def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway:
async def create_recharge_order(
db: AsyncSession,
user_id: str,
amount: float,
credits: float,
payment_method: str,
price: float,
label: str,
bonus_credits: float = 0,
method: str = "alipay",
) -> PaymentOrder:
"""Create a new pending payment order and call the payment gateway.
If mock mode is enabled, auto-approves.
Returns the PaymentOrder with qr_code (or None if mock).
"""
configs = await _get_payment_configs(db)
is_mock = configs.get("payment_mock", "false").lower() == "true"
is_mock = _is_mock_mode(configs)
total_credits = credits + bonus_credits
order = PaymentOrder(
id=generate_id(),
user_id=user_id,
order_no=generate_order_no(),
amount=amount,
credits=credits,
payment_method=payment_method,
amount=price,
credits=total_credits,
payment_method=method,
status="pending" if not is_mock else "paid",
qr_url=None,
)
@@ -263,7 +283,7 @@ async def create_recharge_order(
logger.info(
f"ORDER_CREATED order_no={order.order_no} user={user_id} "
f"amount={amount} credits={credits} method={payment_method} mock={is_mock}"
f"amount={price} credits={total_credits} method={method} mock={is_mock}"
)
if is_mock:
@@ -273,12 +293,12 @@ async def create_recharge_order(
return order
# Real payment
if payment_method == "alipay":
if method == "alipay":
qr_url = _create_alipay_order(order, configs)
if qr_url:
order.qr_url = qr_url
await db.flush()
elif payment_method == "wechat":
elif method == "wechat":
_create_wechat_order(order, configs)
# Wechat would get a qr_url too, but stubbed for now
@@ -286,6 +306,29 @@ async def create_recharge_order(
return order
async def verify_wechat_callback(data: dict, db: AsyncSession) -> bool:
"""Verify Wechat Pay callback signature.
Note: This is a stub implementation.
"""
configs = await _get_payment_configs(db)
wechat_api_key = configs.get("payment_wechat_api_key", "")
if not wechat_api_key:
logger.warning("Wechat API key not configured, skipping signature verify")
return True
try:
# TODO: Implement proper Wechat Pay signature verification
logger.info(
f"WECHAT_CALLBACK order_no={data.get('out_trade_no')} "
f"transaction_id={data.get('transaction_id')}"
)
return True
except Exception:
logger.exception("Error verifying Wechat callback")
return False
# ---------------------------------------------------------------------------
# Wechat stub for now
# ---------------------------------------------------------------------------