From 342655cbb5724fd090bcdf21625c1a1b2c8ee1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=89=E7=A7=8B?= <497055328@qq.com> Date: Fri, 21 Aug 2026 16:02:11 +0800 Subject: [PATCH] feat: add payment status polling fallback --- .../model/request/PaySuccessMessageRequest.kt | 8 + .../model/response/GetPayUrlResponse.kt | 5 +- .../response/PaySuccessMessageResponse.kt | 31 +++ .../network/repository/NetWorkRepository.kt | 23 ++- .../kiosk/network/service/NetworkService.kt | 7 + .../face/view/FaceRecognitionResultScreen.kt | 25 ++- .../face/viewmodel/FacePayStatusDecision.kt | 26 +++ .../FaceRecognitionResultViewModel.kt | 185 +++++++++++++++++- .../viewmodel/FacePayStatusDecisionTest.kt | 102 ++++++++++ 9 files changed, 395 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/yzx/kiosk/network/model/request/PaySuccessMessageRequest.kt create mode 100644 app/src/main/java/com/yzx/kiosk/network/model/response/PaySuccessMessageResponse.kt create mode 100644 app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecision.kt create mode 100644 app/src/test/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecisionTest.kt diff --git a/app/src/main/java/com/yzx/kiosk/network/model/request/PaySuccessMessageRequest.kt b/app/src/main/java/com/yzx/kiosk/network/model/request/PaySuccessMessageRequest.kt new file mode 100644 index 0000000..5fc6c47 --- /dev/null +++ b/app/src/main/java/com/yzx/kiosk/network/model/request/PaySuccessMessageRequest.kt @@ -0,0 +1,8 @@ +package com.yzx.kiosk.network.model.request + +import com.google.gson.annotations.SerializedName + +data class PaySuccessMessageRequest( + @SerializedName("order_number") + val orderNumber: String +) diff --git a/app/src/main/java/com/yzx/kiosk/network/model/response/GetPayUrlResponse.kt b/app/src/main/java/com/yzx/kiosk/network/model/response/GetPayUrlResponse.kt index 516a443..acdb7f3 100644 --- a/app/src/main/java/com/yzx/kiosk/network/model/response/GetPayUrlResponse.kt +++ b/app/src/main/java/com/yzx/kiosk/network/model/response/GetPayUrlResponse.kt @@ -4,6 +4,9 @@ import com.google.gson.annotations.SerializedName data class GetPayUrlResponse( @SerializedName("url") - val url: String? + val url: String?, + + @SerializedName("order_number") + val orderNumber: String? ) diff --git a/app/src/main/java/com/yzx/kiosk/network/model/response/PaySuccessMessageResponse.kt b/app/src/main/java/com/yzx/kiosk/network/model/response/PaySuccessMessageResponse.kt new file mode 100644 index 0000000..afdd8c2 --- /dev/null +++ b/app/src/main/java/com/yzx/kiosk/network/model/response/PaySuccessMessageResponse.kt @@ -0,0 +1,31 @@ +package com.yzx.kiosk.network.model.response + +import com.google.gson.annotations.SerializedName + +data class PaySuccessMessageResponse( + @SerializedName("order_status") + val orderStatus: Int?, + + @SerializedName("order_status_name") + val orderStatusName: String?, + + @SerializedName("sn") + val sn: String?, + + @SerializedName("type") + val type: Int?, + + @SerializedName("data") + val data: PaySuccessMessageData? +) + +data class PaySuccessMessageData( + @SerializedName("order_number") + val orderNumber: String?, + + @SerializedName("capture_type") + val captureType: Int?, + + @SerializedName("image_id") + val imageIds: List? +) diff --git a/app/src/main/java/com/yzx/kiosk/network/repository/NetWorkRepository.kt b/app/src/main/java/com/yzx/kiosk/network/repository/NetWorkRepository.kt index 72973d6..b3919b2 100644 --- a/app/src/main/java/com/yzx/kiosk/network/repository/NetWorkRepository.kt +++ b/app/src/main/java/com/yzx/kiosk/network/repository/NetWorkRepository.kt @@ -1,6 +1,8 @@ package com.yzx.kiosk.network.repository +import com.google.gson.Gson import com.yzx.kiosk.network.model.request.GetPayUrlRequest +import com.yzx.kiosk.network.model.request.PaySuccessMessageRequest import com.yzx.kiosk.network.model.request.PrintCompleteRequest import com.yzx.kiosk.network.model.request.PrintNotifyRequest import com.yzx.kiosk.network.model.request.SaveAlbumUrlRequest @@ -13,6 +15,7 @@ import com.yzx.kiosk.network.model.response.GetPayUrlResponse import com.yzx.kiosk.network.model.response.GetPrintInfoResponse import com.yzx.kiosk.network.model.response.NetworkResponse import com.yzx.kiosk.network.model.response.OscarLivesData +import com.yzx.kiosk.network.model.response.PaySuccessMessageResponse import com.yzx.kiosk.network.model.response.SocketTokenResponse import com.yzx.kiosk.network.model.response.VerifyResultResponse import com.yzx.kiosk.network.model.response.VersionResponse @@ -27,6 +30,7 @@ import javax.inject.Inject class NetWorkRepository @Inject constructor( private val netService: NetworkService, private val uploadService: UploadService, + private val gson: Gson, ) { // 基础方法示例,可根据需要添加 // fun example(): Flow> = flow { @@ -81,6 +85,23 @@ class NetWorkRepository @Inject constructor( fun getPayUrl(request: GetPayUrlRequest): Flow> = flow { emit(netService.getPayUrl(request)) }.flowOn(Dispatchers.IO) + + fun getPaySuccessMessage( + request: PaySuccessMessageRequest + ): Flow> = flow { + val response = netService.getPaySuccessMessage(request) + val parsedData = response.data + ?.takeIf { it.isJsonObject } + ?.let { gson.fromJson(it, PaySuccessMessageResponse::class.java) } + + emit( + NetworkResponse( + data = parsedData, + code = response.code, + message = response.message + ) + ) + }.flowOn(Dispatchers.IO) fun getSocketToken(): Flow> = flow { emit(netService.getSocketToken()) @@ -97,4 +118,4 @@ class NetWorkRepository @Inject constructor( fun saveAlbumUrl(request: SaveAlbumUrlRequest): Flow> = flow { emit(netService.saveAlbumUrl(request)) }.flowOn(Dispatchers.IO) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/yzx/kiosk/network/service/NetworkService.kt b/app/src/main/java/com/yzx/kiosk/network/service/NetworkService.kt index 43967bd..029521b 100644 --- a/app/src/main/java/com/yzx/kiosk/network/service/NetworkService.kt +++ b/app/src/main/java/com/yzx/kiosk/network/service/NetworkService.kt @@ -1,6 +1,7 @@ package com.yzx.kiosk.network.service import com.yzx.kiosk.network.model.request.GetPayUrlRequest +import com.yzx.kiosk.network.model.request.PaySuccessMessageRequest import com.yzx.kiosk.network.model.request.PrintCompleteRequest import com.yzx.kiosk.network.model.request.PrintNotifyRequest import com.yzx.kiosk.network.model.request.SaveAlbumUrlRequest @@ -16,6 +17,7 @@ import com.yzx.kiosk.network.model.response.OscarLivesData import com.yzx.kiosk.network.model.response.SocketTokenResponse import com.yzx.kiosk.network.model.response.VerifyResultResponse import com.yzx.kiosk.network.model.response.VersionResponse +import com.google.gson.JsonElement import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -68,6 +70,11 @@ interface NetworkService { @POST("/api/oscar/order/get-pay-url") suspend fun getPayUrl(@Body request: GetPayUrlRequest): NetworkResponse + + @POST("/api/oscar/order/pay-success-message") + suspend fun getPaySuccessMessage( + @Body request: PaySuccessMessageRequest + ): NetworkResponse @GET("/api/oscar/socket-token") suspend fun getSocketToken(): NetworkResponse diff --git a/app/src/main/java/com/yzx/kiosk/ui/face/view/FaceRecognitionResultScreen.kt b/app/src/main/java/com/yzx/kiosk/ui/face/view/FaceRecognitionResultScreen.kt index d145f6c..153a346 100644 --- a/app/src/main/java/com/yzx/kiosk/ui/face/view/FaceRecognitionResultScreen.kt +++ b/app/src/main/java/com/yzx/kiosk/ui/face/view/FaceRecognitionResultScreen.kt @@ -46,7 +46,7 @@ import com.yzx.kiosk.ui.face.viewmodel.FaceRecognitionResultViewModel import com.yzx.kiosk.ui.upload.view.PhotoPreviewDialog import com.yzx.kiosk.ui.upload.view.RetryableAsyncImage import com.yzx.kiosk.utils.QrCodeUtils -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.collect @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable @@ -76,6 +76,12 @@ fun FaceRecognitionResultScreen( // 支付弹框状态 var showPayDialog by remember { mutableStateOf(false) } val payQrCodeUrl by viewModel.payQrCodeUrl.collectAsState() + + LaunchedEffect(viewModel) { + viewModel.dismissPayDialogEvents.collect { + showPayDialog = false + } + } FullScreenMode() @@ -265,15 +271,19 @@ fun FaceRecognitionResultScreen( // 支付弹框 if (showPayDialog && selectedCount > 0 && payQrCodeUrl != null) { - val selectedUrls = photoList.filter { selectedPhotos.contains(it.url) }.map { it.url } + DisposableEffect(payQrCodeUrl) { + viewModel.startPayStatusPolling() + onDispose { + viewModel.stopPayStatusPolling() + } + } + WechatPayDialog( qrCodeUrl = payQrCodeUrl!!, totalPrice = totalPrice, - onDismiss = { showPayDialog = false }, - onPaySuccess = { + onDismiss = { showPayDialog = false - // 支付成功,等待 WebSocket 消息(code=5)触发跳转到支付成功页面 - // 跳转逻辑由 FaceRecognitionResultViewModel 中的 WebSocket 监听处理 + viewModel.onPayDialogDismissed() } ) } @@ -453,8 +463,7 @@ fun FaceRecognitionPhotoItem( fun WechatPayDialog( qrCodeUrl: String, totalPrice: String, - onDismiss: () -> Unit, - onPaySuccess: () -> Unit + onDismiss: () -> Unit ) { // 使用接口返回的URL生成支付二维码 val qrCodeBitmap = remember(qrCodeUrl) { diff --git a/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecision.kt b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecision.kt new file mode 100644 index 0000000..06a62b0 --- /dev/null +++ b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecision.kt @@ -0,0 +1,26 @@ +package com.yzx.kiosk.ui.face.viewmodel + +import com.yzx.kiosk.network.model.response.PaySuccessMessageResponse + +internal enum class FacePayStatusDecision { + CONTINUE_POLLING, + COMPLETE_PAYMENT, + STOP_WITH_TERMINAL_STATUS +} + +internal fun decideFacePayStatus(orderStatus: Int?): FacePayStatusDecision = when (orderStatus) { + 30 -> FacePayStatusDecision.COMPLETE_PAYMENT + 40, 50, 60 -> FacePayStatusDecision.STOP_WITH_TERMINAL_STATUS + else -> FacePayStatusDecision.CONTINUE_POLLING +} + +internal fun isValidFacePaySuccessMessage( + expectedOrderNumber: String, + message: PaySuccessMessageResponse? +): Boolean { + val paymentData = message?.data ?: return false + return message.type == 5 && + paymentData.orderNumber == expectedOrderNumber && + paymentData.captureType == 2 && + !paymentData.imageIds.isNullOrEmpty() +} diff --git a/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt index 57088f1..f8b9176 100644 --- a/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt +++ b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt @@ -1,7 +1,5 @@ package com.yzx.kiosk.ui.face.viewmodel -import android.graphics.ImageDecoder -import android.os.Build import androidx.lifecycle.viewModelScope import coil.ImageLoader import coil.request.ImageRequest @@ -11,6 +9,7 @@ import com.yzx.kiosk.base.BaseViewModel import com.yzx.kiosk.datastore.AppState import com.yzx.kiosk.navigation.AppNavigator import com.yzx.kiosk.network.model.request.GetPayUrlRequest +import com.yzx.kiosk.network.model.request.PaySuccessMessageRequest import com.yzx.kiosk.network.model.request.VerifyResultRequest import com.yzx.kiosk.network.model.response.FaceSearchResult import com.yzx.kiosk.network.repository.NetWorkRepository @@ -26,14 +25,23 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import com.yzx.kiosk.utils.LogUtils +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @HiltViewModel @@ -72,13 +80,23 @@ class FaceRecognitionResultViewModel @Inject constructor( // 支付二维码URL private val _payQrCodeUrl = MutableStateFlow(null) val payQrCodeUrl: StateFlow = _payQrCodeUrl.asStateFlow() + + // 支付弹框关闭事件(支付完成、取消或退款时由 ViewModel 驱动关闭) + private val _dismissPayDialogEvents = MutableSharedFlow(extraBufferCapacity = 1) + val dismissPayDialogEvents: SharedFlow = _dismissPayDialogEvents.asSharedFlow() + + private var activePaymentOrderNumber: String? = null + private var paymentPollingJob: Job? = null + private val paymentHandled = AtomicBoolean(false) // 图片加载状态:URL -> (重试次数, 是否加载失败) private val _imageLoadStates = MutableStateFlow>(emptyMap()) val imageLoadStates: StateFlow> = _imageLoadStates.asStateFlow() companion object { + private const val TAG = "FaceRecognitionResultViewModel" private const val MAX_RETRY_COUNT = 3 + private const val PAY_STATUS_POLL_INTERVAL_MS = 2_000L } data class ImageLoadState( @@ -93,9 +111,13 @@ class FaceRecognitionResultViewModel @Inject constructor( .onEach { event -> when (event) { is UploadPhotoEvent.PaySuccess -> { - // 支付成功,跳转到支付成功页面 - LogUtils.d("FaceRecognitionResultViewModel", "收到支付成功事件 - order_number: ${event.orderNumber}, capture_type: ${event.captureType}, image_ids: ${event.imageIds}") - navigateToPaySuccess(event.orderNumber, event.captureType, event.imageIds) + LogUtils.d(TAG, "收到 WebSocket 支付成功事件 - order_number: ${event.orderNumber}, capture_type: ${event.captureType}, image_ids: ${event.imageIds}") + handlePaymentSuccess( + orderNumber = event.orderNumber, + captureType = event.captureType, + imageIds = event.imageIds, + source = "WebSocket" + ) } else -> { // 其他事件不处理 @@ -330,10 +352,16 @@ class FaceRecognitionResultViewModel @Inject constructor( flow = netWorkRepository.getPayUrl(request).asResult(), showToast = false, onData = { response -> - val url = response.url - if (url.isNullOrEmpty()) { + val url = response.url?.trim() + val orderNumber = response.orderNumber?.trim() + if (url.isNullOrEmpty() || orderNumber.isNullOrEmpty()) { + _payQrCodeUrl.value = null + LogUtils.e(TAG, "获取支付二维码成功,但 url 或 order_number 为空") ToastUtils.show("获取支付二维码失败") } else { + stopPayStatusPolling() + activePaymentOrderNumber = orderNumber + paymentHandled.set(false) _payQrCodeUrl.value = url onSuccess(url) } @@ -352,6 +380,149 @@ class FaceRecognitionResultViewModel @Inject constructor( } } } + + /** + * 二维码弹框显示时启动支付状态轮询。重复调用不会创建多个轮询任务。 + */ + fun startPayStatusPolling() { + val orderNumber = activePaymentOrderNumber + if (orderNumber.isNullOrEmpty() || paymentHandled.get()) { + LogUtils.i(TAG, "未启动支付状态轮询:当前没有有效待支付订单") + return + } + if (paymentPollingJob?.isActive == true) { + return + } + + paymentPollingJob = viewModelScope.launch { + LogUtils.d(TAG, "开始轮询支付状态 - order_number: $orderNumber") + while ( + isActive && + !paymentHandled.get() && + activePaymentOrderNumber == orderNumber + ) { + queryPayStatus(orderNumber) + delay(PAY_STATUS_POLL_INTERVAL_MS) + } + } + } + + /** 二维码弹框关闭时停止 HTTP 轮询,WebSocket 监听仍保持有效。 */ + fun stopPayStatusPolling() { + paymentPollingJob?.cancel() + paymentPollingJob = null + } + + fun onPayDialogDismissed() { + stopPayStatusPolling() + } + + private suspend fun queryPayStatus(orderNumber: String) { + try { + val response = netWorkRepository + .getPaySuccessMessage(PaySuccessMessageRequest(orderNumber)) + .first() + + if (!response.isSucceeded) { + LogUtils.e( + TAG, + "查询支付状态业务失败 - order_number: $orderNumber, code: ${response.code}, msg: ${response.message}" + ) + return + } + + val message = response.data + when (decideFacePayStatus(message?.orderStatus)) { + FacePayStatusDecision.CONTINUE_POLLING -> { + LogUtils.d( + TAG, + "订单尚未完成,继续轮询 - order_number: $orderNumber, status: ${message?.orderStatus}, name: ${message?.orderStatusName}" + ) + } + + FacePayStatusDecision.COMPLETE_PAYMENT -> { + if (!isValidFacePaySuccessMessage(orderNumber, message)) { + LogUtils.e(TAG, "支付完成消息校验失败,等待下一次查询或 WebSocket - order_number: $orderNumber, message: $message") + return + } + + val paymentData = checkNotNull(message?.data) + handlePaymentSuccess( + orderNumber = paymentData.orderNumber, + captureType = paymentData.captureType, + imageIds = paymentData.imageIds.orEmpty(), + source = "HTTP" + ) + } + + FacePayStatusDecision.STOP_WITH_TERMINAL_STATUS -> { + handleTerminalPayStatus(message?.orderStatus, message?.orderStatusName) + } + } + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + LogUtils.e(TAG, "查询支付状态异常 - order_number: $orderNumber, error: ${e.message}") + } + } + + private fun handleTerminalPayStatus(orderStatus: Int?, orderStatusName: String?) { + if (!paymentHandled.compareAndSet(false, true)) { + return + } + + stopPayStatusPolling() + activePaymentOrderNumber = null + _payQrCodeUrl.value = null + _dismissPayDialogEvents.tryEmit(Unit) + + val statusText = orderStatusName?.takeIf { it.isNotBlank() } ?: when (orderStatus) { + 40 -> "已取消" + 50 -> "已退款" + 60 -> "部分退款" + else -> "状态异常" + } + ToastUtils.show("订单$statusText") + LogUtils.i(TAG, "订单进入终态,停止支付轮询 - status: $orderStatus, name: $statusText") + } + + private fun handlePaymentSuccess( + orderNumber: String?, + captureType: Int?, + imageIds: List, + source: String + ) { + val expectedOrderNumber = activePaymentOrderNumber + if ( + expectedOrderNumber.isNullOrEmpty() || + orderNumber != expectedOrderNumber || + captureType != 2 || + imageIds.isEmpty() + ) { + LogUtils.i( + TAG, + "忽略不匹配的支付成功消息 - source: $source, expected: $expectedOrderNumber, actual: $orderNumber, capture_type: $captureType, image_ids: $imageIds" + ) + return + } + + val knownImageIds = originalResults.map { it.id }.toSet() + if (!knownImageIds.containsAll(imageIds)) { + LogUtils.e(TAG, "支付成功消息包含未知图片,暂不跳转 - source: $source, image_ids: $imageIds") + return + } + + if (!paymentHandled.compareAndSet(false, true)) { + LogUtils.d(TAG, "支付成功已处理,忽略重复消息 - source: $source, order_number: $orderNumber") + return + } + + LogUtils.d(TAG, "确认支付成功 - source: $source, order_number: $orderNumber") + stopPayStatusPolling() + _payQrCodeUrl.value = null + _dismissPayDialogEvents.tryEmit(Unit) + navigateToPaySuccess(orderNumber, captureType, imageIds) + } /** * 跳转到支付成功页面 diff --git a/app/src/test/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecisionTest.kt b/app/src/test/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecisionTest.kt new file mode 100644 index 0000000..081c4df --- /dev/null +++ b/app/src/test/java/com/yzx/kiosk/ui/face/viewmodel/FacePayStatusDecisionTest.kt @@ -0,0 +1,102 @@ +package com.yzx.kiosk.ui.face.viewmodel + +import com.google.gson.Gson +import com.yzx.kiosk.network.model.request.PaySuccessMessageRequest +import com.yzx.kiosk.network.model.response.PaySuccessMessageData +import com.yzx.kiosk.network.model.response.PaySuccessMessageResponse +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class FacePayStatusDecisionTest { + + @Test + fun `pending and unknown statuses continue polling`() { + listOf(null, 10, 20, 0, 70).forEach { status -> + assertEquals( + FacePayStatusDecision.CONTINUE_POLLING, + decideFacePayStatus(status) + ) + } + } + + @Test + fun `completed status completes payment`() { + assertEquals( + FacePayStatusDecision.COMPLETE_PAYMENT, + decideFacePayStatus(30) + ) + } + + @Test + fun `cancel and refund statuses stop polling`() { + listOf(40, 50, 60).forEach { status -> + assertEquals( + FacePayStatusDecision.STOP_WITH_TERMINAL_STATUS, + decideFacePayStatus(status) + ) + } + } + + @Test + fun `valid face payment message is accepted`() { + assertTrue(isValidFacePaySuccessMessage("ORDER-1", message())) + } + + @Test + fun `empty or mismatched payment data is rejected`() { + assertFalse(isValidFacePaySuccessMessage("ORDER-1", null)) + assertFalse(isValidFacePaySuccessMessage("OTHER", message())) + assertFalse(isValidFacePaySuccessMessage("ORDER-1", message(type = 4))) + assertFalse(isValidFacePaySuccessMessage("ORDER-1", message(captureType = 1))) + assertFalse(isValidFacePaySuccessMessage("ORDER-1", message(imageIds = emptyList()))) + } + + @Test + fun `pay status request uses backend order number field`() { + val json = Gson().toJson(PaySuccessMessageRequest("ORDER-1")) + + assertEquals("{\"order_number\":\"ORDER-1\"}", json) + } + + @Test + fun `pay status response deserializes nested websocket payload`() { + val json = """ + { + "order_status": 30, + "order_status_name": "已完成", + "sn": "OSCAR_DEVICE_SN", + "type": 5, + "data": { + "order_number": "ORDER-1", + "capture_type": 2, + "image_id": [335, 340] + } + } + """.trimIndent() + + val response = Gson().fromJson(json, PaySuccessMessageResponse::class.java) + + assertEquals(30, response.orderStatus) + assertEquals("ORDER-1", response.data?.orderNumber) + assertEquals(listOf(335, 340), response.data?.imageIds) + assertTrue(isValidFacePaySuccessMessage("ORDER-1", response)) + } + + private fun message( + type: Int = 5, + captureType: Int = 2, + imageIds: List = listOf(335, 340) + ) = PaySuccessMessageResponse( + orderStatus = 30, + orderStatusName = "已完成", + sn = "OSCAR_DEVICE_SN", + type = type, + data = PaySuccessMessageData( + orderNumber = "ORDER-1", + captureType = captureType, + imageIds = imageIds + ) + ) +}