Implemented DialoginitialisierungAnonym (to start an anonymous communication with bank)

This commit is contained in:
dankl 2019-10-03 23:06:53 +02:00 committed by dankito
parent 46585d6fec
commit f1e6ed1350
57 changed files with 1248 additions and 1 deletions

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages
enum class Existenzstatus {
Mandatory,
Optional,
NotAllowed
}

View File

@ -0,0 +1,27 @@
package net.dankito.fints.messages
/**
* Der HBCI-Basiszeichensatz baut auf dem international normierten Zeichensatz ISO 8859 auf.
* Im DE Unterstützte Sprachen in die Bankparameterdaten (s. Kap. D.2) stellt das Kreditinstitut
* das jeweiligen Codeset des ISO 8859 ein. Ferner wird in die BPD das sprachen-spezifische Subset
* des ISO 8859 eingestellt. Codeset und Subset definieren gemeinsam den FinTS-Basiszeichensatz.
* Dieser gilt grundsätzlich für sämtliche nicht-binären Datenelemente. Sofern hiervon aufgrund
* von Verarbeitungsrestriktionen abgewichen wird, ist dies bei der jeweiligen Formatbeschreibung
* vermerkt. Für transparente Daten gilt der jeweilige Zeichensatz des Fremdformats.
*
* Kreditinstitutsseitig ist jeweils der vollständige erlaubte Zeichensatz zu unterstützen.
* FinTS-Syntaxzeichen (s. Kap. H.1.1) bleiben von den Zeichensatzvorgaben unberührt (d. h.
* sind stets erforderlich und mit fester Codierung vorgegeben).
*
* Wird ein Auftrag an ein Kreditinstitut übermittelt, der hinsichtlich Zeichensatz und Codierung
* nicht den Richtlinien entspricht, so ist dieser abzuweisen. Eine kreditinstitutsseitige Korrektur
* der Auftragsdaten erfolgt nicht.
*/
class HbciCharset {
companion object {
val DefaultCharset = Charsets.ISO_8859_1
}
}

View File

@ -0,0 +1,8 @@
package net.dankito.fints.messages
abstract class Nachrichtenteil {
abstract fun format(): String
}

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.datenelemente
import net.dankito.fints.messages.Existenzstatus
abstract class Datenelement(existenzstatus: Existenzstatus): DatenelementBase(existenzstatus) {
@Throws(IllegalArgumentException::class)
abstract fun validate()
@Throws(IllegalArgumentException::class)
protected fun throwValidationException(message: String) {
throw IllegalArgumentException("Daten von ${javaClass.simpleName} sind ungültig: $message")
}
}

View File

@ -0,0 +1,7 @@
package net.dankito.fints.messages.datenelemente
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.Nachrichtenteil
abstract class DatenelementBase(val existenzstatus: Existenzstatus) : Nachrichtenteil()

View File

