667 lines
24 KiB
Python
667 lines
24 KiB
Python
"""虎牙任务执行器:积分与商城(由 huya_runner.py 按功能域拆分)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from datetime import UTC, datetime
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from core.huya.wss_client import POINTS_REDEEM_GOODS_NUM
|
|
|
|
from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask
|
|
from .huya_activity_query import query_huya_activity
|
|
from .huya_pay_link import pay_qr_path, resolve_pay_targets
|
|
from .huya_points_center import (
|
|
fetch_points_center_catalog,
|
|
points_center_options_from_config,
|
|
)
|
|
from .huya_points_exchange import (
|
|
EXCHANGE_STATUS_EXCHANGED,
|
|
EXCHANGE_STATUS_PAYMENT_REQUIRED,
|
|
complete_exchange_after_payment,
|
|
normalize_spu_id,
|
|
prepare_exchange,
|
|
)
|
|
|
|
# 积分查询超时:HTTP 通道正常几百毫秒,10 秒足够;失败时别让整批任务长时间挂住。
|
|
HUYA_POINTS_QUERY_TIMEOUT_SECONDS = 10.0
|
|
|
|
|
|
class GoodsMixin:
|
|
"""积分与商城域:积分/兑换记录/商品刷新/兑换。"""
|
|
|
|
if TYPE_CHECKING:
|
|
payload: dict
|
|
|
|
@staticmethod
|
|
def _to_int(value: Any) -> int: ...
|
|
|
|
@staticmethod
|
|
def _format_local_time(timestamp: int) -> str: ...
|
|
|
|
def _resolve_uid(self, account_info: dict) -> int: ...
|
|
def _push_log(self, level: str, message: str) -> None: ...
|
|
def _activity_client(self, uid: int, cookie: str): ...
|
|
def _shop_client(self, uid: int, cookie: str): ...
|
|
def _http_client(self, proxies: dict[str, str] | None = None): ...
|
|
def _exchange_proxies(
|
|
self,
|
|
) -> tuple[dict[str, str] | None, str, str]: ...
|
|
def _probe_bind_status(
|
|
self, uid: int, cookie: str, b_act_id_int: int, outer_act_id: str = ""
|
|
): ...
|
|
def _mark_task(self, *args: Any, **kwargs: Any) -> None: ...
|
|
def _update_task_progress(self, *args: Any, **kwargs: Any) -> None: ...
|
|
def _wait_recharge_payment(self, *args: Any, **kwargs: Any): ...
|
|
def _wait_until(self, when: datetime, uid: int) -> bool: ...
|
|
def _parse_scheduled_time(self, value: Any) -> datetime | None: ...
|
|
|
|
def _resolve_live_exchange_goods(
|
|
self,
|
|
client: Any,
|
|
*,
|
|
uid: int,
|
|
cookie: str,
|
|
config_info: dict,
|
|
submitted_product_id: str,
|
|
product_name: str,
|
|
) -> tuple[dict | None, str]:
|
|
"""用实时积分中心商品把旧 prize_id 或过期选择解析成当前 SPU。
|
|
|
|
2026-11-13 两份最新 HAR 确认:页面商品列表使用 queryAllocation 返回的
|
|
``hy-*`` SPU;旧快照里的数字 prize_id 不能直接用于兑换。
|
|
"""
|
|
catalog = fetch_points_center_catalog(
|
|
uid,
|
|
cookie,
|
|
client=client,
|
|
**points_center_options_from_config(config_info),
|
|
)
|
|
goods, message = catalog.goods, catalog.message
|
|
if not goods:
|
|
return None, message or "实时商品列表为空"
|
|
|
|
wanted_name = product_name.strip()
|
|
wanted_id = normalize_spu_id(submitted_product_id)
|
|
if wanted_name:
|
|
by_name = [
|
|
item
|
|
for item in goods
|
|
if str(item.get("name") or "").strip() == wanted_name
|
|
]
|
|
if len(by_name) == 1:
|
|
return by_name[0], ""
|
|
if len(by_name) > 1 and wanted_id:
|
|
for item in by_name:
|
|
if normalize_spu_id(item.get("product_id")) == wanted_id:
|
|
return item, ""
|
|
if wanted_id:
|
|
for item in goods:
|
|
if normalize_spu_id(item.get("product_id")) == wanted_id:
|
|
return item, ""
|
|
return (
|
|
None,
|
|
"商品列表已更新,当前实时列表中找不到所选商品,请重新选择",
|
|
)
|
|
|
|
def _execute_query_act_tasks(
|
|
self,
|
|
worker_db: Session,
|
|
task: HuyaTask,
|
|
account: HuyaAccount,
|
|
account_info: dict,
|
|
config_info: dict,
|
|
):
|
|
"""读取精英宝典任务详情,供专用工作台展示购买/观看任务。"""
|
|
uid = self._resolve_uid(account_info)
|
|
cookie = account_info.get("cookie") or ""
|
|
if not uid or not cookie:
|
|
self._mark_task(worker_db, task, "failed", "账号 UID 或 Cookie 为空")
|
|
return
|
|
act_id = self._to_int(self.payload.get("act_id") or 25135)
|
|
if not act_id:
|
|
self._mark_task(worker_db, task, "failed", "精英宝典活动 ID 无效")
|
|
return
|
|
client: Any = self._activity_client(uid, cookie)
|
|
response = client.get_act_task_detail(uid=uid, cookie=cookie, act_id=act_id)
|
|
if response is None:
|
|
self._mark_task(worker_db, task, "error", "虎牙活动任务接口无响应")
|
|
return
|
|
result = response.to_dict()
|
|
result["act_id"] = act_id
|
|
act_info = client.get_act_info(act_id=act_id)
|
|
user_tasks = client.get_act_user_task_detail(
|
|
uid=uid, cookie=cookie, act_id=act_id
|
|
)
|
|
if act_info is not None:
|
|
result["act_info"] = act_info.to_dict()
|
|
if user_tasks is not None:
|
|
result["user_task_detail"] = user_tasks.to_dict()
|
|
if response.status != 200:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
response.msg or f"虎牙活动任务查询失败: {response.status}",
|
|
result,
|
|
)
|
|
return
|
|
account.status = "tasks_queried"
|
|
account.updated_at = datetime.now(UTC)
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"success",
|
|
f"已读取 {len(result.get('tasks', []))} 项精英宝典任务",
|
|
result,
|
|
)
|
|
|
|
def _execute_query_points(
|
|
self,
|
|
worker_db: Session,
|
|
task: HuyaTask,
|
|
account: HuyaAccount,
|
|
account_info: dict,
|
|
config_info: dict,
|
|
):
|
|
"""查询积分:先验 CK,再走积分中心 HTTP 接口。
|
|
|
|
2026-10-15 真机实测:旧活动接口 ``webActUI.getUserScore`` 对任何 sid 都返回
|
|
603「活动已结束」(该接口已下线,与配置无关),不再作为回退——否则积分中心
|
|
超时后还要再等一次 15 秒。积分中心接口返回 ``tag2``=当前积分、
|
|
``tag3``=兑换门槛积分,另可顺手取 GameBizUI 的成长值/成长等级。
|
|
|
|
CK 判定复用外部单协作的新探针(HTTP + outer_act_id + 15 秒):积分中心对
|
|
失效 CK 仍可能返回 200/0 分,不能拿积分结果判断凭据是否有效。
|
|
"""
|
|
uid = self._resolve_uid(account_info)
|
|
if not uid:
|
|
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
|
|
return
|
|
|
|
cookie = account_info.get("cookie") or ""
|
|
if not cookie:
|
|
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
|
return
|
|
|
|
# ---- 先验 CK:没有回包就立即判失效,不再空跑积分与旧接口 ----
|
|
ck_valid = False
|
|
protocol = self._bind_protocol(config_info, self.payload)
|
|
b_act_id_int = protocol["b_act_id"]
|
|
if protocol["bind_gid"] or b_act_id_int:
|
|
bind_status, _bind_query_result = self._probe_bind_status(
|
|
uid=uid,
|
|
cookie=cookie,
|
|
b_act_id_int=b_act_id_int,
|
|
outer_act_id=protocol["outer_act_id"],
|
|
gid=protocol["gid"],
|
|
is_use_outer_act_id=protocol["is_use_outer_act_id"],
|
|
scene=protocol["scene"],
|
|
)
|
|
if bind_status is None:
|
|
account.status = "ck_expired"
|
|
account.updated_at = datetime.now(UTC)
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"error",
|
|
"虎牙角色信息接口无响应(CK 已失效,请重新登录或重新导入 Cookie)",
|
|
{
|
|
"ck_expired": True,
|
|
"ck_expired_reason": "角色信息接口无响应",
|
|
},
|
|
)
|
|
return
|
|
ck_valid = int(getattr(bind_status, "status", 0) or 0) == 200
|
|
|
|
goods_num = (
|
|
self._to_int(self.payload.get("goods_num")) or POINTS_REDEEM_GOODS_NUM
|
|
)
|
|
# 直接走 HTTP:积分中心不需要 WSS 会话,省掉每个账号建连/初始化的几秒等待。
|
|
client: Any = self._http_client()
|
|
redeem = client.get_redeem_need_goods(
|
|
uid=uid,
|
|
guid="",
|
|
cookie=cookie,
|
|
goods_num=goods_num,
|
|
timeout=HUYA_POINTS_QUERY_TIMEOUT_SECONDS,
|
|
)
|
|
if redeem is None:
|
|
offline_result: dict[str, Any] = {"goods_num": goods_num}
|
|
if ck_valid:
|
|
offline_result["ck_valid"] = True
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"error",
|
|
"虎牙积分中心接口无响应(CK 探针正常,请稍后重试)"
|
|
if ck_valid
|
|
else "虎牙积分中心接口无响应",
|
|
offline_result,
|
|
)
|
|
return
|
|
|
|
result = redeem.to_dict()
|
|
result["goods_num"] = goods_num
|
|
if redeem.code != 200:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
redeem.message or f"虎牙积分查询失败: {redeem.code}",
|
|
result,
|
|
)
|
|
return
|
|
|
|
points = self._to_int(redeem.value("tag2"))
|
|
result["points"] = points
|
|
result["redeem_threshold"] = self._to_int(redeem.value("tag3"))
|
|
growth = client.get_grow_level_user_info(
|
|
uid=uid,
|
|
guid="",
|
|
cookie=cookie,
|
|
timeout=HUYA_POINTS_QUERY_TIMEOUT_SECONDS,
|
|
)
|
|
if growth is not None and growth.code == 0:
|
|
result["grow_value"] = self._to_int(growth.value("tag1", "tag0"))
|
|
result["grow_level"] = self._to_int(growth.value("tag1", "tag1"))
|
|
result["nickname"] = growth.value("tag2") or ""
|
|
|
|
account.points = points
|
|
account.status = "points_queried"
|
|
account.updated_at = datetime.now(UTC)
|
|
self._mark_task(worker_db, task, "success", f"积分: {points}", result)
|
|
|
|
def _execute_query_exchange_records(
|
|
self,
|
|
worker_db: Session,
|
|
task: HuyaTask,
|
|
account: HuyaAccount,
|
|
account_info: dict,
|
|
config_info: dict,
|
|
):
|
|
uid = self._resolve_uid(account_info)
|
|
if not uid:
|
|
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
|
|
return
|
|
|
|
cookie = account_info.get("cookie") or ""
|
|
if not cookie:
|
|
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
|
return
|
|
|
|
client: Any = self._http_client()
|
|
activity = query_huya_activity(
|
|
client,
|
|
uid=uid,
|
|
cookie=cookie,
|
|
config_info=config_info,
|
|
configured_sid=self._to_int(
|
|
self.payload.get("sid") or config_info.get("sid")
|
|
),
|
|
want_records=True,
|
|
)
|
|
sid_int = activity.sid
|
|
configured_sid = activity.configured_sid
|
|
sid_source = activity.sid_source
|
|
if activity.sid_error and sid_int:
|
|
self._push_log(
|
|
"warning",
|
|
f"[{uid}] 自动发现兑换活动 SID 失败,回退配置值: {activity.sid_error}",
|
|
)
|
|
if not sid_int:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
activity.errors[-1]
|
|
if activity.errors
|
|
else "无法确定当前兑换活动 SID,请先刷新积分中心商品",
|
|
)
|
|
return
|
|
|
|
response = activity.records_response
|
|
if response is None:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"error",
|
|
activity.errors[-1] if activity.errors else "虎牙兑换记录接口无响应",
|
|
)
|
|
return
|
|
|
|
result = response.to_dict()
|
|
result["sid"] = sid_int
|
|
result["sid_source"] = sid_source
|
|
result["configured_sid"] = configured_sid
|
|
if response.status != 200:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
response.msg or f"虎牙兑换记录查询失败: {response.status}",
|
|
result,
|
|
)
|
|
return
|
|
|
|
records = result.get("records", [])
|
|
for index, item in enumerate(records, start=1):
|
|
item["index"] = index
|
|
item["exchange_time_text"] = self._format_local_time(
|
|
int(item.get("exchange_time") or 0)
|
|
)
|
|
if item.get("score") is not None:
|
|
item["score_text"] = f"{int(item.get('score') or 0)}积分"
|
|
|
|
account.status = "exchange_records_queried"
|
|
account.updated_at = datetime.now(UTC)
|
|
count = len(records)
|
|
message = f"兑换记录 {count} 条" if count else "暂无兑换记录"
|
|
self._mark_task(worker_db, task, "success", message, result)
|
|
|
|
def _execute_refresh_goods(
|
|
self,
|
|
worker_db: Session,
|
|
task: HuyaTask,
|
|
account: HuyaAccount,
|
|
account_info: dict,
|
|
config_info: dict,
|
|
):
|
|
uid = self._resolve_uid(account_info)
|
|
if not uid:
|
|
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
|
|
return
|
|
|
|
cookie = account_info.get("cookie") or ""
|
|
if not cookie:
|
|
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
|
return
|
|
|
|
sid_int = self._to_int(config_info.get("sid")) or 2203
|
|
query_options = points_center_options_from_config(config_info)
|
|
catalog = fetch_points_center_catalog(
|
|
uid,
|
|
cookie,
|
|
client=self._http_client(),
|
|
**query_options,
|
|
)
|
|
goods, message = catalog.goods, catalog.message
|
|
now = datetime.now(UTC)
|
|
worker_db.query(HuyaGoodsSnapshot).filter(
|
|
HuyaGoodsSnapshot.account_id == account.id,
|
|
HuyaGoodsSnapshot.sid == sid_int,
|
|
).delete(synchronize_session=False)
|
|
for item in goods:
|
|
worker_db.add(
|
|
HuyaGoodsSnapshot(
|
|
account_id=account.id,
|
|
sid=sid_int,
|
|
product_id=item["product_id"],
|
|
name=item["name"],
|
|
price=item["price"],
|
|
remain_text=item["remain_text"],
|
|
raw=item,
|
|
updated_at=now,
|
|
)
|
|
)
|
|
|
|
result = {
|
|
"source": "PlayMallUI.queryAllocation",
|
|
"sid": sid_int,
|
|
"page_code": query_options["page_code"],
|
|
"source_id": query_options["source_id"],
|
|
"scene": query_options["scene"],
|
|
"goods": goods,
|
|
}
|
|
if not goods:
|
|
self._mark_task(worker_db, task, "failed", message, result)
|
|
return
|
|
account.status = "goods_refreshed"
|
|
account.updated_at = now
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"success",
|
|
f"已刷新积分中心商品 {len(goods)} 个",
|
|
result,
|
|
)
|
|
|
|
def _execute_exchange_goods(
|
|
self,
|
|
worker_db: Session,
|
|
task: HuyaTask,
|
|
account: HuyaAccount,
|
|
account_info: dict,
|
|
config_info: dict,
|
|
):
|
|
submitted_product_id = normalize_spu_id(self.payload.get("product_id"))
|
|
requested_product_name = str(self.payload.get("product_name") or "").strip()
|
|
if not submitted_product_id and not requested_product_name:
|
|
self._mark_task(worker_db, task, "failed", "请选择兑换商品")
|
|
return
|
|
|
|
uid = self._resolve_uid(account_info)
|
|
if not uid:
|
|
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
|
|
return
|
|
|
|
cookie = account_info.get("cookie") or ""
|
|
if not cookie:
|
|
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
|
return
|
|
|
|
scheduled_at = self._parse_scheduled_time(self.payload.get("scheduled_at"))
|
|
if self.payload.get("scheduled_at") and scheduled_at is None:
|
|
self._mark_task(worker_db, task, "failed", "定时兑换时间格式无效")
|
|
return
|
|
if (
|
|
scheduled_at
|
|
and scheduled_at.timestamp() > time.time()
|
|
and not self._wait_until(scheduled_at, uid)
|
|
):
|
|
self._mark_task(worker_db, task, "stopped", "兑换任务已停止")
|
|
return
|
|
|
|
pid = self._to_int(config_info.get("room_pid"))
|
|
if not pid:
|
|
self._mark_task(worker_db, task, "failed", "请先配置虎牙直播间 ID")
|
|
return
|
|
pay_channel = self._normalize_pay_channel(
|
|
self.payload.get("pay_channel") or config_info.get("pay_channel")
|
|
)
|
|
exchange_use_proxy = str(
|
|
config_info.get("exchange_use_proxy") or "0"
|
|
).strip().lower() in {"1", "true", "on", "yes"}
|
|
if exchange_use_proxy:
|
|
proxies, proxy_source, proxy_error = self._exchange_proxies()
|
|
else:
|
|
proxies, proxy_source, proxy_error = None, "disabled", ""
|
|
if proxy_error:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
f"兑换代理不可用:{proxy_error}",
|
|
{"proxy_used": False, "proxy_source": "error"},
|
|
)
|
|
return
|
|
if proxies:
|
|
self._push_log("info", f"[{uid}] 兑换任务使用代理: {proxy_source}")
|
|
elif exchange_use_proxy:
|
|
self._push_log("info", f"[{uid}] 兑换任务未使用代理: {proxy_source}")
|
|
else:
|
|
self._push_log("info", f"[{uid}] 兑换任务直连(兑换代理开关关闭)")
|
|
client: Any = self._http_client(proxies=proxies)
|
|
live_goods, live_goods_error = self._resolve_live_exchange_goods(
|
|
client,
|
|
uid=uid,
|
|
cookie=cookie,
|
|
config_info=config_info,
|
|
submitted_product_id=submitted_product_id,
|
|
product_name=requested_product_name,
|
|
)
|
|
if live_goods is None:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
f"实时商品校验失败:{live_goods_error}",
|
|
{
|
|
"submitted_product_id": submitted_product_id,
|
|
"product_name": requested_product_name,
|
|
"proxy_used": bool(proxies),
|
|
"proxy_source": proxy_source,
|
|
},
|
|
)
|
|
return
|
|
spu_id = normalize_spu_id(live_goods.get("product_id"))
|
|
product_name = str(live_goods.get("name") or requested_product_name or spu_id)
|
|
prepared = prepare_exchange(
|
|
client,
|
|
uid=uid,
|
|
cookie=cookie,
|
|
pid=pid,
|
|
spu_id=spu_id,
|
|
pay_channel=pay_channel,
|
|
stopped=self._stop.is_set,
|
|
log=lambda message: self._push_log("info", message),
|
|
)
|
|
common = {
|
|
"spu_id": spu_id,
|
|
"submitted_product_id": submitted_product_id,
|
|
"resolved_product_id": spu_id,
|
|
"product_name": (prepared.target.get("name") if prepared.target else "")
|
|
or product_name,
|
|
"scheduled_at": scheduled_at.isoformat() if scheduled_at else "",
|
|
"executed_at": datetime.now(UTC).isoformat(),
|
|
"redeem_state": prepared.state.to_dict() if prepared.state else None,
|
|
"target": prepared.target,
|
|
"exchange_plan": prepared.plan,
|
|
"proxy_used": bool(proxies),
|
|
"proxy_source": proxy_source,
|
|
}
|
|
if prepared.status == EXCHANGE_STATUS_EXCHANGED:
|
|
result = {
|
|
**common,
|
|
"exchange_mode": "points_only",
|
|
"exchange_order": prepared.order,
|
|
"payment_status": "not_required",
|
|
"payment_status_label": "积分直兑",
|
|
}
|
|
account.status = "goods_exchanged"
|
|
account.updated_at = datetime.now(UTC)
|
|
if prepared.state:
|
|
account.points = prepared.state.user_score
|
|
self._mark_task(worker_db, task, "success", prepared.message, result)
|
|
return
|
|
if prepared.status != EXCHANGE_STATUS_PAYMENT_REQUIRED or not prepared.purchase:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
prepared.message,
|
|
{**common, **prepared.detail},
|
|
)
|
|
return
|
|
|
|
purchase = prepared.purchase
|
|
pay_targets = resolve_pay_targets(purchase.pay_url)
|
|
result = {
|
|
**common,
|
|
"exchange_mode": "buy_points_then_exchange",
|
|
"points_package": prepared.plan,
|
|
"points_purchase": {
|
|
"order_id": purchase.order_id,
|
|
"app_order_id": purchase.app_order_id,
|
|
"pay_order_id": purchase.pay_order_id,
|
|
"product_name": purchase.product_name,
|
|
"count": purchase.count,
|
|
"unit_price": purchase.unit_price,
|
|
"amount": purchase.amount,
|
|
"supplier_name": purchase.supplier_name,
|
|
"order": purchase.order_raw,
|
|
"pay": purchase.pay_raw,
|
|
},
|
|
"pay_url": purchase.pay_url,
|
|
"pay_qr_code": pay_targets.qr_code,
|
|
"pay_cashier_url": pay_targets.cashier_url,
|
|
"pay_qr_path": pay_qr_path(task.id) if task is not None else "",
|
|
"payment_status": "pending",
|
|
"payment_status_label": "等待支付积分包",
|
|
"payment_paid": False,
|
|
}
|
|
account.status = "exchange_payment_pending"
|
|
account.updated_at = datetime.now(UTC)
|
|
self._update_task_progress(
|
|
worker_db,
|
|
task,
|
|
"running",
|
|
f"积分不足,需先购买 {prepared.plan['name'] if prepared.plan else '积分包'},等待扫码支付",
|
|
result,
|
|
)
|
|
payment_status, _payment_order = self._wait_recharge_payment(
|
|
client=client,
|
|
uid=uid,
|
|
guid="",
|
|
cookie=cookie,
|
|
order_id=purchase.order_id,
|
|
sid=self._to_int(config_info.get("sid")) or 2203,
|
|
result=result,
|
|
)
|
|
if payment_status == "paid":
|
|
completed = complete_exchange_after_payment(
|
|
client,
|
|
uid=uid,
|
|
cookie=cookie,
|
|
pid=pid,
|
|
spu_id=spu_id,
|
|
)
|
|
result.update(
|
|
{
|
|
"post_payment_redeem_state": (
|
|
completed.state.to_dict() if completed.state else None
|
|
),
|
|
"exchange_order": completed.order,
|
|
"target": completed.target or prepared.target,
|
|
}
|
|
)
|
|
if completed.status == EXCHANGE_STATUS_EXCHANGED:
|
|
account.status = "goods_exchanged"
|
|
account.updated_at = datetime.now(UTC)
|
|
if completed.state:
|
|
account.points = completed.state.user_score
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"success",
|
|
f"积分包支付成功,{completed.message}",
|
|
result,
|
|
)
|
|
else:
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"failed",
|
|
f"积分包支付成功,但兑换失败:{completed.message}",
|
|
result,
|
|
)
|
|
return
|
|
if payment_status == "stopped":
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"stopped",
|
|
"积分包支付监听已停止",
|
|
result,
|
|
)
|
|
return
|
|
self._mark_task(
|
|
worker_db,
|
|
task,
|
|
"timeout",
|
|
f"积分包支付超时,订单 #{purchase.order_id} 仍未完成",
|
|
result,
|
|
)
|