Refactored code be more readable

This commit is contained in:
Md Asif 2024-09-28 04:33:35 +05:30
parent c62c233778
commit 20d91d5c47
15 changed files with 263 additions and 221 deletions

View File

@ -2,6 +2,7 @@
<configuration default="false" name="ApplicationKt" type="KtorApplicationConfigurationType" factoryName="Ktor" nameIsGenerated="true">
<envs>
<env name="KTOR_ENV" value="dev" />
<env name="DB_PASSWORD" value="pacs_db" />
</envs>
<option name="MAIN_CLASS_NAME" value="net.ipksindia.ApplicationKt" />
<module name="neft-server.main" />

View File

@ -0,0 +1,3 @@
package net.ipksindia
class ItemNotFoundException(type: String, name: String) : Exception("Not Found $type: $name")

View File

@ -1,90 +1,115 @@
package net.ipksindia
import dao.TellerDao
import net.ipksindia.dao.TellerDao
import model.TransactionRequest
import net.ipksindia.config.AppConfig
import net.ipksindia.dao.TransactionDao
import net.ipksindia.model.NeftTransaction
import net.ipksindia.model.OutwardNeftResponse
import net.ipksindia.model.TransferTransaction
import org.ipks.model.Transaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import response.TransactionFailureResponse
import response.TransactionResponse
import response.TransactionSuccessResponse
class NeftRequestProcessor {
companion object {
private val logger: Logger = LoggerFactory.getLogger(NeftRequestProcessor::class.java)
private val migratedDCCBCodes = listOf("0003","0015")
val bankDccbToSftpMap = mutableMapOf<String, String>(
"0015" to "0005", //Tamluk
"0003" to "0021", //Balageria
// "0016" to "0001", //WBSCB
)
private val migratedDCCBCodes = AppConfig.bankCodes
fun process(transactionNumber: String): Pair<String, String>? {
/**
* Process the transaction request based on the transaction number.
* @param transactionNumber The transaction number to be processed.
* @return A pair of transferQueueNumber and neftQueueNumber if successful, null otherwise.
*/
fun process(transactionNumber: String): OutwardNeftResponse {
return try {
val transactionRequest = fetchTransactionRequest(transactionNumber)
val dccbCode = transactionRequest.dccbCode.padStart(4, '0')
val branchCode = transactionRequest.branchCode.padStart(5, '0')
val outwardTransaction = TransactionDao().getTransactionRequest(transactionNumber) ?: run {
logger.error("TXN: #{} FAILED REASON: Transaction not found", transactionNumber)
return null
}
logger.info("TXN: #{} FOUND", transactionNumber)
if (!isDCCBCodeMigrated(dccbCode)) {
logDCCBCodeNotMigrated(transactionNumber)
return OutwardNeftResponse(0, "dccb code not migrated", null, null)
}
val dccbCode = outwardTransaction.dccbCode.padStart(4, '0')
val branchCode = outwardTransaction.branchCode.padStart(5, '0')
val teller = TellerDao().getTeller(dccbCode, branchCode)
val transactionPair = TransactionFactory(transactionRequest, teller)
.createTransactionPair()
if(dccbCode !in migratedDCCBCodes) {
logger.error("TXN: #{} FAILED REASON: DCCB Code not migrated", transactionNumber)
return null
}
val makerTeller = TellerDao.getTeller(dccbCode, branchCode) ?: run {
logger.error("TXN: #{} FAILED REASON: Teller not found", transactionNumber)
return null
}
val transactionPair = TransactionFactory(outwardTransaction, makerTeller).createTransactionPair()
logger.info("TXN: #{} TRANSFER RRN: {} NEFT RRN: {}", transactionNumber, transactionPair.first.rrn, transactionPair.second.rrn)
val (transferResponse, neftResponse) = try {
TransactionExecutor().executePair(transactionPair)
executeAndProcessTransaction(transactionNumber, transactionRequest, transactionPair)
} catch (e: Exception) {
logger.error("TXN: #{} FAILED REASON: {}", transactionNumber, e.message)
return null
logger.error("TXN: #{} FAILED REASON: {}", transactionNumber, e.toString())
OutwardNeftResponse(0, e.message ?: "", null, null)
}
}
logger.info(
"TXN: #{} TRF_TXN: {} NEFT_TXN: {}",
transactionNumber,
transferResponse.status,
neftResponse.status
)
/**
* Fetch the transaction request using the transaction number.
*/
private fun fetchTransactionRequest(transactionNumber: String): TransactionRequest {
val transactionRequest = TransactionDao().getTransactionRequest(transactionNumber)
logger.info("TXN: #{} FOUND", transactionNumber)
return transactionRequest
}
if (transferResponse.status == "SUCCESS" && neftResponse.status == "SUCCESS") {
/**
* Check if the DCCB code is not migrated.
*/
private fun isDCCBCodeMigrated(dccbCode: String)= dccbCode in migratedDCCBCodes
/**
* Log an error if the DCCB code is not migrated.
*/
private fun logDCCBCodeNotMigrated(transactionNumber: String) {
logger.error("TXN: #{} FAILED REASON: DCCB Code not migrated", transactionNumber)
}
/**
* Execute the transaction pair and process the results.
*/
private fun executeAndProcessTransaction(
transactionNumber: String,
transactionRequest: TransactionRequest,
transactionPair: Pair<TransferTransaction, NeftTransaction>
): OutwardNeftResponse {
val (transferResponse, neftResponse) = TransactionExecutor().executePair(transactionPair)
return if (isSuccess(transferResponse, neftResponse)) {
val transferQueueNumber = (transferResponse as TransactionSuccessResponse).queueNumber
val neftQueueNumber = (neftResponse as TransactionSuccessResponse).queueNumber
try {
TransactionDao().updateSuccessTransaction(outwardTransaction, transferQueueNumber, neftQueueNumber)
logger.info("TXN: #{} UPDATED RESULTS SUCCESSFULLY", transactionNumber)
} catch (e: Exception) {
logger.error(
"TXN: #{} QUEUE INITIATED BUT FAILED TO UPDATE RESULT: {}",
transactionNumber,
e.message
)
}
return Pair(transferQueueNumber, neftQueueNumber)
TransactionDao().updateSuccessTransaction(transactionRequest, transferQueueNumber, neftQueueNumber)
logger.info("TXN: #{} UPDATED RESULTS SUCCESSFULLY", transactionNumber)
OutwardNeftResponse(1, "transaction successful", transferQueueNumber, neftQueueNumber)
} else {
val transferErrorMsg = (transferResponse as TransactionFailureResponse).errorMsg
val neftErrorMsg = (neftResponse as TransactionFailureResponse).errorMsg
logger.error(
"TXN: #{} TRANSFER TXN FAILED DUE TO: {}",
transactionNumber,
transferErrorMsg
)
logger.error(
"TXN: #{} NEFT TXN FAILED DUE TO: {}",
transactionNumber,
neftErrorMsg
)
logTransactionFailure(transactionNumber, transferResponse, neftResponse)
OutwardNeftResponse(0, "transaction failed", null, null)
}
return null
}
/**
* Check if both transfer and NEFT responses are successful.
*/
private fun isSuccess(transferResponse: TransactionResponse, neftResponse: TransactionResponse) = transferResponse is TransactionSuccessResponse && neftResponse is TransactionSuccessResponse
/**
* Log errors if the transaction fails.
*/
private fun logTransactionFailure(
transactionNumber: String,
transferResponse: Any,
neftResponse: Any
) {
val transferErrorMsg = (transferResponse as? TransactionFailureResponse)?.errorMsg ?: "Unknown Error"
val neftErrorMsg = (neftResponse as? TransactionFailureResponse)?.errorMsg ?: "Unknown Error"
logger.error("TXN: #{} TRANSFER TXN FAILED DUE TO: {}", transactionNumber, transferErrorMsg)
logger.error("TXN: #{} NEFT TXN FAILED DUE TO: {}", transactionNumber, neftErrorMsg)
}
}
}

View File

@ -1 +0,0 @@
class TellerNotFoundException(s: String) : Exception(s)

View File

@ -8,8 +8,8 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject
import org.slf4j.LoggerFactory
import response.TransactionFailureResponse
import response.TransactionResponse
import response.TransactionSuccessResponse
@ -17,7 +17,7 @@ import java.io.IOException
class TransactionExecutor() {
private val logger = LoggerFactory.getLogger(TransactionExecutor::class.java)
private val transactionUrl = AppConfig.remoteServerConfig.transactionUrl
fun executePair(transactionPair: Pair<TransferTransaction, NeftTransaction>): Pair<TransactionResponse, TransactionResponse> {
@ -25,13 +25,16 @@ class TransactionExecutor() {
val transferTransaction = transactionPair.first
val neftTransaction = transactionPair.second
val transferResponse = execute(Json.encodeToString(transferTransaction))
val neftResponse = execute(Json.encodeToString(neftTransaction))
val transferResponseString = execute(Json.encodeToString(transferTransaction))
logger.debug("TRANSFER-RRN: {} - CBS Response: {}", transferTransaction.rrn, transferResponseString)
val transferResponse = processResponse(transferResponseString)
val neftResponseString = execute(Json.encodeToString(neftTransaction))
logger.debug("NEFT-RRN: {}, CBS Response: {}", neftTransaction.rrn, neftResponseString)
val neftResponse = processResponse(neftResponseString)
return Pair(transferResponse, neftResponse)
}
private fun execute(postBody: String): TransactionResponse {
// println(postBody)
private fun execute(postBody: String): String {
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
val httpClient = OkHttpClient
@ -45,26 +48,29 @@ class TransactionExecutor() {
.post(postBody.toRequestBody(jsonMediaType))
.build()
val responseBody = httpClient.newCall(request).execute().use { response ->
return httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("Unexpected response: ${response.body}")
}
response.body?.string() ?: throw IOException("no response body")
}
val responseObj = JSONObject(responseBody)
val status = try {responseObj.getString("status") } catch(_: JSONException) { "" }
val message = try { responseObj.getString("message") }catch(_: JSONException) { "" }
val error = try { responseObj.getInt("error") } catch(_: JSONException) { 1 }
if(responseBody.contains("SUCCESS")) {
val queueNo = try { responseObj.getJSONObject("response").getString("QueueId") } catch(_: JSONException) { "" }
return TransactionSuccessResponse(status, message, queueNo, error)
} else {
val errorMsg = try { responseObj.getJSONObject("response").getString("errorMsg") } catch(_: JSONException) { responseBody }
return TransactionFailureResponse(status, message, errorMsg, error)
}
}
private fun processResponse(responseBody: String): TransactionResponse {
val responseObj = JSONObject(responseBody)
val status = responseObj.getString("status")
val message = responseObj.getString("message")
val error = responseObj.getInt("error")
if(responseBody.contains("SUCCESS")) {
val queueNo = responseObj.getJSONObject("response").getString("QueueId")
return TransactionSuccessResponse(status, message, queueNo, error)
} else {
val errorMsg = responseObj.getJSONObject("response").getString("errorMsg")
return TransactionFailureResponse(status, message, errorMsg, error)
}
}
}

View File

@ -5,19 +5,18 @@ import net.ipksindia.model.NeftTransaction
import model.Teller
import model.TransactionRequest
import net.ipksindia.model.TransferTransaction
import net.ipksindia.NeftRequestProcessor.Companion.bankDccbToSftpMap
import java.time.format.DateTimeFormatter
class TransactionFactory(private val transactionRequest: TransactionRequest, private val teller: Teller) {
private val bankDccbToSftpMap = mutableMapOf("0015" to "0005", "0003" to "0021")
private val date = transactionRequest.date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
private val rrn = transactionRequest.date.format(DateTimeFormatter.ofPattern("ddMM")) + transactionRequest.transactionNumber.takeLast(4)
private fun createTransferTransaction(): TransferTransaction {
val bankCode = bankDccbToSftpMap[transactionRequest.dccbCode.padStart(4, '0')] ?: throw ItemNotFoundException("SFTP code for", transactionRequest.dccbCode)
return TransferTransaction(
bankCode = bankDccbToSftpMap[transactionRequest.dccbCode.padStart(4, '0')]!!,
bankCode = bankCode,
branchCode = transactionRequest.branchCode.padStart(3,'0'),
// branchCode = "99999", for UAT
cbsTellerId = teller.tellerId,
cbsTellerUserType = teller.userType,
queIdType = "5",
@ -42,10 +41,10 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
}
private fun createNEFTTransaction(): NeftTransaction {
val bankCode = bankDccbToSftpMap[transactionRequest.dccbCode.padStart(4, '0')] ?: throw ItemNotFoundException("SFTP code for", transactionRequest.dccbCode)
return NeftTransaction(
bankCode = bankDccbToSftpMap[transactionRequest.dccbCode.padStart(4, '0')]!!,
bankCode = bankCode,
branchCode = transactionRequest.branchCode.padStart(3,'0'),
// branchCode = "99999", for UAT
cbsTellerId = teller.tellerId,
cbsTellerUserType = teller.userType,
queIdType = "5",

View File

@ -25,7 +25,8 @@ object AppConfig {
"bank.server.host",
"bank.server.port",
"bank.server.rootRoute",
"bank.server.transactionRoute"
"bank.server.transactionRoute",
"bank.codes"
)
val missingKeys = requiredKeys.filterNot { config.hasPath(it) }
@ -65,6 +66,8 @@ object AppConfig {
config.getString("bank.server.transactionRoute")
)
}
val bankCodes: MutableList<String> by lazy { config.getStringList("bank.codes")}
}
data class RemoteServerConfig(

View File

@ -4,6 +4,7 @@ import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import net.ipksindia.config.AppConfig
import java.sql.Connection
import java.sql.SQLException
class DatabaseFactory private constructor() {
private val dataSource: HikariDataSource

View File

@ -1,60 +1,58 @@
package dao
package net.ipksindia.dao
import model.Teller
import net.ipksindia.ItemNotFoundException
class TellerDao {
companion object {
private val tellerMap = mapOf(
"0003" to mapOf(
"00012" to "312",
"00017" to "317",
"00013" to "313",
"00014" to "314",
"00015" to "315",
"00016" to "316",
"00019" to "319",
"00020" to "320",
"00026" to "11126",
"00010" to "310"
),
"0015" to mapOf(
"00006" to "11106",
"00005" to "11105",
"00002" to "11102",
"00004" to "11104",
"00023" to "1234",
"00008" to "11108",
"00017" to "11117",
"00011" to "10111",
"00021" to "1234",
"00001" to "1234",
"00018" to "11118",
"00012" to "11112",
"00019" to "11119",
"00003" to "11103",
"00009" to "11109",
"00015" to "11115",
"00020" to "11120",
"00013" to "11113",
"00014" to "1234",
"00016" to "11116",
"00010" to "11110",
"00007" to "11107",
"00022" to "1234",
"00026" to "11126"
),
private val tellerMap = mapOf(
"0003" to mapOf(
"00012" to "312",
"00017" to "317",
"00013" to "313",
"00014" to "314",
"00015" to "315",
"00016" to "316",
"00019" to "319",
"00020" to "320",
"00026" to "11126",
"00010" to "310"
),
"0015" to mapOf(
"00006" to "11106",
"00005" to "11105",
"00002" to "11102",
"00004" to "11104",
"00023" to "1234",
"00008" to "11108",
"00017" to "11117",
"00011" to "10111",
"00021" to "1234",
"00001" to "1234",
"00018" to "11118",
"00012" to "11112",
"00019" to "11119",
"00003" to "11103",
"00009" to "11109",
"00015" to "11115",
"00020" to "11120",
"00013" to "11113",
"00014" to "1234",
"00016" to "11116",
"00010" to "11110",
"00007" to "11107",
"00022" to "1234",
"00026" to "11126"
),
)
fun getTeller(dccbCode: String, branchCode: String): Teller {
val branchList = tellerMap[dccbCode] ?: throw ItemNotFoundException("Branch Code", branchCode)
val tellerId = branchList[branchCode] ?: throw ItemNotFoundException("DCCB Code", branchCode)
val teller = Teller(
tellerId, dccbCode, branchCode
)
fun getTeller(dccbCode: String, branchCode: String): Teller? {
val branchList = tellerMap[dccbCode] ?: return null
val tellerId = branchList[branchCode] ?: return null
val teller = Teller(
tellerId,
dccbCode,
branchCode
)
return teller
}
return teller
}
}

View File

@ -1,10 +1,12 @@
package net.ipksindia.dao
import model.TransactionRequest
import net.ipksindia.ItemNotFoundException
import java.sql.Date
import java.sql.ResultSet
import java.sql.SQLException
class TransactionDao() {
class TransactionDao {
private val singleTransactionRequestQuery = """
SELECT
@ -63,74 +65,73 @@ class TransactionDao() {
""".trimIndent()
fun updateSuccessTransaction(request: TransactionRequest, transferQueueNumber: String, neftQueueNumber: String) {
DatabaseFactory.instance.getConnection()
.use { connection ->
connection
.prepareStatement(transactionUpdateQuery)
.also {
it.setString(1, request.transactionNumber)
it.setString(2, request.pacsCurrentAccountNumber)
it.setString(3, request.neftBeneficiaryAccountNumber)
it.setString(4, request.ifscCode)
it.setString(5, request.amount)
it.setDate(6, Date.valueOf(request.date))
it.setString(7, request.tellerId)
it.setString(8, "PROCESSED")
it.setString(9, request.beneficiaryName)
it.setString(10, request.beneficiaryAddress)
it.setString(11, request.pacsId)
it.setString(12, request.commissionTransactionNumber)
it.setString(13, request.commissionAmount)
it.setString(14, request.dccbCode)
it.setString(15, request.branchCode)
it.setString(16, request.remitterName)
it.setString(17, transferQueueNumber)
it.setString(18, request.pacsAccountNumber)
it.setString(19, neftQueueNumber)
}
.use { it.executeUpdate() }
try {
DatabaseFactory.instance.getConnection().use { connection ->
connection.prepareStatement(transactionUpdateQuery).also {
it.setString(1, request.transactionNumber)
it.setString(2, request.pacsCurrentAccountNumber)
it.setString(3, request.neftBeneficiaryAccountNumber)
it.setString(4, request.ifscCode)
it.setString(5, request.amount)
it.setDate(6, Date.valueOf(request.date))
it.setString(7, request.tellerId)
it.setString(8, "PROCESSED")
it.setString(9, request.beneficiaryName)
it.setString(10, request.beneficiaryAddress)
it.setString(11, request.pacsId)
it.setString(12, request.commissionTransactionNumber)
it.setString(13, request.commissionAmount)
it.setString(14, request.dccbCode)
it.setString(15, request.branchCode)
it.setString(16, request.remitterName)
it.setString(17, transferQueueNumber)
it.setString(18, request.pacsAccountNumber)
it.setString(19, neftQueueNumber)
}.use { it.executeUpdate() }
}
}
fun getTransactionRequest(transactionNumber: String): TransactionRequest? {
return DatabaseFactory.instance
.getConnection()
.use { connection ->
connection
.prepareStatement(singleTransactionRequestQuery)
.apply { setString(1, transactionNumber) }
.executeQuery()
.use { mapToObject(it).firstOrNull() }
}
}
private fun mapToObject(rs: ResultSet): List<TransactionRequest> {
val list = mutableListOf<TransactionRequest>()
while (rs.next()) {
val transactionRequest = TransactionRequest(
transactionNumber = rs.getString("txn_no"),
pacsCurrentAccountNumber = rs.getString("src_ac_no"),
neftBeneficiaryAccountNumber = rs.getString("dest_ac_no"),
ifscCode = rs.getString("ifsc_code"),
amount = rs.getString("txn_amt"),
date = rs.getDate("txn_date").toLocalDate(),
tellerId = rs.getString("teller_id"),
status = rs.getString("status"),
beneficiaryName = rs.getString("beneficiary_name"),
beneficiaryAddress = rs.getString("beneficiary_add"),
pacsId = rs.getString("pacs_id"),
commissionTransactionNumber = rs.getString("comm_txn_no"),
commissionAmount = rs.getString("comm_txn_amt"),
dccbCode = rs.getString("dccb_code"),
branchCode = rs.getString("br_Code"),
remitterName = rs.getString("remitter_name"),
pacsAccountNumber = rs.getString("pacs_acc_no"),
linkedCBSAccountNumber = rs.getString("cbs_sb_acc_no"),
)
list.add(transactionRequest)
}catch (e: ExceptionInInitializerError) {
throw SQLException("Failed to connect to the database")
}
return list
}
fun getTransactionRequest(transactionNumber: String): TransactionRequest {
return try {
DatabaseFactory.instance.getConnection().use { connection ->
connection.prepareStatement(singleTransactionRequestQuery).apply { setString(1, transactionNumber) }
.executeQuery()
.use { mapToObject(it) ?: throw ItemNotFoundException("Transaction Number", transactionNumber) }
}
} catch (e: ExceptionInInitializerError) {
throw SQLException("Failed to connect to the database")
}
}
}
private fun mapToObject(rs: ResultSet): TransactionRequest? {
if (rs.next()) {
return TransactionRequest(
transactionNumber = rs.getString("txn_no"),
pacsCurrentAccountNumber = rs.getString("src_ac_no"),
neftBeneficiaryAccountNumber = rs.getString("dest_ac_no"),
ifscCode = rs.getString("ifsc_code"),
amount = rs.getString("txn_amt"),
date = rs.getDate("txn_date").toLocalDate(),
tellerId = rs.getString("teller_id"),
status = rs.getString("status"),
beneficiaryName = rs.getString("beneficiary_name"),
beneficiaryAddress = rs.getString("beneficiary_add"),
pacsId = rs.getString("pacs_id"),
commissionTransactionNumber = rs.getString("comm_txn_no"),
commissionAmount = rs.getString("comm_txn_amt"),
dccbCode = rs.getString("dccb_code"),
branchCode = rs.getString("br_Code"),
remitterName = rs.getString("remitter_name"),
pacsAccountNumber = rs.getString("pacs_acc_no"),
linkedCBSAccountNumber = rs.getString("cbs_sb_acc_no"),
)
}
return null
}

View File

@ -0,0 +1,11 @@
package net.ipksindia.model
import kotlinx.serialization.Serializable
@Serializable
data class OutwardNeftResponse(
val status: Int,
val message: String,
val transferQueueNumber: String?,
val neftQueueNumber: String?
)

View File

@ -13,8 +13,8 @@ fun Application.configureRouting() {
post {
val neftRequest = call.receive<OutwardNeftRequest>()
val transactionNumber = neftRequest.transactionNumber
val response = NeftRequestProcessor.process(transactionNumber) ?: Pair("500", "Error doing outward neft" )
call.respond("${response.first}\n${response.second}")
val response = NeftRequestProcessor.process(transactionNumber)
call.respond(response)
}
}

View File

@ -10,9 +10,4 @@ fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
}
}

View File

@ -14,4 +14,5 @@ bank {
rootRoute = "IPKS_Queue_Generation"
transactionRoute = "IpksApi"
}
codes = ["0003", "0015"]
}

View File

@ -23,8 +23,7 @@
</encoder>
</appender>
<!-- <logger name="org.eclipse.jetty" level="WARN"/>-->
<!-- <logger name="io.netty" level="WARN"/>-->
<logger name="io.netty" level="WARN"/>
<logger name="net.ipksindia" level="DEBUG" additivity="false">
<appender-ref ref="FILE-ROLLING"/>
<appender-ref ref="STDOUT" />