结算流程用真实下单证据修正:新卡代填 iframe vault、订单号正则、登录态判据
- 新卡代填改用真实 DOM 结构:卡号/有效期分别托管在 Rakuten PCI 代付 vault 的 跨域 iframe 里,此前按 autocomplete/name 猜的 selector 在主文档里根本找不到 元素;持卡人姓名字段改用「名義人」标签相对定位,不用 placeholder 示例文案 (TARO RAKUTEN 只是示例用户名,不是稳定标识) - 订单号正则修正 实体导致的分隔符匹配失败(2026-08-13 真实下单验证) - login_runner._is_logged_in 改用与 AuthSession 一致的 __INITIAL_STATE__ 判据, 修掉此前误判「已登录」导致保存无效 storage_state 的问题 - 新增 CheckoutBlockedError:站点风控拦截(session upgrade/3DS)转 needs_human, 不当普通失败重试 - account.yaml 支持 phone / payment.credit-card 字段 - data/evidence/(真实 PII)加入 .gitignore Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ import contextlib
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.shared.errors import AppError, OrderGuardError
|
||||
from app.shared.errors import AppError, CheckoutBlockedError, OrderGuardError
|
||||
from app.shared.task_state import OrderState, TaskStatus
|
||||
from app.trading.worker import verify
|
||||
from app.trading.worker.client import GatewayClient
|
||||
@@ -203,6 +203,21 @@ class WorkerRunner:
|
||||
terminal=True,
|
||||
terminal_status=TaskStatus.NEEDS_HUMAN,
|
||||
)
|
||||
except CheckoutBlockedError as exc:
|
||||
# 站点风控拦截(session upgrade / 3DS 等):转 needs_human 交人工接管,
|
||||
# 不当普通失败重试(规格 §10.1)
|
||||
logger.warning(
|
||||
"结算被站点风控拦截,转 needs_human:task_id=%s msg=%s",
|
||||
task.task_id,
|
||||
exc.message,
|
||||
)
|
||||
await self._report_safe(
|
||||
task,
|
||||
state=_coerce_state(task.known_state),
|
||||
detail=exc.message,
|
||||
terminal=True,
|
||||
terminal_status=TaskStatus.NEEDS_HUMAN,
|
||||
)
|
||||
except AppError as exc:
|
||||
logger.warning(
|
||||
"执行失败:task_id=%s code=%s msg=%s",
|
||||
@@ -256,6 +271,7 @@ class WorkerRunner:
|
||||
action=self._noop(),
|
||||
state=OrderState.CREATED,
|
||||
detail=f"下单确认页已解析:应付 {summary.payable_yen} 円",
|
||||
html=checkout_html,
|
||||
evidence_meta={
|
||||
"payable_yen": summary.payable_yen,
|
||||
"site_order_id": summary.site_order_id,
|
||||
@@ -333,16 +349,24 @@ class WorkerRunner:
|
||||
payable_yen: int | None = None,
|
||||
pay_deadline: str | None = None,
|
||||
evidence_meta: dict | None = None,
|
||||
html: str | None = None,
|
||||
) -> None:
|
||||
"""单步执行:动作 → 落证据 → 写本地 → 回报 gateway"""
|
||||
await action()
|
||||
"""单步执行:动作 → 落证据 → 写本地 → 回报 gateway
|
||||
|
||||
`action` 的返回值若是字符串,视为该步骤要落盘的页面 HTML(add_to_cart /
|
||||
verify_cart 均已改为返回渲染后的页面);显式传入的 `html` 优先级更高,
|
||||
供 step3 等已经单独拿到页面 HTML 的调用点直接使用。
|
||||
"""
|
||||
result = await action()
|
||||
if html is None and isinstance(result, str):
|
||||
html = result
|
||||
meta = {
|
||||
"step": step_name,
|
||||
"state": state.value,
|
||||
**(evidence_meta or {}),
|
||||
}
|
||||
evidence_ref = self._evidence.write_step(
|
||||
task.task_id, step_no, step_name, meta=meta
|
||||
task.task_id, step_no, step_name, html=html, meta=meta
|
||||
)
|
||||
await self._db.index_evidence(task.task_id, step_no, step_name, evidence_ref)
|
||||
await self._db.record_event(
|
||||
|
||||
Reference in New Issue
Block a user