fix(trading): cart count status=101 识别为空车,区分空车与获取失败

This commit is contained in:
2026-08-17 01:42:08 +08:00
parent 48c6f2a28f
commit daa5555fd4
6 changed files with 279 additions and 4 deletions
+24 -4
View File
@@ -6,6 +6,10 @@ data/evidence/checkout-research-20260811/NOTES.md):
- clear_cart、remove_item 用 UI 点击 `button[aria-label^="削除"]` 路径实现,
2026-08-11 用真账号跑通下单测试时确认实际 aria-label 是「削除する」而非旧探针
记录的「削除」,selector 已改前缀匹配;modal 是否存在仍未实测确认
- cart count JSONP API 契约 2026-08-16 真账号实测(scripts/probe_cart_count.py +
.probe/cart_count/):status=101 + count="" 是**空车**(合法响应,记 count=0),
status=300(referer/sid 被拒)才是获取失败——此前一律按失败处理,
空车时 clear_cart 末尾校验拿 -1、开单前清理被误判失败
- enter_checkout 已实现到「进入 session upgrade 密码页并尝试自动复核」:购物车页
点击真正的「購入手続き」(注意页面上还有个促销用的「カード入会&購入手続き」
按钮,selector 必须精确匹配,不能 .first 模糊选)后,即使 SSO 会话仍有效,
@@ -133,6 +137,11 @@ _BASKET_PATH = "/rms/mall/bss/cartadd/set"
_CART_PAGE = auth_site.RAKUTEN_CART_URL
_CART_COUNT_API = "https://cart-api.step.rakuten.co.jp/rms/mall/cart/count/all/jsonp/"
# cart count API 的「空车」状态码(2026-08-16 真账号实测,
# scripts/probe_cart_count.py):status=101 + message "value not found." +
# count="" 表示购物车为空,是合法响应而非获取失败;status=300 之类才是被拒
_CART_EMPTY_STATUS = "101"
# 购物车页未登录标记(旧 marker;新 SPA 上不可靠,这里只作辅助判据)
_LEGACY_LOGGED_OUT_MARKER = auth_site.RAKUTEN_LOGGED_OUT_MARKER
@@ -942,6 +951,7 @@ class SiteInteractor:
策略(探针实测最稳的两步):
1. 打 cart count JSONP API:status=100 且 count>=1 才算「购物车非空」
(status=101 是空车,count 记 0,见 _query_cart_count)
2. 打开 cart 页等 SPA 渲染,在 HTML 里找 item_id
Returns:
@@ -974,7 +984,8 @@ class SiteInteractor:
返回 {logged_in, count, raw_status}。
count 是站点返回的购物车里商品总件数(含数量,非 SKU 数)。
raw_status 是站点的状态码字符串"100" 表示正常。
raw_status 是站点的状态码字符串"100" 非空、"101" 空车(count=0);
其余 status 属「获取失败」,抛 CartOperationError 而不是返回。
"""
async with self._lock:
await self._auth_session.require_logged_in("rakuten")
@@ -999,7 +1010,8 @@ class SiteInteractor:
- 若 SPA 把按钮渲染在 iframe 里,selector 失败需实测后调整
- Rakuten cart item 卡片无 data-testid,本方法不依赖 DOM 结构定位
返回 {removed_count, cart_count};cart_count=-1 表示末尾 count API 调用失败
返回 {removed_count, cart_count};cart_count=-1 表示末尾 count API 调用失败
(空车属正常结果返回 0,见 _query_cart_count 的 status=101 处理)。
"""
async with self._lock:
await self._auth_session.require_logged_in("rakuten")
@@ -1125,8 +1137,14 @@ class SiteInteractor:
async def _query_cart_count(self) -> tuple[str, int]:
"""调 cart count JSONP API,返回 (raw_status, count)
解析失败、status 非 100 都抛 CartOperationError——这是站点在告诉我们
「请求被拒了」,常见原因是 Referer 错或 cookie 失效。
站点契约(2026-08-16 真账号实测,scripts/probe_cart_count.py +
.probe/cart_count/)——必须区分「车是空的」与「获取失败」:
- status "100":购物车非空,count 是数字字符串
- status "101"(message "value not found."):**购物车为空**,count 是
空串——这是合法结果,返回 ("101", 0),不能当失败
- 其余 status(实测如 "300":referer 缺失 / sid 不可用)或响应无法解析,
才是「获取失败」,抛 CartOperationError——常见原因是 Referer 错或
cookie 失效
"""
resp = await self._context.request.get(
_CART_COUNT_API + "?sid=1010",
@@ -1137,6 +1155,8 @@ class SiteInteractor:
if not status_match:
raise CartOperationError(f"cart count 响应无法解析:{body[:200]!r}")
raw_status = status_match.group(1)
if raw_status == _CART_EMPTY_STATUS:
return raw_status, 0
if raw_status != "100":
raise CartOperationError(
f"cart count API 异常:status={raw_status} body={body[:200]!r}"