Implemented parsing PinInfo and setting HKTAN only if required according to PinInfo

This commit is contained in:
dankito 2020-05-13 20:29:53 +02:00
parent de91056094
commit 1859fb2575
8 changed files with 110 additions and 14 deletions

View File

@ -746,6 +746,10 @@ open class FinTsClient @JvmOverloads constructor(
// 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.supportedTanProcedures = mapToTanProcedures(tanInfo)
}

View File

@ -105,10 +105,9 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(generator.resetSegmentNumber(2), parameter, account)
else KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), parameter, account)
val segments = listOf(
transactionsJob,
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.AccountTransactionsMt940)
)
val segments = mutableListOf<Segment>(transactionsJob)
addTanSegmentIfRequired(CustomerSegmentId.AccountTransactionsMt940, dialogContext, segments)
return createMessageBuilderResult(dialogContext, segments)
}
@ -133,10 +132,9 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
val balanceJob = if (result.isAllowed(5)) SaldenabfrageVersion5(generator.resetSegmentNumber(2), account)
else SaldenabfrageVersion7(generator.resetSegmentNumber(2), account, dialogContext.bank)
val segments = listOf(
balanceJob,
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Balance)
)
val segments = mutableListOf<Segment>(balanceJob)
addTanSegmentIfRequired(CustomerSegmentId.Balance, dialogContext, segments)
return createMessageBuilderResult(dialogContext, segments)
}
@ -208,10 +206,10 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
val urn = messageBuilderResultAndNullableUrn.second
if (result.isJobVersionSupported && urn != null) {
val segments = listOf(
SepaBankTransferBase(segmentId, generator.resetSegmentNumber(2), urn, dialogContext.customer, account, dialogContext.bank.bic, data),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentId)
)
val segments = mutableListOf<Segment>(SepaBankTransferBase(segmentId, generator.resetSegmentNumber(2),
urn, dialogContext.customer, account, dialogContext.bank.bic, data))
addTanSegmentIfRequired(segmentId, dialogContext, segments)
return createMessageBuilderResult(dialogContext, segments)
}
@ -401,6 +399,18 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return MessageBuilderResult(false)
}
protected open fun addTanSegmentIfRequired(segmentId: CustomerSegmentId, dialogContext: DialogContext, segments: MutableList<Segment>) {
if (isTanRequiredForJob(segmentId, dialogContext)) {
segments.add(ZweiSchrittTanEinreichung(
generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentId))
}
}
protected open fun isTanRequiredForJob(segmentId: CustomerSegmentId, dialogContext: DialogContext): Boolean {
return dialogContext.bank.pinInfo?.jobTanConfiguration?.first { it.segmentId == segmentId.id }?.tanRequired
?: false // TODO: actually in this case it's not allowed to execute job via PIN/TAN at all
}
protected open fun getSepaUrnFor(segmentId: CustomerSegmentId, account: AccountData, sepaDataFormat: String): String? {
return getAllowedJobs(segmentId, account)

View File

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
open class JobTanConfiguration(
val segmentId: String,
val tanRequired: Boolean
) {
override fun toString(): String {
return "$segmentId requires TAN? $tanRequired"
}
}

View File

@ -5,6 +5,7 @@ import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.HbciVersion
import net.dankito.fints.response.segments.ChangeTanMediaParameters
import net.dankito.fints.response.segments.JobParameters
import net.dankito.fints.response.segments.PinInfo
open class BankData(
@ -24,6 +25,7 @@ open class BankData(
var supportedHbciVersions: List<HbciVersion> = listOf(),
var supportedTanProcedures: List<TanProcedure> = listOf(),
var changeTanMediumParameters: ChangeTanMediaParameters? = null,
var pinInfo: PinInfo? = null,
var supportedLanguages: List<Dialogsprache> = listOf(),
var supportedJobs: List<JobParameters> = listOf()
) {

View File

@ -25,6 +25,8 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId {
SepaAccountInfoParameters("HISPAS"),
PinInfo("HIPINS"),
TanInfo("HITANS"),
Tan("HITAN"),

View File

@ -89,6 +89,7 @@ open class ResponseParser @JvmOverloads constructor(
InstituteSegmentId.SepaAccountInfo.id -> parseSepaAccountInfo(segment, dataElementGroups)
InstituteSegmentId.SepaAccountInfoParameters.id -> parseSepaAccountInfoParameters(segment, segmentId, dataElementGroups)
InstituteSegmentId.PinInfo.id -> parsePinInfo(segment, segmentId, dataElementGroups)
InstituteSegmentId.TanInfo.id -> parseTanInfo(segment, segmentId, dataElementGroups)
InstituteSegmentId.Tan.id -> parseTanResponse(segment, dataElementGroups)
InstituteSegmentId.TanMediaList.id -> parseTanMediaList(segment, dataElementGroups)
@ -304,6 +305,31 @@ open class ResponseParser @JvmOverloads constructor(
}
protected open fun parsePinInfo(segment: String, segmentId: String, dataElementGroups: List<String>): PinInfo {
val jobParameters = parseJobParameters(segment, segmentId, dataElementGroups)
val dataElements = getDataElements(dataElementGroups[4])
val minPinLength = parseIntToNullIfEmpty(dataElements[0])
val maxPinLength = parseIntToNullIfEmpty(dataElements[1])
val minTanLength = parseIntToNullIfEmpty(dataElements[2])
val userIdHint = parseStringToNullIfEmpty(dataElements[3])
val customerIdHint = parseStringToNullIfEmpty(dataElements[4])
return PinInfo(jobParameters, minPinLength, maxPinLength, minTanLength, userIdHint, customerIdHint,
parseJobTanConfigurations(dataElements.subList(5, dataElements.size)))
}
protected open fun parseJobTanConfigurations(dataElementGroups: List<String>): List<JobTanConfiguration> {
return dataElementGroups.chunked(2).map {
JobTanConfiguration(
parseString(it[0]),
parseBoolean(it[1])
)
}
}
protected open fun parseTanInfo(segment: String, segmentId: String, dataElementGroups: List<String>): TanInfo? {
val jobParameters = parseJobParameters(segment, segmentId, dataElementGroups)

View File

@ -0,0 +1,15 @@
package net.dankito.fints.response.segments
import net.dankito.fints.messages.datenelemente.implementierte.tan.JobTanConfiguration
open class PinInfo(
parameters: JobParameters,
val minPinLength: Int?,
val maxPinLength: Int?,
val minTanLength: Int?,
val userIdHint: String?,
val customerIdHint: String?,
val jobTanConfiguration: List<JobTanConfiguration>
)
: JobParameters(parameters)

View File

@ -660,6 +660,30 @@ class ResponseParserTest : FinTsTestBase() {
}
@Test
fun parsePinInfo() {
// when
val result = underTest.parse("HIPINS:49:1:4+1+1+0+5:20:6:VR-NetKey oder Alias::HKTAN:N:HKKAZ:J:HKSAL:N:HKEKA:N:HKPAE:J:HKPSP:N:HKQTG:N:HKCSA:J:HKCSB:N:HKCSL:J:HKCSE:J:HKCCS:J:HKSPA:N:HKCCM:J:HKCDB:N:HKCDL:J:HKPPD:J:HKCDN:J:HKDSB:N:HKCUB:N:HKCUM:J:HKCDE:J:HKDSW:J:HKCME:J:HKCMB:N:HKCML:J:HKWPD:N:HKWDU:N:HKKAU:N:HKKIF:N:HKCAZ:J:HKKAA:N:HKPOF:N:HKIPS:N:HKIPZ:J:GKVPU:N:GKVPD:N'")
// then
assertSuccessfullyParsedSegment(result, InstituteSegmentId.PinInfo, 49, 1, 4)
result.getFirstSegmentById<PinInfo>(InstituteSegmentId.PinInfo)?.let { segment ->
assertThat(segment.minPinLength).isEqualTo(5)
assertThat(segment.maxPinLength).isEqualTo(20)
assertThat(segment.minTanLength).isEqualTo(6)
assertThat(segment.userIdHint).isEqualTo("VR-NetKey oder Alias")
assertThat(segment.customerIdHint).isNull()
assertThat(segment.jobTanConfiguration).hasSize(37)
assertThat(segment.jobTanConfiguration.first { it.segmentId == "HKKAZ" }.tanRequired).isTrue()
assertThat(segment.jobTanConfiguration.first { it.segmentId == "HKSAL" }.tanRequired).isFalse()
}
?: run { Assert.fail("No segment of type PinInfo found in ${result.receivedSegments}") }
}
@Test
fun parseTanInfo() {
@ -936,7 +960,7 @@ class ResponseParserTest : FinTsTestBase() {
assertThat(segment.accountProductName).isEqualTo(accountProductName)
assertThat(segment.balanceOfPreBookedTransactions).isNull()
}
?: run { Assert.fail("No segment of type Balance found in ${result.receivedSegments}") }
?: run { Assert.fail("No segment of type BalanceSegment found in ${result.receivedSegments}") }
}
@Test
@ -963,7 +987,7 @@ class ResponseParserTest : FinTsTestBase() {
assertThat(segment.accountProductName).isEqualTo(accountProductName)
assertThat(segment.balanceOfPreBookedTransactions).isNull()
}
?: run { Assert.fail("No segment of type Balance found in ${result.receivedSegments}") }
?: run { Assert.fail("No segment of type BalanceSegment found in ${result.receivedSegments}") }
}