Implemented logic to send transaction to C-EDGE servers and process response.

This commit is contained in:
Md Asif 2024-09-18 10:23:56 +05:30
parent dff61dc294
commit cf623a30e1
18 changed files with 378 additions and 143 deletions

13
.idea/dataSources.xml generated
View File

@ -15,5 +15,18 @@
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@ipkstest" uuid="e42fe80a-de71-4d1d-a67a-1752bc2542f2">
<driver-ref>oracle.19</driver-ref>
<synchronize>true</synchronize>
<auto-commit>false</auto-commit>
<jdbc-driver>oracle.jdbc.OracleDriver</jdbc-driver>
<jdbc-url>jdbc:oracle:thin:@localhost:1521:IPKSDB</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

1
.idea/gradle.xml generated
View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>

View File

@ -15,6 +15,8 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("redis.clients:jedis:5.1.2")
implementation("org.slf4j:slf4j-api:2.0.16")
implementation("ch.qos.logback:logback-classic:1.3.14")
}
tasks.test {

View File

@ -1,24 +1,5 @@
import dao.TellerDao
import dao.TransactionDao
fun main() {
val migratedDCCBCodes = listOf("0012")
val transactionRequestList = TransactionDao().getTransactionRequests()
transactionRequestList
.filter { it.dccbCode in migratedDCCBCodes }
.forEach { request ->
val makerTeller = TellerDao().getTeller(request.dccbCode, request.branchCode) ?:
run {
println("40004: #${request.transactionNumber}")
return@forEach
}
val transactionFactory = TransactionFactory(request, makerTeller)
val transferTransaction = transactionFactory.createTransferTransaction()
val neftTransaction = transactionFactory.createNEFTTransaction()
val transactionPair = Pair(transferTransaction, neftTransaction)
val success = TransactionExecutor().execute(transactionPair)
print(success)
}
NeftRequestProcessor.process("160424100640690")
}

View File

@ -0,0 +1,78 @@
import dao.TellerDao
import dao.TransactionDao
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import redis.clients.jedis.JedisPooled
import response.TransactionSuccessResponse
class NeftRequestProcessor {
companion object {
private val logger: Logger = LoggerFactory.getLogger(NeftRequestProcessor::class.java)
private const val PROCESSED_TRANSACTION_LIST = "ipks:processed_outward_transactions"
private val migratedDCCBCodes = listOf("0012")
fun process(transactionNumber: String): Pair<String, String>? {
val outwardTransaction = TransactionDao().getTransactionRequest(transactionNumber) ?: run {
logger.error("TXN: #{} FAILED REASON: Transaction not found", transactionNumber)
return null
}
logger.info("TXN: #{} FOUND", transactionNumber)
val jedis = JedisPooled("localhost", 6379)
val processedTransactionList = jedis.smembers(PROCESSED_TRANSACTION_LIST)
val dccbCode = outwardTransaction.dccbCode.padStart(4, '0')
val branchCode = outwardTransaction.branchCode.padStart(3, '0')
if (dccbCode !in migratedDCCBCodes) {
logger.error("TXN: #{} FAILED REASON: DCCB Code not migrated", transactionNumber)
return null
}
if (transactionNumber in processedTransactionList) {
logger.error("TXN: #{} FAILED REASON: Transaction already processed", 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()
val (transferResponse, neftResponse) = try {
TransactionExecutor().executePair(transactionPair)
} catch (e: Exception) {
logger.error("TXN: #{} FAILED REASON: {}", transactionNumber, e.message)
return null
}
logger.info(
"TXN: #{} TRF_TXN: {} NEFT_TXN: {}",
transactionNumber,
transferResponse.status,
neftResponse.status
)
jedis.sadd(PROCESSED_TRANSACTION_LIST, outwardTransaction.transactionNumber)
if (transferResponse.status == "SUCCESS" && neftResponse.status == "SUCCESS") {
val transferQueueNumber = (transferResponse as TransactionSuccessResponse).response.queueId
val neftQueueNumber = (neftResponse as TransactionSuccessResponse).response.queueId
try {
logger.debug("TXN: UPDATING RESULTS")
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)
} else {
logger.error("TXN: #{} QUEUE INITIATED BUT FAILED TO UPDATE RESULT", transactionNumber)
return null
}
}
}
}

View File

@ -2,34 +2,35 @@
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import model.NeftTransaction
import model.TransferTransaction
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import model.TransferTransaction
import response.TransactionFailureResponse
import response.TransactionResponse
import response.TransactionSuccessResponse
import java.io.IOException
class TransactionExecutor() {
private val protocol = "http"
private val host = "localhost"
private val port = "3000"
private val port = "8080"
private val rootRoute = "WESTBANGAL/api"
private val remoteUrl = "$protocol://$host:$port/$rootRoute"
fun execute(transactionPair: Pair<TransferTransaction, NeftTransaction>): Boolean {
fun executePair(transactionPair: Pair<TransferTransaction, NeftTransaction>): Pair<TransactionResponse, TransactionResponse> {
val transferTransaction = transactionPair.first
val neftTransaction = transactionPair.second
val transferSuccess = execute(Json.encodeToString(transferTransaction))
if(!transferSuccess) {
return false
}
val neftSuccess = execute(Json.encodeToString(neftTransaction))
return neftSuccess
val transferResponse = execute(Json.encodeToString(transferTransaction))
val neftResponse = execute(Json.encodeToString(neftTransaction))
return Pair(transferResponse, neftResponse)
}
fun execute(postBody: String): Boolean {
private fun execute(postBody: String): TransactionResponse {
val transferRoute = "IPKSNeftRtgsApiTransfer"
val transferURL = "$remoteUrl/$transferRoute"
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
@ -41,15 +42,19 @@ class TransactionExecutor() {
.post(postBody.toRequestBody(jsonMediaType))
.build()
httpClient.newCall(request).execute().use { response ->
val responseBody = httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
return false
throw IOException("Unexpected code $response")
}
response.body!!.string()
}
println("success")
return true
val response = if(responseBody.contains("SUCCESS")) {
Json.decodeFromString<TransactionSuccessResponse>(responseBody)
} else {
Json.decodeFromString<TransactionFailureResponse>(responseBody)
}
return response
}
}

View File

@ -1,9 +1,9 @@
import enums.TransactionType
import model.NeftTransaction
import model.Teller
import model.TransactionRequest
import model.TransferTransaction
import enums.TransactionType
import java.time.format.DateTimeFormatter
class TransactionFactory(private val transactionRequest: TransactionRequest, private val teller: Teller) {
@ -11,9 +11,9 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
private val date = transactionRequest.date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
private val rrn = transactionRequest.date.format(DateTimeFormatter.ofPattern("ddMM")) + transactionRequest.transactionNumber.takeLast(4)
fun createTransferTransaction(): TransferTransaction {
private fun createTransferTransaction(): TransferTransaction {
return TransferTransaction(
bankCode = transactionRequest.dccbCode,
bankCode = transactionRequest.dccbCode.padStart(4, '0'),
branchCode = transactionRequest.branchCode.padStart(3,'0'),
cbsTellerId = teller.tellerId,
cbsTellerUserIdType = teller.userType,
@ -22,7 +22,7 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
priority = "1",
cbsTellerCapability = teller.capability,
txnScreenNo = TransactionType.TRANSFER.code.padStart(6, '0'),
txnAmt = transactionRequest.amount.toString(),
txnAmt = transactionRequest.amount,
txnDate = date,
sourceAcctNo = transactionRequest.pacsCurrentAccountNumber,
destinationAcctNo = transactionRequest.linkedCBSAccountNumber,
@ -36,7 +36,7 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
}
fun createNEFTTransaction(): NeftTransaction {
private fun createNEFTTransaction(): NeftTransaction {
return NeftTransaction(
bankCode = transactionRequest.dccbCode,
branchCode = transactionRequest.branchCode.padStart(3,'0'),
@ -47,7 +47,7 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
priority = "1",
cbsTellerCapability = teller.capability,
txnScreenNo = TransactionType.NEFT.code.padStart(6, '0'),
txnAmt = transactionRequest.amount.toString(),
txnAmt = transactionRequest.amount,
txnDate = date,
sourceAcctNo = transactionRequest.linkedCBSAccountNumber,
destinationAcctNo = transactionRequest.neftBeneficiaryAccountNumber,
@ -60,4 +60,8 @@ class TransactionFactory(private val transactionRequest: TransactionRequest, pri
rrn = rrn + "2"
)
}
fun createTransactionPair(): Pair<TransferTransaction, NeftTransaction> {
return Pair(createTransferTransaction(), createNEFTTransaction())
}
}

View File

@ -1,11 +0,0 @@
import kotlinx.serialization.Serializable
@Serializable
data class TransactionResponse(
val status: String,
val message: String,
val response: ResponseData,
val error: String
)

View File

@ -3,7 +3,7 @@ package dao
import model.Teller
class TellerDao {
companion object {
private val tellerMap = mapOf(
"0016" to mapOf(
"008" to "118"
@ -32,7 +32,6 @@ class TellerDao {
)
)
fun getTeller(dccbCode: String, branchCode: String): Teller? {
val branchList = tellerMap[dccbCode] ?: return null
val tellerId = branchList[branchCode] ?: return null
@ -43,4 +42,5 @@ class TellerDao {
)
return teller
}
}
}

View File

@ -1,56 +1,160 @@
package dao
import model.TransactionRequest
import org.slf4j.LoggerFactory
import java.sql.Date
import java.sql.DriverManager
import java.sql.ResultSet
import java.util.Properties
import java.util.*
class TransactionDao {
private val logger = LoggerFactory.getLogger(TransactionDao::class.java)
private val transactionRequestQuery = """
select txn_no,
trim(src_ac_no) AS src_ac_no,
trim(dest_ac_no) AS dest_ac_no,
SELECT
txn_no,
TRIM(src_ac_no) AS src_ac_no,
TRIM(dest_ac_no) AS dest_ac_no,
ifsc_code,
txn_amt,
txn_date,
t.teller_id,
(case
when t.ifsc_code like 'WBSC%' then
'FAILED'
else
'RECEIVED'
end) AS status,
substr(regexp_replace(regexp_replace(upper(beneficiary_name),'[^A-Z0-9 ]',''),' {2,}',' '),1,35) AS beneficiary_name,
CASE
WHEN t.ifsc_code LIKE 'WBSC%' THEN 'FAILED'
ELSE 'RECEIVED'
END AS status,
SUBSTR(REGEXP_REPLACE(REGEXP_REPLACE(UPPER(beneficiary_name), '[^A-Z0-9 ]', ''), ' {2,}', ' '), 1, 35) AS beneficiary_name,
beneficiary_add,
t.pacs_id,
comm_txn_no,
comm_txn_amt,
dccb_code,
TO_NUMBER(cbs_br_code) AS br_code,
substr(regexp_replace(regexp_replace(upper(remm_name),'[^A-Z0-9 ]',''),' {2,}',' '),1,35) AS remitter_name,
SUBSTR(REGEXP_REPLACE(REGEXP_REPLACE(UPPER(remm_name), '[^A-Z0-9 ]', ''), ' {2,}', ' '),1,35) AS remitter_name,
ipks_accno AS pacs_acc_no,
da.link_accno AS cbs_sb_acc_no,
'pacs_db' AS db_name
from neft_rtgs_txn t
join dep_account da ON t.ipks_accno = da.key_1
where
t.txn_date = (select system_date from system_date)
and t.STATUS = 'A'
and t.bank_channel = 'SCB'
and da.link_accno IS NOT NULL
FROM neft_rtgs_txn t
JOIN dep_account da ON t.ipks_accno = da.key_1
WHERE
t.txn_date = TO_DATE('2024-04-16', 'YYYY-MM-DD')
AND t.STATUS = 'A'
AND t.bank_channel = 'SCB'
AND da.link_accno IS NOT NULL
""".trimIndent()
private val singleTransactionRequestQuery = """
SELECT
txn_no,
TRIM(src_ac_no) AS src_ac_no,
TRIM(dest_ac_no) AS dest_ac_no,
ifsc_code,
txn_amt,
txn_date,
t.teller_id,
CASE
WHEN t.ifsc_code LIKE 'WBSC%' THEN 'FAILED'
ELSE 'RECEIVED'
END AS status,
SUBSTR(REGEXP_REPLACE(REGEXP_REPLACE(UPPER(beneficiary_name), '[^A-Z0-9 ]', ''), ' {2,}', ' '), 1, 35) AS beneficiary_name,
beneficiary_add,
t.pacs_id,
comm_txn_no,
comm_txn_amt,
dccb_code,
TO_NUMBER(cbs_br_code) AS br_code,
SUBSTR(REGEXP_REPLACE(REGEXP_REPLACE(UPPER(remm_name), '[^A-Z0-9 ]', ''), ' {2,}', ' '),1,35) AS remitter_name,
ipks_accno AS pacs_acc_no,
da.link_accno AS cbs_sb_acc_no,
'pacs_db' AS db_name
FROM neft_rtgs_txn t
JOIN dep_account da ON t.ipks_accno = da.key_1
WHERE
t.txn_no = ?
""".trimIndent()
private val transactionUpdateQuery = """
INSERT INTO neft_rtgs_txn_queue (
txn_no,
src_ac_no,
dest_ac_no,
ifsc_code,
txn_amt,
txn_date,
teller_id,
status,
beneficiary_name,
beneficiary_add,
pacs_id,
comm_txn_no,
comm_txn_amt,
dccb_code,
br_code,
remitter_name,
cbs_queue_no,
pacs_acc_no,
cbs_queue_no2
) VALUES (
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
)
""".trimIndent()
fun getTransactionRequest(transactionNumber: String): TransactionRequest? {
val dbUrl = getDatabaseUrl()
val (dbUser, dbPassword) = getUserCredentials()
return DriverManager.getConnection(dbUrl, dbUser, dbPassword).use { connection ->
connection.prepareStatement(singleTransactionRequestQuery).apply {
setString(1, transactionNumber)
}.executeQuery().use {
mapToObject(it).firstOrNull()
}
}
}
fun updateSuccessTransaction(request: TransactionRequest, transferQueueNumber: String, neftQueueNumber: String) {
val dbUrl = getDatabaseUrl()
val (dbUser, dbPassword) = getUserCredentials()
try {
DriverManager.getConnection(dbUrl, dbUser, dbPassword).use { connection ->
logger.debug("CONNECTION ESTABLISHED")
connection.prepareStatement(transactionUpdateQuery).also {
logger.debug("PREPARED STATEMENT CREATED")
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 {
logger.debug("EXECUTING UPDATE")
it.executeUpdate()
logger.debug("UPDATE EXECUTED")
}
}
}catch (e: Exception){
e.printStackTrace()
}
}
fun getTransactionRequests(): List<TransactionRequest> {
val prop = loadProp()
val dbHost = getProp(prop, "DB_HOST")
val dbPort = getProp(prop, "DB_PORT")
val dbName = getProp(prop, "DB_NAME")
val dbUrl = "jdbc:oracle:thin:@$dbHost:$dbPort:$dbName"
val dbUser = getProp(prop, "DB_USER")
val dbPassword = getProp(prop, "DB_PASSWORD")
val transactionList: List<TransactionRequest>
val dbUrl = getDatabaseUrl()
val (dbUser, dbPassword) = getUserCredentials()
DriverManager.getConnection(dbUrl, dbUser, dbPassword).use { connection ->
connection.prepareStatement(transactionRequestQuery).executeQuery().use {
@ -61,6 +165,25 @@ class TransactionDao {
return transactionList
}
private fun getDatabaseUrl(): String {
val prop = loadProp()
val dbHost = getProp(prop, "DB_HOST")
val dbPort = getProp(prop, "DB_PORT")
val dbName = getProp(prop, "DB_NAME")
return "jdbc:oracle:thin:@$dbHost:$dbPort:$dbName"
}
private fun getUserCredentials(): Pair<String, String> {
val prop = loadProp()
val dbUser = getProp(prop, "DB_USER")
val dbPassword = getProp(prop, "DB_PASSWORD")
return Pair(dbUser, dbPassword)
}
private fun loadProp(): Properties {
val props = javaClass.classLoader.getResourceAsStream("application.properties").use {
@ -81,15 +204,17 @@ class TransactionDao {
pacsCurrentAccountNumber = rs.getString("src_ac_no"),
neftBeneficiaryAccountNumber = rs.getString("dest_ac_no"),
ifscCode = rs.getString("ifsc_code"),
amount = rs.getString("txn_amt").toFloat(),
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"),
dccbCode = rs.getString("dccb_code").padStart(4, '0'),
branchCode = rs.getString("br_Code").padStart(3, '0'),
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"),
@ -98,4 +223,6 @@ class TransactionDao {
}
return list
}
}

View File

@ -1,7 +1,7 @@
package model
import kotlinx.serialization.Serializable
import org.ipks.enums.TransactionType
import kotlinx.serialization.Transient
import org.ipks.model.Transaction
@Serializable
@ -25,5 +25,6 @@ class NeftTransaction(
override val apiType: String,
override val remitterName: String,
val ifscCode: String,
override val rrn: String
override val rrn: String,
@Transient override var queueNo: String = ""
) : Transaction

View File

@ -20,4 +20,5 @@ interface Transaction {
val apiType: String
val remitterName: String
val rrn: String
val queueNo: String?
}

View File

@ -1,8 +1,5 @@
package model
import kotlinx.serialization.Serializable
import org.ipks.enums.TransactionType
import org.ipks.model.Transaction
import java.time.LocalDate
@ -11,13 +8,15 @@ data class TransactionRequest(
val pacsCurrentAccountNumber: String,
val neftBeneficiaryAccountNumber: String,
val ifscCode: String,
val amount: Float,
val amount: String,
val date: LocalDate,
val tellerId: String,
val status: String,
val beneficiaryName: String,
val beneficiaryAddress: String?,
val pacsId: String,
val commissionTransactionNumber: String?,
val commissionAmount: String?,
val dccbCode: String,
val branchCode: String,
val remitterName: String,

View File

@ -1,6 +1,7 @@
package model
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import org.ipks.model.Transaction
@Serializable
@ -23,5 +24,6 @@ class TransferTransaction(
override val sourceStat: String,
override val apiType: String,
override val remitterName: String,
override val rrn: String
override val rrn: String,
@Transient override var queueNo: String = ""
) : Transaction

View File

@ -1,3 +1,5 @@
package response
import kotlinx.serialization.Serializable
@ -5,10 +7,10 @@ import kotlinx.serialization.Serializable
data class ResponseData (
val transactionDate: Int,
val sourceStat: String,
val journalId: String,
val journalId: Int,
val queueId: String,
val error: String,
val error: Int,
val apiType: String,
val errorMsg: String,
val txnScreenNo: String
val txnScreenNo: Int
)

View File

@ -0,0 +1,12 @@
package response
import kotlinx.serialization.Serializable
@Serializable
data class TransactionFailureResponse(
override val status: String,
override val message: String,
val response: String,
override val error: Int
): TransactionResponse

View File

@ -0,0 +1,7 @@
package response
interface TransactionResponse {
val status: String
val message: String
val error: Int
}

View File

@ -0,0 +1,11 @@
package response
import kotlinx.serialization.Serializable
@Serializable
data class TransactionSuccessResponse(
override val status: String,
override val message: String,
val response: ResponseData,
override val error: Int
): TransactionResponse