Compare commits

..

No commits in common. "c5b7967ce1e85bf54ecd796f2a217f05270f7787" and "bd052587c5431d9c83f00454c71d9beaaaa16ba4" have entirely different histories.

30 changed files with 137 additions and 548 deletions

View File

@ -3,11 +3,9 @@ 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
interface BankingClient {
@ -38,16 +36,7 @@ interface BankingClient {
* Convenience wrapper around [getAccountDataAsync].
* Updates account's transactions beginning from [BankAccount.lastTransactionsRetrievalTime].
* This may requires TAN if [BankAccount.lastTransactionsRetrievalTime] is older than 90 days.
*
* Optionally specify which [accounts] should be updated. If not specified all accounts will be updated.
*/
suspend fun updateAccountTransactionsAsync(user: User, accounts: List<BankAccount>? = null): 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>
suspend fun updateAccountTransactionsAsync(user: UserAccount, accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
}

View File

@ -1,14 +1,11 @@
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
interface BankingClientForUser {
@ -40,11 +37,6 @@ interface BankingClientForUser {
* Updates account's transactions beginning from [BankAccount.lastTransactionsRetrievalTime].
* This may requires TAN if [BankAccount.lastTransactionsRetrievalTime] is older than 90 days.
*/
suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
suspend fun transferMoneyAsync(recipientName: String, recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null): Response<TransferMoneyResponse>
suspend fun transferMoneyAsync(request: TransferMoneyRequest): Response<TransferMoneyResponse>
suspend fun updateAccountTransactionsAsync(): Response<List<GetTransactionsResponse>>
}

View File

@ -1,13 +1,9 @@
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.User
import net.codinux.banking.client.model.UserAccount
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
@ -16,7 +12,7 @@ abstract class BankingClientForUserBase(
protected val client: BankingClient
) : BankingClientForUser {
private lateinit var user: User
private lateinit var user: UserAccount
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
client.getAccountDataAsync(GetAccountDataRequest(credentials, options)).also {
@ -25,14 +21,7 @@ abstract class BankingClientForUserBase(
}
}
override suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> =
client.updateAccountTransactionsAsync(user, 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(user.bankCode, user.loginName, user.password!!, request))
override suspend fun updateAccountTransactionsAsync(): Response<List<GetTransactionsResponse>> =
client.updateAccountTransactionsAsync(user)
}

View File

@ -1,58 +1,32 @@
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.User
import net.codinux.banking.client.model.UserAccount
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
/* 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(user: User, accounts: List<BankAccount>? = null) = runBlocking {
updateAccountTransactionsAsync(user, accounts)
fun BankingClient.updateAccountTransactions(user: UserAccount, accounts: List<BankAccount>? = null) = runBlocking {
this@updateAccountTransactions.updateAccountTransactionsAsync(user, accounts)
}
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()
this@getAccountData.getAccountDataAsync()
}
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
getAccountDataAsync(options)
this@getAccountData.getAccountDataAsync(options)
}
fun BankingClientForUser.updateAccountTransactions() = runBlocking {
updateAccountTransactionsAsync()
}
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)
this@updateAccountTransactions.updateAccountTransactionsAsync()
}

View File

@ -5,9 +5,9 @@ import net.codinux.banking.client.model.config.NoArgConstructor
@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 unparsedReference: String, // Alternative: purpose (or Remittance information)
/**
* Transaction date (Buchungstag) - der Tag, an dem ein Zahlungsvorgang in das System einer Bank eingegangen ist.
@ -22,115 +22,52 @@ 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,
/**
* Storno, ob die Buchung storniert wurde(?).
* Aus:
* RC = Storno Haben
* RD = Storno Soll
*/
val isReversal: Boolean = false,
val transactionReferenceNumber: String? = null,
val relatedReferenceNumber: String? = null,
var userSetDisplayName: String? = null,
var category: String? = null,
var notes: String? = null,
) {
val reference: String
get() = sepaReference ?: unparsedReference
open val identifier by lazy {
"$amount $currency $bookingDate $valueDate $reference $otherPartyName $otherPartyBankId $otherPartyAccountId"
"$amount $currency $bookingDate $valueDate $unparsedReference $sepaReference $otherPartyName $otherPartyBankCode $otherPartyAccountId"
}
override fun toString() = "${valueDate.dayOfMonth}.${valueDate.monthNumber}.${valueDate.year} ${amount.toString().padStart(4, ' ')} ${if (currency == "EUR") "€" else currency} ${otherPartyName ?: ""} - $reference"

View File

@ -1,33 +1,35 @@
package net.codinux.banking.client.model
import kotlinx.datetime.*
import kotlinx.datetime.Instant
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 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 features: Set<BankAccountFeatures> = emptySet(),
// var balance: BigDecimal = BigDecimal.ZERO,
var balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
val serverTransactionsRetentionDays: Int? = null,
val isAccountTypeSupportedByApplication: Boolean = false,
val features: Set<BankAccountFeatures> = emptySet(),
open var lastTransactionsRetrievalTime: Instant? = null,
var retrievedTransactionsFrom: LocalDate? = null,
var haveAllTransactionsBeenRetrieved: Boolean = false,
val countDaysForWhichTransactionsAreKept: Int? = null,
open val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
open val prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
open val unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
var userSetDisplayName: String? = null,
var displayIndex: Int = 0,
@ -39,40 +41,8 @@ open class BankAccount(
open val displayName: String
get() = userSetDisplayName ?: productName ?: identifier
@get:JsonIgnore
open val supportsTransactionRetrieval: Boolean
get() = supportsAnyFeature(BankAccountFeatures.RetrieveBalance)
@get:JsonIgnore
open val supportsBalanceRetrieval: 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 =
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)"
}

View File

@ -4,5 +4,5 @@ enum class BankAccountFeatures {
RetrieveTransactions,
RetrieveBalance,
TransferMoney,
InstantTransfer
InstantPayment
}

View File

@ -8,7 +8,5 @@ open class BankAccountIdentifier(
val subAccountNumber: String?,
val iban: String?
) {
constructor(account: BankAccount) : this(account.identifier, account.subAccountNumber, account.iban)
override fun toString() = "$identifier, $iban"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -1,5 +0,0 @@
package net.codinux.banking.client.model
object DefaultValues {
const val DefaultCurrency = "EUR"
}

View File

@ -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 {
}

View File

@ -7,7 +7,7 @@ import net.codinux.banking.client.model.tan.TanMethod
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
@NoArgConstructor
open class User(
open class UserAccount(
val bankCode: String,
var loginName: String,
/**
@ -19,17 +19,7 @@ open class User(
val bic: String,
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.
*/
val userId: String? = null,
val userId: String = loginName,
open val accounts: List<BankAccount> = emptyList(),
@ -39,7 +29,7 @@ open class User(
* 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.
*/
val selectedTanMethodIdentifier: String? = null,
val selectedTanMethodId: String? = null,
open val tanMethods: List<TanMethod> = listOf(),
/**
@ -48,20 +38,18 @@ open class User(
* 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.
*/
val selectedTanMediumIdentifier: String? = null,
val selectedTanMediumName: String? = null,
open val tanMedia: List<TanMedium> = listOf(),
var bankingGroup: BankingGroup? = null,
open var serverAddress: String? = null
var iconUrl: String? = null,
) {
var wrongCredentialsEntered: Boolean = false
var userSetDisplayName: String? = null
var displayIndex: Int = 0
var iconUrl: String? = null
var wrongCredentialsEntered: Boolean = false
@get:JsonIgnore
open val displayName: String
@ -70,11 +58,11 @@ open class User(
@get:JsonIgnore
val selectedTanMethod: TanMethod
get() = tanMethods.first { it.identifier == selectedTanMethodIdentifier }
get() = tanMethods.first { it.identifier == selectedTanMethodId }
@get:JsonIgnore
val selectedTanMedium: TanMedium?
get() = tanMedia.firstOrNull { it.identifier == selectedTanMediumIdentifier }
get() = tanMedia.firstOrNull { it.mediumName == selectedTanMediumName }
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"

View File

@ -0,0 +1,17 @@
package net.codinux.banking.client.model
import net.codinux.banking.client.model.config.NoArgConstructor
/**
* Contains only the basic info of a [UserAccount], just enough that a client application can display it to the user
* and the user knows exactly which [UserAccount] is meant / referred.
*/
@NoArgConstructor
open class UserAccountViewInfo(
val bankCode: String,
var loginName: String,
val bankName: String,
val bankingGroup: BankingGroup? = null
) {
override fun toString() = "$bankCode $bankName $loginName"
}

View File

@ -36,8 +36,6 @@ open class GetAccountDataOptions(
*/
val preferredTanMethods: List<TanMethodType>? = TanMethodType.NonVisualOrImageBased,
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

View File

@ -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)

View File

@ -1,79 +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.NonVisualOrImageBased,
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications
) {
override fun toString() = "$amount to $recipientName - $paymentReference"
}

View File

@ -1,49 +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
/**
* 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.NonVisualOrImageBased,
tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
) : TransferMoneyRequest(senderAccount, recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer, preferredTanMethods, tanMethodsNotSupportedByApplication) {
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)
override fun toString() = "$bankCode $loginName ${super.toString()}"
}

View File

@ -1,14 +1,14 @@
package net.codinux.banking.client.model.response
import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.User
import net.codinux.banking.client.model.UserAccount
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 user: User
val user: UserAccount
) {
@get:JsonIgnore

View File

@ -5,7 +5,7 @@ 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.UnbookedAccountTransaction
import net.codinux.banking.client.model.config.NoArgConstructor
@NoArgConstructor
@ -13,7 +13,7 @@ open class GetTransactionsResponse(
val account: BankAccount,
val balance: Amount? = null,
val bookedTransactions: List<AccountTransaction>,
val prebookedTransactions: List<PrebookedAccountTransaction>,
val unbookedTransactions: List<UnbookedAccountTransaction>,
val transactionsRetrievalTime: Instant,
val retrievedTransactionsFrom: LocalDate? = null,
val retrievedTransactionsTo: LocalDate? = null

View File

@ -1,9 +0,0 @@
package net.codinux.banking.client.model.response
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

View File

@ -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
)

View File

@ -1,10 +1,8 @@
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.User
import net.codinux.banking.client.model.BankViewInfo
import net.codinux.banking.client.model.UserAccount
import net.codinux.banking.client.model.UserAccountViewInfo
import net.codinux.banking.client.model.config.JsonIgnore
import net.codinux.banking.client.model.config.NoArgConstructor
@ -26,7 +24,7 @@ open class TanChallenge(
* 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
* [User] may contains an outdated list of available TanMethods.
* [UserAccount] 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.
*/
@ -43,15 +41,8 @@ open class TanChallenge(
open val tanImage: TanImage? = null,
open val flickerCode: FlickerCode? = null,
open val user: 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()
open val user: UserAccountViewInfo,
open val account: BankAccountViewInfo? = null
) {
@get:JsonIgnore
@ -64,17 +55,7 @@ open class TanChallenge(
/**
* 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.
* Principally a no-op method, not implemented for all client, only implementing client for not: 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.

View File

@ -53,7 +53,7 @@ open class TanMedium(
val displayName: String by lazy {
identifier + " " + when (status) {
TanMediumStatus.Used -> "Aktiv"
TanMediumStatus.Used -> "Aktive"
TanMediumStatus.Available -> "Verfügbar"
TanMediumStatus.ActiveFollowUpCard -> " Folgekarte, aktiv bei erster Nutzung"
TanMediumStatus.AvailableFollowUpCard -> " Folgekarte, die erst aktiviert werden muss"

View File

@ -1,9 +1,7 @@
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,
@ -12,10 +10,5 @@ open class TanMethod(
open val maxTanInputLength: Int? = null,
open val allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric
) {
@get:JsonIgnore
open val isNumericTan: Boolean
get() = allowedTanFormat == AllowedTanFormat.Numeric
override fun toString() = "$displayName ($type, ${identifier})"
}

View File

@ -43,8 +43,6 @@ enum class TanMethodType {
addAll(listOf(TanMethodType.ChipTanManuell)) // this is quite inconvenient for user, so i added it as last
}
val TanMethodsNotSupportedByMostApplications = listOf(TanMethodType.ChipTanUsb)
}
}

View File

@ -22,8 +22,6 @@ open class BridgeFintTsToBankingClientCallback(
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
if (enterTanResult.enteredTan != null) {
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
} else if (enterTanResult.changeTanMethodTo != null) {
tanChallenge.userAsksToChangeTanMethod(mapper.mapTanMethod(enterTanResult.changeTanMethodTo!!))
} else {
tanChallenge.userDidNotEnterTan()
}

View File

@ -4,10 +4,9 @@ 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.User
import net.codinux.banking.client.model.UserAccount
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.fints.FinTsClient
import net.codinux.banking.fints.config.FinTsClientConfiguration
@ -21,18 +20,18 @@ open class FinTs4kBankingClient(
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
protected open val mapper = FinTs4kMapper()
protected val mapper = FinTs4kMapper()
protected open val client = FinTsClient(config, BridgeFintTsToBankingClientCallback(callback, mapper))
protected val client = FinTsClient(config, 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)
return mapper.map(response)
}
override suspend fun updateAccountTransactionsAsync(user: User, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
override suspend fun updateAccountTransactionsAsync(user: UserAccount, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
val accountsToRequest = (accounts ?: user.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveBalance) }
if (accountsToRequest.isNotEmpty()) {
@ -56,11 +55,4 @@ open class FinTs4kBankingClient(
return Response.error(ErrorType.NoneOfTheAccountsSupportsRetrievingData, "Keiner 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)
}
}

View File

@ -8,8 +8,6 @@ import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.Amount
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.TanChallenge
@ -22,14 +20,12 @@ 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.codinux.banking.fints.mapper.FinTsModelMapper
import net.codinux.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
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.TanMediumStatus
import net.dankito.banking.banklistcreator.prettifier.BankingGroupMapper
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@ -40,59 +36,41 @@ open class FinTs4kMapper {
protected val bankingGroupMapper = BankingGroupMapper()
open fun mapToGetAccountDataParameter(request: GetAccountDataRequest, options: GetAccountDataOptions) = GetAccountDataParameter(
request.bankCode, request.loginName, request.password,
options.accounts.map { mapBankAccountIdentifier(it) },
open 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(user: User, account: BankAccount, finTsModel: BankData?): GetAccountDataParameter {
val defaults = GetAccountDataOptions()
open fun mapToUpdateAccountTransactionsParameter(user: UserAccount, account: BankAccount, finTsModel: BankData?): GetAccountDataParameter {
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
val from = account.lastTransactionsRetrievalTime?.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)
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.OfLast90Days
// val preferredTanMethods = listOf(mapTanMethodType(user.selectedTanMethod.type)) // TODO: currently we aren't saving TanMethods in database, re-enable as soon as TanMethods get saved
val preferredTanMethods = defaults.preferredTanMethods?.map { mapTanMethodType(it) }
val preferredTanMethods = emptyList<net.codinux.banking.fints.model.TanMethodType>()
return GetAccountDataParameter(user.bankCode, user.loginName, user.password!!, listOf(accountIdentifier), true,
retrieveTransactions, from,
preferredTanMethods = preferredTanMethods,
preferredTanMedium = user.selectedTanMediumIdentifier,
preferredTanMedium = user.selectedTanMediumName,
finTsModel = finTsModel
)
}
open fun mapBankAccountIdentifier(account: BankAccountIdentifier): BankAccountIdentifierImpl =
BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
open fun mapTanMethod(method: TanMethod) = net.codinux.banking.fints.model.TanMethod( // TODO: get instance from FinTsData, don't create manually
method.displayName, Sicherheitsfunktion.entries.first { it.code == method.identifier }, mapTanMethodType(method.type), null, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
)
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
net.codinux.banking.fints.model.TanMethodType.valueOf(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, bank: BankInfo? = null): Response<GetAccountDataResponse> =
if (response.successful && response.customerAccount != null) {
Response.success(GetAccountDataResponse(mapUser(response.customerAccount!!, bank)))
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
return if (response.successful && response.customerAccount != null) {
Response.success(GetAccountDataResponse(mapUser(response.customerAccount!!)))
} else {
mapError(response)
}
}
open fun map(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
@ -124,7 +102,7 @@ open class FinTs4kMapper {
}
open fun mapToUserAccountViewInfo(bank: BankData): BankViewInfo = BankViewInfo(
open fun mapToUserAccountViewInfo(bank: BankData): UserAccountViewInfo = UserAccountViewInfo(
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
)
@ -135,17 +113,15 @@ open class FinTs4kMapper {
)
protected open fun mapUser(user: net.dankito.banking.client.model.CustomerAccount, bank: BankInfo? = null) = User(
protected open fun mapUser(user: net.dankito.banking.client.model.CustomerAccount) = UserAccount(
user.bankCode, user.loginName, user.password,
bank?.name ?: user.bankName, user.bic, user.customerName, user.userId,
user.accounts.map { mapAccount(it) }.sortedBy { it.type }
.onEachIndexed { index, bankAccount -> bankAccount.displayIndex = index },
user.bankName, user.bic, user.customerName, user.userId,
user.accounts.map { mapAccount(it) },
user.selectedTanMethod?.securityFunction?.code, user.tanMethods.map { mapTanMethod(it) },
user.selectedTanMedium?.mediumName, user.tanMedia.map { mapTanMedium(it) },
bank?.bankingGroup ?: getBankingGroup(user.bankName, user.bic),
user.finTsServerAddress
getBankingGroup(user.bankName, user.bic)
)
protected open fun getBankingGroup(bankName: String, bic: String): BankingGroup? =
@ -153,12 +129,12 @@ open class FinTs4kMapper {
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.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
account.productName, account.currency, account.accountLimit, mapAmount(account.balance),
account.isAccountTypeSupportedByApplication, mapFeatures(account),
mapAmount(account.balance),
account.serverTransactionsRetentionDays,
account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom,
// TODO: map haveAllTransactionsBeenRetrieved
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
bookedTransactions = mapBookedTransactions(account).toMutableList()
)
@ -176,7 +152,7 @@ open class FinTs4kMapper {
add(BankAccountFeatures.TransferMoney)
}
if (account.supportsInstantPayment) {
add(BankAccountFeatures.InstantTransfer)
add(BankAccountFeatures.InstantPayment)
}
}
@ -185,28 +161,25 @@ open class FinTs4kMapper {
account.bookedTransactions.map { mapTransaction(it) }
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.reference,
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
transaction.bookingDate, transaction.valueDate,
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
transaction.postingText,
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
transaction.bookingText, null,
transaction.statementNumber, transaction.sequenceNumber,
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
transaction.statementNumber, transaction.sheetNumber,
transaction.customerReference, transaction.bankReference,
transaction.furtherInformation,
transaction.endToEndReference, transaction.mandateReference,
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference,
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
transaction.compensationAmount, transaction.originalAmount,
transaction.sepaReference,
transaction.deviantOriginator, transaction.deviantRecipient,
transaction.referenceWithNoSpecialType,
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
transaction.journalNumber, transaction.textKeyAddition,
transaction.currencyType, transaction.bookingKey,
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution,
transaction.supplementaryDetails,
transaction.orderReferenceNumber, transaction.referenceNumber,
transaction.isReversal
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
)
protected open fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
@ -231,11 +204,7 @@ open class FinTs4kMapper {
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, selectedTanMethodId, tanMethods, selectedTanMediumName, tanMedia, tanImage, flickerCode, user, account, challenge.tanExpirationTime, challenge.challengeCreationTimestamp) {
override fun addTanExpiredCallback(callback: () -> Unit) {
challenge.addTanExpiredCallback(callback)
}
return object : TanChallenge(type, action, challenge.messageToShowToUser, selectedTanMethodId, tanMethods, selectedTanMediumName, tanMedia, tanImage, flickerCode, user, account) {
override fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
challenge.addUserApprovedDecoupledTanCallback(callback)
}
@ -251,7 +220,7 @@ open class FinTs4kMapper {
protected open fun mapActionRequiringTan(action: net.codinux.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
ActionRequiringTan.valueOf(action.name)
open fun mapTanMethod(method: net.codinux.banking.fints.model.TanMethod): TanMethod = TanMethod(
protected open fun mapTanMethod(method: net.codinux.banking.fints.model.TanMethod): TanMethod = TanMethod(
method.displayName, mapTanMethodType(method.type), method.securityFunction.code, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
)
@ -301,27 +270,7 @@ open class FinTs4kMapper {
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) }
)
open fun mapTransferMoneyResponse(response: net.dankito.banking.client.model.response.TransferMoneyResponse): Response<TransferMoneyResponse> =
if (response.successful) {
Response.success(TransferMoneyResponse())
} else {
mapError(response)
}
open fun mapToMoney(amount: Amount, currency: String): Money = Money(amount.amount, currency)
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.FinTsClientResponse): Response<T> {
protected open 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())

View File

@ -3,9 +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 kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@Ignore
class FinTs4kBankingClientTest {
companion object {