@ -0,0 +1,23 @@
package net.dankito.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Es sind nur die jeweils aufgeführten Werte zulässig.
*/
abstract class Code(code: String, val allowedValues: List<String>, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(code, existenzstatus) {
override fun validate() {
super.validate()
if (allowedValues.contains(text) == false) {
throwValidationException("'$text' ist kein Wert aus der Liste der zulässigen Werte: " +
allowedValues.joinToString(", ")
)
}
}
}

View File

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Dient der eindeutigen Kennzeichnung von Objekten (z. B. Benutzerkennung, Kontonummer).
*
* Maximal 30 Zeichen
*/
abstract class Identifikation(identifikation: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identifikation, existenzstatus, 30)

View File

@ -0,0 +1,30 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
/**
* Es gilt der FinTS-Basiszeichensatz ohne die Zeichen CR und LF.
*/
abstract class AlphanumerischesDatenelement @JvmOverloads constructor(
alphanumericValue: String, existenzstatus: Existenzstatus, val maxLength: Int? = null
) : TextDatenelement(alphanumericValue, existenzstatus) {
override fun validate() {
super.validate()
if (text.contains("\r") || text.contains("\n")) {
throwValidationException("Alphanumerischer Wert '$text' darf kein Carriage Return (\r) oder " +
"Line Feed (\n) enthalten.")
}
maxLength?.let {
if (text.length > maxLength) {
throwValidationException("Wert '$text' darf maximal $maxLength Zeichen lang sein, " +
"hat aber ${text.length} Zeichen.")
}
}
}
}

View File

@ -0,0 +1,27 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.Datenelement
/**
* Zulässig sind lediglich die Ziffern 0 bis 9. Führende Nullen sind nicht zugelassen.
*/
abstract class NumerischesDatenelement(val value: Int, val numberOfDigits: Int, existenzstatus: Existenzstatus)
: Datenelement(existenzstatus) {
override fun format(): String {
return value.toString()
}
override fun validate() {
val maxValue = Math.pow(10.0, numberOfDigits.toDouble()) - 1
if (value < 0 || value > maxValue) {
throwValidationException("Wert '$value' muss im Wertebereich von 0 - $maxValue liegen.")
}
}
}

View File

@ -0,0 +1,36 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.HbciCharset
import net.dankito.fints.messages.datenelemente.Datenelement
/**
* Es gilt der vollständige FinTS-Basiszeichensatz.
*/
abstract class TextDatenelement(val text: String, existenzstatus: Existenzstatus) : Datenelement(existenzstatus) {
override fun format(): String {
return text
}
override fun validate() {
try {
if (HbciCharset.DefaultCharset.newEncoder().canEncode(text) == false) {
throwInvalidCharacterException()
}
} catch (e: Exception) {
throwInvalidCharacterException()
}
}
protected open fun throwInvalidCharacterException() {
throwValidationException(
"Wert '$text' enthält Zeichen die gemäß des Zeichensatzes " +
"${HbciCharset.DefaultCharset.displayName()} nicht erlaubt sind."
)
}
}

View File

@ -0,0 +1,17 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
/**
* Zulässig sind die Ziffern 0 bis 9. Führende Nullen sind zugelassen.
*/
abstract class ZiffernDatenelement(value: Int, numberOfDigits: Int, existenzstatus: Existenzstatus)
: NumerischesDatenelement(value, numberOfDigits, existenzstatus) {
override fun format(): String {
return String.format("%0${numberOfDigits}d", value)
}
}

View File

@ -0,0 +1,24 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Es handelt sich um eine kreditinstitutsseitig vergebene Versionsnummer der
* Bankparameterdaten (BPD), die den jeweiligen Stand der instituts-spezifischen
* Unterstützung des Systems kennzeichnet (bei jeder für das Kundensystem relevanten
* Änderung des Kreditinstitutssystems werden neue BPD mit einer neuen BPD-Versionsnummer
* kreditinstitutsseitig bereitgestellt).
*
* Diese BPD-Versionsnummer ist unabhängig von der Version des BPD-Nachrichtenformats,
* die im Nachrichtenkopf eingestellt ist und lediglich das syntaktische Format der
* Nachricht, nicht jedoch deren Inhalt kennzeichnet.
*/
class BPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
companion object {
const val VersionNotReceivedYet = 0
}
}

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Sofern sich ein Kreditinstitutssegment auf ein bestimmtes Kundensegment bezieht
* (z. B. Antwortrückmeldung auf einen Kundenauftrag) hat das Kreditinstitut die
* Segmentnummer des Segments der Kundennachricht einzustellen, auf das sich das
* aktuelle Segment bezieht (s. DE Segmentnummer). In Zusammenhang mit den Angaben
* zur Bezugsnachricht aus dem Nachrichtenkopf ist hierdurch eine eindeutige Referenz
* auf das Segment einer Kundennachricht möglich.
*
* Falls die Angabe eines Bezugssegments erforderlich ist, ist dieses bei der
* Formatbeschreibung eines Kreditinstitutsegments angegeben.
*/
abstract class Bezugssegment(segmentNumber: Int, existenzstatus: Existenzstatus)
: NumerischesDatenelement(segmentNumber, 3, existenzstatus) {
override fun validate() {
super.validate()
if (value < 1) {
throwValidationException("Bezug Segmentnummer '$value' muss größer oder gleich 1 sein.")
}
}
}

View File

@ -0,0 +1,6 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
open class BezugssegmentInKreditinstitutsnachricht(segmentNumber: Int) : Bezugssegment(segmentNumber, Existenzstatus.Optional)

View File

@ -0,0 +1,6 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
open class BezugssegmentInKundennachricht(segmentNumber: Int) : Bezugssegment(segmentNumber, Existenzstatus.NotAllowed)

View File

@ -0,0 +1,7 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
class DialogId(dialogId: String) : Identifikation(dialogId, Existenzstatus.Mandatory)

