接入虎牙精英宝典最新商城前置流程

This commit is contained in:
yml2213
2026-09-01 11:42:52 +08:00
parent 684e16a07a
commit eb065a7c77
6 changed files with 243 additions and 2 deletions
+46
View File
@@ -851,6 +851,52 @@ class HuyaHttpClient:
"shopMiddleUI", "getGoodsInfoV5", req, GoodsInfoRsp, uid, guid, cookie "shopMiddleUI", "getGoodsInfoV5", req, GoodsInfoRsp, uid, guid, cookie
) )
def list_pay_channels(
self, uid: int, guid: str, cookie: str, spu_id: str, sku_id: int,
supplier_id: int = 87401, timeout: float = 15.0,
):
from .shop_structs import ListPayChannelReq, ListPayChannelRsp
req = ListPayChannelReq()
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", 4)
req.supplierId = int(supplier_id or 0)
req.spuId = spu_id
req.skuId = int(sku_id or 0)
return self.call_rpc(
"shopMiddleUI", "listPayChannelV5", req, ListPayChannelRsp,
uid, guid, cookie, timeout=timeout,
)
def check_hy_protocol(
self, uid: int, guid: str, cookie: str, timeout: float = 15.0
):
from .shop_structs import CheckHyProtocolReq, ShopCodeRsp
req = CheckHyProtocolReq()
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", 4)
return self.call_rpc(
"shopMiddleUI", "checkHyProtocolV5", req, ShopCodeRsp,
uid, guid, cookie, timeout=timeout,
)
def check_user_buy_auth(
self, uid: int, guid: str, cookie: str, spu_id: str, sku_id: int,
timeout: float = 15.0,
):
from .shop_structs import CheckUserBuyAuthReq, ShopCodeRsp
req = CheckUserBuyAuthReq()
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", 4)
req.spuId = spu_id
req.skuId = int(sku_id or 0)
return self.call_rpc(
"shopMiddleUI", "checkUserBuyAuth", req, ShopCodeRsp,
uid, guid, cookie, timeout=timeout,
)
def query_user_order_list( def query_user_order_list(
self, self,
uid, uid,
+104
View File
@@ -639,6 +639,110 @@ class QueryUserOrderListRsp(TafStruct):
} }
class ShopAppRequest(TafStruct):
"""商城前置 RPC 的公共请求字段。"""
def __init__(self):
self.userId = UserId()
self.shopAppInfo = ShopAppInfo()
def read_from(self, ins: TafInputStream):
self.userId = ins.read_struct(0, UserId) or self.userId
self.shopAppInfo = ins.read_struct(1, ShopAppInfo) or self.shopAppInfo
class ListPayChannelReq(ShopAppRequest):
def __init__(self):
super().__init__()
self.supplierId = 0
self.bizType = 5
self.gameCategoryId = 507
self.spuId = ""
self.skuId = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_struct(1, self.shopAppInfo)
os.write_int64(2, self.supplierId)
os.write_int32(3, self.bizType)
os.write_int32(4, self.gameCategoryId)
os.write_string(5, self.spuId)
os.write_int64(6, self.skuId)
class CheckHyProtocolReq(ShopAppRequest):
def __init__(self):
super().__init__()
self.protocolVersion = 1
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_struct(1, self.shopAppInfo)
os.write_int32(2, self.protocolVersion)
class CheckUserBuyAuthReq(ShopAppRequest):
def __init__(self):
super().__init__()
self.spuId = ""
self.skuId = 0
self.checkType = 3
self.payChannel = ""
self.itemCount = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_struct(1, self.shopAppInfo)
os.write_string(2, self.spuId)
os.write_int64(3, self.skuId)
os.write_int32(4, self.checkType)
os.write_string(5, self.payChannel)
os.write_int32(6, self.itemCount)
class ListPayChannelRsp(TafStruct):
"""shopMiddleUI.listPayChannelV5 响应。"""
def __init__(self):
self.code = 0
self.message = ""
self.channels: list[str] = []
def read_from(self, ins: TafInputStream):
self.channels = ins.read_list(0)
self.code = ins.read_int32(1, default=self.code)
self.message = ins.read_string(2, default=self.message)
_skip_to_struct_end(ins)
def write_to(self, os: TafOutputStream):
os.write_list(0, self.channels)
os.write_int32(1, self.code)
os.write_string(2, self.message)
def to_dict(self) -> dict:
return {"code": self.code, "message": self.message, "channels": self.channels}
class ShopCodeRsp(TafStruct):
"""checkHyProtocolV5/checkUserBuyAuth 的状态响应。"""
def __init__(self):
self.code = 0
self.message = ""
def read_from(self, ins: TafInputStream):
self.code = ins.read_int32(0, default=self.code)
self.message = ins.read_string(1, default=self.message)
_skip_to_struct_end(ins)
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.code)
os.write_string(1, self.message)
def to_dict(self) -> dict:
return {"code": self.code, "message": self.message}
class OrderDetailOrder(TafStruct): class OrderDetailOrder(TafStruct):
"""orderDetailV5 中的订单摘要;保留支付轮询所需字段。""" """orderDetailV5 中的订单摘要;保留支付轮询所需字段。"""
+40
View File
@@ -656,6 +656,46 @@ class HuyaWssClient:
"shopMiddleUI", "getGoodsInfoV5", req, GoodsInfoRsp, timeout=15.0 "shopMiddleUI", "getGoodsInfoV5", req, GoodsInfoRsp, timeout=15.0
) )
async def list_pay_channels(
self, uid: int, guid: str, cookie: str, spu_id: str, sku_id: int = 0,
supplier_id: int = 87401,
):
from .shop_structs import ListPayChannelReq, ListPayChannelRsp
req = ListPayChannelReq()
req.userId = self._build_biz_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", WSS_SHOP_SCENE)
req.supplierId = int(supplier_id or 0)
req.spuId = spu_id
req.skuId = int(sku_id or 0)
return await self.call_rpc(
"shopMiddleUI", "listPayChannelV5", req, ListPayChannelRsp, timeout=15.0
)
async def check_hy_protocol(self, uid: int, guid: str, cookie: str):
from .shop_structs import CheckHyProtocolReq, ShopCodeRsp
req = CheckHyProtocolReq()
req.userId = self._build_biz_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", WSS_SHOP_SCENE)
return await self.call_rpc(
"shopMiddleUI", "checkHyProtocolV5", req, ShopCodeRsp, timeout=15.0
)
async def check_user_buy_auth(
self, uid: int, guid: str, cookie: str, spu_id: str, sku_id: int,
):
from .shop_structs import CheckUserBuyAuthReq, ShopCodeRsp
req = CheckUserBuyAuthReq()
req.userId = self._build_biz_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", WSS_SHOP_SCENE)
req.spuId = spu_id
req.skuId = int(sku_id or 0)
return await self.call_rpc(
"shopMiddleUI", "checkUserBuyAuth", req, ShopCodeRsp, timeout=15.0
)
async def query_user_order_list( async def query_user_order_list(
self, self,
uid: int, uid: int,
@@ -117,7 +117,8 @@ UID、Cookie、guid、baseinfo、WUP requestId、支付宝 payOrderId、ctoken
- 兑换提交前读取实时详情和积分,校验可兑换状态、库存、时间窗口和余额;成功后回查积分和兑换记录。 - 兑换提交前读取实时详情和积分,校验可兑换状态、库存、时间窗口和余额;成功后回查积分和兑换记录。
- 增加 `orderDetailV5` 请求/响应结构,支付监听优先按订单号查询,旧订单列表作为 fallback。 - 增加 `orderDetailV5` 请求/响应结构,支付监听优先按订单号查询,旧订单列表作为 fallback。
- 支付超时改为终态 `timeout`,不再误报成功;支付完成后回查活动积分。 - 支付超时改为终态 `timeout`,不再误报成功;支付完成后回查活动积分。
- 支付前增加 `checkHyProtocolV5``listPayChannelV5``checkUserBuyAuth`,不再直接跳过商城前置校验。
- 商城 HTTP fallback 的 `UserId` 改为 9.1 商城 UA/空 guid 形态;商品详情 `gameId` 改为空字符串,下单仍使用 `gameId="0"` - 商城 HTTP fallback 的 `UserId` 改为 9.1 商城 UA/空 guid 形态;商品详情 `gameId` 改为空字符串,下单仍使用 `gameId="0"`
- 外部活动默认值由旧值更新为 `17096`,已有旧配置会在读取时迁移。 - 外部活动默认值由旧值更新为 `17096`,已有旧配置会在读取时迁移。
仍待下一轮实施:将批处理执行器从 HTTP fallback 迁移到统一活动/商城 WSS session;补齐 `listPayChannelV5``checkHyProtocolV5``getMyPromotion``calcOrderPromotion``checkUserBuyAuth` 的显式前置调用;按账号隔离商品快照。 仍待下一轮实施:将批处理执行器从 HTTP fallback 迁移到统一活动/商城 WSS session;补齐 `getMyPromotion``calcOrderPromotion` 的显式调用;按账号隔离商品快照。
+22 -1
View File
@@ -1,6 +1,12 @@
from core.huya.activity_structs import ActPrizeDetailItem, GetActPrizeDetailResp from core.huya.activity_structs import ActPrizeDetailItem, GetActPrizeDetailResp
from core.huya.http_client import SHOP_BIZ_UA, HuyaHttpClient from core.huya.http_client import SHOP_BIZ_UA, HuyaHttpClient
from core.huya.shop_structs import OrderDetailData, OrderDetailOrder, OrderDetailRsp from core.huya.shop_structs import (
ListPayChannelRsp,
OrderDetailData,
OrderDetailOrder,
OrderDetailRsp,
ShopCodeRsp,
)
from core.huya.taf_protocol import TafInputStream, TafOutputStream, TafType from core.huya.taf_protocol import TafInputStream, TafOutputStream, TafType
@@ -66,3 +72,18 @@ def test_order_detail_round_trip_exposes_payment_status():
assert decoded.order.orderId == 10938643 assert decoded.order.orderId == 10938643
assert decoded.order.orderStatus == 50 assert decoded.order.orderStatus == 50
assert decoded.order.payTime > 0 assert decoded.order.payTime > 0
def test_shop_preflight_response_contracts():
channels = ListPayChannelRsp()
channels.channels = ["Zfb", "Weixin"]
channels.code = 200
decoded_channels = _round_trip(channels)
assert decoded_channels.channels == ["Zfb", "Weixin"]
assert decoded_channels.code == 200
status = ShopCodeRsp()
status.code = 200
status.message = ""
decoded_status = _round_trip(status)
assert decoded_status.code == 200
@@ -434,6 +434,35 @@ class RechargeMixin:
) )
return return
protocol = client.check_hy_protocol(uid=uid, guid="", cookie=cookie)
if protocol is None or protocol.code != 200:
self._mark_task(
worker_db, task, "failed",
getattr(protocol, "message", "商城协议校验失败") or "商城协议校验失败",
{"goods": detail, "protocol": protocol.to_dict() if protocol else None},
)
return
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:
self._mark_task(
worker_db, task, "failed", "当前支付渠道不可用",
{"goods": detail, "channels": channels.to_dict() if channels else None},
)
return
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:
self._mark_task(
worker_db, task, "failed",
getattr(auth, "message", "购买权限校验失败") or "购买权限校验失败",
{"goods": detail, "auth": auth.to_dict() if auth else None},
)
return
order_resp = client.create_order( order_resp = client.create_order(
uid=uid, uid=uid,
guid="", guid="",