Implemented changing TAN medium (HKTAU)

This commit is contained in:
dankl 2019-12-30 22:30:53 +01:00 committed by dankito
parent 11f115936b
commit cb557812c4
27 changed files with 623 additions and 10 deletions

View File

@ -12,7 +12,9 @@ import net.dankito.banking.fints4java.android.ui.MainWindowPresenter
import net.dankito.banking.fints4java.android.ui.dialogs.AddAccountDialog import net.dankito.banking.fints4java.android.ui.dialogs.AddAccountDialog
import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog
import net.dankito.fints.FinTsClientCallback import net.dankito.fints.FinTsClientCallback
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.CustomerData
import net.dankito.fints.model.EnterTanGeneratorAtcResult
import net.dankito.fints.model.TanChallenge import net.dankito.fints.model.TanChallenge
import net.dankito.fints.model.TanProcedure import net.dankito.fints.model.TanProcedure
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -34,6 +36,10 @@ class MainActivity : AppCompatActivity() {
return getTanFromUserOffUiThread(customer, tanChallenge) return getTanFromUserOffUiThread(customer, tanChallenge)
} }
override fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? {
return null
}
}) })

View File

@ -6,6 +6,7 @@ import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.fints.model.* import net.dankito.fints.model.*
@ -345,6 +346,61 @@ open class FinTsClient @JvmOverloads constructor(
} }
open fun changeTanMediumAsync(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData,
callback: (FinTsClientResponse) -> Unit) {
threadPool.runAsync {
callback(changeTanMedium(newActiveTanMedium, bank, customer))
}
}
open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData): FinTsClientResponse {
val lastCreatedMessage = messageBuilder.lastCreatedMessage
// lastCreatedMessage?.let { closeDialog(bank, customer, ) } // TODO: close previous dialog
var enteredAtc: EnterTanGeneratorAtcResult? = null
if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) {
enteredAtc = callback.enterTanGeneratorAtc(customer, newActiveTanMedium)
if (enteredAtc == null) {
return FinTsClientResponse(Response(false, exception =
Exception("Bank requires to enter ATC and TAN in order to change TAN medium."))) // TODO: translate
}
}
val dialogData = DialogData()
val initDialogResponse = initDialog(bank, customer, dialogData)
if (initDialogResponse.successful == false) {
return FinTsClientResponse(initDialogResponse)
}
dialogData.increaseMessageNumber()
val message = messageBuilder.createChangeTanMediumMessage(newActiveTanMedium, bank, customer, dialogData,
enteredAtc?.tan, enteredAtc?.atc)
val response = getAndHandleResponseForMessage(message, bank)
closeDialog(bank, customer, dialogData)
lastCreatedMessage?.let {
resendMessageInNewDialogAsync(lastCreatedMessage, bank, customer)
}
return FinTsClientResponse(response)
}
open fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, open fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData,
customer: CustomerData, callback: (FinTsClientResponse) -> Unit) { customer: CustomerData, callback: (FinTsClientResponse) -> Unit) {
@ -377,6 +433,37 @@ open class FinTsClient @JvmOverloads constructor(
} }
protected open fun resendMessageInNewDialogAsync(message: MessageBuilderResult, bank: BankData,
customer: CustomerData) {
threadPool.runAsync {
resendMessageInNewDialog(message, bank, customer)
}
}
protected open fun resendMessageInNewDialog(message: MessageBuilderResult, bank: BankData,
customer: CustomerData): FinTsClientResponse {
log.info("Resending message ${message.messageBodySegments.map { it.dataElementsAndGroups.firstOrNull()?.format() }} in a new dialog") // TODO: remove again
val dialogData = DialogData()
val initDialogResponse = initDialog(bank, customer, dialogData)
if (initDialogResponse.successful == false) {
return FinTsClientResponse(initDialogResponse)
}
val newMessage = messageBuilder.rebuildMessage(message, bank, customer, dialogData)
val response = getAndHandleResponseForMessageThatMayRequiresTan(newMessage, bank, customer, dialogData)
closeDialog(bank, customer, dialogData)
return FinTsClientResponse(response)
}
protected open fun initDialog(bank: BankData, customer: CustomerData, dialogData: DialogData): Response { protected open fun initDialog(bank: BankData, customer: CustomerData, dialogData: DialogData): Response {
// we first need to retrieve supported tan procedures and jobs before we can do anything // we first need to retrieve supported tan procedures and jobs before we can do anything
@ -596,6 +683,13 @@ open class FinTsClient @JvmOverloads constructor(
} }
} }
// TODO: check if response contains '3931 TAN-Generator gesperrt, Synchronisierung erforderlich' or
// '3933 TAN-Generator gesperrt, Synchronisierung erforderlich Kartennummer ##########' message,
// call callback.enterAtc() and implement and call HKTSY job (p. 77)
// TODO: also check '9931 Sperrung des Kontos nach %1 Fehlversuchen' -> if %1 == 3 synchronize TAN generator
// as it's quite unrealistic that user entered TAN wrong three times, in most cases TAN generator is not synchronized
return response return response
} }
@ -639,6 +733,10 @@ open class FinTsClient @JvmOverloads constructor(
} }
} }
response.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters ->
bank.changeTanMediumParameters = parameters
}
if (response.supportedJobs.isNotEmpty()) { if (response.supportedJobs.isNotEmpty()) {
bank.supportedJobs = response.supportedJobs bank.supportedJobs = response.supportedJobs
} }

