diff --git a/docs/pain.001.001.03_with_comments.xml b/docs/pain.001.001.03_with_comments.xml
new file mode 100644
index 00000000..cbd58331
--- /dev/null
+++ b/docs/pain.001.001.03_with_comments.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+ Message-ID-4711
+
+
+ 2010-11-11T09:30:47.000Z
+
+
+ 2
+
+
+ 6655.86
+
+
+
+
+
+ Initiator Name
+
+
+
+
+
+
+
+
+
+ Payment-Information-ID-4711
+
+
+ TRF
+
+
+
+ true
+
+
+ 2
+
+
+ 6655.86
+
+
+
+
+
+
+
+
+ SEPA
+
+
+
+
+
+ 2010-11-25
+
+
+
+
+ Debtor Name
+
+
+
+
+
+
+
+ DE87200500001234567890
+
+
+
+
+
+
+
+
+ BANKDEFFXXX
+
+
+
+
+
+ SLEV
+
+
+
+
+
+
+
+
+ OriginatorID1234
+
+
+
+
+
+
+ 6543.14
+
+
+
+
+
+
+
+
+ SPUEDE2UXXX
+
+
+
+
+
+
+ Creditor Name
+
+
+
+
+
+ DE21500500009876543210
+
+
+
+
+
+
+
+ Unstructured Remittance Information
+
+
+
+
+
+
+ OriginatorID1235
+
+
+ 112.72
+
+
+
+ SPUEDE2UXXX
+
+
+
+ Other Creditor Name
+
+
+
+ DE21500500001234567897
+
+
+
+ Unstructured Remittance Information
+
+
+
+
+
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt
index 126c7008..d24f4b0c 100644
--- a/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt
@@ -3,10 +3,7 @@ package net.dankito.fints
import net.dankito.fints.messages.MessageBuilder
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
-import net.dankito.fints.model.BankData
-import net.dankito.fints.model.CustomerData
-import net.dankito.fints.model.DialogData
-import net.dankito.fints.model.ProductData
+import net.dankito.fints.model.*
import net.dankito.fints.response.InstituteSegmentId
import net.dankito.fints.response.Response
import net.dankito.fints.response.ResponseParser
@@ -92,14 +89,6 @@ open class FinTsClient(
return response
}
- protected open fun closeDialog(bank: BankData, customer: CustomerData, dialogData: DialogData) {
- dialogData.increaseMessageNumber()
-
- val dialogEndRequestBody = messageBuilder.createDialogEndMessage(bank, customer, dialogData)
-
- getResponseForMessage(dialogEndRequestBody, bank)
- }
-
open fun getTransactions(bank: BankData, customer: CustomerData, product: ProductData): Response {
val dialogData = DialogData()
@@ -134,6 +123,37 @@ open class FinTsClient(
}
+ open fun doBankTransfer(bankTransferData: BankTransferData, bank: BankData, customer: CustomerData, product: ProductData): Response {
+ val dialogData = DialogData()
+
+ val initDialogResponse = initDialog(bank, customer, product, dialogData)
+
+ if (initDialogResponse.successful == false) {
+ return initDialogResponse
+ }
+
+
+ dialogData.increaseMessageNumber()
+
+ val requestBody = messageBuilder.createBankTransferMessage(bankTransferData, bank, customer, dialogData)
+
+ val response = getAndHandleResponseForMessage(requestBody, bank)
+
+ closeDialog(bank, customer, dialogData)
+
+ return response
+ }
+
+
+ protected open fun closeDialog(bank: BankData, customer: CustomerData, dialogData: DialogData) {
+ dialogData.increaseMessageNumber()
+
+ val dialogEndRequestBody = messageBuilder.createDialogEndMessage(bank, customer, dialogData)
+
+ getResponseForMessage(dialogEndRequestBody, bank)
+ }
+
+
protected open fun getAndHandleResponseForMessage(requestBody: String, bank: BankData): Response {
val webResponse = getResponseForMessage(requestBody, bank)
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 e0645dc8..b30c33fc 100644
--- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt
@@ -8,12 +8,10 @@ import net.dankito.fints.messages.segmente.SegmentNumberGenerator
import net.dankito.fints.messages.segmente.Synchronisierung
import net.dankito.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.fints.messages.segmente.implementierte.*
+import net.dankito.fints.messages.segmente.implementierte.sepa.SepaEinzelueberweisung
import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version5
-import net.dankito.fints.model.BankData
-import net.dankito.fints.model.CustomerData
-import net.dankito.fints.model.DialogData
-import net.dankito.fints.model.ProductData
import net.dankito.fints.messages.segmente.implementierte.umsaetze.Saldenabfrage
+import net.dankito.fints.model.*
import net.dankito.fints.util.FinTsUtils
import java.util.concurrent.ThreadLocalRandom
@@ -109,6 +107,15 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
}
+ open fun createBankTransferMessage(bankTransferData: BankTransferData, bank: BankData, customer: CustomerData, dialogData: DialogData): String {
+
+ return createSignedMessage(bank, customer, dialogData, listOf(
+ SepaEinzelueberweisung(generator.resetSegmentNumber(2), customer, bank.bic!!, bankTransferData),
+ ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.SepaBankTransfer)
+ ))
+ }
+
+
open fun createSignedMessage(bank: BankData, customer: CustomerData, dialogData: DialogData,
payloadSegments: List): String {
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/id/CustomerSegmentId.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/id/CustomerSegmentId.kt
index 6e626db8..125cad2c 100644
--- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/id/CustomerSegmentId.kt
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/id/CustomerSegmentId.kt
@@ -15,6 +15,8 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
Balance("HKSAL"),
- AccountTransactionsMt940("HKKAZ")
+ AccountTransactionsMt940("HKKAZ"),
+
+ SepaBankTransfer("HKCCS")
}
\ No newline at end of file
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/ISepaMessageCreator.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/ISepaMessageCreator.kt
new file mode 100644
index 00000000..0805157d
--- /dev/null
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/ISepaMessageCreator.kt
@@ -0,0 +1,8 @@
+package net.dankito.fints.messages.segmente.implementierte.sepa
+
+
+interface ISepaMessageCreator {
+
+ fun createXmlFile(filename: String, replacementStrings: Map): String
+
+}
\ No newline at end of file
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisung.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisung.kt
new file mode 100644
index 00000000..143e74f0
--- /dev/null
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisung.kt
@@ -0,0 +1,44 @@
+package net.dankito.fints.messages.segmente.implementierte.sepa
+
+import net.dankito.fints.messages.segmente.id.CustomerSegmentId
+import net.dankito.fints.model.BankTransferData
+import net.dankito.fints.model.CustomerData
+
+
+open class SepaEinzelueberweisung(
+ segmentNumber: Int,
+ debitor: CustomerData,
+ debitorBic: String,
+ data: BankTransferData,
+ messageCreator: ISepaMessageCreator = SepaMessageCreator()
+)
+ : SepaSegment(
+ segmentNumber,
+ CustomerSegmentId.SepaBankTransfer,
+ 1,
+ "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", // TODO: read from HISPAS
+ "pain.001.001.03.xml",
+ data.creditorIban,
+ data.creditorBic,
+ mapOf(
+ SepaMessageCreator.NumberOfTransactionsKey to "1", // TODO: may someday support more then one transaction per file
+ "DebitorName" to debitor.name,
+ "DebitorIban" to debitor.iban!!,
+ "DebitorBic" to debitorBic,
+ "CreditorName" to data.creditorName,
+ "CreditorIban" to data.creditorIban,
+ "CreditorBic" to data.creditorBic,
+ "Amount" to data.amount.toString(),
+ "Usage" to data.usage,
+ "RequestedExecutionDate" to RequestedExecutionDateValueForNotScheduledTransfers
+ ),
+ messageCreator
+) {
+
+ companion object {
+ /**
+ * In das Mussfeld RequestedExecutionDate ist der 1999-01-01 einzustellen.
+ */
+ const val RequestedExecutionDateValueForNotScheduledTransfers = "1999-01-01"
+ }
+}
\ No newline at end of file
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaMessageCreator.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaMessageCreator.kt
new file mode 100644
index 00000000..0c0afd58
--- /dev/null
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaMessageCreator.kt
@@ -0,0 +1,75 @@
+package net.dankito.fints.messages.segmente.implementierte.sepa
+
+import net.dankito.fints.messages.datenelemente.implementierte.sepa.SepaMessage
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+/**
+ * It may sounds like beginners programming loading a XML file and doing string replacements to set actual values.
+ * And yes I know how to use xjc :).
+ *
+ * But there's some reason behind it:
+ * - Serializing to XML is always a bit problematic on Android.
+ * - I don't need another dependency.
+ * - And it should be a little bit faster (even though not much :) ).
+ */
+open class SepaMessageCreator : ISepaMessageCreator {
+
+ companion object {
+ const val MessageIdKey = "MessageId"
+
+ const val CreationDateTimeKey = "CreationDateTime"
+
+ const val PaymentInformationIdKey = "PaymentInformationId"
+
+ const val NumberOfTransactionsKey = "NumberOfTransactions"
+
+ val IsoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+
+ private val log = LoggerFactory.getLogger(SepaMessageCreator::class.java)
+ }
+
+
+ override fun createXmlFile(filename: String, replacementStrings: Map): String {
+ var xmlFile = loadXmlFile(filename)
+
+ val now = Date()
+ val nowInIsoDate = IsoDateFormat.format(now)
+
+ if (replacementStrings.containsKey(MessageIdKey) == false) {
+ xmlFile = replacePlaceholderWithValue(xmlFile, MessageIdKey, nowInIsoDate)
+ }
+ if (replacementStrings.containsKey(CreationDateTimeKey) == false) {
+ xmlFile = replacePlaceholderWithValue(xmlFile, CreationDateTimeKey, nowInIsoDate)
+ }
+ if (replacementStrings.containsKey(PaymentInformationIdKey) == false) {
+ xmlFile = replacePlaceholderWithValue(xmlFile, PaymentInformationIdKey, nowInIsoDate)
+ }
+
+ replacementStrings.forEach { entry ->
+ xmlFile = replacePlaceholderWithValue(xmlFile, entry.key, entry.value)
+ }
+
+ return xmlFile
+ }
+
+ protected open fun loadXmlFile(filename: String): String {
+ val filePath = "sepa/" + filename
+
+ SepaMessage::class.java.classLoader.getResourceAsStream(filePath)?.use { inputStream ->
+ return inputStream.bufferedReader().readText()
+ }
+
+ log.error("Could not load SEPA file from path ${File(filePath).absolutePath}") // TODO: how to inform user?
+
+ return ""
+ }
+
+ protected open fun replacePlaceholderWithValue(xmlFile: String, key: String, value: String): String {
+ return xmlFile.replace("$$key$", value)
+ }
+
+}
\ No newline at end of file
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaSegment.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaSegment.kt
new file mode 100644
index 00000000..75f4fe30
--- /dev/null
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaSegment.kt
@@ -0,0 +1,28 @@
+package net.dankito.fints.messages.segmente.implementierte.sepa
+
+import net.dankito.fints.messages.Existenzstatus
+import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
+import net.dankito.fints.messages.datenelemente.implementierte.sepa.SepaMessage
+import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
+import net.dankito.fints.messages.datenelementgruppen.implementierte.account.KontoverbindungInternational
+import net.dankito.fints.messages.segmente.Segment
+import net.dankito.fints.messages.segmente.id.ISegmentId
+
+
+open class SepaSegment(
+ segmentNumber: Int,
+ segmentId: ISegmentId,
+ segmentVersion: Int,
+ sepaDescriptorUrn: String,
+ sepaFileName: String,
+ iban: String,
+ bic: String,
+ replacementStrings: Map,
+ messageCreator: ISepaMessageCreator = SepaMessageCreator()
+)
+ : Segment(listOf(
+ Segmentkopf(segmentId, segmentVersion, segmentNumber),
+ KontoverbindungInternational(iban, bic, null),
+ object : AlphanumerischesDatenelement(sepaDescriptorUrn, Existenzstatus.Mandatory, 256) { },
+ SepaMessage(sepaFileName, replacementStrings, messageCreator)
+), Existenzstatus.Mandatory)
\ No newline at end of file
diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankTransferData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankTransferData.kt
new file mode 100644
index 00000000..9bda9c46
--- /dev/null
+++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankTransferData.kt
@@ -0,0 +1,12 @@
+package net.dankito.fints.model
+
+import java.math.BigDecimal
+
+
+open class BankTransferData(
+ val creditorName: String,
+ val creditorIban: String,
+ val creditorBic: String,
+ val amount: BigDecimal,
+ val usage: String
+)
\ No newline at end of file
diff --git a/fints4javaLib/src/main/resources/sepa/pain.001.001.03.xml b/fints4javaLib/src/main/resources/sepa/pain.001.001.03.xml
new file mode 100644
index 00000000..4a57d09f
--- /dev/null
+++ b/fints4javaLib/src/main/resources/sepa/pain.001.001.03.xml
@@ -0,0 +1 @@
+$MessageId$$CreationDateTime$$NumberOfTransactions$$Amount$$DebitorName$$PaymentInformationId$TRF$NumberOfTransactions$$Amount$SEPA$RequestedExecutionDate$$DebitorName$$DebitorIban$$DebitorBic$SLEVNOTPROVIDED$Amount$$CreditorBic$$CreditorName$$CreditorIban$$Usage$
\ No newline at end of file
diff --git a/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisungTest.kt b/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisungTest.kt
new file mode 100644
index 00000000..247e5e51
--- /dev/null
+++ b/fints4javaLib/src/test/kotlin/net/dankito/fints/messages/segmente/implementierte/sepa/SepaEinzelueberweisungTest.kt
@@ -0,0 +1,39 @@
+package net.dankito.fints.messages.segmente.implementierte.sepa
+
+import net.dankito.fints.model.BankTransferData
+import net.dankito.fints.model.CustomerData
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class SepaEinzelueberweisungTest {
+
+ @Test
+ fun format() {
+
+ // given
+ val segmentNumber = 7
+ val debitorName = "Nelson Mandela"
+ val debitorIban = "ZA123456780987654321"
+ val debitorBic = "ABCDZAEFXXX"
+ val creditorName = "Mahatma Gandhi"
+ val creditorIban = "IN123456780987654321"
+ val creditorBic = "ABCDINEFXXX"
+ val amount = 1234.56.toBigDecimal()
+ val usage = "What should Mahatma Gandhi want with money?"
+
+ val underTest = SepaEinzelueberweisung(segmentNumber,
+ CustomerData("", "", "", debitorName, debitorIban),
+ debitorBic,
+ BankTransferData(creditorName, creditorIban, creditorBic, amount, usage)
+ )
+
+
+ // when
+ val result = underTest.format()
+
+
+ // then
+ assertThat(result).contains(debitorName, debitorIban, debitorBic, creditorName, creditorIban, creditorBic,
+ amount.toString(), usage)
+ }
+}
\ No newline at end of file