fints4k/fints4javaLib/src/test/kotlin/net/dankito/fints/bankdetails/BanksFinTsDetailsRetriever.kt

258 lines
No EOL
12 KiB
Kotlin

package net.dankito.fints.bankdetails
import net.dankito.fints.FinTsClient
import net.dankito.fints.banks.InMemoryBankFinder
import net.dankito.fints.callback.NoOpFinTsClientCallback
import net.dankito.fints.messages.MessageBuilder
import net.dankito.fints.messages.Separators
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.model.*
import net.dankito.fints.model.mapper.BankDataMapper
import net.dankito.fints.response.Response
import net.dankito.fints.response.ResponseParser
import net.dankito.fints.response.segments.SepaAccountInfoParameters
import net.dankito.fints.response.segments.TanInfo
import net.dankito.fints.response.segments.TanProcedureParameters
import net.dankito.fints.util.Java8Base64Service
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVPrinter
import org.junit.Ignore
import org.junit.Test
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
@Ignore // not a real test, run manually to retrieve FinTS information from all banks
class BanksFinTsDetailsRetriever {
companion object {
private val OutputFolderDateFormat = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss")
private val log = LoggerFactory.getLogger(BanksFinTsDetailsRetriever::class.java)
}
private val bankFinder = InMemoryBankFinder()
private val bankDataMapper = BankDataMapper()
private val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "0.1") // TODO: get version dynamically
private val messageBuilder = MessageBuilder()
private val finTsClient = object : FinTsClient(NoOpFinTsClientCallback(), Java8Base64Service()) {
fun getAndHandleResponseForMessagePublic(requestBody: String, bank: BankData): Response {
return getAndHandleResponseForMessage(requestBody, bank)
}
fun updateBankDataPublic(bank: BankData, response: Response) {
super.updateBankData(bank, response)
}
fun mapToTanProcedureTypePublic(parameters: TanProcedureParameters): TanProcedureType? {
return super.mapToTanProcedureType(parameters)
}
}
private val requestNotSuccessful = mutableListOf<BankInfo>()
private val tanProcedureParameter = mutableMapOf<String, MutableSet<TanProcedureParameters>>()
private val tanProcedureTypes = mutableMapOf<TanProcedureType?, MutableSet<TanProcedureParameters>>()
private val doesNotSupportHKTAN6 = mutableListOf<BankInfo>()
private val doesNotSupportHKSAL5or7 = mutableListOf<BankInfo>()
private val doesNotSupportHKKAZ5to7 = mutableListOf<BankInfo>()
private val doesNotSupportHKCCS1 = mutableListOf<BankInfo>()
private val supportsHhd13 = mutableListOf<BankInfo>()
private val supportsHhd14 = mutableListOf<BankInfo>()
private val doesNotSupportPain_001_001_or_003_03 = mutableListOf<BankInfo>()
@Test
fun retrieveAllBanksFinTsDetails() {
val allBanks = bankFinder.getBankList()
val banksSupportingFinTs = allBanks.filter { it.supportsFinTs3_0 }
val outputFolder = File("bankData", OutputFolderDateFormat.format(Date()))
val responsesFolder = File(outputFolder, "responses")
responsesFolder.mkdirs()
val csvFile = FileWriter(File(outputFolder, "bank_details.csv"))
val csvPrinter = CSVPrinter(csvFile, CSVFormat.DEFAULT.withHeader(
"BLZ", "Name", "Ort", "BPD", "Tanverfahren", "Technische Tanverfahrennamen", "HHD 1.3?", "HHD 1.4?",
"HKTAN 6?", "HKTAN", "HKSAL 5?", "HKSAL", "HKKAZ 5-7?", "HKKAZ", "HKCAZ", "HKCCS 1?", "HKCCS",
"pain.001.001.03?", "SEPA Formate", "Sprachen", "Untersstützte Geschäftsvorfälle"
))
val uniqueBanks = banksSupportingFinTs.associateBy { "${it.bankCode}_${it.name}" }
var bankIndex = 0
uniqueBanks.forEach { bankName, bankInfo ->
log.info("[${++bankIndex}] Getting details for $bankName ...")
getAndSaveBankDetails(bankInfo, responsesFolder, csvPrinter)
}
printStatistics()
csvPrinter.close()
csvFile.close()
}
private fun getAnonymousBankInfo(bank: BankData): Response {
val dialogData = DialogData()
val requestBody = messageBuilder.createAnonymousDialogInitMessage(bank, product, dialogData)
val anonymousBankInfoResponse =
finTsClient.getAndHandleResponseForMessagePublic(requestBody, bank)
finTsClient.updateBankDataPublic(bank, anonymousBankInfoResponse)
return anonymousBankInfoResponse
}
private fun getAndSaveBankDetails(bankInfo: BankInfo, responsesFolder: File, csvPrinter: CSVPrinter) {
val bank = bankDataMapper.mapFromBankInfo(bankInfo)
val anonymousBankInfoResponse = getAnonymousBankInfo(bank)
File(responsesFolder, "${bankInfo.bankCode}_${bankInfo.name.replace('/', '-')}").writeText(
anonymousBankInfoResponse.receivedSegments.joinToString(System.lineSeparator()) { it.segmentString + Separators.SegmentSeparator })
if (anonymousBankInfoResponse.successful == false) {
requestNotSuccessful.add(bankInfo)
log.warn("Did not receive response from bank $bankInfo: ${anonymousBankInfoResponse.receivedSegments.joinToString(System.lineSeparator()) { it.segmentString + Separators.SegmentSeparator }}")
return
}
val supportsHKTAN6 = supportsJobInVersion(bank, "HKTAN", 6)
val supportsHKSAL5or7 = supportsJobInVersion(bank, "HKSAL", listOf(5, 7))
val supportsHKKAZ5to7 = supportsJobInVersion(bank, "HKKAZ", listOf(5, 6, 7))
val supportsHKCCS1 = supportsJobInVersion(bank, "HKCCS", 1)
val tanInfo = anonymousBankInfoResponse.receivedSegments.filterIsInstance(TanInfo::class.java)
val tanProcedureParameters = tanInfo.flatMap { it.tanProcedureParameters.procedureParameters }
val supportedTanProcedures = tanProcedureParameters.map { it.technicalTanProcedureIdentification }
val hhd13Supported = supportedTanProcedures.firstOrNull { it.startsWith("hhd1.3", true) } != null
val hhd14Supported = supportedTanProcedures.firstOrNull { it.startsWith("hhd1.4", true) } != null
val supportedHKTANVersions = tanInfo.map { it.segmentVersion }
val supportedHKSALVersions = getSupportedVersions(bank, "HKSAL")
val supportedHKKAZVersions = getSupportedVersions(bank, "HKKAZ")
val supportedHKCAZVersions = getSupportedVersions(bank, "HKCAZ")
val supportedHKCCSVersions = getSupportedVersions(bank, "HKCCS")
val sepaAccountInfoParameters = anonymousBankInfoResponse.receivedSegments.filterIsInstance<SepaAccountInfoParameters>()
val supportedSepaFormats = sepaAccountInfoParameters.flatMap { it.supportedSepaFormats }.map { it.substring(it.indexOf(":xsd:") + ":xsd:".length) }
val supportsPain_001_001_or_003_03 = supportedSepaFormats.firstOrNull { it.contains("pain.001.001.03") or it.contains("pain.001.003.03") } != null
csvPrinter.printRecord(bankInfo.bankCode, bankInfo.name, bankInfo.city,
bank.bpdVersion,
bank.supportedTanProcedures.joinToString(", ") { it.securityFunction.code + ": " + it.displayName + " (" + it.type + ")" },
supportedTanProcedures.joinToString(", "),
hhd13Supported,
hhd14Supported,
supportsHKTAN6,
supportedHKTANVersions.joinToString(", "),
supportsHKSAL5or7,
supportedHKSALVersions.joinToString(", "),
supportsHKKAZ5to7,
supportedHKKAZVersions.joinToString(", "),
supportedHKCAZVersions.joinToString(", "),
supportsHKCCS1,
supportedHKCCSVersions.joinToString(", "),
supportsPain_001_001_or_003_03,
supportedSepaFormats.joinToString(", "),
bank.supportedLanguages.filter { it != Dialogsprache.German }.joinToString(", ") { it.name },
bank.supportedJobs.joinToString(", ") { it.jobName + " " + it.segmentVersion }
)
tanProcedureParameters.forEach { procedureParameter ->
if (tanProcedureParameter.containsKey(procedureParameter.procedureName) == false) {
tanProcedureParameter.put(procedureParameter.procedureName, mutableSetOf(procedureParameter))
}
else {
tanProcedureParameter[procedureParameter.procedureName]?.add(procedureParameter)
}
val tanProcedureType = finTsClient.mapToTanProcedureTypePublic(procedureParameter)
if (tanProcedureTypes.containsKey(tanProcedureType) == false) {
tanProcedureTypes.put(tanProcedureType, mutableSetOf(procedureParameter))
}
else {
tanProcedureTypes[tanProcedureType]?.add(procedureParameter)
}
}
if (supportsHKTAN6 == false) {
doesNotSupportHKTAN6.add(bankInfo)
}
if (supportsHKSAL5or7 == false) {
doesNotSupportHKSAL5or7.add(bankInfo)
}
if (supportsHKKAZ5to7 == false) {
doesNotSupportHKKAZ5to7.add(bankInfo)
}
if (supportsHKCCS1 == false) {
doesNotSupportHKCCS1.add(bankInfo)
}
if (hhd13Supported) {
supportsHhd13.add(bankInfo)
}
if (hhd14Supported) {
supportsHhd14.add(bankInfo)
}
if (supportsPain_001_001_or_003_03 == false) {
doesNotSupportPain_001_001_or_003_03.add(bankInfo)
}
}
private fun getSupportedVersions(bank: BankData, jobName: String): List<Int> {
return bank.supportedJobs.filter { it.jobName == jobName }.map { it.segmentVersion }
}
private fun supportsJobInVersion(bank: BankData, jobName: String, version: Int): Boolean {
return supportsJobInVersion(bank, jobName, listOf(version))
}
private fun supportsJobInVersion(bank: BankData, jobName: String, versions: List<Int>): Boolean {
return bank.supportedJobs.firstOrNull { it.jobName == jobName && versions.contains(it.segmentVersion) } != null
}
private fun printStatistics() {
log.info("Did not receive response from Banks ${printBanks(requestNotSuccessful)}")
log.info("ZkaTanProcedures: ${ResponseParser.ZkaTanProcedures.joinToString()}\n\n")
log.info("Mapped tanProcedureTypes: ${tanProcedureTypes.map { System.lineSeparator() + it.key + ": " + it.value.map { it.procedureName + " " + it.zkaTanProcedure + " " + it.technicalTanProcedureIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") }}\n\n")
log.info("TanProcedureParameters:${tanProcedureParameter.map { System.lineSeparator() + it.key + ": " + it.value.map { it.securityFunction.code + " " + it.zkaTanProcedure + " " + it.technicalTanProcedureIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") } }\n\n")
log.info("Banks supporting HHD 1.3 (${supportsHhd13.size}):${printBanks(supportsHhd13)}")
log.info("Banks supporting HHD 1.4 (${supportsHhd14.size}):${printBanks(supportsHhd14)}")
log.info("Banks not supporting HKTAN 6 ${printBanks(doesNotSupportHKTAN6)}")
log.info("Banks not supporting HKSAL 5 or 7 ${printBanks(doesNotSupportHKSAL5or7)}")
log.info("Banks not supporting HKKAZ 5-7 ${printBanks(doesNotSupportHKKAZ5to7)}")
log.info("Banks not supporting HKCCS 1 ${printBanks(doesNotSupportHKCCS1)}")
log.info("Banks not supporting pain.001.001.03 or pain.001.003.03 ${printBanks(doesNotSupportPain_001_001_or_003_03)}")
}
private fun printBanks(banks: List<BankInfo>): String {
return "(${banks.size}):${ banks.joinToString { System.lineSeparator() + it } }\n\n\n"
}
}