View File

@ -1,6 +1,8 @@
package net.dankito.fints package net.dankito.fints
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.CustomerData
import net.dankito.fints.model.EnterTanGeneratorAtcResult
import net.dankito.fints.model.TanChallenge import net.dankito.fints.model.TanChallenge
import net.dankito.fints.model.TanProcedure import net.dankito.fints.model.TanProcedure
@ -11,4 +13,9 @@ interface FinTsClientCallback {
fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): String? fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): String?
/**
* This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator.
*/
fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult?
} }

View File

@ -1,6 +1,7 @@
package net.dankito.fints package net.dankito.fints
import net.dankito.fints.messages.MessageBuilder import net.dankito.fints.messages.MessageBuilder
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.* import net.dankito.fints.model.*
import net.dankito.fints.response.ResponseParser import net.dankito.fints.response.ResponseParser
import net.dankito.fints.response.client.AddAccountResponse import net.dankito.fints.response.client.AddAccountResponse
@ -40,4 +41,8 @@ open class FinTsClientForCustomer(
client.doBankTransferAsync(bankTransferData, bank, customer, callback) client.doBankTransferAsync(bankTransferData, bank, customer, callback)
} }
open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, callback: (FinTsClientResponse) -> Unit) {
client.changeTanMediumAsync(newActiveTanMedium, bank, customer, callback)
}
} }

View File

