feat: add payment status polling fallback

This commit is contained in:
2026-08-21 16:02:11 +08:00
parent 92dbd59c23
commit 342655cbb5
9 changed files with 395 additions and 17 deletions
@@ -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
)
@@ -4,6 +4,9 @@ import com.google.gson.annotations.SerializedName
data class GetPayUrlResponse( data class GetPayUrlResponse(
@SerializedName("url") @SerializedName("url")
val url: String? val url: String?,
@SerializedName("order_number")
val orderNumber: String?
) )
@@ -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<Int>?
)
@@ -1,6 +1,8 @@
package com.yzx.kiosk.network.repository 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.GetPayUrlRequest
import com.yzx.kiosk.network.model.request.PaySuccessMessageRequest
import com.yzx.kiosk.network.model.request.PrintCompleteRequest import com.yzx.kiosk.network.model.request.PrintCompleteRequest
import com.yzx.kiosk.network.model.request.PrintNotifyRequest import com.yzx.kiosk.network.model.request.PrintNotifyRequest
import com.yzx.kiosk.network.model.request.SaveAlbumUrlRequest 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.GetPrintInfoResponse
import com.yzx.kiosk.network.model.response.NetworkResponse import com.yzx.kiosk.network.model.response.NetworkResponse
import com.yzx.kiosk.network.model.response.OscarLivesData 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.SocketTokenResponse
import com.yzx.kiosk.network.model.response.VerifyResultResponse import com.yzx.kiosk.network.model.response.VerifyResultResponse
import com.yzx.kiosk.network.model.response.VersionResponse import com.yzx.kiosk.network.model.response.VersionResponse
@@ -27,6 +30,7 @@ import javax.inject.Inject
class NetWorkRepository @Inject constructor( class NetWorkRepository @Inject constructor(
private val netService: NetworkService, private val netService: NetworkService,
private val uploadService: UploadService, private val uploadService: UploadService,
private val gson: Gson,
) { ) {
// 基础方法示例,可根据需要添加 // 基础方法示例,可根据需要添加
// fun example(): Flow<NetworkResponse<Any>> = flow { // fun example(): Flow<NetworkResponse<Any>> = flow {
@@ -82,6 +86,23 @@ class NetWorkRepository @Inject constructor(
emit(netService.getPayUrl(request)) emit(netService.getPayUrl(request))
}.flowOn(Dispatchers.IO) }.flowOn(Dispatchers.IO)
fun getPaySuccessMessage(
request: PaySuccessMessageRequest
): Flow<NetworkResponse<PaySuccessMessageResponse>> = 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<NetworkResponse<SocketTokenResponse>> = flow { fun getSocketToken(): Flow<NetworkResponse<SocketTokenResponse>> = flow {
emit(netService.getSocketToken()) emit(netService.getSocketToken())
}.flowOn(Dispatchers.IO) }.flowOn(Dispatchers.IO)
@@ -1,6 +1,7 @@
package com.yzx.kiosk.network.service package com.yzx.kiosk.network.service
import com.yzx.kiosk.network.model.request.GetPayUrlRequest 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.PrintCompleteRequest
import com.yzx.kiosk.network.model.request.PrintNotifyRequest import com.yzx.kiosk.network.model.request.PrintNotifyRequest
import com.yzx.kiosk.network.model.request.SaveAlbumUrlRequest 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.SocketTokenResponse
import com.yzx.kiosk.network.model.response.VerifyResultResponse import com.yzx.kiosk.network.model.response.VerifyResultResponse
import com.yzx.kiosk.network.model.response.VersionResponse import com.yzx.kiosk.network.model.response.VersionResponse
import com.google.gson.JsonElement
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
@@ -69,6 +71,11 @@ interface NetworkService {
@POST("/api/oscar/order/get-pay-url") @POST("/api/oscar/order/get-pay-url")
suspend fun getPayUrl(@Body request: GetPayUrlRequest): NetworkResponse<GetPayUrlResponse> suspend fun getPayUrl(@Body request: GetPayUrlRequest): NetworkResponse<GetPayUrlResponse>
@POST("/api/oscar/order/pay-success-message")
suspend fun getPaySuccessMessage(
@Body request: PaySuccessMessageRequest
): NetworkResponse<JsonElement>
@GET("/api/oscar/socket-token") @GET("/api/oscar/socket-token")
suspend fun getSocketToken(): NetworkResponse<SocketTokenResponse> suspend fun getSocketToken(): NetworkResponse<SocketTokenResponse>
@@ -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.PhotoPreviewDialog
import com.yzx.kiosk.ui.upload.view.RetryableAsyncImage import com.yzx.kiosk.ui.upload.view.RetryableAsyncImage
import com.yzx.kiosk.utils.QrCodeUtils import com.yzx.kiosk.utils.QrCodeUtils
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.collect
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
@@ -77,6 +77,12 @@ fun FaceRecognitionResultScreen(
var showPayDialog by remember { mutableStateOf(false) } var showPayDialog by remember { mutableStateOf(false) }
val payQrCodeUrl by viewModel.payQrCodeUrl.collectAsState() val payQrCodeUrl by viewModel.payQrCodeUrl.collectAsState()
LaunchedEffect(viewModel) {
viewModel.dismissPayDialogEvents.collect {
showPayDialog = false
}
}
FullScreenMode() FullScreenMode()
AppScaffold( AppScaffold(
@@ -265,15 +271,19 @@ fun FaceRecognitionResultScreen(
// 支付弹框 // 支付弹框
if (showPayDialog && selectedCount > 0 && payQrCodeUrl != null) { 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( WechatPayDialog(
qrCodeUrl = payQrCodeUrl!!, qrCodeUrl = payQrCodeUrl!!,
totalPrice = totalPrice, totalPrice = totalPrice,
onDismiss = { showPayDialog = false }, onDismiss = {
onPaySuccess = {
showPayDialog = false showPayDialog = false
// 支付成功,等待 WebSocket 消息(code=5)触发跳转到支付成功页面 viewModel.onPayDialogDismissed()
// 跳转逻辑由 FaceRecognitionResultViewModel 中的 WebSocket 监听处理
} }
) )
} }
@@ -453,8 +463,7 @@ fun FaceRecognitionPhotoItem(
fun WechatPayDialog( fun WechatPayDialog(
qrCodeUrl: String, qrCodeUrl: String,
totalPrice: String, totalPrice: String,
onDismiss: () -> Unit, onDismiss: () -> Unit
onPaySuccess: () -> Unit
) { ) {
// 使用接口返回的URL生成支付二维码 // 使用接口返回的URL生成支付二维码
val qrCodeBitmap = remember(qrCodeUrl) { val qrCodeBitmap = remember(qrCodeUrl) {
@@ -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()
}
@@ -1,7 +1,5 @@
package com.yzx.kiosk.ui.face.viewmodel package com.yzx.kiosk.ui.face.viewmodel
import android.graphics.ImageDecoder
import android.os.Build
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
@@ -11,6 +9,7 @@ import com.yzx.kiosk.base.BaseViewModel
import com.yzx.kiosk.datastore.AppState import com.yzx.kiosk.datastore.AppState
import com.yzx.kiosk.navigation.AppNavigator import com.yzx.kiosk.navigation.AppNavigator
import com.yzx.kiosk.network.model.request.GetPayUrlRequest 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.request.VerifyResultRequest
import com.yzx.kiosk.network.model.response.FaceSearchResult import com.yzx.kiosk.network.model.response.FaceSearchResult
import com.yzx.kiosk.network.repository.NetWorkRepository import com.yzx.kiosk.network.repository.NetWorkRepository
@@ -26,14 +25,23 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll 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.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import com.yzx.kiosk.utils.LogUtils import com.yzx.kiosk.utils.LogUtils
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@@ -73,12 +81,22 @@ class FaceRecognitionResultViewModel @Inject constructor(
private val _payQrCodeUrl = MutableStateFlow<String?>(null) private val _payQrCodeUrl = MutableStateFlow<String?>(null)
val payQrCodeUrl: StateFlow<String?> = _payQrCodeUrl.asStateFlow() val payQrCodeUrl: StateFlow<String?> = _payQrCodeUrl.asStateFlow()
// 支付弹框关闭事件(支付完成、取消或退款时由 ViewModel 驱动关闭)
private val _dismissPayDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val dismissPayDialogEvents: SharedFlow<Unit> = _dismissPayDialogEvents.asSharedFlow()
private var activePaymentOrderNumber: String? = null
private var paymentPollingJob: Job? = null
private val paymentHandled = AtomicBoolean(false)
// 图片加载状态:URL -> (重试次数, 是否加载失败) // 图片加载状态:URL -> (重试次数, 是否加载失败)
private val _imageLoadStates = MutableStateFlow<Map<String, ImageLoadState>>(emptyMap()) private val _imageLoadStates = MutableStateFlow<Map<String, ImageLoadState>>(emptyMap())
val imageLoadStates: StateFlow<Map<String, ImageLoadState>> = _imageLoadStates.asStateFlow() val imageLoadStates: StateFlow<Map<String, ImageLoadState>> = _imageLoadStates.asStateFlow()
companion object { companion object {
private const val TAG = "FaceRecognitionResultViewModel"
private const val MAX_RETRY_COUNT = 3 private const val MAX_RETRY_COUNT = 3
private const val PAY_STATUS_POLL_INTERVAL_MS = 2_000L
} }
data class ImageLoadState( data class ImageLoadState(
@@ -93,9 +111,13 @@ class FaceRecognitionResultViewModel @Inject constructor(
.onEach { event -> .onEach { event ->
when (event) { when (event) {
is UploadPhotoEvent.PaySuccess -> { is UploadPhotoEvent.PaySuccess -> {
// 支付成功,跳转到支付成功页面 LogUtils.d(TAG, "收到 WebSocket 支付成功事件 - order_number: ${event.orderNumber}, capture_type: ${event.captureType}, image_ids: ${event.imageIds}")
LogUtils.d("FaceRecognitionResultViewModel", "收到支付成功事件 - order_number: ${event.orderNumber}, capture_type: ${event.captureType}, image_ids: ${event.imageIds}") handlePaymentSuccess(
navigateToPaySuccess(event.orderNumber, event.captureType, event.imageIds) orderNumber = event.orderNumber,
captureType = event.captureType,
imageIds = event.imageIds,
source = "WebSocket"
)
} }
else -> { else -> {
// 其他事件不处理 // 其他事件不处理
@@ -330,10 +352,16 @@ class FaceRecognitionResultViewModel @Inject constructor(
flow = netWorkRepository.getPayUrl(request).asResult(), flow = netWorkRepository.getPayUrl(request).asResult(),
showToast = false, showToast = false,
onData = { response -> onData = { response ->
val url = response.url val url = response.url?.trim()
if (url.isNullOrEmpty()) { val orderNumber = response.orderNumber?.trim()
if (url.isNullOrEmpty() || orderNumber.isNullOrEmpty()) {
_payQrCodeUrl.value = null
LogUtils.e(TAG, "获取支付二维码成功,但 url 或 order_number 为空")
ToastUtils.show("获取支付二维码失败") ToastUtils.show("获取支付二维码失败")
} else { } else {
stopPayStatusPolling()
activePaymentOrderNumber = orderNumber
paymentHandled.set(false)
_payQrCodeUrl.value = url _payQrCodeUrl.value = url
onSuccess(url) onSuccess(url)
} }
@@ -353,6 +381,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<Int>,
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)
}
/** /**
* 跳转到支付成功页面 * 跳转到支付成功页面
* @param orderNumber 订单号 * @param orderNumber 订单号
@@ -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<Int> = listOf(335, 340)
) = PaySuccessMessageResponse(
orderStatus = 30,
orderStatusName = "已完成",
sn = "OSCAR_DEVICE_SN",
type = type,
data = PaySuccessMessageData(
orderNumber = "ORDER-1",
captureType = captureType,
imageIds = imageIds
)
)
}