Implemented hbci4jBankingClient
This commit is contained in:
parent
5b5173132f
commit
cfcad3f5e0
|
@ -22,6 +22,7 @@ dependencies {
|
|||
implementation project(':BankingJavaFxControls')
|
||||
|
||||
implementation project(':fints4javaBankingClient')
|
||||
implementation project(':hbci4jBankingClient')
|
||||
|
||||
implementation project(':BankingPersistenceJson')
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package net.dankito.banking.javafx.dialogs.mainwindow
|
||||
|
||||
import javafx.scene.control.SplitPane
|
||||
import net.dankito.banking.fints4javaBankingClientCreator
|
||||
import net.dankito.banking.hbci4jBankingClientCreator
|
||||
import net.dankito.banking.persistence.BankingPersistenceJson
|
||||
import net.dankito.banking.ui.javafx.RouterJavaFx
|
||||
import net.dankito.banking.ui.javafx.controls.AccountTransactionsView
|
||||
import net.dankito.banking.ui.javafx.controls.AccountsView
|
||||
import net.dankito.banking.ui.javafx.dialogs.mainwindow.controls.MainMenuBar
|
||||
import net.dankito.banking.ui.javafx.util.Base64ServiceJava8
|
||||
import net.dankito.banking.ui.presenter.MainWindowPresenter
|
||||
import tornadofx.*
|
||||
import tornadofx.FX.Companion.messages
|
||||
|
@ -18,7 +17,8 @@ class MainWindow : View(messages["application.title"]) {
|
|||
|
||||
private val dataFolder = File("data", "accounts")
|
||||
|
||||
private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder, BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceJava8(), RouterJavaFx())
|
||||
// private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(OkHttpWebClient(), Base64ServiceJava8()), dataFolder, BankingPersistenceJson(File(dataFolder, "accounts.json")), RouterJavaFx())
|
||||
private val presenter = MainWindowPresenter(hbci4jBankingClientCreator(), dataFolder, BankingPersistenceJson(File(dataFolder, "accounts.json")), RouterJavaFx())
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package net.dankito.banking.ui
|
||||
|
||||
import net.dankito.banking.util.IBase64Service
|
||||
import net.dankito.fints.model.BankInfo
|
||||
import net.dankito.utils.IThreadPool
|
||||
import net.dankito.utils.web.client.IWebClient
|
||||
import java.io.File
|
||||
|
||||
|
||||
interface IBankingClientCreator {
|
||||
|
||||
fun createClient(bankInfo: BankInfo, // TODO: create own value object to get rid off fints4java dependency
|
||||
fun createClient(
|
||||
bankInfo: BankInfo, // TODO: create own value object to get rid off fints4java dependency
|
||||
customerId: String,
|
||||
pin: String,
|
||||
webClient: IWebClient, // TODO: wrap away JavaUtils IWebClient
|
||||
base64Service: IBase64Service,
|
||||
threadPool: IThreadPool, // TODO: wrap away JavaUtils IThreadPool
|
||||
dataFolder: File,
|
||||
threadPool: IThreadPool, // TODO: wrap away JavaUtils IWebClient
|
||||
callback: BankingClientCallback
|
||||
): IBankingClient
|
||||
|
||||
|
|
|
@ -16,14 +16,11 @@ import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
|
|||
import net.dankito.banking.ui.model.tan.EnterTanResult
|
||||
import net.dankito.banking.ui.model.tan.TanChallenge
|
||||
import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
|
||||
import net.dankito.banking.util.IBase64Service
|
||||
import net.dankito.fints.banks.BankFinder
|
||||
import net.dankito.fints.model.BankInfo
|
||||
import net.dankito.utils.IThreadPool
|
||||
import net.dankito.utils.ThreadPool
|
||||
import net.dankito.utils.extensions.ofMaxLength
|
||||
import net.dankito.utils.web.client.IWebClient
|
||||
import net.dankito.utils.web.client.OkHttpWebClient
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
|
@ -35,9 +32,7 @@ open class MainWindowPresenter(
|
|||
protected val bankingClientCreator: IBankingClientCreator,
|
||||
protected val dataFolder: File,
|
||||
protected val persister: IBankingPersistence,
|
||||
protected val base64Service: IBase64Service,
|
||||
protected val router: IRouter,
|
||||
protected val webClient: IWebClient = OkHttpWebClient(),
|
||||
protected val threadPool: IThreadPool = ThreadPool()
|
||||
) {
|
||||
|
||||
|
@ -107,7 +102,7 @@ open class MainWindowPresenter(
|
|||
val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null)
|
||||
|
||||
val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password,
|
||||
dataFolder, webClient, base64Service, threadPool, callback)
|
||||
dataFolder, threadPool, callback)
|
||||
|
||||
try {
|
||||
newClient.restoreData()
|
||||
|
@ -135,7 +130,7 @@ open class MainWindowPresenter(
|
|||
// TODO: move BankInfo out of fints4javaLib
|
||||
open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) {
|
||||
|
||||
val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, dataFolder, webClient, base64Service, threadPool, this.callback)
|
||||
val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, dataFolder, threadPool, this.callback)
|
||||
|
||||
newClient.addAccountAsync { response ->
|
||||
val account = response.account
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// TODO: move to versions.gradle
|
||||
ext {
|
||||
appVersionName = '0.1.0-SNAPSHOT'
|
||||
|
@ -9,6 +8,10 @@ ext {
|
|||
|
||||
javaUtilsVersion = '1.0.9'
|
||||
|
||||
|
||||
hbci4jVersion = '3.1.37'
|
||||
|
||||
|
||||
androidUtilsVersion = '1.1.0'
|
||||
|
||||
clansFloatingActionButtonVersion = '1.6.4'
|
||||
|
|
|
@ -21,6 +21,7 @@ import net.dankito.banking.fints4javaBankingClientCreator
|
|||
import net.dankito.banking.persistence.BankingPersistenceJson
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.presenter.MainWindowPresenter
|
||||
import net.dankito.utils.web.client.OkHttpWebClient
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
|
@ -47,8 +48,8 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
val dataFolder = File(this.filesDir, "data/accounts")
|
||||
|
||||
presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder,
|
||||
BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceAndroid(), RouterAndroid(this))
|
||||
presenter = MainWindowPresenter(fints4javaBankingClientCreator(OkHttpWebClient(), Base64ServiceAndroid()), dataFolder,
|
||||
BankingPersistenceJson(File(dataFolder, "accounts.json")), RouterAndroid(this))
|
||||
|
||||
initUi()
|
||||
}
|
||||
|
|
|
@ -8,24 +8,24 @@ import net.dankito.banking.util.UiCommonBase64ServiceWrapper
|
|||
import net.dankito.fints.model.BankInfo
|
||||
import net.dankito.utils.IThreadPool
|
||||
import net.dankito.utils.web.client.IWebClient
|
||||
import net.dankito.utils.web.client.OkHttpWebClient
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class fints4javaBankingClientCreator : IBankingClientCreator {
|
||||
open class fints4javaBankingClientCreator(
|
||||
protected val webClient: IWebClient,
|
||||
protected val base64Service: IBase64Service
|
||||
) : IBankingClientCreator {
|
||||
|
||||
override fun createClient(
|
||||
bankInfo: BankInfo,
|
||||
customerId: String,
|
||||
pin: String,
|
||||
dataFolder: File,
|
||||
webClient: IWebClient,
|
||||
base64Service: IBase64Service,
|
||||
threadPool: IThreadPool,
|
||||
callback: BankingClientCallback
|
||||
): IBankingClient {
|
||||
|
||||
return fints4javaBankingClient(bankInfo, customerId, pin, dataFolder, OkHttpWebClient(), UiCommonBase64ServiceWrapper(base64Service), threadPool, callback)
|
||||
return fints4javaBankingClient(bankInfo, customerId, pin, dataFolder, webClient, UiCommonBase64ServiceWrapper(base64Service), threadPool, callback)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
|
||||
sourceCompatibility = "1.8"
|
||||
targetCompatibility = "1.8"
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
api project(':BankingUiCommon')
|
||||
implementation "com.github.hbci4j:hbci4j-core:$hbci4jVersion"
|
||||
|
||||
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package net.dankito.banking
|
||||
|
||||
import net.dankito.banking.model.AccountCredentials
|
||||
import net.dankito.banking.ui.BankingClientCallback
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.model.tan.FlickerCodeTanChallenge
|
||||
import net.dankito.banking.ui.model.tan.ImageTanChallenge
|
||||
import net.dankito.banking.ui.model.tan.TanChallenge
|
||||
import net.dankito.banking.ui.model.tan.TanImage
|
||||
import net.dankito.banking.util.hbci4jModelMapper
|
||||
import org.kapott.hbci.callback.AbstractHBCICallback
|
||||
import org.kapott.hbci.callback.HBCICallback
|
||||
import org.kapott.hbci.manager.HBCIUtils
|
||||
import org.kapott.hbci.manager.MatrixCode
|
||||
import org.kapott.hbci.manager.QRCode
|
||||
import org.kapott.hbci.passport.HBCIPassport
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Ueber diesen Callback kommuniziert HBCI4Java mit dem Benutzer und fragt die benoetigten
|
||||
* Informationen wie Benutzerkennung, PIN usw. ab.
|
||||
*/
|
||||
open class HbciCallback(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val account: Account,
|
||||
protected val mapper: hbci4jModelMapper,
|
||||
protected val callback: BankingClientCallback
|
||||
) : AbstractHBCICallback() {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(HbciCallback::class.java)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see org.kapott.hbci.callback.HBCICallback.log
|
||||
*/
|
||||
override fun log(msg: String, level: Int, date: Date, trace: StackTraceElement) {
|
||||
// Ausgabe von Log-Meldungen bei Bedarf
|
||||
when (level) {
|
||||
HBCIUtils.LOG_ERR -> log.error(msg)
|
||||
HBCIUtils.LOG_WARN -> log.warn(msg)
|
||||
HBCIUtils.LOG_INFO-> log.info(msg)
|
||||
HBCIUtils.LOG_DEBUG, HBCIUtils.LOG_DEBUG2 -> log.debug(msg)
|
||||
else -> log.trace(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.kapott.hbci.callback.HBCICallback.callback
|
||||
*/
|
||||
override fun callback(passport: HBCIPassport, reason: Int, msg: String, datatype: Int, retData: StringBuffer) {
|
||||
log.info("Callback: [$reason] $msg ($retData)") // TODO: remove again
|
||||
|
||||
// Diese Funktion ist wichtig. Ueber die fragt HBCI4Java die benoetigten Daten von uns ab.
|
||||
when (reason) {
|
||||
// Mit dem Passwort verschluesselt HBCI4Java die Passport-Datei.
|
||||
// Wir nehmen hier der Einfachheit halber direkt die PIN. In der Praxis
|
||||
// sollte hier aber ein staerkeres Passwort genutzt werden.
|
||||
// Die Ergebnis-Daten muessen in dem StringBuffer "retData" platziert werden.
|
||||
// if you like or need to change your pin, return your old one for NEED_PASSPHRASE_LOAD and your new
|
||||
// one for NEED_PASSPHRASE_SAVE
|
||||
HBCICallback.NEED_PASSPHRASE_LOAD, HBCICallback.NEED_PASSPHRASE_SAVE -> retData.replace(0, retData.length, credentials.password)
|
||||
|
||||
|
||||
/* Customer (authentication) data */
|
||||
|
||||
// BLZ wird benoetigt
|
||||
HBCICallback.NEED_BLZ -> retData.replace(0, retData.length, credentials.bankCode)
|
||||
|
||||
// Die Benutzerkennung
|
||||
HBCICallback.NEED_USERID -> retData.replace(0, retData.length, credentials.customerId)
|
||||
|
||||
// Die Kundenkennung. Meist identisch mit der Benutzerkennung.
|
||||
// Bei manchen Banken kann man die auch leer lassen
|
||||
HBCICallback.NEED_CUSTOMERID -> retData.replace(0, retData.length, credentials.customerId)
|
||||
|
||||
// PIN wird benoetigt
|
||||
HBCICallback.NEED_PT_PIN -> retData.replace(0, retData.length, credentials.password)
|
||||
|
||||
|
||||
/* TAN */
|
||||
|
||||
// ADDED: Auswaehlen welches PinTan Verfahren verwendet werden soll
|
||||
HBCICallback.NEED_PT_SECMECH -> selectTanProcedure(retData.toString())?.let { selectedTanProcedure ->
|
||||
account.selectedTanProcedure = selectedTanProcedure
|
||||
retData.replace(0, retData.length, selectedTanProcedure.bankInternalProcedureCode)
|
||||
}
|
||||
|
||||
// chipTan or simple TAN request (iTAN, smsTAN, ...)
|
||||
HBCICallback.NEED_PT_TAN -> {
|
||||
getTanFromUser(account, msg, retData.toString())?.let { enteredTan ->
|
||||
retData.replace(0, retData.length, enteredTan)
|
||||
}
|
||||
}
|
||||
|
||||
// chipTAN-QR
|
||||
HBCICallback.NEED_PT_QRTAN -> { // use class QRCode to display QR code
|
||||
val qrData = retData.toString()
|
||||
val qrCode = QRCode(qrData, msg)
|
||||
val enterTanResult = callback.enterTan(account, ImageTanChallenge(TanImage(qrCode.mimetype, qrCode.image), msg, account.selectedTanProcedure!!))
|
||||
enterTanResult.enteredTan?.let { enteredTan ->
|
||||
retData.replace(0, retData.length, enteredTan)
|
||||
}
|
||||
}
|
||||
|
||||
// photoTan
|
||||
HBCICallback.NEED_PT_PHOTOTAN -> { // use class MatrixCode to display photo
|
||||
val matrixCode = MatrixCode(retData.toString())
|
||||
val enterTanResult = callback.enterTan(account, ImageTanChallenge(TanImage(matrixCode.mimetype, matrixCode.image), msg, account.selectedTanProcedure!!))
|
||||
enterTanResult.enteredTan?.let { enteredTan ->
|
||||
retData.replace(0, retData.length, enteredTan)
|
||||
}
|
||||
}
|
||||
|
||||
// smsTan: Select cell phone to which SMS should be send
|
||||
HBCICallback.NEED_PT_TANMEDIA -> {
|
||||
log.info("TODO: select cell phone: $msg ($retData)")
|
||||
}
|
||||
|
||||
// wrong pin entered -> inform user
|
||||
HBCICallback.WRONG_PIN -> {
|
||||
log.info("TODO: user entered wrong pin: $msg ($retData)")
|
||||
}
|
||||
|
||||
// UserId changed -> inform user
|
||||
HBCICallback.USERID_CHANGED -> { // im Parameter retData stehen die neuen Daten im Format UserID|CustomerID drin
|
||||
log.info("TODO: UserId changed: $msg ($retData)")
|
||||
}
|
||||
|
||||
// user entered wrong Banleitzahl or Kontonummer -> inform user
|
||||
HBCICallback.HAVE_CRC_ERROR -> { // retData contains wrong values in form "BLZ|KONTONUMMER". Set correct ones in the same form in retData
|
||||
log.info("TODO: wrong Banleitzahl or Kontonummer entered: $msg ($retData)")
|
||||
}
|
||||
|
||||
// user entered wrong IBAN -> inform user
|
||||
HBCICallback.HAVE_IBAN_ERROR -> { // retData contains wrong IBAN. Set correct IBAN in retData
|
||||
log.info("TODO: wrong IBAN entered: $msg ($retData)")
|
||||
}
|
||||
|
||||
// message from bank to user. should get displayed to user
|
||||
HBCICallback.HAVE_INST_MSG -> {
|
||||
// TODO: inform user
|
||||
log.error("TODO: inform user, received a message from bank: $msg\n$retData")
|
||||
}
|
||||
|
||||
// Manche Fehlermeldungen werden hier ausgegeben
|
||||
HBCICallback.HAVE_ERROR -> { // to ignore error set an empty String in retData
|
||||
// TODO: inform user
|
||||
log.error("TODO: inform user, error occurred: $msg\n$retData")
|
||||
}
|
||||
|
||||
else -> { // Wir brauchen nicht alle der Callbacks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.kapott.hbci.callback.HBCICallback.status
|
||||
*/
|
||||
override fun status(passport: HBCIPassport, statusTag: Int, o: Array<Any>?) {
|
||||
// So aehnlich wie log(String,int,Date,StackTraceElement) jedoch fuer Status-Meldungen.
|
||||
val param = if (o == null) "" else o.joinToString()
|
||||
|
||||
when (statusTag) {
|
||||
HBCICallback.STATUS_MSG_RAW_SEND -> log.debug("Sending message:\n$param")
|
||||
HBCICallback.STATUS_MSG_RAW_RECV -> log.debug("Received message:\n$param")
|
||||
// else -> log.debug("New status [$statusTag]: $param")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open fun getTanFromUser(account: Account, messageToShowToUser: String, challengeHHD_UC: String): String? {
|
||||
// Wenn per "retData" Daten uebergeben wurden, dann enthalten diese
|
||||
// den fuer chipTAN optisch zu verwendenden Flickercode.
|
||||
// Falls nicht, ist es eine TAN-Abfrage, fuer die keine weiteren
|
||||
// Parameter benoetigt werden (z.B. smsTAN, iTAN oder aehnliches)
|
||||
|
||||
// Die Variable "msg" aus der Methoden-Signatur enthaelt uebrigens
|
||||
// den bankspezifischen Text mit den Instruktionen fuer den User.
|
||||
// Der Text aus "msg" sollte daher im Dialog dem User angezeigt
|
||||
// werden.
|
||||
|
||||
val enterTanResult = if (challengeHHD_UC.isNullOrEmpty()) {
|
||||
callback.enterTan(account, TanChallenge(messageToShowToUser, account.selectedTanProcedure!!))
|
||||
}
|
||||
else {
|
||||
// for Sparkasse messageToShowToUser started with "chipTAN optisch\nTAN-Nummer\n\n"
|
||||
val usefulMessage = messageToShowToUser.split("\n").last().trim()
|
||||
|
||||
// val parsedDataSet = FlickerCode(challengeHHD_UC).render()
|
||||
callback.enterTan(account, FlickerCodeTanChallenge(net.dankito.banking.ui.model.tan.FlickerCode("", challengeHHD_UC), usefulMessage, account.selectedTanProcedure!!))
|
||||
}
|
||||
|
||||
return enterTanResult.enteredTan
|
||||
}
|
||||
|
||||
|
||||
|
||||
open fun selectTanProcedure(supportedTanProceduresString: String): net.dankito.banking.ui.model.tan.TanProcedure? {
|
||||
val supportedTanProcedures = mapper.mapTanProcedures(supportedTanProceduresString)
|
||||
|
||||
account.supportedTanProcedures = supportedTanProcedures
|
||||
|
||||
if (supportedTanProcedures.isNotEmpty()) {
|
||||
// select any procedure, user then can select her preferred one in EnterTanDialog; try not to select 'chipTAN manuell'
|
||||
return supportedTanProcedures.firstOrNull { it.displayName.contains("manuell", true) == false }
|
||||
?: supportedTanProcedures.firstOrNull()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,367 @@
|
|||
package net.dankito.banking
|
||||
|
||||
import net.dankito.banking.model.AccountCredentials
|
||||
import net.dankito.banking.model.ConnectResult
|
||||
import net.dankito.banking.ui.BankingClientCallback
|
||||
import net.dankito.banking.ui.IBankingClient
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.model.AccountTransaction
|
||||
import net.dankito.banking.ui.model.Bank
|
||||
import net.dankito.banking.ui.model.BankAccount
|
||||
import net.dankito.banking.ui.model.parameters.GetTransactionsParameter
|
||||
import net.dankito.banking.ui.model.parameters.TransferMoneyData
|
||||
import net.dankito.banking.ui.model.responses.AddAccountResponse
|
||||
import net.dankito.banking.ui.model.responses.BankingClientResponse
|
||||
import net.dankito.banking.ui.model.responses.GetTransactionsResponse
|
||||
import net.dankito.banking.util.AccountTransactionMapper
|
||||
import net.dankito.banking.util.hbci4jModelMapper
|
||||
import net.dankito.fints.model.BankInfo
|
||||
import net.dankito.utils.IThreadPool
|
||||
import net.dankito.utils.ThreadPool
|
||||
import org.kapott.hbci.GV.HBCIJob
|
||||
import org.kapott.hbci.GV_Result.GVRKUms
|
||||
import org.kapott.hbci.GV_Result.GVRSaldoReq
|
||||
import org.kapott.hbci.manager.HBCIHandler
|
||||
import org.kapott.hbci.manager.HBCIUtils
|
||||
import org.kapott.hbci.manager.HBCIVersion
|
||||
import org.kapott.hbci.passport.AbstractHBCIPassport
|
||||
import org.kapott.hbci.passport.HBCIPassport
|
||||
import org.kapott.hbci.status.HBCIExecStatus
|
||||
import org.kapott.hbci.structures.Value
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
open class hbci4jBankingClient(
|
||||
bankInfo: BankInfo,
|
||||
customerId: String,
|
||||
pin: String,
|
||||
protected val dataFolder: File,
|
||||
protected val threadPool: IThreadPool = ThreadPool(),
|
||||
protected val callback: BankingClientCallback
|
||||
) : IBankingClient {
|
||||
|
||||
companion object {
|
||||
// the date format is hard coded in HBCIUtils.string2DateISO()
|
||||
val HbciLibDateFormat = SimpleDateFormat("yyyy-MM-dd")
|
||||
|
||||
const val NinetyDaysInMilliseconds = 90 * 24 * 60 * 60 * 1000L
|
||||
|
||||
private val log = LoggerFactory.getLogger(hbci4jBankingClient::class.java)
|
||||
}
|
||||
|
||||
|
||||
protected val credentials = AccountCredentials(bankInfo.bankCode, customerId, pin)
|
||||
|
||||
protected var bank = Bank(bankInfo.name, bankInfo.bankCode, bankInfo.bic, bankInfo.pinTanAddress ?: "")
|
||||
|
||||
protected var account = Account(bank, customerId, pin, "")
|
||||
|
||||
|
||||
protected val mapper = hbci4jModelMapper()
|
||||
|
||||
protected val accountTransactionMapper = AccountTransactionMapper()
|
||||
|
||||
|
||||
|
||||
override fun addAccountAsync(callback: (AddAccountResponse) -> Unit) {
|
||||
threadPool.runAsync {
|
||||
callback(addAccount())
|
||||
}
|
||||
}
|
||||
|
||||
open fun addAccount(): AddAccountResponse {
|
||||
val connection = connect()
|
||||
closeConnection(connection)
|
||||
|
||||
if(connection.successful) {
|
||||
connection.passport?.let { passport ->
|
||||
val accounts = passport.accounts
|
||||
if (accounts == null || accounts.size == 0) {
|
||||
log.error("Keine Konten ermittelbar")
|
||||
return AddAccountResponse(false, "Keine Konten ermittelbar", account) // TODO: translate
|
||||
}
|
||||
|
||||
this.account.bankAccounts = mapper.mapBankAccounts(account, accounts, passport)
|
||||
|
||||
val transactionsOfLast90DaysResponse = tryToRetrieveAccountTransactionsForAddedAccounts(account)
|
||||
|
||||
return AddAccountResponse(true, null, account, transactionsOfLast90DaysResponse.isSuccessful,
|
||||
transactionsOfLast90DaysResponse.bookedTransactions, transactionsOfLast90DaysResponse.unbookedTransactions,
|
||||
transactionsOfLast90DaysResponse.balances)
|
||||
}
|
||||
}
|
||||
|
||||
return AddAccountResponse(false, null, account, error = connection.error)
|
||||
}
|
||||
|
||||
protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(account: Account): GetTransactionsResponse {
|
||||
val transactionsOfLast90DaysResponses = mutableListOf<GetTransactionsResponse>()
|
||||
val balances = mutableMapOf<BankAccount, BigDecimal>()
|
||||
val bookedTransactions = mutableMapOf<BankAccount, List<AccountTransaction>>()
|
||||
val unbookedTransactions = mutableMapOf<BankAccount, List<Any>>()
|
||||
|
||||
account.bankAccounts.forEach { bankAccount ->
|
||||
if (bankAccount.supportsRetrievingAccountTransactions) {
|
||||
val response = getTransactionsOfLast90Days(bankAccount)
|
||||
transactionsOfLast90DaysResponses.add(response)
|
||||
|
||||
response.bookedTransactions[bankAccount]?.let { bookedTransactions.put(bankAccount, it) }
|
||||
response.unbookedTransactions[bankAccount]?.let { unbookedTransactions.put(bankAccount, it) }
|
||||
response.balances[bankAccount]?.let { balances.put(bankAccount, it) }
|
||||
}
|
||||
}
|
||||
|
||||
val supportsRetrievingTransactionsOfLast90DaysWithoutTan = transactionsOfLast90DaysResponses.firstOrNull { it.isSuccessful } != null
|
||||
|
||||
return GetTransactionsResponse(supportsRetrievingTransactionsOfLast90DaysWithoutTan, null, bookedTransactions, unbookedTransactions, balances)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* According to PSD2 for the accounting entries of the last 90 days the two-factor authorization does not have to
|
||||
* be applied. It depends on the bank if they request a second factor or not.
|
||||
*
|
||||
* So we simply try to retrieve at accounting entries of the last 90 days and see if a second factor is required
|
||||
* or not.
|
||||
*/
|
||||
open fun getTransactionsOfLast90DaysAsync(bankAccount: BankAccount, callback: (GetTransactionsResponse) -> Unit) {
|
||||
threadPool.runAsync {
|
||||
callback(getTransactionsOfLast90Days(bankAccount))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* According to PSD2 for the accounting entries of the last 90 days the two-factor authorization does not have to
|
||||
* be applied. It depends on the bank if they request a second factor or not.
|
||||
*
|
||||
* So we simply try to retrieve at accounting entries of the last 90 days and see if a second factor is required
|
||||
* or not.
|
||||
*/
|
||||
open fun getTransactionsOfLast90Days(bankAccount: BankAccount): GetTransactionsResponse {
|
||||
val ninetyDaysAgo = Date(Date().time - NinetyDaysInMilliseconds)
|
||||
|
||||
return getTransactions(bankAccount, GetTransactionsParameter(bankAccount.supportsRetrievingBalance, ninetyDaysAgo))
|
||||
}
|
||||
|
||||
override fun getTransactionsAsync(bankAccount: BankAccount, parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
|
||||
threadPool.runAsync {
|
||||
callback(getTransactions(bankAccount, parameter))
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun getTransactions(bankAccount: BankAccount, parameter: GetTransactionsParameter): GetTransactionsResponse {
|
||||
val connection = connect()
|
||||
|
||||
connection.handle?.let { handle ->
|
||||
try {
|
||||
val (nullableBalanceJob, accountTransactionsJob, status) = executeJobsForGetAccountingEntries(handle, bankAccount, parameter)
|
||||
|
||||
// Pruefen, ob die Kommunikation mit der Bank grundsaetzlich geklappt hat
|
||||
if (!status.isOK) {
|
||||
log.error("Could not connect to bank ${credentials.bankCode} ${status.toString()}: ${status.errorString}")
|
||||
return GetTransactionsResponse(false, null, error = Exception("Could not connect to bank ${credentials.bankCode}: ${status.toString()}"))
|
||||
}
|
||||
|
||||
// Auswertung des Saldo-Abrufs.
|
||||
var balance = BigDecimal.ZERO
|
||||
if (parameter.alsoRetrieveBalance && nullableBalanceJob != null) {
|
||||
val balanceResult = nullableBalanceJob.jobResult as GVRSaldoReq
|
||||
if(balanceResult.isOK == false) {
|
||||
log.error("Could not fetch balance of bank account $bankAccount: $balanceResult", balanceResult.getJobStatus().exceptions)
|
||||
return GetTransactionsResponse(false, null, error = Exception("Could not fetch balance of bank account $bankAccount: $balanceResult"))
|
||||
}
|
||||
|
||||
balance = balanceResult.entries[0].ready.value.bigDecimalValue
|
||||
}
|
||||
|
||||
|
||||
// Das Ergebnis des Jobs koennen wir auf "GVRKUms" casten. Jobs des Typs "KUmsAll"
|
||||
// liefern immer diesen Typ.
|
||||
val result = accountTransactionsJob.jobResult as GVRKUms
|
||||
|
||||
// Pruefen, ob der Abruf der Umsaetze geklappt hat
|
||||
if (result.isOK == false) {
|
||||
log.error("Could not get fetch account transactions of bank account $bankAccount: $result", result.getJobStatus().exceptions)
|
||||
return GetTransactionsResponse(false, null, error = Exception("Could not fetch account transactions of bank account $bankAccount: $result"))
|
||||
}
|
||||
|
||||
return GetTransactionsResponse(true, null, mapOf(bankAccount to accountTransactionMapper.mapAccountTransactions(bankAccount, result)),
|
||||
mapOf(), mapOf(bankAccount to balance))
|
||||
}
|
||||
catch(e: Exception) {
|
||||
log.error("Could not get accounting details for bank ${credentials.bankCode}", e)
|
||||
return GetTransactionsResponse(false, null, error = e)
|
||||
}
|
||||
finally {
|
||||
closeConnection(connection)
|
||||
}
|
||||
}
|
||||
|
||||
closeConnection(connection)
|
||||
|
||||
return GetTransactionsResponse(false, null, error = connection.error)
|
||||
}
|
||||
|
||||
protected open fun executeJobsForGetAccountingEntries(handle: HBCIHandler, bankAccount: BankAccount, parameter: GetTransactionsParameter): Triple<HBCIJob?, HBCIJob, HBCIExecStatus> {
|
||||
val konto = mapper.mapToKonto(bank, bankAccount)
|
||||
|
||||
// 1. Auftrag fuer das Abrufen des Saldos erzeugen
|
||||
var balanceJob: HBCIJob? = null
|
||||
if (parameter.alsoRetrieveBalance) {
|
||||
val createdBalanceJob = handle.newJob("SaldoReq")
|
||||
createdBalanceJob.setParam("my", konto) // festlegen, welches Konto abgefragt werden soll.
|
||||
createdBalanceJob.addToQueue() // Zur Liste der auszufuehrenden Auftraege hinzufuegen
|
||||
|
||||
balanceJob = createdBalanceJob
|
||||
}
|
||||
// 2. Auftrag fuer das Abrufen der Umsaetze erzeugen
|
||||
val accountTransactionsJob = handle.newJob("KUmsAll")
|
||||
accountTransactionsJob.setParam("my", konto) // festlegen, welches Konto abgefragt werden soll.
|
||||
// evtl. Datum setzen, ab welchem die Auszüge geholt werden sollen
|
||||
parameter.fromDate?.let {
|
||||
accountTransactionsJob.setParam("startdate", HbciLibDateFormat.format(it))
|
||||
}
|
||||
accountTransactionsJob.addToQueue() // Zur Liste der auszufuehrenden Auftraege hinzufuegen
|
||||
|
||||
// Hier koennen jetzt noch weitere Auftraege fuer diesen Bankzugang hinzugefuegt
|
||||
// werden. Z.Bsp. Ueberweisungen.
|
||||
|
||||
// Alle Auftraege aus der Liste ausfuehren.
|
||||
val status = handle.execute()
|
||||
|
||||
return Triple(balanceJob, accountTransactionsJob, status)
|
||||
}
|
||||
|
||||
|
||||
override fun transferMoneyAsync(data: TransferMoneyData, bankAccount: BankAccount, callback: (BankingClientResponse) -> Unit) {
|
||||
threadPool.runAsync {
|
||||
callback(transferMoney(data, bankAccount))
|
||||
}
|
||||
}
|
||||
|
||||
open fun transferMoney(data: TransferMoneyData, bankAccount: BankAccount): BankingClientResponse {
|
||||
val connection = connect()
|
||||
|
||||
connection.handle?.let { handle ->
|
||||
try {
|
||||
createTransferCashJob(handle, data, bankAccount)
|
||||
|
||||
val status = handle.execute()
|
||||
|
||||
return BankingClientResponse(status.isOK, status.toString())
|
||||
} catch(e: Exception) {
|
||||
log.error("Could not transfer cash for account $bankAccount" , e)
|
||||
return BankingClientResponse(false, e.localizedMessage, e)
|
||||
}
|
||||
finally {
|
||||
closeConnection(connection)
|
||||
}
|
||||
}
|
||||
|
||||
return BankingClientResponse(false, "Could not connect", connection.error)
|
||||
}
|
||||
|
||||
protected open fun createTransferCashJob(handle: HBCIHandler, data: TransferMoneyData, bankAccount: BankAccount) {
|
||||
val transferCashJob = handle.newJob("UebSEPA")
|
||||
|
||||
val source = mapper.mapToKonto(bank, bankAccount)
|
||||
val destination = mapper.mapToKonto(data)
|
||||
val amount = Value(data.amount, "EUR")
|
||||
|
||||
transferCashJob.setParam("src", source)
|
||||
transferCashJob.setParam("dst", destination)
|
||||
transferCashJob.setParam("btg", amount)
|
||||
transferCashJob.setParam("usage", data.usage)
|
||||
|
||||
transferCashJob.addToQueue()
|
||||
}
|
||||
|
||||
|
||||
override fun restoreData() {
|
||||
// nothing to do for hbci4j
|
||||
}
|
||||
|
||||
|
||||
protected open fun connect(): ConnectResult {
|
||||
return connect(credentials, HBCIVersion.HBCI_300)
|
||||
}
|
||||
|
||||
protected open fun connect(credentials: AccountCredentials, version: HBCIVersion): ConnectResult {
|
||||
// HBCI4Java initialisieren
|
||||
// In "props" koennen optional Kernel-Parameter abgelegt werden, die in der Klasse
|
||||
// org.kapott.hbci.manager.HBCIUtils (oben im Javadoc) beschrieben sind.
|
||||
val props = Properties()
|
||||
HBCIUtils.init(props, HbciCallback(credentials, account, mapper, callback))
|
||||
|
||||
// In der Passport-Datei speichert HBCI4Java die Daten des Bankzugangs (Bankparameterdaten, Benutzer-Parameter, etc.).
|
||||
// Die Datei kann problemlos geloescht werden. Sie wird beim naechsten mal automatisch neu erzeugt,
|
||||
// wenn der Parameter "client.passport.PinTan.init" den Wert "1" hat (siehe unten).
|
||||
// Wir speichern die Datei der Einfachheit halber im aktuellen Verzeichnis.
|
||||
dataFolder.mkdirs()
|
||||
val passportFile = File(dataFolder,"passport.dat")
|
||||
|
||||
// Wir setzen die Kernel-Parameter zur Laufzeit. Wir koennten sie alternativ
|
||||
// auch oben in "props" setzen.
|
||||
HBCIUtils.setParam("client.passport.default", "PinTan") // Legt als Verfahren PIN/TAN fest.
|
||||
HBCIUtils.setParam("client.passport.PinTan.filename", passportFile.absolutePath)
|
||||
HBCIUtils.setParam("client.passport.PinTan.init", "1")
|
||||
|
||||
var handle: HBCIHandler? = null
|
||||
var passport: HBCIPassport? = null
|
||||
|
||||
try {
|
||||
// Erzeugen des Passport-Objektes.
|
||||
passport = AbstractHBCIPassport.getInstance()
|
||||
|
||||
// Konfigurieren des Passport-Objektes.
|
||||
// Das kann alternativ auch alles ueber den Callback unten geschehen
|
||||
|
||||
// Das Land.
|
||||
passport.country = "DE"
|
||||
|
||||
// Server-Adresse angeben. Koennen wir entweder manuell eintragen oder direkt von HBCI4Java ermitteln lassen
|
||||
val info = HBCIUtils.getBankInfo(credentials.bankCode)
|
||||
passport.host = info.pinTanAddress
|
||||
|
||||
// TCP-Port des Servers. Bei PIN/TAN immer 443, da das ja ueber HTTPS laeuft.
|
||||
passport.port = 443
|
||||
|
||||
// Art der Nachrichten-Codierung. Bei Chipkarte/Schluesseldatei wird
|
||||
// "None" verwendet. Bei PIN/TAN kommt "Base64" zum Einsatz.
|
||||
passport.filterType = "Base64"
|
||||
|
||||
// Verbindung zum Server aufbauen
|
||||
handle = HBCIHandler(version.getId(), passport)
|
||||
|
||||
|
||||
}
|
||||
catch(e: Exception) {
|
||||
log.error("Could not connect to bank ${credentials.bankCode}", e)
|
||||
closeConnection(handle, passport)
|
||||
|
||||
return ConnectResult(false, e)
|
||||
}
|
||||
|
||||
return ConnectResult(true, null, handle, passport)
|
||||
}
|
||||
|
||||
protected open fun closeConnection(connection: ConnectResult) {
|
||||
closeConnection(connection.handle, connection.passport)
|
||||
}
|
||||
|
||||
protected open fun closeConnection(handle: HBCIHandler?, passport: HBCIPassport?) {
|
||||
// Sicherstellen, dass sowohl Passport als auch Handle nach Beendigung geschlossen werden.
|
||||
try {
|
||||
handle?.close()
|
||||
|
||||
passport?.close()
|
||||
|
||||
HBCIUtils.doneThread() // i hate static variables, here's one of the reasons why: Old callbacks and therefore credentials get stored in static variables and therefor always the first entered credentials have been used
|
||||
} catch(e: Exception) { log.error("Could not close connection", e) }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package net.dankito.banking
|
||||
|
||||
import net.dankito.banking.ui.BankingClientCallback
|
||||
import net.dankito.banking.ui.IBankingClient
|
||||
import net.dankito.banking.ui.IBankingClientCreator
|
||||
import net.dankito.fints.model.BankInfo
|
||||
import net.dankito.utils.IThreadPool
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class hbci4jBankingClientCreator : IBankingClientCreator {
|
||||
|
||||
override fun createClient(
|
||||
bankInfo: BankInfo,
|
||||
customerId: String,
|
||||
pin: String,
|
||||
dataFolder: File,
|
||||
threadPool: IThreadPool,
|
||||
callback: BankingClientCallback
|
||||
): IBankingClient {
|
||||
|
||||
return hbci4jBankingClient(bankInfo, customerId, pin, dataFolder, threadPool, callback)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package net.dankito.banking.model
|
||||
|
||||
|
||||
open class AccountCredentials(
|
||||
val bankCode: String,
|
||||
val customerId: String,
|
||||
var password: String
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
package net.dankito.banking.model
|
||||
|
||||
import org.kapott.hbci.manager.HBCIHandler
|
||||
import org.kapott.hbci.passport.HBCIPassport
|
||||
|
||||
|
||||
open class ConnectResult(
|
||||
val successful: Boolean,
|
||||
val error: Exception? = null,
|
||||
val handle: HBCIHandler? = null,
|
||||
val passport: HBCIPassport? = null
|
||||
)
|
|
@ -0,0 +1,162 @@
|
|||
package net.dankito.banking.util
|
||||
|
||||
import net.dankito.banking.ui.model.AccountTransaction
|
||||
import net.dankito.banking.ui.model.BankAccount
|
||||
import org.kapott.hbci.GV_Result.GVRKUms
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.math.BigDecimal
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
|
||||
open class AccountTransactionMapper {
|
||||
|
||||
companion object {
|
||||
protected val DateStartString = "DATUM "
|
||||
protected val DateEndString = " UHR"
|
||||
|
||||
protected val DateTimeFormat = SimpleDateFormat("dd.MM.yyyy,HH.mm")
|
||||
|
||||
protected val DateFormat = SimpleDateFormat("dd.MM.yyyy,")
|
||||
|
||||
private val log = LoggerFactory.getLogger(AccountTransactionMapper::class.java)
|
||||
}
|
||||
|
||||
|
||||
open fun mapAccountTransactions(bankAccount: BankAccount, result: GVRKUms): List<AccountTransaction> {
|
||||
val entries = ArrayList<AccountTransaction>()
|
||||
|
||||
result.flatData.forEach { transaction ->
|
||||
entries.add(mapAccountingEntry(bankAccount, transaction))
|
||||
}
|
||||
|
||||
log.debug("Retrieved ${result.flatData.size} accounting entries")
|
||||
|
||||
return entries.sortedByDescending { it.bookingDate }
|
||||
}
|
||||
|
||||
protected open fun mapAccountingEntry(bankAccount: BankAccount, transaction: GVRKUms.UmsLine): AccountTransaction {
|
||||
|
||||
val result = AccountTransaction(BigDecimal.valueOf(transaction.value.longValue).divide(BigDecimal.valueOf(100)), transaction.bdate, transaction.usage.joinToString(""),
|
||||
if (transaction.other.name2.isNullOrBlank() == false) transaction.other.name + " " + transaction.other.name2 else transaction.other.name,
|
||||
if (transaction.other.bic != null) transaction.other.bic else transaction.other.blz,
|
||||
if (transaction.other.iban != null) transaction.other.iban else transaction.other.number,
|
||||
transaction.text, BigDecimal.valueOf(transaction.saldo.value.longValue), transaction.value.curr, bankAccount)
|
||||
|
||||
// mapUsage(transaction, result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* From https://sites.google.com/a/crem-solutions.de/doku/version-2012-neu/buchhaltung/03-zahlungsverkehr/05-e-banking/technische-beschreibung-der-mt940-sta-datei:
|
||||
*
|
||||
* Weitere 4 Verwendungszwecke können zu den Feldschlüsseln 60 bis 63 eingestellt werden.
|
||||
* Jeder Bezeichner [z.B. EREF+] muss am Anfang eines Subfeldes [z. B. ?21] stehen.
|
||||
* Bei Längenüberschreitung wird im nachfolgenden Subfeld ohne Wiederholung des Bezeichners fortgesetzt. Bei Wechsel des Bezeichners ist ein neues Subfeld zu beginnen.
|
||||
* Belegung in der nachfolgenden Reihenfolge, wenn vorhanden:
|
||||
* EREF+[ Ende-zu-Ende Referenz ] (DD-AT10; CT-AT41 - Angabe verpflichtend; NOTPROVIDED wird nicht eingestellt.)
|
||||
* KREF+[Kundenreferenz]
|
||||
* MREF+[Mandatsreferenz] (DD-AT01 - Angabe verpflichtend)
|
||||
* CRED+[Creditor Identifier] (DD-AT02 - Angabe verpflichtend bei SEPA-Lastschriften, nicht jedoch bei SEPA-Rücklastschriften)
|
||||
* DEBT+[Originators Identification Code](CT-AT10- Angabe verpflichtend,)
|
||||
* Entweder CRED oder DEBT
|
||||
*
|
||||
* optional zusätzlich zur Einstellung in Feld 61, Subfeld 9:
|
||||
*
|
||||
* COAM+ [Compensation Amount / Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift sowie optionalem Zinsausgleich.]
|
||||
* OAMT+[Original Amount] Betrag der ursprünglichen Lastschrift
|
||||
*
|
||||
* SVWZ+[SEPA-Verwendungszweck] (DD-AT22; CT-AT05 -Angabe verpflichtend, nicht jedoch bei R-Transaktionen)
|
||||
* ABWA+[Abweichender Überweisender] (CT-AT08) / Abweichender Zahlungsempfänger (DD-AT38) ] (optional)
|
||||
* ABWE+[Abweichender Zahlungsemp-fänger (CT-AT28) / Abweichender Zahlungspflichtiger ((DD-AT15)] (optional)
|
||||
*/
|
||||
// protected open fun mapUsage(buchung: GVRKUms.UmsLine, entry: AccountingEntry) {
|
||||
// var lastUsageLineType = UsageLineType.ContinuationFromLastLine
|
||||
// var typeValue = ""
|
||||
//
|
||||
// buchung.usage.forEach { line ->
|
||||
// val (type, adjustedString) = getUsageLineType(line, entry)
|
||||
//
|
||||
// if (type == UsageLineType.ContinuationFromLastLine) {
|
||||
// typeValue += (if(adjustedString[0].isUpperCase()) " " else "") + adjustedString
|
||||
// }
|
||||
// else if (lastUsageLineType != type) {
|
||||
// if (lastUsageLineType != UsageLineType.ContinuationFromLastLine) {
|
||||
// setUsageLineValue(entry, lastUsageLineType, typeValue)
|
||||
// }
|
||||
//
|
||||
// typeValue = adjustedString
|
||||
// lastUsageLineType = type
|
||||
// }
|
||||
//
|
||||
// tryToParseBookingDateFromUsageLine(entry, adjustedString, typeValue)
|
||||
// }
|
||||
//
|
||||
// if(lastUsageLineType != UsageLineType.ContinuationFromLastLine) {
|
||||
// setUsageLineValue(entry, lastUsageLineType, typeValue)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected open fun setUsageLineValue(entry: AccountingEntry, lastUsageLineType: UsageLineType, typeValue: String) {
|
||||
// entry.parsedUsages.add(typeValue)
|
||||
//
|
||||
// when (lastUsageLineType) {
|
||||
// UsageLineType.EREF -> entry.endToEndReference = typeValue
|
||||
// UsageLineType.KREF -> entry.kundenreferenz = typeValue
|
||||
// UsageLineType.MREF -> entry.mandatsreferenz = typeValue
|
||||
// UsageLineType.CRED -> entry.creditorIdentifier = typeValue
|
||||
// UsageLineType.DEBT -> entry.originatorsIdentificationCode = typeValue
|
||||
// UsageLineType.COAM -> entry.compensationAmount = typeValue
|
||||
// UsageLineType.OAMT -> entry.originalAmount = typeValue
|
||||
// UsageLineType.SVWZ -> entry.sepaVerwendungszweck = typeValue
|
||||
// UsageLineType.ABWA -> entry.abweichenderAuftraggeber = typeValue
|
||||
// UsageLineType.ABWE -> entry.abweichenderZahlungsempfaenger = typeValue
|
||||
// UsageLineType.NoSpecialType -> entry.usageWithNoSpecialType = typeValue
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected open fun getUsageLineType(line: String, entry: AccountingEntry): Pair<UsageLineType, String> {
|
||||
// return when {
|
||||
// line.startsWith("EREF+") -> Pair(UsageLineType.EREF, line.substring(5))
|
||||
// line.startsWith("KREF+") -> Pair(UsageLineType.KREF, line.substring(5))
|
||||
// line.startsWith("MREF+") -> Pair(UsageLineType.MREF, line.substring(5))
|
||||
// line.startsWith("CRED+") -> Pair(UsageLineType.CRED, line.substring(5))
|
||||
// line.startsWith("DEBT+") -> Pair(UsageLineType.DEBT, line.substring(5))
|
||||
// line.startsWith("COAM+") -> Pair(UsageLineType.COAM, line.substring(5))
|
||||
// line.startsWith("OAMT+") -> Pair(UsageLineType.OAMT, line.substring(5))
|
||||
// line.startsWith("SVWZ+") -> Pair(UsageLineType.SVWZ, line.substring(5))
|
||||
// line.startsWith("ABWA+") -> Pair(UsageLineType.ABWA, line.substring(5))
|
||||
// line.startsWith("ABWE+") -> Pair(UsageLineType.ABWE, line.substring(5))
|
||||
// entry.usage.startsWith(line) -> Pair(UsageLineType.NoSpecialType, line)
|
||||
// else -> Pair(UsageLineType.ContinuationFromLastLine, line)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected open fun tryToParseBookingDateFromUsageLine(entry: AccountingEntry, currentLine: String, typeLine: String) {
|
||||
// if (currentLine.startsWith(DateStartString)) {
|
||||
// tryToParseBookingDateFromUsageLine(entry, currentLine)
|
||||
// }
|
||||
// else if (typeLine.startsWith(DateStartString)) {
|
||||
// tryToParseBookingDateFromUsageLine(entry, typeLine)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected open fun tryToParseBookingDateFromUsageLine(entry: AccountingEntry, line: String) {
|
||||
// var dateString = line.replace(DateStartString, "")
|
||||
// val index = dateString.indexOf(DateEndString)
|
||||
// if (index > 0) {
|
||||
// dateString = dateString.substring(0, index)
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// entry.bookingDate = DateTimeFormat.parse(dateString)
|
||||
// } catch (e: Exception) {
|
||||
// try {
|
||||
// entry.bookingDate = DateFormat.parse(dateString)
|
||||
// } catch (secondException: Exception) {
|
||||
// log.debug("Could not parse '$dateString' from '$line' to a Date", e)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package net.dankito.banking.util
|
||||
|
||||
import net.dankito.banking.ui.model.Account
|
||||
import net.dankito.banking.ui.model.Bank
|
||||
import net.dankito.banking.ui.model.BankAccount
|
||||
import net.dankito.banking.ui.model.BankAccountType
|
||||
import net.dankito.banking.ui.model.parameters.TransferMoneyData
|
||||
import net.dankito.banking.ui.model.tan.TanProcedureType
|
||||
import org.kapott.hbci.passport.HBCIPassport
|
||||
import org.kapott.hbci.structures.Konto
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
open class hbci4jModelMapper {
|
||||
|
||||
open fun mapToKonto(bank: Bank, bankAccount: BankAccount): Konto {
|
||||
val konto = Konto("DE", bank.bankCode, bankAccount.identifier, bankAccount.subAccountNumber)
|
||||
|
||||
konto.name = bank.name
|
||||
konto.iban = bankAccount.iban
|
||||
konto.bic = bank.bic
|
||||
|
||||
return konto
|
||||
}
|
||||
|
||||
open fun mapToKonto(data: TransferMoneyData): Konto {
|
||||
return mapToKonto(data.creditorName, data.creditorIban, data.creditorBic)
|
||||
}
|
||||
|
||||
open fun mapToKonto(accountHolderName: String, iban: String, bic: String): Konto {
|
||||
val konto = Konto()
|
||||
|
||||
konto.name = accountHolderName
|
||||
konto.iban = iban
|
||||
konto.bic = bic
|
||||
|
||||
return konto
|
||||
}
|
||||
|
||||
|
||||
open fun mapBankAccounts(account: Account, bankAccounts: Array<out Konto>, passport: HBCIPassport): List<BankAccount> {
|
||||
return bankAccounts.map { bankAccount ->
|
||||
val iban = if (bankAccount.iban.isNullOrBlank() == false) bankAccount.iban else passport.upd.getProperty("KInfo.iban") ?: ""
|
||||
|
||||
BankAccount(account, bankAccount.number,
|
||||
if (bankAccount.name2.isNullOrBlank() == false) bankAccount.name + " " + bankAccount.name2 else bankAccount.name,
|
||||
iban, bankAccount.subnumber, BigDecimal.ZERO, bankAccount.curr, mapBankAccountType(bankAccount),
|
||||
bankAccount.allowedGVs.contains("HKKAZ"), bankAccount.allowedGVs.contains("HKSAL"), bankAccount.allowedGVs.contains("HKCCS"))
|
||||
}
|
||||
}
|
||||
|
||||
open fun mapBankAccountType(bankAccount: Konto): BankAccountType {
|
||||
val type = bankAccount.acctype
|
||||
|
||||
return when {
|
||||
type.length == 1 -> BankAccountType.Girokonto
|
||||
type.startsWith("1") -> BankAccountType.Sparkonto
|
||||
type.startsWith("2") -> BankAccountType.Festgeldkonto
|
||||
type.startsWith("3") -> BankAccountType.Wertpapierdepot
|
||||
type.startsWith("4") -> BankAccountType.Darlehenskonto
|
||||
type.startsWith("5") -> BankAccountType.Kreditkartenkonto
|
||||
type.startsWith("6") -> BankAccountType.FondsDepot
|
||||
type.startsWith("7") -> BankAccountType.Bausparvertrag
|
||||
type.startsWith("8") -> BankAccountType.Versicherungsvertrag
|
||||
type.startsWith("9") -> BankAccountType.Sonstige
|
||||
else -> BankAccountType.Sonstige
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open fun mapTanProcedures(tanProceduresString: String): List<net.dankito.banking.ui.model.tan.TanProcedure> {
|
||||
return tanProceduresString.split('|')
|
||||
.map { mapTanProcedure(it) }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
open fun mapTanProcedure(tanProcedureString: String): net.dankito.banking.ui.model.tan.TanProcedure? {
|
||||
val parts = tanProcedureString.split(':')
|
||||
|
||||
if (parts.size > 1) {
|
||||
val code = parts[0]
|
||||
val displayName = parts[1]
|
||||
val displayNameLowerCase = displayName.toLowerCase()
|
||||
|
||||
return when {
|
||||
displayNameLowerCase.contains("chiptan") -> {
|
||||
if (displayNameLowerCase.contains("qr")) {
|
||||
net.dankito.banking.ui.model.tan.TanProcedure(displayName, TanProcedureType.ChipTanQrCode, code)
|
||||
}
|
||||
else {
|
||||
net.dankito.banking.ui.model.tan.TanProcedure(displayName, TanProcedureType.ChipTanOptisch, code)
|
||||
}
|
||||
}
|
||||
|
||||
displayNameLowerCase.contains("sms") -> net.dankito.banking.ui.model.tan.TanProcedure(displayName, TanProcedureType.SmsTan, code)
|
||||
displayNameLowerCase.contains("push") -> net.dankito.banking.ui.model.tan.TanProcedure(displayName, TanProcedureType.PushTan, code)
|
||||
|
||||
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue