From 504fbaf13b8cc9c65bbefdb1f69a4c58dd9142eb Mon Sep 17 00:00:00 2001 From: dankito Date: Mon, 2 Sep 2024 13:03:04 +0200 Subject: [PATCH] Implemented Decoupled TAN process (HKTAN 7) --- .../codinux/banking/fints/FinTsJobExecutor.kt | 65 ++++++++++++++++--- .../banking/fints/messages/MessageBuilder.kt | 18 ++++- .../implementierte/tan/TanProcess.kt | 7 +- .../ZweiSchrittTanEinreichung.kt | 9 +-- .../model/DecoupledTanMethodParameters.kt | 6 +- .../banking/fints/model/EnterTanResult.kt | 7 ++ .../banking/fints/model/MessageType.kt | 2 + .../banking/fints/model/TanChallenge.kt | 9 ++- .../codinux/banking/fints/model/TanMethod.kt | 1 + .../banking/fints/model/mapper/ModelMapper.kt | 12 ++-- .../banking/fints/response/ResponseParser.kt | 2 +- .../response/segments/TanMethodParameters.kt | 6 +- .../banking/fints/util/TanMethodSelector.kt | 4 +- .../fints/response/ResponseParserTest.kt | 20 ++++++ 14 files changed, 134 insertions(+), 34 deletions(-) diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt index 105bc38b..e83745a8 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt @@ -21,6 +21,8 @@ import net.codinux.banking.fints.util.TanMethodSelector import net.codinux.banking.fints.extensions.minusDays import net.codinux.banking.fints.extensions.todayAtEuropeBerlin import net.codinux.banking.fints.extensions.todayAtSystemDefaultTimeZone +import kotlin.math.max +import kotlin.time.Duration.Companion.seconds /** @@ -413,16 +415,60 @@ open class FinTsJobExecutor( } } - protected open fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) { + protected open suspend fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) { context.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters -> - if (tanResponse.tanProcess == TanProcess.AppTan && decoupledTanMethodParameters.periodicStateRequestsAllowed) { - automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge) + if (decoupledTanMethodParameters.periodicStateRequestsAllowed) { + val responseAfterApprovingDecoupledTan = + automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, decoupledTanMethodParameters) + + if (responseAfterApprovingDecoupledTan != null) { + tanChallenge.userApprovedDecoupledTan(responseAfterApprovingDecoupledTan) + } else { + tanChallenge.userDidNotEnterTan() + } } } } - protected open fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge) { + protected open suspend fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse, parameters: DecoupledTanMethodParameters): BankResponse? { log.info { "automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge" } + + delay(max(5, parameters.initialDelayInSecondsForStateRequest).seconds) + + var iteration = 0 + val minWaitTime = when { + parameters.maxNumberOfStateRequests <= 10 -> 30 + parameters.maxNumberOfStateRequests <= 24 -> 10 + else -> 3 + } + val delayForNextStateRequest = max(minWaitTime, parameters.delayInSecondsForNextStateRequest).seconds + + while (iteration < parameters.maxNumberOfStateRequests) { + try { + val message = messageBuilder.createDecoupledTanStatusMessage(context, tanResponse) + + val response = getAndHandleResponseForMessage(context, message) + + val tanFeedbacks = response.segmentFeedbacks.filter { it.referenceSegmentNumber == MessageBuilder.SignedMessagePayloadFirstSegmentNumber } + if (tanFeedbacks.isNotEmpty()) { + // new feedback code for Decoupled TAN: 0900 Sicherheitsfreigabe gültig + val isTanApproved = tanFeedbacks.any { it.feedbacks.any { it.responseCode == 900 } } + if (isTanApproved) { + return response + } + } + + iteration++ + // sometimes delayInSecondsForNextStateRequests is only 1 or 2 seconds, that's too fast i think + delay(delayForNextStateRequest) + } catch (e: Throwable) { + log.error(e) { "Could not check status of Decoupled TAN" } + + return null + } + } + + return null } protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse, @@ -430,19 +476,18 @@ open class FinTsJobExecutor( if (enteredTanResult.changeTanMethodTo != null) { return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo) - } - else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) { + } else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) { return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo, enteredTanResult.changeTanMediumResultCallback) - } - else if (enteredTanResult.enteredTan == null) { + } else if (enteredTanResult.userApprovedDecoupledTan == true && enteredTanResult.responseAfterApprovingDecoupledTan != null) { + return enteredTanResult.responseAfterApprovingDecoupledTan + } else if (enteredTanResult.enteredTan == null) { // i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank) // but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog response.tanRequiredButUserDidNotEnterOne = true return response - } - else { + } else { return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse) } } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/MessageBuilder.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/MessageBuilder.kt index d312702b..d9daca09 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/MessageBuilder.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/MessageBuilder.kt @@ -41,7 +41,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) { private const val SignatureHeaderSegmentNumber = MessageHeaderSegmentNumber + 1 - private const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1 + const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1 } @@ -295,12 +295,22 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) { val segments = listOf( ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, tanProcess, null, - tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier) + tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier, tanResponse.segmentVersion) ) return createSignedMessageBuilderResult(context, MessageType.Tan, createSignedMessage(context, enteredTan, segments), segments) } + open fun createDecoupledTanStatusMessage(context: JobContext, tanResponse: TanResponse): MessageBuilderResult { + + val segments = listOf( + ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, TanProcess.AppTan, + jobReference = tanResponse.jobReference, furtherTanFollows = false, segmentVersion = 7, tanMediaIdentifier = tanResponse.tanMediaIdentifier) + ) + + return createSignedMessageBuilderResult(context, MessageType.CheckDecoupledTanStatus, createSignedMessage(context, null, segments), segments) + } + open fun createBankTransferMessage(context: JobContext, data: BankTransferData, account: AccountData): MessageBuilderResult { @@ -521,8 +531,10 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) { } protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId, segmentNumber: Int): ZweiSchrittTanEinreichung { + val bank = context.bank + return ZweiSchrittTanEinreichung(segmentNumber, TanProcess.TanProcess4, segmentId, - tanMediaIdentifier = getTanMediaIdentifierIfRequired(context)) + tanMediaIdentifier = getTanMediaIdentifierIfRequired(context), segmentVersion = bank.selectedTanMethod.hktanVersion) } protected open fun getTanMediaIdentifierIfRequired(context: JobContext): String? { diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/datenelemente/implementierte/tan/TanProcess.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/datenelemente/implementierte/tan/TanProcess.kt index a6f00112..d4eeeb24 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/datenelemente/implementierte/tan/TanProcess.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/datenelemente/implementierte/tan/TanProcess.kt @@ -50,6 +50,11 @@ enum class TanProcess(override val code: String) : ICodeEnum { */ TanProcess4("4"), - AppTan("S") // TODO: what is this? + /** + * kann nur nach dem ersten Schritt auftreten. Er dient im DecoupledVerfahren der Statusabfrage der vom Kunden zu + * tätigenden Sicherheitsfreigabe auf einem anderen Gerät mittels HKTAN. Dieser Geschäftsvorfall wird mit HITAN, + * TAN-Prozess=S beantwortet. + */ + AppTan("S") } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt index d7094b3c..252129c9 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt @@ -19,16 +19,17 @@ open class ZweiSchrittTanEinreichung( jobReference: String? = null, furtherTanFollows: Boolean? = false, cancelJob: Boolean? = null, - tanMediaIdentifier: String? = null + tanMediaIdentifier: String? = null, + segmentVersion: Int = 6 ) : Segment(listOf( - Segmentkopf(CustomerSegmentId.Tan, 6, segmentNumber), + Segmentkopf(CustomerSegmentId.Tan, segmentVersion, segmentNumber), TANProzessDatenelement(process), Segmentkennung(segmentIdForWhichTanShouldGetGenerated?.id ?: ""), // M: bei TAN-Prozess=1. M: bei TAN-Prozess=4 und starker Authentifizierung. N: sonst NotAllowedDatenelement(), // Kontoverbindung // M: bei TAN-Prozess=1 und "Auftraggeberkonto erforderlich"=2 und Kontoverbindung im Auftrag enthalten. N: sonst AuftragsHashwert(jobHashValue ?: "", Existenzstatus.NotAllowed), // M: bei AuftragsHashwertverfahren<>0 und TAN-Prozess=1. N: sonst - Auftragsreferenz(jobReference ?: "", Existenzstatus.Mandatory), // M: bei TAN-Prozess=2, 3, 4. O: bei TAN-Prozess=1 - JaNein(furtherTanFollows, if (process == TanProcess.TanProcess1 || process == TanProcess.TanProcess2) Existenzstatus.Mandatory else Existenzstatus.NotAllowed), // M: bei TAN-Prozess=1, 2. N: bei TAN-Prozess=3, 4 + Auftragsreferenz(jobReference ?: "", if (process == TanProcess.TanProcess2 || process == TanProcess.TanProcess3 || process == TanProcess.AppTan) Existenzstatus.Mandatory else Existenzstatus.Optional), // M: bei TAN-Prozess=2, 3, 4. O: bei TAN-Prozess=1 + JaNein(furtherTanFollows, if (process == TanProcess.TanProcess1 || process == TanProcess.TanProcess2 || process == TanProcess.AppTan) Existenzstatus.Mandatory else Existenzstatus.NotAllowed), // M: bei TAN-Prozess=1, 2. N: bei TAN-Prozess=3, 4 JaNein(cancelJob, if (process == TanProcess.TanProcess2 && cancelJob != null) Existenzstatus.Optional else Existenzstatus.NotAllowed), // O: bei TAN-Prozess=2 und „Auftragsstorno erlaubt“=J. N: sonst NotAllowedDatenelement(), // TODO: SMS-Abbuchungskonto // M: Bei TAN-Process=1, 3, 4 und „SMS-Abbuchungskonto erforderlich“=2. O: sonst NotAllowedDatenelement(), // TODO: Challenge-Klasse // M: bei TAN-Prozess=1 und „Challenge-Klasse erforderlich“=J. N: sonst diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/DecoupledTanMethodParameters.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/DecoupledTanMethodParameters.kt index 70bcf3f2..5482381a 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/DecoupledTanMethodParameters.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/DecoupledTanMethodParameters.kt @@ -8,12 +8,12 @@ open class DecoupledTanMethodParameters( open val manualConfirmationAllowed: Boolean, open val periodicStateRequestsAllowed: Boolean, open val maxNumberOfStateRequests: Int, - open val initialDelayInSecondsForStateRequests: Int, - open val delayInSecondsForNextStateRequests: Int + open val initialDelayInSecondsForStateRequest: Int, + open val delayInSecondsForNextStateRequest: Int ) { override fun toString(): String { - return "DecoupledTanMethodParameters(manualConfirmationAllowed=$manualConfirmationAllowed, periodicStateRequestsAllowed=$periodicStateRequestsAllowed, maxNumberOfStateRequests=$maxNumberOfStateRequests, initialDelayInSecondsForStateRequests=$initialDelayInSecondsForStateRequests, delayInSecondsForNextStateRequests=$delayInSecondsForNextStateRequests)" + return "DecoupledTanMethodParameters(manualConfirmationAllowed=$manualConfirmationAllowed, periodicStateRequestsAllowed=$periodicStateRequestsAllowed, maxNumberOfStateRequests=$maxNumberOfStateRequests, initialDelayInSecondsForStateRequests=$initialDelayInSecondsForStateRequest, delayInSecondsForNextStateRequests=$delayInSecondsForNextStateRequest)" } } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/EnterTanResult.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/EnterTanResult.kt index 3cb281d5..16204698 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/EnterTanResult.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/EnterTanResult.kt @@ -1,17 +1,24 @@ package net.codinux.banking.fints.model import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium +import net.codinux.banking.fints.response.BankResponse import net.codinux.banking.fints.response.client.FinTsClientResponse open class EnterTanResult( val enteredTan: String?, + val userApprovedDecoupledTan: Boolean? = null, + val responseAfterApprovingDecoupledTan: BankResponse? = null, val changeTanMethodTo: TanMethod? = null, val changeTanMediumTo: TanMedium? = null, val changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)? = null ) { override fun toString(): String { + if (userApprovedDecoupledTan == true) { + return "User approved Decoupled TAN" + } + if (changeTanMethodTo != null) { return "User asks to change TAN method to $changeTanMethodTo" } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/MessageType.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/MessageType.kt index 99f9caea..12cc5812 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/MessageType.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/MessageType.kt @@ -15,6 +15,8 @@ enum class MessageType { SynchronizeCustomerSystemId, + CheckDecoupledTanStatus, + Tan, GetBalance, diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanChallenge.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanChallenge.kt index ad25fc1f..b213fcef 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanChallenge.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanChallenge.kt @@ -1,6 +1,7 @@ package net.codinux.banking.fints.model import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium +import net.codinux.banking.fints.response.BankResponse import net.codinux.banking.fints.response.client.FinTsClientResponse @@ -25,16 +26,20 @@ open class TanChallenge( this.enterTanResult = EnterTanResult(enteredTan.replace(" ", "")) } + internal fun userApprovedDecoupledTan(responseAfterApprovingDecoupledTan: BankResponse) { + this.enterTanResult = EnterTanResult(null, true, responseAfterApprovingDecoupledTan) + } + fun userDidNotEnterTan() { this.enterTanResult = EnterTanResult(null) } fun userAsksToChangeTanMethod(changeTanMethodTo: TanMethod) { - this.enterTanResult = EnterTanResult(null, changeTanMethodTo) + this.enterTanResult = EnterTanResult(null, changeTanMethodTo = changeTanMethodTo) } fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) { - this.enterTanResult = EnterTanResult(null, null, changeTanMediumTo, changeTanMediumResultCallback) + this.enterTanResult = EnterTanResult(null, changeTanMediumTo = changeTanMediumTo, changeTanMediumResultCallback = changeTanMediumResultCallback) } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanMethod.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanMethod.kt index ea7046c7..bb22044e 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanMethod.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/TanMethod.kt @@ -14,6 +14,7 @@ open class TanMethod( open val maxTanInputLength: Int? = null, open val allowedTanFormat: AllowedTanFormat? = null, open val nameOfTanMediumRequired: Boolean = false, + open val hktanVersion: Int = 6, open val decoupledParameters: DecoupledTanMethodParameters? = null ) { diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/mapper/ModelMapper.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/mapper/ModelMapper.kt index e797f550..46bf47b1 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/mapper/ModelMapper.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/mapper/ModelMapper.kt @@ -176,11 +176,11 @@ open class ModelMapper( protected open fun mapToTanMethods(tanInfo: TanInfo): List { return tanInfo.tanProcedureParameters.methodParameters.mapNotNull { - mapToTanMethod(it) + mapToTanMethod(it, tanInfo.segmentVersion) } } - protected open fun mapToTanMethod(parameters: TanMethodParameters): TanMethod? { + protected open fun mapToTanMethod(parameters: TanMethodParameters, hktanVersion: Int): TanMethod? { val methodName = parameters.methodName // we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2 @@ -192,7 +192,7 @@ open class ModelMapper( mapToTanMethodType(parameters) ?: TanMethodType.EnterTan, mapHhdVersion(parameters), parameters.maxTanInputLength, parameters.allowedTanFormat, parameters.nameOfTanMediumRequired == BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden, - mapDecoupledTanMethodParameters(parameters)) + hktanVersion, mapDecoupledTanMethodParameters(parameters)) } protected open fun mapToTanMethodType(parameters: TanMethodParameters): TanMethodType? { @@ -276,10 +276,10 @@ open class ModelMapper( parameters.manualConfirmationAllowedForDecoupled?.let { manualConfirmationAllowed -> return DecoupledTanMethodParameters( manualConfirmationAllowed, - parameters.periodicStateRequestsAllowedForDecoupled ?: false, // this and the following values are all set when manualConfirmationAllowedForDecoupled is set + parameters.periodicDecoupledStateRequestsAllowed ?: 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 + parameters.initialDelayInSecondsForDecoupledStateRequest ?: Int.MAX_VALUE, + parameters.delayInSecondsForNextDecoupledStateRequests ?: Int.MAX_VALUE ) } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/ResponseParser.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/ResponseParser.kt index 8ef27dfc..94ff5252 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/ResponseParser.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/ResponseParser.kt @@ -431,7 +431,7 @@ open class ResponseParser( } if (parsedMethodParameters.dkTanMethod == DkTanMethod.Decoupled) { - if (parsedMethodParameters.periodicStateRequestsAllowedForDecoupled != null) { + if (parsedMethodParameters.periodicDecoupledStateRequestsAllowed != null) { return 26 } else if (parsedMethodParameters.manualConfirmationAllowedForDecoupled != null) { diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/segments/TanMethodParameters.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/segments/TanMethodParameters.kt index b10d3d5a..d50bf687 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/segments/TanMethodParameters.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/response/segments/TanMethodParameters.kt @@ -27,10 +27,10 @@ open class TanMethodParameters( val hhdUcResponseRequired: Boolean, // TODO: wird hierueber gesteuert ob eine TAN eingegeben werden muss (z. B. beim EasyTAN Verfahren muss ja keine eingegeben werden) val countSupportedActiveTanMedia: Int?, val maxNumberOfStateRequestsForDecoupled: Int? = null, - val initialDelayInSecondsForStateRequestsForDecoupled: Int? = null, - val delayInSecondsForNextStateRequestsForDecoupled: Int? = null, + val initialDelayInSecondsForDecoupledStateRequest: Int? = null, + val delayInSecondsForNextDecoupledStateRequests: Int? = null, val manualConfirmationAllowedForDecoupled: Boolean? = null, - val periodicStateRequestsAllowedForDecoupled: Boolean? = null + val periodicDecoupledStateRequestsAllowed: Boolean? = null ) { diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/util/TanMethodSelector.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/util/TanMethodSelector.kt index d097972e..cf386ce4 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/util/TanMethodSelector.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/util/TanMethodSelector.kt @@ -16,7 +16,9 @@ open class TanMethodSelector { open fun getSuggestedTanMethod(tanMethods: List): TanMethod? { - return tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan && it.type != TanMethodType.ChipTanManuell } + return tanMethods.firstOrNull { it.decoupledParameters != null } // decoupled TAN method is the most simplistic TAN method, user only has to confirm the action in her TAN app, no manual TAN entering required + ?: tanMethods.firstOrNull { it.type == TanMethodType.AppTan } // that's the second most simplistic TAN method: user has to confirm action in her TAN app and then enter the displayed TAN + ?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan && it.type != TanMethodType.ChipTanManuell } ?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan } ?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb } ?: first(tanMethods) diff --git a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/response/ResponseParserTest.kt b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/response/ResponseParserTest.kt index 5ae7d66d..d7e1c33c 100644 --- a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/response/ResponseParserTest.kt +++ b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/response/ResponseParserTest.kt @@ -799,6 +799,26 @@ class ResponseParserTest : FinTsTestBase() { ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } } + @Test + fun parseTanInfo7_DecoupledTanMethod() { + + // when + val result = underTest.parse("HITANS:50:7:4+1+1+1+N:N:0:946:2:DECOUPLED:Decoupled::SecureGo plus (Direktfreigabe):::TAN:2048:J:1:N:0:2:N:J:00:0:N:1:150:2:2:J:J'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 50, 7, 4) + + result.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { segment -> + val tanMethodParameters = segment.tanProcedureParameters.methodParameters + assertSize(1, tanMethodParameters) + + assertTanMethodParameter(tanMethodParameters, 0, Sicherheitsfunktion.PIN_TAN_946, DkTanMethod.Decoupled, "SecureGo plus (Direktfreigabe)") + + assertEquals("DECOUPLED", tanMethodParameters[0].technicalTanMethodIdentification) + } + ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } + } + private fun assertTanMethodParameter(parsedTanMethodParameters: List, index: Int, securityFunction: Sicherheitsfunktion, tanMethod: DkTanMethod?, methodName: String) {