@ -3,6 +3,7 @@ package net.dankito.fints.messages
import net.dankito.fints.extensions.containsAny import net.dankito.fints.extensions.containsAny
import net.dankito.fints.messages.datenelemente.implementierte.Aufsetzpunkt import net.dankito.fints.messages.datenelemente.implementierte.Aufsetzpunkt
import net.dankito.fints.messages.datenelemente.implementierte.Synchronisierungsmodus import net.dankito.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess
@ -14,6 +15,7 @@ import net.dankito.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.fints.messages.segmente.implementierte.* import net.dankito.fints.messages.segmente.implementierte.*
import net.dankito.fints.messages.segmente.implementierte.sepa.SepaEinzelueberweisung import net.dankito.fints.messages.segmente.implementierte.sepa.SepaEinzelueberweisung
import net.dankito.fints.messages.segmente.implementierte.tan.TanGeneratorListeAnzeigen import net.dankito.fints.messages.segmente.implementierte.tan.TanGeneratorListeAnzeigen
import net.dankito.fints.messages.segmente.implementierte.tan.TanGeneratorTanMediumAnOderUmmelden
import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version5 import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version5
import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version6 import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version6
import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version7 import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version7
@ -40,6 +42,10 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
var lastCreatedMessage: MessageBuilderResult? = null
protected set
/** /**
* Um Kunden die Möglichkeit zu geben, sich anonym anzumelden, um sich bspw. über die * Um Kunden die Möglichkeit zu geben, sich anonym anzumelden, um sich bspw. über die
* angebotenen Geschäftsvorfälle fremder Kreditinstitute (von denen sie keine BPD besitzen) * angebotenen Geschäftsvorfälle fremder Kreditinstitute (von denen sie keine BPD besitzen)
@ -157,6 +163,23 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return result return result
} }
open fun createChangeTanMediumMessage(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData,
dialogData: DialogData, tan: String? = null, atc: Int? = null): MessageBuilderResult {
val result = getSupportedVersionsOfJob(CustomerSegmentId.ChangeTanMedium, customer, listOf(1, 2, 3))
if (result.isJobVersionSupported) {
val segments = listOf(
TanGeneratorTanMediumAnOderUmmelden(result.getHighestAllowedVersion!!, generator.resetSegmentNumber(2),
bank, customer, newActiveTanMedium, tan, atc)
)
return createMessageBuilderResult(bank, customer, dialogData, segments)
}
return result
}
open fun createSendEnteredTanMessage(enteredTan: String, tanResponse: TanResponse, bank: BankData, customer: CustomerData, dialogData: DialogData): String { open fun createSendEnteredTanMessage(enteredTan: String, tanResponse: TanResponse, bank: BankData, customer: CustomerData, dialogData: DialogData): String {
val tanProcess = if (tanResponse.tanProcess == TanProcess.TanProcess1) TanProcess.TanProcess1 else TanProcess.TanProcess2 val tanProcess = if (tanResponse.tanProcess == TanProcess.TanProcess1) TanProcess.TanProcess1 else TanProcess.TanProcess2
@ -203,13 +226,23 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
aufsetzpunkte.forEach { it.resetContinuationId(continuationId) } aufsetzpunkte.forEach { it.resetContinuationId(continuationId) }
return rebuildMessage(message, bank, customer, dialogData)
}
open fun rebuildMessage(message: MessageBuilderResult, bank: BankData, customer: CustomerData,
dialogData: DialogData): MessageBuilderResult {
dialogData.increaseMessageNumber() dialogData.increaseMessageNumber()
return createMessageBuilderResult(bank, customer, dialogData, message.messageBodySegments) return createMessageBuilderResult(bank, customer, dialogData, message.messageBodySegments)
} }
protected open fun createMessageBuilderResult(bank: BankData, customer: CustomerData, dialogData: DialogData, segments: List<Segment>): MessageBuilderResult { protected open fun createMessageBuilderResult(bank: BankData, customer: CustomerData, dialogData: DialogData, segments: List<Segment>): MessageBuilderResult {
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, segments), segments) val message = MessageBuilderResult(createSignedMessage(bank, customer, dialogData, segments), segments)
lastCreatedMessage = message
return message
} }

View File

@ -2,14 +2,20 @@ package net.dankito.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.fints.messages.Existenzstatus import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
/** /**
* Es sind nur die jeweils aufgeführten Werte zulässig. * Es sind nur die jeweils aufgeführten Werte zulässig.
*/ */
abstract class Code(code: String?, val allowedValues: List<String>, existenzstatus: Existenzstatus) open class Code(code: String?, val allowedValues: List<String>, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(code, existenzstatus) { : AlphanumerischesDatenelement(code, existenzstatus) {
constructor(code: ICodeEnum?, allowedValues: List<String>, existenzstatus: Existenzstatus)
: this(code?.code, allowedValues, existenzstatus)
override fun validate() { override fun validate() {
super.validate() super.validate()

View File

@ -6,7 +6,7 @@ import net.dankito.fints.messages.Existenzstatus
/** /**
* Es gilt der FinTS-Basiszeichensatz ohne die Zeichen CR und LF. * Es gilt der FinTS-Basiszeichensatz ohne die Zeichen CR und LF.
*/ */
abstract class AlphanumerischesDatenelement @JvmOverloads constructor( open class AlphanumerischesDatenelement @JvmOverloads constructor(
alphanumericValue: String?, existenzstatus: Existenzstatus, val maxLength: Int? = null alphanumericValue: String?, existenzstatus: Existenzstatus, val maxLength: Int? = null
) : TextDatenelement(alphanumericValue, existenzstatus) { ) : TextDatenelement(alphanumericValue, existenzstatus) {

View File

@ -6,7 +6,7 @@ import net.dankito.fints.messages.Existenzstatus
/** /**
* Zulässig sind lediglich die Ziffern 0 bis 9. Führende Nullen sind nicht zugelassen. * Zulässig sind lediglich die Ziffern 0 bis 9. Führende Nullen sind nicht zugelassen.
*/ */
abstract class NumerischesDatenelement(val number: Int?, val numberOfDigits: Int, existenzstatus: Existenzstatus) open class NumerischesDatenelement(val number: Int?, val numberOfDigits: Int, existenzstatus: Existenzstatus)
: TextDatenelement(number?.toString(), existenzstatus) { : TextDatenelement(number?.toString(), existenzstatus) {

View File

@ -0,0 +1,10 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.TextDatenelement
/**
* A dummy data element for conditional data elements building to tell formatter not to print this data element
*/
open class DoNotPrintDatenelement : TextDatenelement("", Existenzstatus.NotAllowed)

View File

@ -8,7 +8,7 @@ class TanGeneratorTanMedium(
status: TanMediumStatus, status: TanMediumStatus,
val cardNumber: String, val cardNumber: String,
val followUpCardNumber: String?, val followUpCardNumber: String?,
val cardType: String?, val cardType: Int?,
val validFrom: Date?, val validFrom: Date?,
val validTo: Date?, val validTo: Date?,
val mediaName: String? val mediaName: String?

View File

@ -5,6 +5,9 @@ import net.dankito.fints.messages.datenelemente.implementierte.account.KontoDepo
import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
import net.dankito.fints.model.AccountData
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
/** /**
@ -26,4 +29,12 @@ open class Kontoverbindung(
KontoDepotnummer(accountNumber, Existenzstatus.Mandatory), KontoDepotnummer(accountNumber, Existenzstatus.Mandatory),
Unterkontomerkmal(subAccountAttribute ?: "", Existenzstatus.Optional), Unterkontomerkmal(subAccountAttribute ?: "", Existenzstatus.Optional),
Kreditinstitutskennung(bankCountryCode, bankCode) Kreditinstitutskennung(bankCountryCode, bankCode)
), Existenzstatus.Mandatory) ), Existenzstatus.Mandatory) {
constructor(bank: BankData, customer: CustomerData, account: AccountData?)
: this(bank, customer, account?.subAccountAttribute)
constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?)
: this(bank.countryCode, bank.bankCode, customer.customerId, subAccountAttribute)
}

View File

@ -7,6 +7,7 @@ import net.dankito.fints.messages.datenelemente.implementierte.account.KontoDepo
import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
import net.dankito.fints.model.AccountData
import net.dankito.fints.model.BankData import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.CustomerData
@ -32,6 +33,9 @@ open class KontoverbindungInternational(
Kreditinstitutskennung(bankCountryCode ?: 0, bankCode ?: "", if (bankCountryCode != null && bankCode != null) Existenzstatus.Optional else Existenzstatus.NotAllowed) Kreditinstitutskennung(bankCountryCode ?: 0, bankCode ?: "", if (bankCountryCode != null && bankCode != null) Existenzstatus.Optional else Existenzstatus.NotAllowed)
), Existenzstatus.Mandatory) { ), Existenzstatus.Mandatory) {
constructor(bank: BankData, customer: CustomerData, account: AccountData?)
: this(bank, customer, account?.subAccountAttribute)
constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?) constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?)
: this(customer.iban, bank.bic, bank.countryCode, bank.bankCode, customer.customerId, subAccountAttribute) : this(customer.iban, bank.bic, bank.countryCode, bank.bankCode, customer.customerId, subAccountAttribute)
} }

View File

@ -3,6 +3,7 @@ package net.dankito.fints.messages.segmente
import net.dankito.fints.messages.Nachrichtenteil import net.dankito.fints.messages.Nachrichtenteil
import net.dankito.fints.messages.Separators import net.dankito.fints.messages.Separators
import net.dankito.fints.messages.datenelemente.DatenelementBase import net.dankito.fints.messages.datenelemente.DatenelementBase
import net.dankito.fints.messages.datenelemente.implementierte.DoNotPrintDatenelement
import java.util.regex.Pattern import java.util.regex.Pattern
@ -15,7 +16,7 @@ abstract class Segment(val dataElementsAndGroups: List<DatenelementBase>) : Nach
override fun format(): String { override fun format(): String {
val formattedSegment = dataElementsAndGroups.joinToString(Separators.DataElementGroupsSeparator) { it.format() } val formattedSegment = dataElementsAndGroups.filter { it is DoNotPrintDatenelement == false }.joinToString(Separators.DataElementGroupsSeparator) { it.format() }
return cutEmptyDataElementGroupsAtEndOfSegment(formattedSegment) return cutEmptyDataElementGroupsAtEndOfSegment(formattedSegment)
} }

View File

@ -15,6 +15,8 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
TanMediaList("HKTAB"), TanMediaList("HKTAB"),
ChangeTanMedium("HKTAU"),
Balance("HKSAL"), Balance("HKSAL"),
AccountTransactionsMt940("HKKAZ"), AccountTransactionsMt940("HKKAZ"),

View File

@ -27,7 +27,7 @@ open class TanGeneratorListeAnzeigen(
if (supportedMediaClasses.contains(tanMediumClass) == false) { if (supportedMediaClasses.contains(tanMediumClass) == false) {
throw UnsupportedOperationException("Value $tanMediumClass for TAN medium class is not valid for HKTAB version $segmentVersion. " + throw UnsupportedOperationException("Value $tanMediumClass for TAN medium class is not valid for HKTAB version $segmentVersion. " +
"Supported values are: " + TanMediumKlasse.values().filter { it.supportedHkTabVersions.contains(segmentVersion) }.map { it.code }) "Supported values are: " + supportedMediaClasses)
} }
} }

View File

@ -0,0 +1,86 @@
package net.dankito.fints.messages.segmente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.DoNotPrintDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.NotAllowedDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.fints.messages.datenelementgruppen.implementierte.account.KontoverbindungInternational
import net.dankito.fints.messages.segmente.Segment
import net.dankito.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
import net.dankito.fints.response.segments.ChangeTanMediaParameters
/**
* The actual job is called "TAN-Medium an- bzw. ummelden", but as TAN lists aren't supported anymore I've implemented
* it only for TAN Generators.
*
* Mit Hilfe dieses Geschäftsvorfalls kann der Kunde seinem Institut mitteilen, welches Medium (Chipkarte,
* TAN-Generator oder bilateral vereinbart) er für die Autorisierung der Aufträge per TAN verwenden wird.
*
* Welches Medium gerade aktiv ist, kann mit Hilfe des Geschäftsvorfalls TAN-Medium anzeigen Bestand (HKTAB) bzw.
* für Detailinformationen zur Karte auch Kartenanzeige anfordern (HKAZK) durch den Kunden erfragt werden.
*
* Der Kunde entscheidet selbst, welches seiner verfügbaren TAN-Medien er verwenden möchte.
*
* chipTAN-Verfahren:
* Steht beim chipTAN-Verfahren ein Kartenwechsel an, so kann der Kunde mit diesem Geschäftsvorfall seine Karte bzw.
* Folgekarte aktivieren. Kann der Kunde mehrere Karten verwenden, dann kann mit diesem GV die Ummeldung auf eine
* andere Karte erfolgen. Das Kreditinstitut entscheidet selbst, ob dieser GV TAN-pflichtig istoder nicht.
*/
open class TanGeneratorTanMediumAnOderUmmelden(
segmentVersion: Int,
segmentNumber: Int,
bank: BankData,
customer: CustomerData,
newActiveTanMedium: TanGeneratorTanMedium,
/**
* Has to be set if Eingabe von ATC und TAN erforderlich (BPD)=J
*/
tan: String? = null,
/**
* Has to be set if Eingabe von ATC und TAN erforderlich (BPD)=J
*/
atc: Int? = null,
/**
* An optional field and only used in version 3
*/
iccsn: String? = null,
parameters: ChangeTanMediaParameters = bank.changeTanMediumParameters!!
)
: Segment(listOf(
Segmentkopf(CustomerSegmentId.ChangeTanMedium, segmentVersion, segmentNumber),
Code(TanMediumKlasse.TanGenerator, allCodes<TanMediumKlasse>(), Existenzstatus.Mandatory),
AlphanumerischesDatenelement(newActiveTanMedium.cardNumber, Existenzstatus.Mandatory),
AlphanumerischesDatenelement(newActiveTanMedium.followUpCardNumber, if (parameters.enteringFollowUpCardNumberRequired) Existenzstatus.Mandatory else Existenzstatus.NotAllowed),
if (segmentVersion > 1) NumerischesDatenelement(newActiveTanMedium.cardType, 2, if (parameters.enteringCardTypeAllowed) Existenzstatus.Optional else Existenzstatus.NotAllowed) else DoNotPrintDatenelement(),
if (segmentVersion == 2) Kontoverbindung(bank, customer, customer.accounts.firstOrNull()) else DoNotPrintDatenelement(),
if (segmentVersion >= 3 && parameters.accountInfoRequired) KontoverbindungInternational(bank, customer, customer.accounts.firstOrNull()) else DoNotPrintDatenelement(),
if (segmentVersion >= 2) Datum(newActiveTanMedium.validFrom, Existenzstatus.Optional) else DoNotPrintDatenelement(),
if (segmentVersion >= 2) Datum(newActiveTanMedium.validTo, Existenzstatus.Optional) else DoNotPrintDatenelement(),
if (segmentVersion >= 3) AlphanumerischesDatenelement(iccsn, Existenzstatus.Optional, 19) else DoNotPrintDatenelement(),
NotAllowedDatenelement(), // TAN-Listennummer not supported anymore
NumerischesDatenelement(atc, 5, if (parameters.enteringAtcAndTanRequired) Existenzstatus.Mandatory else Existenzstatus.NotAllowed),
AlphanumerischesDatenelement(tan, if (parameters.enteringAtcAndTanRequired) Existenzstatus.Mandatory else Existenzstatus.NotAllowed, 99)
)) {
init {
if (parameters.enteringAtcAndTanRequired) {
if (atc == null || tan == null) {
throw UnsupportedOperationException("As „Eingabe von ATC und TAN erforderlich“ is set to \"J\" " +
"(ChangeTanMediaParameters.enteringAtcAndTanRequired is set to true) parameters atc and tan have to be set.")
}
}
}
}

View File

@ -3,6 +3,7 @@ package net.dankito.fints.model
import net.dankito.fints.messages.datenelemente.implementierte.BPDVersion import net.dankito.fints.messages.datenelemente.implementierte.BPDVersion
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.HbciVersion 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.JobParameters
@ -22,6 +23,7 @@ open class BankData(
var supportedHbciVersions: List<HbciVersion> = listOf(), var supportedHbciVersions: List<HbciVersion> = listOf(),
var supportedTanProcedures: List<TanProcedure> = listOf(), var supportedTanProcedures: List<TanProcedure> = listOf(),
var changeTanMediumParameters: ChangeTanMediaParameters? = null,
var supportedLanguages: List<Dialogsprache> = listOf(), var supportedLanguages: List<Dialogsprache> = listOf(),
var supportedJobs: List<JobParameters> = listOf() var supportedJobs: List<JobParameters> = listOf()
) { ) {

View File

@ -0,0 +1,13 @@
package net.dankito.fints.model
open class EnterTanGeneratorAtcResult(
val tan: String,
val atc: Int
) {
override fun toString(): String {
return "TAN: $tan, ATC: $atc"
}
}

View File

@ -31,6 +31,8 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId {
TanMediaList("HITAB"), TanMediaList("HITAB"),
ChangeTanMediaParameters("HITAUS"), // there's no response data segment for HKTAU -> HITAU does not exist
Balance("HISAL"), Balance("HISAL"),
AccountTransactionsMt940("HIKAZ") AccountTransactionsMt940("HIKAZ")

View File

@ -8,7 +8,7 @@ import net.dankito.fints.messages.segmente.id.MessageSegmentId
import net.dankito.fints.response.segments.* import net.dankito.fints.response.segments.*
open class Response constructor( open class Response(
val didReceiveResponse: Boolean, val didReceiveResponse: Boolean,
val receivedResponse: String? = null, val receivedResponse: String? = null,
val receivedSegments: List<ReceivedSegment> = listOf(), val receivedSegments: List<ReceivedSegment> = listOf(),

View File

@ -94,6 +94,7 @@ open class ResponseParser @JvmOverloads constructor(
InstituteSegmentId.TanInfo.id -> parseTanInfo(segment, segmentId, dataElementGroups) InstituteSegmentId.TanInfo.id -> parseTanInfo(segment, segmentId, dataElementGroups)
InstituteSegmentId.Tan.id -> parseTanResponse(segment, dataElementGroups) InstituteSegmentId.Tan.id -> parseTanResponse(segment, dataElementGroups)
InstituteSegmentId.TanMediaList.id -> parseTanMediaList(segment, dataElementGroups) InstituteSegmentId.TanMediaList.id -> parseTanMediaList(segment, dataElementGroups)
InstituteSegmentId.ChangeTanMediaParameters.id -> parseChangeTanMediaParameters(segment, segmentId, dataElementGroups)
InstituteSegmentId.Balance.id -> parseBalanceSegment(segment, dataElementGroups) InstituteSegmentId.Balance.id -> parseBalanceSegment(segment, dataElementGroups)
InstituteSegmentId.AccountTransactionsMt940.id -> parseMt940AccountTransactions(segment, dataElementGroups) InstituteSegmentId.AccountTransactionsMt940.id -> parseMt940AccountTransactions(segment, dataElementGroups)
@ -432,7 +433,7 @@ open class ResponseParser @JvmOverloads constructor(
protected open fun parseTanGeneratorTanMedium(mediumClass: TanMediumKlasse, status: TanMediumStatus, protected open fun parseTanGeneratorTanMedium(mediumClass: TanMediumKlasse, status: TanMediumStatus,
hitabVersion: Int, dataElements: List<String>): TanGeneratorTanMedium { hitabVersion: Int, dataElements: List<String>): TanGeneratorTanMedium {
val cardType = if (hitabVersion < 2) null else parseStringToNullIfEmpty(dataElements[2]) // TODO: may parse to number val cardType = if (hitabVersion < 2) null else parseNullableInt(dataElements[2])
// TODO: may also parse account info // TODO: may also parse account info
val validFrom = if (hitabVersion < 2) null else parseNullableDate(dataElements[8]) val validFrom = if (hitabVersion < 2) null else parseNullableDate(dataElements[8])
val validTo = if (hitabVersion < 2) null else parseNullableDate(dataElements[9]) val validTo = if (hitabVersion < 2) null else parseNullableDate(dataElements[9])
@ -443,6 +444,28 @@ open class ResponseParser @JvmOverloads constructor(
} }
protected open fun parseChangeTanMediaParameters(segment: String, segmentId: String, dataElementGroups: List<String>): ChangeTanMediaParameters {
val jobParameters = parseJobParameters(segment, segmentId, dataElementGroups)
val hiTausSegmentVersion = parseInt(getDataElements(dataElementGroups[0])[2])
val changeTanGeneratorParameters = getDataElements(dataElementGroups[4])
val enteringCardTypeRequired = if (hiTausSegmentVersion < 2) false else parseBoolean(changeTanGeneratorParameters[3])
val accountInfoRequired = if (hiTausSegmentVersion < 3) false else parseBoolean(changeTanGeneratorParameters[4])
val remainingParameters = when (hiTausSegmentVersion) {
1 -> listOf()
2 -> changeTanGeneratorParameters.subList(4, changeTanGeneratorParameters.size)
else -> changeTanGeneratorParameters.subList(5, changeTanGeneratorParameters.size)
}
val allowedCardTypes = remainingParameters.map { parseInt(it) }
return ChangeTanMediaParameters(jobParameters, parseBoolean(changeTanGeneratorParameters[0]),
parseBoolean(changeTanGeneratorParameters[1]), parseBoolean(changeTanGeneratorParameters[2]),
enteringCardTypeRequired, accountInfoRequired, allowedCardTypes)
}
protected open fun parseBalanceSegment(segment: String, dataElementGroups: List<String>): BalanceSegment { protected open fun parseBalanceSegment(segment: String, dataElementGroups: List<String>): BalanceSegment {
// dataElementGroups[1] is account details // dataElementGroups[1] is account details

View File

@ -0,0 +1,13 @@
package net.dankito.fints.response.segments
open class ChangeTanMediaParameters(
parameters: JobParameters,
val enteringTanListNumberRequired: Boolean,
val enteringFollowUpCardNumberRequired: Boolean,
val enteringAtcAndTanRequired: Boolean,
val enteringCardTypeAllowed: Boolean,
val accountInfoRequired: Boolean,
val allowedCardTypes: List<Int>
)
: JobParameters(parameters)

View File

@ -4,6 +4,7 @@ import net.dankito.fints.FinTsClient;
import net.dankito.fints.FinTsClientCallback; import net.dankito.fints.FinTsClientCallback;
import net.dankito.fints.banks.BankFinder; import net.dankito.fints.banks.BankFinder;
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion; import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion;
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium;
import net.dankito.fints.model.*; import net.dankito.fints.model.*;
import net.dankito.fints.model.mapper.BankDataMapper; import net.dankito.fints.model.mapper.BankDataMapper;
import net.dankito.fints.response.client.GetTransactionsResponse; import net.dankito.fints.response.client.GetTransactionsResponse;
@ -45,6 +46,11 @@ public class JavaShowcase {
return null; return null;
} }
@Nullable
@Override
public EnterTanGeneratorAtcResult enterTanGeneratorAtc(@NotNull CustomerData customer, @NotNull TanGeneratorTanMedium tanMedium) {
return null;
}
}; };
FinTsClient finTsClient = new FinTsClient(callback, new Java8Base64Service()); FinTsClient finTsClient = new FinTsClient(callback, new Java8Base64Service());

View File

@ -6,6 +6,7 @@ import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatus import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatus
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanEinsatzOption import net.dankito.fints.messages.datenelemente.implementierte.tan.TanEinsatzOption
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.fints.model.* import net.dankito.fints.model.*
@ -13,6 +14,7 @@ import net.dankito.fints.model.mapper.BankDataMapper
import net.dankito.fints.response.client.FinTsClientResponse import net.dankito.fints.response.client.FinTsClientResponse
import net.dankito.fints.util.Java8Base64Service import net.dankito.fints.util.Java8Base64Service
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -41,6 +43,12 @@ class FinTsClientTest {
return null return null
} }
override fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? {
Assert.fail("Bank asks you to synchronize your TAN generator for card ${tanMedium.cardNumber} " +
"(follow-up number ${tanMedium.followUpCardNumber}). Please do this via your online banking portal or Banking UI.")
return null // should actually never be called
}
} }

View File

@ -5,6 +5,8 @@ import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Laenderkennze
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.dankito.fints.model.* import net.dankito.fints.model.*
import net.dankito.fints.response.segments.ChangeTanMediaParameters
import net.dankito.fints.response.segments.JobParameters
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*
@ -45,6 +47,11 @@ abstract class FinTsTestBase {
const val Date = 19880327 const val Date = 19880327
const val Time = 182752 const val Time = 182752
init {
Bank.changeTanMediumParameters = ChangeTanMediaParameters(JobParameters("", 1, 1, 1, ":0:0"), false, false, false, false, false, listOf())
}
} }
@ -68,4 +75,9 @@ abstract class FinTsTestBase {
return message.replace(0.toChar(), ' ') return message.replace(0.toChar(), ' ')
} }
protected open fun createEmptyJobParameters(): JobParameters {
return JobParameters("", 1, 1, 1, ":0:0")
}
} }

View File

@ -0,0 +1,191 @@
package net.dankito.fints.messages.segmente.implementierte.tan
import net.dankito.fints.FinTsTestBase
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMediumStatus
import net.dankito.fints.response.segments.ChangeTanMediaParameters
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class TanGeneratorTanMediumAnOderUmmeldenTest: FinTsTestBase() {
companion object {
private const val TAN = "123456"
private const val ATC = 12345
private const val CardNumber = "9876543210"
private const val FollowUpCardNumber = "02"
private const val CardType = 11
private const val SegmentNumber = 3
private val NewActiveTanMedium = TanGeneratorTanMedium(TanMediumKlasse.TanGenerator, TanMediumStatus.Verfuegbar, CardNumber, FollowUpCardNumber, CardType, null, null, "EC-Card")
}
@Test
fun format_Version1_AtcNotRequired_FollowUpCardNumberNotRequired() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, false, false, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(1, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:1+G+$CardNumber")
}
@Test
fun format_Version1_AtcRequired_FollowUpCardNumberNotRequired() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, false, true, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(1, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:1+G+$CardNumber+++$ATC+$TAN")
}
@Test
fun format_Version1_AtcNotRequired_FollowUpCardNumberRequired() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, true, false, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(1, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:1+G+$CardNumber+$FollowUpCardNumber")
}
@Test
fun format_Version1_AtcRequired_FollowUpCardNumberRequired() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, true, true, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(1, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:1+G+$CardNumber+$FollowUpCardNumber++$ATC+$TAN")
}
@Test
fun format_Version2_AtcNotRequired_FollowUpCardNumberNotRequired_CardTypeNotAllowed() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, false, false, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(2, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:2+G+$CardNumber+++$CustomerId::$BankCountryCode:$BankCode")
}
@Test
fun format_Version2_AtcRequired_FollowUpCardNumberNotRequired_CardTypeNotAllowed() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, false, true, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(2, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:2+G+$CardNumber+++$CustomerId::$BankCountryCode:$BankCode++++$ATC+$TAN")
}
@Test
fun format_Version2_AtcNotRequired_FollowUpCardNumberRequired_CardTypeNotAllowed() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, true, false, false, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(2, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:2+G+$CardNumber+$FollowUpCardNumber++$CustomerId::$BankCountryCode:$BankCode")
}
@Test
fun format_Version2_AtcNotRequired_FollowUpCardNumberNotRequired_CardTypeAllowed() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, false, false, true, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(2, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:2+G+$CardNumber++$CardType+$CustomerId::$BankCountryCode:$BankCode")
}
@Test
fun format_Version2_AtcRequired_FollowUpCardNumberRequired_CardTypeAllowed() {
// given
val parameters = ChangeTanMediaParameters(createEmptyJobParameters(), false, true, true, true, false, listOf())
val underTest = TanGeneratorTanMediumAnOderUmmelden(2, SegmentNumber, Bank, Customer, NewActiveTanMedium, TAN, ATC, null, parameters)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKTAU:$SegmentNumber:2+G+$CardNumber+$FollowUpCardNumber+$CardType+$CustomerId::$BankCountryCode:$BankCode++++$ATC+$TAN")
}
// TODO: may also test 'gueltig ab' and 'gueltig bis'
// TODO: also test Version3
}

View File

@ -710,6 +710,80 @@ class ResponseParserTest : FinTsTestBase() {
} }
@Test
fun parseChangeTanMediaParametersVersion1() {
// when
val result = underTest.parse("HITAUS:64:1:3+1+1+1+J:N:J:'")
// then
assertSuccessfullyParsedSegment(result, InstituteSegmentId.ChangeTanMediaParameters, 64, 1, 3)
result.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { segment ->
assertThat(segment.maxCountJobs).isEqualTo(1)
assertThat(segment.minimumCountSignatures).isEqualTo(1)
assertThat(segment.securityClass).isEqualTo(1)
assertThat(segment.enteringTanListNumberRequired).isTrue()
assertThat(segment.enteringFollowUpCardNumberRequired).isFalse()
assertThat(segment.enteringAtcAndTanRequired).isTrue()
assertThat(segment.enteringCardTypeAllowed).isFalse()
assertThat(segment.accountInfoRequired).isFalse()
assertThat(segment.allowedCardTypes).isEmpty()
}
?: run { Assert.fail("No segment of type ChangeTanMediaParameters found in ${result.receivedSegments}") }
}
@Test
fun parseChangeTanMediaParametersVersion2() {
// when
val result = underTest.parse("HITAUS:64:2:3+1+1+1+N:J:N:J:11:13:15:17'")
// then
assertSuccessfullyParsedSegment(result, InstituteSegmentId.ChangeTanMediaParameters, 64, 2, 3)
result.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { segment ->
assertThat(segment.maxCountJobs).isEqualTo(1)
assertThat(segment.minimumCountSignatures).isEqualTo(1)
assertThat(segment.securityClass).isEqualTo(1)
assertThat(segment.enteringTanListNumberRequired).isFalse()
assertThat(segment.enteringFollowUpCardNumberRequired).isTrue()
assertThat(segment.enteringAtcAndTanRequired).isFalse()
assertThat(segment.enteringCardTypeAllowed).isTrue()
assertThat(segment.accountInfoRequired).isFalse()
assertThat(segment.allowedCardTypes).containsExactlyInAnyOrder(11, 13, 15, 17)
}
?: run { Assert.fail("No segment of type ChangeTanMediaParameters found in ${result.receivedSegments}") }
}
@Test
fun parseChangeTanMediaParametersVersion3() {
// when
val result = underTest.parse("HITAUS:64:3:3+1+1+1+N:J:N:J:J:11:13:15:17'")
// then
assertSuccessfullyParsedSegment(result, InstituteSegmentId.ChangeTanMediaParameters, 64, 3, 3)
result.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { segment ->
assertThat(segment.maxCountJobs).isEqualTo(1)
assertThat(segment.minimumCountSignatures).isEqualTo(1)
assertThat(segment.securityClass).isEqualTo(1)
assertThat(segment.enteringTanListNumberRequired).isFalse()
assertThat(segment.enteringFollowUpCardNumberRequired).isTrue()
assertThat(segment.enteringAtcAndTanRequired).isFalse()
assertThat(segment.enteringCardTypeAllowed).isTrue()
assertThat(segment.accountInfoRequired).isTrue()
assertThat(segment.allowedCardTypes).containsExactlyInAnyOrder(11, 13, 15, 17)
}
?: run { Assert.fail("No segment of type ChangeTanMediaParameters found in ${result.receivedSegments}") }
}
@Test @Test
fun parseBalance() { fun parseBalance() {