1
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user