diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt index ade09db7..bcac4dd1 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt @@ -2,6 +2,7 @@ package net.dankito.fints.messages import net.dankito.fints.messages.datenelemente.implementierte.* import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion +import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess import net.dankito.fints.messages.nachrichten.Nachricht import net.dankito.fints.messages.segmente.ISegmentNumberGenerator import net.dankito.fints.messages.segmente.Segment @@ -39,8 +40,12 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg productVersion: String ): String { - return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt, - BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion, false, false) + val customerId = KundenID.Anonymous + + return createMessage(false, false, bankCountryCode, bankCode, customerId, listOf( + IdentifikationsSegment(generator.resetSegmentNumber(1), bankCountryCode, bankCode, customerId, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt), + Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion) + )) } open fun createDialogInitMessage( @@ -53,14 +58,13 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg updVersion: Int, language: Dialogsprache, productName: String, - productVersion: String, - signMessage: Boolean = true, - encryptMessage: Boolean = true + productVersion: String ): String { - return createMessage(signMessage, encryptMessage, bankCountryCode, bankCode, customerId, listOf( - IdentifikationsSegment(generator.resetSegmentNumber(if (signMessage) 2 else 1), bankCountryCode, bankCode, customerId, customerSystemId, status), - Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bpdVersion, updVersion, language, productName, productVersion) + return createMessage(true, true, bankCountryCode, bankCode, customerId, listOf( + IdentifikationsSegment(generator.resetSegmentNumber(2), bankCountryCode, bankCode, customerId, customerSystemId, status), + Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bpdVersion, updVersion, language, productName, productVersion), + ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, "HKIDN") )) } diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/basisformate/BinaerDatenelement.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/basisformate/BinaerDatenelement.kt index 695dc61c..3773820d 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/basisformate/BinaerDatenelement.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/basisformate/BinaerDatenelement.kt @@ -25,7 +25,11 @@ open class BinaerDatenelement @JvmOverloads constructor(val data: ByteArray, exi * Spezifikation vorzusehen. */ override fun format(): String { - return "@${data.size}@" + String(data) + if (data.size > 0) { + return "@${data.size}@" + String(data) + } + + return "" } override fun validate() { diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/signatur/SicherheitsfunktionWhatIsThisShit.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/signatur/SicherheitsfunktionWhatIsThisShit.kt new file mode 100644 index 00000000..ea356229 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/signatur/SicherheitsfunktionWhatIsThisShit.kt @@ -0,0 +1,29 @@ +package net.dankito.fints.messages.datenelemente.implementierte.signatur + + +/** + * Ab FinTS 3.0 existieren beim RAH-Verfahren drei Schlüssel (DS-Schlüssel für Non-Repudiation, + * Signierschlüssel für Authentication und Chiffrierschlüssel für Verschlüsselung) und somit + * auch drei Sicherheitsfunktionen (Sicherheitsfunktion 1 bei Verwendung des DS-Schlüssels, + * Sicherheitsfunktion 2 bei Verwendung des Signierschlüssels und Sicherheitsfunktion 4 bei + * Verwendung des Chiffrierschlüssels) beim RAH-Verfahren. + * + * Die Sicherheitsfunktion hat ab FinTS 3.0 lediglich informatorischen Wert, da die eigentliche + * Steuerung über die Sicherheitsprofile und –Klassen erfolgt. + * + * Kodierte Information über die Sicherheitsfunktion, die auf die Nachricht angewendet wird. + * + * Codierung: + * - 1: Non-Repudiation of Origin (NRO) + * - 2: Message Origin Authentication (AUT) + * - 4: Encryption, Verschlüsselung und evtl. Komprimierung (ENC) + */ +enum class SicherheitsfunktionWhatIsThisShit(val code: String) { + + NonRepudiationOfOrigin("1"), + + MessageOriginAuthentication("2"), + + Encryption("4") + +} \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/AuftragsHashwert.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/AuftragsHashwert.kt new file mode 100644 index 00000000..63cede7a --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/AuftragsHashwert.kt @@ -0,0 +1,30 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.basisformate.BinaerDatenelement + + +/** + * Er enthält im Falle des Zwei-Schritt-TAN-Verfahrens bei TAN-Prozess=1 den Hashwert über die + * Daten eines Kundenauftrags (z. B. „HKCCS“). Dieser wird z. B. im Rahmen des Geschäftsvorfalls + * HKTAN vom Kunden übermittelt und vom Kreditinstitut in der Antwortnachricht HITAN gespiegelt. + * + * Das vom Institut verwendete Auftrags-Hashwertverfahren wird in der BPD übermittelt. In der + * vorliegenden Version wird RIPEMD-160 verwendet. + * + * In die Berechnung des Auftrags-Hashwerts geht der Bereich vom ersten bit des Segmentkopfes + * bis zum letzten bit des Trennzeichens ein. + * + * RIPEMD-160 + * + * Der Hash-Algorithmus RIPEMD-160 bildet Eingabe-Bitfolgen beliebiger Länge auf einen als + * Bytefolge dargestellten Hash-Wert von 20 Byte (160 Bit) Länge ab. Teil des Hash-Algorithmus + * ist das Padding von Eingabe-Bitfolgen auf ein Vielfaches von 64 Byte. Das Padding erfolgt + * auch dann, wenn die Eingabe-Bitfolge bereits eine Länge hat, die ein Vielfaches von 64 Byte ist. + * RIPEMD-160 verarbeitet die Eingabe-Bitfolgen in Blöcken von 64 Byte Länge. + * + * Als Initialisierungsvektor dient die binäre Zeichenfolge + * X’01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10 F0 E1 D2 C3’. + */ +open class AuftragsHashwert(hash: ByteArray, existenzstatus: Existenzstatus) + : BinaerDatenelement(hash, existenzstatus, 256) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/Auftragsreferenz.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/Auftragsreferenz.kt new file mode 100644 index 00000000..d01de8d4 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/Auftragsreferenz.kt @@ -0,0 +1,17 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement + + +/** + * Enthält im Falle des Zwei-Schritt-TAN-Verfahrens die Referenz auf einen eingereichten Auftrag. + * Die Auftragsreferenz wird bei der späteren Einreichung der zugehörigen TANs (mittels HKTAN bei + * TAN-Prozess=2 bzw. 3) zur Referenzierung des Auftrags verwendet. + * + * Da die Auftragsreferenz immer eindeutig ist, sollten Kundenprodukte diese als zentrale + * Referenzierung verwenden und dem Kunden auch zusammen mit den Auftragsdaten präsentieren bzw. + * für die Problemverfolgung leicht zugänglich machen. + */ +open class Auftragsreferenz(reference: String, existenzstatus: Existenzstatus) + : AlphanumerischesDatenelement(reference, existenzstatus, 35) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/BezeichnungDesTANMediums.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/BezeichnungDesTANMediums.kt new file mode 100644 index 00000000..128876df --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/BezeichnungDesTANMediums.kt @@ -0,0 +1,13 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement + + +/** + * Symbolischer Name für ein TAN-Medium wie z. B. TAN-Generator oder Mobiltelefon. Diese Bezeichnung + * kann in Verwaltungs-Geschäftsvorfällen benutzt werden, wenn z. B. die Angabe der echten Handynummer + * aus Datenschutzgründen nicht möglich ist oder auch um die Benutzerfreundlichkeit zu erhöhen. + */ +open class BezeichnungDesTANMediums(identifier: String, existenzstatus: Existenzstatus) + : AlphanumerischesDatenelement(identifier, existenzstatus, 32) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeHHD_UC.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeHHD_UC.kt new file mode 100644 index 00000000..6312d083 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeHHD_UC.kt @@ -0,0 +1,16 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.basisformate.BinaerDatenelement + + +/** + * Bei Verwendung von Zwei-Schritt-Verfahren mit unidirektionaler Kopplung (vgl. hierzu [HHD_UC]) + * müssen zusätzlich zum Datenelement „Challenge“ die Daten für die Übertragung z. B. über eine + * optische Schnittstelle bereitgestellt werden. Die einzelnen Datenelemente der „Challenge HHD_UC“ + * sind in [HHD_UC] beschrieben und werden hier im FinTS Data Dictionary nicht näher erläutert. + * Da HHD_UC einen anderen Basiszeichensatz verwendet (ISO 646) wird die HHD_UC-Struktur als binär + * definiert. Als maximale Länge kann ein Wert von 128 angenommen werden. + */ +open class ChallengeHHD_UC(challenge: ByteArray, existenzstatus: Existenzstatus) + : BinaerDatenelement(challenge, existenzstatus, 128) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeVersion3.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeVersion3.kt new file mode 100644 index 00000000..8b8b562d --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/ChallengeVersion3.kt @@ -0,0 +1,27 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement + + +/** + * Dieses Datenelement enthält im Falle des Zwei-Schritt-TAN-Verfahrens die Challenge zu einem + * eingereichten Auftrag. Aus der Challenge wird vom Kunden die eigentliche TAN ermittelt. + * Die Challenge wird unabhängig vom Prozessvariante 1 oder 2 in der Kreditinstitutsantwort im + * Segment HITAN übermittelt. + * + * Ist der BPD-Parameter „Challenge strukturiert“ mit „J“ belegt, so können im Text folgende + * Formatsteuerzeichen enthalten sein, die kundenseitig entsprechend zu interpretieren sind. + * Eine Kaskadierung von Steuerzeichen ist nicht erlaubt. + * + *
Zeilenumbruch + *

Neuer Absatz + * ... Fettdruck + * ... Kursivdruck + * ... Unterstreichen + *

Beginn / Ende Aufzählung + *
    ...
Beginn / Ende Nummerierte Liste + *
  • ...
  • Listenelement einer Aufzählung / Nummerierten Liste + */ +open class ChallengeVersion3(challenge: String, existenzstatus: Existenzstatus) + : AlphanumerischesDatenelement(challenge, existenzstatus, 2048) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TANProzessDatenelement.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TANProzessDatenelement.kt new file mode 100644 index 00000000..3110667a --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TANProzessDatenelement.kt @@ -0,0 +1,57 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code +import net.dankito.fints.messages.datenelemente.implementierte.allCodes + + +/** + * Beim Zwei-Schritt-Verfahren werden die notwendigen Prozess-Schritte mittels des Geschäftsvorfalls + * HKTAN durchgeführt. Dieser unterstützt flexibel vier unterschiedliche Ausprägungen für die beiden + * Prozessvarianten für Zwei-Schritt-Verfahren, wobei die TAN-Prozesse 3 und 4 nicht isoliert und nur + * in Verbindung mit TAN-Prozess=2 auftreten können. Der TAN-Prozess wird wie folgt kodiert: + * + * Codierung: + * + * Prozessvariante 1: + * + * TAN-Prozess=1: + * Im ersten Schritt wird der Auftrags-Hashwert über den Geschäftsvorfall HKTAN mitgeteilt, im + * zweiten Schritt erfolgt nach Ermittlung der TAN aus der zurückgemeldeten Challenge die + * Einreichung des eigentlichen Auftrags inklusive der TAN über das normale Auftragssegment. + * Abfolge der Segmente am Beispiel HKCCS: + * 1. Schritt: HKTAN <-> HITAN + * 2. Schritt: HKCCS <-> HIRMS zu HKCCS + * + * Prozessvariante 2: + * Im ersten Schritt wird der Auftrag komplett über das normale Auftragssegment eingereicht, + * jedoch ohne Übermittlung der TAN. Im zweiten Schritt erfolgt nach Ermittlung der TAN aus + * der zurückgemeldeten Challenge die Einreichung der TAN über den Geschäftsvorfall HKTAN. + * Abfolge der Segmente am Beispiel HKCCS: + * Schritt 1: HKCCS und HKTAN  HITAN + * Schritt 2: HKTAN  HITAN und HIRMS zu HICCS + * + * TAN-Prozess=2: + * kann nur im zweiten Schritt auftreten. Er dient zur Übermittlung der TAN mittels HKTAN, + * nachdem der Auftrag selbst zuvor bereits mit TAN-Prozess=3 oder 4 eingereicht wurde. + * Dieser Geschäftsvorfall wird mit HITAN, TAN-Prozess=2 beantwortet. + * + * TAN-Prozess=3: + * kann nur im ersten Schritt bei Mehrfach-TANs für die zweite und ggf. dritte TAN auftreten. + * Hierdurch wird die Einreichung eingeleitet, wenn zeitversetzte Einreichung von + * Mehrfach-TANs erlaubt ist. + * + * TAN-Prozess=4: + * kann nur im ersten Schritt auftreten. Hiermit wird das Zwei-Schritt-Verfahren nach + * Prozessvariante 2 für die erste TAN eingeleitet. HKTAN wird zusammen mit dem Auftragssegment + * übertragen und durch HITAN mit TAN-Prozess=4 beantwortet. TAN-Prozess=4 wird auch beim + * Geschäftsvorfall „Prüfen / Verbrennen von TANs“ eingesetzt. + */ +open class TANProzessDatenelement(process: TanProcess) : Code(process.code, + AllowedValues, Existenzstatus.Mandatory) { + + companion object { + val AllowedValues = allCodes() + } + +} \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanProcess.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanProcess.kt new file mode 100644 index 00000000..41fa9173 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanProcess.kt @@ -0,0 +1,16 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + +import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum + + +enum class TanProcess(override val code: String) : ICodeEnum { + + TanProcess1("1"), + + TanProcess2("2"), + + TanProcess3("3"), + + TanProcess4("4"), + +} \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelementgruppen/implementierte/tan/GueltigkeitsdatumUndUhrzeitFuerChallenge.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelementgruppen/implementierte/tan/GueltigkeitsdatumUndUhrzeitFuerChallenge.kt new file mode 100644 index 00000000..88beeb8f --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelementgruppen/implementierte/tan/GueltigkeitsdatumUndUhrzeitFuerChallenge.kt @@ -0,0 +1,17 @@ +package net.dankito.fints.messages.datenelementgruppen.implementierte.tan + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Datum +import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Uhrzeit +import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe + + +/** + * Datum und Uhrzeit, bis zu welchem Zeitpunkt eine TAN auf Basis der gesendeten Challenge + * gültig ist. Nach Ablauf der Gültigkeitsdauer wird die entsprechende TAN entwertet. + */ +open class GueltigkeitsdatumUndUhrzeitFuerChallenge(date: Int, time: Int) + : Datenelementgruppe(listOf( + Datum(date, Existenzstatus.Mandatory), + Uhrzeit(time, Existenzstatus.Mandatory) +), Existenzstatus.Optional) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt new file mode 100644 index 00000000..e5aea161 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/ZweiSchrittTanEinreichung.kt @@ -0,0 +1,29 @@ +package net.dankito.fints.messages.segmente.implementierte + +import net.dankito.fints.messages.Existenzstatus +import net.dankito.fints.messages.datenelemente.implementierte.NotAllowedDatenelement +import net.dankito.fints.messages.datenelemente.implementierte.tan.* +import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf +import net.dankito.fints.messages.segmente.Segment + + +open class ZweiSchrittTanEinreichung( + segmentNumber: Int, + process: TanProcess, + jobReference: String, + challenge: String = "", + challgendHHD_UC: ByteArray = byteArrayOf(), + date: Int? = null, + time: Int? = null, + tanMediaIdentifier: String? = "N" // TODO: why 'N'? + +) : Segment(listOf( + Segmentkopf("HKTAN", 6, segmentNumber), + TANProzessDatenelement(process), +// AuftragsHashwert(), // 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 + ChallengeVersion3(challenge, Existenzstatus.Mandatory), // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2 + ChallengeHHD_UC(challgendHHD_UC, Existenzstatus.Optional), + NotAllowedDatenelement(), // GueltigkeitsdatumUndUhrzeitFuerChallenge // TODO: how to not write an element if it's optional and its paramters (date and time) are not set? + BezeichnungDesTANMediums(tanMediaIdentifier ?: "", Existenzstatus.Optional)// M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst +), Existenzstatus.Mandatory) \ No newline at end of file diff --git a/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/MessageBuilderTest.kt b/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/MessageBuilderTest.kt index 2fbe0895..485dcb68 100644 --- a/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/MessageBuilderTest.kt +++ b/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/MessageBuilderTest.kt @@ -78,13 +78,14 @@ class MessageBuilderTest { // then assertThat(normalizeBinaryData(result)).isEqualTo(normalizeBinaryData( - "HNHBK:1:3+000000000363+300+0+1'" + + "HNHBK:1:3+000000000386+300+0+1'" + "HNVSK:998:3+PIN:2+998+1+1::0+1:$Date:$Time+2:16:14:@8@ :5:1+280:$BankCode:$CustomerId:V:0:0+0+'" + - "HNVSD:999:1+@198@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" + + "HNVSD:999:1+@221@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" + "HKIDN:3:2+280:$BankCode+$CustomerId+0+1'" + "HKVVB:4:3+0+0+${Language.code}+$ProductName+$ProductVersion'" + - "HNSHA:5:2+$ControlReference++$Pin''" + - "HNHBS:6:1+1'" + "HKTAN:5:6+4+HKIDN++++N'" + + "HNSHA:6:2+$ControlReference++$Pin''" + + "HNHBS:7:1+1'" )) }