Store Razorpay test keys alongside live keys
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:
@@ -27,6 +27,9 @@ class RazorpaySettingsSchemaFix(
|
||||
key_id varchar not null,
|
||||
key_secret varchar not null,
|
||||
webhook_secret varchar,
|
||||
key_id_test varchar,
|
||||
key_secret_test varchar,
|
||||
webhook_secret_test varchar,
|
||||
is_test boolean not null default false,
|
||||
updated_at timestamptz not null
|
||||
)
|
||||
@@ -37,5 +40,8 @@ class RazorpaySettingsSchemaFix(
|
||||
jdbcTemplate.execute("alter table razorpay_settings alter column key_id type text")
|
||||
jdbcTemplate.execute("alter table razorpay_settings alter column key_secret type text")
|
||||
jdbcTemplate.execute("alter table razorpay_settings alter column webhook_secret type text")
|
||||
jdbcTemplate.execute("alter table razorpay_settings add column if not exists key_id_test text")
|
||||
jdbcTemplate.execute("alter table razorpay_settings add column if not exists key_secret_test text")
|
||||
jdbcTemplate.execute("alter table razorpay_settings add column if not exists webhook_secret_test text")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +146,10 @@ class RazorpayPaymentLinksController(
|
||||
}
|
||||
|
||||
private fun postJson(url: String, settings: com.android.trisolarisserver.models.payment.RazorpaySettings, json: String): ResponseEntity<String> {
|
||||
val (keyId, keySecret) = requireKeys(settings)
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(settings.keyId, settings.keySecret))
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(keyId, keySecret))
|
||||
return restTemplate.exchange(url, HttpMethod.POST, HttpEntity(json, headers), String::class.java)
|
||||
}
|
||||
|
||||
@@ -166,6 +167,14 @@ class RazorpayPaymentLinksController(
|
||||
return "Basic $encoded"
|
||||
}
|
||||
|
||||
private fun requireKeys(settings: com.android.trisolarisserver.models.payment.RazorpaySettings): Pair<String, String> {
|
||||
val keyId = settings.resolveKeyId()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
val keySecret = settings.resolveKeySecret()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
return keyId to keySecret
|
||||
}
|
||||
|
||||
private fun parseExpiryEpoch(value: String?): Long? {
|
||||
if (value.isNullOrBlank()) return null
|
||||
return value.trim().toLongOrNull()
|
||||
|
||||
@@ -158,9 +158,10 @@ class RazorpayPaymentRequestsController(
|
||||
settings: com.android.trisolarisserver.models.payment.RazorpaySettings,
|
||||
json: String
|
||||
): org.springframework.http.ResponseEntity<String> {
|
||||
val (keyId, keySecret) = requireKeys(settings)
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(settings.keyId, settings.keySecret))
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(keyId, keySecret))
|
||||
return restTemplate.exchange(url, HttpMethod.POST, HttpEntity(json, headers), String::class.java)
|
||||
}
|
||||
|
||||
@@ -178,6 +179,14 @@ class RazorpayPaymentRequestsController(
|
||||
return "Basic $encoded"
|
||||
}
|
||||
|
||||
private fun requireKeys(settings: com.android.trisolarisserver.models.payment.RazorpaySettings): Pair<String, String> {
|
||||
val keyId = settings.resolveKeyId()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
val keySecret = settings.resolveKeySecret()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
return keyId to keySecret
|
||||
}
|
||||
|
||||
private fun isQrClosed(status: String?): Boolean {
|
||||
return when (status?.lowercase()) {
|
||||
"closed", "expired", "credited" -> true
|
||||
|
||||
@@ -310,9 +310,10 @@ class RazorpayQrPayments(
|
||||
}
|
||||
|
||||
private fun postJson(url: String, settings: com.android.trisolarisserver.models.payment.RazorpaySettings, json: String): ResponseEntity<String> {
|
||||
val (keyId, keySecret) = requireKeys(settings)
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(settings.keyId, settings.keySecret))
|
||||
headers.set(HttpHeaders.AUTHORIZATION, basicAuth(keyId, keySecret))
|
||||
return restTemplate.exchange(url, HttpMethod.POST, HttpEntity(json, headers), String::class.java)
|
||||
}
|
||||
|
||||
@@ -329,4 +330,12 @@ class RazorpayQrPayments(
|
||||
val encoded = Base64.getEncoder().encodeToString(raw.toByteArray())
|
||||
return "Basic $encoded"
|
||||
}
|
||||
|
||||
private fun requireKeys(settings: com.android.trisolarisserver.models.payment.RazorpaySettings): Pair<String, String> {
|
||||
val keyId = settings.resolveKeyId()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
val keySecret = settings.resolveKeySecret()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Razorpay test keys not configured")
|
||||
return keyId to keySecret
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,10 @@ class RazorpaySettingsController(
|
||||
isTest = false,
|
||||
hasKeyId = false,
|
||||
hasKeySecret = false,
|
||||
hasWebhookSecret = false
|
||||
hasWebhookSecret = false,
|
||||
hasKeyIdTest = false,
|
||||
hasKeySecretTest = false,
|
||||
hasWebhookSecretTest = false
|
||||
)
|
||||
} else {
|
||||
settings.toResponse()
|
||||
@@ -68,21 +71,39 @@ class RazorpaySettingsController(
|
||||
candidate?.trim()?.ifBlank { null } ?: request.salt256?.trim()?.ifBlank { null } ?: request.salt32?.trim()?.ifBlank { null }
|
||||
}
|
||||
val webhookSecret = request.webhookSecret?.trim()?.ifBlank { null }
|
||||
val keyIdTest = request.keyIdTest?.trim()?.ifBlank { null }
|
||||
val keySecretTest = request.keySecretTest?.trim()?.ifBlank { null }
|
||||
val webhookSecretTest = request.webhookSecretTest?.trim()?.ifBlank { null }
|
||||
val isTest = request.isTest
|
||||
val hasKeyId = keyId != null
|
||||
val hasKeySecret = keySecret != null
|
||||
if (hasKeyId != hasKeySecret) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "keyId and keySecret must be provided together")
|
||||
}
|
||||
val hasKeyIdTest = keyIdTest != null
|
||||
val hasKeySecretTest = keySecretTest != null
|
||||
if (hasKeyIdTest != hasKeySecretTest) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "keyIdTest and keySecretTest must be provided together")
|
||||
}
|
||||
if (existing == null && (keyId == null || keySecret == null)) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "keyId/keySecret required")
|
||||
}
|
||||
if (isTest == true || existing?.isTest == true) {
|
||||
val effectiveKeyIdTest = keyIdTest ?: existing?.keyIdTest
|
||||
val effectiveKeySecretTest = keySecretTest ?: existing?.keySecretTest
|
||||
if (effectiveKeyIdTest.isNullOrBlank() || effectiveKeySecretTest.isNullOrBlank()) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "keyIdTest/keySecretTest required when isTest=true")
|
||||
}
|
||||
}
|
||||
val updated = if (existing == null) {
|
||||
RazorpaySettings(
|
||||
property = property,
|
||||
keyId = keyId!!,
|
||||
keySecret = keySecret!!,
|
||||
webhookSecret = webhookSecret,
|
||||
keyIdTest = keyIdTest,
|
||||
keySecretTest = keySecretTest,
|
||||
webhookSecretTest = webhookSecretTest,
|
||||
isTest = isTest ?: false,
|
||||
updatedAt = OffsetDateTime.now()
|
||||
)
|
||||
@@ -90,6 +111,9 @@ class RazorpaySettingsController(
|
||||
if (keyId != null) existing.keyId = keyId
|
||||
if (keySecret != null) existing.keySecret = keySecret
|
||||
if (webhookSecret != null) existing.webhookSecret = webhookSecret
|
||||
if (keyIdTest != null) existing.keyIdTest = keyIdTest
|
||||
if (keySecretTest != null) existing.keySecretTest = keySecretTest
|
||||
if (webhookSecretTest != null) existing.webhookSecretTest = webhookSecretTest
|
||||
isTest?.let { existing.isTest = it }
|
||||
existing.updatedAt = OffsetDateTime.now()
|
||||
existing
|
||||
@@ -105,6 +129,9 @@ private fun RazorpaySettings.toResponse(): RazorpaySettingsResponse {
|
||||
isTest = isTest,
|
||||
hasKeyId = keyId.isNotBlank(),
|
||||
hasKeySecret = keySecret.isNotBlank(),
|
||||
hasWebhookSecret = !webhookSecret.isNullOrBlank()
|
||||
hasWebhookSecret = !webhookSecret.isNullOrBlank(),
|
||||
hasKeyIdTest = !keyIdTest.isNullOrBlank(),
|
||||
hasKeySecretTest = !keySecretTest.isNullOrBlank(),
|
||||
hasWebhookSecretTest = !webhookSecretTest.isNullOrBlank()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class RazorpayWebhookCapture(
|
||||
if (body.isNullOrBlank()) return
|
||||
val signature = request.getHeader("X-Razorpay-Signature")
|
||||
?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing signature")
|
||||
val secret = settings.webhookSecret
|
||||
val secret = settings.resolveWebhookSecret()
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Webhook secret not configured")
|
||||
if (!verifySignature(body, secret, signature)) {
|
||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid signature")
|
||||
|
||||
@@ -6,6 +6,9 @@ data class RazorpaySettingsUpsertRequest(
|
||||
val keyId: String? = null,
|
||||
val keySecret: String? = null,
|
||||
val webhookSecret: String? = null,
|
||||
val keyIdTest: String? = null,
|
||||
val keySecretTest: String? = null,
|
||||
val webhookSecretTest: String? = null,
|
||||
val isTest: Boolean? = null,
|
||||
// Backward-compatible aliases (older clients sending PayU-shaped payloads)
|
||||
val merchantKey: String? = null,
|
||||
@@ -20,7 +23,10 @@ data class RazorpaySettingsResponse(
|
||||
val isTest: Boolean,
|
||||
val hasKeyId: Boolean,
|
||||
val hasKeySecret: Boolean,
|
||||
val hasWebhookSecret: Boolean
|
||||
val hasWebhookSecret: Boolean,
|
||||
val hasKeyIdTest: Boolean,
|
||||
val hasKeySecretTest: Boolean,
|
||||
val hasWebhookSecretTest: Boolean
|
||||
)
|
||||
|
||||
data class RazorpayQrGenerateRequest(
|
||||
|
||||
@@ -26,9 +26,30 @@ class RazorpaySettings(
|
||||
@Column(name = "webhook_secret", columnDefinition = "text")
|
||||
var webhookSecret: String? = null,
|
||||
|
||||
@Column(name = "key_id_test", columnDefinition = "text")
|
||||
var keyIdTest: String? = null,
|
||||
|
||||
@Column(name = "key_secret_test", columnDefinition = "text")
|
||||
var keySecretTest: String? = null,
|
||||
|
||||
@Column(name = "webhook_secret_test", columnDefinition = "text")
|
||||
var webhookSecretTest: String? = null,
|
||||
|
||||
@Column(name = "is_test", nullable = false)
|
||||
var isTest: Boolean = false,
|
||||
|
||||
@Column(name = "updated_at", nullable = false, columnDefinition = "timestamptz")
|
||||
var updatedAt: OffsetDateTime = OffsetDateTime.now()
|
||||
)
|
||||
) {
|
||||
fun resolveKeyId(): String? {
|
||||
return if (isTest) keyIdTest?.ifBlank { null } else keyId.ifBlank { null }
|
||||
}
|
||||
|
||||
fun resolveKeySecret(): String? {
|
||||
return if (isTest) keySecretTest?.ifBlank { null } else keySecret.ifBlank { null }
|
||||
}
|
||||
|
||||
fun resolveWebhookSecret(): String? {
|
||||
return if (isTest) webhookSecretTest?.ifBlank { null } else webhookSecret?.ifBlank { null }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user