View File

@ -0,0 +1,14 @@
package net.dankito.fints.messages.datenelemente.implementierte
enum class Dialogsprache(val code: String) {
Default("0"),
German("1"),
English("2"),
French("3")
}

View File

@ -0,0 +1,35 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
/**
* Über dieses DE spezifiziert der Kunde die Sprache, in der er im laufenden Dialog
* mit dem Kreditinstitut kommunizieren möchte. Rückmeldungen und Kreditinstitutsmeldungen
* werden (soweit kreditinstitutsseitig unterstützt) in der zuvor spezifizierten Sprache
* an den Kunden übermittelt. Damit verbunden wird ein zugehöriger FinTS-Basiszeichensatz
* (s. Kap. B.1), der sich durch einen ISO 8859-Codeset und einen ISO 8859-Subset definiert,
* ausgewählt. Die Definition des Subsets ist den Anlagen (Kap. I.3) zu entnehmen. Der
* Codeset soll ermöglichen, zu einem späteren Zeitpunkt evtl. auch nicht-lateinische
* Zeichensätze zuzulassen.
*
* Codierung:
* - 0: Standard
* - 1: Deutsch, Code de (German), Subset Deutsch, Codeset 1 (Latin 1)
* - 2: Englisch, Code en (English), Subset Englisch, Codeset 1 (Latin 1)
* - 3: Französisch, Code fr (French), Subset Französisch, Codeset 1 (Latin 1)
*/
class DialogspracheDatenelement(language: Dialogsprache, existenzstatus: Existenzstatus)
: Code(language.code, AllowedValues, existenzstatus) {
companion object {
val AllowedValues = listOf(
Dialogsprache.Default.code,
Dialogsprache.German.code,
Dialogsprache.English.code,
Dialogsprache.French.code
)
}
}

View File

@ -0,0 +1,6 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
open class FinTsKundensystemStatus : KundensystemStatus(KundensystemStatusWerte.NichtBenoetigt, Existenzstatus.Mandatory)

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte
enum class HbciVersion(val versionNumber: Int) {
Hbci_2_0_1(201),
Hbci_2_1_0(210),
Hbci_2_2_0(220),
FinTs_3_0_0(300),
FinTs_4_0_0(400)
}

View File

@ -0,0 +1,34 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Version der HBCI-/FinTS-Schnittstellenspezifikation, die der jeweiligen Realisierung zugrunde liegt.
*
* HBCI- bzw. FinTS-Versionen, die vor Version 2.0.1 veröffentlicht wurden, werden
* kreditinstitutsseitig nicht unterstützt.
*
* Ein geregelter Dialog ist nur zwischen Systemen möglich, die mit derselben HBCI-/FinTS-Version
* arbeiten. Stimmt die vom Kunden übermittelte HBCI-/FinTS-Version nicht mit einer der vom
* Kreditinstitut in den BPD mitgeteilten unterstützten HBCI-/FinTS-Versionen überein, so muss der
* Dialog vom Kreditinstitut beendet werden. Innerhalb eines Dialoges dürfen nicht Nachrichten
* unterschiedlicher HBCI-/FinTS-Versionen gesendet werden.
*
* Segment- und HBCI-/FinTS-Versionen werden unabhängig voneinander geführt. Innerhalb eines HBCI-/
* FinTS-Dialoges dürfen nur Versionen administrativer Segmente gesendet werden, die der angegebenen
* HBCI-/FinTS-Version entsprechen. Im Rahmen einer HBCI-/FinTS-Version wird eine Liste der zugehörigen
* Segmentversionen veröffentlicht (s. [Messages], Anlagen). Weiterhin werden in dieser Liste auch die
* zusätzlich noch unterstützten Segmentversionen genannt.
*
* Der Zeitpunkt der Unterstützung einer neuen HBCI-/FinTS-Version kann zwischen den Kreditinstituten variieren.
*
* Zulässige Werte:
*
* - Version 2.0.1: 201 (Spezifikationsstatus: obsolet)
* - Version 2.1: 210 (Spezifikationsstatus: obsolet)
* - Version 2.2: 220 (Spezifikationsstatus: obsolet)
* - Version 3.0: 300
*/
class HbciVersionDatenelement(version: HbciVersion) : NumerischesDatenelement(version.versionNumber, 3, Existenzstatus.Mandatory)

