对齐 Android 立即收款页面 UI,并补齐推送状态与语音开关。

统一收款详情布局、设置金额弹窗和收款记录样式,接入 type 6/1 推送驱动与轮询 fallback,使 iOS 收款反馈与 Android 一致。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 15:17:09 +08:00
parent 5bdf4a7dbf
commit 3dcfb99254
11 changed files with 924 additions and 228 deletions

View File

@ -14,7 +14,7 @@ final class PaymentViewModelTests: XCTestCase {
///
func testLoadPayCodeWithoutScenicClearsState() async {
let api = PaymentMockAPI()
let viewModel = PaymentCollectionViewModel()
let viewModel = makeViewModel()
viewModel.staticPayUrl = "old"
await viewModel.loadPayCode(api: api, scenicId: nil)
@ -28,7 +28,7 @@ final class PaymentViewModelTests: XCTestCase {
func testLoadPayCodeUsesScenicIdAndAppliesStaticURL() async {
let api = PaymentMockAPI()
api.payCodeResponse = PayCodeResponse(staticPayUrl: "https://pay.example.com/static", dynamicPayUrl: "https://pay.example.com/dynamic")
let viewModel = PaymentCollectionViewModel()
let viewModel = makeViewModel()
await viewModel.loadPayCode(api: api, scenicId: 9)
@ -39,7 +39,7 @@ final class PaymentViewModelTests: XCTestCase {
/// URL
func testApplyDynamicAmountBuildsDynamicURL() async {
let viewModel = PaymentCollectionViewModel()
let viewModel = makeViewModel()
viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
viewModel.amountText = "12.345"
viewModel.remarkText = "茶水"
@ -50,7 +50,71 @@ final class PaymentViewModelTests: XCTestCase {
XCTAssertEqual(viewModel.status, .waiting)
}
///
///
func testConfirmAmountUpdatesQRCode() {
let viewModel = makeViewModel()
viewModel.staticPayUrl = "https://pay.example.com/static"
viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
viewModel.amountText = "18.50"
viewModel.remarkText = "门票"
XCTAssertTrue(viewModel.confirmAmount())
XCTAssertTrue(viewModel.currentPayUrl.contains("amount=18.5"))
XCTAssertEqual(viewModel.status, .waiting)
XCTAssertNil(viewModel.scanSuccessTime)
XCTAssertNil(viewModel.paymentSuccessData)
}
///
func testHandlePaymentPushScanSuccess() {
let viewModel = makeViewModel()
viewModel.paymentSuccessData = PaymentSuccessDisplayData(
orderAmount: "10",
orderNumber: "A",
payTime: "2026-06-22 10:00",
remark: ""
)
viewModel.handlePaymentPush(type: 6, dataJSON: nil)
XCTAssertNotNil(viewModel.scanSuccessTime)
XCTAssertNil(viewModel.paymentSuccessData)
}
///
func testHandlePaymentPushPaymentSuccess() {
let viewModel = makeViewModel()
viewModel.scanSuccessTime = "2026-06-22 10:00"
let payload = """
{"order_amount":"88.00","order_number":"ORDER-1","pay_time":"2026-06-22 10:01","remark":""}
"""
viewModel.handlePaymentPush(type: 1, dataJSON: payload)
XCTAssertNil(viewModel.scanSuccessTime)
XCTAssertEqual(viewModel.paymentSuccessData?.orderAmount, "88.00")
XCTAssertEqual(viewModel.paymentSuccessData?.orderNumber, "ORDER-1")
XCTAssertEqual(viewModel.paymentSuccessData?.payTime, "2026-06-22 10:01")
XCTAssertEqual(viewModel.paymentSuccessData?.remark, "测试")
}
///
func testToggleVoiceBroadcastPersistsPreference() {
let defaults = UserDefaults(suiteName: "PaymentViewModelTests.voice")!
defaults.removePersistentDomain(forName: "PaymentViewModelTests.voice")
let preferences = AppPreferencesStore(defaults: defaults)
let viewModel = makeViewModel(preferencesStore: preferences)
XCTAssertFalse(viewModel.isVoiceBroadcast)
viewModel.toggleVoiceBroadcast()
XCTAssertTrue(viewModel.isVoiceBroadcast)
XCTAssertTrue(preferences.loadPaymentReceiveVoiceEnabled())
let reloaded = makeViewModel(preferencesStore: preferences)
XCTAssertTrue(reloaded.isVoiceBroadcast)
}
///
func testPollingFindsNewPaymentRecord() async {
let api = PaymentMockAPI()
api.recordResponses = [
@ -62,13 +126,18 @@ final class PaymentViewModelTests: XCTestCase {
PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")
])
]
let viewModel = PaymentCollectionViewModel()
let viewModel = makeViewModel()
viewModel.amountText = "12.35"
viewModel.remarkText = "备注"
await viewModel.primePaymentRecords(api: api, scenicId: 9)
await viewModel.pollUntilPaymentDetected(api: api, scenicId: 9, maxAttempts: 1, intervalNanoseconds: 0)
XCTAssertEqual(viewModel.status, .success(PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")))
XCTAssertEqual(viewModel.paymentSuccessData?.orderAmount, "12.35")
XCTAssertEqual(viewModel.paymentSuccessData?.orderNumber, "B")
XCTAssertEqual(viewModel.paymentSuccessData?.payTime, "2026-06-22 10:01")
XCTAssertEqual(viewModel.paymentSuccessData?.remark, "备注")
}
///
@ -84,6 +153,11 @@ final class PaymentViewModelTests: XCTestCase {
XCTAssertEqual(groups[0].analyse.orderCount, 2)
XCTAssertEqual(groups[0].analyse.orderAmountSum, "20")
}
/// ViewModel
private func makeViewModel(preferencesStore: AppPreferencesStore = AppPreferencesStore(defaults: UserDefaults(suiteName: "PaymentViewModelTests.default")!)) -> PaymentCollectionViewModel {
PaymentCollectionViewModel(preferencesStore: preferencesStore)
}
}
@MainActor