Implemented SEPA bank transfer
This commit is contained in:
parent
975a84cded
commit
304b3ba9d6
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Mit XMLSpy v2008 rel. 2 sp2 (http://www.altova.com) im Januar 2016 von der SIZ GmbH (Wenzel) bearbeitet -->
|
||||||
|
<!-- Änderungen: NUR NAMESPACE -->
|
||||||
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd">
|
||||||
|
<!-- Customer Credit Transfer Initiation: Überweisungsauftrag durch den Kunden -->
|
||||||
|
<CstmrCdtTrfInitn>
|
||||||
|
|
||||||
|
<!-- Group Header: Kenndaten, die für alle Transaktionen innerhalb der SEPA-Nachricht gelten -->
|
||||||
|
<GrpHdr>
|
||||||
|
<!-- MessageIdentification: Punkt-zu-Punkt-Referenz der anweisenden Partei für die folgende Partei in der Nachrichten-Kette, um die Nachricht (Datei) eindeutig zu identifizieren. -->
|
||||||
|
<!-- Die <MsgID> in Kombination mit der Kunden-ID oder der Auftraggeber-IBAN kann als Kriterium für die Verhinderung einer Doppelverarbeitung bei versehentlich doppelt eingereichten Dateien dienen und muss somit für jede neue pain-Nachricht einen neuen Wert enthalten. -->
|
||||||
|
<MsgId>Message-ID-4711</MsgId>
|
||||||
|
|
||||||
|
<!-- CreationDateTime: Datum und Zeit, wann die ZV-Nachricht durch die anweisende Partei erzeugt wurde. ISODateTime -->
|
||||||
|
<CreDtTm>2010-11-11T09:30:47.000Z</CreDtTm>
|
||||||
|
|
||||||
|
<!-- NumberOfTransactions: Anzahl der einzelnen Transaktionen innerhalb der gesamten Nachricht. Max15NumericText -->
|
||||||
|
<NbOfTxs>2</NbOfTxs>
|
||||||
|
|
||||||
|
<!-- ControlSum: Summe der Beträge aller Einzeltransaktionen in der gesamten Nachricht. DecimalNumber. Es sind maximal zwei Nachkommastellen zulässig. -->
|
||||||
|
<CtrlSum>6655.86</CtrlSum>
|
||||||
|
|
||||||
|
<!-- InitiatingParty: Informationen über die Partei, welche die Zahlung anweist, das heißt der Zahler (Auftraggeber) oder eine Partei, welche im Auftrag des Zahlers handelt. -->
|
||||||
|
<!-- Belegung ist auch abweichend von Debtor zugelassen. Empfehlung: Nur das Unterelement Name sollte verwendet werden. -->
|
||||||
|
<InitgPty>
|
||||||
|
<!-- Name. Max70Text. Name ist begrenzt auf 70 Zeichen. -->
|
||||||
|
<Nm>Initiator Name</Nm>
|
||||||
|
<!-- Identification (<Id>): Es wird empfohlen, diese Feldgruppe nicht zu verwenden. -->
|
||||||
|
</InitgPty>
|
||||||
|
|
||||||
|
</GrpHdr>
|
||||||
|
|
||||||
|
<!-- PaymentInformation -->
|
||||||
|
<PmtInf>
|
||||||
|
|
||||||
|
<!-- PaymentInformationIdentification: Referenz zur eindeutigen Identifizierung des Sammlers. RestrictedIdentificationSEPA1 -->
|
||||||
|
<PmtInfId>Payment-Information-ID-4711</PmtInfId>
|
||||||
|
|
||||||
|
<!-- PaymentMethod: Zahlungsinstrument, z. B. Überweisung. PaymentMethodSCTCode. Enthält die Konstante TRF -->
|
||||||
|
<PmtMtd>TRF</PmtMtd>
|
||||||
|
|
||||||
|
<!-- BatchBooking ([0..1]): Indikator, der aussagt, ob es sich um eine Sammelbuchung (true) oder eine Einzelbuchung handelt (false) -->
|
||||||
|
<!-- Nur wenn eine entsprechende Vereinbarung für Einzelbuchungen mit dem Kunden vorliegt, wird im Falle von Belegung mit false, jede Transaktion einzeln auf dem Kontoauszug des Zahlers (Auftraggebers) dargestellt. Andernfalls immer Sammelbuchung (Default/pre-agreed: true). -->
|
||||||
|
<BtchBookg>true</BtchBookg>
|
||||||
|
|
||||||
|
<!-- NumberOfTransactions: Anzahl der einzelnen Transaktionen innerhalb des Payment Information Blocks. Max15NumericText -->
|
||||||
|
<NbOfTxs>2</NbOfTxs>
|
||||||
|
|
||||||
|
<!-- ControlSum: Summe der Beträge aller Einzeltransaktionen innerhalb des Payment Information Blocks. DecimalNumber. Es sind maximal zwei Nachkommastellen zulässig. -->
|
||||||
|
<CtrlSum>6655.86</CtrlSum>
|
||||||
|
|
||||||
|
<!-- PaymentTypeInformation ([0..1]): Transaktionstyp. PaymentTypeInformationSCT1. -->
|
||||||
|
<!-- Es wird empfohlen, <PmtTpInf> hier und nicht auf Einzeltransaktionsebene zu belegen. Zudem ist eine Belegung der Elementgruppe auf beiden Ebenen gleichzeitig nicht zulässig. -->
|
||||||
|
<PmtTpInf>
|
||||||
|
<!-- ServiceLevel: Vereinbarung oder Regel, nach der die Transaktion verarbeitet werden sollte. ServiceLevelSEPA -->
|
||||||
|
<SvcLvl>
|
||||||
|
<!-- Code: Code einer vorvereinbarten Serviceleistung zwischen den Parteien. ExternalServiceLevel1Code -->
|
||||||
|
<!-- Einziger zugelassener Wert aus der externen ISO20022-Codeliste ist SEPA. -->
|
||||||
|
<Cd>SEPA</Cd>
|
||||||
|
</SvcLvl>
|
||||||
|
</PmtTpInf>
|
||||||
|
|
||||||
|
<!-- RequestedExecutionDate: Ausführungstermin. ISODate -->
|
||||||
|
<!-- Vom Kunden gewünschter Ausführungstermin. Fällt der angegebene Termin auf keinen TARGET-Geschäftstag, so ist die Bank berechtigt, den folgenden TARGET-Geschäftstag als Ausführungstag anzugeben. Geht der Datensatz erst nach der von der Bank angegebenen Cut-Off-Zeit ein, so gilt der Auftrag erst am folgenden Geschäftstag als zugegangen. Banken sind nicht verpflichtet, Auftragsdaten zu verarbeiten, die mehr als 15 Kalendertage VOR dem Ausführungsdatum eingeliefert wurden. -->
|
||||||
|
<ReqdExctnDt>2010-11-25</ReqdExctnDt>
|
||||||
|
|
||||||
|
<!-- Debtor -->
|
||||||
|
<Dbtr>
|
||||||
|
<!-- Max70Text. Name ist auf 70 Zeichen begrenzt. -->
|
||||||
|
<Nm>Debtor Name</Nm>
|
||||||
|
</Dbtr>
|
||||||
|
|
||||||
|
<!-- DebtorAccount: Konto des Zahlers (Auftraggebers). CashAccountSEPA1 -->
|
||||||
|
<DbtrAcct>
|
||||||
|
<!-- Identification: Identifikation des Kontos. AccountIdentificationSEPA -->
|
||||||
|
<Id>
|
||||||
|
<!-- International Bank Account Number (IBAN). IBAN2007Identifier. Diese kann maximal 34 Stellen lang sein. -->
|
||||||
|
<IBAN>DE87200500001234567890</IBAN>
|
||||||
|
</Id>
|
||||||
|
</DbtrAcct>
|
||||||
|
|
||||||
|
<!-- DebtorAgent: Kreditinstitut des Zahlers (Auftraggebers). BranchAndFinancialInstitutionIdentificationSEPA3 -->
|
||||||
|
<DbtrAgt>
|
||||||
|
<!-- FinancialInstitutionIdentification: eindeutige Identifikation eines Kreditinstituts. FinancialInstituteIdentificationSEPA3 -->
|
||||||
|
<FinInstnId>
|
||||||
|
<!-- Business Identifier Code (SWIFT-Code). BICIdentifier. Dieser kann 8 oder 11 Stellen lang sein. -->
|
||||||
|
<BIC>BANKDEFFXXX</BIC>
|
||||||
|
</FinInstnId>
|
||||||
|
</DbtrAgt>
|
||||||
|
|
||||||
|
<!-- ChargeBearer ([0..1]): Entgeltverrechnung; Code, der bedeutet, dass bestimmte Regeln Anwendung finden. ChargeBearerTypeSEPACode -->
|
||||||
|
<!-- Es wird empfohlen, <ChrgBr> hier und nicht auf Einzeltransaktionsebene zu belegen. Zudem ist eine Belegung auf beiden Ebenen gleichzeitig nicht zulässig. Falls belegt, enthält es die Konstante SLEV -->
|
||||||
|
<ChrgBr>SLEV</ChrgBr>
|
||||||
|
|
||||||
|
<!-- CreditTransferTransactionInformation ([1..n]): Einzeltransaktion -->
|
||||||
|
<CdtTrfTxInf>
|
||||||
|
|
||||||
|
<!-- PaymentIdentification: Referenzierung dieser Transaktion. PaymentIdentificationSEPA -->
|
||||||
|
<PmtId>
|
||||||
|
<!-- EndToEndIdentification: eindeutige Referenz des Zahlers (Auftraggebers). Diese Referenz wird unverändert durch die gesamte Kette bis zum Zahlungsempfänger geleitet (Ende-zu-Ende-Referenz). -->
|
||||||
|
<!-- Es wird empfohlen, jede Überweisung mit einer eindeutigen Referenz zu belegen. Ist keine Referenz vorhanden muss die Konstante NOTPROVIDED benutzt werden. -->
|
||||||
|
<EndToEndId>OriginatorID1234</EndToEndId>
|
||||||
|
</PmtId>
|
||||||
|
|
||||||
|
<!-- Amount: Betrag. AmountTypeSEPA -->
|
||||||
|
<Amt>
|
||||||
|
<!-- InstructedAmount: beauftragterBetrag. ActiveOrHistoricCurrencyAndAmountSEPA -->
|
||||||
|
<!-- Ist mit einem Geldbetrag zu belegen, das Dezimaltrennzeichen ist ein Punkt. -->
|
||||||
|
<InstdAmt Ccy="EUR">6543.14</InstdAmt>
|
||||||
|
</Amt>
|
||||||
|
|
||||||
|
<!-- CreditorAgent ([0..1]): Kreditinstitut des Zahlungsempfängers. BranchAndFinancialInstitutionIdentificationSEPA1 -->
|
||||||
|
<CdtrAgt>
|
||||||
|
<!-- FinancialInstitutionIdentification: eindeutige Identifikation eines Kreditinstituts. FinancialInstitutionIdentificationSEPA1 -->
|
||||||
|
<FinInstnId>
|
||||||
|
<!-- BusinessCode Identifier (SWIFT-Code gemäß ISO 9362) -->
|
||||||
|
<!-- Diese Angabe kann vom ZDL bei Zahlungen außerhalb EU/EWR verlangt werden. Der BIC kann 8 oder 11 Stellen lang sein. -->
|
||||||
|
<BIC>SPUEDE2UXXX</BIC>
|
||||||
|
</FinInstnId>
|
||||||
|
</CdtrAgt>
|
||||||
|
|
||||||
|
<!-- Creditor -->
|
||||||
|
<Cdtr>
|
||||||
|
<!-- Max70Text. Name ist begrenzt auf 70 Zeichen. -->
|
||||||
|
<Nm>Creditor Name</Nm>
|
||||||
|
</Cdtr>
|
||||||
|
|
||||||
|
<!-- CreditorAccount: Konto des Zahlungsempfängers. CashAccountSEPA2 -->
|
||||||
|
<CdtrAcct>
|
||||||
|
<Id>
|
||||||
|
<IBAN>DE21500500009876543210</IBAN>
|
||||||
|
</Id>
|
||||||
|
</CdtrAcct>
|
||||||
|
|
||||||
|
<!-- RemittanceInformation ([0..1]): Verwendungszweck. Es wird entweder Unstructured oder Structured, belegt, jedoch nicht beide Structured sollte nur in Absprache mit dem Zahlungsempfänger belegt werden. -->
|
||||||
|
<RmtInf>
|
||||||
|
<!-- Unstructured: unstrukturierter Verwendungszweck. Max140Text. -->
|
||||||
|
<!-- Es wird empfohlen, den unstrukturierten Verwendungszweck zu verwenden. In bilateraler Abstimmung zwischen Zahlungsempfänger und Zahler (Auftraggeber) kann der unstrukturierte Verwendungszweck strukturierte Informationen enthalten. -->
|
||||||
|
<Ustrd>Unstructured Remittance Information</Ustrd>
|
||||||
|
</RmtInf>
|
||||||
|
</CdtTrfTxInf>
|
||||||
|
|
||||||
|
<!-- Zweite Überweisung, Parameter wie oben -->
|
||||||
|
<CdtTrfTxInf>
|
||||||
|
<PmtId>
|
||||||
|
<EndToEndId>OriginatorID1235</EndToEndId>
|
||||||
|
</PmtId>
|
||||||
|
<Amt>
|
||||||
|
<InstdAmt Ccy="EUR">112.72</InstdAmt>
|
||||||
|
</Amt>
|
||||||
|
<CdtrAgt>
|
||||||
|
<FinInstnId>
|
||||||
|
<BIC>SPUEDE2UXXX</BIC>
|
||||||
|
</FinInstnId>
|
||||||
|
</CdtrAgt>
|
||||||
|
<Cdtr>
|
||||||
|
<Nm>Other Creditor Name</Nm>
|
||||||
|
</Cdtr>
|
||||||
|
<CdtrAcct>
|
||||||
|
<Id>
|
||||||
|
<IBAN>DE21500500001234567897</IBAN>
|
||||||
|
</Id>
|
||||||
|
</CdtrAcct>
|
||||||
|
<RmtInf>
|
||||||
|
<Ustrd>Unstructured Remittance Information</Ustrd>
|
||||||
|
</RmtInf>
|
||||||
|
</CdtTrfTxInf>
|
||||||
|
</PmtInf>
|
||||||
|
</CstmrCdtTrfInitn>
|
||||||
|
</Document>
|
|
@ -3,10 +3,7 @@ package net.dankito.fints
|
||||||
import net.dankito.fints.messages.MessageBuilder
|
import net.dankito.fints.messages.MessageBuilder
|
||||||
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
|
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
|
||||||
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
|
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
|
||||||
import net.dankito.fints.model.BankData
|
import net.dankito.fints.model.*
|
||||||
import net.dankito.fints.model.CustomerData
|
|
||||||
import net.dankito.fints.model.DialogData
|
|
||||||
import net.dankito.fints.model.ProductData
|
|
||||||
import net.dankito.fints.response.InstituteSegmentId
|
import net.dankito.fints.response.InstituteSegmentId
|
||||||
import net.dankito.fints.response.Response
|
import net.dankito.fints.response.Response
|
||||||
import net.dankito.fints.response.ResponseParser
|
import net.dankito.fints.response.ResponseParser
|
||||||
|
@ -92,14 +89,6 @@ open class FinTsClient(
|
||||||
return response
|
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 {
|
open fun getTransactions(bank: BankData, customer: CustomerData, product: ProductData): Response {
|
||||||
val dialogData = DialogData()
|
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 {
|
protected open fun getAndHandleResponseForMessage(requestBody: String, bank: BankData): Response {
|
||||||
val webResponse = getResponseForMessage(requestBody, bank)
|
val webResponse = getResponseForMessage(requestBody, bank)
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,10 @@ import net.dankito.fints.messages.segmente.SegmentNumberGenerator
|
||||||
import net.dankito.fints.messages.segmente.Synchronisierung
|
import net.dankito.fints.messages.segmente.Synchronisierung
|
||||||
import net.dankito.fints.messages.segmente.id.CustomerSegmentId
|
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.umsaetze.KontoumsaetzeZeitraumMt940Version5
|
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.messages.segmente.implementierte.umsaetze.Saldenabfrage
|
||||||
|
import net.dankito.fints.model.*
|
||||||
import net.dankito.fints.util.FinTsUtils
|
import net.dankito.fints.util.FinTsUtils
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
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,
|
open fun createSignedMessage(bank: BankData, customer: CustomerData, dialogData: DialogData,
|
||||||
payloadSegments: List<Segment>): String {
|
payloadSegments: List<Segment>): String {
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
|
||||||
|
|
||||||
Balance("HKSAL"),
|
Balance("HKSAL"),
|
||||||
|
|
||||||
AccountTransactionsMt940("HKKAZ")
|
AccountTransactionsMt940("HKKAZ"),
|
||||||
|
|
||||||
|
SepaBankTransfer("HKCCS")
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package net.dankito.fints.messages.segmente.implementierte.sepa
|
||||||
|
|
||||||
|
|
||||||
|
interface ISepaMessageCreator {
|
||||||
|
|
||||||
|
fun createXmlFile(filename: String, replacementStrings: Map<String, String>): String
|
||||||
|
|
||||||
|
}
|
|
@ -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 <ReqdExctnDt> ist der 1999-01-01 einzustellen.
|
||||||
|
*/
|
||||||
|
const val RequestedExecutionDateValueForNotScheduledTransfers = "1999-01-01"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, String>): 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String, String>,
|
||||||
|
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)
|
|
@ -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
|
||||||
|
)
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>$MessageId$</MsgId><CreDtTm>$CreationDateTime$</CreDtTm><NbOfTxs>$NumberOfTransactions$</NbOfTxs><CtrlSum>$Amount$</CtrlSum><InitgPty><Nm>$DebitorName$</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>$PaymentInformationId$</PmtInfId><PmtMtd>TRF</PmtMtd><NbOfTxs>$NumberOfTransactions$</NbOfTxs><CtrlSum>$Amount$</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt>$RequestedExecutionDate$</ReqdExctnDt><Dbtr><Nm>$DebitorName$</Nm></Dbtr><DbtrAcct><Id><IBAN>$DebitorIban$</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BIC>$DebitorBic$</BIC></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><EndToEndId>NOTPROVIDED</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">$Amount$</InstdAmt></Amt><CdtrAgt><FinInstnId><BIC>$CreditorBic$</BIC></FinInstnId></CdtrAgt><Cdtr><Nm>$CreditorName$</Nm></Cdtr><CdtrAcct><Id><IBAN>$CreditorIban$</IBAN></Id></CdtrAcct><RmtInf><Ustrd>$Usage$</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document>
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue