Files
live-hub-py/tests/test_huya_shop_flow_reuse.py
T
yml2213 0b9a01ab3f feat(huya): 开通支付码复用未支付旧单 + 二维码改用支付宝收银台直达地址
- 限购商品(精英宝典 buy_limit=1)挂着未支付旧单时,真正先失败的是购买权限
  校验 checkUserBuyAuth(真机 703「已超过该商品的购买上限,下单失败~」),
  createOrderV5 根本不会执行;现在 checkUserBuyAuth 与 createOrderV5 两处失败
  都会回退 find_reusable_order,拿同一商品的未支付旧单继续 payOrderSubmitV5。
- 新增支付宝收银台直达地址解析(resolve_pay_entry_url):跟随网关 302 后的
  excashier.alipay.com/standard/auth.htm?payOrderId=…(93 字符,原始网关地址
  1616 字符)写入结果 pay_cashier_url;解析失败回落本站短链
  /api/huya/pay/<task_id>?k=<hmac>,后端跑在 localhost 时客户也扫得开。
- 前端 QRCode 显式 color="#000" bgColor="#fff":antd v6 暗色主题默认
  color=token.colorText、bgColor=transparent,白码落在白底容器里根本看不见。
- 支付成功后列表「二维码」列不再展示二维码(qrDoneLabel → 已支付/已开通)。

测试:pytest 767 passed / 6 skipped;typecheck、eslint、ruff 通过。
2026-10-15 19:28:26 +08:00

