"""虎牙积分中心(m-shop.huya.com/points.html)开通 / 买币下单链路。
2026-10-13「开通精英宝典 / 购买幸运币」抓包实证的帧顺序:
getGoodsInfoV5 → getSupplierInfoV5 → listPayChannelV5 → checkHyProtocolV5
→ PlayMallUI.getMyPromotion → calcOrderPromotion → acceptHyProtocolV5
→ checkUserBuyAuth → createOrderV5 → payOrderSubmitV5 → orderDetailV5(轮询)
本模块只负责「准备 → 下单 → 起支付」;支付到账轮询由调用方决定(任务侧会轮询,
外部 CK 批量开通只出支付码)。任务执行器与外部 CK 批量开通共用这里,避免链路
在两处各写一遍。
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any
#: 小黄车供应商(和平精英官方小店),抓包实测。
SUPPLIER_ID_YELLOW_CAR = 87401
@dataclass
class ShopOrderOutcome:
"""开通链路的结果(ok=False 时 message 是失败原因)。"""
ok: bool
message: str
detail: dict = field(default_factory=dict)
supplier_name: str = ""
promotion: dict | None = None
promotion_info: dict | None = None
order_raw: dict = field(default_factory=dict)
pay_raw: dict = field(default_factory=dict)
order_id: int = 0
app_order_id: str = ""
pay_order_id: str = ""
pay_url: str = ""
amount: int = 0
pay_channel: str = "Zfb"
sku_id: int = 0
unit_price: int = 0
count: int = 1
product_name: str = ""
def run_open_flow(
client: Any,
*,
uid: int,
cookie: str,
pid: int,
spu_id: str,
sku_id: int = 0,
count: int = 1,
pay_channel: str = "Zfb",
supplier_id: int = SUPPLIER_ID_YELLOW_CAR,
snapshot_price: int = 0,
log: Callable[[str], None] | None = None,
stopped: Callable[[], bool] | None = None,
) -> ShopOrderOutcome:
"""执行抓包链路;成功时返回「已生成支付二维码」的订单信息。"""
def emit(message: str) -> None:
if log is not None:
log(message)
def fail(message: str, **extra: Any) -> ShopOrderOutcome:
return ShopOrderOutcome(ok=False, message=message, **extra)
# 1) 商品详情(拿 skuId / 价格 / 商品名)
detail_resp = client.get_goods_info(
uid=uid,
guid="",
cookie=cookie,
pid=pid,
spu_id=spu_id,
sku_id=sku_id or 0,
game_id="",
)
if detail_resp is None:
return fail("虎牙充值商品详情接口无响应")
detail = detail_resp.to_dict()
if detail_resp.code != 200:
return fail(
detail_resp.message or f"虎牙充值商品详情获取失败: {detail_resp.code}",
detail=detail,
)
sku_id = int(detail.get("sku_id") or sku_id or 0)
product_name = str(detail.get("name") or spu_id)
unit_price = int(detail.get("price") or snapshot_price or 0)
if not sku_id:
return fail("充值商品缺少 SKU,请先刷新充值商品列表", detail=detail)
# 2) 供应商(小店)信息
supplier = client.get_supplier_info(uid=uid, guid="", cookie=cookie)
supplier_name = ""
if supplier is not None:
raw_name = supplier.value("tag2", "tag0")
supplier_name = raw_name if isinstance(raw_name, str) else ""
emit(f"[{uid}] 小店:{supplier_name or '未知'}(supplierId={supplier_id})")
# 3) 支付渠道
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
):
return fail(
"当前支付渠道不可用",
detail=detail,
supplier_name=supplier_name,
)
# 4) 商城协议校验
protocol = client.check_hy_protocol(uid=uid, guid="", cookie=cookie)
if protocol is None or protocol.code != 200:
return fail(
getattr(protocol, "message", "") or "商城协议校验失败",
detail=detail,
supplier_name=supplier_name,
)
# 5) 返利/促销查询 + 下单试算
promotion_info = client.play_mall_get_my_promotion(
uid=uid,
guid="",
cookie=cookie,
spu_id=spu_id,
sku_id=sku_id,
price=unit_price,
)
promotion = client.calc_order_promotion(
uid=uid,
guid="",
cookie=cookie,
pid=pid,
spu_id=spu_id,
sku_id=sku_id,
item_count=count,
)
promotion_dict = promotion.to_dict() if promotion is not None else None
if promotion is not None and promotion.code not in (0, 200):
emit(f"[{uid}] 促销试算返回 {promotion.code}:{promotion.message}")
# 6) 同意商城协议(抓包:这一步在 checkUserBuyAuth 之前)
accept = client.accept_hy_protocol(uid=uid, guid="", cookie=cookie)
if accept is None or accept.code != 200:
return fail(
getattr(accept, "message", "") or "虎牙商城协议确认失败",
detail=detail,
supplier_name=supplier_name,
promotion=promotion_dict,
promotion_info=(
promotion_info.to_dict() if promotion_info is not None else None
),
)
if stopped is not None and stopped():
return fail("已停止", detail=detail, supplier_name=supplier_name)
# 7) 购买权限
auth = client.check_user_buy_auth(
uid=uid, guid="", cookie=cookie, spu_id=spu_id, sku_id=sku_id
)
if auth is None or auth.code != 200:
return fail(
getattr(auth, "message", "") or "购买权限校验失败",
detail=detail,
supplier_name=supplier_name,
promotion=promotion_dict,
)
# 8) 下单
order_resp = client.create_order(
uid=uid,
guid="",
cookie=cookie,
pid=pid,
spu_id=spu_id,
sku_id=sku_id,
item_count=count,
game_id="0",
order_type=6,
)
common = {
"detail": detail,
"supplier_name": supplier_name,
"promotion": promotion_dict,
"promotion_info": (
promotion_info.to_dict() if promotion_info is not None else None
),
"sku_id": sku_id,
"unit_price": unit_price,
"count": count,
"product_name": product_name,
"pay_channel": pay_channel,
}
if order_resp is None:
return fail("虎牙下单接口无响应", **common)
order_raw = order_resp.to_dict()
common["order_raw"] = order_raw
if order_resp.code != 200 or not order_resp.orderId:
return fail(
order_resp.message or f"虎牙下单失败: {order_resp.code}",
**common,
)
# 9) 起支付(出支付宝/微信二维码)
pay_resp = client.pay_order_submit(
uid=uid,
guid="",
cookie=cookie,
order_id=order_resp.orderId,
pay_type=pay_channel,
pid=pid,
item_count=count,
)
if pay_resp is None:
return fail("虎牙支付接口无响应", **common)
pay_raw = pay_resp.to_dict()
common["pay_raw"] = pay_raw
if pay_resp.code != 200 or not pay_resp.payUrl:
return fail(
pay_resp.message or f"虎牙支付二维码生成失败: {pay_resp.code}",
**common,
)
amount = int(pay_resp.amount or unit_price * count or 0)
return ShopOrderOutcome(
ok=True,
message="已生成支付二维码",
order_id=int(order_resp.orderId),
app_order_id=pay_resp.appOrderId,
pay_order_id=pay_resp.payOrderId,
pay_url=pay_resp.payUrl,
amount=amount,
**common,
)