"""限购商品的旧订单复用:已有未支付订单时不该「已超过该商品的购买上限」直接失败。 生产反馈(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 = "", channel_code: int = 200, channel_message: str = "", channels=("Zfb",), ): 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.channel_code = channel_code self.channel_message = channel_message self.channels = list(channels) 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=self.channel_code, message=self.channel_message, channels=self.channels ) 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_unbuyable_goods_surfaces_channel_message(): """能看不能买的商品(listPayChannelV5 401):把服务端原话透出来。 真机 2026-10-15:hy-5872824「精英徽章」是积分中心的礼物 spu,getGoodsInfoV5 返回 200 + ¥1,但支付渠道接口返回 401「您的设备环境暂不支持购买该商品」。 以前一律报「当前支付渠道不可用」,看不出是商品不能买。 """ client = _Client( create_code=200, channel_code=401, channel_message="您的设备环境暂不支持购买该商品", channels=(), ) outcome = _run(client) assert outcome.ok is False assert outcome.message == "您的设备环境暂不支持购买该商品" # 渠道校验没过就不该下单、更不该起支付 assert client.create_calls == 0 assert client.pay_order_ids == [] def test_unavailable_pay_channel_lists_what_is_available(): client = _Client(create_code=200, channels=("Weixin",)) outcome = _run(client) # 默认走 Zfb assert outcome.ok is False assert "当前支付渠道不可用" in outcome.message assert "Weixin" in outcome.message assert client.create_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