311 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""限购商品的旧订单复用:已有未支付订单时不该「已超过该商品的购买上限」直接失败。
生产反馈(2026-10-15):账号上挂着上一张没付掉的宝典订单时,先是**购买权限校验**
`checkUserBuyAuth` 返回 703
已超过该商品的购买上限,下单失败~(如有未支付订单,请及时完成支付)
(后端日志 18:34 / 18:48 / 19:00 三批都停在 checkUserBuyAuth,createOrderV5 根本没发),
下单接口 `createOrderV5` 也可能返回同一句。限购 1 件的商品,旧单没付掉就再也下不了
新单 —— 所以这两种失败都要复用那张旧单去发起支付(官方页面同样是复用旧单继续付)。
"""
from types import SimpleNamespace
from web.backend.services.huya_shop_flow import find_reusable_order, run_open_flow
BUY_LIMIT_MESSAGE = (
"已超过该商品的购买上限,下单失败~(如有未支付订单,请及时完成支付)"
)
SPU_ID = "hy-8328015"
UNPAID_ELITE_ORDER = {
"order_id": "11346649",
"item_name": "精英宝典",
"order_status": 10,
"pay_time": 0,
"goods_detail": {"spu_id": SPU_ID, "sku_id": 7756811},
}
PAID_ELITE_ORDER = {
"order_id": "11346648",
"item_name": "精英宝典",
"order_status": 60,
"pay_time": 1791895396000,
"goods_detail": {"spu_id": SPU_ID, "sku_id": 7756811},
}
OTHER_GOODS_ORDER = {
"order_id": "11346647",
"item_name": "精英币",
"order_status": 10,
"pay_time": 0,
"goods_detail": {"spu_id": "hy-8327356", "sku_id": 7759407},
}
#: 真机 2026-10-15:账号 66 的旧单被用户取消 → order_status=90、pay_time=0
CANCELLED_ELITE_ORDER = {
"order_id": "11357769",
"item_name": "精英宝典",
"order_status": 90,
"pay_time": 0,
"goods_detail": {"spu_id": SPU_ID, "sku_id": 7756811},
}
class _Order(SimpleNamespace):
def to_dict(self) -> dict:
return dict(self.__dict__)
class _OrderList(SimpleNamespace):
pass
class _Client:
"""商城链路假客户端:只造流程需要的那几个返回。"""
def __init__(
self,
*,
create_code: int,
create_message: str = "",
orders=None,
auth_code: int = 200,
auth_message: str = "",
):
self.create_code = create_code
self.create_message = create_message
self.orders = orders or []
self.auth_code = auth_code
self.auth_message = auth_message
self.pay_order_ids: list[int] = []
self.order_list_calls = 0
self.create_calls = 0
def get_goods_info(self, **kwargs):
return SimpleNamespace(
code=200,
message="",
to_dict=lambda: {
"sku_id": 7756811,
"name": "精英宝典",
"price": 3000,
},
)
def get_supplier_info(self, **kwargs):
resp = SimpleNamespace(code=200, message="", payload={})
resp.value = lambda *path: "和平精英官方小店"
return resp
def list_pay_channels(self, **kwargs):
return SimpleNamespace(code=200, message="", channels=["Zfb"])
def check_hy_protocol(self, **kwargs):
return SimpleNamespace(code=200, message="")
def play_mall_get_my_promotion(self, **kwargs):
return SimpleNamespace(code=200, message="", to_dict=lambda: {"code": 200})
def calc_order_promotion(self, **kwargs):
return SimpleNamespace(code=200, message="", to_dict=lambda: {"code": 200})
def accept_hy_protocol(self, **kwargs):
return SimpleNamespace(code=200, message="")
def check_user_buy_auth(self, **kwargs):
return SimpleNamespace(code=self.auth_code, message=self.auth_message)
def create_order(self, **kwargs):
self.create_calls += 1
if self.create_code == 200:
return SimpleNamespace(
code=200,
message="",
orderId=11346649,
to_dict=lambda: {"order_id": 11346649, "code": 200},
)
return SimpleNamespace(
code=self.create_code,
message=self.create_message,
orderId=0,
to_dict=lambda: {"code": self.create_code, "message": self.create_message},
)
def query_user_order_list(self, **kwargs):
self.order_list_calls += 1
return _OrderList(
totalCount=len(self.orders),
orders=[_Order(**item) for item in self.orders],
)
def pay_order_submit(self, **kwargs):
self.pay_order_ids.append(int(kwargs.get("order_id") or 0))
return SimpleNamespace(
code=200,
message="",
payUrl="https://openapi.alipay.com/gateway.do?probe=1",
appOrderId="app-1",
payOrderId="pay-1",
amount=3000,
to_dict=lambda: {
"code": 200,
"pay_url": "https://openapi.alipay.com/gateway.do?probe=1",
},
)
def _run(client: _Client):
return run_open_flow(
client,
uid=1199665170519,
cookie="yyuid=1199665170519",
pid=1199650619883,
spu_id=SPU_ID,
sku_id=7756811,
count=1,
pay_channel="Zfb",
)
def test_reuse_unpaid_order_when_create_hits_buy_limit():
client = _Client(
create_code=703, create_message=BUY_LIMIT_MESSAGE, orders=[UNPAID_ELITE_ORDER]
)
outcome = _run(client)
assert outcome.ok is True
assert outcome.order_id == 11346649 # 复用旧单
assert outcome.message == "已复用未支付订单 #11346649"
assert outcome.pay_url.startswith("https://openapi.alipay.com/gateway.do")
assert client.pay_order_ids == [11346649] # 支付发给的是旧单
assert client.create_calls == 1 # 先试过下单,失败后才复用
assert outcome.reused_order["order_id"] == "11346649"
def test_auth_buy_limit_reuses_unpaid_order_without_creating():
"""真机 2026-10-15:限购校验先报 703,这时复用旧单且不该再发下单请求。"""
client = _Client(
create_code=200,
orders=[UNPAID_ELITE_ORDER],
auth_code=703,
auth_message=BUY_LIMIT_MESSAGE,
)
outcome = _run(client)
assert outcome.ok is True
assert outcome.order_id == 11346649
assert outcome.message == "已复用未支付订单 #11346649"
assert client.pay_order_ids == [11346649]
assert client.create_calls == 0
assert outcome.reused_order["order_status"] == 10
def test_auth_buy_limit_without_unpaid_order_keeps_original_error():
"""没有可复用的旧单时保持原样:报限购错误、不下单、不起支付。"""
client = _Client(
create_code=200,
orders=[],
auth_code=703,
auth_message=BUY_LIMIT_MESSAGE,
)
outcome = _run(client)
assert outcome.ok is False
assert outcome.message == BUY_LIMIT_MESSAGE
assert client.pay_order_ids == []
assert client.create_calls == 0
def test_auth_buy_limit_ignores_paid_and_cancelled_orders():
client = _Client(
create_code=200,
orders=[CANCELLED_ELITE_ORDER, PAID_ELITE_ORDER],
auth_code=703,
auth_message=BUY_LIMIT_MESSAGE,
)
outcome = _run(client)
assert outcome.ok is False
assert outcome.message == BUY_LIMIT_MESSAGE
assert client.pay_order_ids == []
def test_auth_no_response_fails_without_touching_orders():
client = _Client(create_code=200, orders=[UNPAID_ELITE_ORDER], auth_code=200)
client.check_user_buy_auth = lambda **kwargs: None
outcome = _run(client)
assert outcome.ok is False
assert outcome.message == "购买权限校验接口无响应"
assert client.create_calls == 0
assert client.order_list_calls == 0
def test_paid_order_is_not_reused():
client = _Client(
create_code=703, create_message=BUY_LIMIT_MESSAGE, orders=[PAID_ELITE_ORDER]
)
outcome = _run(client)
assert outcome.ok is False
assert outcome.message == BUY_LIMIT_MESSAGE
assert client.pay_order_ids == []
def test_cancelled_order_is_not_reused():
"""已取消(90)的旧单不能复用(真机:用户取消后状态变 90)。"""
client = _Client(
create_code=703,
create_message=BUY_LIMIT_MESSAGE,
orders=[CANCELLED_ELITE_ORDER],
)
outcome = _run(client)
assert outcome.ok is False
assert client.pay_order_ids == []
def test_other_goods_order_is_not_reused():
client = _Client(
create_code=703, create_message=BUY_LIMIT_MESSAGE, orders=[OTHER_GOODS_ORDER]
)
outcome = _run(client)
assert outcome.ok is False
assert client.pay_order_ids == []
def test_create_success_does_not_look_up_old_orders():
client = _Client(create_code=200)
outcome = _run(client)
assert outcome.ok is True
assert outcome.message == "已生成支付二维码"
assert outcome.reused_order == {}
# 下单成功就不该多打一次订单列表
assert client.order_list_calls == 0
def test_find_reusable_order_skips_to_next_page():
class _PagedClient(_Client):
def query_user_order_list(self, **kwargs):
self.order_list_calls += 1
offset = int(kwargs.get("offset") or 0)
page = [OTHER_GOODS_ORDER] * 20 if offset == 0 else [UNPAID_ELITE_ORDER]
return _OrderList(totalCount=21, orders=[_Order(**item) for item in page])
client = _PagedClient(create_code=200)
reused = find_reusable_order(client, uid=1, cookie="c", spu_id=SPU_ID)
assert reused["order_id"] == "11346649"
assert client.order_list_calls == 2