View File

@ -0,0 +1,14 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Landesspezifische Kennung, die das Kreditinstitut eindeutig identifiziert. In Deutschland
* wird die Bankleitzahl eingestellt. Bei Kreditinstituten, die in Ländern ohne
* Institutskennungssystem beheimatet sind, kann die Belegung entfallen.
* Zu weiteren Informationen siehe Kap. E.5.
*/
open class Kreditinstitutscode(bankCode: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(bankCode, existenzstatus, 30)

View File

@ -0,0 +1,19 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Institutsweit eindeutige Identifikation des Kunden. Die Vergabe obliegt dem Kreditinstitut.
* Die Kunden-ID kann beliebige Informationen enthalten. Es steht dem Kreditinstitut frei, ob
* es jedem Kunden genau eine Kunden-ID zuordnet oder dem Kunden in Abhängigkeit vom Benutzer
* jeweils eine unterschiedliche Kunden-ID zuordnet.
*/
open class KundenID(customerId: String) : Identifikation(customerId, Existenzstatus.Mandatory) {
companion object {
const val Anonymous = "9999999999"
}
}

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Eindeutige Kennzeichnung des Kundensystems, die in Kombination mit der Signatur-ID die
* Validität (Eindeutigkeit) der Signatur sichert.
*
* Die Kundensystem-ID ist nicht eindeutig für das Endgerät (PC), sondern für die Anwendung
* auf einem Endgerät, d. h., wenn der Kunde auf einem Endgerät mit mehreren Homebanking-
* Anwendungen arbeitet, muss für jede Anwendung eine eigene Kundensystem-ID geführt werden.
*
* Die Kundensystem-ID ist beim HBCI RAH- / RDH- sowie dem PIN/TAN-Verfahren erforderlich.
* Bei der Verwendung von RAH-/RDH-Chipkarten ab Sicherheitsprofil-Version 3 wird anstatt
* der Kundensystem-ID die CID der gesteckten Karte verwendet. Beim HBCI DDV-Verfahren und
* bei TAN-Verfahren ist dieses DE mit dem Wert 0 zu belegen.
*/
open class KundensystemID(customerSystemId: String) : Identifikation(customerSystemId, Existenzstatus.Mandatory) {
companion object {
const val Anonymous = "0"
}
}

View File

@ -0,0 +1,28 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
/**
* Information darüber, ob die Kundensystem-ID erforderlich ist:
*
* Codierung:
*
* 0: Kundensystem-ID wird nicht benötigt (HBCI DDV-Verfahren und chipkartenbasierte Verfahren ab Sicherheitsprofil-Version 3)
*
* 1: Kundensystem-ID wird benötigt (sonstige HBCI RAH- / RDH- und PIN/TAN-Verfahren)
*/
open class KundensystemStatus(status: KundensystemStatusWerte, existenzstatus: Existenzstatus)
: Code(status.code, AllowedValues, existenzstatus) {
companion object {
val AllowedValues = listOf(
KundensystemStatusWerte.NichtBenoetigt.code,
KundensystemStatusWerte.Benoetigt.code
)
val Anonymous = KundensystemStatusWerte.NichtBenoetigt
}
}

View File

@ -0,0 +1,10 @@
package net.dankito.fints.messages.datenelemente.implementierte
enum class KundensystemStatusWerte(val code: String) {
NichtBenoetigt("0"),
Benoetigt("1")
}

View File

@ -0,0 +1,22 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**
* Kennzeichen gemäß ISO 3166-1 (numerischer Code).
*
* (Siehe z.B. PDF Messages_Geschaeftsvorfaelle Kapitel E.4 (S. 813) oder https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes .)
*/
open class Laenderkennzeichen(countryCode: Int, existenzstatus: Existenzstatus)
: ZiffernDatenelement(countryCode, 3, existenzstatus) {
companion object {
/**
* Für Deutschland wird der Code 280 verwendet da dieser im Kreditgewerbe gebräuchlicher als der neue Code 276 ist.
*/
const val Germany = 280
}
}

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**
* Größe der Nachricht (nach Verschlüsselung und Komprimierung) in Byte.
* Das DE ist mit führenden Nullen auf die vorgegebene feste Länge aufzufüllen.
* Dies ist erforderlich, damit die Nachrichtenlänge nicht mit der Länge des DE variiert.
*/
class Nachrichtengroesse(messageSize: Int) : ZiffernDatenelement(messageSize, 12, Existenzstatus.Mandatory)

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Information zur Referenzierung von Nachrichten innerhalb eines Dialoges. In
* Zusammenhang mit der Dialog-ID und der Kundensystem-ID können Nachrichten
* über die Nachrichtennummer auch dialogübergreifend eindeutig referenziert werden.
* Eine Doppeleinreichungskontrolle ist mit Hilfe der Nachrichtennummer nicht möglich.
*
* Mit Hilfe der Nachrichtennummer nummerieren sowohl das Kundensystem als auch das
* Kreditinstitutssystem seine Nachrichten unabhängig voneinander innerhalb eines Dialoges
* in Einerschritten streng monoton aufsteigend. Die Nummerierung beginnt sowohl beim Kunden-
* als auch beim Kreditinstitutssystem mit der Dialoginitialisierungsnachricht bei '1'.
* Nachrichten, deren Nummerierung nicht streng monoton aufsteigend erfolgt ist, werden
* institutsseitig bzw. kundenseitig abgelehnt.
*/
class Nachrichtennummer(number: Int) : NumerischesDatenelement(number, 4, Existenzstatus.Mandatory) {
companion object {
const val FirstMessageNumber = 1
}
}

View File

@ -0,0 +1,19 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Name des Kundenproduktes, mit dem kundenseitig die Nachrichten erzeugt wurden.
* Diese Angabe dient dem Kreditinstitut, um Kundenprodukthersteller gezielt unterstützen zu können.
*
* Die Produktbezeichnung ist verpflichtend mit aussagekräftigen Informationen über das verwendete
* Kundenprodukt, nicht eine ggf. verwendete interne FinTS-/HBCI-Bibliothek, zu füllen, um
* Support-Anfragen leichter beantworten zu können.
*
* Kundenprodukte, die nach dem durch die Deutsche Kreditwirtschaft festgelegten Verfahren registriert
* sind, müssen in dieses DE die vergebene Produktregistrierungsnummer einstellen.
*/
class Produktbezeichnung(name: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(name, existenzstatus, 25)

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Version des Kundenproduktes, mit dem kundenseitig die Nachrichten erzeugt wurden.
*
* Die Produktversion ist verpflichtend mit aussagekräftigen Informationen über das verwendete
* Kundenprodukt, nicht eine ggf. verwendete interne FinTS-/HBCI-Bibliothek, zu füllen, um
* Support-Anfragen leichter beantworten zu können.
*/
class Produktversion(version: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(version, existenzstatus, 5)

View File

@ -0,0 +1,11 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Segmentspezifische Kennung, die jedem Segment bzw. Auftrag zugeordnet ist (z. B. "HKCCS"
* für "SEPA Einzelüberweisung"). Die Angabe hat in Großschreibung zu erfolgen.
*/
open class Segmentkennung(identifier: String): AlphanumerischesDatenelement(identifier, Existenzstatus.Mandatory, 6)

View File

@ -0,0 +1,22 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Information zur eindeutigen Identifizierung eines Segments innerhalb einer Nachricht.
* Die Segmente einer Nachricht werden in Einerschritten streng monoton aufsteigend nummeriert.
* Die Nummerierung beginnt mit 1 im ersten Segment der Nachricht (Nachrichtenkopf).
*/
open class Segmentnummer(number: Int) : NumerischesDatenelement(number, 3, Existenzstatus.Mandatory) {
override fun validate() {
super.validate()
if (value < 1) {
throwValidationException("Segmentnummer '$value' muss größer oder gleich 1 sein.")
}
}
}

View File

@ -0,0 +1,25 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Versionsnummer zur Dokumentation von Änderungen eines Segmentformats.
* Die Segmentversion von administrativen Segmenten (die Segmentart Administration bzw. Geschäftsvorfall
* ist bei jeder Segmentbeschreibung angegeben) wird bei jeder Änderung des Segmentformats inkrementiert.
*
* Bei Geschäftsvorfallssegmenten wird die Segmentversion auf logischer Ebene verwaltet, d. h. sie ist für das
* Auftrags-, das Antwort- und das Parametersegment des Geschäftsvorfalls stets identisch und wird inkrementiert,
* wenn sich das Format von mindestens einem der drei Segmente ändert.
*
* Dieses Verfahren gilt bei Standardsegmenten einheitlich für alle Kreditinstitute. Bei verbandsindividuellen
* Segmenten obliegt die Versionssteuerung dem jeweiligen Verband. Der Zeitpunkt der Unterstützung einer neuen
* Segmentversion kann jedoch zwischen den Verbänden variieren.
*
* Die für die jeweilige HBCI-Version gültige Segmentversion ist bei der jeweiligen Segmentbeschreibung vermerkt.
*
* Falls der Kunde ein Segment mit einer veralteten Versionsnummer einreicht, sollte ihm in einer entsprechenden
* Warnung rückgemeldet werden, dass sein Kundenprodukt aktualisiert werden sollte.
*/
open class Segmentversion(version: Int) : NumerischesDatenelement(version, 3, Existenzstatus.Mandatory)

View File

@ -0,0 +1,17 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Versionsnummer der Userparameterdaten (UPD). Bei jeder kreditinstitutsseitigen Änderung
* wird die Version inkrementiert. (S. auch DE BPD-Version).
*/
class UPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
companion object {
const val VersionNotReceivedYet = 0
}
}

View File

@ -0,0 +1,19 @@
package net.dankito.fints.messages.datenelementgruppen
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.DatenelementBase
abstract class Datenelementgruppe(val dataElements: List<DatenelementBase>, existenzstatus: Existenzstatus)
: DatenelementBase(existenzstatus) {
companion object {
const val DataElementsSeparator = ":"
}
override fun format(): String {
return dataElements.joinToString(DataElementsSeparator) { it.format() }
}
}

View File

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.Kreditinstitutscode
import net.dankito.fints.messages.datenelemente.implementierte.Laenderkennzeichen
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
open class Kreditinstitutskennung(bankCountryCode: Int, bankCode: String)
: Datenelementgruppe(listOf(
Laenderkennzeichen(bankCountryCode, Existenzstatus.Mandatory),
Kreditinstitutscode(bankCode, Existenzstatus.Mandatory)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,21 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.Segmentkennung
import net.dankito.fints.messages.datenelemente.implementierte.Segmentnummer
import net.dankito.fints.messages.datenelemente.implementierte.Segmentversion
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
open class Segmentkopf @JvmOverloads constructor(
identifier: String,
segmentNumber: Int,
segmentVersion: Int,
bezugssegment: Int? = null
) : Datenelementgruppe(listOf(
Segmentkennung(identifier),
Segmentnummer(segmentNumber),
Segmentversion(segmentVersion) // TODO: how to conditionally add Bezugsegment?
), Existenzstatus.Mandatory) {
}

View File

@ -0,0 +1,19 @@
package net.dankito.fints.messages.nachrichten
import net.dankito.fints.messages.Nachrichtenteil
import net.dankito.fints.messages.segmente.Segment
open class Nachricht(val segments: List<Segment>) : Nachrichtenteil() {
companion object {
const val SegmentSeparator = "'"
}
override fun format(): String {
return segments.joinToString(SegmentSeparator, postfix = SegmentSeparator) { it.format() }
}
}

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.nachrichten.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.Nachrichtennummer.Companion.FirstMessageNumber
import net.dankito.fints.messages.nachrichten.Nachricht
import net.dankito.fints.messages.segmente.implementierte.IdentifikationsSegment
import net.dankito.fints.messages.segmente.implementierte.Nachrichtenabschluss
import net.dankito.fints.messages.segmente.implementierte.Nachrichtenkopf
import net.dankito.fints.messages.segmente.implementierte.Verarbeitungsvorbereitung
open class Dialoginitialisierung(
messageSize: Int, // TODO: how to get / calculate size? (give each Segment, Dataelement, ... a size value?)
bankCountryCode: Int,
bankCode: String,
customerId: String,
customerSystemId: String,
bpdVersion: Int,
updVersion: Int,
language: Dialogsprache,
productName: String,
productVersion: String
)
: Nachricht(listOf(
Nachrichtenkopf(1, messageSize, "0", FirstMessageNumber),
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId),
Verarbeitungsvorbereitung(3, bpdVersion, updVersion, language, productName, productVersion),
Nachrichtenabschluss(4, FirstMessageNumber)
))

View File

@ -0,0 +1,20 @@
package net.dankito.fints.messages.nachrichten.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.*
/**
* 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)
* zu informieren bzw. nicht-signierungspflichtige Aufträge bei fremden Kreditinstituten
* einreichen zu können, kann sich der Kunde anonym (als Gast) anmelden.
*
* Bei anonymen Dialogen werden Nachrichten weder signiert, noch können sie verschlüsselt und komprimiert werden.
*/
open class DialoginitialisierungAnonym(
bankCountryCode: Int,
bankCode: String,
productName: String,
productVersion: String
) : Dialoginitialisierung(125, bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous,
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion)

View File

@ -0,0 +1,20 @@
package net.dankito.fints.messages.segmente
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.DatenelementBase
abstract class Segment(val dataElementsAndGroups: List<DatenelementBase>, existenzstatus: Existenzstatus)
: DatenelementBase(existenzstatus) {
companion object {
const val DataElementGroupsSeparator = "+"
}
override fun format(): String {
// TODO: really use DatenelementGruppenSeparator for all elements or use for Datenelement Datenelementgruppe.DatenelementSeparator ?
return dataElementsAndGroups.joinToString(DataElementGroupsSeparator) { it.format() }
}
}

View File

@ -0,0 +1,25 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.FinTsKundensystemStatus
import net.dankito.fints.messages.datenelemente.implementierte.KundenID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
open class IdentifikationsSegment(
segmentNumber: Int,
bankCountryCode: Int,
bankCode: String,
customerId: String,
customerSystemId: String
) : Segment(listOf(
Segmentkopf("HKIDN", segmentNumber, 2),
Kreditinstitutskennung(bankCountryCode, bankCode),
KundenID(customerId),
KundensystemID(customerSystemId),
FinTsKundensystemStatus()
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.Nachrichtennummer
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
/**
* Dieses Segment beendet alle Kunden- und Kreditinstitutsnachrichten.
*/
class Nachrichtenabschluss(
segmentNumber: Int,
messageNumber: Int
) : Segment(listOf(
Segmentkopf("HNHBS", segmentNumber, 1),
Nachrichtennummer(messageNumber)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,21 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.*
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
open class Nachrichtenkopf(
segmentNumber: Int,
messageSize: Int,
dialogId: String,
messageNumber: Int
) : Segment(listOf(
Segmentkopf("HNHBK", segmentNumber, 3),
Nachrichtengroesse(messageSize),
HbciVersionDatenelement(HbciVersion.FinTs_3_0_0),
DialogId(dialogId),
Nachrichtennummer(messageNumber)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,23 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.*
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
class Verarbeitungsvorbereitung(
segmentNumber: Int,
bpdVersion: Int,
updVersion: Int,
language: Dialogsprache,
productName: String,
productVersion: String
) : Segment(listOf(
Segmentkopf("HKVVB", segmentNumber, 3),
BPDVersion(bpdVersion, Existenzstatus.Mandatory),
UPDVersion(updVersion, Existenzstatus.Mandatory),
DialogspracheDatenelement(language, Existenzstatus.Mandatory),
Produktbezeichnung(productName, Existenzstatus.Mandatory),
Produktversion(productVersion, Existenzstatus.Mandatory)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,26 @@
package net.dankito.fints.util
import java.nio.charset.Charset
interface IBase64Service {
companion object {
val DefaultCharset = Charsets.UTF_8
}
fun encode(text: String): String {
return encode(text, DefaultCharset)
}
fun encode(text: String, charset: Charset): String
fun decode(base64: String): String {
return decode(base64, DefaultCharset)
}
fun decode(base64: String, charset: Charset): String
}

View File

@ -0,0 +1,66 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
import org.junit.Test
class TextDatenelementTest {
@Test
fun validate_ValidCharacters() {
// given
val underTest = createTextDatenelement("àéôçñøäöüß¿")
// when
underTest.validate()
// then
// no exception
}
@Test(expected = IllegalArgumentException::class)
fun validate_GreekCharacter_Invalid() {
// given
val underTest = createTextDatenelement("α")
// when
underTest.validate()
// then
// exception gets thrown
}
@Test(expected = IllegalArgumentException::class)
fun validate_CyrillicCharacter_Invalid() {
// given
val underTest = createTextDatenelement("Я")
// when
underTest.validate()
// then
// exception gets thrown
}
@Test(expected = IllegalArgumentException::class)
fun validate_EuroSymbol_Invalid() {
// given
val underTest = createTextDatenelement("") // is only allowed by ISO-8859-15, not by ISO-8859-1
// when
underTest.validate()
// then
// exception gets thrown
}
private fun createTextDatenelement(text: String): TextDatenelement {
return object : TextDatenelement(text, Existenzstatus.Mandatory) { }
}
}

View File

@ -0,0 +1,81 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class ZiffernDatenelementTest {
@Test
fun format() {
// given
val underTest = createZiffernDatenelement(1, 6)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("000001")
}
@Test
fun validate_0_Valid() {
// given
val underTest = createZiffernDatenelement(0, 3)
// when
underTest.validate()
// then
// no exception
}
@Test
fun validate_999_Valid() {
// given
val underTest = createZiffernDatenelement(999, 3)
// when
underTest.validate()
// then
// no exception
}
@Test(expected = IllegalArgumentException::class)
fun validate_Minus1_Invalid() {
// given
val underTest = createZiffernDatenelement(-1, 3)
// when
underTest.validate()
// then
// exception gets thrown
}
@Test(expected = IllegalArgumentException::class)
fun validate_1000_Invalid() {
// given
val underTest = createZiffernDatenelement(1000, 3)
// when
underTest.validate()
// then
// exception gets thrown
}
private fun createZiffernDatenelement(value: Int, numberOfDigits: Int): ZiffernDatenelement {
return object : ZiffernDatenelement(value, numberOfDigits, Existenzstatus.Mandatory) { }
}
}

View File

@ -0,0 +1,76 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class BPDVersionTest {
@Test
fun format() {
// given
val underTest = BPDVersion(3, Existenzstatus.Mandatory)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("3")
}
@Test
fun validate_0_Valid() {
// given
val underTest = BPDVersion(0, Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// no exception
}
@Test
fun validate_999_Valid() {
// given
val underTest = BPDVersion(999, Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// no exception
}
@Test(expected = IllegalArgumentException::class)
fun validate_TooLow() {
// given
val underTest = BPDVersion(-1, Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// exception gets thrown
}
@Test(expected = IllegalArgumentException::class)
fun validate_TooHigh() {
// given
val underTest = BPDVersion(1000, Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// exception gets thrown
}
}

View File

@ -0,0 +1,35 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import org.junit.Test
class ProduktbezeichnungTest {
@Test
fun validate_MaxLength() {
// given
val underTest = Produktbezeichnung("1234567890123456789012345", Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// no exception
}
@Test(expected = IllegalArgumentException::class)
fun validate_MaxLengthExceeded() {
// given
val underTest = Produktbezeichnung("12345678901234567890123456", Existenzstatus.Mandatory)
// when
underTest.validate()
// then
// exception gets thrown
}
}

View File

@ -0,0 +1,28 @@
package net.dankito.fints.messages.nachrichten.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.Laenderkennzeichen
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class DialoginitialisierungAnonymTest {
@Test
fun format() {
// given
val underTest = DialoginitialisierungAnonym(Laenderkennzeichen.Germany, "12345678", "36792786FA12F235F04647689", "3")
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo(
"HNHBK:1:3+000000000125+300+0+1'" +
"HKIDN:2:2+280:12345678+9999999999+0+0'" +
"HKVVB:3:3+0+0+0+36792786FA12F235F04647689+3'" +
"HNHBS:4:1+1'"
)
}
}

View File

@ -0,0 +1,25 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.KundenID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelemente.implementierte.Laenderkennzeichen
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class IdentifikationsSegmentTest {
@Test
fun format() {
// given
val underTest = IdentifikationsSegment(2, Laenderkennzeichen.Germany, "12345678", KundenID.Anonymous, KundensystemID.Anonymous)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKIDN:2:2+280:12345678+9999999999+0+0")
}
}

View File

@ -0,0 +1,21 @@
package net.dankito.fints.util
import java.nio.charset.Charset
import java.util.*
// TODO: use version from JavaFxUtils
class Java8Base64Service : IBase64Service {
override fun encode(text: String, charset: Charset): String {
return Base64.getEncoder().encodeToString(text.toByteArray(charset))
}
override fun decode(base64: String, charset: Charset): String {
val decodedBytes = Base64.getDecoder().decode(base64)
return String(decodedBytes, charset)
}
}

View File

@ -1,3 +1,3 @@
rootProject.name = 'fints4java'
include ':FinTS4javaLib'
include ':fints4javaLib'