修复:下单后经过支付方式选择页导致订单号/下单时间读取被提前放弃 #210

Open
opened 2026-09-03 17:11:43 +08:00 by ila · 1 comment
Owner

来源与目标

2026-09-03,CG63(purchase_task #63)在 #209 修复生效后成功下单,但订单号与下单时间未回传服务端,pdd_order_no/order_submitted_at 落库为 NULL。用户报告并确认原话:「cg63采购成功,但停留在待付款页面,这里向下滑动到最下面,可以找到订单号和下单时间,获取这两个提交给admin」「但现在不会在待付款/待支付等界面向下滑动,获取订单号/订单编号 和下单时间」「只要下单成功了,来到了待支付/待付款界面就要去获取订单号和下单时间」。

目标:让 readOrderResult() 在下单成功、最终落到待付款/待支付详情页时,能可靠读取订单号与下单时间并回传,不因下单流程中途经过支付方式选择页两次而提前放弃。

当前事实(真机证据,2026-09-03)

  • purchase_task #63:status=order_result_unknown,pdd_order_no=NULL,order_submitted_at=NULL。
  • purchase_task_attempt #119(本次唯一使用 #209 修复后 APK 的采购尝试):started_at=17:00:56.041,finished_at=17:01:06.495(约 10.5 秒),error_code=PURCHASE_ORDER_PAYMENT_REPEATED,error_message=支付页重复出现,已停止自动核单,result_type=order_result_unknown。
  • 任务结束后数分钟,用手机原地读取当前无障碍控件树(未做任何操作),当前 Activity 为 com.xunmeng.pinduoduo.activity.NewPageActivity(非 PDD_PAYMENT_ACTIVITIES 集合中的 PayActivity),页面标签中确认存在:
    • 待付款承诺后天送达(含 UNPAID_MARKERS 之一「待付款」)
    • 订单编号:260903-262227956044044
    • 下单时间: 2026-09-03 17:01:04
  • 离线用与代码相同的正则复算,两者均能正确提取:
    • ORDER_NO 命中 260903-262227956044044
    • ORDER_TIME 命中 2026-09-03 17:01:04

结论:待付款详情页本身证据完整、格式合法,readOrderResult() 的解析规则本身没有问题。故障发生在到达该页面之前。

代码事实

android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt 的 readOrderResult():

val paymentVisible = isKnownPddPaymentActivity(snapshot) && !orderContextVisible && !unpaidContextVisible
if (paymentVisible) {
    if (!backedOutOfPayment) {
        backedOutOfPayment = true
        if (!driver.backPurchase()) {
            return unknown("PURCHASE_ORDER_PAYMENT_BACK_FAILED", "支付页无法安全返回订单详情")
        }
        pause(500)
    } else {
        return unknown("PURCHASE_ORDER_PAYMENT_REPEATED", "支付页重复出现,已停止自动核单")
    }
    return@repeat
}

isKnownPddPaymentActivity 只按 Activity 名判断:

private fun isKnownPddPaymentActivity(snapshot: UiSnapshot): Boolean =
    snapshot.packageName == PDD_PACKAGE && snapshot.activityName in PDD_PAYMENT_ACTIVITIES
// PDD_PAYMENT_ACTIVITIES = setOf("com.xunmeng.pinduoduo.app_pay.core.PayActivity")

采样节奏:ORDER_RESULT_SAMPLE_INTERVAL_MS = 200L,ORDER_RESULT_MAX_SAMPLES = 60(总预算 12 秒,含各类等待)。

backedOutOfPayment 是一次性布尔标志:只要 PayActivity 且当前采样无订单/待付款证据的情形出现第二次,无论是「真的卡在支付页出不来」还是「下单跳转过程中的过渡帧恰好落在同一 Activity 上、标签尚未渲染」,都会被同等对待并立即放弃整个核单,不再重试、不再多等一轮采样确认。

已知风险与约束(必须保留)

  • 这段代码的安全目的是绝不能在支付页反复停留或点击,只允许一次安全返回;不得放宽为可以点击支付页上的任何控件,也不得放宽为无限次返回尝试。
  • driver.backPurchase()、PAYMENT_MARKERS("立即支付"、"确认支付"、"输入支付密码")等现有支付识别与拦截逻辑不得削弱。
  • 属于采购下单后紧邻支付的读取逻辑,按仓库规则为高风险区域,任何放宽必须有明确的、有界的判据,不能变成"多试几次直到成功"式的无界重试。

范围

  1. Android:区分「真的困在支付页」与「过渡帧短暂命中同一 Activity」,避免第二次遇到 PayActivity 即刻放弃。具体判据由实施方案决定,需满足:判据必须基于已有的确定性证据(如 Activity 持续时长、连续采样次数、页面元素状态),不得引入新的支付页点击行为,且必须保留一个有限、可预期的总放弃上限(时间或采样次数),不得实质性无界重试。
  2. Android:为 PURCHASE_ORDER_PAYMENT_REPEATED(如保留该终态)或新增的终态追加同函数内 SPEC_PANEL_NOT_OPENED/#209 已有先例风格的标量诊断(例如经历的 Activity 序列计数、是否观测到订单证据等),不得包含节点文本、路径、控件树或截图。
  3. 补充单测:覆盖「下单后经过一次真实支付方式选择页、随后正确落到待付款详情页并读取成功」「真的连续/长时间停留在支付页应仍然放弃」两类场景,前者是本工单要修复的回归,后者是必须继续守住的安全边界。

非目标

  • 不修改服务端、采购任务状态机、PurchaseResultPayload 字段映射或数据库结构。
  • 不放宽 PAYMENT_MARKERS 识别、driver.backPurchase() 的失败处理、或允许在支付页点击任何控件。
  • 不引入无界重试或忽略总采样预算。
  • 不自动重试 CG63、不创建订单、不执行支付、不使用 OCR/VLM、不保存原始控件树或截图。

方案与设计证据

属于恢复既有「下单成功后可靠读取订单号与下单时间」行为的 Android 缺陷修复,无新页面、导航或用户可见交互变化,不需要 UI 原型。设计证据为上述真机证据(purchase_task_attempt #119 的错误与耗时、任务结束后原地读取到的待付款详情页真实文本、正则离线复算结果)以及现有 readOrderResult() 的既有安全语义。

验收

  • 复现「下单后经过支付方式选择页、随后落到待付款详情页」的场景时,能正确读取订单号与下单时间并通过 unknown/正常返回路径以外的成功路径返回 PurchaseOrderEvidence。
  • 真的持续停留在支付页(无过渡)的场景仍然在有限预算内放弃,不产生无界重试,不点击支付页任何控件。
  • 新增/调整的失败终态诊断不含节点文本、路径、原始控件树或截图。
  • Android 单测与 :app:assembleDebug 通过。
  • 真机验证 CG63 或其他任务需人工授权后单独进行,本工单实施阶段不执行下单或支付。

文档影响

如最终方案调整了 readOrderResult() 对支付页的判定语义,需同步更新 Business-Rules-and-Glossary 中相关的采购只读核单安全边界描述。按 Wiki-first 流程先改线上页面并在线回读 revision,再执行一次 sync 与一次 sync --check。

工作区已知存在与本工单无关的既有改动 docs/12-syb-erp-interface.md(曾在 #208、#209 两次阻止 sync),若再次阻止,记录原因,不得重置或覆盖。

依赖

无阻塞依赖。与 #209(已实施,待验收)为同一 CG63 采购流程的相邻阶段,#209 解决了规格入口识别,本工单解决其之后的订单结果读取。

实施分工

实施由 Codex 执行;本仓库 Gitea 无 codex 账号,指派人记为 ila。实施完成后由 Claude 按本工单验收标准审核。

## 来源与目标 2026-09-03,CG63(`purchase_task` #63)在 #209 修复生效后成功下单,但订单号与下单时间未回传服务端,`pdd_order_no`/`order_submitted_at` 落库为 `NULL`。用户报告并确认原话:「cg63采购成功,但停留在待付款页面,这里向下滑动到最下面,可以找到订单号和下单时间,获取这两个提交给admin」「但现在不会在待付款/待支付等界面向下滑动,获取订单号/订单编号 和下单时间」「只要下单成功了,来到了待支付/待付款界面就要去获取订单号和下单时间」。 目标:让 `readOrderResult()` 在下单成功、最终落到待付款/待支付详情页时,能可靠读取订单号与下单时间并回传,不因下单流程中途经过支付方式选择页两次而提前放弃。 ## 当前事实(真机证据,2026-09-03) - `purchase_task` #63:`status=order_result_unknown`,`pdd_order_no=NULL`,`order_submitted_at=NULL`。 - `purchase_task_attempt` #119(本次唯一使用 #209 修复后 APK 的采购尝试):`started_at=17:00:56.041`,`finished_at=17:01:06.495`(约 10.5 秒),`error_code=PURCHASE_ORDER_PAYMENT_REPEATED`,`error_message=支付页重复出现,已停止自动核单`,`result_type=order_result_unknown`。 - 任务结束后数分钟,用手机原地读取当前无障碍控件树(未做任何操作),当前 Activity 为 `com.xunmeng.pinduoduo.activity.NewPageActivity`(非 `PDD_PAYMENT_ACTIVITIES` 集合中的 `PayActivity`),页面标签中确认存在: - `待付款承诺后天送达`(含 `UNPAID_MARKERS` 之一「待付款」) - `订单编号:260903-262227956044044` - `下单时间: 2026-09-03 17:01:04` - 离线用与代码相同的正则复算,两者均能正确提取: - `ORDER_NO` 命中 `260903-262227956044044` - `ORDER_TIME` 命中 `2026-09-03 17:01:04` 结论:待付款详情页本身证据完整、格式合法,`readOrderResult()` 的解析规则本身没有问题。故障发生在到达该页面**之前**。 ## 代码事实 `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt` 的 `readOrderResult()`: ```kotlin val paymentVisible = isKnownPddPaymentActivity(snapshot) && !orderContextVisible && !unpaidContextVisible if (paymentVisible) { if (!backedOutOfPayment) { backedOutOfPayment = true if (!driver.backPurchase()) { return unknown("PURCHASE_ORDER_PAYMENT_BACK_FAILED", "支付页无法安全返回订单详情") } pause(500) } else { return unknown("PURCHASE_ORDER_PAYMENT_REPEATED", "支付页重复出现,已停止自动核单") } return@repeat } ``` `isKnownPddPaymentActivity` 只按 Activity 名判断: ```kotlin private fun isKnownPddPaymentActivity(snapshot: UiSnapshot): Boolean = snapshot.packageName == PDD_PACKAGE && snapshot.activityName in PDD_PAYMENT_ACTIVITIES // PDD_PAYMENT_ACTIVITIES = setOf("com.xunmeng.pinduoduo.app_pay.core.PayActivity") ``` 采样节奏:`ORDER_RESULT_SAMPLE_INTERVAL_MS = 200L`,`ORDER_RESULT_MAX_SAMPLES = 60`(总预算 12 秒,含各类等待)。 `backedOutOfPayment` 是一次性布尔标志:只要 `PayActivity` 且当前采样无订单/待付款证据的情形出现第二次,无论是「真的卡在支付页出不来」还是「下单跳转过程中的过渡帧恰好落在同一 Activity 上、标签尚未渲染」,都会被同等对待并立即放弃整个核单,不再重试、不再多等一轮采样确认。 ## 已知风险与约束(必须保留) - 这段代码的安全目的是**绝不能在支付页反复停留或点击**,只允许一次安全返回;不得放宽为可以点击支付页上的任何控件,也不得放宽为无限次返回尝试。 - `driver.backPurchase()`、`PAYMENT_MARKERS`("立即支付"、"确认支付"、"输入支付密码")等现有支付识别与拦截逻辑不得削弱。 - 属于采购下单后紧邻支付的读取逻辑,按仓库规则为高风险区域,任何放宽必须有明确的、有界的判据,不能变成"多试几次直到成功"式的无界重试。 ## 范围 1. Android:区分「真的困在支付页」与「过渡帧短暂命中同一 Activity」,避免第二次遇到 `PayActivity` 即刻放弃。具体判据由实施方案决定,需满足:判据必须基于已有的确定性证据(如 Activity 持续时长、连续采样次数、页面元素状态),不得引入新的支付页点击行为,且必须保留一个有限、可预期的总放弃上限(时间或采样次数),不得实质性无界重试。 2. Android:为 `PURCHASE_ORDER_PAYMENT_REPEATED`(如保留该终态)或新增的终态追加同函数内 `SPEC_PANEL_NOT_OPENED`/#209 已有先例风格的标量诊断(例如经历的 Activity 序列计数、是否观测到订单证据等),不得包含节点文本、路径、控件树或截图。 3. 补充单测:覆盖「下单后经过一次真实支付方式选择页、随后正确落到待付款详情页并读取成功」「真的连续/长时间停留在支付页应仍然放弃」两类场景,前者是本工单要修复的回归,后者是必须继续守住的安全边界。 ## 非目标 - 不修改服务端、采购任务状态机、`PurchaseResultPayload` 字段映射或数据库结构。 - 不放宽 `PAYMENT_MARKERS` 识别、`driver.backPurchase()` 的失败处理、或允许在支付页点击任何控件。 - 不引入无界重试或忽略总采样预算。 - 不自动重试 CG63、不创建订单、不执行支付、不使用 OCR/VLM、不保存原始控件树或截图。 ## 方案与设计证据 属于恢复既有「下单成功后可靠读取订单号与下单时间」行为的 Android 缺陷修复,无新页面、导航或用户可见交互变化,不需要 UI 原型。设计证据为上述真机证据(`purchase_task_attempt` #119 的错误与耗时、任务结束后原地读取到的待付款详情页真实文本、正则离线复算结果)以及现有 `readOrderResult()` 的既有安全语义。 ## 验收 - 复现「下单后经过支付方式选择页、随后落到待付款详情页」的场景时,能正确读取订单号与下单时间并通过 `unknown`/正常返回路径以外的成功路径返回 `PurchaseOrderEvidence`。 - 真的持续停留在支付页(无过渡)的场景仍然在有限预算内放弃,不产生无界重试,不点击支付页任何控件。 - 新增/调整的失败终态诊断不含节点文本、路径、原始控件树或截图。 - Android 单测与 `:app:assembleDebug` 通过。 - 真机验证 CG63 或其他任务需人工授权后单独进行,本工单实施阶段不执行下单或支付。 ## 文档影响 如最终方案调整了 `readOrderResult()` 对支付页的判定语义,需同步更新 `Business-Rules-and-Glossary` 中相关的采购只读核单安全边界描述。按 Wiki-first 流程先改线上页面并在线回读 revision,再执行一次 `sync` 与一次 `sync --check`。 工作区已知存在与本工单无关的既有改动 `docs/12-syb-erp-interface.md`(曾在 #208、#209 两次阻止 `sync`),若再次阻止,记录原因,不得重置或覆盖。 ## 依赖 无阻塞依赖。与 #209(已实施,待验收)为同一 CG63 采购流程的相邻阶段,#209 解决了规格入口识别,本工单解决其之后的订单结果读取。 ## 实施分工 实施由 Codex 执行;本仓库 Gitea 无 codex 账号,指派人记为 ila。实施完成后由 Claude 按本工单验收标准审核。
ila self-assigned this 2026-09-03 17:11:43 +08:00
Author
Owner

已完成并推送,待验收。

实现

分支 feat/210-order-result-payment-transition,基于 9e800ce(#209 完成态)。实施由 Codex 执行,审核与收尾由 Claude 完成。

494969d fix(android): bound payment transition sampling (#210)

  • 把一次性布尔 backedOutOfPayment 换成两个计数器 paymentBackAttempts / consecutivePaymentSamplesAfterBack。
  • 真实支付页仍然只安全返回一次(paymentBackAttempts 最多为 1,不会变成反复返回)。
  • 返回后再次落在 PayActivity 且无订单/待付款证据时,不再立即放弃,而是容忍最多 2 次连续无证据采样(200ms 节奏),第 3 次才判定为真的卡住并放弃,即总放弃上限约 600ms,仍然是有限、确定性的边界,不是无界重试。
  • 计数器在任意一次非 PayActivity 采样时清零,因此门槛是「安全返回后连续 3 次仍在支付页」,不是全局累计次数。
  • 未新增任何对支付页控件的点击;PAYMENT_MARKERS 识别与 pageProblem() 里的既有支付拦截未改动。
  • 失败终态诊断串追加标量 [paymentBackAttempts=1;consecutivePaymentSamplesAfterBack=3],不含节点文本、路径、控件树或截图,格式沿用 #209 已有先例。

验证

审核阶段由 Claude 独立重跑,均加 --rerun-tasks 强制重跑、不吃缓存:

  • ./gradlew :app:testDebugUnitTest --rerun-tasks:261 通过,0 失败,0 错误,0 跳过。本工单新增的两个用例(payment transition frame after safe back reaches unpaid order evidence without payment clicks、continuous payment activity still stops at bounded post back samples without payment clicks)已确认实际执行,且均断言 driver.clicked.none { 含"支付" }。
  • ./gradlew :app:assembleDebug --rerun-tasks:BUILD SUCCESSFUL。
  • git diff --check:clean。
  • 提交内容核对:仅 PurchaseLiveAutomation.kt 与对应单测 2 个文件;工作区既有无关改动(docs/12-syb-erp-interface.md、server/config/settings.yml 及未跟踪文件)未被修改、未被暂存、未被提交。

已知局限(需要记录,供后续判断)

新增的 3 次 / 约 600ms 容忍窗口是 Codex 基于工单描述的安全约束自行设计的固定值,未用真实故障现场的过渡帧数量校准——CG63 失败当时的具体过渡帧序列已经无法回放(手机页面已翻篇,仅在任务结束数分钟后原地读到了最终稳定的待付款详情页)。该窗口大概率覆盖本次已观测到的场景,但不保证覆盖所有更慢的设备/网络过渡。若后续再次出现 PURCHASE_ORDER_PAYMENT_REPEATED,优先怀疑该阈值需要调大,而非出现了新根因。

文档

本次改动是既有安全读取行为的内部实现调整(判据从一次性布尔改为有界计数),未改变 Business-Rules-and-Glossary 中已记录的「只允许一次安全返回、不点击支付控件」等安全边界的对外描述,判定为无长期文档影响,未更新 Wiki,未执行 sync。

未执行

  • 未合并到 main,未推送到 main。分支已推送至 origin/feat/210-order-result-payment-transition。
  • 未安装本次 APK 到真机(后续步骤单独执行)。
  • 未领取或重试 CG63 或其他任务,未创建订单,未执行支付。
  • 真机验证需人工授权后单独进行。
已完成并推送,待验收。 ## 实现 分支 `feat/210-order-result-payment-transition`,基于 `9e800ce`(#209 完成态)。实施由 Codex 执行,审核与收尾由 Claude 完成。 `494969d fix(android): bound payment transition sampling (#210)` - 把一次性布尔 `backedOutOfPayment` 换成两个计数器 `paymentBackAttempts` / `consecutivePaymentSamplesAfterBack`。 - 真实支付页仍然只安全返回一次(`paymentBackAttempts` 最多为 1,不会变成反复返回)。 - 返回后再次落在 `PayActivity` 且无订单/待付款证据时,不再立即放弃,而是容忍最多 2 次连续无证据采样(200ms 节奏),第 3 次才判定为真的卡住并放弃,即总放弃上限约 600ms,仍然是有限、确定性的边界,不是无界重试。 - 计数器在任意一次非 `PayActivity` 采样时清零,因此门槛是「安全返回后连续 3 次仍在支付页」,不是全局累计次数。 - 未新增任何对支付页控件的点击;`PAYMENT_MARKERS` 识别与 `pageProblem()` 里的既有支付拦截未改动。 - 失败终态诊断串追加标量 `[paymentBackAttempts=1;consecutivePaymentSamplesAfterBack=3]`,不含节点文本、路径、控件树或截图,格式沿用 #209 已有先例。 ## 验证 审核阶段由 Claude 独立重跑,均加 `--rerun-tasks` 强制重跑、不吃缓存: - `./gradlew :app:testDebugUnitTest --rerun-tasks`:**261 通过,0 失败,0 错误,0 跳过**。本工单新增的两个用例(`payment transition frame after safe back reaches unpaid order evidence without payment clicks`、`continuous payment activity still stops at bounded post back samples without payment clicks`)已确认实际执行,且均断言 `driver.clicked.none { 含"支付" }`。 - `./gradlew :app:assembleDebug --rerun-tasks`:BUILD SUCCESSFUL。 - `git diff --check`:clean。 - 提交内容核对:仅 `PurchaseLiveAutomation.kt` 与对应单测 2 个文件;工作区既有无关改动(`docs/12-syb-erp-interface.md`、`server/config/settings.yml` 及未跟踪文件)未被修改、未被暂存、未被提交。 ## 已知局限(需要记录,供后续判断) 新增的 3 次 / 约 600ms 容忍窗口是 Codex 基于工单描述的安全约束自行设计的固定值,**未用真实故障现场的过渡帧数量校准**——CG63 失败当时的具体过渡帧序列已经无法回放(手机页面已翻篇,仅在任务结束数分钟后原地读到了最终稳定的待付款详情页)。该窗口大概率覆盖本次已观测到的场景,但不保证覆盖所有更慢的设备/网络过渡。若后续再次出现 `PURCHASE_ORDER_PAYMENT_REPEATED`,优先怀疑该阈值需要调大,而非出现了新根因。 ## 文档 本次改动是既有安全读取行为的内部实现调整(判据从一次性布尔改为有界计数),未改变 `Business-Rules-and-Glossary` 中已记录的「只允许一次安全返回、不点击支付控件」等安全边界的对外描述,判定为**无长期文档影响**,未更新 Wiki,未执行 `sync`。 ## 未执行 - 未合并到 main,未推送到 main。分支已推送至 `origin/feat/210-order-result-payment-transition`。 - 未安装本次 APK 到真机(后续步骤单独执行)。 - 未领取或重试 CG63 或其他任务,未创建订单,未执行支付。 - 真机验证需人工授权后单独进行。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#210