diff --git a/app/src/main/java/com/example/talkeys_new/screens/payment/EventPaymentScreen.kt b/app/src/main/java/com/example/talkeys_new/screens/payment/EventPaymentScreen.kt
index c0a898a..d70420c 100644
--- a/app/src/main/java/com/example/talkeys_new/screens/payment/EventPaymentScreen.kt
+++ b/app/src/main/java/com/example/talkeys_new/screens/payment/EventPaymentScreen.kt
@@ -14,7 +14,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -22,7 +21,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.talkeys_new.R
-import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -32,7 +30,6 @@ fun EventPaymentScreen(
eventPrice: String,
navController: NavController
) {
- val context = LocalContext.current
val scrollState = rememberScrollState()
// Convert price to double for calculations
@@ -234,9 +231,7 @@ private fun PhonePePaymentSection(
navController: NavController,
onPaymentInitiated: () -> Unit
) {
- val context = LocalContext.current
var isLoading by remember { mutableStateOf(false) }
- val scope = rememberCoroutineScope()
val paymentViewModel = sharedPaymentCheckoutViewModel()
val checkoutState by paymentViewModel.checkoutState.collectAsState()
val errorMessage = checkoutState.errorMessage
@@ -355,21 +350,13 @@ private fun PhonePePaymentSection(
val passType = determinePassType(amount)
val friends = getUserSelectedFriends()
- scope.launch {
- val tokenManager = com.example.talkeys_new.screens.authentication.TokenManager(context)
- val tokenResult = tokenManager.getToken()
- val authToken = when (tokenResult) {
- is com.example.talkeys_new.utils.Result.Success -> tokenResult.data?.takeIf { it.isNotBlank() }
- else -> null
- }
-
- paymentViewModel.startCheckout(
- eventId = eventId,
- passType = passType,
- friends = friends,
- authToken = authToken
- )
- }
+ paymentViewModel.startCheckout(
+ eventId = eventId,
+ passType = passType,
+ friends = friends,
+ teamCode = null,
+ authToken = null
+ )
},
modifier = Modifier
.fillMaxWidth()
diff --git a/app/src/main/java/com/example/talkeys_new/screens/payment/WebViewPaymentScreen.kt b/app/src/main/java/com/example/talkeys_new/screens/payment/WebViewPaymentScreen.kt
index f1dcb5f..86d0f10 100644
--- a/app/src/main/java/com/example/talkeys_new/screens/payment/WebViewPaymentScreen.kt
+++ b/app/src/main/java/com/example/talkeys_new/screens/payment/WebViewPaymentScreen.kt
@@ -2,6 +2,7 @@ package com.example.talkeys_new.screens.payment
import android.annotation.SuppressLint
import android.graphics.Bitmap
+import android.content.Intent
import android.net.http.SslError
import android.webkit.*
import androidx.compose.foundation.layout.*
@@ -94,7 +95,12 @@ fun WebViewPaymentScreen(
// WebView
AndroidView(
factory = { context ->
- WebView(context).apply {
+ WebView(context).apply webView@{
+ CookieManager.getInstance().apply {
+ setAcceptCookie(true)
+ setAcceptThirdPartyCookies(this@webView, true)
+ }
+
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
@@ -104,7 +110,15 @@ fun WebViewPaymentScreen(
loadWithOverviewMode = true
useWideViewPort = true
javaScriptCanOpenWindowsAutomatically = true
+ setSupportMultipleWindows(true)
mediaPlaybackRequiresUserGesture = false
+ loadsImagesAutomatically = true
+ cacheMode = WebSettings.LOAD_DEFAULT
+ allowContentAccess = true
+ userAgentString = userAgentString
+ ?.replace("; wv", "")
+ ?.replace("Version/4.0 ", "")
+ ?: userAgentString
// Allow mixed content for payment gateways
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
@@ -122,7 +136,7 @@ fun WebViewPaymentScreen(
super.onPageFinished(view, url)
isLoading = false
currentUrl = url ?: ""
- Log.d("WebViewPayment", "Payment page finished loading")
+ Log.d("WebViewPayment", "Payment page finished loading for host=${runCatching { android.net.Uri.parse(url).host }.getOrNull() ?: "unknown"}")
// Check for session expired error
view?.evaluateJavascript(
@@ -218,6 +232,24 @@ fun WebViewPaymentScreen(
isLoading = false
}
}
+
+ override fun onCreateWindow(
+ view: WebView?,
+ isDialog: Boolean,
+ isUserGesture: Boolean,
+ resultMsg: android.os.Message?
+ ): Boolean {
+ val popupUrl = view?.hitTestResult?.extra
+ if (!popupUrl.isNullOrBlank()) {
+ runCatching {
+ context.startActivity(Intent(Intent.ACTION_VIEW, android.net.Uri.parse(popupUrl)))
+ }.onFailure { error ->
+ Log.e("WebViewPayment", "Failed to open popup URL: ${error.message}")
+ }
+ return false
+ }
+ return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg)
+ }
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean = true
}
diff --git a/app/src/main/java/com/example/talkeys_new/screens/profile/ProfileScreen.kt b/app/src/main/java/com/example/talkeys_new/screens/profile/ProfileScreen.kt
index 4f83d3b..e189a56 100644
--- a/app/src/main/java/com/example/talkeys_new/screens/profile/ProfileScreen.kt
+++ b/app/src/main/java/com/example/talkeys_new/screens/profile/ProfileScreen.kt
@@ -74,13 +74,20 @@ fun ProfileScreen(navController: NavController) {
val userProfile = remember(sharedProfileState, localGoogleProfile) {
val content = sharedProfileState as? ProfileUiState.Content
content?.profile?.let { profile ->
+ val resolvedDisplayName = firstNonBlank(
+ profile.displayName,
+ profile.name,
+ localGoogleProfile.givenName,
+ localGoogleProfile.name
+ )
+ val resolvedEmail = firstNonBlank(profile.email, localGoogleProfile.email)
UserProfile(
- id = profile.id,
- name = profile.displayName ?: profile.name,
- email = profile.email,
- profileImageUrl = profile.avatarUrl,
- givenName = profile.displayName ?: profile.name,
- familyName = ""
+ id = firstNonBlank(profile.id, localGoogleProfile.id),
+ name = resolvedDisplayName,
+ email = resolvedEmail,
+ profileImageUrl = profile.avatarUrl ?: localGoogleProfile.profileImageUrl,
+ givenName = resolvedDisplayName,
+ familyName = localGoogleProfile.familyName
)
} ?: localGoogleProfile
}
@@ -951,6 +958,10 @@ private fun getUserDisplayName(userProfile: UserProfile): String {
}
}
+private fun firstNonBlank(vararg values: String?): String {
+ return values.firstOrNull { !it.isNullOrBlank() }?.trim().orEmpty()
+}
+
private fun getUserEmail(userProfile: UserProfile): String {
return if (userProfile.email.isNotEmpty()) {
"@${userProfile.email.substringBefore("@")}"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9d81c95..895b20d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,6 @@
TalkeysApk
+ 563385258779-75kq583ov98fk7h3dqp5em0639769a61.apps.googleusercontent.com
fcm_default_channel
FCM Registration Token: %1$s
-
\ No newline at end of file
+
diff --git a/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentModels.kt b/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentModels.kt
index 39dc470..a40b541 100644
--- a/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentModels.kt
+++ b/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentModels.kt
@@ -1,12 +1,16 @@
package com.talkeys.shared.data.payment
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.JsonNames
@Serializable
data class BookTicketRequest(
val eventId: String,
val passType: String,
- val friends: List
+ val friends: List,
+ val teamCode: String? = null
)
@Serializable
@@ -23,18 +27,27 @@ data class BookTicketResponse(
)
@Serializable
+@OptIn(ExperimentalSerializationApi::class)
data class PaymentOrderData(
val passId: String,
val merchantOrderId: String,
- val orderId: String, // Backend sends "orderId" not "phonePeOrderId"
- val amount: Int,
- val amountInPaisa: Int,
- val totalTickets: Int,
- val token: String, // Backend sends "token" not "paymentUrl"
- val event: EventInfo,
- val qrStrings: List,
- val friends: List
-)
+ @JsonNames("orderId", "phonePeOrderId")
+ val orderId: String? = null,
+ val amount: Int? = null,
+ val amountInPaisa: Int? = null,
+ val totalTickets: Int? = null,
+ @JsonNames("token", "paymentToken", "payment_token")
+ val token: String? = null,
+ @SerialName("paymentUrl")
+ @JsonNames("paymentUrl", "checkoutUrl", "redirectUrl", "url", "redirectURL")
+ val paymentUrl: String? = null,
+ val event: EventInfo? = null,
+ val qrStrings: List = emptyList(),
+ val friends: List = emptyList()
+) {
+ fun checkoutTokenOrUrl(): String? = paymentUrl?.takeIf { it.isNotBlank() }
+ ?: token?.takeIf { it.isNotBlank() }
+}
@Serializable
data class EventInfo(
@@ -58,4 +71,4 @@ data class PaymentStatusData(
val passId: String,
val passUUID: String? = null, // Make optional since backend may not always include it
val paymentStatus: String
-)
\ No newline at end of file
+)
diff --git a/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentRepository.kt b/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentRepository.kt
index 88f2931..38468ca 100644
--- a/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentRepository.kt
+++ b/shared/src/commonMain/kotlin/com/talkeys/shared/data/payment/PaymentRepository.kt
@@ -2,9 +2,13 @@ package com.talkeys.shared.data.payment
import com.talkeys.shared.network.PaymentApiService
import com.talkeys.shared.config.ProductionConfig
+import com.talkeys.shared.auth.TokenStorage
import co.touchlab.kermit.Logger
-open class PaymentRepository(private val paymentApiService: PaymentApiService) {
+open class PaymentRepository(
+ private val paymentApiService: PaymentApiService,
+ private val tokenStorage: TokenStorage? = null
+) {
private val logger = Logger.withTag("PaymentRepository")
@@ -22,6 +26,7 @@ open class PaymentRepository(private val paymentApiService: PaymentApiService) {
eventId: String,
passType: String,
friends: List,
+ teamCode: String? = null,
authToken: String? = null
): Result {
// Production validation
@@ -29,16 +34,20 @@ open class PaymentRepository(private val paymentApiService: PaymentApiService) {
return Result.failure(Exception("Maximum ${ProductionConfig.MAX_FRIENDS_PER_BOOKING} friends allowed per booking"))
}
+ val resolvedAuthToken = resolveAuthToken(authToken)
+ ?: return Result.failure(Exception("Please login to continue"))
+
logger.i { "Production: Booking ticket for event: $eventId, passType: $passType, friends: ${friends.size}" }
val request = BookTicketRequest(
eventId = eventId,
passType = passType,
- friends = friends
+ friends = friends,
+ teamCode = teamCode?.trim()?.takeIf { it.isNotEmpty() }
)
return try {
- val result = paymentApiService.bookTicketApp(request, authToken)
+ val result = paymentApiService.bookTicketApp(request, resolvedAuthToken)
result.fold(
onSuccess = { response ->
@@ -66,10 +75,13 @@ open class PaymentRepository(private val paymentApiService: PaymentApiService) {
* Verify payment status after PhonePe payment completion
*/
open suspend fun verifyPaymentStatus(merchantOrderId: String, authToken: String? = null): Result {
+ val resolvedAuthToken = resolveAuthToken(authToken)
+ ?: return Result.failure(Exception("Please login to verify payment"))
+
logger.d { "Verifying payment status" }
return try {
- val result = paymentApiService.checkPaymentStatus(merchantOrderId, authToken)
+ val result = paymentApiService.checkPaymentStatus(merchantOrderId, resolvedAuthToken)
result.fold(
onSuccess = { response ->
@@ -120,4 +132,9 @@ open class PaymentRepository(private val paymentApiService: PaymentApiService) {
Result.failure(e)
}
}
+
+ private suspend fun resolveAuthToken(authToken: String?): String? {
+ return authToken?.trim()?.takeIf { it.isNotBlank() }
+ ?: tokenStorage?.getToken()?.trim()?.takeIf { it.isNotBlank() }
+ }
}
diff --git a/shared/src/commonMain/kotlin/com/talkeys/shared/di/SharedModule.kt b/shared/src/commonMain/kotlin/com/talkeys/shared/di/SharedModule.kt
index 8a8a478..7c17adf 100644
--- a/shared/src/commonMain/kotlin/com/talkeys/shared/di/SharedModule.kt
+++ b/shared/src/commonMain/kotlin/com/talkeys/shared/di/SharedModule.kt
@@ -14,7 +14,7 @@ import org.koin.dsl.module
val sharedModule = module {
single { ApiClient() }
single { PaymentApiService(get()) }
- single { PaymentRepository(get()) }
+ single { PaymentRepository(get(), get()) }
// Phase 4 — Events vertical slice (read-only)
single { EventsApi(get()) }
diff --git a/shared/src/commonMain/kotlin/com/talkeys/shared/network/PaymentApiService.kt b/shared/src/commonMain/kotlin/com/talkeys/shared/network/PaymentApiService.kt
index c89e536..b73b5d4 100644
--- a/shared/src/commonMain/kotlin/com/talkeys/shared/network/PaymentApiService.kt
+++ b/shared/src/commonMain/kotlin/com/talkeys/shared/network/PaymentApiService.kt
@@ -4,19 +4,33 @@ import com.talkeys.shared.data.payment.BookTicketRequest
import com.talkeys.shared.data.payment.BookTicketResponse
import com.talkeys.shared.data.payment.PaymentStatusResponse
import io.ktor.client.call.*
+import io.ktor.client.statement.bodyAsText
import io.ktor.client.plugins.timeout
import io.ktor.client.request.*
import io.ktor.http.*
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.booleanOrNull
+import kotlinx.serialization.json.contentOrNull
+import kotlinx.serialization.json.intOrNull
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
class PaymentApiService(private val apiClient: ApiClient) {
+ private val json = Json {
+ isLenient = true
+ ignoreUnknownKeys = true
+ }
/**
* Book ticket and create payment order
- * POST /api/book-ticket-app
+ * POST /api/book-ticket
*/
suspend fun bookTicketApp(request: BookTicketRequest, authToken: String? = null): Result {
return try {
- val response = apiClient.httpClient.post("${ApiClient.BASE_URL}/api/book-ticket-app") {
+ val response = apiClient.httpClient.post("${ApiClient.BASE_URL}/api/book-ticket") {
contentType(ContentType.Application.Json)
authToken?.let { token ->
header("Authorization", "Bearer $token")
@@ -28,9 +42,28 @@ class PaymentApiService(private val apiClient: ApiClient) {
}
if (response.status.isSuccess()) {
- Result.success(response.body())
+ val rawBody = response.bodyAsText()
+ val parsed = try {
+ json.decodeFromString(rawBody)
+ } catch (exception: SerializationException) {
+ return Result.failure(
+ Exception(
+ buildString {
+ append("Unable to read payment response")
+ val shape = describePayloadShape(rawBody)
+ if (shape != null) {
+ append(": ")
+ append(shape)
+ }
+ },
+ exception
+ )
+ )
+ }
+ Result.success(parsed)
} else {
- Result.failure(Exception("HTTP ${response.status.value}: ${response.status.description}"))
+ val errorBody = runCatching { response.bodyAsText() }.getOrNull()
+ Result.failure(Exception(formatHttpError(response.status, errorBody)))
}
} catch (e: Exception) {
Result.failure(e)
@@ -60,10 +93,70 @@ class PaymentApiService(private val apiClient: ApiClient) {
val statusResponse = response.body()
Result.success(statusResponse)
} else {
- Result.failure(Exception("HTTP ${response.status.value}: ${response.status.description}"))
+ val errorBody = runCatching { response.bodyAsText() }.getOrNull()
+ Result.failure(Exception(formatHttpError(response.status, errorBody)))
}
} catch (e: Exception) {
Result.failure(e)
}
}
-}
\ No newline at end of file
+
+ private fun formatHttpError(status: HttpStatusCode, body: String?): String {
+ val trimmedBody = body?.trim()?.takeIf { it.isNotEmpty() }
+ return buildString {
+ append("HTTP ${status.value}: ${status.description}")
+ if (trimmedBody != null) {
+ append(" - ")
+ append(trimmedBody)
+ }
+ }
+ }
+
+ private fun describePayloadShape(rawBody: String): String? {
+ val root = runCatching { json.parseToJsonElement(rawBody).jsonObject }.getOrNull() ?: return null
+ val data = root["data"] as? JsonObject
+ val dataKeys = data?.keys?.sorted().orEmpty()
+
+ val success = (root["success"] as? JsonPrimitive)?.booleanOrNull
+ val message = (root["message"] as? JsonPrimitive)?.stringOrNull()
+
+ return buildString {
+ append("response keys=")
+ append(root.keys.sorted())
+ if (success != null) {
+ append(", success=")
+ append(success)
+ }
+ if (!message.isNullOrBlank()) {
+ append(", message=")
+ append(message)
+ }
+ if (data != null) {
+ append(", data keys=")
+ append(dataKeys)
+ val merchantOrderId = (data["merchantOrderId"] as? JsonPrimitive)?.stringOrNull()
+ val passId = (data["passId"] as? JsonPrimitive)?.stringOrNull()
+ val hasToken = (data["token"] as? JsonPrimitive)?.stringOrNull()?.isNotBlank() == true
+ val hasPaymentUrl = listOf("paymentUrl", "checkoutUrl", "redirectUrl", "url")
+ .any { key -> (data[key] as? JsonPrimitive)?.stringOrNull()?.isNotBlank() == true }
+ val amount = (data["amount"] as? JsonPrimitive)?.intOrNull
+ if (!merchantOrderId.isNullOrBlank()) {
+ append(", merchantOrderId present")
+ }
+ if (!passId.isNullOrBlank()) {
+ append(", passId present")
+ }
+ if (amount != null) {
+ append(", amount=")
+ append(amount)
+ }
+ append(", hasToken=")
+ append(hasToken)
+ append(", hasPaymentUrl=")
+ append(hasPaymentUrl)
+ }
+ }
+ }
+
+ private fun JsonPrimitive.stringOrNull(): String? = runCatching { content }.getOrNull()
+}
diff --git a/shared/src/commonMain/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModel.kt b/shared/src/commonMain/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModel.kt
index 7232500..9dbcb17 100644
--- a/shared/src/commonMain/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModel.kt
+++ b/shared/src/commonMain/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModel.kt
@@ -25,21 +25,31 @@ class PaymentCheckoutViewModel(
eventId: String,
passType: String,
friends: List,
+ teamCode: String? = null,
authToken: String?
) {
- if (authToken.isNullOrBlank()) {
- _checkoutState.value = PaymentCheckoutUiState(errorMessage = "Please login to continue")
- return
- }
-
_checkoutState.value = PaymentCheckoutUiState(isLoading = true)
viewModelScope.launch {
- repository.bookTicket(eventId, passType, friends, authToken)
+ repository.bookTicket(
+ eventId = eventId,
+ passType = passType,
+ friends = friends,
+ teamCode = teamCode,
+ authToken = authToken
+ )
.onSuccess { paymentOrder ->
+ val checkoutTokenOrUrl = paymentOrder.checkoutTokenOrUrl()
+ if (checkoutTokenOrUrl.isNullOrBlank()) {
+ _checkoutState.value = PaymentCheckoutUiState(
+ errorMessage = "Payment response did not include checkout details. Please try again."
+ )
+ return@onSuccess
+ }
+
_checkoutState.value = PaymentCheckoutUiState(
checkoutData = PaymentCheckoutData(
paymentUrl = PhonePeCheckoutUrlBuilder.buildCheckoutUrl(
- tokenOrUrl = paymentOrder.token,
+ tokenOrUrl = checkoutTokenOrUrl,
isProduction = isPhonePeProduction
),
merchantOrderId = paymentOrder.merchantOrderId,
@@ -56,11 +66,6 @@ class PaymentCheckoutViewModel(
}
fun verifyPaymentStatus(merchantOrderId: String, authToken: String?) {
- if (authToken.isNullOrBlank()) {
- _verificationState.value = PaymentVerificationUiState(errorMessage = "Please login to verify payment")
- return
- }
-
_verificationState.value = PaymentVerificationUiState(isLoading = true)
viewModelScope.launch {
repository.verifyPaymentStatus(merchantOrderId, authToken)
diff --git a/shared/src/commonTest/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModelTest.kt b/shared/src/commonTest/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModelTest.kt
index 8068a61..6aed393 100644
--- a/shared/src/commonTest/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModelTest.kt
+++ b/shared/src/commonTest/kotlin/com/talkeys/shared/presentation/payment/PaymentCheckoutViewModelTest.kt
@@ -34,9 +34,12 @@ class PaymentCheckoutViewModelTest {
@Test
fun startCheckout_requiresAuthToken() = runTest {
- val viewModel = PaymentCheckoutViewModel(fakeRepository(Result.success(orderData())))
+ val viewModel = PaymentCheckoutViewModel(
+ fakeRepository(Result.failure(Exception("Please login to continue")))
+ )
viewModel.startCheckout("event-1", "General", emptyList(), authToken = null)
+ advanceUntilIdle()
assertFalse(viewModel.checkoutState.value.isLoading)
assertEquals("Please login to continue", viewModel.checkoutState.value.errorMessage)
@@ -58,6 +61,36 @@ class PaymentCheckoutViewModelTest {
assertEquals("https://mercury-t2.phonepe.com/transact/pg?token=token%2Babc", checkout.paymentUrl)
}
+ @Test
+ fun startCheckout_usesFullPaymentUrlWhenBackendReturnsIt() = runTest {
+ val paymentUrl = "https://mercury.phonepe.com/transact/pg?token=abc123"
+ val viewModel = PaymentCheckoutViewModel(
+ repository = fakeRepository(Result.success(orderData(token = null, paymentUrl = paymentUrl))),
+ isPhonePeProduction = true
+ )
+
+ viewModel.startCheckout("event-1", "General", emptyList(), authToken = "jwt")
+ advanceUntilIdle()
+
+ val checkout = assertNotNull(viewModel.checkoutState.value.checkoutData)
+ assertEquals(paymentUrl, checkout.paymentUrl)
+ }
+
+ @Test
+ fun startCheckout_failsWhenBackendOmitsCheckoutDetails() = runTest {
+ val viewModel = PaymentCheckoutViewModel(
+ repository = fakeRepository(Result.success(orderData(token = null, paymentUrl = null)))
+ )
+
+ viewModel.startCheckout("event-1", "General", emptyList(), authToken = "jwt")
+ advanceUntilIdle()
+
+ assertEquals(
+ "Payment response did not include checkout details. Please try again.",
+ viewModel.checkoutState.value.errorMessage
+ )
+ }
+
@Test
fun verifyPaymentStatus_setsCompletedStatus() = runTest {
val viewModel = PaymentCheckoutViewModel(
@@ -80,11 +113,12 @@ class PaymentCheckoutViewModelTest {
val viewModel = PaymentCheckoutViewModel(
repository = fakeRepository(
bookTicketResult = Result.success(orderData()),
- statusResult = Result.success(PaymentStatusData("pass-1", "uuid-1", "COMPLETED"))
+ statusResult = Result.failure(Exception("Please login to verify payment"))
)
)
viewModel.verifyPaymentStatus("merchant-1", authToken = null)
+ advanceUntilIdle()
assertFalse(viewModel.verificationState.value.isLoading)
assertEquals("Please login to verify payment", viewModel.verificationState.value.errorMessage)
@@ -98,6 +132,7 @@ class PaymentCheckoutViewModelTest {
eventId: String,
passType: String,
friends: List,
+ teamCode: String?,
authToken: String?
): Result = bookTicketResult
@@ -107,7 +142,10 @@ class PaymentCheckoutViewModelTest {
): Result = statusResult
}
- private fun orderData(token: String = "token") = PaymentOrderData(
+ private fun orderData(
+ token: String? = "token",
+ paymentUrl: String? = null
+ ) = PaymentOrderData(
passId = "pass-1",
merchantOrderId = "merchant-1",
orderId = "order-1",
@@ -115,6 +153,7 @@ class PaymentCheckoutViewModelTest {
amountInPaisa = 10000,
totalTickets = 1,
token = token,
+ paymentUrl = paymentUrl,
event = EventInfo(id = "event-1"),
qrStrings = emptyList(),
friends = emptyList()