2020-01-02 21:39:02 +00:00
package net.dankito.banking
2020-01-02 23:35:36 +00:00
import net.dankito.banking.ui.BankingClientCallback
2020-01-02 21:39:02 +00:00
import net.dankito.banking.ui.IBankingClient
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
2020-05-18 19:23:03 +00:00
import net.dankito.banking.fints.FinTsClientForCustomer
import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.util.IBase64Service
2020-06-03 15:49:29 +00:00
import net.dankito.banking.fints.util.PureKotlinBase64Service
import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.fints.webclient.KtorWebClient
2020-07-02 21:08:35 +00:00
import net.dankito.banking.extensions.toMoney
2020-09-22 04:06:11 +00:00
import net.dankito.banking.fints.model.BankData
2020-07-15 19:45:20 +00:00
import net.dankito.banking.fints.response.client.FinTsClientResponse
2020-09-11 10:25:05 +00:00
import net.dankito.banking.ui.model.*
import net.dankito.banking.ui.model.MessageLogEntry
import net.dankito.banking.ui.model.mapper.IModelCreator
2020-07-12 10:26:16 +00:00
import net.dankito.banking.util.ISerializer
import net.dankito.utils.multiplatform.File
import net.dankito.utils.multiplatform.log.LoggerFactory
2020-01-02 21:39:02 +00:00
2020-05-16 18:19:42 +00:00
open class fints4kBankingClient (
2020-09-22 04:06:11 +00:00
protected val bank : TypedBankData ,
2020-09-11 10:25:05 +00:00
protected val modelCreator : IModelCreator ,
2020-01-25 19:29:44 +00:00
protected val dataFolder : File ,
2020-07-12 10:26:16 +00:00
protected val serializer : ISerializer ,
2020-06-03 15:49:29 +00:00
webClient : IWebClient = KtorWebClient ( ) ,
base64Service : IBase64Service = PureKotlinBase64Service ( ) ,
2020-01-02 23:35:36 +00:00
callback : BankingClientCallback
2020-01-02 21:39:02 +00:00
) : IBankingClient {
2020-01-25 19:29:44 +00:00
companion object {
2020-05-16 18:19:42 +00:00
val fints4kClientDataFilename = " fints4kClientData.json "
2020-01-25 19:29:44 +00:00
2020-07-12 10:26:16 +00:00
private val log = LoggerFactory . getLogger ( fints4kBankingClient :: class )
2020-01-25 19:29:44 +00:00
}
2020-09-11 10:25:05 +00:00
protected val mapper = net . dankito . banking . mapper . fints4kModelMapper ( modelCreator )
2020-01-02 21:39:02 +00:00
2020-01-25 19:29:44 +00:00
2020-09-22 04:06:11 +00:00
protected val fintsBank = restoreDataOrMapFromUiModel ( bank )
2020-01-02 21:39:02 +00:00
2020-01-02 23:35:36 +00:00
2020-09-22 04:06:11 +00:00
protected open val client = FinTsClientForCustomer ( fintsBank , createFinTsClientCallback ( callback ) , webClient , base64Service )
2020-01-02 21:39:02 +00:00
2020-05-16 20:51:51 +00:00
override val messageLogWithoutSensitiveData : List < MessageLogEntry >
2020-09-22 04:06:11 +00:00
get ( ) = client . messageLogWithoutSensitiveData . map { MessageLogEntry ( it . message , it . time , bank ) }
2020-05-16 20:51:51 +00:00
2020-01-02 21:39:02 +00:00
override fun addAccountAsync ( callback : ( AddAccountResponse ) -> Unit ) {
client . addAccountAsync { response ->
2020-07-15 19:45:20 +00:00
handleAddAccountResponse ( response , callback )
}
}
2020-01-02 21:39:02 +00:00
2020-07-15 19:45:20 +00:00
protected open fun handleAddAccountResponse ( response : net . dankito . banking . fints . response . client . AddAccountResponse ,
callback : ( AddAccountResponse ) -> Unit ) {
2020-09-30 00:41:05 +00:00
if ( response . successful ) { // if fintsBank couldn't be restored and then an error occurs, e.g. no network connection, then fintsBank contains almost no data which then gets mapped to bank -> accounts, TAN methods, TAN procedures, ... are lost
mapper . mapBank ( bank , fintsBank )
}
2020-09-22 04:06:11 +00:00
val mappedResponse = mapper . mapResponse ( bank , response )
2020-01-25 19:29:44 +00:00
2020-07-15 19:45:20 +00:00
saveData ( )
callback ( mappedResponse )
2020-01-02 21:39:02 +00:00
}
2020-07-15 19:45:20 +00:00
2020-09-19 02:05:34 +00:00
override fun getTransactionsAsync ( parameter : GetTransactionsParameter , callback : ( GetTransactionsResponse ) -> Unit ) {
2020-09-22 04:06:11 +00:00
val account = parameter . account
2020-09-19 02:05:34 +00:00
2020-09-30 00:46:07 +00:00
findAccountForAccount ( account ) { accountData , response ->
2020-09-22 04:06:11 +00:00
if ( accountData == null ) {
2020-09-30 00:46:07 +00:00
if ( response != null ) {
callback ( GetTransactionsResponse ( account , response ) )
}
else { // should never be the case
callback ( GetTransactionsResponse ( account , " " ) )
}
2020-07-27 14:57:38 +00:00
}
else {
2020-09-22 04:06:11 +00:00
val mappedParameter = GetTransactionsParameter ( accountData , parameter . alsoRetrieveBalance , parameter . fromDate ,
2020-09-16 13:07:21 +00:00
parameter . toDate , null , parameter . abortIfTanIsRequired ) {
2020-09-22 04:06:11 +00:00
parameter . retrievedChunkListener ?. invoke ( mapper . mapTransactions ( account , it ) )
2020-09-16 13:07:21 +00:00
}
2020-07-27 14:57:38 +00:00
2020-09-22 04:06:11 +00:00
doGetTransactionsAsync ( mappedParameter , account , callback )
2020-07-27 14:57:38 +00:00
}
2020-07-15 19:45:20 +00:00
}
}
2020-01-25 19:29:44 +00:00
2020-07-15 19:45:20 +00:00
protected open fun doGetTransactionsAsync ( parameter : net . dankito . banking . fints . model . GetTransactionsParameter ,
2020-09-22 04:06:11 +00:00
account : TypedBankAccount , callback : ( GetTransactionsResponse ) -> Unit ) {
2020-09-19 02:05:34 +00:00
client . getTransactionsAsync ( parameter ) { response ->
2020-09-22 04:06:11 +00:00
handleGetTransactionsResponse ( account , response , callback )
2020-01-02 21:39:02 +00:00
}
}
2020-09-22 04:06:11 +00:00
protected open fun handleGetTransactionsResponse ( account : TypedBankAccount , response : net . dankito . banking . fints . response . client . GetTransactionsResponse ,
2020-07-15 19:45:20 +00:00
callback : ( GetTransactionsResponse ) -> Unit ) {
2020-09-22 04:06:11 +00:00
val mappedResponse = mapper . mapResponse ( account , response )
2020-07-15 19:45:20 +00:00
saveData ( )
callback ( mappedResponse )
}
2020-09-02 14:54:33 +00:00
override fun transferMoneyAsync ( data : TransferMoneyData , callback : ( BankingClientResponse ) -> Unit ) {
2020-09-30 00:46:07 +00:00
findAccountForAccount ( data . account ) { account , response ->
2020-07-27 14:57:38 +00:00
if ( account == null ) {
2020-09-30 00:46:07 +00:00
if ( response != null ) {
callback ( response )
}
else { // should never be the case
callback ( BankingClientResponse ( false , " Konnte Kontodaten nicht vom Bankserver abrufen für ${data.account.identifier} . " +
" Besteht eine Netzwerkverbindung und sind der eingegebenen Benutzername und Passwort korrekt? " ) ) // TODO: translate
}
2020-07-27 14:57:38 +00:00
}
else {
2020-09-24 00:53:09 +00:00
val mappedData = BankTransferData ( data . recipientName , data . recipientAccountId , data . recipientBankCode , data . amount . toMoney ( ) , data . reference , data . realTimeTransfer )
2020-07-27 14:57:38 +00:00
doBankTransferAsync ( mappedData , account , callback )
}
2020-07-15 19:45:20 +00:00
}
}
2020-01-25 19:29:44 +00:00
2020-07-15 19:45:20 +00:00
protected open fun doBankTransferAsync ( data : BankTransferData , account : AccountData , callback : ( BankingClientResponse ) -> Unit ) {
client . doBankTransferAsync ( data , account ) { response ->
handleBankTransferResponse ( callback , response )
2020-01-02 21:39:02 +00:00
}
}
2020-01-19 15:22:43 +00:00
2020-07-15 19:45:20 +00:00
protected open fun handleBankTransferResponse ( callback : ( BankingClientResponse ) -> Unit , response : FinTsClientResponse ) {
saveData ( )
callback ( mapper . mapResponse ( response ) )
}
2020-01-25 19:29:44 +00:00
2020-09-22 04:06:11 +00:00
override fun dataChanged ( bank : TypedBankData ) {
mapper . mapChangesFromUiToClientModel ( bank , this . fintsBank )
2020-09-08 14:25:28 +00:00
}
2020-09-22 04:06:11 +00:00
override fun deletedBank ( bank : TypedBankData , wasLastAccountWithThisCredentials : Boolean ) {
2020-09-13 23:00:09 +00:00
if ( wasLastAccountWithThisCredentials ) {
2020-09-22 04:06:11 +00:00
getFints4kClientDataFile ( bank ) . delete ( )
2020-09-13 23:00:09 +00:00
}
}
2020-09-08 14:25:28 +00:00
2020-09-30 00:46:07 +00:00
protected open fun findAccountForAccount ( account : TypedBankAccount , findAccountResult : ( AccountData ? , BankingClientResponse ? ) -> Unit ) {
2020-09-22 04:06:11 +00:00
val mappedAccount = mapper . findMatchingAccount ( fintsBank , account )
2020-07-27 14:57:38 +00:00
if ( mappedAccount != null ) {
findAccountResult ( mappedAccount , null )
}
2020-09-30 00:43:12 +00:00
else { // then try to get account data by fetching data from bank
2020-07-27 14:57:38 +00:00
addAccountAsync { response ->
2020-09-30 00:43:12 +00:00
if ( response . successful ) {
2020-09-30 00:46:07 +00:00
findAccountResult ( mapper . findMatchingAccount ( fintsBank , account ) , response )
2020-09-30 00:43:12 +00:00
}
else {
2020-09-30 00:46:07 +00:00
findAccountResult ( null , response )
2020-09-30 00:43:12 +00:00
}
2020-07-27 14:57:38 +00:00
}
}
}
2020-09-22 04:06:11 +00:00
protected open fun restoreDataOrMapFromUiModel ( bank : TypedBankData ) : BankData {
if ( isNewAccount ( bank ) ) {
return mapToBankData ( bank )
2020-09-13 22:10:13 +00:00
}
2020-09-22 04:06:11 +00:00
return restoreData ( bank ) ?: mapToBankData ( bank )
2020-09-13 22:10:13 +00:00
}
2020-09-22 04:06:11 +00:00
protected open fun isNewAccount ( bank : TypedBankData ) : Boolean {
return bank . accounts . isEmpty ( )
2020-09-13 22:10:13 +00:00
}
2020-09-22 04:06:11 +00:00
protected open fun mapToBankData ( bank : TypedBankData ) : BankData {
2020-09-24 02:22:40 +00:00
return BankData ( bank . bankCode , bank . userName , bank . password , bank . finTsServerAddress , bank . bic , bank . bankName )
2020-09-08 12:23:56 +00:00
}
2020-04-28 16:17:47 +00:00
2020-09-22 04:06:11 +00:00
protected open fun restoreData ( bank : TypedBankData ) : BankData ? {
2020-09-08 12:23:56 +00:00
try {
2020-09-22 04:06:11 +00:00
return serializer . deserializeObject ( getFints4kClientDataFile ( bank ) , BankData :: class )
2020-09-08 12:23:56 +00:00
} catch ( e : Exception ) {
2020-09-22 04:06:11 +00:00
log . warn ( e ) { " Could not deserialize bank data of $bank " }
2020-01-25 19:29:44 +00:00
}
2020-09-08 12:23:56 +00:00
return null
2020-01-25 19:29:44 +00:00
}
protected open fun saveData ( ) {
try {
2020-09-22 04:06:11 +00:00
val clientDataFile = getFints4kClientDataFile ( fintsBank . bankCode , fintsBank . customerId )
2020-06-14 19:00:45 +00:00
2020-09-22 04:06:11 +00:00
serializer . serializeObject ( fintsBank , clientDataFile )
2020-01-25 19:29:44 +00:00
} catch ( e : Exception ) {
2020-09-22 04:06:11 +00:00
log . error ( " Could not save bank data for $fintsBank " , e )
2020-01-25 19:29:44 +00:00
}
}
2020-09-22 04:06:11 +00:00
protected open fun getFints4kClientDataFile ( bank : TypedBankData ) : File {
2020-09-24 02:22:40 +00:00
return getFints4kClientDataFile ( bank . bankCode , bank . userName )
2020-09-13 23:00:09 +00:00
}
2020-09-08 12:23:56 +00:00
protected open fun getFints4kClientDataFile ( bankCode : String , customerId : String ) : File {
2020-07-27 14:58:29 +00:00
val folder = File ( dataFolder , " fints4k-client " )
folder . mkdirs ( )
2020-09-08 12:23:56 +00:00
return File ( folder , " ${bankCode} _ ${customerId} _ $fints4kClientDataFilename " )
2020-01-25 19:29:44 +00:00
}
2020-07-15 19:45:20 +00:00
2020-07-21 08:59:33 +00:00
protected open fun createFinTsClientCallback ( clientCallback : BankingClientCallback ) : FinTsClientCallback {
2020-07-15 19:45:20 +00:00
return object : FinTsClientCallback {
2020-09-21 17:25:51 +00:00
override fun askUserForTanMethod ( supportedTanMethods : List < TanMethod > , suggestedTanMethod : TanMethod ? , callback : ( TanMethod ? ) -> Unit ) {
2020-09-21 22:25:51 +00:00
handleAskUserForTanMethod ( supportedTanMethods , suggestedTanMethod , callback )
2020-07-15 19:45:20 +00:00
}
2020-09-08 12:07:17 +00:00
override fun enterTan ( bank : BankData , tanChallenge : TanChallenge , callback : ( EnterTanResult ) -> Unit ) {
handleEnterTan ( bank , tanChallenge , callback , clientCallback )
2020-07-15 19:45:20 +00:00
}
2020-09-08 12:07:17 +00:00
override fun enterTanGeneratorAtc ( bank : BankData , tanMedium : TanGeneratorTanMedium , callback : ( EnterTanGeneratorAtcResult ) -> Unit ) {
handleEnterTanGeneratorAtc ( bank , tanMedium , callback , clientCallback )
2020-07-15 19:45:20 +00:00
}
}
}
2020-09-21 22:25:51 +00:00
protected open fun handleAskUserForTanMethod ( supportedTanMethods : List < TanMethod > , suggestedTanMethod : TanMethod ? , callback : ( TanMethod ? ) -> Unit ) {
2020-07-15 19:45:20 +00:00
// we simply return suggestedTanProcedure as even so it's not user's preferred TAN procedure she still can select it in EnterTanDialog
2020-09-21 17:25:51 +00:00
callback ( suggestedTanMethod )
2020-07-15 19:45:20 +00:00
}
2020-09-08 12:07:17 +00:00
protected open fun handleEnterTan ( bank : BankData , tanChallenge : TanChallenge , enterTanCallback : ( EnterTanResult ) -> Unit , clientCallback : BankingClientCallback ) {
2020-09-22 04:06:11 +00:00
mapper . updateTanMediaAndMethods ( this @fints4kBankingClient . bank , bank )
2020-07-15 19:45:20 +00:00
2020-09-22 04:06:11 +00:00
clientCallback . enterTan ( this @fints4kBankingClient . bank , mapper . mapTanChallenge ( tanChallenge ) ) { result ->
2020-09-08 12:07:17 +00:00
enterTanCallback ( mapper . mapEnterTanResult ( result , bank ) )
2020-07-21 08:59:33 +00:00
}
2020-07-15 19:45:20 +00:00
}
2020-09-08 12:07:17 +00:00
protected open fun handleEnterTanGeneratorAtc ( bank : BankData , tanMedium : TanGeneratorTanMedium , enterAtcCallback : ( EnterTanGeneratorAtcResult ) -> Unit , clientCallback : BankingClientCallback ) {
2020-09-22 04:06:11 +00:00
mapper . updateTanMediaAndMethods ( this @fints4kBankingClient . bank , bank )
2020-07-15 19:45:20 +00:00
2020-07-21 08:59:33 +00:00
clientCallback . enterTanGeneratorAtc ( mapper . mapTanMedium ( tanMedium ) ) { result ->
enterAtcCallback ( mapper . mapEnterTanGeneratorAtcResult ( result ) )
}
2020-07-15 19:45:20 +00:00
}
2020-01-02 21:39:02 +00:00
}