fix(huya): 开通商品只保留真正可购买的 spu,并清理历史死商品行
真机 2026-10-15(HAR 10.15-精英徽章两个-1 + 逐个 spu 探测):面板里的「精英徽章 hy-5872824」是积分中心「赠送指定礼物得积分」那排的礼物 spu —— getGoodsInfoV5 仍返回 200+价格,但 listPayChannelV5 返回 401「您的设备环境暂不支持购买该商品」,所以下单必报「当前支付渠道不可用」(与数量无关)。真能买的是 hy-8343549 精英徽章 ¥1 / hy-8343551 精英令 ¥10。 - 内置候选换成可购买的 4 个:hy-8328015 宝典 / hy-8327356 币 / hy-8343549 徽章 / hy-8343551 令 - 刷新商品增加「能买到」闸门:详情 200 后再校验 listPayChannelV5,非 200 或渠道为空即剔除并记录服务端原话 - 下单流程渠道失败时透出服务端消息,只有渠道真不在可用列表才说「当前支付渠道不可用(可用:…)」 - 商品列表只认「最近一次刷新」那一套:原先按账号并集返回,别的账号旧刷新留下的礼物 spu 会和最新一套叠成 7 个选项 - 刷新时清理历史死行:本账号旧行 + 全局默认行(account_id 为空)+ 其它账号里已不在候选中的 spu 行 - 测试:401 商品被剔除、渠道失败透传、列表只认最近一套、刷新清理别处死行
This commit is contained in:
@@ -134,19 +134,29 @@ def list_recharge_goods(
|
||||
):
|
||||
"""查看已缓存的虎牙充值商品快照。
|
||||
|
||||
只返回账号级快照(account_id 非空):库里还有一批 2026-07-04 写入的全局默认行
|
||||
(account_id 为空、sid=0,含旧版精英宝典/精英令/精英徽章),它们已不是当前在售
|
||||
商品,未刷新过的账号显示空列表、点一次「刷新开通商品」即可。同一 spu_id 只保留
|
||||
最新一行,避免下拉框出现同名重复项。
|
||||
商品目录是**全局同一套**(每个账号刷出来的是同样的商品),但表里是按账号存的行,
|
||||
所以这里只返回「最近一次刷新」那一整套:
|
||||
- 账号级旧刷新留下的行(真机 2026-10-15:账号 65/66 的旧礼物 spu)会和最新一套
|
||||
并集成 7 个选项,面板里就能选到「能看不能买」的商品;
|
||||
- 2026-07-04 写入的全局默认行(account_id 为空、sid=0)已不是当前在售商品,一并忽略。
|
||||
同一 spu_id 只保留最新一行,避免下拉框出现同名重复项。
|
||||
"""
|
||||
rows = (
|
||||
db.query(HuyaRechargeGoodsSnapshot)
|
||||
.filter(HuyaRechargeGoodsSnapshot.account_id.is_not(None))
|
||||
.order_by(HuyaRechargeGoodsSnapshot.id.desc())
|
||||
.order_by(
|
||||
HuyaRechargeGoodsSnapshot.updated_at.desc(),
|
||||
HuyaRechargeGoodsSnapshot.id.desc(),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
if not rows:
|
||||
return []
|
||||
newest = rows[0].updated_at
|
||||
latest: dict[str, HuyaRechargeGoodsSnapshot] = {}
|
||||
for row in rows:
|
||||
if row.updated_at != newest:
|
||||
continue
|
||||
latest.setdefault(row.spu_id, row)
|
||||
return list(latest.values())
|
||||
|
||||
|
||||
@@ -20,12 +20,22 @@ HUYA_RECHARGE_ACT_ID = 25135
|
||||
HUYA_RECHARGE_APP_SCENE = SHOP_APP_SCENE
|
||||
HUYA_PAYMENT_POLL_SECONDS = 180
|
||||
HUYA_PAYMENT_POLL_INTERVAL = 3
|
||||
# 内置开通商品候选 = 积分中心页面(m-shop.huya.com/points.html)「赠送指定礼物得积分」
|
||||
# 那一排商品,全部经 shopMiddleUI.getGoodsInfoV5 真机验证 code=200:
|
||||
# hy-8328015 精英宝典 ¥30(送300积分,2026-10-13 抓包下单商品)
|
||||
# hy-8327356 精英币 ¥0.1(已开通后首格「自定义数量」,送1积分)
|
||||
# hy-5872824 精英徽章 ¥1(送10积分)、hy-5879346 精英令 ¥10(送100积分)
|
||||
# hy-5874269 精英令*10 ¥100(送1000积分)、hy-5874270 精英令*100 ¥1000(送10000积分)
|
||||
# 内置开通商品候选。**必须同时满足** getGoodsInfoV5=200(能看到)与
|
||||
# listPayChannelV5=200(能买到)——真机(2026-10-15,hy_300546644 / uid 1199667339402):
|
||||
#
|
||||
# 可购买(两个接口都 200,支付渠道 ["Zfb","Weixin"]):
|
||||
# hy-8328015 精英宝典 ¥30(sku 7756811,2026-10-13 抓包下单商品)
|
||||
# hy-8327356 精英币 ¥0.1(sku 7759407)
|
||||
# hy-8343549 精英徽章 ¥1 (sku 7750721,10.15「精英徽章两个」抓包下单商品)
|
||||
# hy-8343551 精英令 ¥10(sku 7750723)
|
||||
# 不可购买(getGoodsInfoV5 仍返回 200 + 价格,但 listPayChannelV5 返回
|
||||
# 401「您的设备环境暂不支持购买该商品」)——这是积分中心「赠送指定礼物得积分」
|
||||
# 那一排的礼物 spu,只能送礼物拿积分,不能当商品下单:
|
||||
# hy-5872824 精英徽章 ¥1、hy-5879346 精英令 ¥10、
|
||||
# hy-5874269 精英令*10 ¥100、hy-5874270 精英令*100 ¥1000
|
||||
# (这就是之前多选数量下单报「当前支付渠道不可用」的原因:面板里选的是礼物 spu。
|
||||
# 精英令*10/*100 暂未找到可购买的 spu,等抓到对应页面再补。)
|
||||
#
|
||||
# 旧版精英宝典 hy-5879340 页面上已不再使用,从候选里去掉。
|
||||
# 旧活动接口 webActUI.getActTaskDetail(act 25135) 已下线(601 活动已下线),
|
||||
# 商品来源不能再依赖它,只能作为可选补充。
|
||||
@@ -45,33 +55,19 @@ HUYA_RECHARGE_BUILTIN_PRODUCTS = [
|
||||
"sort": 1,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5872824",
|
||||
"spu_id": "hy-8343549",
|
||||
"name": "精英徽章",
|
||||
"task_name": "购买精英徽章",
|
||||
"description": "送10积分",
|
||||
"description": "得10积分丨可兑指定返场皮肤道具",
|
||||
"sort": 2,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5879346",
|
||||
"spu_id": "hy-8343551",
|
||||
"name": "精英令",
|
||||
"task_name": "购买精英令",
|
||||
"description": "送100积分",
|
||||
"description": "得100积分",
|
||||
"sort": 3,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5874269",
|
||||
"name": "精英令*10",
|
||||
"task_name": "购买精英令*10",
|
||||
"description": "送1000积分",
|
||||
"sort": 4,
|
||||
},
|
||||
{
|
||||
"spu_id": "hy-5874270",
|
||||
"name": "精英令*100",
|
||||
"task_name": "购买精英令*100",
|
||||
"description": "送10000积分",
|
||||
"sort": 5,
|
||||
},
|
||||
]
|
||||
from .huya_runner_core import HUYA_RECHARGE_SOURCE_ID
|
||||
|
||||
@@ -374,6 +370,31 @@ class RechargeMixin:
|
||||
)
|
||||
continue
|
||||
|
||||
# 商品详情 200 只代表「能看到」:积分中心那批礼物 spu(hy-5872824 等)在这里
|
||||
# 也能返回价格,但 listPayChannelV5 会返回 401
|
||||
# 「您的设备环境暂不支持购买该商品」,选它下单必然失败。用支付渠道当
|
||||
# 「能买到」的闸门,只留真正可下单的商品(真机 2026-10-15)。
|
||||
channels = client.list_pay_channels(
|
||||
uid=uid,
|
||||
guid="",
|
||||
cookie=cookie,
|
||||
spu_id=spu_id,
|
||||
sku_id=0,
|
||||
)
|
||||
if channels is None:
|
||||
failed.append({"spu_id": spu_id, "message": "支付渠道接口无响应"})
|
||||
continue
|
||||
if channels.code != 200 or not getattr(channels, "channels", None):
|
||||
failed.append(
|
||||
{
|
||||
"spu_id": spu_id,
|
||||
"message": channels.message
|
||||
or f"该商品当前不可购买(支付渠道 {channels.code})",
|
||||
"detail": detail,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
item = {
|
||||
**candidate,
|
||||
**detail,
|
||||
@@ -390,11 +411,24 @@ class RechargeMixin:
|
||||
}
|
||||
goods.append(item)
|
||||
|
||||
# 清掉该账号的全部旧商品行(不限 sid):否则跨 sid 的旧行会和本次结果
|
||||
# 在列表接口里叠成同一商品的重复项(如 精英宝典 x2)。
|
||||
# 商品目录是全局同一套,顺手把历史遗留的「能看不能买」行也清掉:
|
||||
# ① 本账号的全部旧商品行(不限 sid:跨 sid 的旧行会和本次结果叠成重复项);
|
||||
# ② 其它账号里已经不在候选里的 spu(真机 2026-10-15:账号 65/66 还留着修复前的
|
||||
# 礼物 spu hy-5872824 等,会和最新一套并集成 7 个选项出现在面板里);
|
||||
# ③ 2026-07-04 的全局默认行(account_id 为空,早已不是在售商品)。
|
||||
kept_spus = {item["spu_id"] for item in goods}
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot).filter(
|
||||
HuyaRechargeGoodsSnapshot.account_id == account.id,
|
||||
).delete(synchronize_session=False)
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot).filter(
|
||||
HuyaRechargeGoodsSnapshot.account_id.is_(None),
|
||||
).delete(synchronize_session=False)
|
||||
if kept_spus:
|
||||
worker_db.query(HuyaRechargeGoodsSnapshot).filter(
|
||||
HuyaRechargeGoodsSnapshot.account_id.is_not(None),
|
||||
HuyaRechargeGoodsSnapshot.account_id != account.id,
|
||||
HuyaRechargeGoodsSnapshot.spu_id.notin_(kept_spus),
|
||||
).delete(synchronize_session=False)
|
||||
for item in goods:
|
||||
worker_db.add(
|
||||
HuyaRechargeGoodsSnapshot(
|
||||
|
||||
@@ -168,17 +168,27 @@ def run_open_flow(
|
||||
emit(f"[{uid}] 小店:{supplier_name or '未知'}(supplierId={supplier_id})")
|
||||
|
||||
# 3) 支付渠道
|
||||
#
|
||||
# 真机 2026-10-15:积分中心那批「礼物」spu(hy-5872824 精英徽章、hy-5879346
|
||||
# 精英令…)在这里返回 401「您的设备环境暂不支持购买该商品」,而商品详情接口
|
||||
# 仍是 200 + 价格 —— 「详情拿得到」不代表「能买」。失败时把服务端原话透出来,
|
||||
# 不要再糊一句「当前支付渠道不可用」让人以为是渠道配置问题。
|
||||
channels = client.list_pay_channels(
|
||||
uid=uid, guid="", cookie=cookie, spu_id=spu_id, sku_id=0
|
||||
)
|
||||
available_channels = set(getattr(channels, "channels", []) or [])
|
||||
if (
|
||||
channels is None
|
||||
or channels.code != 200
|
||||
or pay_channel not in available_channels
|
||||
):
|
||||
if channels is None:
|
||||
return fail("支付渠道接口无响应", detail=detail, supplier_name=supplier_name)
|
||||
if channels.code != 200:
|
||||
return fail(
|
||||
"当前支付渠道不可用",
|
||||
channels.message or f"该商品当前不可购买(支付渠道 {channels.code})",
|
||||
detail=detail,
|
||||
supplier_name=supplier_name,
|
||||
)
|
||||
available_channels = set(getattr(channels, "channels", []) or [])
|
||||
if pay_channel not in available_channels:
|
||||
channel_text = "/".join(sorted(available_channels)) or "无"
|
||||
return fail(
|
||||
f"当前支付渠道不可用(可用:{channel_text})",
|
||||
detail=detail,
|
||||
supplier_name=supplier_name,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user