This commit is contained in:
2026-06-10 17:49:08 +08:00
parent 10bd2bd6a0
commit 25ffd45e40
+65 -29
View File
@@ -149,16 +149,18 @@ def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway:
) )
return None return None
config = AlipayClientConfig()
config.server_url = gateway or "https://openapi.alipay.com/gateway.do"
config.app_id = app_id
config.app_private_key = private_key
config.alipay_public_key = public_key
config.sign_type = "RSA2"
config.charset = "utf-8"
try: try:
_alipay_client = DefaultAlipayClient(config, logger) config = AlipayClientConfig()
config.server_url = gateway or "https://openapi.alipay.com/gateway.do"
config.app_id = app_id
config.app_private_key = private_key
config.alipay_public_key = public_key
config.sign_type = "RSA2"
config.charset = "utf-8"
_alipay_client = DefaultAlipayClient(alipay_client_config=config)
_alipay_client_app_id = app_id _alipay_client_app_id = app_id
logger.info(f"Alipay client initialized successfully for app_id={app_id}")
except Exception: except Exception:
logger.exception("Failed to initialize Alipay client") logger.exception("Failed to initialize Alipay client")
_alipay_client = None _alipay_client = None
@@ -204,10 +206,27 @@ async def create_recharge_order(
raise ValueError("微信支付未完成配置,请联系管理员") raise ValueError("微信支付未完成配置,请联系管理员")
total_credits = credits + bonus_credits total_credits = credits + bonus_credits
# Generate order number once
order_no = generate_order_no()
# For real payment methods, create payment request BEFORE saving order to DB
qr_url = None
if not mock_mode and method == "alipay":
# Try to create Alipay order first
qr_url = _create_alipay_order_with_params(
order_no=order_no,
amount=price,
credits=total_credits,
db_configs=db_configs,
)
if not qr_url:
raise ValueError("支付宝预下单失败,请检查配置或稍后重试")
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=order_no,
amount=price, amount=price,
credits=total_credits, credits=total_credits,
payment_method=method, payment_method=method,
@@ -239,14 +258,9 @@ async def create_recharge_order(
# Real payment: delegate to WeChat or Alipay # Real payment: delegate to WeChat or Alipay
if method == "wechat": if method == "wechat":
_create_wechat_order(order, db_configs) _create_wechat_order(order, db_configs)
elif method == "alipay": elif method == "alipay" and qr_url:
qr_url = _create_alipay_order(order, db_configs) # Attach QR URL to the order instance (transient, not persisted)
if qr_url: order.qr_url = qr_url # type: ignore[attr-defined]
# Attach QR URL to the order instance (transient, not persisted)
order.qr_url = qr_url # type: ignore[attr-defined]
else:
# Precreate failed — do not leave a pending order that can never be paid
raise ValueError("支付宝预下单失败,请检查配置或稍后重试")
return order return order
@@ -274,10 +288,17 @@ def _create_wechat_order(order: PaymentOrder, db_configs: dict[str, str]) -> Non
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str | None: def _create_alipay_order_with_params(
order_no: str,
amount: float,
credits: float,
db_configs: dict[str, str],
) -> str | None:
"""Call Alipay ``trade.precreate`` to obtain a QR code URL. """Call Alipay ``trade.precreate`` to obtain a QR code URL.
Reads all Alipay config from the database (admin panel). This version accepts parameters directly instead of an order object,
allowing us to call it before creating the database record.
Returns the ``qr_code`` URL on success, or ``None`` on failure. Returns the ``qr_code`` URL on success, or ``None`` on failure.
""" """
app_id = db_configs.get("payment_alipay_app_id", "") app_id = db_configs.get("payment_alipay_app_id", "")
@@ -306,14 +327,14 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
# 构造业务参数 # 构造业务参数
model = AlipayTradePrecreateModel() model = AlipayTradePrecreateModel()
model.out_trade_no = order.order_no model.out_trade_no = order_no
model.total_amount = f"{order.amount:.2f}" model.total_amount = f"{amount:.2f}"
model.subject = f"充值订单 {order.order_no}" model.subject = f"充值订单 {order_no}"
model.product_code = "QR_CODE_OFFLINE" model.product_code = "QR_CODE_OFFLINE"
body_parts = [] body_parts = []
if order.credits > 0: if credits > 0:
body_parts.append(f"{order.credits}积分") body_parts.append(f"{credits}积分")
if body_parts: if body_parts:
model.body = " ".join(body_parts) model.body = " ".join(body_parts)
@@ -323,7 +344,7 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
# 执行API调用 # 执行API调用
response_content = client.execute(request) response_content = client.execute(request)
if not response_content: if not response_content:
logger.error(f"Alipay precreate failed: empty response, order_no={order.order_no}") logger.error(f"Alipay precreate failed: empty response, order_no={order_no}")
return None return None
# 解析响应结果 # 解析响应结果
@@ -333,7 +354,7 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
if response.is_success(): if response.is_success():
qr_url = response.qr_code qr_url = response.qr_code
logger.info( logger.info(
f"Alipay precreate success: order_no={order.order_no}, " f"Alipay precreate success: order_no={order_no}, "
f"qr_url={qr_url}" f"qr_url={qr_url}"
) )
return qr_url return qr_url
@@ -341,15 +362,30 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
logger.error( logger.error(
f"Alipay precreate failed: code={response.code}, " f"Alipay precreate failed: code={response.code}, "
f"msg={response.msg}, sub_code={response.sub_code}, " f"msg={response.msg}, sub_code={response.sub_code}, "
f"sub_msg={response.sub_msg}, order_no={order.order_no}" f"sub_msg={response.sub_msg}, order_no={order_no}"
) )
return None return None
except Exception: except Exception:
logger.exception(f"Alipay precreate exception: order_no={order.order_no}") logger.exception(f"Alipay precreate exception: order_no={order_no}")
return None return None
def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str | None:
"""Call Alipay ``trade.precreate`` to obtain a QR code URL.
Reads all Alipay config from the database (admin panel).
Returns the ``qr_code`` URL on success, or ``None`` on failure.
Deprecated: Use _create_alipay_order_with_params instead for better error handling.
"""
return _create_alipay_order_with_params(
order_no=order.order_no,
amount=order.amount,
credits=order.credits,
db_configs=db_configs,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Alipay callback verification # Alipay callback verification
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------