Extracted ModelMapper
This commit is contained in:
parent
b6a0e48fd7
commit
41d02ec343
|
@ -4,6 +4,7 @@ import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
import net.dankito.banking.fints.log.MessageLogCollector
|
import net.dankito.banking.fints.log.MessageLogCollector
|
||||||
import net.dankito.banking.fints.messages.MessageBuilder
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
import net.dankito.banking.fints.model.*
|
import net.dankito.banking.fints.model.*
|
||||||
|
import net.dankito.banking.fints.model.mapper.ModelMapper
|
||||||
import net.dankito.banking.fints.response.ResponseParser
|
import net.dankito.banking.fints.response.ResponseParser
|
||||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||||
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
||||||
|
@ -25,10 +26,11 @@ open class FinTsClientForCustomer(
|
||||||
responseParser: ResponseParser = ResponseParser(),
|
responseParser: ResponseParser = ResponseParser(),
|
||||||
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
||||||
|
modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
||||||
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically){}
|
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically){}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
protected val client = FinTsClient(FinTsJobExecutor(callback, webClient, base64Service, messageBuilder, responseParser, mt940Parser, messageLogCollector, product))
|
protected val client = FinTsClient(FinTsJobExecutor(callback, webClient, base64Service, messageBuilder, responseParser, mt940Parser, messageLogCollector, modelMapper, product))
|
||||||
|
|
||||||
|
|
||||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
||||||
|
|
|
@ -5,14 +5,12 @@ import net.dankito.banking.fints.log.IMessageLogAppender
|
||||||
import net.dankito.banking.fints.log.MessageLogCollector
|
import net.dankito.banking.fints.log.MessageLogCollector
|
||||||
import net.dankito.banking.fints.messages.MessageBuilder
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
import net.dankito.banking.fints.messages.MessageBuilderResult
|
import net.dankito.banking.fints.messages.MessageBuilderResult
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.Dialogsprache
|
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
|
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
|
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.*
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.*
|
||||||
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
|
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
|
||||||
import net.dankito.banking.fints.messages.segmente.id.ISegmentId
|
import net.dankito.banking.fints.messages.segmente.id.ISegmentId
|
||||||
import net.dankito.banking.fints.model.*
|
import net.dankito.banking.fints.model.*
|
||||||
|
import net.dankito.banking.fints.model.mapper.ModelMapper
|
||||||
import net.dankito.banking.fints.response.BankResponse
|
import net.dankito.banking.fints.response.BankResponse
|
||||||
import net.dankito.banking.fints.response.InstituteSegmentId
|
import net.dankito.banking.fints.response.InstituteSegmentId
|
||||||
import net.dankito.banking.fints.response.ResponseParser
|
import net.dankito.banking.fints.response.ResponseParser
|
||||||
|
@ -50,6 +48,7 @@ open class FinTsJobExecutor(
|
||||||
protected open val responseParser: ResponseParser = ResponseParser(),
|
protected open val responseParser: ResponseParser = ResponseParser(),
|
||||||
protected open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
protected open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
||||||
|
protected open val modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
||||||
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -140,10 +139,8 @@ open class FinTsJobExecutor(
|
||||||
protected open fun handleGetUsersTanMethodsResponse(response: BankResponse, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
protected open fun handleGetUsersTanMethodsResponse(response: BankResponse, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
||||||
val getUsersTanMethodsResponse = GetUserTanMethodsResponse(response)
|
val getUsersTanMethodsResponse = GetUserTanMethodsResponse(response)
|
||||||
|
|
||||||
if (getUsersTanMethodsResponse.successful) { // TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part
|
// TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part
|
||||||
updateBankData(dialogContext.bank, getUsersTanMethodsResponse)
|
updateBankAndCustomerDataIfResponseSuccessful(dialogContext, getUsersTanMethodsResponse)
|
||||||
updateCustomerData(dialogContext.bank, getUsersTanMethodsResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// even though it is required by specification some banks don't support retrieving user's TAN method by setting TAN method to '999'
|
// even though it is required by specification some banks don't support retrieving user's TAN method by setting TAN method to '999'
|
||||||
if (bankDoesNotSupportRetrievingUsersTanMethods(getUsersTanMethodsResponse)) {
|
if (bankDoesNotSupportRetrievingUsersTanMethods(getUsersTanMethodsResponse)) {
|
||||||
|
@ -201,8 +198,7 @@ open class FinTsJobExecutor(
|
||||||
closeDialog(dialogContext)
|
closeDialog(dialogContext)
|
||||||
|
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
updateBankData(bank, response)
|
updateBankAndCustomerData(bank, response)
|
||||||
updateCustomerData(bank, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(response)
|
callback(response)
|
||||||
|
@ -313,8 +309,7 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
||||||
if (response.successful) {
|
if (response.successful) {
|
||||||
updateBankData(bank, response)
|
updateBankAndCustomerData(bank, response)
|
||||||
updateCustomerData(bank, response)
|
|
||||||
|
|
||||||
closeDialog(dialogContext)
|
closeDialog(dialogContext)
|
||||||
}
|
}
|
||||||
|
@ -722,11 +717,7 @@ open class FinTsJobExecutor(
|
||||||
val message = messageBuilder.createInitDialogMessage(dialogContext)
|
val message = messageBuilder.createInitDialogMessage(dialogContext)
|
||||||
|
|
||||||
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
||||||
|
updateBankAndCustomerDataIfResponseSuccessful(dialogContext, response)
|
||||||
if (response.successful) {
|
|
||||||
updateBankData(dialogContext.bank, response)
|
|
||||||
updateCustomerData(dialogContext.bank, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(response)
|
callback(response)
|
||||||
}
|
}
|
||||||
|
@ -738,10 +729,7 @@ open class FinTsJobExecutor(
|
||||||
val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(dialogContext, segmentIdForTwoStepTanProcess)
|
val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(dialogContext, segmentIdForTwoStepTanProcess)
|
||||||
|
|
||||||
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
getAndHandleResponseForMessage(message, dialogContext) { response ->
|
||||||
if (response.successful) {
|
updateBankAndCustomerDataIfResponseSuccessful(dialogContext, response)
|
||||||
updateBankData(dialogContext.bank, response)
|
|
||||||
updateCustomerData(dialogContext.bank, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(response)
|
callback(response)
|
||||||
}
|
}
|
||||||
|
@ -832,317 +820,24 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
|
|
||||||
protected open fun updateBankData(bank: BankData, response: BankResponse) {
|
protected open fun updateBankData(bank: BankData, response: BankResponse) {
|
||||||
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->
|
modelMapper.updateBankData(bank, response)
|
||||||
bank.bpdVersion = bankParameters.bpdVersion
|
}
|
||||||
bank.bankCode = bankParameters.bankCode
|
|
||||||
bank.countryCode = bankParameters.bankCountryCode
|
|
||||||
bank.countMaxJobsPerMessage = bankParameters.countMaxJobsPerMessage
|
|
||||||
bank.supportedHbciVersions = bankParameters.supportedHbciVersions
|
|
||||||
bank.supportedLanguages = bankParameters.supportedLanguages
|
|
||||||
|
|
||||||
// bank.bic = bankParameters. // TODO: where's the BIC?
|
protected open fun updateBankAndCustomerDataIfResponseSuccessful(dialogContext: DialogContext, response: BankResponse) {
|
||||||
}
|
if (response.successful) {
|
||||||
|
updateBankAndCustomerData(dialogContext.bank, response)
|
||||||
response.getFirstSegmentById<PinInfo>(InstituteSegmentId.PinInfo)?.let { pinInfo ->
|
|
||||||
bank.pinInfo = pinInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<TanInfo>(InstituteSegmentId.TanInfo)?.let { tanInfo ->
|
|
||||||
bank.tanMethodSupportedByBank = mapToTanMethods(tanInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<CommunicationInfo>(InstituteSegmentId.CommunicationInfo)?.let { communicationInfo ->
|
|
||||||
communicationInfo.parameters.firstOrNull { it.type == Kommunikationsdienst.Https }?.address?.let { address ->
|
|
||||||
bank.finTs3ServerAddress = if (address.startsWith("https://", true)) address else "https://$address"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
|
||||||
sepaAccountInfo.account.bic?.let {
|
|
||||||
bank.bic = it // TODO: really set BIC on bank then?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
|
||||||
sepaAccountInfo.account.bic?.let {
|
|
||||||
bank.bic = it // TODO: really set BIC on bank then?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters ->
|
|
||||||
bank.changeTanMediumParameters = parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.supportedJobs.isNotEmpty()) {
|
|
||||||
bank.supportedJobs = response.supportedJobs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun updateCustomerData(bank: BankData, response: BankResponse) {
|
protected open fun updateBankAndCustomerData(bank: BankData, response: BankResponse) {
|
||||||
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->
|
updateBankData(bank, response)
|
||||||
// TODO: ask user if there is more than one supported language? But it seems that almost all banks only support German.
|
|
||||||
if (bank.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) {
|
|
||||||
bank.selectedLanguage = bankParameters.supportedLanguages.first()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<ReceivedSynchronization>(InstituteSegmentId.Synchronization)?.let { synchronization ->
|
modelMapper.updateCustomerData(bank, response)
|
||||||
synchronization.customerSystemId?.let {
|
|
||||||
bank.customerSystemId = it
|
|
||||||
|
|
||||||
bank.customerSystemStatus = KundensystemStatusWerte.Benoetigt // TODO: didn't find out for sure yet, but i think i read somewhere, that this has to be set when customerSystemId is set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getSegmentsById<AccountInfo>(InstituteSegmentId.AccountInfo).forEach { accountInfo ->
|
|
||||||
var accountHolderName = accountInfo.accountHolderName1
|
|
||||||
accountInfo.accountHolderName2?.let {
|
|
||||||
accountHolderName += it // TODO: add a whitespace in between?
|
|
||||||
}
|
|
||||||
bank.customerName = accountHolderName
|
|
||||||
|
|
||||||
findExistingAccount(bank, accountInfo)?.let { account ->
|
|
||||||
// TODO: update AccountData. But can this ever happen that an account changes?
|
|
||||||
}
|
|
||||||
?: run {
|
|
||||||
val newAccount = AccountData(accountInfo.accountIdentifier, accountInfo.subAccountAttribute,
|
|
||||||
accountInfo.bankCountryCode, accountInfo.bankCode, accountInfo.iban, accountInfo.customerId,
|
|
||||||
mapAccountType(accountInfo), accountInfo.currency, accountHolderName, accountInfo.productName,
|
|
||||||
accountInfo.accountLimit, accountInfo.allowedJobNames)
|
|
||||||
|
|
||||||
bank.supportedJobs.filterIsInstance<RetrieveAccountTransactionsParameters>().sortedByDescending { it.segmentVersion }.firstOrNull { newAccount.allowedJobNames.contains(it.jobName) }?.let { transactionsParameters ->
|
|
||||||
newAccount.countDaysForWhichTransactionsAreKept = transactionsParameters.countDaysForWhichTransactionsAreKept
|
|
||||||
}
|
|
||||||
|
|
||||||
bank.addAccount(newAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: may also make use of other info
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
|
||||||
// TODO: make use of information
|
|
||||||
sepaAccountInfo.account.iban?.let {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<UserParameters>(InstituteSegmentId.UserParameters)?.let { userParameters ->
|
|
||||||
bank.updVersion = userParameters.updVersion
|
|
||||||
|
|
||||||
if (bank.customerName.isEmpty()) {
|
|
||||||
userParameters.username?.let {
|
|
||||||
bank.customerName = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: may also make use of other info
|
|
||||||
}
|
|
||||||
|
|
||||||
response.getFirstSegmentById<CommunicationInfo>(InstituteSegmentId.CommunicationInfo)?.let { communicationInfo ->
|
|
||||||
if (bank.selectedLanguage != communicationInfo.defaultLanguage) {
|
|
||||||
bank.selectedLanguage = communicationInfo.defaultLanguage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val supportedJobs = response.supportedJobs
|
|
||||||
if (supportedJobs.isNotEmpty()) { // if allowedJobsForBank is empty than bank didn't send any allowed job
|
|
||||||
for (account in bank.accounts) {
|
|
||||||
setAllowedJobsForAccount(bank, account, supportedJobs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (bank.supportedJobs.isNotEmpty()) {
|
|
||||||
for (account in bank.accounts) {
|
|
||||||
if (account.allowedJobs.isEmpty()) {
|
|
||||||
setAllowedJobsForAccount(bank, account, bank.supportedJobs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.supportedTanMethodsForUser.isNotEmpty()) {
|
|
||||||
bank.tanMethodsAvailableForUser = response.supportedTanMethodsForUser.mapNotNull { findTanMethod(it, bank) }
|
|
||||||
|
|
||||||
if (bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction == bank.selectedTanMethod.securityFunction } == null) { // supportedTanMethods don't contain selectedTanMethod anymore
|
|
||||||
bank.resetSelectedTanMethod()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun findTanMethod(securityFunction: Sicherheitsfunktion, bank: BankData): TanMethod? {
|
|
||||||
return bank.tanMethodSupportedByBank.firstOrNull { it.securityFunction == securityFunction }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun setAllowedJobsForAccount(bank: BankData, account: AccountData, supportedJobs: List<JobParameters>) {
|
|
||||||
val allowedJobsForAccount = mutableListOf<JobParameters>()
|
|
||||||
|
|
||||||
for (job in supportedJobs) {
|
|
||||||
if (isJobSupported(account, job)) {
|
|
||||||
allowedJobsForAccount.add(job)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
account.allowedJobs = allowedJobsForAccount
|
|
||||||
|
|
||||||
account.setSupportsFeature(AccountFeature.RetrieveAccountTransactions, messageBuilder.supportsGetTransactions(account))
|
|
||||||
account.setSupportsFeature(AccountFeature.RetrieveBalance, messageBuilder.supportsGetBalance(account))
|
|
||||||
account.setSupportsFeature(AccountFeature.TransferMoney, messageBuilder.supportsBankTransfer(bank, account))
|
|
||||||
account.setSupportsFeature(AccountFeature.RealTimeTransfer, messageBuilder.supportsSepaRealTimeTransfer(bank, account))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapToTanMethods(tanInfo: TanInfo): List<TanMethod> {
|
|
||||||
return tanInfo.tanProcedureParameters.methodParameters.mapNotNull {
|
|
||||||
mapToTanMethod(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapToTanMethod(parameters: TanMethodParameters): TanMethod? {
|
|
||||||
val methodName = parameters.methodName
|
|
||||||
|
|
||||||
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
|
||||||
if (methodName.toLowerCase() == "itan") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return TanMethod(methodName, parameters.securityFunction,
|
|
||||||
mapToTanMethodType(parameters) ?: TanMethodType.EnterTan, mapHhdVersion(parameters),
|
|
||||||
parameters.maxTanInputLength, parameters.allowedTanFormat,
|
|
||||||
parameters.nameOfTanMediumRequired == BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden,
|
|
||||||
mapDecoupledTanMethodParameters(parameters))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapToTanMethodType(parameters: TanMethodParameters): TanMethodType? {
|
|
||||||
val name = parameters.methodName.toLowerCase()
|
|
||||||
|
|
||||||
return when {
|
|
||||||
// names are like 'chipTAN (comfort) manuell', 'Smart(-)TAN plus (manuell)' and
|
|
||||||
// technical identification is 'HHD'. Exception: there's one that states itself as 'chipTAN (Manuell)'
|
|
||||||
// but its DkTanMethod is set to 'HHDOPT1' -> handle ChipTanManuell before ChipTanFlickercode
|
|
||||||
parameters.dkTanMethod == DkTanMethod.HHD || name.contains("manuell") ->
|
|
||||||
TanMethodType.ChipTanManuell
|
|
||||||
|
|
||||||
// names are like 'chipTAN optisch/comfort', 'SmartTAN (plus) optic/USB', 'chipTAN (Flicker)' and
|
|
||||||
// technical identification is 'HHDOPT1'
|
|
||||||
parameters.dkTanMethod == DkTanMethod.HHDOPT1 ||
|
|
||||||
tanMethodNameContains(name, "optisch", "optic", "comfort", "flicker") ->
|
|
||||||
TanMethodType.ChipTanFlickercode
|
|
||||||
|
|
||||||
// 'Smart-TAN plus optisch / USB' seems to be a Flickertan method -> test for 'optisch' first
|
|
||||||
name.contains("usb") -> TanMethodType.ChipTanUsb
|
|
||||||
|
|
||||||
// QRTAN+ from 1822 direct has nothing to do with chipTAN QR.
|
|
||||||
name.contains("qr") -> {
|
|
||||||
if (tanMethodNameContains(name, "chipTAN", "Smart")) TanMethodType.ChipTanQrCode
|
|
||||||
else TanMethodType.QrCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// photoTAN from Commerzbank (comdirect), Deutsche Bank, norisbank has nothing to do with chipTAN photo
|
|
||||||
name.contains("photo") -> {
|
|
||||||
// e.g. 'Smart-TAN photo' / description 'Challenge'
|
|
||||||
if (tanMethodNameContains(name, "chipTAN", "Smart")) TanMethodType.ChipTanPhotoTanMatrixCode
|
|
||||||
// e.g. 'photoTAN-Verfahren', description 'Freigabe durch photoTAN'
|
|
||||||
else TanMethodType.photoTan
|
|
||||||
}
|
|
||||||
|
|
||||||
tanMethodNameContains(name, "SMS", "mobile", "mTAN") -> TanMethodType.SmsTan
|
|
||||||
|
|
||||||
// 'flateXSecure' identifies itself as 'PPTAN' instead of 'AppTAN'
|
|
||||||
// 'activeTAN-Verfahren' can actually be used either with an app or a reader; it's like chipTAN QR but without a chip card
|
|
||||||
parameters.dkTanMethod == DkTanMethod.App
|
|
||||||
|| tanMethodNameContains(name, "push", "app", "BestSign", "SecureGo", "TAN2go", "activeTAN", "easyTAN", "SecurePlus", "TAN+")
|
|
||||||
|| technicalTanMethodIdentificationContains(parameters, "SECURESIGN", "PPTAN") ->
|
|
||||||
TanMethodType.AppTan
|
|
||||||
|
|
||||||
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapHhdVersion(parameters: TanMethodParameters): HHDVersion? {
|
|
||||||
return when {
|
|
||||||
technicalTanMethodIdentificationContains(parameters, "HHD1.4") -> HHDVersion.HHD_1_4
|
|
||||||
technicalTanMethodIdentificationContains(parameters, "HHD1.3") -> HHDVersion.HHD_1_3
|
|
||||||
parameters.versionDkTanMethod?.contains("1.4") == true -> HHDVersion.HHD_1_4
|
|
||||||
parameters.versionDkTanMethod?.contains("1.3") == true -> HHDVersion.HHD_1_4
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun tanMethodNameContains(name: String, vararg namesToTest: String): Boolean {
|
|
||||||
namesToTest.forEach { nameToTest ->
|
|
||||||
if (name.contains(nameToTest.toLowerCase())) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun technicalTanMethodIdentificationContains(parameters: TanMethodParameters, vararg valuesToTest: String): Boolean {
|
|
||||||
valuesToTest.forEach { valueToTest ->
|
|
||||||
if (parameters.technicalTanMethodIdentification.contains(valueToTest, true)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapDecoupledTanMethodParameters(parameters: TanMethodParameters): DecoupledTanMethodParameters? {
|
|
||||||
parameters.manualConfirmationAllowedForDecoupled?.let { manualConfirmationAllowed ->
|
|
||||||
return DecoupledTanMethodParameters(
|
|
||||||
manualConfirmationAllowed,
|
|
||||||
parameters.periodicStateRequestsAllowedForDecoupled ?: false, // this and the following values are all set when manualConfirmationAllowedForDecoupled is set
|
|
||||||
parameters.maxNumberOfStateRequestsForDecoupled ?: 0,
|
|
||||||
parameters.initialDelayInSecondsForStateRequestsForDecoupled ?: Int.MAX_VALUE,
|
|
||||||
parameters.delayInSecondsForNextStateRequestsForDecoupled ?: Int.MAX_VALUE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun isJobSupported(bank: BankData, segmentId: ISegmentId): Boolean {
|
open fun isJobSupported(bank: BankData, segmentId: ISegmentId): Boolean {
|
||||||
return bank.supportedJobs.map { it.jobName }.contains(segmentId.id)
|
return modelMapper.isJobSupported(bank, segmentId)
|
||||||
}
|
|
||||||
|
|
||||||
open fun isJobSupported(account: AccountData, supportedJob: JobParameters): Boolean {
|
|
||||||
for (allowedJobName in account.allowedJobNames) {
|
|
||||||
if (allowedJobName == supportedJob.jobName) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun findExistingAccount(bank: BankData, accountInfo: AccountInfo): AccountData? {
|
|
||||||
bank.accounts.forEach { account ->
|
|
||||||
if (account.accountIdentifier == accountInfo.accountIdentifier
|
|
||||||
&& account.productName == accountInfo.productName) {
|
|
||||||
|
|
||||||
return account
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun mapAccountType(accountInfo: AccountInfo): AccountType? {
|
|
||||||
if (accountInfo.accountType == null || accountInfo.accountType == AccountType.Sonstige) {
|
|
||||||
accountInfo.productName?.let { name ->
|
|
||||||
// comdirect doesn't set account type field but names its bank accounts according to them like 'Girokonto', 'Tagesgeldkonto', ...
|
|
||||||
return when {
|
|
||||||
name.contains("Girokonto", true) -> AccountType.Girokonto
|
|
||||||
name.contains("Festgeld", true) -> AccountType.Festgeldkonto
|
|
||||||
name.contains("Tagesgeld", true) -> AccountType.Sparkonto // learnt something new today: according to Wikipedia some direct banks offer a modern version of saving accounts as 'Tagesgeldkonto'
|
|
||||||
name.contains("Kreditkarte", true) -> AccountType.Kreditkartenkonto
|
|
||||||
else -> accountInfo.accountType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountInfo.accountType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,335 @@
|
||||||
|
package net.dankito.banking.fints.model.mapper
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.Dialogsprache
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.BezeichnungDesTanMediumsErforderlich
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.DkTanMethod
|
||||||
|
import net.dankito.banking.fints.messages.segmente.id.ISegmentId
|
||||||
|
import net.dankito.banking.fints.model.*
|
||||||
|
import net.dankito.banking.fints.response.BankResponse
|
||||||
|
import net.dankito.banking.fints.response.InstituteSegmentId
|
||||||
|
import net.dankito.banking.fints.response.segments.*
|
||||||
|
|
||||||
|
|
||||||
|
open class ModelMapper(
|
||||||
|
protected open val messageBuilder: MessageBuilder // TODO: may extract class that contains common methods of ModelMapper and MessageBuilder
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
open fun updateBankData(bank: BankData, response: BankResponse) {
|
||||||
|
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->
|
||||||
|
bank.bpdVersion = bankParameters.bpdVersion
|
||||||
|
bank.bankCode = bankParameters.bankCode
|
||||||
|
bank.countryCode = bankParameters.bankCountryCode
|
||||||
|
bank.countMaxJobsPerMessage = bankParameters.countMaxJobsPerMessage
|
||||||
|
bank.supportedHbciVersions = bankParameters.supportedHbciVersions
|
||||||
|
bank.supportedLanguages = bankParameters.supportedLanguages
|
||||||
|
|
||||||
|
// bank.bic = bankParameters. // TODO: where's the BIC?
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<PinInfo>(InstituteSegmentId.PinInfo)?.let { pinInfo ->
|
||||||
|
bank.pinInfo = pinInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<TanInfo>(InstituteSegmentId.TanInfo)?.let { tanInfo ->
|
||||||
|
bank.tanMethodSupportedByBank = mapToTanMethods(tanInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<CommunicationInfo>(InstituteSegmentId.CommunicationInfo)?.let { communicationInfo ->
|
||||||
|
communicationInfo.parameters.firstOrNull { it.type == Kommunikationsdienst.Https }?.address?.let { address ->
|
||||||
|
bank.finTs3ServerAddress = if (address.startsWith("https://", true)) address else "https://$address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
||||||
|
sepaAccountInfo.account.bic?.let {
|
||||||
|
bank.bic = it // TODO: really set BIC on bank then?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
||||||
|
sepaAccountInfo.account.bic?.let {
|
||||||
|
bank.bic = it // TODO: really set BIC on bank then?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters ->
|
||||||
|
bank.changeTanMediumParameters = parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.supportedJobs.isNotEmpty()) {
|
||||||
|
bank.supportedJobs = response.supportedJobs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun updateCustomerData(bank: BankData, response: BankResponse) {
|
||||||
|
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->
|
||||||
|
// TODO: ask user if there is more than one supported language? But it seems that almost all banks only support German.
|
||||||
|
if (bank.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) {
|
||||||
|
bank.selectedLanguage = bankParameters.supportedLanguages.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<ReceivedSynchronization>(InstituteSegmentId.Synchronization)?.let { synchronization ->
|
||||||
|
synchronization.customerSystemId?.let {
|
||||||
|
bank.customerSystemId = it
|
||||||
|
|
||||||
|
bank.customerSystemStatus = KundensystemStatusWerte.Benoetigt // TODO: didn't find out for sure yet, but i think i read somewhere, that this has to be set when customerSystemId is set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getSegmentsById<AccountInfo>(InstituteSegmentId.AccountInfo).forEach { accountInfo ->
|
||||||
|
var accountHolderName = accountInfo.accountHolderName1
|
||||||
|
accountInfo.accountHolderName2?.let {
|
||||||
|
accountHolderName += it // TODO: add a whitespace in between?
|
||||||
|
}
|
||||||
|
bank.customerName = accountHolderName
|
||||||
|
|
||||||
|
findExistingAccount(bank, accountInfo)?.let { account ->
|
||||||
|
// TODO: update AccountData. But can this ever happen that an account changes?
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
val newAccount = AccountData(accountInfo.accountIdentifier, accountInfo.subAccountAttribute,
|
||||||
|
accountInfo.bankCountryCode, accountInfo.bankCode, accountInfo.iban, accountInfo.customerId,
|
||||||
|
mapAccountType(accountInfo), accountInfo.currency, accountHolderName, accountInfo.productName,
|
||||||
|
accountInfo.accountLimit, accountInfo.allowedJobNames)
|
||||||
|
|
||||||
|
bank.supportedJobs.filterIsInstance<RetrieveAccountTransactionsParameters>().sortedByDescending { it.segmentVersion }.firstOrNull { newAccount.allowedJobNames.contains(it.jobName) }?.let { transactionsParameters ->
|
||||||
|
newAccount.countDaysForWhichTransactionsAreKept = transactionsParameters.countDaysForWhichTransactionsAreKept
|
||||||
|
}
|
||||||
|
|
||||||
|
bank.addAccount(newAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: may also make use of other info
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
|
||||||
|
// TODO: make use of information
|
||||||
|
sepaAccountInfo.account.iban?.let {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<UserParameters>(InstituteSegmentId.UserParameters)?.let { userParameters ->
|
||||||
|
bank.updVersion = userParameters.updVersion
|
||||||
|
|
||||||
|
if (bank.customerName.isEmpty()) {
|
||||||
|
userParameters.username?.let {
|
||||||
|
bank.customerName = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: may also make use of other info
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getFirstSegmentById<CommunicationInfo>(InstituteSegmentId.CommunicationInfo)?.let { communicationInfo ->
|
||||||
|
if (bank.selectedLanguage != communicationInfo.defaultLanguage) {
|
||||||
|
bank.selectedLanguage = communicationInfo.defaultLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val supportedJobs = response.supportedJobs
|
||||||
|
if (supportedJobs.isNotEmpty()) { // if allowedJobsForBank is empty than bank didn't send any allowed job
|
||||||
|
for (account in bank.accounts) {
|
||||||
|
setAllowedJobsForAccount(bank, account, supportedJobs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bank.supportedJobs.isNotEmpty()) {
|
||||||
|
for (account in bank.accounts) {
|
||||||
|
if (account.allowedJobs.isEmpty()) {
|
||||||
|
setAllowedJobsForAccount(bank, account, bank.supportedJobs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.supportedTanMethodsForUser.isNotEmpty()) {
|
||||||
|
bank.tanMethodsAvailableForUser = response.supportedTanMethodsForUser.mapNotNull { findTanMethod(it, bank) }
|
||||||
|
|
||||||
|
if (bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction == bank.selectedTanMethod.securityFunction } == null) { // supportedTanMethods don't contain selectedTanMethod anymore
|
||||||
|
bank.resetSelectedTanMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun findTanMethod(securityFunction: Sicherheitsfunktion, bank: BankData): TanMethod? {
|
||||||
|
return bank.tanMethodSupportedByBank.firstOrNull { it.securityFunction == securityFunction }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun setAllowedJobsForAccount(bank: BankData, account: AccountData, supportedJobs: List<JobParameters>) {
|
||||||
|
val allowedJobsForAccount = mutableListOf<JobParameters>()
|
||||||
|
|
||||||
|
for (job in supportedJobs) {
|
||||||
|
if (isJobSupported(account, job)) {
|
||||||
|
allowedJobsForAccount.add(job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account.allowedJobs = allowedJobsForAccount
|
||||||
|
|
||||||
|
account.setSupportsFeature(AccountFeature.RetrieveAccountTransactions, messageBuilder.supportsGetTransactions(account))
|
||||||
|
account.setSupportsFeature(AccountFeature.RetrieveBalance, messageBuilder.supportsGetBalance(account))
|
||||||
|
account.setSupportsFeature(AccountFeature.TransferMoney, messageBuilder.supportsBankTransfer(bank, account))
|
||||||
|
account.setSupportsFeature(AccountFeature.RealTimeTransfer, messageBuilder.supportsSepaRealTimeTransfer(bank, account))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapToTanMethods(tanInfo: TanInfo): List<TanMethod> {
|
||||||
|
return tanInfo.tanProcedureParameters.methodParameters.mapNotNull {
|
||||||
|
mapToTanMethod(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapToTanMethod(parameters: TanMethodParameters): TanMethod? {
|
||||||
|
val methodName = parameters.methodName
|
||||||
|
|
||||||
|
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
||||||
|
if (methodName.toLowerCase() == "itan") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return TanMethod(methodName, parameters.securityFunction,
|
||||||
|
mapToTanMethodType(parameters) ?: TanMethodType.EnterTan, mapHhdVersion(parameters),
|
||||||
|
parameters.maxTanInputLength, parameters.allowedTanFormat,
|
||||||
|
parameters.nameOfTanMediumRequired == BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden,
|
||||||
|
mapDecoupledTanMethodParameters(parameters))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapToTanMethodType(parameters: TanMethodParameters): TanMethodType? {
|
||||||
|
val name = parameters.methodName.toLowerCase()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
// names are like 'chipTAN (comfort) manuell', 'Smart(-)TAN plus (manuell)' and
|
||||||
|
// technical identification is 'HHD'. Exception: there's one that states itself as 'chipTAN (Manuell)'
|
||||||
|
// but its DkTanMethod is set to 'HHDOPT1' -> handle ChipTanManuell before ChipTanFlickercode
|
||||||
|
parameters.dkTanMethod == DkTanMethod.HHD || name.contains("manuell") ->
|
||||||
|
TanMethodType.ChipTanManuell
|
||||||
|
|
||||||
|
// names are like 'chipTAN optisch/comfort', 'SmartTAN (plus) optic/USB', 'chipTAN (Flicker)' and
|
||||||
|
// technical identification is 'HHDOPT1'
|
||||||
|
parameters.dkTanMethod == DkTanMethod.HHDOPT1 ||
|
||||||
|
tanMethodNameContains(name, "optisch", "optic", "comfort", "flicker") ->
|
||||||
|
TanMethodType.ChipTanFlickercode
|
||||||
|
|
||||||
|
// 'Smart-TAN plus optisch / USB' seems to be a Flickertan method -> test for 'optisch' first
|
||||||
|
name.contains("usb") -> TanMethodType.ChipTanUsb
|
||||||
|
|
||||||
|
// QRTAN+ from 1822 direct has nothing to do with chipTAN QR.
|
||||||
|
name.contains("qr") -> {
|
||||||
|
if (tanMethodNameContains(name, "chipTAN", "Smart")) TanMethodType.ChipTanQrCode
|
||||||
|
else TanMethodType.QrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// photoTAN from Commerzbank (comdirect), Deutsche Bank, norisbank has nothing to do with chipTAN photo
|
||||||
|
name.contains("photo") -> {
|
||||||
|
// e.g. 'Smart-TAN photo' / description 'Challenge'
|
||||||
|
if (tanMethodNameContains(name, "chipTAN", "Smart")) TanMethodType.ChipTanPhotoTanMatrixCode
|
||||||
|
// e.g. 'photoTAN-Verfahren', description 'Freigabe durch photoTAN'
|
||||||
|
else TanMethodType.photoTan
|
||||||
|
}
|
||||||
|
|
||||||
|
tanMethodNameContains(name, "SMS", "mobile", "mTAN") -> TanMethodType.SmsTan
|
||||||
|
|
||||||
|
// 'flateXSecure' identifies itself as 'PPTAN' instead of 'AppTAN'
|
||||||
|
// 'activeTAN-Verfahren' can actually be used either with an app or a reader; it's like chipTAN QR but without a chip card
|
||||||
|
parameters.dkTanMethod == DkTanMethod.App
|
||||||
|
|| tanMethodNameContains(name, "push", "app", "BestSign", "SecureGo", "TAN2go", "activeTAN", "easyTAN", "SecurePlus", "TAN+")
|
||||||
|
|| technicalTanMethodIdentificationContains(parameters, "SECURESIGN", "PPTAN") ->
|
||||||
|
TanMethodType.AppTan
|
||||||
|
|
||||||
|
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapHhdVersion(parameters: TanMethodParameters): HHDVersion? {
|
||||||
|
return when {
|
||||||
|
technicalTanMethodIdentificationContains(parameters, "HHD1.4") -> HHDVersion.HHD_1_4
|
||||||
|
technicalTanMethodIdentificationContains(parameters, "HHD1.3") -> HHDVersion.HHD_1_3
|
||||||
|
parameters.versionDkTanMethod?.contains("1.4") == true -> HHDVersion.HHD_1_4
|
||||||
|
parameters.versionDkTanMethod?.contains("1.3") == true -> HHDVersion.HHD_1_4
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun tanMethodNameContains(name: String, vararg namesToTest: String): Boolean {
|
||||||
|
namesToTest.forEach { nameToTest ->
|
||||||
|
if (name.contains(nameToTest.toLowerCase())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun technicalTanMethodIdentificationContains(parameters: TanMethodParameters, vararg valuesToTest: String): Boolean {
|
||||||
|
valuesToTest.forEach { valueToTest ->
|
||||||
|
if (parameters.technicalTanMethodIdentification.contains(valueToTest, true)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapDecoupledTanMethodParameters(parameters: TanMethodParameters): DecoupledTanMethodParameters? {
|
||||||
|
parameters.manualConfirmationAllowedForDecoupled?.let { manualConfirmationAllowed ->
|
||||||
|
return DecoupledTanMethodParameters(
|
||||||
|
manualConfirmationAllowed,
|
||||||
|
parameters.periodicStateRequestsAllowedForDecoupled ?: false, // this and the following values are all set when manualConfirmationAllowedForDecoupled is set
|
||||||
|
parameters.maxNumberOfStateRequestsForDecoupled ?: 0,
|
||||||
|
parameters.initialDelayInSecondsForStateRequestsForDecoupled ?: Int.MAX_VALUE,
|
||||||
|
parameters.delayInSecondsForNextStateRequestsForDecoupled ?: Int.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun isJobSupported(bank: BankData, segmentId: ISegmentId): Boolean {
|
||||||
|
return bank.supportedJobs.map { it.jobName }.contains(segmentId.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun isJobSupported(account: AccountData, supportedJob: JobParameters): Boolean {
|
||||||
|
for (allowedJobName in account.allowedJobNames) {
|
||||||
|
if (allowedJobName == supportedJob.jobName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun findExistingAccount(bank: BankData, accountInfo: AccountInfo): AccountData? {
|
||||||
|
bank.accounts.forEach { account ->
|
||||||
|
if (account.accountIdentifier == accountInfo.accountIdentifier
|
||||||
|
&& account.productName == accountInfo.productName) {
|
||||||
|
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapAccountType(accountInfo: AccountInfo): AccountType? {
|
||||||
|
if (accountInfo.accountType == null || accountInfo.accountType == AccountType.Sonstige) {
|
||||||
|
accountInfo.productName?.let { name ->
|
||||||
|
// comdirect doesn't set account type field but names its bank accounts according to them like 'Girokonto', 'Tagesgeldkonto', ...
|
||||||
|
return when {
|
||||||
|
name.contains("Girokonto", true) -> AccountType.Girokonto
|
||||||
|
name.contains("Festgeld", true) -> AccountType.Festgeldkonto
|
||||||
|
name.contains("Tagesgeld", true) -> AccountType.Sparkonto // learnt something new today: according to Wikipedia some direct banks offer a modern version of saving accounts as 'Tagesgeldkonto'
|
||||||
|
name.contains("Kreditkarte", true) -> AccountType.Kreditkartenkonto
|
||||||
|
else -> accountInfo.accountType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountInfo.accountType
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.SmsAb
|
||||||
import net.dankito.banking.fints.model.*
|
import net.dankito.banking.fints.model.*
|
||||||
import net.dankito.banking.bankfinder.BankInfo
|
import net.dankito.banking.bankfinder.BankInfo
|
||||||
import net.dankito.banking.fints.FinTsJobExecutor
|
import net.dankito.banking.fints.FinTsJobExecutor
|
||||||
|
import net.dankito.banking.fints.model.mapper.ModelMapper
|
||||||
import net.dankito.banking.fints.response.BankResponse
|
import net.dankito.banking.fints.response.BankResponse
|
||||||
import net.dankito.banking.fints.response.segments.SepaAccountInfoParameters
|
import net.dankito.banking.fints.response.segments.SepaAccountInfoParameters
|
||||||
import net.dankito.banking.fints.response.segments.TanInfo
|
import net.dankito.banking.fints.response.segments.TanInfo
|
||||||
|
@ -50,19 +51,19 @@ class BanksFinTsDetailsRetriever {
|
||||||
|
|
||||||
private val messageBuilder = MessageBuilder()
|
private val messageBuilder = MessageBuilder()
|
||||||
|
|
||||||
private val jobExecutor = object : FinTsJobExecutor(NoOpFinTsClientCallback()) {
|
private val modelMapper = object : ModelMapper(messageBuilder) {
|
||||||
|
|
||||||
fun getAndHandleResponseForMessagePublic(message: MessageBuilderResult, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
|
||||||
getAndHandleResponseForMessage(message, dialogContext, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateBankDataPublic(bank: BankData, response: BankResponse) {
|
|
||||||
super.updateBankData(bank, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapToTanMethodTypePublic(parameters: TanMethodParameters): TanMethodType? {
|
fun mapToTanMethodTypePublic(parameters: TanMethodParameters): TanMethodType? {
|
||||||
return super.mapToTanMethodType(parameters)
|
return super.mapToTanMethodType(parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val jobExecutor = object : FinTsJobExecutor(NoOpFinTsClientCallback(), modelMapper = modelMapper) {
|
||||||
|
|
||||||
|
fun getAndHandleResponseForMessagePublic(message: MessageBuilderResult, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
||||||
|
getAndHandleResponseForMessage(message, dialogContext, callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ class BanksFinTsDetailsRetriever {
|
||||||
|
|
||||||
countDownLatch.await(30, TimeUnit.SECONDS)
|
countDownLatch.await(30, TimeUnit.SECONDS)
|
||||||
|
|
||||||
jobExecutor.updateBankDataPublic(bank, anonymousBankInfoResponse.get())
|
modelMapper.updateBankData(bank, anonymousBankInfoResponse.get())
|
||||||
|
|
||||||
return anonymousBankInfoResponse.get()
|
return anonymousBankInfoResponse.get()
|
||||||
}
|
}
|
||||||
|
@ -212,7 +213,7 @@ class BanksFinTsDetailsRetriever {
|
||||||
tanMethodParameter[methodParameter.methodName]?.add(methodParameter)
|
tanMethodParameter[methodParameter.methodName]?.add(methodParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tanMethodType = jobExecutor.mapToTanMethodTypePublic(methodParameter)
|
val tanMethodType = modelMapper.mapToTanMethodTypePublic(methodParameter)
|
||||||
if (tanMethodTypes.containsKey(tanMethodType) == false) {
|
if (tanMethodTypes.containsKey(tanMethodType) == false) {
|
||||||
tanMethodTypes.put(tanMethodType, mutableSetOf(methodParameter))
|
tanMethodTypes.put(tanMethodType, mutableSetOf(methodParameter))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue