package com.android.trisolarisserver.controller import com.android.trisolarisserver.component.PropertyAccess import com.android.trisolarisserver.controller.dto.RazorpayQrGenerateRequest import com.android.trisolarisserver.controller.dto.RazorpayQrGenerateResponse import com.android.trisolarisserver.models.booking.BookingStatus import com.android.trisolarisserver.models.payment.RazorpayQrRequest import com.android.trisolarisserver.models.property.Role import com.android.trisolarisserver.db.repo.BookingRepo import com.android.trisolarisserver.repo.RazorpayQrRequestRepo import com.android.trisolarisserver.repo.RazorpaySettingsRepo 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.ResponseEntity import org.springframework.security.core.annotation.AuthenticationPrincipal 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.RestController import org.springframework.web.client.RestTemplate import org.springframework.web.server.ResponseStatusException import org.springframework.http.HttpStatus import org.springframework.transaction.annotation.Transactional import java.time.OffsetDateTime import java.util.Base64 import java.util.UUID @RestController @RequestMapping("/properties/{propertyId}/bookings/{bookingId}/payments/razorpay") class RazorpayQrPayments( private val propertyAccess: PropertyAccess, private val bookingRepo: BookingRepo, private val settingsRepo: RazorpaySettingsRepo, private val qrRequestRepo: RazorpayQrRequestRepo, private val restTemplate: RestTemplate, private val objectMapper: ObjectMapper ) { @PostMapping("/qr") @Transactional fun createQr( @PathVariable propertyId: UUID, @PathVariable bookingId: UUID, @RequestBody request: RazorpayQrGenerateRequest, @AuthenticationPrincipal principal: MyPrincipal? ): RazorpayQrGenerateResponse { 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") } if (booking.status == BookingStatus.CANCELLED || booking.status == BookingStatus.NO_SHOW) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Booking is not active") } val settings = settingsRepo.findByPropertyId(propertyId) ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay settings not configured") val amount = request.amount ?: 0L if (amount <= 0) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "amount must be > 0") } val currency = booking.property.currency val existing = qrRequestRepo.findTopByBookingIdAndAmountAndCurrencyAndStatusOrderByCreatedAtDesc( bookingId, amount, currency, "active" ) if (existing != null && existing.qrId != null) { return RazorpayQrGenerateResponse( qrId = existing.qrId, amount = existing.amount, currency = existing.currency, imageUrl = existing.imageUrl, razorpayResponse = existing.responsePayload ?: "{}" ) } val expirySeconds = request.expirySeconds ?: request.expiryMinutes?.let { it * 60 } val expiresAt = expirySeconds?.let { OffsetDateTime.now().plusSeconds(it.toLong()) } val notes = mapOf( "bookingId" to bookingId.toString(), "propertyId" to propertyId.toString() ) val payload = linkedMapOf( "type" to "upi_qr", "name" to "Booking $bookingId", "usage" to "single_use", "fixed_amount" to true, "payment_amount" to amount * 100, "notes" to notes ) if (expirySeconds != null) { payload["close_by"] = OffsetDateTime.now().plusSeconds(expirySeconds.toLong()).toEpochSecond() } val requestPayload = objectMapper.writeValueAsString(payload) val response = postJson(resolveBaseUrl(settings.isTest) + "/qr_codes", settings, requestPayload) val body = response.body ?: "{}" val node = objectMapper.readTree(body) val qrId = node.path("id").asText(null) val status = node.path("status").asText("unknown") val imageUrl = node.path("image_url").asText(null) val record = qrRequestRepo.save( RazorpayQrRequest( property = booking.property, booking = booking, qrId = qrId, amount = amount, currency = currency, status = status, imageUrl = imageUrl, requestPayload = requestPayload, responsePayload = body, expiryAt = expiresAt ) ) if (!response.statusCode.is2xxSuccessful) { record.status = "failed" qrRequestRepo.save(record) throw ResponseStatusException(HttpStatus.BAD_GATEWAY, "Razorpay request failed") } return RazorpayQrGenerateResponse( qrId = qrId, amount = amount, currency = currency, imageUrl = imageUrl, razorpayResponse = body ) } private fun postJson(url: String, settings: com.android.trisolarisserver.models.payment.RazorpaySettings, json: String): ResponseEntity { 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" } }