"""虎牙积分中心(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, )