razorpay: add payment link support
This commit is contained in:
@@ -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<Unit>
|
||||
|
||||
@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<List<RazorpayQrListItemDto>>
|
||||
): Response<List<RazorpayRequestListItemDto>>
|
||||
|
||||
@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<Unit>
|
||||
|
||||
@GET("properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events")
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,5 +12,5 @@ data class RazorpayQrState(
|
||||
val isClosed: Boolean = false,
|
||||
val isCredited: Boolean = false,
|
||||
val paymentLink: String? = null,
|
||||
val qrList: List<com.android.trisolarispms.data.api.model.RazorpayQrListItemDto> = emptyList()
|
||||
val qrList: List<com.android.trisolarispms.data.api.model.RazorpayRequestListItemDto> = emptyList()
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user