diff --git a/app/src/main/java/com/android/trisolarispms/data/api/BookingApi.kt b/app/src/main/java/com/android/trisolarispms/data/api/BookingApi.kt index f857f0c..c27d8d3 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/BookingApi.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/BookingApi.kt @@ -15,7 +15,7 @@ import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest import com.android.trisolarispms.data.api.model.BookingDetailsResponse import com.android.trisolarispms.data.api.model.RoomStayDto import com.android.trisolarispms.data.api.model.RazorpayQrEventDto -import com.android.trisolarispms.data.api.model.RazorpayQrListItemDto +import com.android.trisolarispms.data.api.model.RazorpayRequestListItemDto import com.android.trisolarispms.data.api.model.RazorpayQrRequest import com.android.trisolarispms.data.api.model.RazorpayQrResponse import com.android.trisolarispms.data.api.model.RazorpayPaymentLinkRequest @@ -139,17 +139,17 @@ interface BookingApi { @Path("paymentId") paymentId: String ): Response - @GET("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr") - suspend fun listRazorpayQrs( + @GET("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/requests") + suspend fun listRazorpayRequests( @Path("propertyId") propertyId: String, @Path("bookingId") bookingId: String - ): Response> + ): Response> - @POST("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/close") - suspend fun closeRazorpayQr( + @POST("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/close") + suspend fun closeRazorpayRequest( @Path("propertyId") propertyId: String, @Path("bookingId") bookingId: String, - @Path("qrId") qrId: String + @Body body: com.android.trisolarispms.data.api.model.RazorpayCloseRequest ): Response @GET("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events") diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpayQrModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpayQrModels.kt index 25a1b89..b0c6f7a 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpayQrModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpayQrModels.kt @@ -24,6 +24,11 @@ data class RazorpayPaymentLinkResponse( val paymentLink: String? = null ) +data class RazorpayCloseRequest( + val qrId: String? = null, + val paymentLinkId: String? = null +) + data class RazorpayQrEventDto( val event: String? = null, val qrId: String? = null, @@ -31,12 +36,16 @@ data class RazorpayQrEventDto( val receivedAt: String? = null ) -data class RazorpayQrListItemDto( - val qrId: String? = null, +data class RazorpayRequestListItemDto( + val type: String? = null, + val requestId: String? = null, val amount: Long? = null, val currency: String? = null, val status: String? = null, + val createdAt: String? = null, + val qrId: String? = null, val imageUrl: String? = null, val expiryAt: String? = null, - val createdAt: String? = null + val paymentLinkId: String? = null, + val paymentLink: String? = null ) diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrScreen.kt index ce9eeab..c3e6284 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -45,6 +46,9 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.activity.compose.BackHandler import com.android.trisolarispms.BuildConfig +import androidx.compose.ui.platform.LocalContext +import android.content.Intent +import android.net.Uri @Composable @OptIn(ExperimentalMaterial3Api::class) @@ -57,6 +61,7 @@ fun RazorpayQrScreen( viewModel: RazorpayQrViewModel = viewModel() ) { val state by viewModel.state.collectAsState() + val context = LocalContext.current DisposableEffect(Unit) { onDispose { viewModel.reset() } @@ -205,6 +210,18 @@ fun RazorpayQrScreen( Text("Generate QR") } } + Spacer(modifier = Modifier.height(8.dp)) + TextButton( + onClick = { viewModel.generateLink(propertyId, bookingId) }, + enabled = !state.isLoading, + modifier = Modifier.fillMaxWidth() + ) { + if (state.isLoading) { + Text("Generating link…") + } else { + Text("Generate payment link") + } + } state.error?.let { Spacer(modifier = Modifier.height(12.dp)) @@ -213,13 +230,19 @@ fun RazorpayQrScreen( if (state.qrList.isNotEmpty()) { Spacer(modifier = Modifier.height(16.dp)) - Text(text = "QR List", style = MaterialTheme.typography.titleMedium) + Text(text = "Requests", style = MaterialTheme.typography.titleMedium) Spacer(modifier = Modifier.height(8.dp)) state.qrList.forEachIndexed { index, item -> + val status = item.status?.lowercase().orEmpty() + val isInactive = status == "closed" || status == "expired" || status == "credited" || status == "cancelled" || status == "canceled" Card( modifier = Modifier .fillMaxWidth() - .clickable { viewModel.openQrFromList(item) }, + .clickable( + enabled = !isInactive && + item.type?.equals("QR", ignoreCase = true) == true && + !item.imageUrl.isNullOrBlank() + ) { viewModel.openQrFromList(item) }, colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceVariant ) @@ -230,23 +253,58 @@ fun RazorpayQrScreen( .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "QR ${index + 1}", - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + Column { + Text( + text = when (item.type?.uppercase()) { + "PAYMENT_LINK" -> "Payment Link" + else -> "QR" + } + " ${index + 1}", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + item.status?.let { status -> + Text( + text = status.replaceFirstChar { it.uppercase() }, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } Spacer(modifier = Modifier.weight(1f)) Text( text = "${item.amount ?: "-"} ${item.currency.orEmpty()}", style = MaterialTheme.typography.titleMedium ) - val qrId = item.qrId - if (!qrId.isNullOrBlank()) { + if (item.type?.equals("PAYMENT_LINK", ignoreCase = true) == true && + !item.paymentLink.isNullOrBlank() && + !isInactive + ) { + Spacer(modifier = Modifier.width(8.dp)) + TextButton( + onClick = { + val intent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, item.paymentLink) + if (!guestPhone.isNullOrBlank()) { + putExtra("address", guestPhone) + putExtra(Intent.EXTRA_PHONE_NUMBER, guestPhone) + } + } + context.startActivity( + Intent.createChooser(intent, "Share payment link") + ) + } + ) { + Text("Share") + } + } + if (!item.qrId.isNullOrBlank() || !item.paymentLinkId.isNullOrBlank()) { IconButton( onClick = { - viewModel.closeQr(propertyId, bookingId, qrId) + viewModel.closeRequest(propertyId, bookingId, item) }, - modifier = Modifier.size(32.dp) + modifier = Modifier.size(32.dp), + enabled = !isInactive ) { Icon( imageVector = Icons.Default.Delete, diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrState.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrState.kt index 4b2e93a..b9c0a3d 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrState.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrState.kt @@ -12,5 +12,5 @@ data class RazorpayQrState( val isClosed: Boolean = false, val isCredited: Boolean = false, val paymentLink: String? = null, - val qrList: List = emptyList() + val qrList: List = emptyList() ) diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrViewModel.kt index babe3e0..79d9737 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrViewModel.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpayQrViewModel.kt @@ -4,8 +4,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.android.trisolarispms.data.api.ApiClient import com.android.trisolarispms.data.api.ApiConstants +import com.android.trisolarispms.data.api.model.RazorpayCloseRequest import com.android.trisolarispms.data.api.model.RazorpayQrEventDto -import com.android.trisolarispms.data.api.model.RazorpayQrListItemDto +import com.android.trisolarispms.data.api.model.RazorpayRequestListItemDto import com.android.trisolarispms.data.api.model.RazorpayPaymentLinkRequest import com.android.trisolarispms.data.api.model.RazorpayQrRequest import com.google.gson.Gson @@ -232,16 +233,10 @@ class RazorpayQrViewModel : ViewModel() { fun loadQrList(propertyId: String, bookingId: String) { viewModelScope.launch { try { - val response = ApiClient.create().listRazorpayQrs(propertyId, bookingId) + val response = ApiClient.create().listRazorpayRequests(propertyId, bookingId) val body = response.body() if (response.isSuccessful && body != null) { - val filtered = body.filterNot { item -> - when (item.status?.lowercase()) { - "closed", "expired", "credited" -> true - else -> false - } - } - _state.update { it.copy(qrList = filtered) } + _state.update { it.copy(qrList = body) } } } catch (_: Exception) { // ignore list load errors @@ -249,17 +244,20 @@ class RazorpayQrViewModel : ViewModel() { } } - fun closeQr(propertyId: String, bookingId: String, qrId: String) { + fun closeRequest(propertyId: String, bookingId: String, item: RazorpayRequestListItemDto) { + val qrId = item.qrId + val paymentLinkId = item.paymentLinkId + if (qrId.isNullOrBlank() && paymentLinkId.isNullOrBlank()) return viewModelScope.launch { try { - val response = ApiClient.create().closeRazorpayQr( + val response = ApiClient.create().closeRazorpayRequest( propertyId = propertyId, bookingId = bookingId, - qrId = qrId + body = RazorpayCloseRequest(qrId = qrId, paymentLinkId = paymentLinkId) ) if (response.isSuccessful) { _state.update { current -> - current.copy(qrList = current.qrList.filterNot { it.qrId == qrId }) + current.copy(qrList = current.qrList.filterNot { it.requestId == item.requestId }) } loadQrList(propertyId, bookingId) } @@ -269,7 +267,7 @@ class RazorpayQrViewModel : ViewModel() { } } - fun openQrFromList(item: RazorpayQrListItemDto) { + fun openQrFromList(item: RazorpayRequestListItemDto) { val status = item.status?.lowercase() _state.update { it.copy( @@ -288,7 +286,7 @@ class RazorpayQrViewModel : ViewModel() { startQrEventStream(propertyId, bookingId, state.value.qrId) } - fun generateLink(propertyId: String, bookingId: String, onReady: (String) -> Unit) { + fun generateLink(propertyId: String, bookingId: String) { val current = state.value val amount = current.amountInput.toLongOrNull()?.takeIf { it > 0 } if (amount == null) { @@ -315,7 +313,7 @@ class RazorpayQrViewModel : ViewModel() { error = null ) } - onReady(body.paymentLink) + loadQrList(propertyId, bookingId) } else { _state.update { it.copy(