优化斗鱼充值

This commit is contained in:
yml2213
2026-08-13 21:43:27 +08:00
parent 902e669614
commit 0bf69c1240
19 changed files with 450 additions and 266 deletions
+2 -1
View File
@@ -6,7 +6,7 @@ from .login_api_wgapi import WgapiLoginAPI
from .login_api_iframe import IframeLoginAPI
from .email_verifier import EmailVerifier
from .activity_client import DouyuActivityClient, DouyuActivityError
from .recharge_api import FishFinRechargeClient, FishFinRechargeConfig, FishFinRechargeError
from .recharge_api import FishFinRechargeClient, FishFinRechargeConfig, FishFinRechargeConfigError, FishFinRechargeError
__all__ = [
"DouyuLogin",
@@ -19,5 +19,6 @@ __all__ = [
"DouyuActivityError",
"FishFinRechargeClient",
"FishFinRechargeConfig",
"FishFinRechargeConfigError",
"FishFinRechargeError",
]
+35 -31
View File
@@ -29,6 +29,7 @@ class FishFinRechargeConfig:
app_id: str
app_secret: str
app_key: str = ""
notify_url: str = ""
timeout: tuple[float, float] = (8, 20)
debug: bool = False
@@ -41,6 +42,7 @@ class FishFinRechargeConfig:
app_id=os.getenv("FISH_FIN_RECHARGE_APP_ID", "").strip(),
app_secret=os.getenv("FISH_FIN_RECHARGE_APP_SECRET", "").strip(),
app_key=os.getenv("FISH_FIN_RECHARGE_APP_KEY", "").strip(),
notify_url=os.getenv("FISH_FIN_RECHARGE_NOTIFY_URL", "").strip(),
timeout=(8, timeout),
debug=os.getenv("FISH_FIN_RECHARGE_DEBUG", "false").strip().lower() in {"1", "true", "yes"},
)
@@ -195,71 +197,73 @@ class FishFinRechargeClient:
return payload
@staticmethod
def _price(value: Decimal | int | float | str) -> str:
def _amount(value: Decimal | int | float | str) -> str:
"""规范化金额,避免浮点数表达式进入签名或订单请求。"""
try:
price = Decimal(str(value))
except (InvalidOperation, ValueError) as exc:
raise ValueError("customer_price 必须是有效金额") from exc
raise ValueError("pay_amount 必须是有效金额") from exc
if not price.is_finite() or price <= 0:
raise ValueError("customer_price 必须大于 0")
raise ValueError("pay_amount 必须大于 0")
return format(price.normalize(), "f")
@classmethod
def _json_price(cls, value: Decimal | int | float | str) -> int | float:
def _json_amount(cls, value: Decimal | int | float | str) -> int | float:
"""按文档以 JSON 数字发送金额,整数不附带无意义的小数位。"""
price_text = cls._price(value)
return int(price_text) if "." not in price_text else float(price_text)
amount_text = cls._amount(value)
return int(amount_text) if "." not in amount_text else float(amount_text)
@staticmethod
def _json_argument(value: list[dict[str, Any]] | dict[str, Any], field: str) -> str:
"""将供应商规定的扩展参数编码为紧凑 JSON 字符串。"""
if not value:
raise ValueError(f"{field} 不能为空")
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
def create_order(
self,
*,
charge_account: str,
buy_num: int,
customer_price: Decimal | int | float | str,
customer_order_no: str,
pay_amount: Decimal | int | float | str,
out_order_id: str,
product_id: str,
recharge_arg: list[dict[str, Any]] | None = None,
recharge_arg: list[dict[str, Any]],
order_type: int = 0,
notify_url: str = "",
ext_arg: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""创建直充订单;成功后应使用 query_order 或供应商回调确认最终状态。"""
if not str(charge_account).strip():
raise ValueError("charge_account 不能为空")
if not str(customer_order_no).strip():
raise ValueError("customer_order_no 不能为空")
if not str(out_order_id).strip():
raise ValueError("out_order_id 不能为空")
if not str(product_id).strip():
raise ValueError("product_id 不能为空")
if not isinstance(buy_num, int) or isinstance(buy_num, bool) or buy_num < 1:
raise ValueError("buy_num 必须是不小于 1 的整数")
if recharge_arg is not None and (not isinstance(recharge_arg, list) or not recharge_arg):
if not isinstance(order_type, int) or isinstance(order_type, bool) or order_type not in {0, 1, 2, 3}:
raise ValueError("order_type 必须是 0 至 3 的整数")
if not isinstance(recharge_arg, list) or not recharge_arg:
raise ValueError("recharge_arg 必须是非空数组")
params: dict[str, Any] = {
"charge_account": str(charge_account).strip(),
"buy_num": buy_num,
# 文档示例使用 JSON 数字;签名使用实际 JSON 数字对应的文本。
"customer_price": self._json_price(customer_price),
"customer_order_no": str(customer_order_no).strip(),
"pay_amount": self._json_amount(pay_amount),
"out_order_id": str(out_order_id).strip(),
"product_id": str(product_id).strip(),
"order_type": order_type,
"recharge_arg": self._json_argument(recharge_arg, "recharge_arg"),
}
# 文档规定可选参数未特别约定时默认不传,不能以空值或内部字段占位。
if recharge_arg:
params["recharge_arg"] = recharge_arg
if str(notify_url).strip():
params["notify_url"] = str(notify_url).strip()
if ext_arg:
params["ext_arg"] = ext_arg
params["ext_arg"] = self._json_argument(ext_arg, "ext_arg")
return self._request("POST", self.CREATE_ORDER_PATH, params)
def query_order(self, **query: Any) -> dict[str, Any]:
"""查询订单。
文档未列出查询字段,调用方必须显式传入供应商确认的订单标识,例如
``customer_order_no`` 或供应商订单号。
"""
if not query or not any(str(value).strip() for value in query.values() if value is not None):
raise ValueError("查询订单至少需要一个非空订单标识")
return self._request("GET", self.QUERY_ORDER_PATH, query)
def query_order(self, out_order_id: str) -> dict[str, Any]:
"""按新版文档使用来源/外部订单号查询订单。"""
out_order_id = str(out_order_id).strip()
if not out_order_id:
raise ValueError("out_order_id 不能为空")
return self._request("GET", self.QUERY_ORDER_PATH, {"out_order_id": out_order_id})
def account_info(self) -> dict[str, Any]:
"""查询商户账户信息。"""