diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt index 69e3ba91..6599d3a8 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt @@ -1141,13 +1141,13 @@ open class FinTsClient( 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 ZkaTanMethod is set to 'HHDOPT1' -> handle ChipTanManuell before ChipTanFlickercode - parameters.zkaTanMethod == ZkaTanMethod.HHD || name.contains("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.zkaTanMethod == ZkaTanMethod.HHDOPT1 || + parameters.dkTanMethod == DkTanMethod.HHDOPT1 || tanMethodNameContains(name, "optisch", "optic", "comfort", "flicker") -> TanMethodType.ChipTanFlickercode @@ -1172,7 +1172,8 @@ open class FinTsClient( // '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 - tanMethodNameContains(name, "push", "app", "BestSign", "SecureGo", "TAN2go", "activeTAN", "easyTAN", "SecurePlus", "TAN+") + parameters.dkTanMethod == DkTanMethod.App + || tanMethodNameContains(name, "push", "app", "BestSign", "SecureGo", "TAN2go", "activeTAN", "easyTAN", "SecurePlus", "TAN+") || technicalTanMethodIdentificationContains(parameters, "SECURESIGN", "PPTAN") -> TanMethodType.AppTan @@ -1185,8 +1186,8 @@ open class FinTsClient( return when { technicalTanMethodIdentificationContains(parameters, "HHD1.4") -> HHDVersion.HHD_1_4 technicalTanMethodIdentificationContains(parameters, "HHD1.3") -> HHDVersion.HHD_1_3 - parameters.versionZkaTanMethod?.contains("1.4") == true -> HHDVersion.HHD_1_4 - parameters.versionZkaTanMethod?.contains("1.3") == true -> HHDVersion.HHD_1_4 + parameters.versionDkTanMethod?.contains("1.4") == true -> HHDVersion.HHD_1_4 + parameters.versionDkTanMethod?.contains("1.3") == true -> HHDVersion.HHD_1_4 else -> null } } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/ZkaTanMethod.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/DkTanMethod.kt similarity index 54% rename from fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/ZkaTanMethod.kt rename to fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/DkTanMethod.kt index c10b5135..ae3813f4 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/ZkaTanMethod.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/DkTanMethod.kt @@ -1,7 +1,7 @@ package net.dankito.banking.fints.messages.datenelemente.implementierte.tan -enum class ZkaTanMethod { +enum class DkTanMethod { HHD, @@ -11,8 +11,10 @@ enum class ZkaTanMethod { mobileTAN, - // values not specified by standard but found out there in the wild + App, - appTAN + Decoupled, + + DecoupledPush } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt index 23a644f9..75e68b64 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt @@ -10,7 +10,7 @@ open class TanMethod( open val type: TanMethodType, open val hhdVersion: HHDVersion? = null, open val maxTanInputLength: Int? = null, - open val allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric, + open val allowedTanFormat: AllowedTanFormat? = null, open val nameOfTanMediumRequired: Boolean = false ) { diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/ResponseParser.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/ResponseParser.kt index b537362d..d3df744b 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/ResponseParser.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/ResponseParser.kt @@ -390,33 +390,57 @@ open class ResponseParser( var remainingDataElements = methodsDataElements while (remainingDataElements.size >= 20) { // parameters have at least 20 data elements, the last element is optional - val dataElementForNextMethod = if (remainingDataElements.size >= 21) remainingDataElements.subList(0, 21) - else remainingDataElements.subList(0, 20) - - val methodParameters = mapToSingleTanMethodParameters(dataElementForNextMethod) + val methodParameters = mapToSingleTanMethodParameters(remainingDataElements) parsedMethodsParameters.add(methodParameters) - val has21ElementsParsed = methodParameters.countSupportedActiveTanMedia != null || - (dataElementForNextMethod.size >= 21 && dataElementForNextMethod[20].isBlank()) - - if (has21ElementsParsed) remainingDataElements = remainingDataElements.subList(21, remainingDataElements.size) - else remainingDataElements = remainingDataElements.subList(20, remainingDataElements.size) + val countParsedDataElements = determineCountParsedTanMethodParametersDataElements(remainingDataElements, methodParameters) + remainingDataElements = remainingDataElements.subList(countParsedDataElements, remainingDataElements.size) } return parsedMethodsParameters } + protected open fun determineCountParsedTanMethodParametersDataElements(remainingDataElements: List, parsedMethodParameters: TanMethodParameters): Int { + // last TanMethodParameters data elements + if (remainingDataElements.size == 20) { + return 20 + } + else if (remainingDataElements.size == 21) { + return 21 + } + + if (parsedMethodParameters.dkTanMethod == DkTanMethod.Decoupled) { + if (parsedMethodParameters.periodicStateRequestsAllowedForDecoupled != null) { + return 26 + } + else if (parsedMethodParameters.manualConfirmationAllowedForDecoupled != null) { + return 26 + } + else { + return 24 + } + } + + if (parsedMethodParameters.countSupportedActiveTanMedia != null || remainingDataElements[20].isBlank()) { + return 21 + } + + return 20 + } + protected open fun mapToSingleTanMethodParameters(methodDataElements: List): TanMethodParameters { + val dkTanMethod = tryToParseDkTanMethod(methodDataElements[3]) + val isDecoupledTanMethod = dkTanMethod == DkTanMethod.Decoupled || dkTanMethod == DkTanMethod.DecoupledPush return TanMethodParameters( parseCodeEnum(methodDataElements[0], Sicherheitsfunktion.values()), parseCodeEnum(methodDataElements[1], TanProcess.values()), parseString(methodDataElements[2]), - tryToParseZkaTanMethod(methodDataElements[3]), + dkTanMethod, parseStringToNullIfEmpty(methodDataElements[4]), parseString(methodDataElements[5]), - parseInt(methodDataElements[6]), - parseCodeEnum(methodDataElements[7], AllowedTanFormat.values()), + if (isDecoupledTanMethod) null else parseNullableInt(methodDataElements[6]), + if (isDecoupledTanMethod) null else parseCodeEnum(methodDataElements[7], AllowedTanFormat.values()), parseString(methodDataElements[8]), parseInt(methodDataElements[9]), // for HITANS 4 and 5 here is another "Anzahl unterstützter aktiver TAN-Listen" Integer element @@ -431,30 +455,35 @@ open class ResponseParser( parseCodeEnum(methodDataElements[17], Initialisierungsmodus.values()), parseCodeEnum(methodDataElements[18], BezeichnungDesTanMediumsErforderlich.values()), parseBoolean(methodDataElements[19]), - if (methodDataElements.size > 20) parseNullableInt(methodDataElements[20]) else null + if (methodDataElements.size > 20) parseNullableInt(methodDataElements[20]) else null, + if (isDecoupledTanMethod && methodDataElements.size > 21) parseNullableInt(methodDataElements[21]) else null, + if (isDecoupledTanMethod && methodDataElements.size > 22) parseNullableInt(methodDataElements[22]) else null, + if (isDecoupledTanMethod && methodDataElements.size > 23) parseNullableInt(methodDataElements[23]) else null, + if (isDecoupledTanMethod && methodDataElements.size > 24) parseNullableBoolean(methodDataElements[24]) else null, + if (isDecoupledTanMethod && methodDataElements.size > 25) parseNullableBoolean(methodDataElements[25]) else null ) } - protected open fun tryToParseZkaTanMethod(mayZkaTanMethod: String): ZkaTanMethod? { - if (mayZkaTanMethod.isBlank()) { + protected open fun tryToParseDkTanMethod(mayDkTanMethod: String): DkTanMethod? { + if (mayDkTanMethod.isBlank()) { return null } try { - val lowerCaseMayZkaTanMethod = mayZkaTanMethod.toLowerCase() + val lowerCaseMayDkTanMethod = mayDkTanMethod.toLowerCase() - if (lowerCaseMayZkaTanMethod == "mobiletan" || lowerCaseMayZkaTanMethod == "mtan") { - return ZkaTanMethod.mobileTAN + if (lowerCaseMayDkTanMethod == "mobiletan" || lowerCaseMayDkTanMethod == "mtan") { + return DkTanMethod.mobileTAN } - if (lowerCaseMayZkaTanMethod == "apptan" || lowerCaseMayZkaTanMethod == "phototan") { - return ZkaTanMethod.appTAN + if (lowerCaseMayDkTanMethod == "apptan" || lowerCaseMayDkTanMethod == "phototan") { + return DkTanMethod.App } // TODO: what about these values, all returned by banks in anonymous dialog initialization: // BestSign, HHDUSB1, Secoder_UC, ZkaTANMode, photoTAN, QRTAN, 1822TAN+ - return ZkaTanMethod.valueOf(mayZkaTanMethod) + return DkTanMethod.valueOf(mayDkTanMethod) } catch (ignored: Exception) { } return null @@ -892,6 +921,14 @@ open class ResponseParser( .replace(Separators.MaskingCharacter + Separators.MaskingCharacter, Separators.MaskingCharacter) } + protected open fun parseNullableBoolean(mayBoolean: String): Boolean? { + try { + return parseBoolean(mayBoolean) + } catch (ignored: Exception) { } + + return null + } + protected open fun parseBoolean(dataElement: String): Boolean { if ("J" == dataElement) { return true diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/segments/TanMethodParameters.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/segments/TanMethodParameters.kt index 0b02fd6a..42ba60ba 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/segments/TanMethodParameters.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/segments/TanMethodParameters.kt @@ -8,11 +8,11 @@ open class TanMethodParameters( val securityFunction: Sicherheitsfunktion, val tanProcess: TanProcess, val technicalTanMethodIdentification: String, - val zkaTanMethod: ZkaTanMethod?, - val versionZkaTanMethod: String?, + val dkTanMethod: DkTanMethod?, + val versionDkTanMethod: String?, val methodName: String, - val maxTanInputLength: Int, - val allowedTanFormat: AllowedTanFormat, + val maxTanInputLength: Int?, + val allowedTanFormat: AllowedTanFormat?, val descriptionToShowToUser: String, val maxReturnValueLength: Int, val multipleTansAllowed: Boolean, @@ -25,7 +25,12 @@ open class TanMethodParameters( val initializingMode: Initialisierungsmodus, val nameOfTanMediumRequired: BezeichnungDesTanMediumsErforderlich, 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 countSupportedActiveTanMedia: Int?, + val maxNumberOfStateRequestsForDecoupled: Int? = null, + val initialDelayInSecondsForStateRequestsForDecoupled: Int? = null, + val delayInSecondsForNextStateRequestsForDecoupled: Int? = null, + val manualConfirmationAllowedForDecoupled: Boolean? = null, + val periodicStateRequestsAllowedForDecoupled: Boolean? = null ) { diff --git a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/response/ResponseParserTest.kt b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/response/ResponseParserTest.kt index 7091a8d3..34e1d31a 100644 --- a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/response/ResponseParserTest.kt +++ b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/response/ResponseParserTest.kt @@ -710,7 +710,7 @@ class ResponseParserTest : FinTsTestBase() { @Test - fun parseTanInfo() { + fun parseTanInfo_1() { // when val result = underTest.parse("HITANS:171:6:4+1+1+1+J:N:0:910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5:921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2:900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0'") @@ -734,6 +734,84 @@ class ResponseParserTest : FinTsTestBase() { ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } } + @Test + fun parseTanInfo6_2() { + + // when + val result = underTest.parse("HITANS:77:6:3+1+1+1+J:N:0:942:2:MTAN2:mobileTAN::mobile TAN:6:1:SMS:2048:J:1:N:0:2:N:J:00:0:N:1:944:2:SECUREGO:mobileTAN::SecureGo:6:1:TAN:2048:J:1:N:0:2:N:J:00:0:N:1:962:2:HHD1.4:HHD:1.4:Smart-TAN plus manuell:6:1:Challenge:2048:J:1:N:0:2:N:J:00:0:N:1:972:2:HHD1.4OPT:HHDOPT1:1.4:Smart-TAN plus optisch / USB:6:1:Challenge:2048:J:1:N:0:2:N:J:00:0:N:1:982:2:MS1.0.0:::Smart-TAN photo:6:1:Challenge:2048:J:1:N:0:2:N:J:00:0:N:1'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 77, 6, 3) + + result.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { segment -> + val tanMethodParameters = segment.tanProcedureParameters.methodParameters + expect(tanMethodParameters).hasSize(5) + + assertTanMethodParameter(tanMethodParameters, 0, Sicherheitsfunktion.PIN_TAN_942, DkTanMethod.mobileTAN, "mobile TAN") + assertTanMethodParameter(tanMethodParameters, 1, Sicherheitsfunktion.PIN_TAN_944, DkTanMethod.mobileTAN, "SecureGo") + assertTanMethodParameter(tanMethodParameters, 2, Sicherheitsfunktion.PIN_TAN_962, DkTanMethod.HHD, "Smart-TAN plus manuell") + assertTanMethodParameter(tanMethodParameters, 3, Sicherheitsfunktion.PIN_TAN_972, DkTanMethod.HHDOPT1, "Smart-TAN plus optisch / USB") + assertTanMethodParameter(tanMethodParameters, 4, Sicherheitsfunktion.PIN_TAN_982, null, "Smart-TAN photo") + } + ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } + } + + @Test + fun parseTanInfo6_3() { + + // when + val result = underTest.parse("HITANS:169:6:3+1+1+1+J:N:0:910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5:921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2:900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 169, 6, 3) + + result.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { segment -> + val tanMethodParameters = segment.tanProcedureParameters.methodParameters + expect(tanMethodParameters).hasSize(7) + + assertTanMethodParameter(tanMethodParameters, 0, Sicherheitsfunktion.PIN_TAN_910, null, "chipTAN manuell") + assertTanMethodParameter(tanMethodParameters, 1, Sicherheitsfunktion.PIN_TAN_911, DkTanMethod.HHDOPT1, "chipTAN optisch") + assertTanMethodParameter(tanMethodParameters, 2, Sicherheitsfunktion.PIN_TAN_912, null, "chipTAN-USB") // TODO: parse not specified 'HHDUSB1' to DkTanMethod? + assertTanMethodParameter(tanMethodParameters, 3, Sicherheitsfunktion.PIN_TAN_913, null, "chipTAN-QR") // TODO: parse not specified 'Secoder_UC' to DkTanMethod? + assertTanMethodParameter(tanMethodParameters, 4, Sicherheitsfunktion.PIN_TAN_920, null, "smsTAN") + assertTanMethodParameter(tanMethodParameters, 5, Sicherheitsfunktion.PIN_TAN_921, null, "pushTAN") + assertTanMethodParameter(tanMethodParameters, 6, Sicherheitsfunktion.PIN_TAN_900, null, "iTAN") + } + ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } + } + + @Test + fun parseTanInfo6_4() { + + // when + val result = underTest.parse("HITANS:54:6:3+1+1+1+N:N:0:901:2:CR#1:::SMS-TAN:6:1:SMS-TAN:256:J:2:J:0:0:N:N:01:0:N:1:904:2:CR#5 - 1.4:HHDOPT1:1.4:chipTAN comfort:6:1:chipTAN comfort:2048:N:1:N:0:0:N:N:01:0:N:1:905:2:CR#6 - 1.4:HHD:1.4:chipTAN comfort manuell:6:1:chipTAN comfort manuell:2048:N:1:N:0:0:N:N:01:0:N:0:906:2:CR#8 - MS1.0:::BV AppTAN:6:1:BV AppTAN:2048:N:1:N:0:0:N:N:01:0:N:0:907:2:MS1.0.0:::PhotoTAN:7:1:PhotoTAN:2048:N:1:N:0:0:N:J:01:0:N:0'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 54, 6, 3) + + result.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { segment -> + val tanMethodParameters = segment.tanProcedureParameters.methodParameters + expect(tanMethodParameters).hasSize(5) + + assertTanMethodParameter(tanMethodParameters, 0, Sicherheitsfunktion.PIN_TAN_901, null, "SMS-TAN") + assertTanMethodParameter(tanMethodParameters, 1, Sicherheitsfunktion.PIN_TAN_904, DkTanMethod.HHDOPT1, "chipTAN comfort") + assertTanMethodParameter(tanMethodParameters, 2, Sicherheitsfunktion.PIN_TAN_905, DkTanMethod.HHD, "chipTAN comfort manuell") + assertTanMethodParameter(tanMethodParameters, 3, Sicherheitsfunktion.PIN_TAN_906, null, "BV AppTAN") + assertTanMethodParameter(tanMethodParameters, 4, Sicherheitsfunktion.PIN_TAN_907, null, "PhotoTAN") + } + ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } + } + + private fun assertTanMethodParameter(parsedTanMethodParameters: List, index: Int, securityFunction: Sicherheitsfunktion, + tanMethod: DkTanMethod?, methodName: String) { + + val tanMethodParameters = parsedTanMethodParameters[index] + + expect(tanMethodParameters.securityFunction).toBe(securityFunction) + expect(tanMethodParameters.dkTanMethod).toBe(tanMethod) + expect(tanMethodParameters.methodName).toBe(methodName) + } + @Test fun parseTanInfo_CountSupportedActiveTanMediaIsBlank() { @@ -807,6 +885,54 @@ class ResponseParserTest : FinTsTestBase() { ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } } + @Test + fun parseTanInfo7_DecoupledpushTan() { + + // when + val result = underTest.parse("HITANS:177:7:4+1+1+1+N:N:0:922:2:pushTAN-dec:Decoupled::pushTAN 2.0:::Aufforderung:2048:J:2:N:0:0:N:N:00:2:N:2:180:1:1:J:J'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 177, 7, 4) + + result.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { segment -> + expect(segment.maxCountJobs).toBe(1) + expect(segment.minimumCountSignatures).toBe(1) + expect(segment.securityClass).toBe(1) + expect(segment.tanProcedureParameters.oneStepProcedureAllowed).isFalse() + expect(segment.tanProcedureParameters.moreThanOneTanDependentJobPerMessageAllowed).isFalse() + expect(segment.tanProcedureParameters.jobHashValue).toBe("0") + + expect(segment.tanProcedureParameters.methodParameters).hasSize(1) + + val decoupledPushTanMethod = segment.tanProcedureParameters.methodParameters.first() + + expect(decoupledPushTanMethod.securityFunction).toBe(Sicherheitsfunktion.PIN_TAN_922) + expect(decoupledPushTanMethod.tanProcess).toBe(TanProcess.TanProcess2) + expect(decoupledPushTanMethod.technicalTanMethodIdentification).toBe("pushTAN-dec") + expect(decoupledPushTanMethod.dkTanMethod).toBe(DkTanMethod.Decoupled) + expect(decoupledPushTanMethod.versionDkTanMethod).toBe(null) + expect(decoupledPushTanMethod.methodName).toBe("pushTAN 2.0") + expect(decoupledPushTanMethod.maxTanInputLength).toBe(null) + expect(decoupledPushTanMethod.allowedTanFormat).toBe(null) + + expect(decoupledPushTanMethod.descriptionToShowToUser).toBe("Aufforderung") + expect(decoupledPushTanMethod.maxReturnValueLength).toBe(2048) + expect(decoupledPushTanMethod.multipleTansAllowed).toBe(true) + expect(decoupledPushTanMethod.timeAndDialogRelation).toBe(TanZeitUndDialogbezug.TanZeitversetztDialoguebergreifendErlaubt) + expect(decoupledPushTanMethod.cancellationAllowed).toBe(false) + + expect(decoupledPushTanMethod.nameOfTanMediumRequired).toBe(BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden) + expect(decoupledPushTanMethod.countSupportedActiveTanMedia).toBe(2) + + expect(decoupledPushTanMethod.maxNumberOfStateRequestsForDecoupled).toBe(180) + expect(decoupledPushTanMethod.initialDelayInSecondsForStateRequestsForDecoupled).toBe(1) + expect(decoupledPushTanMethod.delayInSecondsForNextStateRequestsForDecoupled).toBe(1) + expect(decoupledPushTanMethod.manualConfirmationAllowedForDecoupled).toBe(true) + expect(decoupledPushTanMethod.periodicStateRequestsAllowedForDecoupled).toBe(true) + } + ?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") } + } + @Test fun parseTanResponse_NoStrongAuthenticationRequired() { diff --git a/fints4k/src/jvm6Test/kotlin/net/dankito/banking/fints/bankdetails/BanksFinTsDetailsRetriever.kt b/fints4k/src/jvm6Test/kotlin/net/dankito/banking/fints/bankdetails/BanksFinTsDetailsRetriever.kt index 26b1a123..ee8ced08 100644 --- a/fints4k/src/jvm6Test/kotlin/net/dankito/banking/fints/bankdetails/BanksFinTsDetailsRetriever.kt +++ b/fints4k/src/jvm6Test/kotlin/net/dankito/banking/fints/bankdetails/BanksFinTsDetailsRetriever.kt @@ -71,7 +71,7 @@ class BanksFinTsDetailsRetriever { private val tanMethodTypes = mutableMapOf>() private val tanMethodParameterTechnicalIdentification = mutableSetOf() - private val tanMethodParameterVersionZkaTanMethod = mutableSetOf() + private val tanMethodParameterVersionDkTanMethod = mutableSetOf() private val requiresSmsAbbuchungskonto = mutableListOf() private val requiresAuftraggeberkonto = mutableListOf() @@ -220,7 +220,7 @@ class BanksFinTsDetailsRetriever { } tanMethodParameterTechnicalIdentification.add(methodParameter.technicalTanMethodIdentification) - tanMethodParameterVersionZkaTanMethod.add(methodParameter.versionZkaTanMethod) + tanMethodParameterVersionDkTanMethod.add(methodParameter.versionDkTanMethod) if (methodParameter.smsDebitAccountRequired == SmsAbbuchungskontoErforderlich.SmsAbbuchungskontoMussAngegebenWerden) { requiresSmsAbbuchungskonto.add(bankInfo) @@ -283,11 +283,11 @@ class BanksFinTsDetailsRetriever { private fun printStatistics() { log.info("Did not receive response from Banks ${printBanks(requestNotSuccessful)}") - log.info("Mapped tanMethodTypes: ${tanMethodTypes.map { System.lineSeparator() + it.key + ": " + it.value.map { it.methodName + " " + it.zkaTanMethod + " " + it.technicalTanMethodIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") }}\n\n") - log.info("TanMethodParameters:${tanMethodParameter.map { System.lineSeparator() + it.key + ": " + it.value.map { it.securityFunction.code + " " + it.zkaTanMethod + " " + it.technicalTanMethodIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") } }\n\n") + log.info("Mapped tanMethodTypes: ${tanMethodTypes.map { System.lineSeparator() + it.key + ": " + it.value.map { it.methodName + " " + it.dkTanMethod + " " + it.technicalTanMethodIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") }}\n\n") + log.info("TanMethodParameters:${tanMethodParameter.map { System.lineSeparator() + it.key + ": " + it.value.map { it.securityFunction.code + " " + it.dkTanMethod + " " + it.technicalTanMethodIdentification + " (" + it.descriptionToShowToUser + ")" }.toSet().joinToString(", ") } }\n\n") log.info("TanMethodParameters TechnicalIdentification:${tanMethodParameterTechnicalIdentification.joinToString(", ") } \n\n") - log.info("TanMethodParameters VersionZkaTanMethod:${tanMethodParameterVersionZkaTanMethod.joinToString(", ") } \n\n") + log.info("TanMethodParameters VersionDkTanMethod:${tanMethodParameterVersionDkTanMethod.joinToString(", ") } \n\n") log.info("Requires SmsAbbuchungskonto ${printBanks(requiresSmsAbbuchungskonto)}") // no (only 2) log.info("Requires Auftraggeberkonto ${printBanks(requiresAuftraggeberkonto)}") // yes, a lot of (12631) diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/tan/AllowedTanFormat.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/tan/AllowedTanFormat.kt index 7c6c8e65..f54f5eef 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/tan/AllowedTanFormat.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/tan/AllowedTanFormat.kt @@ -5,6 +5,8 @@ enum class AllowedTanFormat { Numeric, - Alphanumeric + Alphanumeric, + + TanIsEnteredOnOtherDevice } \ No newline at end of file diff --git a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt index 4f6078b4..84bc90d6 100644 --- a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt +++ b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt @@ -283,10 +283,11 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) { } } - open fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat): AllowedTanFormat { + open fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat { return when (allowedTanFormat) { net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Alphanumeric -> AllowedTanFormat.Alphanumeric net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Numeric -> AllowedTanFormat.Numeric + null -> AllowedTanFormat.TanIsEnteredOnOtherDevice } } @@ -420,10 +421,11 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) { } } - open fun mapAllowedTanFormat(allowedTanFormat: AllowedTanFormat): net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat { + open fun mapAllowedTanFormat(allowedTanFormat: AllowedTanFormat): net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat? { return when (allowedTanFormat) { AllowedTanFormat.Alphanumeric -> net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Alphanumeric AllowedTanFormat.Numeric -> net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Numeric + AllowedTanFormat.TanIsEnteredOnOtherDevice -> null } }