Add unified close endpoint for Razorpay requests
All checks were successful
build-and-deploy / build-deploy (push) Successful in 34s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 34s
This commit is contained in:
@@ -1,19 +1,31 @@
|
|||||||
package com.android.trisolarisserver.controller
|
package com.android.trisolarisserver.controller
|
||||||
|
|
||||||
import com.android.trisolarisserver.component.PropertyAccess
|
import com.android.trisolarisserver.component.PropertyAccess
|
||||||
|
import com.android.trisolarisserver.controller.dto.RazorpayPaymentRequestCloseRequest
|
||||||
|
import com.android.trisolarisserver.controller.dto.RazorpayPaymentRequestCloseResponse
|
||||||
import com.android.trisolarisserver.controller.dto.RazorpayPaymentRequestResponse
|
import com.android.trisolarisserver.controller.dto.RazorpayPaymentRequestResponse
|
||||||
import com.android.trisolarisserver.db.repo.BookingRepo
|
import com.android.trisolarisserver.db.repo.BookingRepo
|
||||||
import com.android.trisolarisserver.models.property.Role
|
import com.android.trisolarisserver.models.property.Role
|
||||||
import com.android.trisolarisserver.repo.RazorpayPaymentLinkRequestRepo
|
import com.android.trisolarisserver.repo.RazorpayPaymentLinkRequestRepo
|
||||||
import com.android.trisolarisserver.repo.RazorpayQrRequestRepo
|
import com.android.trisolarisserver.repo.RazorpayQrRequestRepo
|
||||||
|
import com.android.trisolarisserver.repo.RazorpaySettingsRepo
|
||||||
import com.android.trisolarisserver.security.MyPrincipal
|
import com.android.trisolarisserver.security.MyPrincipal
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.springframework.http.HttpEntity
|
||||||
|
import org.springframework.http.HttpHeaders
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.client.RestTemplate
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import java.util.Base64
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -22,7 +34,10 @@ class RazorpayPaymentRequestsController(
|
|||||||
private val propertyAccess: PropertyAccess,
|
private val propertyAccess: PropertyAccess,
|
||||||
private val bookingRepo: BookingRepo,
|
private val bookingRepo: BookingRepo,
|
||||||
private val qrRequestRepo: RazorpayQrRequestRepo,
|
private val qrRequestRepo: RazorpayQrRequestRepo,
|
||||||
private val paymentLinkRequestRepo: RazorpayPaymentLinkRequestRepo
|
private val paymentLinkRequestRepo: RazorpayPaymentLinkRequestRepo,
|
||||||
|
private val settingsRepo: RazorpaySettingsRepo,
|
||||||
|
private val restTemplate: RestTemplate,
|
||||||
|
private val objectMapper: ObjectMapper
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping("/requests")
|
@GetMapping("/requests")
|
||||||
@@ -68,4 +83,94 @@ class RazorpayPaymentRequestsController(
|
|||||||
|
|
||||||
return (qrItems + linkItems).sortedByDescending { it.createdAt }
|
return (qrItems + linkItems).sortedByDescending { it.createdAt }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/close")
|
||||||
|
fun closeRequest(
|
||||||
|
@PathVariable propertyId: UUID,
|
||||||
|
@PathVariable bookingId: UUID,
|
||||||
|
@RequestBody request: RazorpayPaymentRequestCloseRequest,
|
||||||
|
@AuthenticationPrincipal principal: MyPrincipal?
|
||||||
|
): RazorpayPaymentRequestCloseResponse {
|
||||||
|
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER, Role.STAFF)
|
||||||
|
val booking = bookingRepo.findById(bookingId).orElseThrow {
|
||||||
|
ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found")
|
||||||
|
}
|
||||||
|
if (booking.property.id != propertyId) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found for property")
|
||||||
|
}
|
||||||
|
|
||||||
|
val qrId = request.qrId?.trim()?.ifBlank { null }
|
||||||
|
val linkId = request.paymentLinkId?.trim()?.ifBlank { null }
|
||||||
|
if ((qrId == null && linkId == null) || (qrId != null && linkId != null)) {
|
||||||
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Provide exactly one of qrId or paymentLinkId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val settings = settingsRepo.findByPropertyId(propertyId)
|
||||||
|
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay settings not configured")
|
||||||
|
|
||||||
|
if (qrId != null) {
|
||||||
|
val record = qrRequestRepo.findTopByQrIdOrderByCreatedAtDesc(qrId)
|
||||||
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "QR not found")
|
||||||
|
if (record.booking.id != bookingId) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "QR not found for booking")
|
||||||
|
}
|
||||||
|
val response = postJson(resolveBaseUrl(settings.isTest) + "/payments/qr_codes/$qrId/close", settings, "{}")
|
||||||
|
if (!response.statusCode.is2xxSuccessful) {
|
||||||
|
throw ResponseStatusException(HttpStatus.BAD_GATEWAY, "Razorpay close request failed")
|
||||||
|
}
|
||||||
|
record.status = "closed"
|
||||||
|
qrRequestRepo.save(record)
|
||||||
|
return RazorpayPaymentRequestCloseResponse(
|
||||||
|
type = "QR",
|
||||||
|
qrId = qrId,
|
||||||
|
status = "closed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentLinkId = linkId!!
|
||||||
|
val record = paymentLinkRequestRepo.findTopByPaymentLinkIdOrderByCreatedAtDesc(paymentLinkId)
|
||||||
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Payment link not found")
|
||||||
|
if (record.booking.id != bookingId) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Payment link not found for booking")
|
||||||
|
}
|
||||||
|
val response = postJson(resolveBaseUrl(settings.isTest) + "/payment_links/$paymentLinkId/cancel", settings, "{}")
|
||||||
|
if (!response.statusCode.is2xxSuccessful) {
|
||||||
|
throw ResponseStatusException(HttpStatus.BAD_GATEWAY, "Razorpay cancel request failed")
|
||||||
|
}
|
||||||
|
val body = response.body ?: "{}"
|
||||||
|
val status = runCatching { objectMapper.readTree(body).path("status").asText(null) }.getOrNull()
|
||||||
|
?: "cancelled"
|
||||||
|
record.status = status
|
||||||
|
paymentLinkRequestRepo.save(record)
|
||||||
|
return RazorpayPaymentRequestCloseResponse(
|
||||||
|
type = "PAYMENT_LINK",
|
||||||
|
paymentLinkId = paymentLinkId,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postJson(
|
||||||
|
url: String,
|
||||||
|
settings: com.android.trisolarisserver.models.payment.RazorpaySettings,
|
||||||
|
json: String
|
||||||
|
): org.springframework.http.ResponseEntity<String> {
|
||||||
|
val headers = HttpHeaders()
|
||||||
|
headers.contentType = MediaType.APPLICATION_JSON
|
||||||
|
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(settings.keyId, settings.keySecret))
|
||||||
|
return restTemplate.exchange(url, HttpMethod.POST, HttpEntity(json, headers), String::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveBaseUrl(isTest: Boolean): String {
|
||||||
|
return if (isTest) {
|
||||||
|
"https://api.razorpay.com/v1"
|
||||||
|
} else {
|
||||||
|
"https://api.razorpay.com/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun basicAuth(keyId: String, keySecret: String): String {
|
||||||
|
val raw = "$keyId:$keySecret"
|
||||||
|
val encoded = Base64.getEncoder().encodeToString(raw.toByteArray())
|
||||||
|
return "Basic $encoded"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,3 +88,15 @@ data class RazorpayPaymentRequestResponse(
|
|||||||
val paymentLinkId: String? = null,
|
val paymentLinkId: String? = null,
|
||||||
val paymentLink: String? = null
|
val paymentLink: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class RazorpayPaymentRequestCloseRequest(
|
||||||
|
val qrId: String? = null,
|
||||||
|
val paymentLinkId: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RazorpayPaymentRequestCloseResponse(
|
||||||
|
val type: String,
|
||||||
|
val qrId: String? = null,
|
||||||
|
val paymentLinkId: String? = null,
|
||||||
|
val status: String? = null
|
||||||
|
)
|
||||||
|
|||||||
@@ -13,4 +13,6 @@ interface RazorpayPaymentLinkRequestRepo : JpaRepository<RazorpayPaymentLinkRequ
|
|||||||
): RazorpayPaymentLinkRequest?
|
): RazorpayPaymentLinkRequest?
|
||||||
|
|
||||||
fun findByBookingIdOrderByCreatedAtDesc(bookingId: UUID): List<RazorpayPaymentLinkRequest>
|
fun findByBookingIdOrderByCreatedAtDesc(bookingId: UUID): List<RazorpayPaymentLinkRequest>
|
||||||
|
|
||||||
|
fun findTopByPaymentLinkIdOrderByCreatedAtDesc(paymentLinkId: String): RazorpayPaymentLinkRequest?
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user