Compare commits
34 Commits
bd052587c5
...
c5b7967ce1
Author | SHA1 | Date |
---|---|---|
dankito | c5b7967ce1 | |
dankito | 99c864bcf1 | |
dankito | 97604d59c9 | |
dankito | aa7b7afaf0 | |
dankito | 73b760ae68 | |
dankito | 1f8fecca7c | |
dankito | 384ab2fd9e | |
dankito | 432e5016a9 | |
dankito | 705740a0d0 | |
dankito | d8499b4ce2 | |
dankito | 8f5024b169 | |
dankito | 4298fc9e40 | |
dankito | 4acb58b571 | |
dankito | 95b37f0cb8 | |
dankito | 2c2db147b4 | |
dankito | e6aec071a2 | |
dankito | 376cb08a9f | |
dankito | 288af22ac6 | |
dankito | 713bfa4b50 | |
dankito | adf8cfa750 | |
dankito | 783675d82a | |
dankito | a680b6534c | |
dankito | cb45c181ae | |
dankito | c35026bfcc | |
dankito | 675066c216 | |
dankito | d48b708a97 | |
dankito | 4153afa814 | |
dankito | ccf38e2c07 | |
dankito | a6caa40267 | |
dankito | 55767f88e4 | |
dankito | 4c860e7b20 | |
dankito | 9512db3402 | |
dankito | 7353b0347e | |
dankito | 1e5c83c369 |
|
@ -3,9 +3,11 @@ 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 {
|
||||
|
||||
|
@ -36,7 +38,16 @@ 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: UserAccount, accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
|
||||
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>
|
||||
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
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 {
|
||||
|
||||
|
@ -37,6 +40,11 @@ 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(): Response<List<GetTransactionsResponse>>
|
||||
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>
|
||||
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.client.model.UserAccount
|
||||
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.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
|
||||
|
||||
|
@ -12,7 +16,7 @@ abstract class BankingClientForUserBase(
|
|||
protected val client: BankingClient
|
||||
) : BankingClientForUser {
|
||||
|
||||
private lateinit var user: UserAccount
|
||||
private lateinit var user: User
|
||||
|
||||
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
||||
client.getAccountDataAsync(GetAccountDataRequest(credentials, options)).also {
|
||||
|
@ -21,7 +25,14 @@ abstract class BankingClientForUserBase(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAccountTransactionsAsync(): Response<List<GetTransactionsResponse>> =
|
||||
client.updateAccountTransactionsAsync(user)
|
||||
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))
|
||||
|
||||
}
|
|
@ -1,32 +1,58 @@
|
|||
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.UserAccount
|
||||
import net.codinux.banking.client.model.User
|
||||
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 {
|
||||
this@getAccountData.getAccountDataAsync(bankCode, loginName, password)
|
||||
getAccountDataAsync(bankCode, loginName, password)
|
||||
}
|
||||
|
||||
fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(request)
|
||||
getAccountDataAsync(request)
|
||||
}
|
||||
|
||||
fun BankingClient.updateAccountTransactions(user: UserAccount, accounts: List<BankAccount>? = null) = runBlocking {
|
||||
this@updateAccountTransactions.updateAccountTransactionsAsync(user, accounts)
|
||||
fun BankingClient.updateAccountTransactions(user: User, accounts: List<BankAccount>? = null) = runBlocking {
|
||||
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 {
|
||||
this@getAccountData.getAccountDataAsync()
|
||||
getAccountDataAsync()
|
||||
}
|
||||
|
||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(options)
|
||||
getAccountDataAsync(options)
|
||||
}
|
||||
|
||||
fun BankingClientForUser.updateAccountTransactions() = runBlocking {
|
||||
this@updateAccountTransactions.updateAccountTransactionsAsync()
|
||||
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)
|
||||
}
|
|
@ -5,9 +5,9 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
|
||||
@NoArgConstructor
|
||||
open class AccountTransaction(
|
||||
val amount: Amount = Amount.Zero,
|
||||
val amount: Amount = Amount.Zero, // TODO: a string is really bad in UI, find a better solution
|
||||
val currency: String,
|
||||
val unparsedReference: String, // Alternative: purpose (or Remittance information)
|
||||
val reference: String?, // Alternative: Remittance information, Transaction description, (payment) purpose, payment reference
|
||||
|
||||
/**
|
||||
* Transaction date (Buchungstag) - der Tag, an dem ein Zahlungsvorgang in das System einer Bank eingegangen ist.
|
||||
|
@ -22,52 +22,115 @@ 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
|
||||
val otherPartyBankCode: String? = null,
|
||||
/**
|
||||
* 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 otherPartyAccountId: String? = null,
|
||||
|
||||
val bookingText: String? = null,
|
||||
val information: String? = null,
|
||||
|
||||
val statementNumber: Int? = null,
|
||||
val sequenceNumber: Int? = null,
|
||||
/**
|
||||
* Buchungstext, z. B. DAUERAUFTRAG, BARGELDAUSZAHLUNG, ONLINE-UEBERWEISUNG, FOLGELASTSCHRIFT, ...
|
||||
*/
|
||||
val postingText: String? = null,
|
||||
|
||||
val openingBalance: Amount? = null,
|
||||
val closingBalance: Amount? = null,
|
||||
|
||||
val endToEndReference: String? = 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 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,
|
||||
val sepaReference: String? = null,
|
||||
/**
|
||||
* Abweichender Überweisender oder Zahlungsempfänger
|
||||
*/
|
||||
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,
|
||||
|
||||
val currencyType: String? = null,
|
||||
val bookingKey: String? = null,
|
||||
val referenceForTheAccountOwner: String? = null,
|
||||
val referenceOfTheAccountServicingInstitution: String? = null,
|
||||
val supplementaryDetails: 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 transactionReferenceNumber: String? = null,
|
||||
val relatedReferenceNumber: 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,
|
||||
|
||||
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 $unparsedReference $sepaReference $otherPartyName $otherPartyBankCode $otherPartyAccountId"
|
||||
"$amount $currency $bookingDate $valueDate $reference $otherPartyName $otherPartyBankId $otherPartyAccountId"
|
||||
}
|
||||
|
||||
override fun toString() = "${valueDate.dayOfMonth}.${valueDate.monthNumber}.${valueDate.year} ${amount.toString().padStart(4, ' ')} ${if (currency == "EUR") "€" else currency} ${otherPartyName ?: ""} - $reference"
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.*
|
||||
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 iban: String? = null,
|
||||
val subAccountNumber: String? = null,
|
||||
val productName: String? = null,
|
||||
val currency: String = "EUR",
|
||||
val currency: String = DefaultValues.DefaultCurrency,
|
||||
var accountLimit: String? = null,
|
||||
|
||||
// var balance: BigDecimal = BigDecimal.ZERO,
|
||||
var balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
|
||||
|
||||
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,
|
||||
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 unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
|
||||
open val prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
||||
|
||||
var userSetDisplayName: String? = null,
|
||||
var displayIndex: Int = 0,
|
||||
|
@ -41,8 +39,40 @@ open class BankAccount(
|
|||
open val displayName: String
|
||||
get() = userSetDisplayName ?: productName ?: identifier
|
||||
|
||||
fun supportsAnyFeature(vararg features: BankAccountFeatures): Boolean =
|
||||
@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 =
|
||||
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,
|
||||
InstantPayment
|
||||
InstantTransfer
|
||||
}
|
|
@ -8,5 +8,7 @@ open class BankAccountIdentifier(
|
|||
val subAccountNumber: String?,
|
||||
val iban: String?
|
||||
) {
|
||||
constructor(account: BankAccount) : this(account.identifier, account.subAccountNumber, account.iban)
|
||||
|
||||
override fun toString() = "$identifier, $iban"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
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"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
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,5 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
object DefaultValues {
|
||||
const val DefaultCurrency = "EUR"
|
||||
}
|
|
@ -3,5 +3,5 @@ package net.codinux.banking.client.model
|
|||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class UnbookedAccountTransaction {
|
||||
open class PrebookedAccountTransaction {
|
||||
}
|
|
@ -7,7 +7,7 @@ import net.codinux.banking.client.model.tan.TanMethod
|
|||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class UserAccount(
|
||||
open class User(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
/**
|
||||
|
@ -19,7 +19,17 @@ open class UserAccount(
|
|||
val bic: String,
|
||||
|
||||
val customerName: String,
|
||||
val userId: String = loginName,
|
||||
/**
|
||||
* 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,
|
||||
|
||||
open val accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
|
@ -29,7 +39,7 @@ open class UserAccount(
|
|||
* 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 selectedTanMethodId: String? = null,
|
||||
val selectedTanMethodIdentifier: String? = null,
|
||||
open val tanMethods: List<TanMethod> = listOf(),
|
||||
|
||||
/**
|
||||
|
@ -38,18 +48,20 @@ open class UserAccount(
|
|||
* 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 selectedTanMediumName: String? = null,
|
||||
val selectedTanMediumIdentifier: String? = null,
|
||||
open val tanMedia: List<TanMedium> = listOf(),
|
||||
|
||||
var bankingGroup: BankingGroup? = null,
|
||||
var iconUrl: String? = null,
|
||||
open var serverAddress: 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
|
||||
|
@ -58,11 +70,11 @@ open class UserAccount(
|
|||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMethod: TanMethod
|
||||
get() = tanMethods.first { it.identifier == selectedTanMethodId }
|
||||
get() = tanMethods.first { it.identifier == selectedTanMethodIdentifier }
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMedium: TanMedium?
|
||||
get() = tanMedia.firstOrNull { it.mediumName == selectedTanMediumName }
|
||||
get() = tanMedia.firstOrNull { it.identifier == selectedTanMediumIdentifier }
|
||||
|
||||
|
||||
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"
|
|
@ -1,17 +0,0 @@
|
|||
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"
|
||||
}
|
|
@ -36,6 +36,8 @@ 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
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
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)
|
||||
open class GetAccountDataRequest(bankCode: String, loginName: String, password: String, val options: GetAccountDataOptions? = null, val bankInfo: BankInfo? = null)
|
||||
: AccountCredentials(bankCode, loginName, password) {
|
||||
|
||||
constructor(credentials: AccountCredentials, options: GetAccountDataOptions? = null)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
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"
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
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()}"
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.UserAccount
|
||||
import net.codinux.banking.client.model.User
|
||||
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: UserAccount
|
||||
val user: User
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
|
|
|
@ -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.UnbookedAccountTransaction
|
||||
import net.codinux.banking.client.model.PrebookedAccountTransaction
|
||||
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 unbookedTransactions: List<UnbookedAccountTransaction>,
|
||||
val prebookedTransactions: List<PrebookedAccountTransaction>,
|
||||
val transactionsRetrievalTime: Instant,
|
||||
val retrievedTransactionsFrom: LocalDate? = null,
|
||||
val retrievedTransactionsTo: LocalDate? = null
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
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
|
|
@ -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
|
||||
)
|
|
@ -1,8 +1,10 @@
|
|||
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.UserAccount
|
||||
import net.codinux.banking.client.model.UserAccountViewInfo
|
||||
import net.codinux.banking.client.model.User
|
||||
import net.codinux.banking.client.model.BankViewInfo
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
|
@ -24,7 +26,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
|
||||
* [UserAccount] may contains an outdated list of available TanMethods.
|
||||
* [User] 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.
|
||||
*/
|
||||
|
@ -41,8 +43,15 @@ open class TanChallenge(
|
|||
|
||||
open val tanImage: TanImage? = null,
|
||||
open val flickerCode: FlickerCode? = null,
|
||||
open val user: UserAccountViewInfo,
|
||||
open val account: BankAccountViewInfo? = 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()
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
|
@ -55,7 +64,17 @@ open class TanChallenge(
|
|||
|
||||
|
||||
/**
|
||||
* Principally a no-op method, not implemented for all client, only implementing client for not: FinTs4jBankingClient.
|
||||
* 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.
|
||||
|
|
|
@ -53,7 +53,7 @@ open class TanMedium(
|
|||
|
||||
val displayName: String by lazy {
|
||||
identifier + " " + when (status) {
|
||||
TanMediumStatus.Used -> "Aktive"
|
||||
TanMediumStatus.Used -> "Aktiv"
|
||||
TanMediumStatus.Available -> "Verfügbar"
|
||||
TanMediumStatus.ActiveFollowUpCard -> " Folgekarte, aktiv bei erster Nutzung"
|
||||
TanMediumStatus.AvailableFollowUpCard -> " Folgekarte, die erst aktiviert werden muss"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
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,
|
||||
|
@ -10,5 +12,10 @@ 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})"
|
||||
}
|
|
@ -43,6 +43,8 @@ enum class TanMethodType {
|
|||
addAll(listOf(TanMethodType.ChipTanManuell)) // this is quite inconvenient for user, so i added it as last
|
||||
}
|
||||
|
||||
val TanMethodsNotSupportedByMostApplications = listOf(TanMethodType.ChipTanUsb)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,8 @@ 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()
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ 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.UserAccount
|
||||
import net.codinux.banking.client.model.User
|
||||
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
|
||||
|
@ -20,18 +21,18 @@ open class FinTs4kBankingClient(
|
|||
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
|
||||
|
||||
|
||||
protected val mapper = FinTs4kMapper()
|
||||
protected open val mapper = FinTs4kMapper()
|
||||
|
||||
protected val client = FinTsClient(config, BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
protected open 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)
|
||||
return mapper.map(response, request.bankInfo)
|
||||
}
|
||||
|
||||
override suspend fun updateAccountTransactionsAsync(user: UserAccount, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
|
||||
override suspend fun updateAccountTransactionsAsync(user: User, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
|
||||
val accountsToRequest = (accounts ?: user.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveBalance) }
|
||||
|
||||
if (accountsToRequest.isNotEmpty()) {
|
||||
|
@ -55,4 +56,11 @@ 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)
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,8 @@ 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
|
||||
|
@ -20,12 +22,14 @@ 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
|
||||
|
||||
|
@ -36,41 +40,59 @@ open class FinTs4kMapper {
|
|||
protected val bankingGroupMapper = BankingGroupMapper()
|
||||
|
||||
|
||||
open fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
credentials.bankCode, credentials.loginName, credentials.password,
|
||||
options.accounts.map { BankAccountIdentifierImpl(it.identifier, it.subAccountNumber, it.iban) },
|
||||
open fun mapToGetAccountDataParameter(request: GetAccountDataRequest, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
request.bankCode, request.loginName, request.password,
|
||||
options.accounts.map { mapBankAccountIdentifier(it) },
|
||||
options.retrieveBalance,
|
||||
RetrieveTransactions.valueOf(options.retrieveTransactions.name), options.retrieveTransactionsFrom, options.retrieveTransactionsTo,
|
||||
preferredTanMethods = options.preferredTanMethods?.map { mapTanMethodType(it) },
|
||||
abortIfTanIsRequired = options.abortIfTanIsRequired
|
||||
tanMethodsNotSupportedByApplication = options.tanMethodsNotSupportedByApplication.map { mapTanMethodType(it) },
|
||||
abortIfTanIsRequired = options.abortIfTanIsRequired,
|
||||
defaultBankValues = request.bankInfo?.let { mapToBankData(request, it) }
|
||||
)
|
||||
|
||||
open fun mapToUpdateAccountTransactionsParameter(user: UserAccount, account: BankAccount, finTsModel: BankData?): GetAccountDataParameter {
|
||||
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()
|
||||
|
||||
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.OfLast90Days
|
||||
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.valueOf(defaults.retrieveTransactions.name)
|
||||
// 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 = emptyList<net.codinux.banking.fints.model.TanMethodType>()
|
||||
val preferredTanMethods = defaults.preferredTanMethods?.map { mapTanMethodType(it) }
|
||||
|
||||
return GetAccountDataParameter(user.bankCode, user.loginName, user.password!!, listOf(accountIdentifier), true,
|
||||
retrieveTransactions, from,
|
||||
preferredTanMethods = preferredTanMethods,
|
||||
preferredTanMedium = user.selectedTanMediumName,
|
||||
preferredTanMedium = user.selectedTanMediumIdentifier,
|
||||
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): Response<GetAccountDataResponse> {
|
||||
return if (response.successful && response.customerAccount != null) {
|
||||
Response.success(GetAccountDataResponse(mapUser(response.customerAccount!!)))
|
||||
|
||||
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)))
|
||||
} 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
|
||||
|
@ -102,7 +124,7 @@ open class FinTs4kMapper {
|
|||
}
|
||||
|
||||
|
||||
open fun mapToUserAccountViewInfo(bank: BankData): UserAccountViewInfo = UserAccountViewInfo(
|
||||
open fun mapToUserAccountViewInfo(bank: BankData): BankViewInfo = BankViewInfo(
|
||||
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
|
||||
)
|
||||
|
||||
|
@ -113,15 +135,17 @@ open class FinTs4kMapper {
|
|||
)
|
||||
|
||||
|
||||
protected open fun mapUser(user: net.dankito.banking.client.model.CustomerAccount) = UserAccount(
|
||||
protected open fun mapUser(user: net.dankito.banking.client.model.CustomerAccount, bank: BankInfo? = null) = User(
|
||||
user.bankCode, user.loginName, user.password,
|
||||
user.bankName, user.bic, user.customerName, user.userId,
|
||||
user.accounts.map { mapAccount(it) },
|
||||
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.selectedTanMethod?.securityFunction?.code, user.tanMethods.map { mapTanMethod(it) },
|
||||
user.selectedTanMedium?.mediumName, user.tanMedia.map { mapTanMedium(it) },
|
||||
|
||||
getBankingGroup(user.bankName, user.bic)
|
||||
bank?.bankingGroup ?: getBankingGroup(user.bankName, user.bic),
|
||||
user.finTsServerAddress
|
||||
)
|
||||
|
||||
protected open fun getBankingGroup(bankName: String, bic: String): BankingGroup? =
|
||||
|
@ -129,12 +153,12 @@ open class FinTs4kMapper {
|
|||
|
||||
|
||||
protected open fun mapAccount(account: net.dankito.banking.client.model.BankAccount) = BankAccount(
|
||||
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
|
||||
account.productName, account.currency, account.accountLimit, mapAmount(account.balance),
|
||||
account.identifier, account.subAccountNumber, account.iban, account.productName, account.accountHolderName,
|
||||
mapAccountType(account.type), account.currency, account.accountLimit,
|
||||
account.isAccountTypeSupportedByApplication, mapFeatures(account),
|
||||
mapAmount(account.balance),
|
||||
account.serverTransactionsRetentionDays,
|
||||
account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom,
|
||||
// TODO: map haveAllTransactionsBeenRetrieved
|
||||
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
||||
bookedTransactions = mapBookedTransactions(account).toMutableList()
|
||||
)
|
||||
|
||||
|
@ -152,7 +176,7 @@ open class FinTs4kMapper {
|
|||
add(BankAccountFeatures.TransferMoney)
|
||||
}
|
||||
if (account.supportsInstantPayment) {
|
||||
add(BankAccountFeatures.InstantPayment)
|
||||
add(BankAccountFeatures.InstantTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,25 +185,28 @@ 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.unparsedReference,
|
||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.reference,
|
||||
transaction.bookingDate, transaction.valueDate,
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
|
||||
transaction.bookingText, null,
|
||||
transaction.statementNumber, transaction.sequenceNumber,
|
||||
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
||||
|
||||
transaction.postingText,
|
||||
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
|
||||
|
||||
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference,
|
||||
transaction.statementNumber, transaction.sheetNumber,
|
||||
|
||||
transaction.customerReference, transaction.bankReference,
|
||||
transaction.furtherInformation,
|
||||
|
||||
transaction.endToEndReference, transaction.mandateReference,
|
||||
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||
transaction.compensationAmount, transaction.originalAmount,
|
||||
transaction.sepaReference,
|
||||
transaction.deviantOriginator, transaction.deviantRecipient,
|
||||
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
|
||||
transaction.referenceWithNoSpecialType,
|
||||
|
||||
transaction.currencyType, transaction.bookingKey,
|
||||
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution,
|
||||
transaction.supplementaryDetails,
|
||||
transaction.journalNumber, transaction.textKeyAddition,
|
||||
|
||||
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
|
||||
transaction.orderReferenceNumber, transaction.referenceNumber,
|
||||
transaction.isReversal
|
||||
)
|
||||
|
||||
protected open fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
|
||||
|
@ -204,7 +231,11 @@ 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) {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
|
||||
challenge.addUserApprovedDecoupledTanCallback(callback)
|
||||
}
|
||||
|
@ -220,7 +251,7 @@ open class FinTs4kMapper {
|
|||
protected open fun mapActionRequiringTan(action: net.codinux.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||
ActionRequiringTan.valueOf(action.name)
|
||||
|
||||
protected open fun mapTanMethod(method: net.codinux.banking.fints.model.TanMethod): TanMethod = TanMethod(
|
||||
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)
|
||||
)
|
||||
|
||||
|
@ -270,7 +301,27 @@ open class FinTs4kMapper {
|
|||
FlickerCode(flickerCode.challengeHHD_UC, flickerCode.parsedDataSet, mapException(flickerCode.decodingError))
|
||||
|
||||
|
||||
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
|
||||
/* 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> {
|
||||
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())
|
||||
|
|
|
@ -3,11 +3,9 @@ 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.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
@Ignore
|
||||
class FinTs4kBankingClientTest {
|
||||
|
||||
companion object {
|
||||
|
|
Loading…
Reference in New Issue