diff --git a/video-gen-api/app/services/payment.py b/video-gen-api/app/services/payment.py index 95eb12ce..66f389c5 100644 --- a/video-gen-api/app/services/payment.py +++ b/video-gen-api/app/services/payment.py @@ -149,16 +149,18 @@ def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway: ) 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: - _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 + logger.info(f"Alipay client initialized successfully for app_id={app_id}") except Exception: logger.exception("Failed to initialize Alipay client") _alipay_client = None @@ -204,10 +206,27 @@ async def create_recharge_order( raise ValueError("微信支付未完成配置,请联系管理员") 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( id=generate_id(), user_id=user_id, - order_no=generate_order_no(), + order_no=order_no, amount=price, credits=total_credits, payment_method=method, @@ -239,14 +258,9 @@ async def create_recharge_order( # Real payment: delegate to WeChat or Alipay if method == "wechat": _create_wechat_order(order, db_configs) - elif method == "alipay": - qr_url = _create_alipay_order(order, db_configs) - if qr_url: - # 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("支付宝预下单失败,请检查配置或稍后重试") + elif method == "alipay" and qr_url: + # Attach QR URL to the order instance (transient, not persisted) + order.qr_url = qr_url # type: ignore[attr-defined] 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. - - 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. """ 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.out_trade_no = order.order_no - model.total_amount = f"{order.amount:.2f}" - model.subject = f"充值订单 {order.order_no}" + model.out_trade_no = order_no + model.total_amount = f"{amount:.2f}" + model.subject = f"充值订单 {order_no}" model.product_code = "QR_CODE_OFFLINE" body_parts = [] - if order.credits > 0: - body_parts.append(f"{order.credits}积分") + if credits > 0: + body_parts.append(f"{credits}积分") if body_parts: model.body = " ".join(body_parts) @@ -323,7 +344,7 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str # 执行API调用 response_content = client.execute(request) 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 # 解析响应结果 @@ -333,7 +354,7 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str if response.is_success(): qr_url = response.qr_code logger.info( - f"Alipay precreate success: order_no={order.order_no}, " + f"Alipay precreate success: order_no={order_no}, " f"qr_url={qr_url}" ) return qr_url @@ -341,15 +362,30 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str logger.error( f"Alipay precreate failed: code={response.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 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 +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 # ---------------------------------------------------------------------------