Compare commits
No commits in common. "main" and "v0.5.0" have entirely different histories.
79 changed files with 725 additions and 2796 deletions
|
@ -53,7 +53,6 @@ kotlin {
|
|||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
|
|
@ -1,57 +1,14 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.*
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.codinux.banking.client.model.response.TransferMoneyResponse
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
interface BankingClient {
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, use the method overload that takes a
|
||||
* [GetAccountDataRequest] parameter and set [RetrieveTransactions] and may to and from date in its options object.
|
||||
*/
|
||||
suspend fun getAccountDataAsync(bankCode: String, loginName: String, password: String) =
|
||||
getAccountDataAsync(GetAccountDataRequest(bankCode, loginName, password))
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, set [RetrieveTransactions] and may to and from
|
||||
* date in [GetAccountDataRequest.options].
|
||||
*/
|
||||
suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse>
|
||||
|
||||
/**
|
||||
* Convenience wrapper around [getAccountDataAsync].
|
||||
* Updates account's transactions beginning from [BankAccount.lastAccountUpdateTime].
|
||||
* This may requires TAN if [BankAccount.lastAccountUpdateTime] is older than 90 days.
|
||||
*
|
||||
* Optionally specify which [accounts] should be updated. If not specified all accounts will be updated.
|
||||
*/
|
||||
suspend fun updateAccountTransactionsAsync(
|
||||
bank: BankAccess, accounts: List<BankAccount>? = null,
|
||||
preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
|
||||
): Response<List<GetTransactionsResponse>>
|
||||
|
||||
|
||||
suspend fun transferMoneyAsync(bankCode: String, loginName: String, password: String, recipientName: String,
|
||||
recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null) =
|
||||
transferMoneyAsync(TransferMoneyRequestForUser(bankCode, loginName, password, null, recipientName, recipientAccountIdentifier, null, amount, paymentReference = paymentReference))
|
||||
|
||||
suspend fun transferMoneyAsync(request: TransferMoneyRequestForUser): Response<TransferMoneyResponse>
|
||||
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
|
@ -8,11 +7,4 @@ interface BankingClientCallback {
|
|||
|
||||
fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
|
||||
|
||||
/**
|
||||
* An optional method, for sure not available for all client implementations (currently only for FinTs4kBankingClient).
|
||||
*
|
||||
* Gets fired when a FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
|
||||
*/
|
||||
fun messageLogAdded(messageLogEntry: MessageLogEntry)
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BankingClientForCustomer {
|
||||
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
suspend fun getAccountDataAsync() = getAccountDataAsync(GetAccountDataOptions())
|
||||
|
||||
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
|
||||
abstract class BankingClientForCustomerBase(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val client: BankingClient
|
||||
) : BankingClientForCustomer {
|
||||
|
||||
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
||||
client.getAccountDataAsync(GetAccountDataRequest(credentials, options))
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequest
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.codinux.banking.client.model.response.TransferMoneyResponse
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
interface BankingClientForUser {
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, use the method overload that takes a
|
||||
* [GetAccountDataOptions] parameter and set [RetrieveTransactions] and may to and from date.
|
||||
*/
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
suspend fun getAccountDataAsync() = getAccountDataAsync(GetAccountDataOptions())
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, set [GetAccountDataOptions.retrieveTransactions]
|
||||
* and may [GetAccountDataOptions.retrieveTransactionsFrom] and [GetAccountDataOptions.retrieveTransactionsTo].
|
||||
*/
|
||||
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
||||
|
||||
/**
|
||||
* Convenience wrapper around [getAccountDataAsync].
|
||||
* Updates account's transactions beginning from [BankAccount.lastAccountUpdateTime].
|
||||
* This may requires TAN if [BankAccount.lastAccountUpdateTime] is older than 90 days.
|
||||
*/
|
||||
suspend fun updateAccountTransactionsAsync(
|
||||
accounts: List<BankAccount>? = null,
|
||||
preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
|
||||
): Response<List<GetTransactionsResponse>>
|
||||
|
||||
|
||||
suspend fun transferMoneyAsync(recipientName: String, recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null): Response<TransferMoneyResponse>
|
||||
|
||||
suspend fun transferMoneyAsync(request: TransferMoneyRequest): Response<TransferMoneyResponse>
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.BankAccess
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
abstract class BankingClientForUserBase(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val client: BankingClient
|
||||
) : BankingClientForUser {
|
||||
|
||||
private lateinit var bank: BankAccess
|
||||
|
||||
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
||||
client.getAccountDataAsync(GetAccountDataRequest(credentials, options)).also {
|
||||
it.data?.bank?.let { retrievedBank ->
|
||||
this.bank = retrievedBank
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>?, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>?): Response<List<GetTransactionsResponse>> =
|
||||
client.updateAccountTransactionsAsync(bank, accounts)
|
||||
|
||||
|
||||
override suspend fun transferMoneyAsync(recipientName: String, recipientAccountIdentifier: String, amount: Amount, paymentReference: String?) =
|
||||
transferMoneyAsync(TransferMoneyRequest(null, recipientName, recipientAccountIdentifier, null, amount, paymentReference = paymentReference))
|
||||
|
||||
override suspend fun transferMoneyAsync(request: TransferMoneyRequest) =
|
||||
client.transferMoneyAsync(TransferMoneyRequestForUser(bank.domesticBankCode, bank.loginName, bank.password!!, request))
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
|
|||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BlockingBankingClientForUser {
|
||||
interface BlockingBankingClientForCustomer {
|
||||
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
fun getAccountData() = getAccountData(GetAccountDataOptions())
|
|
@ -4,10 +4,10 @@ import net.codinux.banking.client.model.AccountCredentials
|
|||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
|
||||
abstract class BlockingBankingClientForUserBase(
|
||||
abstract class BlockingBankingClientForCustomerBase(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val client: BlockingBankingClient
|
||||
) : BlockingBankingClientForUser {
|
||||
) : BlockingBankingClientForCustomer {
|
||||
|
||||
override fun getAccountData(options: GetAccountDataOptions) =
|
||||
client.getAccountData(GetAccountDataRequest(credentials, options))
|
|
@ -1,11 +1,9 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
open class SimpleBankingClientCallback(
|
||||
protected val messageLogAdded: ((MessageLogEntry) -> Unit)? = null,
|
||||
protected val enterTan: ((tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) -> Unit)? = null
|
||||
) : BankingClientCallback {
|
||||
|
||||
|
@ -17,8 +15,4 @@ open class SimpleBankingClientCallback(
|
|||
}
|
||||
}
|
||||
|
||||
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
|
||||
messageLogAdded?.invoke(messageLogEntry)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package net.codinux.banking.client.service
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
|
||||
open class BankingModelService {
|
||||
|
||||
/**
|
||||
* It's not possible to retrieve only new transactions from bank server (almost no bank implements HKKAN job). So
|
||||
* for updating account transactions we start at the date of latest account transactions retrieval time (e.g.
|
||||
* transactions have at last been fetched at 01. September 12:00, then there may have been some other transactions
|
||||
* been booked on September 1st after 12:00 o'clock).
|
||||
*
|
||||
* Therefore retrieved account transactions may contain transactions that we already have locally. This method filters
|
||||
* from [retrievedTransactions] those already in [existingTransactions] and returns only that ones, that are not in
|
||||
* [existingTransactions].
|
||||
*/
|
||||
open fun findNewTransactions(retrievedTransactions: List<AccountTransaction>, existingTransactions: List<AccountTransaction>): List<AccountTransaction> {
|
||||
val existingTransactionsByIdentifier = existingTransactions.associateBy { it.identifier }
|
||||
val existingTransactionsIdentifiers = existingTransactionsByIdentifier.keys
|
||||
|
||||
return retrievedTransactions.filter { transaction -> existingTransactionsIdentifiers.contains(transaction.identifier) == false }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +1,21 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.BankAccess
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
/* BankingClient */
|
||||
|
||||
fun BankingClient.getAccountData(bankCode: String, loginName: String, password: String) = runBlocking {
|
||||
getAccountDataAsync(bankCode, loginName, password)
|
||||
this@getAccountData.getAccountDataAsync(bankCode, loginName, password)
|
||||
}
|
||||
|
||||
fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
||||
getAccountDataAsync(request)
|
||||
this@getAccountData.getAccountDataAsync(request)
|
||||
}
|
||||
|
||||
fun BankingClient.updateAccountTransactions(bank: BankAccess, accounts: List<BankAccount>? = null, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications) = runBlocking {
|
||||
updateAccountTransactionsAsync(bank, accounts, preferredTanMethodsIfSelectedTanMethodIsNotAvailable)
|
||||
fun BankingClientForCustomer.getAccountData() = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync()
|
||||
}
|
||||
|
||||
|
||||
fun BankingClient.transferMoney(bankCode: String, loginName: String, password: String, recipientName: String,
|
||||
recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null) = runBlocking {
|
||||
transferMoneyAsync(bankCode, loginName, password, recipientName, recipientAccountIdentifier, amount, paymentReference)
|
||||
}
|
||||
|
||||
fun BankingClient.transferMoney(request: TransferMoneyRequestForUser) = runBlocking {
|
||||
transferMoneyAsync(request)
|
||||
}
|
||||
|
||||
|
||||
/* BankingClientForUser */
|
||||
|
||||
fun BankingClientForUser.getAccountData() = runBlocking {
|
||||
getAccountDataAsync()
|
||||
}
|
||||
|
||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
getAccountDataAsync(options)
|
||||
}
|
||||
|
||||
fun BankingClientForUser.updateAccountTransactions(accounts: List<BankAccount>? = null, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications) = runBlocking {
|
||||
updateAccountTransactionsAsync(accounts, preferredTanMethodsIfSelectedTanMethodIsNotAvailable)
|
||||
}
|
||||
|
||||
|
||||
fun BankingClientForUser.transferMoney(recipientName: String, recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null) = runBlocking {
|
||||
transferMoneyAsync(recipientName, recipientAccountIdentifier, amount, paymentReference)
|
||||
}
|
||||
|
||||
fun BankingClientForUser.transferMoney(request: TransferMoneyRequest) = runBlocking {
|
||||
transferMoneyAsync(request)
|
||||
fun BankingClientForCustomer.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(options)
|
||||
}
|
|
@ -12,10 +12,10 @@ fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
|||
this@getAccountData.getAccountDataAsync(request)
|
||||
}
|
||||
|
||||
fun BankingClientForUser.getAccountData() = runBlocking {
|
||||
fun BankingClientForCustomer.getAccountData() = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync()
|
||||
}
|
||||
|
||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
fun BankingClientForCustomer.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(options)
|
||||
}
|
|
@ -63,7 +63,6 @@ kotlin {
|
|||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
@ -79,8 +78,6 @@ kotlin {
|
|||
|
||||
val kotlinxDateTimeVersion: String by project
|
||||
val jsJodaTimeZoneVersion: String by project
|
||||
val ionspinBigNumVersion: String by project
|
||||
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
|
@ -104,26 +101,12 @@ kotlin {
|
|||
jsMain {
|
||||
dependencies {
|
||||
api(npm("@js-joda/timezone", jsJodaTimeZoneVersion))
|
||||
|
||||
implementation(npm("big.js", "6.0.3"))
|
||||
}
|
||||
}
|
||||
jsTest { }
|
||||
|
||||
nativeMain { }
|
||||
nativeTest { }
|
||||
|
||||
linuxMain {
|
||||
dependencies {
|
||||
implementation("com.ionspin.kotlin:bignum:$ionspinBigNumVersion")
|
||||
}
|
||||
}
|
||||
|
||||
mingwMain {
|
||||
dependencies {
|
||||
implementation("com.ionspin.kotlin:bignum:$ionspinBigNumVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import platform.Foundation.NSDecimalNumber
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import platform.Foundation.NSDecimalNumberHandler
|
||||
import platform.Foundation.NSRoundingMode
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero = Amount("0.00")
|
||||
|
||||
private val handler = NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(roundingMode = NSRoundingMode.NSRoundBankers, scale = DecimalPrecision.toShort(), false, false, false, false)
|
||||
}
|
||||
|
||||
|
||||
internal val amount = NSDecimalNumber(string = amount)
|
||||
|
||||
|
||||
actual operator fun plus(other: Amount): Amount =
|
||||
Amount(amount.decimalNumberByAdding(other.amount).stringValue)
|
||||
|
||||
actual operator fun minus(other: Amount): Amount =
|
||||
Amount(amount.decimalNumberBySubtracting(other.amount).stringValue)
|
||||
|
||||
actual operator fun times(other: Amount): Amount =
|
||||
Amount(amount.decimalNumberByMultiplyingBy(other.amount).stringValue)
|
||||
|
||||
actual operator fun div(other: Amount): Amount =
|
||||
Amount(amount.decimalNumberByDividingBy(other.amount, handler).stringValue)
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Amount && this.amount == other.amount
|
||||
}
|
||||
|
||||
override fun hashCode() =
|
||||
amount.hashCode()
|
||||
|
||||
actual override fun toString(): String = amount.stringValue
|
||||
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class AccountTransaction(
|
||||
val amount: Amount = Amount.Zero, // TODO: a string is really bad in UI, find a better solution
|
||||
val amount: Amount = Amount.Zero,
|
||||
val currency: String,
|
||||
val reference: String?, // Alternative: Remittance information, Transaction description, (payment) purpose, payment reference
|
||||
val reference: String, // Alternative: purpose (or Remittance information)
|
||||
|
||||
/**
|
||||
* Transaction date (Buchungstag) - der Tag, an dem ein Zahlungsvorgang in das System einer Bank eingegangen ist.
|
||||
|
@ -24,130 +22,44 @@ open class AccountTransaction(
|
|||
*/
|
||||
val valueDate: LocalDate,
|
||||
|
||||
// deutsche Begriffe: "Transaktionspartei" oder "Beteiligte Partei"
|
||||
// Englische: Transaction party (ist die beste Wahl für eine neutrale und übergreifende Beschreibung),
|
||||
// Counterparty (ist nützlich in formellen oder finanziellen Kontexten), Participant (ist breiter gefasst, aber weniger präzise)
|
||||
/**
|
||||
* Name of the Payer (debtor) or Payee (creditor)
|
||||
*/
|
||||
val otherPartyName: String? = null, // Alternatives: Parties involved, Transaction parties.single names: Beneficiary, Payee respectively Payer, Debtor
|
||||
/**
|
||||
* Bank Identifier, in most cases BIC, of the Payer or Payee
|
||||
*/
|
||||
val otherPartyBankId: String? = null,
|
||||
/**
|
||||
* Account Identifier, in most cases IBAN, of the Payer or Payee
|
||||
*/
|
||||
val otherPartyBankCode: String? = null,
|
||||
val otherPartyAccountId: String? = null,
|
||||
|
||||
/**
|
||||
* Buchungstext, z. B. DAUERAUFTRAG, BARGELDAUSZAHLUNG, ONLINE-UEBERWEISUNG, FOLGELASTSCHRIFT, ...
|
||||
*/
|
||||
val postingText: String? = null,
|
||||
val bookingText: String? = null,
|
||||
val information: String? = null,
|
||||
|
||||
val statementNumber: Int? = null,
|
||||
val sequenceNumber: Int? = null,
|
||||
|
||||
val openingBalance: Amount? = null,
|
||||
val closingBalance: Amount? = null,
|
||||
|
||||
/**
|
||||
* Auszugsnummer
|
||||
*/
|
||||
val statementNumber: Int? = null,
|
||||
/**
|
||||
* Blattnummer
|
||||
*/
|
||||
val sheetNumber: Int? = null,
|
||||
|
||||
/**
|
||||
* Kundenreferenz.
|
||||
*/
|
||||
val customerReference: String? = null,
|
||||
/**
|
||||
* Bankreferenz
|
||||
*/
|
||||
val bankReference: String? = null,
|
||||
/**
|
||||
* Währungsart und Umsatzbetrag in Ursprungswährung
|
||||
*/
|
||||
val furtherInformation: String? = null,
|
||||
|
||||
|
||||
/* Information mostly of direct debit (Lastschrift) */
|
||||
|
||||
val endToEndReference: String? = null,
|
||||
val customerReference: String? = null,
|
||||
val mandateReference: String? = null,
|
||||
val creditorIdentifier: String? = null,
|
||||
val originatorsIdentificationCode: String? = null,
|
||||
|
||||
/**
|
||||
* Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift
|
||||
* sowie optionalem Zinsausgleich.
|
||||
*/
|
||||
val compensationAmount: String? = null,
|
||||
/**
|
||||
* Betrag der ursprünglichen Lastschrift
|
||||
*/
|
||||
val originalAmount: String? = null,
|
||||
/**
|
||||
* Abweichender Überweisender oder Zahlungsempfänger
|
||||
*/
|
||||
val sepaReference: String? = null,
|
||||
val deviantOriginator: String? = null,
|
||||
/**
|
||||
* Abweichender Zahlungsempfänger oder Zahlungspflichtiger
|
||||
*/
|
||||
val deviantRecipient: String? = null,
|
||||
val referenceWithNoSpecialType: String? = null,
|
||||
val primaNotaNumber: String? = null,
|
||||
val textKeySupplement: String? = null,
|
||||
|
||||
/**
|
||||
* Primanoten-Nr.
|
||||
*/
|
||||
val journalNumber: String? = null,
|
||||
/**
|
||||
* Bei R-Transaktionen siehe Tabelle der
|
||||
* SEPA-Rückgabecodes, bei SEPALastschriften siehe optionale Belegung
|
||||
* bei GVC 104 und GVC 105 (GVC = Geschäftsvorfallcode)
|
||||
*/
|
||||
val textKeyAddition: String? = null,
|
||||
val currencyType: String? = null,
|
||||
val bookingKey: String? = null,
|
||||
val referenceForTheAccountOwner: String? = null,
|
||||
val referenceOfTheAccountServicingInstitution: String? = null,
|
||||
val supplementaryDetails: String? = null,
|
||||
|
||||
/**
|
||||
* Referenznummer, die vom Sender als eindeutige Kennung für die Nachricht vergeben wurde
|
||||
* (z.B. als Referenz auf stornierte Nachrichten).
|
||||
*/
|
||||
val orderReferenceNumber: String? = null,
|
||||
/**
|
||||
* Bezugsreferenz
|
||||
*/
|
||||
val referenceNumber: String? = null,
|
||||
val transactionReferenceNumber: String? = null,
|
||||
val relatedReferenceNumber: String? = null,
|
||||
|
||||
/**
|
||||
* Storno, ob die Buchung storniert wurde(?).
|
||||
* Aus:
|
||||
* „RC“ = Storno Haben
|
||||
* „RD“ = Storno Soll
|
||||
*/
|
||||
val isReversal: Boolean = false,
|
||||
|
||||
var userSetReference: String? = null,
|
||||
var userSetOtherPartyName: String? = null,
|
||||
var category: String? = null,
|
||||
var userSetDisplayName: String? = null,
|
||||
var notes: String? = null,
|
||||
) {
|
||||
|
||||
open val identifier by lazy {
|
||||
"$amount $currency $bookingDate $valueDate $reference $otherPartyName $otherPartyBankId $otherPartyAccountId"
|
||||
}
|
||||
|
||||
@get:JsonIgnore
|
||||
open val displayedReference: String?
|
||||
get() = userSetReference ?: referenceNumber
|
||||
|
||||
@get:JsonIgnore
|
||||
open val displayedOtherPartyName: String?
|
||||
get() = userSetOtherPartyName ?: otherPartyName
|
||||
|
||||
@get:JsonIgnore
|
||||
open val displayedOtherPartyNameOrPostingText: String?
|
||||
get() = displayedOtherPartyName ?: postingText
|
||||
|
||||
|
||||
override fun toString() = "${valueDate.dayOfMonth}.${valueDate.monthNumber}.${valueDate.year} ${amount.toString().padStart(4, ' ')} ${if (currency == "EUR") "€" else currency} ${otherPartyName ?: ""} - $reference"
|
||||
}
|
|
@ -1,44 +1,18 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
|
||||
internal const val DecimalPrecision = 20 // 20 to match Big.js's behavior
|
||||
|
||||
|
||||
fun Amount.toFloat() =
|
||||
this.toString().toFloat()
|
||||
|
||||
fun Amount.toDouble() =
|
||||
this.toString().toDouble()
|
||||
|
||||
val Amount.isNegative: Boolean
|
||||
get() = this.toString().startsWith("-")
|
||||
|
||||
fun Collection<Amount>.sum(): Amount {
|
||||
var sum: Amount = Amount.Zero
|
||||
|
||||
for (element in this) {
|
||||
sum += element
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
|
||||
|
||||
@JvmInline
|
||||
@NoArgConstructor
|
||||
expect class Amount(amount: String = "0") {
|
||||
value class Amount(val amount: String = "0") {
|
||||
|
||||
companion object {
|
||||
val Zero: Amount
|
||||
val Zero = Amount("0")
|
||||
|
||||
fun fromString(amount: String): Amount = Amount(amount)
|
||||
}
|
||||
|
||||
|
||||
operator fun plus(other: Amount): Amount
|
||||
operator fun minus(other: Amount): Amount
|
||||
operator fun times(other: Amount): Amount
|
||||
operator fun div(other: Amount): Amount
|
||||
|
||||
override fun toString(): String
|
||||
|
||||
override fun toString() = amount
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMedium
|
||||
import net.codinux.banking.client.model.tan.TanMethod
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class BankAccess(
|
||||
/**
|
||||
* The country specific bank code, like in Germany the Bankleitzahl, in USA the Routing Number, in Great Britain
|
||||
* the Sort Code, in India the FSC Code, ...
|
||||
*/
|
||||
val domesticBankCode: String,
|
||||
var loginName: String,
|
||||
/**
|
||||
* User may decides to not save password.
|
||||
*/
|
||||
var password: String?,
|
||||
|
||||
val bankName: String,
|
||||
val bic: String?, // not all banks (in the world) have a BIC
|
||||
|
||||
val customerName: String,
|
||||
/**
|
||||
* The customer is the person or organisation that owns the account at the bank.
|
||||
*
|
||||
* The user is the person that access a customer's account via e.g. FinTS. The user may but not necessarily is
|
||||
* identical with the customer. E.g. an employee from the bookkeeping departement or a tax consultant may only
|
||||
* access an account for someone else.
|
||||
*
|
||||
* So in most cases the userId is identical with the customerId = loginName in our speech, but there are rare cases
|
||||
* where the userId differs from customerId.
|
||||
*/
|
||||
var userId: String? = null,
|
||||
|
||||
open val accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMethod.
|
||||
*
|
||||
* As [tanMethods] also contains selected TanMethod, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMethod] to get selected TanMethod or iterate over [tanMethods] and filter selected one by this id.
|
||||
*/
|
||||
var selectedTanMethodIdentifier: String? = null,
|
||||
open val tanMethods: MutableList<out TanMethod> = mutableListOf(),
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMedium.
|
||||
*
|
||||
* As [tanMedia] also contains selected TanMedium, we didn't want to duplicate this object. Use [selectedTanMedium]
|
||||
* to get selected TanMedium or iterate over [tanMedia] and filter selected one by this medium name.
|
||||
*/
|
||||
var selectedTanMediumIdentifier: String? = null,
|
||||
open val tanMedia: MutableList<out TanMedium> = mutableListOf(),
|
||||
|
||||
var bankingGroup: BankingGroup? = null,
|
||||
open var serverAddress: String? = null,
|
||||
/**
|
||||
* The ISO code of the country where the bank resides and to know the system of [domesticBankCode].
|
||||
*/
|
||||
val countryCode: String = "de"
|
||||
) {
|
||||
|
||||
var userSetDisplayName: String? = null
|
||||
var displayIndex: Int = 0
|
||||
|
||||
var iconUrl: String? = null
|
||||
|
||||
var wrongCredentialsEntered: Boolean = false
|
||||
|
||||
/**
|
||||
* BankingClient specific data of this account that the client needs to fulfill its job.
|
||||
*
|
||||
* You should treat it as opaque data, that only makes sense to the BankingClient, and pass it back to the client if set.
|
||||
*
|
||||
* For fints4k e.g. contains the FinTS jobs the bank supports, FinTS specific data like KundensystemID and so on.
|
||||
*
|
||||
* The deserialized in-memory only value of [serializedClientData] so that we don't have to deserialize [serializedClientData] each time.
|
||||
*/
|
||||
var clientData: Any? = null
|
||||
|
||||
/**
|
||||
* Serialized version of [clientData].
|
||||
*
|
||||
* The same as with [clientData] you should treat this value as opaque that only makes sense to the client implementation.
|
||||
*
|
||||
* [clientData] is the deserialized in-memory model of this value, so that we don't have to serialize this value each time.
|
||||
* serializedClientData is the serialized version of clientData so that you can store (and restore) it e.g. to your
|
||||
* database, so that on next application start client implementation doesn't have to fetch all these data again.
|
||||
* Speeds up e.g. getting account transactions and transferring money with fints4k as FinTS requires quite a lot of
|
||||
* data before account transactions can be retrieved.
|
||||
*/
|
||||
var serializedClientData: String? = null
|
||||
|
||||
|
||||
@get:JsonIgnore
|
||||
open val displayName: String
|
||||
get() = userSetDisplayName ?: bankName
|
||||
|
||||
|
||||
@get:JsonIgnore
|
||||
open val accountsSorted: List<out BankAccount>
|
||||
get() = accounts.sortedBy { it.displayIndex }
|
||||
|
||||
@get:JsonIgnore
|
||||
open val tanMethodsSorted: List<out TanMethod>
|
||||
get() = tanMethods.sortedBy { it.identifier }
|
||||
|
||||
@get:JsonIgnore
|
||||
open val tanMediaSorted: List<out TanMedium>
|
||||
get() = tanMedia.sortedBy { it.status }
|
||||
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMethod: TanMethod
|
||||
get() = tanMethods.first { it.identifier == selectedTanMethodIdentifier }
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMedium: TanMedium?
|
||||
get() = tanMedia.firstOrNull { it.identifier == selectedTanMediumIdentifier }
|
||||
|
||||
|
||||
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"
|
||||
|
||||
}
|
|
@ -1,34 +1,32 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlinx.datetime.*
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class BankAccount(
|
||||
val identifier: String,
|
||||
val subAccountNumber: String? = null,
|
||||
val iban: String? = null,
|
||||
val productName: String? = null,
|
||||
var accountHolderName: String,
|
||||
val type: BankAccountType = BankAccountType.Other,
|
||||
val currency: String = DefaultValues.DefaultCurrency,
|
||||
val iban: String? = null,
|
||||
val subAccountNumber: String? = null,
|
||||
val productName: String? = null,
|
||||
val currency: String = "EUR",
|
||||
var accountLimit: String? = null,
|
||||
|
||||
val isAccountTypeSupportedByApplication: Boolean = false,
|
||||
val isAccountTypeSupportedByApplication: Boolean = true,
|
||||
val features: Set<BankAccountFeatures> = emptySet(),
|
||||
|
||||
var balance: Amount = Amount.Zero,
|
||||
|
||||
val serverTransactionsRetentionDays: Int? = null,
|
||||
open var lastAccountUpdateTime: Instant? = null,
|
||||
// var balance: BigDecimal = BigDecimal.ZERO,
|
||||
var balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
|
||||
var retrievedTransactionsFrom: LocalDate? = null,
|
||||
var retrievedTransactionsTo: LocalDate? = null,
|
||||
|
||||
open val bookedTransactions: MutableList<out AccountTransaction> = mutableListOf(),
|
||||
open val prebookedTransactions: MutableList<out PrebookedAccountTransaction> = mutableListOf(),
|
||||
open val holdings: MutableList<out Holding> = mutableListOf(),
|
||||
var haveAllTransactionsBeenRetrieved: Boolean = false,
|
||||
val countDaysForWhichTransactionsAreKept: Int? = null,
|
||||
|
||||
val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
||||
val unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
|
||||
|
||||
var userSetDisplayName: String? = null,
|
||||
var displayIndex: Int = 0,
|
||||
|
@ -36,56 +34,5 @@ open class BankAccount(
|
|||
var hideAccount: Boolean = false,
|
||||
var includeInAutomaticAccountsUpdate: Boolean = true
|
||||
) {
|
||||
@get:JsonIgnore
|
||||
open val displayName: String
|
||||
get() = userSetDisplayName ?: productName ?: identifier
|
||||
|
||||
open fun addTransactions(transactions: List<out AccountTransaction>) {
|
||||
(this.bookedTransactions as MutableList<AccountTransaction>).addAll(transactions)
|
||||
}
|
||||
|
||||
open fun addPrebookedTransactions(transactions: List<out PrebookedAccountTransaction>) {
|
||||
(this.prebookedTransactions as MutableList<PrebookedAccountTransaction>).addAll(transactions)
|
||||
}
|
||||
|
||||
open fun addHoldings(holdings: List<out Holding>) {
|
||||
(this.holdings as MutableList<Holding>).addAll(holdings)
|
||||
}
|
||||
|
||||
@get:JsonIgnore
|
||||
open val supportsBalanceRetrieval: Boolean
|
||||
get() = supportsAnyFeature(BankAccountFeatures.RetrieveBalance)
|
||||
|
||||
@get:JsonIgnore
|
||||
open val supportsTransactionRetrieval: Boolean
|
||||
get() = supportsAnyFeature(BankAccountFeatures.RetrieveTransactions)
|
||||
|
||||
@get:JsonIgnore
|
||||
open val supportsMoneyTransfer: Boolean
|
||||
get() = supportsAnyFeature(BankAccountFeatures.TransferMoney)
|
||||
|
||||
@get:JsonIgnore
|
||||
open val supportsInstantTransfer: Boolean
|
||||
get() = supportsAnyFeature(BankAccountFeatures.InstantTransfer)
|
||||
|
||||
open fun supportsAnyFeature(vararg features: BankAccountFeatures): Boolean =
|
||||
features.any { this.features.contains(it) }
|
||||
|
||||
/**
|
||||
* Determines if all transactions that are retained on bank server have been fetched.
|
||||
*
|
||||
* Does this by comparing [serverTransactionsRetentionDays] to [retrievedTransactionsFrom].
|
||||
*/
|
||||
open val haveAllRetainedTransactionsBeenRetrieved: Boolean by lazy {
|
||||
val fromDay = retrievedTransactionsFrom
|
||||
if (fromDay == null) {
|
||||
false
|
||||
} else {
|
||||
// if countDaysForWhichTransactionsAreKeptOnBankServer is not set, we cannot know for how long bank server keeps transactions. We then assume for 90 days
|
||||
val storageDays = serverTransactionsRetentionDays ?: 90
|
||||
fromDay < Clock.System.now().toLocalDateTime(TimeZone.of("Europe/Berlin")).date.minus(storageDays, DateTimeUnit.DAY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = "$type $identifier $productName (IBAN: $iban)"
|
||||
}
|
|
@ -4,5 +4,5 @@ enum class BankAccountFeatures {
|
|||
RetrieveTransactions,
|
||||
RetrieveBalance,
|
||||
TransferMoney,
|
||||
InstantTransfer
|
||||
InstantPayment
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class BankAccountIdentifier(
|
||||
val identifier: String,
|
||||
val subAccountNumber: String?,
|
||||
val iban: String?
|
||||
) {
|
||||
constructor(account: BankAccount) : this(account.identifier, account.subAccountNumber, account.iban)
|
||||
|
||||
override fun toString() = "$identifier, $iban"
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class BankInfo(
|
||||
val name: String,
|
||||
val bic: String,
|
||||
val serverAddress: String,
|
||||
val bankingGroup: BankingGroup? = null
|
||||
) {
|
||||
override fun toString() = "$name $bic $bankingGroup"
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
/**
|
||||
* Contains only the basic info of a bank, just enough that a client application can display it to the user
|
||||
* and the user knows exactly which bank is meant / referred to.
|
||||
*
|
||||
* As e.g. when adding a new account, client application has no data about the bank locally, so it can use this
|
||||
* information to display it to the user.
|
||||
*/
|
||||
@NoArgConstructor
|
||||
open class BankViewInfo(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
val bankName: String,
|
||||
val bankingGroup: BankingGroup? = null
|
||||
) {
|
||||
override fun toString() = "$bankCode $bankName $loginName"
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class CustomerAccount(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
/**
|
||||
* User may decides to not save password .
|
||||
*/
|
||||
var password: String?,
|
||||
|
||||
val bankName: String,
|
||||
val bic: String,
|
||||
|
||||
val customerName: String,
|
||||
val userId: String = loginName,
|
||||
|
||||
val accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
var bankingGroup: BankingGroup? = null,
|
||||
var iconUrl: String? = null,
|
||||
) {
|
||||
|
||||
var wrongCredentialsEntered: Boolean = false
|
||||
|
||||
var userSetDisplayName: String? = null
|
||||
var displayIndex: Int = 0
|
||||
|
||||
|
||||
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
/**
|
||||
* Contains only the basic info of a [CustomerAccount], just enough that a client application can display it to the user
|
||||
* and the user knows exactly which [CustomerAccount] is meant / referred.
|
||||
*/
|
||||
@NoArgConstructor
|
||||
open class CustomerAccountViewInfo(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
val bankName: String
|
||||
) {
|
||||
override fun toString() = "$bankCode $bankName $loginName"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
object DefaultValues {
|
||||
const val DefaultCurrency = "EUR"
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
open class MessageLogEntry(
|
||||
open val type: MessageLogEntryType,
|
||||
open val message: String,
|
||||
open val messageWithoutSensitiveData: String? = null,
|
||||
open val error: Throwable? = null,
|
||||
open val time: Instant = Clock.System.now(),
|
||||
|
||||
val messageNumberString: String? = null,
|
||||
val messageNumber: Int? = null,
|
||||
|
||||
val jobType: String? = null,
|
||||
val messageCategory: String? = null,
|
||||
|
||||
val bank: BankAccess? = null, // TODO: make non-null
|
||||
val account: BankAccount? = null
|
||||
) {
|
||||
override fun toString() = "$messageNumberString $jobType $messageCategory $type $message"
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
enum class MessageLogEntryType {
|
||||
Sent,
|
||||
|
||||
Received,
|
||||
|
||||
Error
|
||||
}
|
|
@ -3,5 +3,5 @@ package net.codinux.banking.client.model
|
|||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class PrebookedAccountTransaction {
|
||||
open class UnbookedAccountTransaction {
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package net.codinux.banking.client.model.extensions
|
||||
|
||||
import kotlinx.datetime.DateTimeUnit
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.minus
|
||||
|
||||
|
||||
fun LocalDate.minusDays(days: Int): LocalDate {
|
||||
return this.minus(days, DateTimeUnit.DAY)
|
||||
}
|
||||
|
||||
val TimeZone.Companion.EuropeBerlin: TimeZone
|
||||
get() = TimeZone.of("Europe/Berlin")
|
|
@ -1,47 +1,15 @@
|
|||
package net.codinux.banking.client.model.options
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.BankAccountIdentifier
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
@NoArgConstructor
|
||||
open class GetAccountDataOptions(
|
||||
val retrieveBalance: Boolean = true,
|
||||
val retrieveTransactions: RetrieveTransactions = RetrieveTransactions.OfLast90Days,
|
||||
val retrieveTransactionsFrom: LocalDate? = null,
|
||||
val retrieveTransactionsTo: LocalDate? = null,
|
||||
|
||||
val retrieveBalance: Boolean = true,
|
||||
|
||||
/**
|
||||
* Account(s) may should be excluded from data retrieval, so this option enabled to set for which accounts data
|
||||
* should be retrieved.
|
||||
*/
|
||||
val accounts: List<BankAccountIdentifier> = emptyList(),
|
||||
|
||||
/**
|
||||
* Specifies which [TanMethodType] should be preferred when having to choose between multiple available for user
|
||||
* without requesting the user to choose one.
|
||||
*
|
||||
* By default we don't ask the user which TanMethod she prefers but choose one that could match best. If she really
|
||||
* likes to use a different one, she can select another one in EnterTanDialog.
|
||||
*
|
||||
* By default we prefer non visual TanMethods (like AppTan and SMS) over image based TanMethods (like QR-code and
|
||||
* photoTan) and exclude ChipTanUsb, which is not supported by application, and FlickerCode, which is hard to
|
||||
* implement and therefore most applications have not implemented.
|
||||
*
|
||||
* Console apps can only handle non visual TanMethods.
|
||||
* But also graphical applications prefer non visual TanMethods as then they only have to display a text field to input
|
||||
* TAN, and then image based TanMethods as then they additionally only have to display an image.
|
||||
*/
|
||||
val preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
|
||||
|
||||
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
|
||||
|
||||
val abortIfTanIsRequired: Boolean = false,
|
||||
|
||||
// there's also the option preferredTanMedium, but can hardly find a use case for it as we
|
||||
// cannot know the TanMedium name upfront. In most cases there's only one TanMedium (per TanMethod) anyway.
|
||||
val abortIfTanIsRequired: Boolean = false
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "retrieveBalance=$retrieveBalance, retrieveTransactions=$retrieveTransactions, abortIfTanIsRequired=$abortIfTanIsRequired"
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package net.codinux.banking.client.model.request
|
||||
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.client.model.BankInfo
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
|
||||
@NoArgConstructor
|
||||
open class GetAccountDataRequest(bankCode: String, loginName: String, password: String, val options: GetAccountDataOptions? = null, val bankInfo: BankInfo? = null)
|
||||
open class GetAccountDataRequest(bankCode: String, loginName: String, password: String, val options: GetAccountDataOptions? = null)
|
||||
: AccountCredentials(bankCode, loginName, password) {
|
||||
|
||||
constructor(credentials: AccountCredentials, options: GetAccountDataOptions? = null)
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
package net.codinux.banking.client.model.request
|
||||
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankAccountIdentifier
|
||||
import net.codinux.banking.client.model.DefaultValues
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
@NoArgConstructor
|
||||
open class TransferMoneyRequest(
|
||||
|
||||
/* Sender settings */
|
||||
|
||||
/**
|
||||
* The account from which the money should be withdrawn.
|
||||
* If not specified client retrieves all bank accounts and checks if there is exactly one that supports money transfer.
|
||||
* If no or more than one bank account supports money transfer, the error codes NoAccountSupportsMoneyTransfer or MoreThanOneAccountSupportsMoneyTransfer are returned.
|
||||
*/
|
||||
open val senderAccount: BankAccountIdentifier? = null,
|
||||
|
||||
|
||||
/* Recipient settings */
|
||||
|
||||
open val recipientName: String,
|
||||
/**
|
||||
* The identifier of recipient's account. In most cases the IBAN.
|
||||
*/
|
||||
open val recipientAccountIdentifier: String,
|
||||
/**
|
||||
* The identifier of recipient's bank. In most cases the BIC.
|
||||
* Can be omitted for German banks as the BIC can be derived from IBAN.
|
||||
*/
|
||||
open val recipientBankIdentifier: String? = null,
|
||||
|
||||
|
||||
/* Transfer data */
|
||||
|
||||
open val amount: Amount,
|
||||
|
||||
open val currency: String = DefaultValues.DefaultCurrency,
|
||||
|
||||
/**
|
||||
* The purpose of payment. An optional value that tells the reason for the transfer.
|
||||
*
|
||||
* May not be longer than 140 characters. Some characters are forbidden (TODO: add reference of forbidden characters).
|
||||
*/
|
||||
open val paymentReference: String? = null, // Alternativ: Purpose of payment
|
||||
|
||||
/**
|
||||
* If transfer should be executed as 'real-time transfer', that is the money is in less than 10 seconds
|
||||
* transferred to the account of the recipient.
|
||||
*
|
||||
* May costs extra fees.
|
||||
*
|
||||
* Not supported by all sender and recipient banks.
|
||||
*/
|
||||
open val instantTransfer: Boolean = false, // Alternativ: Instant payment ("Instant payment" ist ebenfalls weit verbreitet und wird oft im Kontext von digitalen Zahlungen verwendet, bei denen die Zahlung in Echtzeit erfolgt. Es kann jedoch breiter gefasst sein und umfasst nicht nur Banktransfers, sondern auch andere Arten von Sofortzahlungen (z.B. mobile Zahlungen).)
|
||||
|
||||
/**
|
||||
* Specifies which [TanMethodType] should be preferred when having to choose between multiple available for user
|
||||
* without requesting the user to choose one.
|
||||
*
|
||||
* By default we don't ask the user which TanMethod she prefers but choose one that could match best. If she really
|
||||
* likes to use a different one, she can select another one in EnterTanDialog.
|
||||
*
|
||||
* By default we prefer non visual TanMethods (like AppTan and SMS) over image based TanMethods (like QR-code and
|
||||
* photoTan) and exclude ChipTanUsb, which is not supported by application, and FlickerCode, which is hard to
|
||||
* implement and therefore most applications have not implemented.
|
||||
*
|
||||
* Console apps can only handle non visual TanMethods.
|
||||
* But also graphical applications prefer non visual TanMethods as then they only have to display a text field to input
|
||||
* TAN, and then image based TanMethods as then they additionally only have to display an image.
|
||||
*/
|
||||
val preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
|
||||
|
||||
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
|
||||
|
||||
val clientData: Any? = null,
|
||||
var serializedClientData: String? = null
|
||||
) {
|
||||
override fun toString() = "$amount to $recipientName - $paymentReference"
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package net.codinux.banking.client.model.request
|
||||
|
||||
import net.codinux.banking.client.model.*
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
/**
|
||||
* For documentation see [TransferMoneyRequest].
|
||||
*/
|
||||
@NoArgConstructor
|
||||
open class TransferMoneyRequestForUser(
|
||||
|
||||
/* Sender settings */
|
||||
|
||||
val bankCode: String,
|
||||
val loginName: String,
|
||||
val password: String,
|
||||
|
||||
senderAccount: BankAccountIdentifier? = null,
|
||||
|
||||
|
||||
/* Recipient settings */
|
||||
|
||||
recipientName: String,
|
||||
recipientAccountIdentifier: String,
|
||||
recipientBankIdentifier: String? = null,
|
||||
|
||||
|
||||
/* Transfer data */
|
||||
|
||||
amount: Amount,
|
||||
currency: String = DefaultValues.DefaultCurrency,
|
||||
paymentReference: String? = null,
|
||||
|
||||
instantTransfer: Boolean = false,
|
||||
|
||||
|
||||
preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
|
||||
tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
|
||||
|
||||
clientData: Any? = null,
|
||||
serializedClientData: String? = null
|
||||
) : TransferMoneyRequest(senderAccount, recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer, preferredTanMethods, tanMethodsNotSupportedByApplication, clientData, serializedClientData) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, request: TransferMoneyRequest)
|
||||
: this(bankCode, loginName, password, request.senderAccount, request.recipientName, request.recipientAccountIdentifier, request.recipientBankIdentifier,
|
||||
request.amount, request.currency, request.paymentReference, request.instantTransfer, request.preferredTanMethods, request.tanMethodsNotSupportedByApplication)
|
||||
|
||||
constructor(
|
||||
bank: BankAccess, account: BankAccount?,
|
||||
recipientName: String, recipientAccountIdentifier: String, recipientBankIdentifier: String? = null,
|
||||
amount: Amount, currency: String = DefaultValues.DefaultCurrency, paymentReference: String? = null, instantTransfer: Boolean = false,
|
||||
preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
|
||||
) : this(bank.domesticBankCode, bank.loginName, bank.password!!, account?.let { BankAccountIdentifier(it.identifier, it.subAccountNumber, it.iban) },
|
||||
recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer,
|
||||
listOf(bank.selectedTanMethod.type) + (preferredTanMethods ?: emptyList()), TanMethodType.TanMethodsNotSupportedByMostApplications,
|
||||
bank.clientData, bank.serializedClientData
|
||||
) {
|
||||
this.bank = bank
|
||||
this.account = account
|
||||
}
|
||||
|
||||
open var bank: BankAccess? = null
|
||||
protected set
|
||||
|
||||
open var account: BankAccount? = null
|
||||
protected set
|
||||
|
||||
override fun toString() = "$bankCode $loginName ${super.toString()}"
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.BankAccess
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class GetAccountDataResponse(
|
||||
val bank: BankAccess
|
||||
val customer: CustomerAccount
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
val bookedTransactions: List<AccountTransaction>
|
||||
get() = bank.accounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate }
|
||||
get() = customer.accounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate }
|
||||
|
||||
|
||||
override fun toString() = bank.toString()
|
||||
override fun toString() = customer.toString()
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.PrebookedAccountTransaction
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||
|
||||
@NoArgConstructor
|
||||
open class GetTransactionsResponse(
|
||||
val account: BankAccount,
|
||||
val balance: Amount? = null,
|
||||
val bookedTransactions: List<AccountTransaction>,
|
||||
val prebookedTransactions: List<PrebookedAccountTransaction>,
|
||||
val holdings: List<Holding> = emptyList(),
|
||||
val transactionsRetrievalTime: Instant,
|
||||
val retrievedTransactionsFrom: LocalDate? = null,
|
||||
val retrievedTransactionsTo: LocalDate? = null
|
||||
) {
|
||||
override fun toString() = "${account.productName} $balance, ${bookedTransactions.size} booked transactions from $retrievedTransactionsFrom"
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
// TODO: may differentiate between ClientResponse, which is either Success or Error, and RestResponse, which can be Success, Error and TanRequired
|
||||
|
@ -9,22 +8,21 @@ open class Response<T> protected constructor(
|
|||
val type: ResponseType,
|
||||
val data: T? = null,
|
||||
val error: Error? = null,
|
||||
val tanRequired: TanRequired? = null,
|
||||
val messageLog: List<MessageLogEntry> = emptyList()
|
||||
val tanRequired: TanRequired? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun <T> success(data: T, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
|
||||
Response(ResponseType.Success, data, messageLog = messageLog)
|
||||
fun <T> success(data: T): Response<T> =
|
||||
Response(ResponseType.Success, data)
|
||||
|
||||
fun <T> error(errorType: ErrorType, internalError: String? = null, errorMessagesFromBank: List<String> = emptyList(), messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
|
||||
Response(ResponseType.Error, null, Error(errorType, internalError, errorMessagesFromBank), messageLog = messageLog)
|
||||
fun <T> error(errorType: ErrorType, internalError: String? = null, errorMessagesFromBank: List<String> = emptyList()): Response<T> =
|
||||
Response(ResponseType.Error, null, Error(errorType, internalError, errorMessagesFromBank))
|
||||
|
||||
fun <T> tanRequired(tanRequired: TanRequired, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
|
||||
Response(ResponseType.TanRequired, null, null, tanRequired, messageLog)
|
||||
fun <T> tanRequired(tanRequired: TanRequired): Response<T> =
|
||||
Response(ResponseType.TanRequired, null, null, tanRequired)
|
||||
|
||||
fun <T> bankReturnedError(errorMessagesFromBank: List<String>, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
|
||||
Response.error(ErrorType.BankReturnedError, null, errorMessagesFromBank, messageLog)
|
||||
fun <T> bankReturnedError(errorMessagesFromBank: List<String>): Response<T> =
|
||||
Response.error(ErrorType.BankReturnedError, null, errorMessagesFromBank)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
/**
|
||||
* Transfer money process does not return any data, only if successful or not (and in latter case an error message).
|
||||
*/
|
||||
@NoArgConstructor
|
||||
open class TransferMoneyResponse
|
|
@ -1,53 +0,0 @@
|
|||
package net.codinux.banking.client.model.securitiesaccount
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class Holding(
|
||||
open var name: String,
|
||||
|
||||
open var isin: String? = null,
|
||||
open var wkn: String? = null,
|
||||
|
||||
open var quantity: Double? = null,
|
||||
open var currency: String? = null,
|
||||
|
||||
/**
|
||||
* Gesamter Kurswert aller Einheiten des Wertpapiers
|
||||
*/
|
||||
open var totalBalance: Amount? = null,
|
||||
/**
|
||||
* Aktueller Kurswert einer einzelnen Einheit des Wertpapiers
|
||||
*/
|
||||
open var marketValue: Amount? = null,
|
||||
|
||||
/**
|
||||
* Änderung in Prozent Aktueller Kurswert gegenüber Einstandspreis.
|
||||
*/
|
||||
open var performancePercentage: Float? = null,
|
||||
|
||||
/**
|
||||
* Gesamter Einstandspreis (Kaufpreis)
|
||||
*/
|
||||
open var totalCostPrice: Amount? = null,
|
||||
/**
|
||||
* (Durchschnittlicher) Einstandspreis/-kurs einer Einheit des Wertpapiers
|
||||
*/
|
||||
open var averageCostPrice: Amount? = null,
|
||||
|
||||
/**
|
||||
* Zeitpunkt zu dem der Kurswert bestimmt wurde
|
||||
*/
|
||||
open var pricingTime: Instant? = null,
|
||||
|
||||
open var buyingDate: LocalDate? = null,
|
||||
|
||||
var userSetDisplayName: String? = null,
|
||||
) {
|
||||
open val identifier: String by lazy { "${isin}_$wkn" }
|
||||
|
||||
override fun toString() = "$name $totalBalance $currency"
|
||||
}
|
|
@ -5,7 +5,7 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
@NoArgConstructor
|
||||
open class EnterTanResult(
|
||||
val enteredTan: String?,
|
||||
val changeTanMethodTo: TanMethod? = null,
|
||||
// val changeTanMethodTo: TanMethod? = null,
|
||||
// val changeTanMediumTo: TanMedium? = null,
|
||||
// val changeTanMediumResultCallback: ((BankingClientResponse) -> Unit)? = null
|
||||
)
|
|
@ -7,13 +7,13 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
@NoArgConstructor
|
||||
open class FlickerCode(
|
||||
val challengeHHD_UC: String,
|
||||
val parsedDataSet: String? = null,
|
||||
val parsedDataSet: String,
|
||||
val decodingError: String? = null
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
val decodingSuccessful: Boolean
|
||||
get() = parsedDataSet != null
|
||||
get() = decodingError == null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import net.codinux.banking.client.model.BankAccountIdentifier
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class MobilePhoneTanMedium(
|
||||
val phoneNumber: String?,
|
||||
val concealedPhoneNumber: String? = null
|
||||
val phoneNumber: String?
|
||||
) {
|
||||
override fun toString() = phoneNumber ?: "No phone number"
|
||||
}
|
|
@ -1,94 +1,27 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.client.model.BankAccountViewInfo
|
||||
import net.codinux.banking.client.model.BankAccess
|
||||
import net.codinux.banking.client.model.BankViewInfo
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.CustomerAccountViewInfo
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class TanChallenge(
|
||||
open val type: TanChallengeType,
|
||||
open val forAction: ActionRequiringTan,
|
||||
open val messageToShowToUser: String,
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMethod.
|
||||
*
|
||||
* As [availableTanMethods] also contains selected TanMethod, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMethod] to get selected TanMethod or iterate over [availableTanMethods] and filter selected one by this id.
|
||||
*/
|
||||
open val selectedTanMethodIdentifier: String,
|
||||
/**
|
||||
* When adding an account, frontend has no UserAccount object in BankingClientCallback to know which TanMethods are
|
||||
* available for user.
|
||||
* Also on other calls to bank server, bank server may returned an updated list of available TanMethods, so that
|
||||
* [BankAccess] may contains an outdated list of available TanMethods.
|
||||
*
|
||||
* Therefore i added list with up to date TanMethods here to ensure EnterTanDialog can display user's up to date TanMethods.
|
||||
*/
|
||||
open val availableTanMethods: List<TanMethod>,
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMedium.
|
||||
*
|
||||
* As [availableTanMedia] also contains selected TanMedium, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMedium] to get selected TanMedium or iterate over [availableTanMedia] and filter selected one by this medium name.
|
||||
*/
|
||||
open val selectedTanMediumIdentifier: String? = null,
|
||||
open val availableTanMedia: List<TanMedium> = emptyList(),
|
||||
|
||||
open val tanImage: TanImage? = null,
|
||||
open val flickerCode: FlickerCode? = null,
|
||||
open val bank: BankViewInfo,
|
||||
open val account: BankAccountViewInfo? = null,
|
||||
/**
|
||||
* Datum und Uhrzeit, bis zu welchem Zeitpunkt eine TAN auf Basis der gesendeten Challenge gültig ist. Nach Ablauf der Gültigkeitsdauer wird die entsprechende TAN entwertet.
|
||||
*
|
||||
* In server's time zone, that is Europe/Berlin.
|
||||
*/
|
||||
val tanExpirationTime: Instant? = null,
|
||||
val challengeCreationTimestamp: Instant = Clock.System.now()
|
||||
val type: TanChallengeType,
|
||||
val forAction: ActionRequiringTan,
|
||||
val messageToShowToUser: String,
|
||||
val tanMethod: TanMethod,
|
||||
val tanImage: TanImage? = null,
|
||||
val flickerCode: FlickerCode? = null,
|
||||
val customer: CustomerAccountViewInfo,
|
||||
val account: BankAccountViewInfo? = null
|
||||
// TODO: add availableTanMethods, selectedTanMedium, availableTanMedia
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
open val selectedTanMethod: TanMethod
|
||||
get() = availableTanMethods.first { it.identifier == selectedTanMethodIdentifier }
|
||||
|
||||
@get:JsonIgnore
|
||||
open val selectedTanMedium: TanMedium?
|
||||
get() = availableTanMedia.firstOrNull { it.mediumName == selectedTanMediumIdentifier }
|
||||
|
||||
|
||||
/**
|
||||
* Not implemented for all client, only implementing client for now: FinTs4jBankingClient.
|
||||
*
|
||||
* If a TAN expires either when [TanChallenge.tanExpirationTime] or a default timeout (15 min) is exceeded,
|
||||
* you can add a callback to get notified when TAN expired e.g. to close a EnterTanDialog.
|
||||
*/
|
||||
open fun addTanExpiredCallback(callback: () -> Unit) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Principally a no-op method, not implemented for all client, only implementing client for now: FinTs4jBankingClient.
|
||||
*
|
||||
* If a TAN is requested for a decoupled TAN method like [TanMethodType.DecoupledTan] or [TanMethodType.DecoupledPushTan],
|
||||
* you can add a callback to get notified when user approved TAN in her app e.g. to close a EnterTanDialog.
|
||||
*/
|
||||
open fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$selectedTanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||
return "$tanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||
TanChallengeType.EnterTan -> ""
|
||||
TanChallengeType.Image -> ", Image: $tanImage"
|
||||
TanChallengeType.FlickerCode -> ", FlickerCode: $flickerCode"
|
||||
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.codinux.banking.client.model.tan
|
|||
enum class TanChallengeType {
|
||||
Image,
|
||||
|
||||
FlickerCode,
|
||||
Flickercode,
|
||||
|
||||
EnterTan
|
||||
}
|
|
@ -1,19 +1,10 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
/**
|
||||
* 'TanGenerator' is in most cases a debit card, but can also be something like "BestSign" app (Postbank).
|
||||
* In latter case [cardNumber] can also be, contrary to specification, be an empty string.
|
||||
*/
|
||||
open class TanGeneratorTanMedium(
|
||||
val cardNumber: String,
|
||||
val cardSequenceNumber: String? = null,
|
||||
val cardType: Int? = null,
|
||||
val validFrom: LocalDate? = null,
|
||||
val validTo: LocalDate? = null
|
||||
val cardNumber: String
|
||||
) {
|
||||
override fun toString() = cardNumber
|
||||
}
|
|
@ -6,22 +6,22 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class TanImage(
|
||||
val mimeType: String? = null,
|
||||
val imageBytesBase64: String? = null,
|
||||
val mimeType: String,
|
||||
val imageBytesBase64: String,
|
||||
val decodingError: String? = null
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
val decodingSuccessful: Boolean
|
||||
get() = mimeType != null && imageBytesBase64 != null
|
||||
get() = decodingError == null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
mimeType?.let {
|
||||
return mimeType
|
||||
if (decodingSuccessful == false) {
|
||||
return "Decoding error: $decodingError"
|
||||
}
|
||||
|
||||
return "Decoding error: $decodingError"
|
||||
return mimeType
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
@NoArgConstructor
|
||||
open class TanMedium(
|
||||
val type: TanMediumType,
|
||||
val mediumName: String?,
|
||||
val displayName: String,
|
||||
val status: TanMediumStatus,
|
||||
/**
|
||||
* Only set if [type] is [TanMediumType.TanGenerator].
|
||||
|
@ -14,54 +14,7 @@ open class TanMedium(
|
|||
/**
|
||||
* Only set if [type] is [TanMediumType.MobilePhone].
|
||||
*/
|
||||
val mobilePhone: MobilePhoneTanMedium? = null,
|
||||
|
||||
var userSetDisplayName: String? = null
|
||||
val mobilePhone: MobilePhoneTanMedium? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Using only [mediumName] as identifier does not work as e.g. Sparkasse names all their TanGenerator TAN media
|
||||
* "SparkassenCard (Debitkarte)" - so it's not possible to differentiate between them solely by medium name.
|
||||
*/
|
||||
val identifier: String by lazy {
|
||||
// TODO: translate
|
||||
var id = mediumName ?: when (type) {
|
||||
TanMediumType.MobilePhone -> "Mobiltelefon"
|
||||
TanMediumType.TanGenerator -> "Tan Generator"
|
||||
TanMediumType.Generic -> "Unbenanntes TAN Medium"
|
||||
}
|
||||
|
||||
if (mobilePhone != null) {
|
||||
id += " " + (mobilePhone.concealedPhoneNumber ?: mobilePhone.phoneNumber)
|
||||
}
|
||||
if (tanGenerator != null) {
|
||||
if (tanGenerator.cardNumber.isNotBlank()) {
|
||||
id += " " + tanGenerator.cardNumber
|
||||
}
|
||||
if (tanGenerator.cardSequenceNumber.isNullOrBlank() == false) {
|
||||
id += " " + tanGenerator.cardSequenceNumber
|
||||
}
|
||||
|
||||
if (tanGenerator.validFrom != null && tanGenerator.validTo != null) {
|
||||
id += ", gültig von " + tanGenerator.validFrom.let { "${it.dayOfMonth}.${it.monthNumber}${it.year}" } +
|
||||
" - " + tanGenerator.validTo.let { "${it.dayOfMonth}.${it.monthNumber}${it.year}" }
|
||||
} else if (tanGenerator.validTo != null) {
|
||||
id += ", gültig bis " + tanGenerator.validTo.let { "${it.dayOfMonth}.${it.monthNumber}${it.year}" }
|
||||
}
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
val displayName: String by lazy {
|
||||
userSetDisplayName
|
||||
?: (identifier + " " + when (status) {
|
||||
TanMediumStatus.Used -> "Aktiv"
|
||||
TanMediumStatus.Available -> "Verfügbar"
|
||||
TanMediumStatus.ActiveFollowUpCard -> " Folgekarte, aktiv bei erster Nutzung"
|
||||
TanMediumStatus.AvailableFollowUpCard -> " Folgekarte, die erst aktiviert werden muss"
|
||||
})
|
||||
}
|
||||
|
||||
override fun toString() = "$mediumName $status"
|
||||
override fun toString() = "$displayName $status"
|
||||
}
|
|
@ -1,25 +1,7 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
enum class TanMediumStatus {
|
||||
|
||||
/**
|
||||
* Die Bank zeigt an, dass es eine TAN-Verifikation gegen dieses Medium vornimmt.
|
||||
*/
|
||||
Used,
|
||||
|
||||
/**
|
||||
* Das Medium kann genutzt werden, muss aber zuvor mit „TAN-Generator an- bzw. ummelden (HKTAU)“ aktiv gemeldet werden.
|
||||
*/
|
||||
Available,
|
||||
|
||||
/**
|
||||
* Mit der ersten Nutzung der Folgekarte wird die zur Zeit aktive Karte gesperrt.
|
||||
*/
|
||||
ActiveFollowUpCard,
|
||||
|
||||
/**
|
||||
* Das Medium kann mit dem Geschäftsvorfall „TAN-Medium an- bzw. ummelden (HKTAU)“ aktiv gemeldet werden.
|
||||
* Die aktuelle Karte kann dann nicht mehr genutzt werden.
|
||||
*/
|
||||
AvailableFollowUpCard
|
||||
Available
|
||||
}
|
|
@ -1,22 +1,14 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class TanMethod(
|
||||
open val displayName: String,
|
||||
open val type: TanMethodType,
|
||||
open val identifier: String,
|
||||
open val maxTanInputLength: Int? = null,
|
||||
open val allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric,
|
||||
open var userSetDisplayName: String? = null
|
||||
val displayName: String,
|
||||
val type: TanMethodType,
|
||||
val identifier: String,
|
||||
val maxTanInputLength: Int? = null,
|
||||
val allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
open val isNumericTan: Boolean
|
||||
get() = allowedTanFormat == AllowedTanFormat.Numeric
|
||||
|
||||
override fun toString() = "$displayName ($type, ${identifier})"
|
||||
}
|
|
@ -3,9 +3,9 @@ package net.codinux.banking.client.model.tan
|
|||
enum class TanMethodType {
|
||||
EnterTan,
|
||||
|
||||
ChipTanManual,
|
||||
ChipTanManuell,
|
||||
|
||||
ChipTanFlickerCode,
|
||||
ChipTanFlickercode,
|
||||
|
||||
ChipTanUsb,
|
||||
|
||||
|
@ -17,52 +17,7 @@ enum class TanMethodType {
|
|||
|
||||
AppTan,
|
||||
|
||||
DecoupledTan,
|
||||
|
||||
DecoupledPushTan,
|
||||
|
||||
photoTan,
|
||||
|
||||
QrCode
|
||||
;
|
||||
|
||||
|
||||
val isDecoupledMethod: Boolean
|
||||
get() = this == DecoupledTan || this == DecoupledPushTan
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
val NonVisual = listOf(TanMethodType.DecoupledTan, TanMethodType.DecoupledPushTan, TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.ChipTanManual, TanMethodType.EnterTan)
|
||||
|
||||
val NonVisualWithoutChipTanManual = NonVisual.toMutableList().apply { remove(TanMethodType.ChipTanManual) }.toList()
|
||||
|
||||
val ImageBased = listOf(
|
||||
TanMethodType.QrCode, TanMethodType.photoTan, // non ChipTan
|
||||
TanMethodType.ChipTanQrCode, TanMethodType.ChipTanPhotoTanMatrixCode // ChipTan; QrCode (e.g. used by Sparkassen) is faster than MatrixCode (e.g. used by Volksbanken)
|
||||
)
|
||||
|
||||
val NonVisualOrImageBased = buildList {
|
||||
addAll(NonVisualWithoutChipTanManual)
|
||||
addAll(ImageBased)
|
||||
addAll(listOf(TanMethodType.ChipTanManual)) // this is quite inconvenient for user, so i added it as last
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as [NonVisualOrImageBased] but including [ChipTanFlickerCode] - for applications supporting it - as
|
||||
* FlickerCode is still the most used ChipTan procedure.
|
||||
*/
|
||||
val NonVisualOrImageBasedOrFlickerCode = NonVisualOrImageBased.toMutableList().apply {
|
||||
val index = this.indexOf(ChipTanQrCode)
|
||||
this.add(index, ChipTanFlickerCode)
|
||||
}.toList()
|
||||
|
||||
|
||||
val TanMethodsPreferredByMostApplications = NonVisualOrImageBased
|
||||
|
||||
|
||||
val TanMethodsNotSupportedByMostApplications = listOf(TanMethodType.ChipTanUsb)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AmountTest {
|
||||
|
||||
@Test
|
||||
fun add() {
|
||||
val result = Amount("0.1") + Amount("0.2")
|
||||
|
||||
assertEquals(Amount("0.3"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minus() {
|
||||
val result = Amount("0.1") - Amount("0.2")
|
||||
|
||||
assertEquals(Amount("-0.1"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiply() {
|
||||
val result = Amount("0.1") * Amount("0.2")
|
||||
|
||||
assertEquals(Amount("0.02"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun divide() {
|
||||
val result = Amount("1") / Amount("3")
|
||||
|
||||
assertEquals(Amount("0.33333333333333333333"), result)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@JsModule("big.js")
|
||||
@JsNonModule
|
||||
open external class Big(value: String) {
|
||||
fun plus(other: Big): Big
|
||||
fun minus(other: Big): Big
|
||||
fun times(other: Big): Big
|
||||
fun div(other: Big): Big
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String): Big(amount) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero = Amount("0.00")
|
||||
}
|
||||
|
||||
|
||||
actual operator fun plus(other: Amount): Amount {
|
||||
return Amount(super.plus(other).toString())
|
||||
}
|
||||
|
||||
actual operator fun minus(other: Amount): Amount {
|
||||
return Amount(super.minus(other).toString())
|
||||
}
|
||||
|
||||
actual operator fun times(other: Amount): Amount {
|
||||
return Amount(super.times(other).toString())
|
||||
}
|
||||
|
||||
actual operator fun div(other: Amount): Amount {
|
||||
return Amount(super.div(other).toString())
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Amount && this.toString() == other.toString()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
|
||||
actual override fun toString(): String = super.toString()
|
||||
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -1,40 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String) : BigDecimal(amount) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero = Amount("0.00")
|
||||
}
|
||||
|
||||
|
||||
actual operator fun plus(other: Amount): Amount =
|
||||
Amount(this.add(other).toString())
|
||||
|
||||
actual operator fun minus(other: Amount): Amount =
|
||||
Amount(this.subtract(other).toString())
|
||||
|
||||
actual operator fun times(other: Amount): Amount =
|
||||
Amount(this.multiply(other).toString())
|
||||
|
||||
actual operator fun div(other: Amount): Amount =
|
||||
// without RoundingMode a java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. will be thrown
|
||||
Amount(this.divide(other, DecimalPrecision, RoundingMode.HALF_UP).toString()) // 20 to match Big.js's behaviour
|
||||
|
||||
|
||||
/* why are these methods required when deriving from BigDecimal? */
|
||||
|
||||
override fun toByte(): Byte {
|
||||
1 + 1
|
||||
return 0 // will never be called; where is this method coming from?
|
||||
}
|
||||
|
||||
override fun toShort(): Short {
|
||||
return 0 // will never be called; where is this method coming from?
|
||||
}
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||
import com.ionspin.kotlin.bignum.decimal.DecimalMode
|
||||
import com.ionspin.kotlin.bignum.decimal.RoundingMode
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero: Amount = Amount("0.00")
|
||||
|
||||
private val decimalMode = DecimalMode(DecimalPrecision.toLong(), RoundingMode.ROUND_HALF_CEILING)
|
||||
}
|
||||
|
||||
|
||||
internal val amount: BigDecimal = BigDecimal.parseString(amount)
|
||||
|
||||
actual operator fun plus(other: Amount): Amount {
|
||||
return Amount(amount.add(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun minus(other: Amount): Amount {
|
||||
return Amount(amount.subtract(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun times(other: Amount): Amount {
|
||||
return Amount(amount.multiply(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun div(other: Amount): Amount {
|
||||
return Amount(amount.divide(other.amount, decimalMode).toString())
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Amount && this.amount == other.amount
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return amount.hashCode()
|
||||
}
|
||||
|
||||
actual override fun toString(): String = amount.toPlainString()
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||
import com.ionspin.kotlin.bignum.decimal.DecimalMode
|
||||
import com.ionspin.kotlin.bignum.decimal.RoundingMode
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero: Amount = Amount("0.00")
|
||||
|
||||
private val decimalMode = DecimalMode(DecimalPrecision.toLong(), RoundingMode.ROUND_HALF_CEILING)
|
||||
}
|
||||
|
||||
|
||||
internal val amount: BigDecimal = BigDecimal.parseString(amount)
|
||||
|
||||
actual operator fun plus(other: Amount): Amount {
|
||||
return Amount(amount.add(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun minus(other: Amount): Amount {
|
||||
return Amount(amount.subtract(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun times(other: Amount): Amount {
|
||||
return Amount(amount.multiply(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun div(other: Amount): Amount {
|
||||
return Amount(amount.divide(other.amount, decimalMode).toString())
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Amount && this.amount == other.amount
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return amount.hashCode()
|
||||
}
|
||||
|
||||
actual override fun toString(): String = amount.toPlainString()
|
||||
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -1,53 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@JsModule("big.js")
|
||||
open external class Big(value: String) {
|
||||
fun plus(other: Big): Big
|
||||
fun minus(other: Big): Big
|
||||
fun times(other: Big): Big
|
||||
fun div(other: Big): Big
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
|
||||
@NoArgConstructor
|
||||
actual class Amount actual constructor(amount: String) {
|
||||
|
||||
actual companion object {
|
||||
actual val Zero = Amount("0.00")
|
||||
}
|
||||
|
||||
|
||||
internal val amount = Big(amount)
|
||||
|
||||
|
||||
actual operator fun plus(other: Amount): Amount {
|
||||
return Amount(amount.plus(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun minus(other: Amount): Amount {
|
||||
return Amount(amount.minus(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun times(other: Amount): Amount {
|
||||
return Amount(amount.times(other.amount).toString())
|
||||
}
|
||||
|
||||
actual operator fun div(other: Amount): Amount {
|
||||
return Amount(amount.div(other.amount).toString())
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Amount && amount.toString() == other.amount.toString()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
|
||||
actual override fun toString(): String = amount.toString()
|
||||
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -4,11 +4,18 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
|
|||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(11)
|
||||
jvmToolchain(8)
|
||||
|
||||
jvm {
|
||||
withJava()
|
||||
|
@ -32,7 +39,7 @@ kotlin {
|
|||
browser {
|
||||
testTask {
|
||||
useKarma {
|
||||
// useChromeHeadless()
|
||||
useChromeHeadless()
|
||||
useFirefoxHeadless()
|
||||
}
|
||||
}
|
||||
|
@ -47,13 +54,12 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
// wasmJs() // ktor is not available for wasmJs yet
|
||||
// wasmJs()
|
||||
|
||||
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
@ -75,7 +81,7 @@ kotlin {
|
|||
dependencies {
|
||||
api(project(":BankingClient"))
|
||||
|
||||
implementation("net.codinux.banking:fints4k:1.0.0-Alpha-15")
|
||||
api("net.codinux.banking:fints4k:1.0.0-Alpha-11")
|
||||
|
||||
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
||||
}
|
||||
|
@ -113,6 +119,21 @@ kotlin {
|
|||
|
||||
|
||||
|
||||
ext["customArtifactId"] = "fints4k-banking-client"
|
||||
//ext["customArtifactId"] = "fints4k-banking-client"
|
||||
//
|
||||
//apply(from = "../gradle/scripts/publish-codinux.gradle.kts")
|
||||
|
||||
apply(from = "../gradle/scripts/publish-codinux-repo.gradle.kts")
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "codinux"
|
||||
url = uri("https://maven.dankito.net/api/packages/codinux/maven")
|
||||
|
||||
credentials(PasswordCredentials::class.java) {
|
||||
username = project.property("codinuxRegistryWriterUsername") as String
|
||||
password = project.property("codinuxRegistryWriterPassword") as String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.model.MessageLogEntryType
|
||||
import net.codinux.banking.fints.callback.FinTsClientCallback
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||
import net.codinux.banking.fints.model.BankData
|
||||
import net.codinux.banking.fints.model.EnterTanGeneratorAtcResult
|
||||
import net.codinux.banking.fints.model.MessageLogEntry
|
||||
import net.codinux.banking.fints.model.TanMethod
|
||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||
import net.dankito.banking.fints.model.BankData
|
||||
import net.dankito.banking.fints.model.EnterTanGeneratorAtcResult
|
||||
import net.dankito.banking.fints.model.TanMethod
|
||||
|
||||
open class BridgeFintTsToBankingClientCallback(
|
||||
protected val bankingClientCallback: BankingClientCallback,
|
||||
|
@ -18,26 +16,17 @@ open class BridgeFintTsToBankingClientCallback(
|
|||
return suggestedTanMethod
|
||||
}
|
||||
|
||||
override suspend fun enterTan(tanChallenge: net.codinux.banking.fints.model.TanChallenge) {
|
||||
override suspend fun enterTan(tanChallenge: net.dankito.banking.fints.model.TanChallenge) {
|
||||
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
|
||||
if (enterTanResult.enteredTan != null) {
|
||||
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
|
||||
} else if (enterTanResult.changeTanMethodTo != null) {
|
||||
val fintsTanMethod = tanChallenge.bank.tanMethodsAvailableForUser.first { it.securityFunction.code == enterTanResult.changeTanMethodTo!!.identifier }
|
||||
tanChallenge.userAsksToChangeTanMethod(fintsTanMethod)
|
||||
} else {
|
||||
tanChallenge.userDidNotEnterTan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanMedium): EnterTanGeneratorAtcResult {
|
||||
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
|
||||
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
|
||||
}
|
||||
|
||||
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
|
||||
val mapped = mapper.mapMessageLogEntry(messageLogEntry)
|
||||
|
||||
bankingClientCallback.messageLogAdded(mapped)
|
||||
}
|
||||
}
|
|
@ -2,66 +2,25 @@ package net.codinux.banking.client.fints4k
|
|||
|
||||
import net.codinux.banking.client.BankingClient
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.BankAccountFeatures
|
||||
import net.codinux.banking.client.model.BankAccess
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||
import net.codinux.banking.client.model.response.*
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
import net.codinux.banking.fints.FinTsClient
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
|
||||
open class FinTs4kBankingClient(
|
||||
config: FinTsClientConfiguration = FinTsClientConfiguration(),
|
||||
callback: BankingClientCallback
|
||||
) : BankingClient {
|
||||
|
||||
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
|
||||
private val mapper = FinTs4kMapper()
|
||||
|
||||
constructor(options: FinTsClientOptions, callback: BankingClientCallback)
|
||||
: this(FinTsClientConfiguration(net.codinux.banking.fints.config.FinTsClientOptions(options.collectMessageLog, false, options.removeSensitiveDataFromMessageLog, options.appendFinTsMessagesToLog, options.closeDialogs, options.version, options.productName)), callback)
|
||||
|
||||
|
||||
protected open val mapper = FinTs4kMapper()
|
||||
|
||||
protected open val client = FinTsClient(config, BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
private val client = FinTsClient(BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
|
||||
|
||||
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {
|
||||
val response = client.getAccountDataAsync(mapper.mapToGetAccountDataParameter(request, request.options ?: GetAccountDataOptions()))
|
||||
|
||||
return mapper.map(response, request.bankInfo)
|
||||
}
|
||||
|
||||
override suspend fun updateAccountTransactionsAsync(bank: BankAccess, accounts: List<BankAccount>?, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>?): Response<List<GetTransactionsResponse>> {
|
||||
val accountsToRequest = (accounts ?: bank.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveTransactions) }
|
||||
|
||||
if (accountsToRequest.isNotEmpty()) {
|
||||
val responses = accountsToRequest.map { account ->
|
||||
val preferredTanMethods = listOf(bank.selectedTanMethod.type) + (preferredTanMethodsIfSelectedTanMethodIsNotAvailable ?: emptyList())
|
||||
|
||||
val parameter = mapper.mapToUpdateAccountTransactionsParameter(bank, account, preferredTanMethods)
|
||||
|
||||
val response = client.getAccountDataAsync(parameter)
|
||||
|
||||
mapper.mapCommonResponseData(bank, response) // so that basic account data doesn't have to be retrieved another time if user has multiple accounts
|
||||
|
||||
Triple(account, parameter, response)
|
||||
}
|
||||
|
||||
return mapper.map(bank, responses)
|
||||
}
|
||||
|
||||
return Response.error(ErrorType.NoneOfTheAccountsSupportsRetrievingData, "Keines der Konten unterstützt das Abholen der Umsätze oder des Kontostands") // TODO: translate
|
||||
}
|
||||
|
||||
|
||||
override suspend fun transferMoneyAsync(request: TransferMoneyRequestForUser): Response<TransferMoneyResponse> {
|
||||
val response = client.transferMoneyAsync(mapper.mapToTransferMoneyParameter(request))
|
||||
|
||||
return mapper.mapTransferMoneyResponse(response, request.bank, request.account)
|
||||
return mapper.map(response)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.BankingClientForCustomerBase
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
|
||||
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, callback: BankingClientCallback)
|
||||
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(callback)) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||
: this(AccountCredentials(bankCode, loginName, password), callback)
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.BankingClientForUserBase
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
|
||||
open class FinTs4kBankingClientForUser(credentials: AccountCredentials, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
|
||||
: BankingClientForUserBase(credentials, FinTs4kBankingClient(config, callback)) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||
: this(bankCode, loginName, password, FinTsClientConfiguration(), callback)
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
|
||||
: this(AccountCredentials(bankCode, loginName, password), config, callback)
|
||||
|
||||
}
|
|
@ -1,22 +1,12 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import net.codinux.banking.client.model.*
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.MessageLogEntryType
|
||||
import net.codinux.banking.client.model.extensions.EuropeBerlin
|
||||
import net.codinux.banking.client.model.tan.*
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||
import net.codinux.banking.client.model.response.*
|
||||
import net.codinux.banking.client.model.tan.ActionRequiringTan
|
||||
import net.codinux.banking.client.model.tan.AllowedTanFormat
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
import net.codinux.banking.client.model.tan.TanImage
|
||||
import net.codinux.banking.client.model.tan.TanMethod
|
||||
|
@ -25,172 +15,66 @@ import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
|||
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.FinTsClientResponse
|
||||
import net.codinux.banking.fints.mapper.FinTsModelMapper
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.*
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.MobilePhoneTanMedium
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMediumStatus
|
||||
import net.codinux.banking.fints.model.*
|
||||
import net.codinux.banking.fints.transactions.swift.model.Holding
|
||||
import net.dankito.banking.banklistcreator.prettifier.BankingGroupMapper
|
||||
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
|
||||
import net.dankito.banking.fints.mapper.FinTsModelMapper
|
||||
import net.dankito.banking.fints.model.*
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
open class FinTs4kMapper {
|
||||
|
||||
companion object {
|
||||
val TanMethodTypesToMigrate = mapOf(
|
||||
net.codinux.banking.fints.model.TanMethodType.ChipTanManuell.name to TanMethodType.ChipTanManual.name,
|
||||
net.codinux.banking.fints.model.TanMethodType.ChipTanFlickercode.name to TanMethodType.ChipTanFlickerCode.name
|
||||
)
|
||||
|
||||
val TanMethodTypesToMigrateReverse = TanMethodTypesToMigrate.map { it.value to it.key }.toMap()
|
||||
}
|
||||
private val fintsModelMapper = FinTsModelMapper()
|
||||
|
||||
|
||||
protected val fintsModelMapper = FinTsModelMapper()
|
||||
|
||||
protected val bankingGroupMapper = BankingGroupMapper()
|
||||
|
||||
|
||||
open fun mapToGetAccountDataParameter(request: GetAccountDataRequest, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
request.bankCode, request.loginName, request.password,
|
||||
options.accounts.map { mapBankAccountIdentifier(it) },
|
||||
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
credentials.bankCode, credentials.loginName, credentials.password,
|
||||
options.accounts.map { BankAccountIdentifierImpl(it.identifier, it.subAccountNumber, it.iban) },
|
||||
options.retrieveBalance,
|
||||
RetrieveTransactions.valueOf(options.retrieveTransactions.name), options.retrieveTransactionsFrom, options.retrieveTransactionsTo,
|
||||
preferredTanMethods = options.preferredTanMethods?.map { mapTanMethodType(it) },
|
||||
tanMethodsNotSupportedByApplication = options.tanMethodsNotSupportedByApplication.map { mapTanMethodType(it) },
|
||||
abortIfTanIsRequired = options.abortIfTanIsRequired,
|
||||
defaultBankValues = request.bankInfo?.let { mapToBankData(request, it) }
|
||||
abortIfTanIsRequired = options.abortIfTanIsRequired
|
||||
)
|
||||
|
||||
protected open fun mapToBankData(credentials: AccountCredentials, bank: BankInfo): BankData = BankData(
|
||||
credentials.bankCode, credentials.loginName, credentials.password,
|
||||
bank.serverAddress, bank.bic, bank.name
|
||||
)
|
||||
|
||||
open fun mapToUpdateAccountTransactionsParameter(bank: BankAccess, account: BankAccount, preferredTanMethods: List<TanMethodType>? = null): GetAccountDataParameter {
|
||||
val defaults = GetAccountDataOptions()
|
||||
|
||||
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
|
||||
val from = account.lastAccountUpdateTime?.toLocalDateTime(TimeZone.EuropeBerlin)?.date // TODO: in case lastTransactionsUpdateTime is not set, this would retrieve all transactions (and require a TAN im most cases)
|
||||
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.valueOf(defaults.retrieveTransactions.name)
|
||||
|
||||
return GetAccountDataParameter(bank.domesticBankCode, bank.loginName, bank.password!!, listOf(accountIdentifier), true,
|
||||
retrieveTransactions, from,
|
||||
preferredTanMethods = preferredTanMethods?.map { mapTanMethodType(it) },
|
||||
preferredTanMedium = bank.selectedTanMediumIdentifier,
|
||||
finTsModel = bank.clientData as? BankData,
|
||||
serializedFinTsModel = bank.serializedClientData
|
||||
)
|
||||
}
|
||||
|
||||
open fun mapBankAccountIdentifier(account: BankAccountIdentifier): BankAccountIdentifierImpl =
|
||||
BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
|
||||
|
||||
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
|
||||
net.codinux.banking.fints.model.TanMethodType.valueOf(TanMethodTypesToMigrateReverse[type.name] ?: type.name)
|
||||
|
||||
protected open fun mapAllowedTanFormat(allowedTanFormat: AllowedTanFormat?): net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat =
|
||||
allowedTanFormat?.let { net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.valueOf(it.name) } ?: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Alphanumeric
|
||||
|
||||
|
||||
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse, bankInfo: BankInfo? = null): Response<GetAccountDataResponse> =
|
||||
if (response.successful && response.customerAccount != null) {
|
||||
val bank = mapBank(response.customerAccount!!, bankInfo, response)
|
||||
Response.success(GetAccountDataResponse(bank), mapMessageLog(response, bank))
|
||||
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
||||
return if (response.successful && response.customerAccount != null) {
|
||||
Response.success(GetAccountDataResponse(mapCustomer(response.customerAccount!!)))
|
||||
} else {
|
||||
mapError(response, mapMessageLog(response))
|
||||
mapError(response)
|
||||
}
|
||||
|
||||
open fun map(bank: BankAccess, responses: List<Triple<BankAccount, GetAccountDataParameter, net.dankito.banking.client.model.response.GetAccountDataResponse>>): Response<List<GetTransactionsResponse>> {
|
||||
val type = if (responses.all { it.third.successful }) ResponseType.Success else ResponseType.Error
|
||||
|
||||
// TODO: update BankAccess and BankAccount objects according to retrieved data
|
||||
val mappedResponses = responses.map { (account, param, getAccountDataResponse) ->
|
||||
val fintsBank = getAccountDataResponse.customerAccount
|
||||
val finTsBankAccount = fintsBank?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
|
||||
|
||||
val messageLog = mapMessageLog(getAccountDataResponse, bank, account)
|
||||
|
||||
if (getAccountDataResponse.successful && fintsBank != null && finTsBankAccount != null) {
|
||||
if (finTsBankAccount.lastAccountUpdateTime != null) {
|
||||
account.lastAccountUpdateTime = finTsBankAccount.lastAccountUpdateTime
|
||||
}
|
||||
if (account.retrievedTransactionsFrom == null || (finTsBankAccount.retrievedTransactionsFrom != null
|
||||
&& finTsBankAccount.retrievedTransactionsFrom!! < account.retrievedTransactionsFrom!!)) {
|
||||
account.retrievedTransactionsFrom = finTsBankAccount.retrievedTransactionsFrom
|
||||
}
|
||||
|
||||
val balance = mapMoney(finTsBankAccount.balance)
|
||||
account.balance = balance
|
||||
|
||||
mapCommonResponseData(bank, getAccountDataResponse)
|
||||
|
||||
Response.success(GetTransactionsResponse(account, balance, mapBookedTransactions(finTsBankAccount), emptyList(),
|
||||
mapHoldings(finTsBankAccount.statementOfHoldings, finTsBankAccount.currency, finTsBankAccount.lastAccountUpdateTime),
|
||||
finTsBankAccount.lastAccountUpdateTime ?: Clock.System.now(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo),
|
||||
messageLog)
|
||||
} else {
|
||||
mapError(getAccountDataResponse, messageLog)
|
||||
}
|
||||
}
|
||||
|
||||
val data = mappedResponses.filter { it.type == ResponseType.Success }.mapNotNull { it.data }
|
||||
|
||||
return (object : Response<List<GetTransactionsResponse>>(type, data, mappedResponses.firstNotNullOfOrNull { it.error }, messageLog = mappedResponses.flatMap { it.messageLog }) { })
|
||||
}
|
||||
|
||||
|
||||
open fun mapToBankViewInfo(bank: BankData): BankViewInfo = BankViewInfo(
|
||||
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
|
||||
fun mapToCustomerAccountViewInfo(bank: BankData): CustomerAccountViewInfo = CustomerAccountViewInfo(
|
||||
bank.bankCode, bank.customerId, bank.bankName
|
||||
)
|
||||
|
||||
open fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
|
||||
fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
|
||||
account.accountIdentifier, account.subAccountAttribute,
|
||||
mapAccountType(fintsModelMapper.map(account.accountType)),
|
||||
account.iban, account.productName
|
||||
)
|
||||
|
||||
|
||||
protected open fun mapBank(bank: net.dankito.banking.client.model.CustomerAccount, info: BankInfo? = null, response: FinTsClientResponse? = null) = BankAccess(
|
||||
bank.bankCode, bank.loginName, bank.password,
|
||||
info?.name ?: bank.bankName, bank.bic, bank.customerName, bank.userId,
|
||||
bank.accounts.map { mapAccount(it) }.sortedBy { it.type }
|
||||
.onEachIndexed { index, bankAccount -> bankAccount.displayIndex = index },
|
||||
|
||||
bank.selectedTanMethod?.securityFunction?.code, bank.tanMethods.map { mapTanMethod(it) }.toMutableList(),
|
||||
bank.selectedTanMedium?.mediumName, bank.tanMedia.map { mapTanMedium(it) }.toMutableList(),
|
||||
|
||||
info?.bankingGroup ?: getBankingGroup(bank.bankName, bank.bic),
|
||||
bank.finTsServerAddress,
|
||||
"de"
|
||||
).apply {
|
||||
response?.let { mapCommonResponseData(this, it) }
|
||||
}
|
||||
|
||||
protected open fun getBankingGroup(bankName: String, bic: String): BankingGroup? =
|
||||
bankingGroupMapper.getBankingGroup(bankName, bic)
|
||||
|
||||
|
||||
protected open fun mapAccount(account: net.dankito.banking.client.model.BankAccount) = BankAccount(
|
||||
account.identifier, account.subAccountNumber, account.iban, account.productName, account.accountHolderName,
|
||||
mapAccountType(account.type), account.currency, account.accountLimit,
|
||||
account.isAccountTypeSupportedByApplication, mapFeatures(account),
|
||||
mapMoney(account.balance),
|
||||
account.serverTransactionsRetentionDays,
|
||||
account.lastAccountUpdateTime, account.retrievedTransactionsFrom,
|
||||
bookedTransactions = mapBookedTransactions(account).toMutableList(),
|
||||
holdings = mapHoldings(account.statementOfHoldings, account.currency, account.lastAccountUpdateTime).toMutableList()
|
||||
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): CustomerAccount = CustomerAccount(
|
||||
customer.bankCode, customer.loginName, customer.password,
|
||||
customer.bankName, customer.bic, customer.customerName, customer.userId,
|
||||
customer.accounts.map { mapAccount(it) }
|
||||
)
|
||||
|
||||
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||
|
||||
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
|
||||
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
|
||||
account.productName, account.currency, account.accountLimit, account.isAccountTypeSupportedByApplication,
|
||||
mapFeatures(account),
|
||||
mapAmount(account.balance), account.retrievedTransactionsFrom, account.retrievedTransactionsTo,
|
||||
// TODO: map haveAllTransactionsBeenRetrieved
|
||||
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
||||
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
||||
)
|
||||
|
||||
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||
BankAccountType.valueOf(type.name)
|
||||
|
||||
protected open fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
|
||||
private fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
|
||||
if (account.supportsRetrievingBalance) {
|
||||
add(BankAccountFeatures.RetrieveBalance)
|
||||
}
|
||||
|
@ -201,278 +85,80 @@ open class FinTs4kMapper {
|
|||
add(BankAccountFeatures.TransferMoney)
|
||||
}
|
||||
if (account.supportsInstantPayment) {
|
||||
add(BankAccountFeatures.InstantTransfer)
|
||||
add(BankAccountFeatures.InstantPayment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun mapBookedTransactions(account: net.dankito.banking.client.model.BankAccount): List<AccountTransaction> =
|
||||
account.bookedTransactions.map { mapTransaction(it) }
|
||||
|
||||
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||
mapMoney(transaction.amount), transaction.amount.currency.code, transaction.reference,
|
||||
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
||||
transaction.bookingDate, transaction.valueDate,
|
||||
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
||||
|
||||
transaction.postingText,
|
||||
mapNullableMoney(transaction.openingBalance), mapNullableMoney(transaction.closingBalance),
|
||||
|
||||
transaction.statementNumber, transaction.sheetNumber,
|
||||
|
||||
transaction.customerReference, transaction.bankReference,
|
||||
transaction.furtherInformation,
|
||||
|
||||
transaction.endToEndReference, transaction.mandateReference,
|
||||
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||
transaction.compensationAmount, transaction.originalAmount,
|
||||
transaction.deviantOriginator, transaction.deviantRecipient,
|
||||
transaction.referenceWithNoSpecialType,
|
||||
|
||||
transaction.journalNumber, transaction.textKeyAddition,
|
||||
|
||||
transaction.orderReferenceNumber, transaction.referenceNumber,
|
||||
transaction.isReversal
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
|
||||
transaction.bookingText, null,
|
||||
transaction.statementNumber, transaction.sequenceNumber,
|
||||
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
|
||||
// TODO: map other properties
|
||||
)
|
||||
|
||||
protected open fun mapHoldings(statements: List<net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings>, accountCurrency: String, lastAccountUpdateTime: Instant? = null) =
|
||||
statements.flatMap { mapHoldings(it, accountCurrency, lastAccountUpdateTime) }
|
||||
private fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
|
||||
|
||||
protected open fun mapHoldings(statement: net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings, accountCurrency: String, lastAccountUpdateTime: Instant? = null): List<net.codinux.banking.client.model.securitiesaccount.Holding> {
|
||||
|
||||
val totalBalance = mapNullableAmount(statement.totalBalance)
|
||||
val currency = statement.currency ?: accountCurrency
|
||||
val statementDate: Instant? = /* statement.statementDate ?: statement.preparationDate ?: */ lastAccountUpdateTime // TODO
|
||||
|
||||
return statement.holdings.map { mapHolding(it, currency, statementDate, if (statement.holdings.size == 1) totalBalance else null) }
|
||||
}
|
||||
|
||||
protected open fun mapHolding(holding: Holding, accountCurrency: String, statementDate: Instant?, totalBalance: Amount? = null) = net.codinux.banking.client.model.securitiesaccount.Holding(
|
||||
holding.name, holding.isin, holding.wkn,
|
||||
|
||||
holding.quantity, holding.currency ?: accountCurrency,
|
||||
|
||||
getTotalBalance(holding), mapNullableAmount(holding.marketValue),
|
||||
calculatePerformance(holding),
|
||||
getTotalCostPrice(holding), mapNullableAmount(holding.averageCostPrice),
|
||||
|
||||
holding.pricingTime ?: statementDate, holding.buyingDate
|
||||
)
|
||||
|
||||
// visible for testing
|
||||
internal fun getTotalBalance(holding: Holding): Amount? {
|
||||
return if (holding.totalBalance != null) {
|
||||
mapNullableAmount(holding.totalBalance)
|
||||
} else if (holding.quantity != null && holding.marketValue != null) {
|
||||
Amount((holding.quantity!! * holding.marketValue.toString().toDouble()).toString())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
internal fun getTotalCostPrice(holding: Holding): Amount? {
|
||||
return if (holding.totalCostPrice != null) {
|
||||
mapNullableAmount(holding.totalCostPrice)
|
||||
} else if (holding.quantity != null && holding.averageCostPrice != null) {
|
||||
Amount((holding.quantity!! * holding.averageCostPrice.toString().toDouble()).toString())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
internal fun calculatePerformance(holding: Holding): Float? {
|
||||
val totalBalance = getTotalBalance(holding)
|
||||
val totalCostPrice = getTotalCostPrice(holding)
|
||||
|
||||
if (totalBalance != null && totalCostPrice != null) {
|
||||
return ((totalBalance - totalCostPrice) / totalCostPrice).toFloat() * 100
|
||||
}
|
||||
|
||||
val marketValue = mapNullableAmount(holding.marketValue)
|
||||
val costPrice = mapNullableAmount(holding.averageCostPrice)
|
||||
if (marketValue != null && costPrice != null) {
|
||||
return ((marketValue - costPrice) / costPrice).toFloat() * 100
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
protected open fun mapNullableMoney(amount: Money?) = amount?.let { mapMoney(it) }
|
||||
|
||||
protected open fun mapMoney(amount: Money) = Amount(amount.amount.string.replace(',', '.'))
|
||||
|
||||
protected open fun mapNullableAmount(amount: net.codinux.banking.fints.model.Amount?) = amount?.let { mapAmount(it) }
|
||||
|
||||
protected open fun mapAmount(amount: net.codinux.banking.fints.model.Amount) = Amount(amount.string.replace(',', '.'))
|
||||
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
||||
|
||||
|
||||
open fun mapTanChallenge(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallenge {
|
||||
fun mapTanChallenge(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallenge {
|
||||
val type = mapTanChallengeType(challenge)
|
||||
val action = mapActionRequiringTan(challenge.forAction)
|
||||
|
||||
val tanMethods = challenge.bank.tanMethodsAvailableForUser.map { mapTanMethod(it) }
|
||||
val selectedTanMethodIdentifier = challenge.tanMethod.securityFunction.code
|
||||
|
||||
val tanMedia = challenge.bank.tanMedia.map { mapTanMedium(it) }
|
||||
// TanMedium has not natural id in FinTS model so we have to create our own one
|
||||
val selectedTanMediumIdentifier = challenge.bank.selectedTanMedium?.let { selected -> tanMedia.firstOrNull { it == selected } }?.identifier
|
||||
|
||||
val bank = mapToBankViewInfo(challenge.bank)
|
||||
val tanMethod = mapTanMethod(challenge.tanMethod)
|
||||
val customer = mapToCustomerAccountViewInfo(challenge.bank)
|
||||
val account = challenge.account?.let { mapToBankAccountViewInfo(it) }
|
||||
|
||||
val tanImage = if (challenge is ImageTanChallenge) mapTanImage(challenge.image) else null
|
||||
val flickerCode = if (challenge is FlickerCodeTanChallenge) mapFlickerCode(challenge.flickerCode) else null
|
||||
|
||||
return object : TanChallenge(type, action, challenge.messageToShowToUser, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, tanImage, flickerCode, bank, account, challenge.tanExpirationTime, challenge.challengeCreationTimestamp) {
|
||||
override fun addTanExpiredCallback(callback: () -> Unit) {
|
||||
challenge.addTanExpiredCallback(callback)
|
||||
}
|
||||
|
||||
override fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
|
||||
challenge.addUserApprovedDecoupledTanCallback(callback)
|
||||
}
|
||||
}
|
||||
return TanChallenge(type, action, challenge.messageToShowToUser, tanMethod, tanImage, flickerCode, customer, account)
|
||||
}
|
||||
|
||||
protected open fun mapTanChallengeType(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallengeType = when {
|
||||
private fun mapTanChallengeType(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallengeType = when {
|
||||
challenge is ImageTanChallenge -> TanChallengeType.Image
|
||||
challenge is FlickerCodeTanChallenge -> TanChallengeType.FlickerCode
|
||||
challenge is FlickerCodeTanChallenge -> TanChallengeType.Flickercode
|
||||
else -> TanChallengeType.EnterTan
|
||||
}
|
||||
|
||||
protected open fun mapActionRequiringTan(action: net.codinux.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||
private fun mapActionRequiringTan(action: net.dankito.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||
ActionRequiringTan.valueOf(action.name)
|
||||
|
||||
open fun mapTanMethod(method: net.codinux.banking.fints.model.TanMethod): TanMethod = TanMethod(
|
||||
private fun mapTanMethod(method: net.dankito.banking.fints.model.TanMethod): TanMethod = TanMethod(
|
||||
method.displayName, mapTanMethodType(method.type), method.securityFunction.code, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
|
||||
)
|
||||
|
||||
protected open fun mapTanMethodType(type: net.codinux.banking.fints.model.TanMethodType): TanMethodType =
|
||||
TanMethodType.valueOf(TanMethodTypesToMigrate[type.name] ?: type.name)
|
||||
private fun mapTanMethodType(type: net.dankito.banking.fints.model.TanMethodType): TanMethodType =
|
||||
TanMethodType.valueOf(type.name)
|
||||
|
||||
protected open fun mapAllowedTanFormat(allowedTanFormat: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
|
||||
private fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
|
||||
allowedTanFormat?.let { AllowedTanFormat.valueOf(it.name) } ?: AllowedTanFormat.Alphanumeric
|
||||
|
||||
protected open fun mapTanImage(image: net.codinux.banking.fints.tan.TanImage): TanImage =
|
||||
private fun mapTanImage(image: net.dankito.banking.fints.tan.TanImage): TanImage =
|
||||
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
protected open fun mapToBase64(bytes: ByteArray?): String? {
|
||||
if (bytes == null || bytes.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun mapToBase64(bytes: ByteArray): String {
|
||||
return Base64.Default.encode(bytes)
|
||||
}
|
||||
|
||||
protected open fun mapTanMedium(tanMedium: TanMedium) = net.codinux.banking.client.model.tan.TanMedium(
|
||||
mapTanMediumType(tanMedium), tanMedium.mediumName, mapTanMediumStatus(tanMedium.status),
|
||||
tanMedium.tanGenerator?.let { mapTanGeneratorTanMedium(it) },
|
||||
tanMedium.mobilePhone?.let { mapMobilePhoneTanMedium(it) }
|
||||
)
|
||||
|
||||
protected open fun mapTanMediumStatus(status: TanMediumStatus): net.codinux.banking.client.model.tan.TanMediumStatus = when (status) {
|
||||
TanMediumStatus.Aktiv -> net.codinux.banking.client.model.tan.TanMediumStatus.Used
|
||||
TanMediumStatus.Verfuegbar -> net.codinux.banking.client.model.tan.TanMediumStatus.Available
|
||||
TanMediumStatus.AktivFolgekarte -> net.codinux.banking.client.model.tan.TanMediumStatus.ActiveFollowUpCard
|
||||
TanMediumStatus.VerfuegbarFolgekarte -> net.codinux.banking.client.model.tan.TanMediumStatus.AvailableFollowUpCard
|
||||
}
|
||||
|
||||
protected open fun mapMobilePhoneTanMedium(tanMedium: MobilePhoneTanMedium) = net.codinux.banking.client.model.tan.MobilePhoneTanMedium(
|
||||
tanMedium.phoneNumber, tanMedium.concealedPhoneNumber
|
||||
)
|
||||
|
||||
protected open fun mapTanGeneratorTanMedium(tanMedium: TanGeneratorTanMedium) = net.codinux.banking.client.model.tan.TanGeneratorTanMedium(
|
||||
tanMedium.cardNumber, tanMedium.cardSequenceNumber, tanMedium.cardType,
|
||||
tanMedium.validFrom, tanMedium.validTo
|
||||
)
|
||||
|
||||
protected open fun mapTanMediumType(tanMedium: TanMedium): TanMediumType = when (tanMedium.mediumClass) {
|
||||
TanMediumKlasse.MobiltelefonMitMobileTan -> TanMediumType.MobilePhone
|
||||
TanMediumKlasse.TanGenerator -> TanMediumType.TanGenerator
|
||||
else -> TanMediumType.Generic
|
||||
}
|
||||
|
||||
protected open fun mapFlickerCode(flickerCode: net.codinux.banking.fints.tan.FlickerCode): FlickerCode =
|
||||
private fun mapFlickerCode(flickerCode: net.dankito.banking.fints.tan.FlickerCode): FlickerCode =
|
||||
FlickerCode(flickerCode.challengeHHD_UC, flickerCode.parsedDataSet, mapException(flickerCode.decodingError))
|
||||
|
||||
|
||||
/* Transfer Money */
|
||||
|
||||
open fun mapToTransferMoneyParameter(request: TransferMoneyRequestForUser): TransferMoneyParameter = TransferMoneyParameter(
|
||||
request.bankCode, request.loginName, request.password, request.senderAccount?.let { mapBankAccountIdentifier(it) },
|
||||
request.recipientName, request.recipientAccountIdentifier, request.recipientBankIdentifier,
|
||||
mapToMoney(request.amount, request.currency), request.paymentReference, request.instantTransfer,
|
||||
request.preferredTanMethods?.map { mapTanMethodType(it) },
|
||||
request.tanMethodsNotSupportedByApplication.map { mapTanMethodType(it) },
|
||||
finTsModel = request.clientData as? BankData,
|
||||
serializedFinTsModel = request.serializedClientData
|
||||
)
|
||||
|
||||
open fun mapTransferMoneyResponse(response: net.dankito.banking.client.model.response.TransferMoneyResponse, bank: BankAccess? = null, account: BankAccount? = null): Response<TransferMoneyResponse> =
|
||||
if (response.successful) {
|
||||
bank?.let { mapCommonResponseData(it, response) }
|
||||
|
||||
Response.success(TransferMoneyResponse(), mapMessageLog(response, bank, account))
|
||||
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
|
||||
return if (response.error != null) {
|
||||
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
|
||||
if (response.error == ErrorCode.BankReturnedError && response.errorMessage !== null) listOf(response.errorMessage!!) else emptyList())
|
||||
} else {
|
||||
mapError(response, mapMessageLog(response, bank, account))
|
||||
Response.error(ErrorType.UnknownError, response.errorMessage)
|
||||
}
|
||||
|
||||
open fun mapToMoney(amount: Amount, currency: String): Money = Money(amount.toString(), currency)
|
||||
|
||||
|
||||
open fun mapMessageLog(response: FinTsClientResponse, bank: BankAccess? = null, account: BankAccount? = null) =
|
||||
mapMessageLog(response.messageLog, bank, account)
|
||||
|
||||
open fun mapMessageLog(messageLog: List<net.codinux.banking.fints.model.MessageLogEntry>, bank: BankAccess? = null, account: BankAccount? = null) =
|
||||
messageLog.map { mapMessageLogEntry(it, bank, account) }
|
||||
|
||||
open fun mapMessageLogEntry(messageLogEntry: net.codinux.banking.fints.model.MessageLogEntry, bank: BankAccess? = null, account: BankAccount? = null): MessageLogEntry {
|
||||
// TODO: may map messageLogEntry.context.BankData to BankAccess
|
||||
val context = messageLogEntry.context
|
||||
val fintsAccount = context.account
|
||||
val effectiveAccount = account ?: bank?.accounts?.firstOrNull { it.identifier == fintsAccount?.accountIdentifier && it.subAccountNumber == fintsAccount.subAccountAttribute }
|
||||
|
||||
val messageNumberString = "${context.jobNumber.toString().padStart(2, '0')}_${context.dialogNumber.toString().padStart(2, '0')}_${context.messageNumber.toString().padStart(2, '0')}"
|
||||
|
||||
return MessageLogEntry(
|
||||
MessageLogEntryType.valueOf(messageLogEntry.type.name),
|
||||
messageLogEntry.message, messageLogEntry.messageWithoutSensitiveData,
|
||||
messageLogEntry.error, messageLogEntry.time,
|
||||
|
||||
messageNumberString,
|
||||
messageNumberString.replace("_", "").toIntOrNull(),
|
||||
|
||||
context.jobType.toString(),
|
||||
context.messageType.toString(),
|
||||
|
||||
bank,
|
||||
effectiveAccount
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
protected open fun <T> mapError(response: FinTsClientResponse, messageLog: List<MessageLogEntry>): Response<T> =
|
||||
if (response.error != null) {
|
||||
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
|
||||
if (response.error == ErrorCode.BankReturnedError && response.errorMessage !== null) listOf(response.errorMessage!!) else emptyList(), messageLog)
|
||||
} else {
|
||||
Response.error(ErrorType.UnknownError, response.errorMessage, messageLog = messageLog)
|
||||
}
|
||||
|
||||
protected open fun mapException(exception: Exception?): String? =
|
||||
private fun mapException(exception: Exception?): String? =
|
||||
exception?.stackTraceToString()
|
||||
|
||||
|
||||
open fun mapCommonResponseData(bank: BankAccess, response: FinTsClientResponse) {
|
||||
response.finTsModel?.let {
|
||||
bank.clientData = it
|
||||
}
|
||||
|
||||
response.serializedFinTsModel?.let {
|
||||
bank.serializedClientData = it
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
data class FinTsClientOptions(
|
||||
|
||||
/**
|
||||
* If FinTS messages sent to and received from bank servers and errors should be collected.
|
||||
*
|
||||
* Set to false by default.
|
||||
*/
|
||||
val collectMessageLog: Boolean = false,
|
||||
|
||||
// /**
|
||||
// * If set to true then [net.codinux.banking.fints.callback.FinTsClientCallback.messageLogAdded] get fired when a
|
||||
// * FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
|
||||
// *
|
||||
// * Defaults to false.
|
||||
// */
|
||||
// val fireCallbackOnMessageLogs: Boolean = false,
|
||||
|
||||
/**
|
||||
* If sensitive data like user name, password, login name should be removed from FinTS messages before being logged.
|
||||
*
|
||||
* Defaults to true.
|
||||
*/
|
||||
val removeSensitiveDataFromMessageLog: Boolean = true,
|
||||
|
||||
val appendFinTsMessagesToLog: Boolean = false,
|
||||
|
||||
val closeDialogs: Boolean = true,
|
||||
|
||||
val version: String = "1.0.0", // TODO: get version dynamically
|
||||
val productName: String = "15E53C26816138699C7B6A3E8"
|
||||
)
|
|
@ -1,72 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.prettifier
|
||||
|
||||
import net.codinux.banking.client.model.BankingGroup
|
||||
|
||||
// TODO: class has been duplicated from BankListCreator, find a better place for it
|
||||
class BankingGroupMapper {
|
||||
|
||||
fun getBankingGroup(bankName: String, bic: String): BankingGroup? {
|
||||
val lowercase = bankName.lowercase()
|
||||
|
||||
return when {
|
||||
bankName.contains("Sparda") -> BankingGroup.Sparda
|
||||
bankName.contains("PSD") -> BankingGroup.PSD
|
||||
bankName.contains("GLS") -> BankingGroup.GLS
|
||||
// see https://de.wikipedia.org/wiki/Liste_der_Genossenschaftsbanken_in_Deutschland
|
||||
bankName.contains("BBBank") || bankName.contains("Evangelische Bank") || bankName.contains("LIGA Bank")
|
||||
|| bankName.contains("Pax") || bankName.contains("Bank für Kirche und Diakonie") || bankName.contains("Bank im Bistum Essen")
|
||||
|| bankName.contains("Bank für Schiffahrt") || bankName.contains("Bank für Kirche")
|
||||
-> BankingGroup.SonstigeGenossenschaftsbank
|
||||
lowercase.contains("deutsche kreditbank") -> BankingGroup.DKB
|
||||
// may check against https://de.wikipedia.org/wiki/Liste_der_Sparkassen_in_Deutschland
|
||||
lowercase.contains("sparkasse") -> BankingGroup.Sparkasse
|
||||
lowercase.contains("comdirect") -> BankingGroup.Comdirect
|
||||
lowercase.contains("commerzbank") -> BankingGroup.Commerzbank
|
||||
lowercase.contains("targo") -> BankingGroup.Targobank
|
||||
lowercase.contains("santander") -> BankingGroup.Santander
|
||||
bankName.contains("KfW") -> BankingGroup.KfW
|
||||
bankName.contains("N26") -> BankingGroup.N26
|
||||
else -> getBankingGroupByBic(bic)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBankingGroupByBic(bic: String): BankingGroup? {
|
||||
if (bic.length < 4) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (bic.startsWith("CMCIDEDD")) {
|
||||
return BankingGroup.Targobank
|
||||
}
|
||||
|
||||
val bankCodeOfBic = bic.substring(0, 4)
|
||||
|
||||
return when (bankCodeOfBic) {
|
||||
"GENO", "VBMH", "VOHA", "VBRS", "DBPB", "VBGT", "FFVB", "WIBA", "VRBU", "MVBM", "VOBA", "ULMV", "VBRT", "VBRA", "VBPF", "VOLO" -> BankingGroup.VolksUndRaiffeisenbanken
|
||||
"BFSW", // Bank fuer Sozialwirtschaft
|
||||
"BEVO", // Berliner Volksbank
|
||||
"DAAE", // apoBank
|
||||
"MHYP", // Münchener Hypothekenbank
|
||||
"DZBM", // DZB Bank
|
||||
"EDEK" // Edekabank
|
||||
-> BankingGroup.SonstigeGenossenschaftsbank
|
||||
"BYLA", "SOLA", "NOLA", "WELA", "HELA", "MALA", "BRLA", "NASS", "TRIS", "OSDD", "ESSL", "GOPS", "SBCR", "BRUS" -> BankingGroup.Sparkasse // filter out DBK, (Bayr.) Landesbank, ...
|
||||
"OLBO" -> BankingGroup.OldenburgischeLandesbank
|
||||
"DEUT" -> BankingGroup.DeutscheBank
|
||||
"PBNK" -> BankingGroup.Postbank
|
||||
"COBA", "DRES" -> BankingGroup.Commerzbank // COBA could also be comdirect, but we cannot differentiate this at this level, this has to do getBankingGroup()
|
||||
"HYVE" -> BankingGroup.Unicredit
|
||||
"INGB" -> BankingGroup.ING
|
||||
"SCFB" -> BankingGroup.Santander
|
||||
"NORS" -> BankingGroup.Norisbank
|
||||
"DEGU" -> BankingGroup.Degussa
|
||||
"OBKL" -> BankingGroup.Oberbank
|
||||
"MARK" -> BankingGroup.Bundesbank
|
||||
"KFWI", "DTAB" -> BankingGroup.KfW
|
||||
"NTSB" -> BankingGroup.N26
|
||||
"CSDB" -> BankingGroup.Consors
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -3,10 +3,11 @@ package net.codinux.banking.client.fints4k
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import net.codinux.banking.client.SimpleBankingClientCallback
|
||||
import net.codinux.banking.client.model.response.ResponseType
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import kotlin.test.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@Ignore
|
||||
class FinTs4kBankingClientTest {
|
||||
|
||||
companion object {
|
||||
|
@ -18,11 +19,8 @@ class FinTs4kBankingClientTest {
|
|||
}
|
||||
|
||||
|
||||
private val underTest = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
println("WARN: TAN is required to execute test: ${tanChallenge.messageToShowToUser}")
|
||||
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { customer, tanChallenge ->
|
||||
|
||||
val enteredTan: String? = null
|
||||
callback(EnterTanResult(enteredTan))
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import kotlin.test.Test
|
||||
import net.codinux.banking.fints.transactions.swift.model.Holding
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FinTs4kMapperTest {
|
||||
|
||||
private val underTest = FinTs4kMapper()
|
||||
|
||||
|
||||
@Test
|
||||
fun getTotalBalance_TotalBalanceIsNull_CalculateByQuantityAndMarketValue() {
|
||||
val holding = Holding("", null, null, null, 4.0, null, null, null, fints4kAmount("13.33"))
|
||||
|
||||
val result = underTest.getTotalBalance(holding)
|
||||
|
||||
assertEquals(Amount("53.32"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getTotalCostPrice_TotalCostPriceIsNull_CalculateByQuantityAndAverageCostPrice() {
|
||||
val holding = Holding("", null, null, null, 47.0, fints4kAmount("16.828"), null)
|
||||
|
||||
val result = underTest.getTotalCostPrice(holding)
|
||||
|
||||
assertEquals(Amount("790.9159999999999"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatePerformance_ByTotalBalanceAndTotalCostPrice() {
|
||||
val holding = Holding("", null, null, null, null, null, fints4kAmount("20217.12"), null, null, null, fints4kAmount("19027.04"))
|
||||
|
||||
val result = underTest.calculatePerformance(holding)
|
||||
|
||||
assertEquals(6.2546773f, result!!, 0.000001f) // for JS the result has too many decimal places, so add a tolerance
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculatePerformance_ByMarketValueAndAverageCostPrice() {
|
||||
val holding = Holding("", null, null, null, null, fints4kAmount("16.828"), null, null, fints4kAmount("16.75"), null, null)
|
||||
|
||||
val result = underTest.calculatePerformance(holding)
|
||||
|
||||
assertEquals(-0.4635132f, result!!, 0.0000001f) // for JS the result has too many decimal places, so add a tolerance
|
||||
}
|
||||
|
||||
|
||||
private fun fints4kAmount(amount: String) =
|
||||
net.codinux.banking.fints.model.Amount(amount)
|
||||
|
||||
}
|
184
README.md
184
README.md
|
@ -1,184 +0,0 @@
|
|||
# Banking Client
|
||||
[](https://maven-badges.herokuapp.com/maven-central/net.codinux.banking.client/banking-client)
|
||||
|
||||
Library to abstract over different banking client implementations like [fints4k](https://git.dankito.net/codinux/fints4k).
|
||||
|
||||
It's primary purpose is to abstract away the different implementation details and to create a common model that can be
|
||||
used in all projects directly or indirectly referencing it - Web Service, Middleware, Native Apps, HTML Apps - so that
|
||||
not each project has the implement to model again.
|
||||
|
||||
## Setup
|
||||
|
||||
### Gradle:
|
||||
|
||||
```
|
||||
repositories {
|
||||
// other repositories like mavenCentral(), ...
|
||||
maven {
|
||||
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation("net.codinux.banking.client:fints4k-banking-client:0.7.2")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
For the full example source code see [ShowUsage](SampleApp/src/main/kotlin/net/codinux/banking/client/fints4k/example/ShowUsage.kt).
|
||||
|
||||
### Get AccountData
|
||||
|
||||
Retrieves data like accounts, balance and booked transactions (Konten, Saldo und Kontoumsätze).
|
||||
|
||||
Simple example:
|
||||
|
||||
```kotlin
|
||||
class ShowUsage {
|
||||
|
||||
private val bankCode = "" // Bankleitzahl deiner Bank
|
||||
|
||||
private val loginName = "" // Online-Banking Login Name mit dem du dich beim Online-Banking deiner Bank anmeldest
|
||||
|
||||
private val password = "" // Online-Banking Password mit dem du dich beim Online-Banking deiner Bank anmeldest
|
||||
|
||||
|
||||
fun getAccountData() {
|
||||
val client = FinTs4kBankingClient(SimpleBankingClientCallback())
|
||||
|
||||
val response = client.getAccountData(bankCode, loginName, password) // that's all
|
||||
|
||||
printReceivedData(response) // now print retrieved data (save it to database, display it in UI, ...)
|
||||
}
|
||||
|
||||
|
||||
private fun printReceivedData(response: Response<GetAccountDataResponse>) {
|
||||
response.data?.let { data ->
|
||||
val user = data.user
|
||||
println("Kunde: ${user.customerName} ${user.accounts.size} Konten @ ${user.bic} ${user.bankName}")
|
||||
|
||||
println()
|
||||
println("Konten:")
|
||||
user.accounts.sortedBy { it.type }.forEach { account ->
|
||||
println("${account.identifier} ${account.productName} ${account.balance} ${account.currency}")
|
||||
}
|
||||
|
||||
println()
|
||||
println("Umsätze:")
|
||||
data.bookedTransactions.forEach { transaction ->
|
||||
println("${transaction.valueDate} ${transaction.amount} ${transaction.currency} ${transaction.otherPartyName ?: ""} - ${transaction.reference}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This fetches the booked account transactions of the last 90 days. In most cases no TAN is required for this.
|
||||
|
||||
|
||||
#### GetAccountData parameter
|
||||
|
||||
You can also specify options e.g. which transactions you would like to retrieve:
|
||||
|
||||
```kotlin
|
||||
val options = GetAccountDataOptions(
|
||||
retrieveBalance = true, // retrieve balance (Saldo / Kontostand) yes or no
|
||||
retrieveTransactions = RetrieveTransactions.AccordingToRetrieveFromAndTo, // so that fromDate and toDate below determine of which time period transactions (Umsätze) should be retrieved; defaults to OfLast90Days which in most cases doesn't require a TAN
|
||||
retrieveTransactionsFrom = LocalDate(2023, 1, 1),
|
||||
retrieveTransactionsTo = LocalDate(2023, 12, 31),
|
||||
abortIfTanIsRequired = false // e.g. for command line application when entering TAN is either not possible or a TAN procedure is used that cannot be handled via a break point (e.g. showing a TAN image or flicker code)
|
||||
)
|
||||
val response = client.getAccountData(options)
|
||||
```
|
||||
|
||||
#### TAN handling
|
||||
|
||||
Retrieving transactions older than 90 days or sometimes even log in requires a TAN, so add TAN handling in Client Callback:
|
||||
|
||||
```kotlin
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val tan: String? = null // if a TAN is required, read TAN from command line, add a UI, ...
|
||||
callback.invoke(EnterTanResult(tan)) // ... set a break point here, get TAN e.g. from your TAN app, set tan variable in debugger view and resume debugger
|
||||
})
|
||||
```
|
||||
|
||||
E.g. TAN handling on the command line:
|
||||
|
||||
```kotlin
|
||||
println("Enter password for $bankCode:")
|
||||
val password = readln() // as an alternative for hard coding password; of course can also be done for bankCode and login name
|
||||
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
println("A TAN is required for ${tanChallenge.forAction}. Selected TAN method is '${tanChallenge.selectedTanMethod.displayName}'. Messsage of your credit institute:")
|
||||
println(tanChallenge.messageToShowToUser)
|
||||
println("Get TAN from your TAN app etc., enter it and press Enter (or press Enter without an input to abort process):")
|
||||
|
||||
val tan: String? = readlnOrNull().takeUnless { it.isNullOrBlank() } // map empty input to null to abort process
|
||||
callback.invoke(EnterTanResult(tan))
|
||||
})
|
||||
```
|
||||
|
||||
#### Error handling
|
||||
|
||||
Add some error handling by checking `response.error`:
|
||||
|
||||
```kotlin
|
||||
response.error?.let{ error ->
|
||||
println("Could not fetch account data: ${error.type} ${error.internalError ?: error.errorMessagesFromBank.joinToString()}")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Update Account Transactions
|
||||
|
||||
The data model saves when it retrieved account transactions the last time (in `BankAccount.lastAccountUpdateTime`).
|
||||
So you only need to call `FinTs4kBankingClient.updateAccountTransactions()` to retrieve all transactions starting from
|
||||
`BankAccount.lastAccountUpdateTime`.
|
||||
|
||||
But as we can only specify from which day on account transactions should be retrieved, response may contain some transactions
|
||||
from the day of `lastAccountUpdateTime` that we already have locally. To filter out these you can use
|
||||
`BankingModelService().findNewTransactions(retrieveTransactions, existingTransactions)`:
|
||||
|
||||
```kotlin
|
||||
fun updateAccountTransactions() {
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback())
|
||||
|
||||
// simulate account transactions we retrieved last time
|
||||
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
val lastCallToBankServer = client.getAccountData(GetAccountDataOptions(RetrieveTransactions.AccordingToRetrieveFromAndTo, retrieveTransactionsFrom = today.minusDays(90), retrieveTransactionsTo = today.minusDays(30)))
|
||||
|
||||
if (lastCallToBankServer.data != null) { // now update account transactions
|
||||
val existingTransactions = lastCallToBankServer.data!!.bookedTransactions
|
||||
|
||||
val updateTransactionsResponse = client.updateAccountTransactions()
|
||||
|
||||
if (updateTransactionsResponse.data != null) {
|
||||
val retrievedTransactions = updateTransactionsResponse.data!!.flatMap { it.bookedTransactions }
|
||||
val newTransactions = BankingModelService().findNewTransactions(retrievedTransactions, existingTransactions)
|
||||
// `retrievedTransactions` may contain transactions we already have locally, `newTransactions` only
|
||||
// transactions that are not in `existingTransactions`
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
BankingClient and fints4k both use [klf](https://github.com/codinux-gmbh/klf), a logging facade for Kotlin (Multiplatform)
|
||||
with appenders for all supported KMP platforms.
|
||||
So logging works on all platforms out of the box. On JVM, if slf4j is on the classpath, logging can be configured with
|
||||
any slf4j compatible logging backend (logback, log4j, JBoss Logging, ...).
|
||||
|
||||
If you want to see all sent and received FinTS messages, set the log level of `net.codinux.banking.fints.log.MessageLogCollector` to `DEBUG`, either via:
|
||||
- your logging framework (e.g. logback)
|
||||
- klf: `net.codinux.log.LoggerFactory.getLogger("net.codinux.banking.fints.log.MessageLogCollector").level = LogLevel.Debug`
|
||||
- `appendFinTsMessagesToLog` option:
|
||||
```kotlin
|
||||
val client = FinTs4kBankingClient(FinTsClientOptions(appendFinTsMessagesToLog = true), SimpleBankingClientCallback())
|
||||
```
|
||||
But be aware, in latter case if you create multiple FinTs4kBankingClient instances, the latest value of `appendFinTsMessagesToLog`
|
||||
overrides the value of all previous FinTs4kBankingClient instances. As with all other options, this configures the logger's level globally,
|
||||
so the latest configured log level value wins.
|
|
@ -1,16 +0,0 @@
|
|||
plugins {
|
||||
kotlin("jvm") // or kotlin("multiplatform"), depending on your requirements
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(":fints4k-banking-client"))
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package net.codinux.banking.client.fints4k.example
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import net.codinux.banking.client.SimpleBankingClientCallback
|
||||
import net.codinux.banking.client.fints4k.FinTs4kBankingClient
|
||||
import net.codinux.banking.client.fints4k.FinTs4kBankingClientForUser
|
||||
import net.codinux.banking.client.getAccountData
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.service.BankingModelService
|
||||
import net.codinux.banking.client.updateAccountTransactions
|
||||
import net.codinux.banking.client.model.extensions.minusDays
|
||||
|
||||
fun main() {
|
||||
val showUsage = ShowUsage()
|
||||
|
||||
showUsage.getAccountDataSimpleExample()
|
||||
// showUsage.getAccountDataFullExample()
|
||||
}
|
||||
|
||||
class ShowUsage {
|
||||
|
||||
private val bankCode = "" // Bankleitzahl deiner Bank
|
||||
|
||||
private val loginName = "" // Online-Banking Login Name mit dem du dich beim Online-Banking deiner Bank anmeldest
|
||||
|
||||
private val password = "" // Online-Banking Password mit dem du dich beim Online-Banking deiner Bank anmeldest
|
||||
|
||||
|
||||
fun getAccountDataSimpleExample() {
|
||||
val client = FinTs4kBankingClient(SimpleBankingClientCallback())
|
||||
|
||||
val response = client.getAccountData(bankCode, loginName, password) // that's all
|
||||
|
||||
printReceivedData(response) // now print retrieved data (save it to database, display it in UI, ...)
|
||||
}
|
||||
|
||||
fun getAccountDataFullExample() {
|
||||
println("Enter password for $bankCode:")
|
||||
val password = readln() // as an alternative for hard coding password; of course can also be done for bankCode and login name
|
||||
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
println("A TAN is required for ${tanChallenge.forAction}. Selected TAN method is '${tanChallenge.selectedTanMethod.displayName}'. Messsage of your credit institute:")
|
||||
println(tanChallenge.messageToShowToUser)
|
||||
println("Get TAN from your TAN app etc., enter it and press Enter (or press Enter without an input to abort process):")
|
||||
|
||||
val tan: String? = readlnOrNull().takeUnless { it.isNullOrBlank() } // map empty input to null to abort process
|
||||
callback.invoke(EnterTanResult(tan))
|
||||
})
|
||||
|
||||
val options = GetAccountDataOptions(
|
||||
retrieveBalance = true, // retrieve balance (Saldo / Kontostand) yes or no
|
||||
retrieveTransactions = RetrieveTransactions.AccordingToRetrieveFromAndTo, // so that fromDate and toDate below determine of which time period transactions (Umsätze) should be retrieved; defaults to OfLast90Days which in most cases doesn't require a TAN
|
||||
retrieveTransactionsFrom = LocalDate(2023, 1, 1),
|
||||
retrieveTransactionsTo = LocalDate(2023, 12, 31),
|
||||
abortIfTanIsRequired = false // e.g. for command line application when entering TAN is either not possible or a TAN procedure is used that cannot be handled via a break point (e.g. showing a TAN image or flicker code)
|
||||
)
|
||||
val response = client.getAccountData(options)
|
||||
|
||||
response.error?.let{ error ->
|
||||
println("Could not fetch account data: ${error.type} ${error.internalError ?: error.errorMessagesFromBank.joinToString()}")
|
||||
}
|
||||
|
||||
printReceivedData(response)
|
||||
}
|
||||
|
||||
|
||||
fun updateAccountTransactions() {
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val tan: String? = readlnOrNull() // if a TAN is required, read TAN from command line, add a UI, ...
|
||||
callback.invoke(EnterTanResult(tan)) // ... set a break point here, get TAN e.g. from your TAN app, set tan variable in debugger view and resume debugger
|
||||
})
|
||||
|
||||
// simulate account transactions we retrieved last time
|
||||
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
val lastCallToBankServer = client.getAccountData(GetAccountDataOptions(RetrieveTransactions.AccordingToRetrieveFromAndTo, retrieveTransactionsFrom = today.minusDays(90), retrieveTransactionsTo = today.minusDays(30)))
|
||||
|
||||
if (lastCallToBankServer.data != null) { // now update account transactions
|
||||
val existingTransactions = lastCallToBankServer.data!!.bookedTransactions
|
||||
|
||||
val updateTransactionsResponse = client.updateAccountTransactions()
|
||||
|
||||
if (updateTransactionsResponse.data != null) {
|
||||
val retrievedTransactions = updateTransactionsResponse.data!!.flatMap { it.bookedTransactions }
|
||||
val newTransactions = BankingModelService().findNewTransactions(retrievedTransactions, existingTransactions)
|
||||
// `retrievedTransactions` may contain transactions we already have locally, `newTransactions` only
|
||||
// transactions that are not in `existingTransactions`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun printReceivedData(response: Response<GetAccountDataResponse>) {
|
||||
response.data?.let { data ->
|
||||
val bank = data.bank
|
||||
println("Kunde: ${bank.customerName} ${bank.accounts.size} Konten @ ${bank.bic} ${bank.bankName}")
|
||||
|
||||
println()
|
||||
println("Konten:")
|
||||
bank.accounts.sortedBy { it.type }.forEach { account ->
|
||||
println("${account.identifier} ${account.productName} ${account.balance} ${account.currency}")
|
||||
}
|
||||
|
||||
println()
|
||||
println("Umsätze:")
|
||||
data.bookedTransactions.forEach { transaction ->
|
||||
println("${transaction.valueDate} ${transaction.amount} ${transaction.currency} ${transaction.otherPartyName ?: ""} - ${transaction.reference}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,18 +12,17 @@ buildscript {
|
|||
|
||||
allprojects {
|
||||
group = "net.codinux.banking.client"
|
||||
version = "0.7.2"
|
||||
version = "0.5.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven {
|
||||
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ext["sourceCodeRepositoryBaseUrl"] = "git.dankito.net/codinux/BankingClient"
|
||||
ext["sourceCodeRepositoryBaseUrl"] = "github.com/codinux/BankingClient"
|
||||
|
||||
ext["projectDescription"] = "Model and base definitions for Banking Client, a common abstraction for different implements of banking libraries"
|
||||
}
|
||||
|
@ -34,7 +33,7 @@ tasks.register("publishAllToMavenLocal") {
|
|||
":BankingClientModel:publishToMavenLocal",
|
||||
":BankingClient:publishToMavenLocal",
|
||||
|
||||
":fints4k-banking-client:publishToMavenLocal"
|
||||
":FinTs4jBankingClient:publishToMavenLocal"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -43,6 +42,6 @@ tasks.register("publishAll") {
|
|||
":BankingClientModel:publish",
|
||||
":BankingClient:publish",
|
||||
|
||||
":fints4k-banking-client:publish"
|
||||
":FinTs4jBankingClient:publish"
|
||||
)
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
Für den deutschen Begriff "Bankzugang" gibt es im Englischen verschiedene mögliche Übersetzungen, abhängig vom Kontext. Hier sind einige Alternativen:
|
||||
|
||||
1. **Bank access** – Diese Übersetzung ist direkt und wird verwendet, wenn es um den allgemeinen Zugang zu einem Bankkonto oder einer Bank geht.
|
||||
|
||||
2. **Online banking access** – Diese Option wird oft genutzt, wenn es sich spezifisch um den Zugang zu einem Online-Banking-Konto handelt.
|
||||
|
||||
3. **Bank account access** – Dies wird verwendet, wenn man den Zugang zu einem spezifischen Bankkonto meint.
|
||||
|
||||
4. **Bank login** – Eine übliche Formulierung für den Zugang zu einem Online-Banking-Portal durch Einloggen mit Anmeldedaten.
|
||||
|
||||
5. **Bank credentials** – Dies bezieht sich auf die Anmeldeinformationen (Benutzername, Passwort, etc.) für den Zugang zu einem Bankkonto, speziell im digitalen Bereich.
|
||||
|
||||
6. **Access to banking services** – Diese Variante beschreibt eher den allgemeinen Zugang zu Bankdienstleistungen, sowohl digital als auch in Filialen.
|
||||
|
||||
Die beste Wahl hängt vom genauen Kontext ab, z.B. ob es sich um physischen Zugang zu einer Bank, Online-Zugang oder Zugang zu bestimmten Bankdiensten handelt.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Hier sind einige Namensvorschläge für deine Klasse mit entsprechenden Übersetzungen ins Englische:
|
||||
|
||||
1. **BankVerbindung**
|
||||
_BankConnection_
|
||||
|
||||
2. **BankZugangsdaten**
|
||||
_BankAccessData_
|
||||
|
||||
3. **OnlineBankProfil**
|
||||
_OnlineBankProfile_
|
||||
|
||||
4. **BankKontoDetails**
|
||||
_BankAccountDetails_
|
||||
|
||||
5. **BankNutzerProfil**
|
||||
_BankUserProfile_
|
||||
|
||||
6. **BankNutzerDaten**
|
||||
_BankUserData_
|
||||
|
||||
7. **BankZugangsprofil**
|
||||
_BankAccessProfile_
|
||||
|
||||
8. **BankKontoverbindung**
|
||||
_BankAccountConnection_
|
||||
|
||||
9. **BankServiceInfo**
|
||||
_BankServiceInfo_
|
||||
|
||||
10. **BankNutzerVerwaltung**
|
||||
_BankUserManagement_
|
||||
|
||||
Diese Namen umfassen sowohl den Zugang zur Bank als auch Daten über die Bank und den Nutzer.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Hier sind einige Namensvorschläge für die Klasse auf Deutsch und Englisch:
|
||||
|
||||
1. **BankZugangsdaten**
|
||||
- **Englisch**: BankAccessData
|
||||
|
||||
2. **OnlineBankingZugang**
|
||||
- **Englisch**: OnlineBankingAccess
|
||||
|
||||
3. **BankVerbindung**
|
||||
- **Englisch**: BankConnection
|
||||
|
||||
4. **BankZugang**
|
||||
- **Englisch**: BankAccess
|
||||
|
||||
5. **NutzerBankDetails**
|
||||
- **Englisch**: UserBankDetails
|
||||
|
||||
6. **BankingAuthentifizierung**
|
||||
- **Englisch**: BankingAuthentication
|
||||
|
||||
7. **BankLoginDaten**
|
||||
- **Englisch**: BankLoginData
|
||||
|
||||
8. **BankZugangsInfo**
|
||||
- **Englisch**: BankAccessInfo
|
||||
|
||||
9. **KontoZugangsdaten**
|
||||
- **Englisch**: AccountAccessData
|
||||
|
||||
10. **OnlineBankingVerbindung**
|
||||
- **Englisch**: OnlineBankingConnection
|
||||
|
||||
Diese Namen sollen die wesentlichen Informationen der Klasse widerspiegeln und die verschiedenen Attribute gut abdecken.
|
|
@ -1,18 +0,0 @@
|
|||
In vielen Ländern gibt es eigene Systeme zur eindeutigen Identifizierung von Banken, die ähnlich wie die **Bankleitzahl** (BLZ) in Deutschland funktionieren. Hier sind einige Beispiele:
|
||||
|
||||
1. **USA** – **Routing Number**: In den USA wird die **Routing Transit Number (RTN)** oder **ABA-Routing-Nummer** verwendet, um Banken zu identifizieren.
|
||||
2. **Großbritannien** – **Sort Code**: In Großbritannien wird der **Sort Code** verwendet, um eine Bank und ihre Filiale zu identifizieren.
|
||||
3. **International** – **SWIFT/BIC**: Das **SWIFT/BIC**-System (Society for Worldwide Interbank Financial Telecommunication / Bank Identifier Code) wird weltweit verwendet, um Banken international eindeutig zu identifizieren.
|
||||
4. **Kanada** – **Institution Number**: In Kanada gibt es eine **Institution Number** (in Kombination mit einer Transitnummer).
|
||||
5. **Australien** – **BSB Number**: In Australien ist es die **BSB Number** (Bank-State-Branch Number).
|
||||
6. **Indien** – **IFSC Code**: In Indien wird der **Indian Financial System Code (IFSC)** verwendet.
|
||||
7. **Schweiz** – **SIC Number**: In der Schweiz gibt es das **SIC (Swiss Interbank Clearing)**-System, um Banken zu identifizieren.
|
||||
|
||||
### Guter englischer Name für eine landesweite eindeutige Bankidentifikation
|
||||
Ein passender englischer Begriff für einen solchen eindeutigen Identifikationswert wäre:
|
||||
|
||||
- **National Bank Identifier (NBI)**: Dies beschreibt eine eindeutige Kennung, die eine Bank auf nationaler Ebene identifiziert.
|
||||
- **Domestic Bank Code (DBC)**: Dies weist ebenfalls auf die Verwendung im nationalen Kontext hin.
|
||||
- **Bank Identifier Code (BIC)**: Obwohl dieser Begriff oft international verwendet wird, kann er auch auf eine landesweite eindeutige Identifikation hinweisen, wenn er im nationalen Kontext benutzt wird.
|
||||
|
||||
Ein allgemeiner Begriff wie **"National Bank Code"** wäre ebenfalls leicht verständlich und eindeutig in seiner Funktion.
|
|
@ -1,14 +1,8 @@
|
|||
kotlin.code.style=official
|
||||
|
||||
#org.gradle.parallel=true
|
||||
|
||||
|
||||
kotlinVersion=1.9.25
|
||||
kotlinVersion=2.0.10
|
||||
|
||||
kotlinxDateTimeVersion=0.5.0
|
||||
jsJodaTimeZoneVersion=2.3.0
|
||||
# Coroutines 1.9 (currently RC) requires Kotlin 2.0
|
||||
coroutinesVersion=1.8.1
|
||||
|
||||
# 0.3.10 uses Kotlin 2.0.0
|
||||
ionspinBigNumVersion=0.3.9
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 88f1b01167e6a34b5b91f8797845bca0b7e4d3ab
|
||||
Subproject commit bdf8b14738c06016a48e1fc9781ad4d999e1219f
|
File diff suppressed because it is too large
Load diff
|
@ -31,5 +31,3 @@ include("FinTs4jBankingClient")
|
|||
project(":FinTs4jBankingClient").apply {
|
||||
name = "fints4k-banking-client"
|
||||
}
|
||||
|
||||
include("SampleApp")
|
||||
|
|
Loading…
Add table
Reference in a new issue