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_wechat_callback,
verify_alipay_callback, verify_alipay_callback,
process_payment_success_by_order_no, process_payment_success_by_order_no,
_get_payment_configs,
_is_mock_mode,
_check_and_expire_order,
) )
router = APIRouter(prefix="/payments", tags=["payments"]) router = APIRouter(prefix="/payments", tags=["payments"])
@@ -42,7 +45,6 @@ async def recharge(
raise HTTPException(status_code=400, detail="不支持的支付方式") raise HTTPException(status_code=400, detail="不支持的支付方式")
# Check if the selected payment method is enabled in admin config # 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) configs = await _get_payment_configs(db)
if not _is_mock_mode(configs): if not _is_mock_mode(configs):
enabled_key = f"payment_{req.method}_enabled" enabled_key = f"payment_{req.method}_enabled"
@@ -118,7 +120,6 @@ async def list_orders(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
# Auto-expire stale pending orders before returning # Auto-expire stale pending orders before returning
from app.services.payment import _check_and_expire_order
result = await db.execute( result = await db.execute(
select(PaymentOrder) select(PaymentOrder)
.where(PaymentOrder.user_id == current_user.id) .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.services.credits import add_credits
from app.utils.id_gen import generate_id, generate_order_no 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) # 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()} 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: async def _check_and_expire_order(db: AsyncSession, order: PaymentOrder) -> bool:
"""If a pending order has passed its expiry, mark it cancelled. """If a pending order has passed its expiry, mark it cancelled.
Returns True if the order was expired. 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( async def create_recharge_order(
db: AsyncSession, db: AsyncSession,
user_id: str, user_id: str,
amount: float,
credits: float, credits: float,
payment_method: str, price: float,
label: str,
bonus_credits: float = 0,
method: str = "alipay",
) -> PaymentOrder: ) -> PaymentOrder:
"""Create a new pending payment order and call the payment gateway. """Create a new pending payment order and call the payment gateway.
If mock mode is enabled, auto-approves. If mock mode is enabled, auto-approves.
Returns the PaymentOrder with qr_code (or None if mock). Returns the PaymentOrder with qr_code (or None if mock).
""" """
configs = await _get_payment_configs(db) 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( order = PaymentOrder(
id=generate_id(), id=generate_id(),
user_id=user_id, user_id=user_id,
order_no=generate_order_no(), order_no=generate_order_no(),
amount=amount, amount=price,
credits=credits, credits=total_credits,
payment_method=payment_method, payment_method=method,
status="pending" if not is_mock else "paid", status="pending" if not is_mock else "paid",
qr_url=None, qr_url=None,
) )
@@ -263,7 +283,7 @@ async def create_recharge_order(
logger.info( logger.info(
f"ORDER_CREATED order_no={order.order_no} user={user_id} " 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: if is_mock:
@@ -273,12 +293,12 @@ async def create_recharge_order(
return order return order
# Real payment # Real payment
if payment_method == "alipay": if method == "alipay":
qr_url = _create_alipay_order(order, configs) qr_url = _create_alipay_order(order, configs)
if qr_url: if qr_url:
order.qr_url = qr_url order.qr_url = qr_url
await db.flush() await db.flush()
elif payment_method == "wechat": elif method == "wechat":
_create_wechat_order(order, configs) _create_wechat_order(order, configs)
# Wechat would get a qr_url too, but stubbed for now # Wechat would get a qr_url too, but stubbed for now
@@ -286,6 +306,29 @@ async def create_recharge_order(
return 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 # Wechat stub for now
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------