Implemented new simplified data model in FinTsClient.getAccountData()
This commit is contained in:
parent
b74b165974
commit
52de5a2956
|
@ -48,8 +48,8 @@ class FirstFragment : Fragment() {
|
|||
|
||||
// TODO: set your credentials here
|
||||
presenter.retrieveAccountData("", "", "", "") { response ->
|
||||
if (response.successful) {
|
||||
accountTransactionsAdapter.items = response.retrievedData.flatMap { it.bookedTransactions }
|
||||
response.customerAccount?.let { customer ->
|
||||
accountTransactionsAdapter.items = customer.accounts.flatMap { it.bookedTransactions }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ import kotlinx.coroutines.GlobalScope
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.FinTsClientDeprecated
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.response.GetAccountDataResponse
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
||||
import net.dankito.banking.fints.model.AddAccountParameter
|
||||
import net.dankito.banking.fints.model.TanChallenge
|
||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||
import net.dankito.utils.multiplatform.extensions.millisSinceEpochAtSystemDefaultTimeZone
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.math.BigDecimal
|
||||
|
@ -24,7 +24,7 @@ open class Presenter {
|
|||
private val log = LoggerFactory.getLogger(Presenter::class.java)
|
||||
}
|
||||
|
||||
private val fintsClient = FinTsClientDeprecated(SimpleFinTsClientCallback { challenge -> enterTan(challenge) })
|
||||
private val fintsClient = FinTsClient(SimpleFinTsClientCallback { challenge -> enterTan(challenge) })
|
||||
|
||||
open var enterTanCallback: ((TanChallenge) -> Unit)? = null
|
||||
|
||||
|
@ -34,10 +34,10 @@ open class Presenter {
|
|||
|
||||
|
||||
|
||||
open fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) {
|
||||
open fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (GetAccountDataResponse) -> Unit) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val response = fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
|
||||
val response = fintsClient.getAccountData(GetAccountDataParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
log.info("Retrieved response from ${response.customerAccount?.bankName} for ${response.customerAccount?.customerName}")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
retrievedResult(response)
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.view.View
|
|||
import net.codinux.banking.fints4k.android.Presenter
|
||||
import net.codinux.banking.fints4k.android.R
|
||||
import net.codinux.banking.fints4k.android.adapter.viewholder.AccountTransactionsViewHolder
|
||||
import net.dankito.banking.fints.model.AccountTransaction
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.fints.util.toBigDecimal
|
||||
import net.dankito.utils.android.extensions.setTextColorToColorResource
|
||||
import net.dankito.utils.android.ui.adapter.ListRecyclerAdapter
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.dankito.banking.fints.model.*
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.fints.model.TanChallenge
|
||||
import react.RBuilder
|
||||
import react.RComponent
|
||||
import react.Props
|
||||
|
@ -27,10 +28,10 @@ class AccountTransactionsView(props: AccountTransactionsViewProps) : RComponent<
|
|||
// TODO: set your credentials here
|
||||
GlobalScope.launch {
|
||||
props.presenter.retrieveAccountData("", "", "", "") { response ->
|
||||
if (response.successful) {
|
||||
val balance = response.retrievedData.sumOf { it.balance?.amount?.string?.replace(',', '.')?.toDoubleOrNull() ?: 0.0 } // i know, double is not an appropriate data type for amounts
|
||||
response.customerAccount?.let { customer ->
|
||||
val balance = customer.accounts.sumOf { it.balance?.amount?.string?.replace(',', '.')?.toDoubleOrNull() ?: 0.0 } // i know, double is not an appropriate data type for amounts
|
||||
|
||||
setState(AccountTransactionsViewState(balance.toString() + " " + (response.retrievedData.firstOrNull()?.balance?.currency ?: ""), response.retrievedData.flatMap { it.bookedTransactions }, state.enterTanChallenge))
|
||||
setState(AccountTransactionsViewState(balance.toString() + " " + (customer.accounts.firstOrNull()?.balance?.currency ?: ""), customer.accounts.flatMap { it.bookedTransactions }, state.enterTanChallenge))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.dankito.banking.fints.FinTsClientDeprecated
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.response.GetAccountDataResponse
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
||||
import net.dankito.banking.fints.model.AddAccountParameter
|
||||
import net.dankito.banking.fints.model.TanChallenge
|
||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||
import net.dankito.banking.fints.model.TanMethod
|
||||
import net.dankito.banking.fints.model.TanMethodType
|
||||
import net.dankito.banking.fints.webclient.KtorWebClient
|
||||
import net.dankito.banking.fints.webclient.ProxyingWebClient
|
||||
import net.dankito.utils.multiplatform.log.LoggerFactory
|
||||
|
@ -20,7 +22,7 @@ open class Presenter {
|
|||
|
||||
// to circumvent CORS we have to use a CORS proxy like the SampleApplications.CorsProxy Application.kt or
|
||||
// https://github.com/Rob--W/cors-anywhere. Set CORS proxy's URL here
|
||||
protected open val fintsClient = FinTsClientDeprecated(SimpleFinTsClientCallback { challenge -> enterTan(challenge) },
|
||||
protected open val fintsClient = FinTsClient(SimpleFinTsClientCallback { challenge -> enterTan(challenge) },
|
||||
ProxyingWebClient("http://localhost:8082/", KtorWebClient()))
|
||||
|
||||
open var enterTanCallback: ((TanChallenge) -> Unit)? = null
|
||||
|
@ -30,11 +32,11 @@ open class Presenter {
|
|||
}
|
||||
|
||||
|
||||
open fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) {
|
||||
open fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (GetAccountDataResponse) -> Unit) {
|
||||
GlobalScope.launch(Dispatchers.Unconfined) {
|
||||
val response = fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
val response = fintsClient.getAccountData(GetAccountDataParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
|
||||
log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
|
||||
log.info("Retrieved response from ${response.customerAccount?.bankName} for ${response.customerAccount?.customerName}")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
retrievedResult(response)
|
||||
|
|
|
@ -51,22 +51,20 @@ struct ContentView: View {
|
|||
|
||||
private func retrieveTransactions() {
|
||||
// TODO: set your credentials here
|
||||
self.presenter.retrieveTransactions("", "", "", "", self.handleRetrieveTransactionsResult)
|
||||
self.presenter.getAccountData("", "", "", "", self.handleGetAccountDataResponse)
|
||||
}
|
||||
|
||||
private func handleRetrieveTransactionsResult(_ result: AddAccountResponse) {
|
||||
NSLog("Retrieved response: \(result.retrievedTransactionsResponses)")
|
||||
private func handleGetAccountDataResponse(_ response: GetAccountDataResponse) {
|
||||
NSLog("Retrieved response: \(response.retrievedTransactions)")
|
||||
|
||||
if (result.successful) {
|
||||
if (response.successful) {
|
||||
var allTransactions: [AccountTransaction] = []
|
||||
|
||||
for accountResponse in result.retrievedTransactionsResponses {
|
||||
if let transactions = accountResponse.retrievedData?.bookedTransactions as? Set<AccountTransaction> { // it's a Set
|
||||
allTransactions.append(contentsOf: transactions)
|
||||
}
|
||||
if let transactions = accountResponse.retrievedData?.bookedTransactions as? [AccountTransaction] {
|
||||
allTransactions.append(contentsOf: transactions)
|
||||
}
|
||||
if let transactions = response.retrievedTransactions as? Set<AccountTransaction> { // it's a Set
|
||||
allTransactions.append(contentsOf: transactions)
|
||||
}
|
||||
if let transactions = response.retrievedTransactions as? [AccountTransaction] {
|
||||
allTransactions.append(contentsOf: transactions)
|
||||
}
|
||||
|
||||
self.transactions = allTransactions
|
||||
|
|
|
@ -18,8 +18,8 @@ class Presenter : ObservableObject {
|
|||
}
|
||||
|
||||
|
||||
func retrieveTransactions(_ bankCode: String, _ customerId: String, _ pin: String, _ finTs3ServerAddress: String, _ callback: @escaping (AddAccountResponse) -> Void) {
|
||||
self.fintsClient.addAccountAsync(parameter: AddAccountParameter(bankCode: bankCode, customerId: customerId, pin: pin, finTs3ServerAddress: finTs3ServerAddress), callback: callback)
|
||||
func getAccountData(_ bankCode: String, _ customerId: String, _ pin: String, _ finTs3ServerAddress: String, _ callback: @escaping (GetAccountDataResponse) -> Void) {
|
||||
self.fintsClient.getAccountData(parameter: GetAccountDataParameter(bankCode: bankCode, customerId: customerId, pin: pin, finTs3ServerAddress: finTs3ServerAddress), callback: callback)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.model.Amount
|
||||
import net.dankito.banking.fints.model.Money
|
||||
import net.dankito.utils.multiplatform.extensions.atUnixEpochStart
|
||||
|
||||
|
||||
open class AccountTransaction(
|
||||
val amount: Money, // TODO: if we decide to stick with Money, create own type, don't use that one from fints.model (or move over from)
|
||||
val unparsedReference: String,
|
||||
val bookingDate: LocalDate,
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankCode: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
val bookingText: String?,
|
||||
val valueDate: LocalDate,
|
||||
val statementNumber: Int,
|
||||
val sequenceNumber: Int?,
|
||||
val openingBalance: Money?,
|
||||
val closingBalance: Money?,
|
||||
|
||||
val endToEndReference: String?,
|
||||
val customerReference: String?,
|
||||
val mandateReference: String?,
|
||||
val creditorIdentifier: String?,
|
||||
val originatorsIdentificationCode: String?,
|
||||
val compensationAmount: String?,
|
||||
val originalAmount: String?,
|
||||
val sepaReference: String?,
|
||||
val deviantOriginator: String?,
|
||||
val deviantRecipient: String?,
|
||||
val referenceWithNoSpecialType: String?,
|
||||
val primaNotaNumber: String?,
|
||||
val textKeySupplement: String?,
|
||||
|
||||
val currencyType: String?,
|
||||
val bookingKey: String,
|
||||
val referenceForTheAccountOwner: String,
|
||||
val referenceOfTheAccountServicingInstitution: String?,
|
||||
val supplementaryDetails: String?,
|
||||
|
||||
val transactionReferenceNumber: String,
|
||||
val relatedReferenceNumber: String?
|
||||
) {
|
||||
|
||||
// for object deserializers
|
||||
internal constructor() : this(Money(Amount.Zero, ""), "", LocalDate.atUnixEpochStart, null, null, null, null, LocalDate.atUnixEpochStart)
|
||||
|
||||
constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate)
|
||||
: this(amount, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
|
||||
0, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, "", "", null, null, "", null)
|
||||
|
||||
|
||||
open val showOtherPartyName: Boolean
|
||||
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
||||
|
||||
val reference: String
|
||||
get() = sepaReference ?: unparsedReference
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is AccountTransaction) return false
|
||||
|
||||
if (amount != other.amount) return false
|
||||
if (unparsedReference != other.unparsedReference) return false
|
||||
if (bookingDate != other.bookingDate) return false
|
||||
if (otherPartyName != other.otherPartyName) return false
|
||||
if (otherPartyBankCode != other.otherPartyBankCode) return false
|
||||
if (otherPartyAccountId != other.otherPartyAccountId) return false
|
||||
if (bookingText != other.bookingText) return false
|
||||
if (valueDate != other.valueDate) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = amount.hashCode()
|
||||
result = 31 * result + unparsedReference.hashCode()
|
||||
result = 31 * result + bookingDate.hashCode()
|
||||
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyAccountId?.hashCode() ?: 0)
|
||||
result = 31 * result + (bookingText?.hashCode() ?: 0)
|
||||
result = 31 * result + valueDate.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$valueDate $amount $otherPartyName: $unparsedReference"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.model.Money
|
||||
|
||||
|
||||
open class BankAccount(
|
||||
identifier: String,
|
||||
subAccountNumber: String?,
|
||||
iban: String?,
|
||||
val accountHolderName: String,
|
||||
val type: BankAccountType = BankAccountType.CheckingAccount,
|
||||
val productName: String? = null,
|
||||
val currency: String = "EUR", // TODO: may parse to a value object
|
||||
val accountLimit: String? = null,
|
||||
// TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ]
|
||||
val supportsRetrievingTransactions: Boolean = false,
|
||||
val supportsRetrievingBalance: Boolean = false,
|
||||
val supportsTransferringMoney: Boolean = false,
|
||||
val supportsInstantPayment: Boolean = false
|
||||
) : BankAccountIdentifier(identifier, subAccountNumber, iban) {
|
||||
|
||||
internal constructor() : this("", null, null, "") // for object deserializers
|
||||
|
||||
constructor(identifier: BankAccountIdentifier) : this(identifier.identifier, identifier.subAccountNumber, identifier.iban, "")
|
||||
|
||||
|
||||
open var balance: Money = Money.Zero
|
||||
|
||||
open var retrievedTransactionsFrom: LocalDate? = null
|
||||
|
||||
open var retrievedTransactionsTo: LocalDate? = null
|
||||
|
||||
open var bookedTransactions: List<AccountTransaction> = listOf()
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$productName ($identifier)"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
|
||||
open class BankAccountIdentifier(
|
||||
open val identifier: String,
|
||||
open val subAccountNumber: String?,
|
||||
open val iban: String?,
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
|
||||
enum class BankAccountType {
|
||||
|
||||
CheckingAccount,
|
||||
|
||||
SavingsAccount,
|
||||
|
||||
FixedTermDepositAccount,
|
||||
|
||||
SecuritiesAccount,
|
||||
|
||||
LoanAccount,
|
||||
|
||||
CreditCardAccount,
|
||||
|
||||
FundDeposit,
|
||||
|
||||
BuildingLoanContract,
|
||||
|
||||
InsuranceContract,
|
||||
|
||||
Other
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||
import net.dankito.banking.fints.model.TanMethod
|
||||
|
||||
//import net.dankito.banking.client.model.tan.TanMedium
|
||||
//import net.dankito.banking.client.model.tan.TanMethod
|
||||
|
||||
|
||||
open class CustomerAccount(
|
||||
override var bankCode: String,
|
||||
override var loginName: String,
|
||||
override var password: String,
|
||||
override var finTsServerAddress: String,
|
||||
open var bankName: String,
|
||||
open var bic: String,
|
||||
|
||||
open var customerName: String = "",
|
||||
open var userId: String = loginName,
|
||||
|
||||
open var accounts: List<BankAccount> = listOf(),
|
||||
|
||||
// TODO: use that ones from .tan sub package
|
||||
open var tanMethods: List<TanMethod> = listOf(),
|
||||
open var selectedTanMethod: TanMethod? = null,
|
||||
open var tanMedia: List<TanMedium> = listOf(),
|
||||
open var selectedTanMedium: TanMedium? = null,
|
||||
) : CustomerCredentials(bankCode, loginName, password, finTsServerAddress) {
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$bankName $loginName"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package net.dankito.banking.client.model
|
||||
|
||||
|
||||
open class CustomerCredentials(
|
||||
open val bankCode: String,
|
||||
open val loginName: String,
|
||||
open val password: String,
|
||||
open val finTsServerAddress: String // TODO: get rid of this
|
||||
) {
|
||||
|
||||
internal constructor() : this("", "", "", "") // for object deserializers
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package net.dankito.banking.client.model.parameter
|
||||
|
||||
import net.dankito.banking.fints.model.BankData
|
||||
import net.dankito.banking.fints.model.TanMethodType
|
||||
import net.dankito.banking.client.model.CustomerCredentials
|
||||
|
||||
|
||||
// TODO: Rename to BankingClientRequest(Base)?
|
||||
open class FinTsClientParameter(
|
||||
bankCode: String,
|
||||
loginName: String,
|
||||
password: String,
|
||||
finTsServerAddress: String, // TODO: get rid of this
|
||||
|
||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||
open val preferredTanMedium: String? = null, // the ID of the medium
|
||||
open val abortIfTanIsRequired: Boolean = false,
|
||||
open val finTsModel: BankData? = null
|
||||
) : CustomerCredentials(bankCode, loginName, password, finTsServerAddress)
|
|
@ -0,0 +1,33 @@
|
|||
package net.dankito.banking.client.model.parameter
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.model.BankData
|
||||
import net.dankito.banking.fints.model.TanMethodType
|
||||
import net.dankito.banking.client.model.BankAccountIdentifier
|
||||
|
||||
|
||||
open class GetAccountDataParameter(
|
||||
bankCode: String,
|
||||
loginName: String,
|
||||
password: String,
|
||||
finTsServerAddress: String, // TODO: get rid of this
|
||||
/**
|
||||
* Optionally specify for which bank account to retrieve the account data.
|
||||
* If not set the data for all bank accounts of this account will be retrieved.
|
||||
*/
|
||||
open val accounts: List<BankAccountIdentifier>? = null,
|
||||
open val retrieveBalance: Boolean = true,
|
||||
open val retrieveTransactions: RetrieveTransactions = RetrieveTransactions.OfLast90Days,
|
||||
open val retrieveTransactionsFrom: LocalDate? = null,
|
||||
open val retrieveTransactionsTo: LocalDate? = null,
|
||||
|
||||
preferredTanMethods: List<TanMethodType>? = null,
|
||||
preferredTanMedium: String? = null,
|
||||
abortIfTanIsRequired: Boolean = false,
|
||||
finTsModel: BankData? = null
|
||||
) : FinTsClientParameter(bankCode, loginName, password, finTsServerAddress, preferredTanMethods, preferredTanMedium, abortIfTanIsRequired, finTsModel) {
|
||||
|
||||
open val retrieveOnlyAccountInfo: Boolean
|
||||
get() = retrieveBalance == false && retrieveTransactions == RetrieveTransactions.No
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package net.dankito.banking.client.model.parameter
|
||||
|
||||
|
||||
enum class RetrieveTransactions {
|
||||
|
||||
No,
|
||||
|
||||
All,
|
||||
|
||||
/**
|
||||
* Some banks support that according to PSD2 account transactions of last 90 days may be retrieved without
|
||||
* a TAN (= no strong customer authorization needed). So try this options if you don't want to enter a TAN.
|
||||
*/
|
||||
OfLast90Days,
|
||||
|
||||
/**
|
||||
* Retrieves account transactions in the boundaries of [GetAccountDataParameter.retrieveTransactionsFrom] to [GetAccountDataParameter.retrieveTransactionsTo].
|
||||
*/
|
||||
AccordingToRetrieveFromAndTo
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package net.dankito.banking.client.model.response
|
||||
|
||||
|
||||
enum class ErrorCode {
|
||||
|
||||
InternalError,
|
||||
|
||||
BankReturnedError,
|
||||
|
||||
WrongCredentials,
|
||||
|
||||
AccountLocked,
|
||||
|
||||
JobNotSupported,
|
||||
|
||||
UserCancelledAction,
|
||||
|
||||
TanRequiredButShouldAbortIfRequiresTan,
|
||||
|
||||
NoneOfTheAccountsSupportsRetrievingData,
|
||||
|
||||
DidNotRetrieveAllAccountData
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package net.dankito.banking.client.model.response
|
||||
|
||||
import net.dankito.banking.fints.model.BankData
|
||||
import net.dankito.banking.fints.model.MessageLogEntry
|
||||
|
||||
|
||||
// TODO: rename to BankingClientResponse?
|
||||
open class FinTsClientResponse(
|
||||
open val error: ErrorCode?,
|
||||
open val errorMessage: String?,
|
||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>,
|
||||
open val finTsModel: BankData? = null
|
||||
) {
|
||||
|
||||
open val successful: Boolean
|
||||
get() = error == null
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package net.dankito.banking.client.model.response
|
||||
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.client.model.CustomerAccount
|
||||
import net.dankito.banking.fints.model.*
|
||||
|
||||
|
||||
open class GetAccountDataResponse(
|
||||
error: ErrorCode?,
|
||||
errorMessage: String?,
|
||||
open val customerAccount: CustomerAccount?,
|
||||
messageLogWithoutSensitiveData: List<MessageLogEntry>,
|
||||
finTsModel: BankData? = null
|
||||
) : FinTsClientResponse(error, errorMessage, messageLogWithoutSensitiveData, finTsModel) {
|
||||
|
||||
open val retrievedTransactions: List<AccountTransaction>
|
||||
get() = customerAccount?.accounts?.flatMap { it.bookedTransactions } ?: listOf()
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package net.dankito.banking.fints
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||
import net.dankito.banking.fints.model.*
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||
import net.dankito.banking.client.model.response.ErrorCode
|
||||
import net.dankito.banking.client.model.response.GetAccountDataResponse
|
||||
import net.dankito.banking.fints.mapper.FinTsModelMapper
|
||||
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
||||
import net.dankito.banking.fints.response.client.GetAccountInfoResponse
|
||||
import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
|
||||
import net.dankito.banking.fints.response.segments.AccountType
|
||||
import net.dankito.banking.fints.webclient.IWebClient
|
||||
import net.dankito.utils.multiplatform.extensions.minusDays
|
||||
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
|
||||
open class FinTsClient @JvmOverloads constructor(
|
||||
open var callback: FinTsClientCallback,
|
||||
protected open val jobExecutor: FinTsJobExecutor = FinTsJobExecutor(),
|
||||
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||
) {
|
||||
|
||||
companion object { // TODO: use the English names
|
||||
val SupportedAccountTypes = listOf(AccountType.Girokonto, AccountType.Festgeldkonto, AccountType.Kreditkartenkonto, AccountType.Sparkonto)
|
||||
}
|
||||
|
||||
|
||||
constructor(callback: FinTsClientCallback) : this(callback, FinTsJobExecutor()) // Swift does not support default parameter values -> create constructor overloads
|
||||
|
||||
constructor(callback: FinTsClientCallback, webClient: IWebClient) : this(callback, FinTsJobExecutor(RequestExecutor(webClient = webClient)))
|
||||
|
||||
|
||||
protected open val mapper = FinTsModelMapper()
|
||||
|
||||
|
||||
open suspend fun getAccountData(param: GetAccountDataParameter): GetAccountDataResponse {
|
||||
val bank = BankData(param.bankCode, param.loginName, param.password, param.finTsServerAddress, "")
|
||||
val accounts = param.accounts
|
||||
|
||||
if (accounts.isNullOrEmpty() || param.retrieveOnlyAccountInfo) { // then first retrieve customer's bank accounts
|
||||
val getAccountInfoResponse = getAccountInfo(param, bank)
|
||||
|
||||
if (getAccountInfoResponse.successful == false || param.retrieveOnlyAccountInfo) {
|
||||
return GetAccountDataResponse(mapper.mapErrorCode(getAccountInfoResponse), mapper.mapErrorMessages(getAccountInfoResponse), null,
|
||||
getAccountInfoResponse.messageLogWithoutSensitiveData, bank)
|
||||
} else {
|
||||
return getAccountData(param, getAccountInfoResponse.bank, getAccountInfoResponse.bank.accounts, getAccountInfoResponse)
|
||||
}
|
||||
} else {
|
||||
return getAccountData(param, bank, accounts.map { mapper.mapToAccountData(it, param) }, null)
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun getAccountData(param: GetAccountDataParameter, bank: BankData, accounts: List<AccountData>, previousJobResponse: FinTsClientResponse?): GetAccountDataResponse {
|
||||
val retrievedTransactionsResponses = mutableListOf<GetAccountTransactionsResponse>()
|
||||
|
||||
val accountsSupportingRetrievingTransactions = accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions }
|
||||
|
||||
if (accountsSupportingRetrievingTransactions.isEmpty()) {
|
||||
val errorMessage = "None of the accounts ${accounts.map { it.productName }} supports retrieving balance or transactions" // TODO: translate
|
||||
return GetAccountDataResponse(ErrorCode.NoneOfTheAccountsSupportsRetrievingData, errorMessage, mapper.map(bank), previousJobResponse?.messageLogWithoutSensitiveData ?: listOf(), bank)
|
||||
}
|
||||
|
||||
accountsSupportingRetrievingTransactions.forEach { account ->
|
||||
retrievedTransactionsResponses.add(getAccountData(param, bank, account))
|
||||
}
|
||||
|
||||
val unsuccessfulJob = retrievedTransactionsResponses.firstOrNull { it.successful == false }
|
||||
val errorCode = unsuccessfulJob?.let { mapper.mapErrorCode(it) }
|
||||
?: if (retrievedTransactionsResponses.size < accountsSupportingRetrievingTransactions.size) ErrorCode.DidNotRetrieveAllAccountData else null
|
||||
return GetAccountDataResponse(errorCode, mapper.mapErrorMessages(unsuccessfulJob), mapper.map(bank, retrievedTransactionsResponses),
|
||||
mapper.mergeMessageLog(previousJobResponse, *retrievedTransactionsResponses.toTypedArray()), bank)
|
||||
}
|
||||
|
||||
protected open suspend fun getAccountData(param: GetAccountDataParameter, bank: BankData, account: AccountData): GetAccountTransactionsResponse {
|
||||
val context = JobContext(JobContextType.GetTransactions, this.callback, product, bank, account)
|
||||
|
||||
val retrieveTransactionsFrom = when (param.retrieveTransactions) {
|
||||
RetrieveTransactions.No -> LocalDate.todayAtEuropeBerlin() // TODO: implement RetrieveTransactions.No
|
||||
RetrieveTransactions.OfLast90Days -> calculate90DaysAgo()
|
||||
RetrieveTransactions.AccordingToRetrieveFromAndTo -> param.retrieveTransactionsFrom
|
||||
else -> null
|
||||
}
|
||||
|
||||
val retrieveTransactionsTo = when (param.retrieveTransactions) {
|
||||
RetrieveTransactions.AccordingToRetrieveFromAndTo -> param.retrieveTransactionsTo
|
||||
else -> null
|
||||
}
|
||||
|
||||
return jobExecutor.getTransactionsAsync(context, GetAccountTransactionsParameter(bank, account, param.retrieveBalance, retrieveTransactionsFrom,
|
||||
retrieveTransactionsTo, abortIfTanIsRequired = param.abortIfTanIsRequired))
|
||||
}
|
||||
|
||||
private fun calculate90DaysAgo(): LocalDate? {
|
||||
// Europe/Berlin: we're communicating with German bank servers, so we have to use their time zone
|
||||
return LocalDate.todayAtEuropeBerlin().minusDays(90)
|
||||
}
|
||||
|
||||
protected open suspend fun getAccountInfo(param: GetAccountDataParameter, bank: BankData): GetAccountInfoResponse {
|
||||
val context = JobContext(JobContextType.AddAccount, this.callback, product, bank) // TODO: add / change JobContextType
|
||||
|
||||
/* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */
|
||||
|
||||
val newUserInfoResponse = jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, param.preferredTanMethods, param.preferredTanMedium)
|
||||
|
||||
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
|
||||
return GetAccountInfoResponse(context, newUserInfoResponse)
|
||||
}
|
||||
|
||||
/* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with
|
||||
strong customer authorization TAN media is required */
|
||||
|
||||
val getAccountsResponse = jobExecutor.getAccounts(context)
|
||||
|
||||
return GetAccountInfoResponse(context, getAccountsResponse)
|
||||
}
|
||||
|
||||
}
|
|
@ -25,11 +25,6 @@ open class FinTsClientDeprecated(
|
|||
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val SupportedAccountTypes = listOf(AccountType.Girokonto, AccountType.Festgeldkonto, AccountType.Kreditkartenkonto)
|
||||
}
|
||||
|
||||
|
||||
constructor(callback: FinTsClientCallback) : this(callback, FinTsJobExecutor()) // Swift does not support default parameter values -> create constructor overloads
|
||||
|
||||
constructor(callback: FinTsClientCallback, webClient: IWebClient) : this(callback, FinTsJobExecutor(RequestExecutor(webClient = webClient)))
|
||||
|
|
|
@ -27,7 +27,7 @@ import net.dankito.utils.multiplatform.extensions.todayAtSystemDefaultTimeZone
|
|||
/**
|
||||
* Low level class that executes concrete business transactions (= FinTS Geschäftsvorfälle).
|
||||
*
|
||||
* In almost all cases you want to use [FinTsClientDeprecated] which wraps these business transactions to a higher level API.
|
||||
* In almost all cases you want to use [FinTsClient] which wraps these business transactions to a higher level API.
|
||||
*/
|
||||
open class FinTsJobExecutor(
|
||||
protected open val requestExecutor: RequestExecutor = RequestExecutor(),
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package net.dankito.banking.fints.mapper
|
||||
|
||||
import net.dankito.banking.client.model.*
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.response.ErrorCode
|
||||
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
|
||||
import net.dankito.banking.fints.model.*
|
||||
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
||||
import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
|
||||
import net.dankito.banking.fints.response.segments.AccountType
|
||||
|
||||
|
||||
open class FinTsModelMapper {
|
||||
|
||||
open fun mapToAccountData(credentials: BankAccountIdentifier, param: GetAccountDataParameter): AccountData {
|
||||
val accountData = AccountData(credentials.identifier, credentials.subAccountNumber, Laenderkennzeichen.Germany, param.bankCode,
|
||||
credentials.iban, param.loginName, null, null, "", null, null, listOf(), listOf())
|
||||
|
||||
// TODO: where to know from if account supports retrieving balance and transactions?
|
||||
accountData.setSupportsFeature(AccountFeature.RetrieveBalance, true)
|
||||
accountData.setSupportsFeature(AccountFeature.RetrieveAccountTransactions, true)
|
||||
|
||||
return accountData
|
||||
}
|
||||
|
||||
|
||||
open fun map(bank: BankData): CustomerAccount {
|
||||
return CustomerAccount(bank.bankCode, bank.customerId, bank.pin, bank.finTs3ServerAddress, bank.bankName, bank.bic, bank.customerName, bank.userId,
|
||||
map(bank.accounts), bank.tanMethodsAvailableForUser, bank.selectedTanMethod, bank.tanMedia, bank.selectedTanMedium)
|
||||
}
|
||||
|
||||
open fun map(accounts: List<AccountData>): List<BankAccount> {
|
||||
return accounts.map { map(it) }
|
||||
}
|
||||
|
||||
open fun map(account: AccountData): BankAccount {
|
||||
return BankAccount(account.accountIdentifier, account.subAccountAttribute, account.iban, account.accountHolderName, map(account.accountType), account.productName,
|
||||
account.currency ?: "EUR", account.accountLimit, account.supportsRetrievingAccountTransactions, account.supportsRetrievingBalance, account.supportsTransferringMoney, account.supportsRealTimeTransfer)
|
||||
}
|
||||
|
||||
open fun map(accountType: AccountType?): BankAccountType {
|
||||
return when (accountType) {
|
||||
AccountType.Girokonto -> BankAccountType.CheckingAccount
|
||||
AccountType.Sparkonto -> BankAccountType.SavingsAccount
|
||||
AccountType.Festgeldkonto -> BankAccountType.FixedTermDepositAccount
|
||||
AccountType.Wertpapierdepot -> BankAccountType.SecuritiesAccount
|
||||
AccountType.Darlehenskonto -> BankAccountType.LoanAccount
|
||||
AccountType.Kreditkartenkonto -> BankAccountType.CreditCardAccount
|
||||
AccountType.FondsDepot -> BankAccountType.FundDeposit
|
||||
AccountType.Bausparvertrag -> BankAccountType.BuildingLoanContract
|
||||
AccountType.Versicherungsvertrag -> BankAccountType.InsuranceContract
|
||||
else -> BankAccountType.Other
|
||||
}
|
||||
}
|
||||
|
||||
open fun map(bank: BankData, retrievedTransactionsResponses: List<GetAccountTransactionsResponse>): CustomerAccount {
|
||||
val customerAccount = map(bank)
|
||||
val retrievedData = retrievedTransactionsResponses.mapNotNull { it.retrievedData }
|
||||
|
||||
customerAccount.accounts.forEach { bankAccount ->
|
||||
retrievedData.firstOrNull { it.account.accountIdentifier == bankAccount.identifier }?.let { accountTransactionsResponse ->
|
||||
bankAccount.balance = accountTransactionsResponse.balance ?: Money.Zero
|
||||
bankAccount.retrievedTransactionsFrom = accountTransactionsResponse.retrievedTransactionsFrom
|
||||
bankAccount.retrievedTransactionsTo = accountTransactionsResponse.retrievedTransactionsTo
|
||||
bankAccount.bookedTransactions = map(accountTransactionsResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return customerAccount
|
||||
}
|
||||
|
||||
open fun map(data: RetrievedAccountData): List<AccountTransaction> {
|
||||
return data.bookedTransactions.map { map(it) }
|
||||
}
|
||||
|
||||
open fun map(transaction: net.dankito.banking.fints.model.AccountTransaction): AccountTransaction {
|
||||
return AccountTransaction(transaction.amount, transaction.unparsedReference, transaction.bookingDate,
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText, transaction.valueDate,
|
||||
transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance,
|
||||
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||
transaction.compensationAmount, transaction.originalAmount, transaction.sepaReference, transaction.deviantOriginator, transaction.deviantRecipient,
|
||||
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
|
||||
transaction.currencyType, transaction.bookingKey, transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails,
|
||||
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber)
|
||||
}
|
||||
|
||||
|
||||
open fun mapErrorCode(response: FinTsClientResponse): ErrorCode? {
|
||||
return when {
|
||||
response.internalError != null -> ErrorCode.InternalError
|
||||
response.errorMessagesFromBank.isNotEmpty() -> ErrorCode.BankReturnedError
|
||||
response.isPinLocked -> ErrorCode.AccountLocked
|
||||
response.wrongCredentialsEntered -> ErrorCode.WrongCredentials
|
||||
response.isJobAllowed == false || response.isJobVersionSupported == false -> ErrorCode.JobNotSupported
|
||||
response.tanRequiredButWeWereToldToAbortIfSo -> ErrorCode.TanRequiredButShouldAbortIfRequiresTan
|
||||
response.userCancelledAction || response.noTanMethodSelected || // either the user really has the choice to select one, then the errorCode would be UserCancelledAction,
|
||||
// or if it gets selected automatically, that means there aren't any TanMethods which should only be the case if before another error occurred
|
||||
// if isStrongAuthenticationRequired is set but tanRequiredButWeWereToldToAbortIfSo then user cancelled entering TAN
|
||||
response.isStrongAuthenticationRequired -> ErrorCode.UserCancelledAction
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
open fun mapErrorMessages(response: FinTsClientResponse?): String? {
|
||||
if (response == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val errorMessages = response.errorMessagesFromBank.toMutableList()
|
||||
|
||||
response.internalError?.let {
|
||||
errorMessages.add(it)
|
||||
}
|
||||
|
||||
return if (errorMessages.isEmpty()) null
|
||||
else errorMessages.joinToString("\r\n")
|
||||
}
|
||||
|
||||
open fun mergeMessageLog(vararg responses: FinTsClientResponse?): List<MessageLogEntry> {
|
||||
return responses.filterNotNull().flatMap { it.messageLogWithoutSensitiveData }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package net.dankito.banking.fints.model
|
||||
|
||||
import net.dankito.banking.fints.FinTsClientDeprecated
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
|
||||
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
|
||||
import net.dankito.banking.fints.response.segments.AccountType
|
||||
|
@ -27,7 +27,7 @@ open class AccountData(
|
|||
|
||||
|
||||
open val isAccountTypeSupportedByApplication: Boolean
|
||||
get() = FinTsClientDeprecated.SupportedAccountTypes.contains(accountType)
|
||||
get() = FinTsClient.SupportedAccountTypes.contains(accountType)
|
||||
|| allowedJobNames.contains(CustomerSegmentId.Balance.id)
|
||||
|| allowedJobNames.contains(CustomerSegmentId.AccountTransactionsMt940.id)
|
||||
|
||||
|
|
|
@ -6,6 +6,11 @@ open class Money(
|
|||
val currency: Currency
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val Zero = Money(Amount.Zero, "EUR")
|
||||
}
|
||||
|
||||
|
||||
|
||||
constructor(amount: Amount, currencyCode: String) : this(amount, Currency(currencyCode))
|
||||
|
||||
|
|
|
@ -8,12 +8,10 @@ open class AddAccountResponse(
|
|||
context: JobContext,
|
||||
getAccountsResponse: BankResponse,
|
||||
open val retrievedTransactionsResponses: List<GetAccountTransactionsResponse> = listOf()
|
||||
) : FinTsClientResponse(context, getAccountsResponse) {
|
||||
|
||||
open val bank: BankData = context.bank
|
||||
) : GetAccountInfoResponse(context, getAccountsResponse) {
|
||||
|
||||
override val successful: Boolean
|
||||
get() = super.successful && bank.accounts.isNotEmpty()
|
||||
get() = super.successful
|
||||
&& bank.accounts.size == retrievedTransactionsResponses.size
|
||||
&& retrievedTransactionsResponses.none { it.noTanMethodSelected }
|
||||
&& retrievedTransactionsResponses.none { it.isPinLocked }
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package net.dankito.banking.fints.response.client
|
||||
|
||||
import net.dankito.banking.fints.model.*
|
||||
import net.dankito.banking.fints.response.BankResponse
|
||||
|
||||
|
||||
open class GetAccountInfoResponse(
|
||||
context: JobContext,
|
||||
getAccountsResponse: BankResponse,
|
||||
) : FinTsClientResponse(context, getAccountsResponse) {
|
||||
|
||||
open val bank: BankData = context.bank
|
||||
|
||||
override val successful: Boolean
|
||||
get() = super.successful && bank.accounts.isNotEmpty()
|
||||
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
package net.dankito.banking.fints
|
||||
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.response.GetAccountDataResponse
|
||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||
import net.dankito.banking.fints.model.AddAccountParameter
|
||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||
import net.dankito.banking.fints.webclient.IWebClient
|
||||
|
||||
open class iOSFinTsClient(
|
||||
|
@ -12,7 +11,7 @@ open class iOSFinTsClient(
|
|||
webClient: IWebClient
|
||||
) {
|
||||
|
||||
protected open val fintsClient = FinTsClientDeprecated(callback, FinTsJobExecutor(RequestExecutor(webClient = webClient)))
|
||||
protected open val fintsClient = FinTsClient(callback, FinTsJobExecutor(RequestExecutor(webClient = webClient)))
|
||||
|
||||
open var callback: FinTsClientCallback
|
||||
get() = fintsClient.callback
|
||||
|
@ -21,9 +20,9 @@ open class iOSFinTsClient(
|
|||
}
|
||||
|
||||
|
||||
open fun addAccountAsync(parameter: AddAccountParameter, callback: (AddAccountResponse) -> Unit) {
|
||||
open fun getAccountData(parameter: GetAccountDataParameter, callback: (GetAccountDataResponse) -> Unit) {
|
||||
GlobalScope.launch(Dispatchers.Main) { // do not block UI thread as with runBlocking { } but stay on UI thread as passing mutable state between threads currently doesn't work in Kotlin/Native
|
||||
callback(fintsClient.addAccountAsync(parameter))
|
||||
callback(fintsClient.getAccountData(parameter))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.dankito.banking.fints.FinTsClientDeprecated
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.client.model.CustomerAccount
|
||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
||||
import net.dankito.banking.fints.model.AddAccountParameter
|
||||
import net.dankito.banking.fints.model.RetrievedAccountData
|
||||
import net.dankito.banking.fints.model.TanChallenge
|
||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||
import net.dankito.utils.multiplatform.extensions.*
|
||||
import platform.posix.exit
|
||||
|
||||
|
@ -23,13 +23,19 @@ class Application {
|
|||
|
||||
fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String) {
|
||||
runBlocking {
|
||||
val client = FinTsClientDeprecated(SimpleFinTsClientCallback { tanChallenge -> enterTan(tanChallenge) })
|
||||
val client = FinTsClient(SimpleFinTsClientCallback { tanChallenge -> enterTan(tanChallenge) })
|
||||
|
||||
val response = client.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
val response = client.getAccountData(GetAccountDataParameter(bankCode, customerId, pin, finTs3ServerAddress))
|
||||
|
||||
println("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
|
||||
if (response.error != null) {
|
||||
println("An error occurred: ${response.error}${response.errorMessage?.let { " $it" }}")
|
||||
}
|
||||
|
||||
displayRetrievedAccountData(response)
|
||||
response.customerAccount?.let { account ->
|
||||
println("Retrieved response from ${account.bankName} for ${account.customerName}")
|
||||
|
||||
displayRetrievedAccountData(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,36 +56,34 @@ class Application {
|
|||
}
|
||||
|
||||
|
||||
private fun displayRetrievedAccountData(response: AddAccountResponse) {
|
||||
if (response.retrievedData.isEmpty()) {
|
||||
private fun displayRetrievedAccountData(customer: CustomerAccount) {
|
||||
if (customer.accounts.isEmpty()) {
|
||||
println()
|
||||
|
||||
if (response.bank.accounts.isEmpty()) {
|
||||
println("No data retrieved")
|
||||
} else {
|
||||
println("No transactions retrieved for accounts:")
|
||||
response.bank.accounts.forEach { account -> println("- $account") }
|
||||
}
|
||||
println("No account data retrieved")
|
||||
} else if (customer.accounts.flatMap { it.bookedTransactions }.isEmpty()) {
|
||||
println()
|
||||
println("No transactions retrieved for accounts:")
|
||||
customer.accounts.forEach { println("- $it") }
|
||||
}
|
||||
|
||||
response.retrievedData.forEach { data ->
|
||||
customer.accounts.forEach { account ->
|
||||
println()
|
||||
println("${data.account}:")
|
||||
println("${account}:")
|
||||
println()
|
||||
|
||||
if (data.bookedTransactions.isEmpty()) {
|
||||
if (account.bookedTransactions.isEmpty()) {
|
||||
println("No transactions retrieved for this account")
|
||||
} else {
|
||||
displayTransactions(data)
|
||||
displayTransactions(account.bookedTransactions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayTransactions(data: RetrievedAccountData) {
|
||||
val countTransactionsDigits = data.bookedTransactions.size.numberOfDigits
|
||||
val largestAmountDigits = data.bookedTransactions.maxByOrNull { it.amount.displayString.length }?.amount?.displayString?.length ?: 0
|
||||
private fun displayTransactions(bookedTransactions: List<AccountTransaction>) {
|
||||
val countTransactionsDigits = bookedTransactions.size.numberOfDigits
|
||||
val largestAmountDigits = bookedTransactions.maxByOrNull { it.amount.displayString.length }?.amount?.displayString?.length ?: 0
|
||||
|
||||
data.bookedTransactions.sortedByDescending { it.valueDate }.forEachIndexed { transactionIndex, transaction ->
|
||||
bookedTransactions.sortedByDescending { it.valueDate }.forEachIndexed { transactionIndex, transaction ->
|
||||
println("${(transactionIndex + 1).toStringWithMinDigits(countTransactionsDigits, " ")}. ${formatDate(transaction.valueDate)} " +
|
||||
"${transaction.amount.displayString.ensureMinStringLength(largestAmountDigits, " ")} ${transaction.otherPartyName ?: ""} - ${transaction.reference}")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue