diff --git a/core/weibo/client.py b/core/weibo/client.py index a47e742..b6fafc1 100644 --- a/core/weibo/client.py +++ b/core/weibo/client.py @@ -277,6 +277,7 @@ class WeiboClient: self.cookie = normalize_weibo_cookie(cookie) if cookie else "" self.timeout = timeout self.session = session or requests.Session() + self._last_text_url = "" self.session.headers.update( { "User-Agent": WEIBO_MOBILE_UA, @@ -336,6 +337,7 @@ class WeiboClient: timeout=self.timeout, ) response.raise_for_status() + self._last_text_url = str(getattr(response, "url", "") or url) return response.text except requests.RequestException as exc: raise WeiboTransportError(f"请求微博收银台失败: {exc}") from exc @@ -821,6 +823,12 @@ class WeiboClient: "Referer": "https://vipclub.weibo.com/", }, ) + if urlsplit(self._last_text_url).path.rstrip("/") == "/sorry": + raise WeiboRechargeError( + "微博支付页拒绝了当前账号(跳转 /sorry)。" + "CK 首页仍有效,请重新登录微博更新 CK," + "或确认该账号具备支付/助威资格" + ) parser = _CashierParser() parser.feed(html) if not parser.pay_id: diff --git a/tests/test_weibo_accounts.py b/tests/test_weibo_accounts.py index 400e8f7..ebf4d57 100644 --- a/tests/test_weibo_accounts.py +++ b/tests/test_weibo_accounts.py @@ -14,6 +14,7 @@ from core.weibo import ( WeiboExchangeResult, WeiboHomeSnapshot, WeiboPaymentStatus, + WeiboRechargeError, WeiboRechargeOrder, ) from web.backend.models import User, WeiboAccount @@ -360,6 +361,35 @@ def test_manual_recharge_order_and_status_routes_serialize( assert status["status"] == "PAY_STATUS_PENDING" +def test_recharge_failure_keeps_valid_ck_status(monkeypatch, db, current_user): + account_out = import_cookies( + WeiboCookieImport(text="SCF=a; SUB=b", tag=""), + db=db, + current=current_user, + ).accounts[0] + account = db.query(WeiboAccount).filter(WeiboAccount.id == account_out.id).one() + + def reject_recharge(*_args, **_kwargs): + raise WeiboRechargeError("微博支付页拒绝了当前账号") + + monkeypatch.setattr( + StubWeiboClient, + "create_recharge_order", + reject_recharge, + raising=False, + ) + + with pytest.raises(WeiboRechargeError, match="支付页拒绝"): + weibo_service.create_weibo_recharge_order( + db, + account, + amount_yuan=1, + ) + + assert account.ck_check_status == "valid" + assert account.ck_check_message == "" + + def test_manual_exchange_requires_confirm_and_serializes_result( monkeypatch, db, current_user ): diff --git a/tests/test_weibo_client.py b/tests/test_weibo_client.py index f0a1e6a..5d7a397 100644 --- a/tests/test_weibo_client.py +++ b/tests/test_weibo_client.py @@ -27,9 +27,10 @@ BIND_URL = ( class FakeResponse: - def __init__(self, payload): + def __init__(self, payload, url: str = ""): self.payload = payload self.text = payload if isinstance(payload, str) else "" + self.url = url def raise_for_status(self): return None @@ -52,7 +53,10 @@ class FakeSession(requests.Session): result = self.responses.pop(0) if isinstance(result, Exception): raise result - return FakeResponse(result) + response = result if isinstance(result, FakeResponse) else FakeResponse(result) + if not response.url: + response.url = url + return response def test_normalize_cookie_accepts_prefix_and_multiline(): @@ -334,6 +338,49 @@ def test_create_recharge_order_full_manual_chain(): assert create_call[2]["params"] == create_call[2]["data"] +def test_create_recharge_order_reports_payment_rejection(): + client = WeiboClient("SCF=a; SUB=b", session=FakeSession([])) + client._load_recharge_context = lambda _activity: ( + {"vuid": "vuid", "source_itemid": "20601", "F": "bowen_tjg"}, + {"tab_type": "8", "cashier_id": "205"}, + { + "tab_info": [ + { + "is_current": 1, + "tab_type": "8", + "cashier_id": "205", + "menu": [ + { + "subitemid": "2060101", + "itemid": 20601, + "appkey": 2000, + "commodity_id": "2060101", + "max_money": 1000, + } + ], + } + ] + }, + ) + client._recharge_bid = lambda *_args: "1000294191" + client._request_json = lambda *_args, **_kwargs: { + "code": 100000, + "data": { + "url": "https://pay.sc.weibo.com/api/merchant/pay/cashier?sign=test", + "orderid": "order-1", + }, + } + + def rejected_page(*_args, **_kwargs): + client._last_text_url = "https://pay.sc.weibo.com/sorry" + return "404错误" + + client._request_text = rejected_page + + with pytest.raises(WeiboRechargeError, match=r"支付页拒绝.*/sorry"): + client.create_recharge_order(amount_yuan=1, activity="bowen_tjg") + + def test_create_recharge_order_rejects_missing_dynamic_menu(): home = { "code": 100000, diff --git a/web/backend/services/weibo_service.py b/web/backend/services/weibo_service.py index 5b7df72..6e96def 100644 --- a/web/backend/services/weibo_service.py +++ b/web/backend/services/weibo_service.py @@ -166,10 +166,13 @@ def _mark_manual_failure( account: WeiboAccount, exc: Exception, ) -> None: - account.ck_check_status = _error_status(exc) - account.ck_check_message = str(exc) - account.ck_checked_at = _now() - db.commit() + """只有明确登录态失效才改动 CK 状态,业务失败不能把有效 CK 标坏。""" + + if isinstance(exc, WeiboAuthError): + account.ck_check_status = "invalid" + account.ck_check_message = str(exc) + account.ck_checked_at = _now() + db.commit() def query_weibo_mall(