From 494969d4f83044ad55a6aa2d383e77d63eb259fd Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Thu, 3 Sep 2026 17:16:00 +0800 Subject: [PATCH] fix(android): bound payment transition sampling (#210) Allow up to two consecutive evidence-free PayActivity transition samples after the single safe Back; fail on the third post-Back sample (200ms cadence) to preserve a finite no-click payment boundary. --- .../automation/PurchaseLiveAutomation.kt | 20 +++++++++++--- .../agent/PurchaseLiveAutomationTest.kt | 27 +++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt index 18779ff..b77992e 100644 --- a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt +++ b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt @@ -117,7 +117,8 @@ class PurchaseLiveAutomation( return null } val labels = linkedSetOf() - var backedOutOfPayment = false + var paymentBackAttempts = 0 + var consecutivePaymentSamplesAfterBack = 0 var backedOutOfChooser = false var restoredFromWechat = false var pddObservedAfterWechatRestore = false @@ -170,17 +171,27 @@ class PurchaseLiveAutomation( // it permits only bounded reading gestures below, never a payment click. val paymentVisible = isKnownPddPaymentActivity(snapshot) && !orderContextVisible && !unpaidContextVisible if (paymentVisible) { - if (!backedOutOfPayment) { - backedOutOfPayment = true + if (paymentBackAttempts == 0) { + paymentBackAttempts++ if (!driver.backPurchase()) { return unknown("PURCHASE_ORDER_PAYMENT_BACK_FAILED", "支付页无法安全返回订单详情") } pause(500) } else { - return unknown("PURCHASE_ORDER_PAYMENT_REPEATED", "支付页重复出现,已停止自动核单") + consecutivePaymentSamplesAfterBack++ + if (consecutivePaymentSamplesAfterBack >= ORDER_RESULT_PAYMENT_POST_BACK_MAX_SAMPLES) { + return unknown( + "PURCHASE_ORDER_PAYMENT_REPEATED", + "支付页安全返回后持续无订单证据,已停止自动核单" + + "[paymentBackAttempts=$paymentBackAttempts;" + + "consecutivePaymentSamplesAfterBack=$consecutivePaymentSamplesAfterBack]", + ) + } + pause(ORDER_RESULT_SAMPLE_INTERVAL_MS) } return@repeat } + consecutivePaymentSamplesAfterBack = 0 if (!orderContextVisible && !unpaidContextVisible) { val entries = orderDetailEntryTargets(snapshot) if (entries.size > 1) { @@ -469,6 +480,7 @@ class PurchaseLiveAutomation( const val ORDER_RESULT_MAX_SAMPLES = 60 const val ORDER_RESULT_MAX_EMPTY_SAMPLES = 15 const val ORDER_RESULT_WECHAT_RESTORE_MAX_SAMPLES = 15 + const val ORDER_RESULT_PAYMENT_POST_BACK_MAX_SAMPLES = 3 const val ORDER_RESULT_SCROLL_SAMPLE_INTERVAL = 15 const val ORDER_RESULT_SAMPLE_INTERVAL_MS = 200L } diff --git a/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt b/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt index 206f043..b67a08e 100644 --- a/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt +++ b/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt @@ -199,8 +199,25 @@ class PurchaseLiveAutomationTest { } @Test - fun `repeated known payment activity still stops without scrolling or payment clicks`() { - val driver = LiveDriver(postSubmitCaptureSequence = listOf("payment", "payment")) + fun `payment transition frame after safe back reaches unpaid order evidence without payment clicks`() { + val driver = LiveDriver(postSubmitCaptureSequence = listOf("payment", "payment", "order")) + val automation = PurchaseLiveAutomation(driver, pause = {}) + val address = automation.updateShippingAddress("_cg55") + automation.finalConfirmation(input().copy(addressSuffix = "_cg55"), address) + automation.submitOrderOnce() + + val order = automation.readOrderResult() + + assertEquals("PDD-202608210001", order?.orderNo) + assertEquals("2026-08-21T02:30:00Z", order?.submittedAt) + assertEquals(1, driver.postSubmitBackCount) + assertEquals(0, driver.genericSwipes) + assertFalse(driver.clicked.any { it.contains("支付") }) + } + + @Test + fun `continuous payment activity still stops at bounded post back samples without payment clicks`() { + val driver = LiveDriver(postSubmitCaptureSequence = List(4) { "payment" }) val automation = PurchaseLiveAutomation(driver, pause = {}) val address = automation.updateShippingAddress("_cg55") automation.finalConfirmation(input().copy(addressSuffix = "_cg55"), address) @@ -208,6 +225,12 @@ class PurchaseLiveAutomationTest { assertEquals(null, automation.readOrderResult()) assertEquals("PURCHASE_ORDER_PAYMENT_REPEATED", automation.lastOrderReadFailure?.code) + assertEquals( + "支付页安全返回后持续无订单证据,已停止自动核单" + + "[paymentBackAttempts=1;consecutivePaymentSamplesAfterBack=3]", + automation.lastOrderReadFailure?.message, + ) + assertEquals(4, driver.postSubmitCaptureCount) assertEquals(1, driver.postSubmitBackCount) assertEquals(0, driver.genericSwipes) assertFalse(driver.clicked.any { it.contains("支付") })