From 3219e40a02ebe671fedb15323190410f6cbd41c8 Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Sun, 1 Feb 2026 16:16:44 +0530 Subject: [PATCH] payments: ability to refund --- .../com/android/trisolarispms/MainActivity.kt | 1 + .../trisolarispms/data/api/BookingApi.kt | 9 ++ .../data/api/model/PaymentModels.kt | 14 ++ .../data/api/model/RazorpaySettingsModels.kt | 17 ++- .../ui/payment/BookingPaymentsScreen.kt | 84 ++++++++++- .../ui/payment/BookingPaymentsState.kt | 1 + .../ui/payment/BookingPaymentsViewModel.kt | 97 ++++++++++-- .../ui/razorpay/RazorpaySettingsScreen.kt | 141 ++++++++---------- .../ui/razorpay/RazorpaySettingsState.kt | 9 +- .../ui/razorpay/RazorpaySettingsViewModel.kt | 75 ++++++---- 10 files changed, 323 insertions(+), 125 deletions(-) diff --git a/app/src/main/java/com/android/trisolarispms/MainActivity.kt b/app/src/main/java/com/android/trisolarispms/MainActivity.kt index 00afef0..b87f625 100644 --- a/app/src/main/java/com/android/trisolarispms/MainActivity.kt +++ b/app/src/main/java/com/android/trisolarispms/MainActivity.kt @@ -491,6 +491,7 @@ class MainActivity : ComponentActivity() { bookingId = currentRoute.bookingId, canAddCash = canManageRazorpaySettings(currentRoute.propertyId), canDeleteCash = canDeleteCashPayment(currentRoute.propertyId), + canRefund = canManageRazorpaySettings(currentRoute.propertyId), onBack = { route.value = AppRoute.BookingDetailsTabs( currentRoute.propertyId, 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 c27d8d3..3697831 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 @@ -22,6 +22,8 @@ import com.android.trisolarispms.data.api.model.RazorpayPaymentLinkRequest import com.android.trisolarispms.data.api.model.RazorpayPaymentLinkResponse import com.android.trisolarispms.data.api.model.PaymentDto import com.android.trisolarispms.data.api.model.PaymentCreateRequest +import com.android.trisolarispms.data.api.model.RazorpayRefundRequest +import com.android.trisolarispms.data.api.model.RazorpayRefundResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET @@ -132,6 +134,13 @@ interface BookingApi { @Body body: PaymentCreateRequest ): Response + @POST("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/refund") + suspend fun refundRazorpayPayment( + @Path("propertyId") propertyId: String, + @Path("bookingId") bookingId: String, + @Body body: RazorpayRefundRequest + ): Response + @DELETE("properties/{propertyId}/bookings/{bookingId}/payments/{paymentId}") suspend fun deletePayment( @Path("propertyId") propertyId: String, diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/PaymentModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/PaymentModels.kt index 8a3eef2..d33ef3a 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/PaymentModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/PaymentModels.kt @@ -23,3 +23,17 @@ data class PaymentDto( data class PaymentCreateRequest( val amount: Long ) + +data class RazorpayRefundRequest( + val paymentId: String? = null, + val razorpayPaymentId: String? = null, + val amount: Long, + val notes: String? = null +) + +data class RazorpayRefundResponse( + val refundId: String? = null, + val status: String? = null, + val amount: Long? = null, + val currency: String? = null +) diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpaySettingsModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpaySettingsModels.kt index ef59145..3e75270 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpaySettingsModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/RazorpaySettingsModels.kt @@ -1,22 +1,23 @@ package com.android.trisolarispms.data.api.model -import com.google.gson.annotations.SerializedName - data class RazorpaySettingsRequest( - val keyId: String, + val keyId: String? = null, val keySecret: String? = null, val webhookSecret: String? = null, - @SerializedName("isTest") val isTest: Boolean, - val useSalt256: Boolean + val keyIdTest: String? = null, + val keySecretTest: String? = null, + val webhookSecretTest: String? = null, + val isTest: Boolean ) data class RazorpaySettingsResponse( val propertyId: String? = null, val configured: Boolean? = null, - @SerializedName("test") val isTest: Boolean? = null, - val useSalt256: Boolean? = null, + @com.google.gson.annotations.SerializedName("test") val isTest: Boolean? = null, val hasKeyId: Boolean? = null, val hasKeySecret: Boolean? = null, val hasWebhookSecret: Boolean? = null, - val hasSalt256: Boolean? = null + val hasKeyIdTest: Boolean? = null, + val hasKeySecretTest: Boolean? = null, + val hasWebhookSecretTest: Boolean? = null ) diff --git a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsScreen.kt index df47be4..f2fd641 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsScreen.kt @@ -25,14 +25,17 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.AlertDialog import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType @@ -49,11 +52,15 @@ fun BookingPaymentsScreen( bookingId: String, canAddCash: Boolean, canDeleteCash: Boolean, + canRefund: Boolean, onBack: () -> Unit, viewModel: BookingPaymentsViewModel = viewModel() ) { val state by viewModel.state.collectAsState() val amountInput = remember { mutableStateOf("") } + val refundTarget = remember { mutableStateOf(null) } + val refundAmount = rememberSaveable { mutableStateOf("") } + val refundNotes = rememberSaveable { mutableStateOf("") } LaunchedEffect(propertyId, bookingId) { viewModel.load(propertyId, bookingId) @@ -108,6 +115,10 @@ fun BookingPaymentsScreen( Text(text = it, color = MaterialTheme.colorScheme.error) Spacer(modifier = Modifier.height(8.dp)) } + state.message?.let { + Text(text = it, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + } if (!state.isLoading && state.error == null && state.payments.isEmpty()) { Text(text = "No payments yet") } @@ -121,21 +132,87 @@ fun BookingPaymentsScreen( PaymentCard( payment = payment, canDeleteCash = canDeleteCash, + canRefund = canRefund, onDelete = { paymentId -> viewModel.deleteCashPayment(propertyId, bookingId, paymentId) + }, + onRefund = { target -> + refundTarget.value = target + refundAmount.value = target.amount?.toString().orEmpty() + refundNotes.value = "" } ) } } } } + + refundTarget.value?.let { payment -> + val hasGateway = !payment.gatewayPaymentId.isNullOrBlank() + val hasPaymentId = !payment.id.isNullOrBlank() + val amountValue = refundAmount.value.toLongOrNull() + val canSubmit = amountValue != null && amountValue > 0 && (hasGateway || hasPaymentId) + AlertDialog( + onDismissRequest = { refundTarget.value = null }, + title = { Text("Confirm refund") }, + text = { + Column { + Text( + text = "This will initiate a Razorpay refund. Please confirm before proceeding.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = refundAmount.value, + onValueChange = { refundAmount.value = it.filter { ch -> ch.isDigit() } }, + label = { Text("Refund amount") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = refundNotes.value, + onValueChange = { refundNotes.value = it }, + label = { Text("Notes (optional)") }, + modifier = Modifier.fillMaxWidth() + ) + } + }, + confirmButton = { + TextButton( + onClick = { + viewModel.refundRazorpayPayment( + propertyId = propertyId, + bookingId = bookingId, + paymentId = if (!payment.id.isNullOrBlank()) payment.id else null, + razorpayPaymentId = if (payment.id.isNullOrBlank()) payment.gatewayPaymentId else null, + amount = amountValue ?: 0L, + notes = refundNotes.value + ) + refundTarget.value = null + }, + enabled = canSubmit + ) { + Text("Refund") + } + }, + dismissButton = { + TextButton(onClick = { refundTarget.value = null }) { + Text("Cancel") + } + } + ) + } } @Composable private fun PaymentCard( payment: PaymentDto, canDeleteCash: Boolean, - onDelete: (String) -> Unit + canRefund: Boolean, + onDelete: (String) -> Unit, + onRefund: (PaymentDto) -> Unit ) { val date = payment.receivedAt?.let { runCatching { OffsetDateTime.parse(it) }.getOrNull() @@ -163,6 +240,11 @@ private fun PaymentCard( payment.currency?.let { append(" $it") } } Text(text = amountText, style = MaterialTheme.typography.titleMedium) + if (canRefund && (!payment.id.isNullOrBlank() || !payment.gatewayPaymentId.isNullOrBlank())) { + TextButton(onClick = { onRefund(payment) }) { + Text("Refund") + } + } if (canDeleteCash && isCash && !payment.id.isNullOrBlank()) { IconButton(onClick = { onDelete(payment.id) }) { Icon( diff --git a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsState.kt b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsState.kt index b384c8b..d8e4a78 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsState.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsState.kt @@ -5,5 +5,6 @@ import com.android.trisolarispms.data.api.model.PaymentDto data class BookingPaymentsState( val isLoading: Boolean = false, val error: String? = null, + val message: String? = null, val payments: List = emptyList() ) diff --git a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsViewModel.kt index 5d307e4..3fc5e83 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsViewModel.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/payment/BookingPaymentsViewModel.kt @@ -3,6 +3,7 @@ package com.android.trisolarispms.ui.payment import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.android.trisolarispms.data.api.ApiClient +import com.android.trisolarispms.data.api.model.RazorpayRefundRequest import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -14,7 +15,7 @@ class BookingPaymentsViewModel : ViewModel() { fun load(propertyId: String, bookingId: String) { viewModelScope.launch { - _state.update { it.copy(isLoading = true, error = null) } + _state.update { it.copy(isLoading = true, error = null, message = null) } try { val api = ApiClient.create() val response = api.listPayments(propertyId, bookingId) @@ -24,14 +25,16 @@ class BookingPaymentsViewModel : ViewModel() { it.copy( isLoading = false, payments = body, - error = null + error = null, + message = null ) } } else { _state.update { it.copy( isLoading = false, - error = "Load failed: ${response.code()}" + error = "Load failed: ${response.code()}", + message = null ) } } @@ -39,7 +42,8 @@ class BookingPaymentsViewModel : ViewModel() { _state.update { it.copy( isLoading = false, - error = e.localizedMessage ?: "Load failed" + error = e.localizedMessage ?: "Load failed", + message = null ) } } @@ -48,11 +52,11 @@ class BookingPaymentsViewModel : ViewModel() { fun addCashPayment(propertyId: String, bookingId: String, amount: Long) { if (amount <= 0) { - _state.update { it.copy(error = "Amount must be greater than 0") } + _state.update { it.copy(error = "Amount must be greater than 0", message = null) } return } viewModelScope.launch { - _state.update { it.copy(isLoading = true, error = null) } + _state.update { it.copy(isLoading = true, error = null, message = null) } try { val api = ApiClient.create() val response = api.createPayment( @@ -66,14 +70,16 @@ class BookingPaymentsViewModel : ViewModel() { current.copy( isLoading = false, payments = listOf(body) + current.payments, - error = null + error = null, + message = "Cash payment added" ) } } else { _state.update { it.copy( isLoading = false, - error = "Create failed: ${response.code()}" + error = "Create failed: ${response.code()}", + message = null ) } } @@ -81,7 +87,8 @@ class BookingPaymentsViewModel : ViewModel() { _state.update { it.copy( isLoading = false, - error = e.localizedMessage ?: "Create failed" + error = e.localizedMessage ?: "Create failed", + message = null ) } } @@ -90,7 +97,7 @@ class BookingPaymentsViewModel : ViewModel() { fun deleteCashPayment(propertyId: String, bookingId: String, paymentId: String) { viewModelScope.launch { - _state.update { it.copy(isLoading = true, error = null) } + _state.update { it.copy(isLoading = true, error = null, message = null) } try { val api = ApiClient.create() val response = api.deletePayment( @@ -103,14 +110,16 @@ class BookingPaymentsViewModel : ViewModel() { current.copy( isLoading = false, payments = current.payments.filterNot { it.id == paymentId }, - error = null + error = null, + message = "Cash payment deleted" ) } } else { _state.update { it.copy( isLoading = false, - error = "Delete failed: ${response.code()}" + error = "Delete failed: ${response.code()}", + message = null ) } } @@ -118,7 +127,69 @@ class BookingPaymentsViewModel : ViewModel() { _state.update { it.copy( isLoading = false, - error = e.localizedMessage ?: "Delete failed" + error = e.localizedMessage ?: "Delete failed", + message = null + ) + } + } + } + } + + fun refundRazorpayPayment( + propertyId: String, + bookingId: String, + paymentId: String?, + razorpayPaymentId: String?, + amount: Long, + notes: String? + ) { + if (amount <= 0) { + _state.update { it.copy(error = "Amount must be greater than 0", message = null) } + return + } + if (paymentId.isNullOrBlank() && razorpayPaymentId.isNullOrBlank()) { + _state.update { it.copy(error = "Missing payment ID", message = null) } + return + } + viewModelScope.launch { + _state.update { it.copy(isLoading = true, error = null, message = null) } + try { + val api = ApiClient.create() + val response = api.refundRazorpayPayment( + propertyId = propertyId, + bookingId = bookingId, + body = RazorpayRefundRequest( + paymentId = paymentId, + razorpayPaymentId = razorpayPaymentId, + amount = amount, + notes = notes?.takeIf { it.isNotBlank() } + ) + ) + val body = response.body() + if (response.isSuccessful && body != null) { + _state.update { + it.copy( + isLoading = false, + error = null, + message = "Refund processed" + ) + } + load(propertyId, bookingId) + } else { + _state.update { + it.copy( + isLoading = false, + error = "Refund failed: ${response.code()}", + message = null + ) + } + } + } catch (e: Exception) { + _state.update { + it.copy( + isLoading = false, + error = e.localizedMessage ?: "Refund failed", + message = null ) } } diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsScreen.kt index b736ed4..755ea80 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsScreen.kt @@ -74,11 +74,12 @@ fun RazorpaySettingsScreen( propertyId = propertyId, state = state, onKeyIdChange = viewModel::onMerchantKeyChange, - onKeySecretChange = viewModel::onSalt32Change, + onKeySecretChange = viewModel::onKeySecretChange, onWebhookSecretChange = viewModel::onWebhookSecretChange, + onKeyIdTestChange = viewModel::onKeyIdTestChange, + onKeySecretTestChange = viewModel::onKeySecretTestChange, + onWebhookSecretTestChange = viewModel::onWebhookSecretTestChange, onIsTestChange = viewModel::onIsTestChange, - onUseSalt256Change = viewModel::onUseSalt256Change, - onSalt256Change = viewModel::onSalt256Change, onSave = { viewModel.save(propertyId) }, clipboard = clipboard ) @@ -93,9 +94,10 @@ private fun RazorpaySettingsTab( onKeyIdChange: (String) -> Unit, onKeySecretChange: (String) -> Unit, onWebhookSecretChange: (String) -> Unit, + onKeyIdTestChange: (String) -> Unit, + onKeySecretTestChange: (String) -> Unit, + onWebhookSecretTestChange: (String) -> Unit, onIsTestChange: (Boolean) -> Unit, - onUseSalt256Change: (Boolean) -> Unit, - onSalt256Change: (String) -> Unit, onSave: () -> Unit, clipboard: ClipboardManager ) { @@ -132,15 +134,12 @@ private fun RazorpaySettingsTab( style = MaterialTheme.typography.bodySmall ) Spacer(modifier = Modifier.height(8.dp)) - if (state.hasKeyId) { - Text(text = "Key ID saved", style = MaterialTheme.typography.bodySmall) - } - if (state.hasKeySecret) { - Text(text = "Key Secret saved", style = MaterialTheme.typography.bodySmall) - } - if (state.hasWebhookSecret) { - Text(text = "Webhook Secret saved", style = MaterialTheme.typography.bodySmall) - } + if (state.hasKeyId) Text(text = "Live Key ID saved", style = MaterialTheme.typography.bodySmall) + if (state.hasKeySecret) Text(text = "Live Key Secret saved", style = MaterialTheme.typography.bodySmall) + if (state.hasWebhookSecret) Text(text = "Live Webhook Secret saved", style = MaterialTheme.typography.bodySmall) + if (state.hasKeyIdTest) Text(text = "Test Key ID saved", style = MaterialTheme.typography.bodySmall) + if (state.hasKeySecretTest) Text(text = "Test Key Secret saved", style = MaterialTheme.typography.bodySmall) + if (state.hasWebhookSecretTest) Text(text = "Test Webhook Secret saved", style = MaterialTheme.typography.bodySmall) Spacer(modifier = Modifier.height(12.dp)) state.message?.let { @@ -153,22 +152,59 @@ private fun RazorpaySettingsTab( Spacer(modifier = Modifier.height(8.dp)) } - OutlinedTextField( - value = state.keyId, - onValueChange = onKeyIdChange, - label = { Text("Key ID") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(12.dp)) - OutlinedTextField( - value = state.webhookSecret, - onValueChange = onWebhookSecretChange, - label = { Text("Webhook Secret") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(12.dp)) + Text(text = "Live", style = MaterialTheme.typography.titleSmall) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = state.keyId, + onValueChange = onKeyIdChange, + label = { Text("Key ID (Live)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = state.keySecret, + onValueChange = onKeySecretChange, + label = { Text("Key Secret (Live)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = state.webhookSecret, + onValueChange = onWebhookSecretChange, + label = { Text("Webhook Secret (Live)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + + Text(text = "Test", style = MaterialTheme.typography.titleSmall) + Spacer(modifier = Modifier.height(8.dp)) + OutlinedTextField( + value = state.keyIdTest, + onValueChange = onKeyIdTestChange, + label = { Text("Key ID (Test)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = state.keySecretTest, + onValueChange = onKeySecretTestChange, + label = { Text("Key Secret (Test)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = state.webhookSecretTest, + onValueChange = onWebhookSecretTestChange, + label = { Text("Webhook Secret (Test)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) Row( modifier = Modifier.fillMaxWidth(), @@ -183,53 +219,8 @@ private fun RazorpaySettingsTab( } Spacer(modifier = Modifier.height(12.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text(text = "Use secondary secret") - Switch( - checked = state.useSalt256, - onCheckedChange = onUseSalt256Change - ) - } Spacer(modifier = Modifier.height(8.dp)) - if (state.useSalt256) { - OutlinedTextField( - value = state.salt256, - onValueChange = onSalt256Change, - label = { Text("Secret (secondary)") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - modifier = Modifier.fillMaxWidth() - ) - if (state.hasSalt256) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "Salt256 saved", - style = MaterialTheme.typography.bodySmall - ) - } - } else { - OutlinedTextField( - value = state.keySecret, - onValueChange = onKeySecretChange, - label = { Text("Key Secret") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - modifier = Modifier.fillMaxWidth() - ) - if (state.hasKeySecret) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "Key Secret saved", - style = MaterialTheme.typography.bodySmall - ) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - Button( onClick = onSave, enabled = !state.isSaving, diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsState.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsState.kt index 52b5f74..ce4148d 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsState.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsState.kt @@ -4,13 +4,16 @@ data class RazorpaySettingsState( val keyId: String = "", val keySecret: String = "", val webhookSecret: String = "", - val salt256: String = "", + val keyIdTest: String = "", + val keySecretTest: String = "", + val webhookSecretTest: String = "", val isTest: Boolean = false, - val useSalt256: Boolean = false, val hasKeyId: Boolean = false, val hasKeySecret: Boolean = false, val hasWebhookSecret: Boolean = false, - val hasSalt256: Boolean = false, + val hasKeyIdTest: Boolean = false, + val hasKeySecretTest: Boolean = false, + val hasWebhookSecretTest: Boolean = false, val configured: Boolean = false, val isLoading: Boolean = false, val isSaving: Boolean = false, diff --git a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsViewModel.kt index c1fff4f..2006ebd 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsViewModel.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/razorpay/RazorpaySettingsViewModel.kt @@ -23,7 +23,7 @@ class RazorpaySettingsViewModel : ViewModel() { _state.update { it.copy(keyId = value, error = null) } } - fun onSalt32Change(value: String) { + fun onKeySecretChange(value: String) { _state.update { it.copy(keySecret = value, error = null) } } @@ -31,37 +31,51 @@ class RazorpaySettingsViewModel : ViewModel() { _state.update { it.copy(webhookSecret = value, error = null) } } - fun onSalt256Change(value: String) { - _state.update { it.copy(salt256 = value, error = null) } + fun onKeyIdTestChange(value: String) { + _state.update { it.copy(keyIdTest = value, error = null) } + } + + fun onKeySecretTestChange(value: String) { + _state.update { it.copy(keySecretTest = value, error = null) } + } + + fun onWebhookSecretTestChange(value: String) { + _state.update { it.copy(webhookSecretTest = value, error = null) } } fun onIsTestChange(value: Boolean) { _state.update { it.copy(isTest = value, error = null) } } - fun onUseSalt256Change(value: Boolean) { - _state.update { it.copy(useSalt256 = value, error = null) } - } - fun save(propertyId: String) { val current = state.value val keyId = current.keyId.trim() val keySecret = current.keySecret.trim() val webhookSecret = current.webhookSecret.trim() - val salt256 = current.salt256.trim() + val keyIdTest = current.keyIdTest.trim() + val keySecretTest = current.keySecretTest.trim() + val webhookSecretTest = current.webhookSecretTest.trim() if (!current.configured) { - if (keyId.isBlank()) { - _state.update { it.copy(error = "Key ID is required") } - return - } - if (current.useSalt256 && salt256.isBlank() && !current.hasSalt256) { - _state.update { it.copy(error = "Secondary secret is required") } - return - } - if (!current.useSalt256 && keySecret.isBlank() && !current.hasKeySecret) { - _state.update { it.copy(error = "Key secret is required") } - return + val needsTest = current.isTest + if (needsTest) { + if (keyIdTest.isBlank() && !current.hasKeyIdTest) { + _state.update { it.copy(error = "Test Key ID is required") } + return + } + if (keySecretTest.isBlank() && !current.hasKeySecretTest) { + _state.update { it.copy(error = "Test Key Secret is required") } + return + } + } else { + if (keyId.isBlank() && !current.hasKeyId) { + _state.update { it.copy(error = "Key ID is required") } + return + } + if (keySecret.isBlank() && !current.hasKeySecret) { + _state.update { it.copy(error = "Key Secret is required") } + return + } } } else { val hasKeyIdInput = keyId.isNotBlank() @@ -70,6 +84,12 @@ class RazorpaySettingsViewModel : ViewModel() { _state.update { it.copy(error = "Key ID and Key Secret must be provided together") } return } + val hasKeyIdTestInput = keyIdTest.isNotBlank() + val hasKeySecretTestInput = keySecretTest.isNotBlank() + if (hasKeyIdTestInput xor hasKeySecretTestInput) { + _state.update { it.copy(error = "Test Key ID and Test Key Secret must be provided together") } + return + } } viewModelScope.launch { @@ -79,11 +99,13 @@ class RazorpaySettingsViewModel : ViewModel() { val response = api.updateRazorpaySettings( propertyId = propertyId, body = RazorpaySettingsRequest( - keyId = keyId, + keyId = keyId.ifBlank { null }, keySecret = keySecret.ifBlank { null }, webhookSecret = webhookSecret.ifBlank { null }, - isTest = current.isTest, - useSalt256 = current.useSalt256 + keyIdTest = keyIdTest.ifBlank { null }, + keySecretTest = keySecretTest.ifBlank { null }, + webhookSecretTest = webhookSecretTest.ifBlank { null }, + isTest = current.isTest ) ) if (response.isSuccessful) { @@ -124,15 +146,18 @@ class RazorpaySettingsViewModel : ViewModel() { it.copy( keyId = "", isTest = body.isTest == true, - useSalt256 = body.useSalt256 == true, hasKeyId = body.hasKeyId == true, hasKeySecret = body.hasKeySecret == true, hasWebhookSecret = body.hasWebhookSecret == true, - hasSalt256 = body.hasSalt256 == true, + hasKeyIdTest = body.hasKeyIdTest == true, + hasKeySecretTest = body.hasKeySecretTest == true, + hasWebhookSecretTest = body.hasWebhookSecretTest == true, configured = body.configured == true, keySecret = "", webhookSecret = "", - salt256 = "", + keyIdTest = "", + keySecretTest = "", + webhookSecretTest = "", isLoading = false, isSaving = false, error = null