Compare commits
24 Commits
b35f1146a5
...
62276e2a02
Author | SHA1 | Date |
---|---|---|
dankito | 62276e2a02 | |
dankito | 7f407dd8b9 | |
dankito | cf50d04ba0 | |
dankito | afade80bcb | |
dankito | a983df42b1 | |
dankito | 72889aeeed | |
dankito | 07926ef0c1 | |
dankito | 748b4cf98d | |
dankito | 79e45f0c02 | |
dankito | 5a2018fd97 | |
dankito | b65658910c | |
dankito | 37c575a1af | |
dankito | f54c03b7bf | |
dankito | 046a0c90eb | |
dankito | 5ff2714512 | |
dankito | ea1c184abc | |
dankito | 68dabf9c68 | |
dankito | 5e77c50ff7 | |
dankito | dcfd3ffc87 | |
dankito | 89846a62f2 | |
dankito | ea6c4000f9 | |
dankito | 1a484e78b8 | |
dankito | 1abf4bc389 | |
dankito | b198706e26 |
|
@ -53,6 +53,7 @@ kotlin {
|
|||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
|
|
@ -1,14 +1,33 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BankingClient {
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, use the method overload that takes a
|
||||
* [GetAccountDataRequest] parameter and set [RetrieveTransactions] and may to and from date in its options object.
|
||||
*/
|
||||
suspend fun getAccountDataAsync(bankCode: String, loginName: String, password: String) =
|
||||
getAccountDataAsync(GetAccountDataRequest(bankCode, loginName, password))
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, set [RetrieveTransactions] and may to and from
|
||||
* date in [GetAccountDataRequest.options].
|
||||
*/
|
||||
suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse>
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
|
@ -7,4 +8,11 @@ interface BankingClientCallback {
|
|||
|
||||
fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
|
||||
|
||||
/**
|
||||
* An optional method, for sure not available for all client implementations (currently only for FinTs4kBankingClient).
|
||||
*
|
||||
* Gets fired when a FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
|
||||
*/
|
||||
fun messageLogAdded(messageLogEntry: MessageLogEntry)
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BankingClientForCustomer {
|
||||
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
suspend fun getAccountDataAsync() = getAccountDataAsync(GetAccountDataOptions())
|
||||
|
||||
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BankingClientForUser {
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, use the method overload that takes a
|
||||
* [GetAccountDataOptions] parameter and set [RetrieveTransactions] and may to and from date.
|
||||
*/
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
suspend fun getAccountDataAsync() = getAccountDataAsync(GetAccountDataOptions())
|
||||
|
||||
/**
|
||||
* Retrieves account data like customer name, her bank accounts, their balance and account transactions.
|
||||
*
|
||||
* By default the account transactions of the last 90 days are retrieved (which in most cases doesn't require a
|
||||
* TAN according to PSD2).
|
||||
*
|
||||
* If you like to retrieve the transactions of a different period, set [GetAccountDataOptions.retrieveTransactions]
|
||||
* and may [GetAccountDataOptions.retrieveTransactionsFrom] and [GetAccountDataOptions.retrieveTransactionsTo].
|
||||
*/
|
||||
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
||||
|
||||
}
|
|
@ -4,10 +4,10 @@ import net.codinux.banking.client.model.AccountCredentials
|
|||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
|
||||
abstract class BankingClientForCustomerBase(
|
||||
abstract class BankingClientForUserBase(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val client: BankingClient
|
||||
) : BankingClientForCustomer {
|
||||
) : BankingClientForUser {
|
||||
|
||||
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
||||
client.getAccountDataAsync(GetAccountDataRequest(credentials, options))
|
|
@ -4,7 +4,7 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
|
|||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
|
||||
interface BlockingBankingClientForCustomer {
|
||||
interface BlockingBankingClientForUser {
|
||||
|
||||
// for languages not supporting default parameters (Java, Swift, JS, ...)
|
||||
fun getAccountData() = getAccountData(GetAccountDataOptions())
|
|
@ -4,10 +4,10 @@ import net.codinux.banking.client.model.AccountCredentials
|
|||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
|
||||
abstract class BlockingBankingClientForCustomerBase(
|
||||
abstract class BlockingBankingClientForUserBase(
|
||||
protected val credentials: AccountCredentials,
|
||||
protected val client: BlockingBankingClient
|
||||
) : BlockingBankingClientForCustomer {
|
||||
) : BlockingBankingClientForUser {
|
||||
|
||||
override fun getAccountData(options: GetAccountDataOptions) =
|
||||
client.getAccountData(GetAccountDataRequest(credentials, options))
|
|
@ -1,9 +1,11 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.MessageLogEntry
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
open class SimpleBankingClientCallback(
|
||||
protected val messageLogAdded: ((MessageLogEntry) -> Unit)? = null,
|
||||
protected val enterTan: ((tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) -> Unit)? = null
|
||||
) : BankingClientCallback {
|
||||
|
||||
|
@ -15,4 +17,8 @@ open class SimpleBankingClientCallback(
|
|||
}
|
||||
}
|
||||
|
||||
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
|
||||
messageLogAdded?.invoke(messageLogEntry)
|
||||
}
|
||||
|
||||
}
|
|
@ -12,10 +12,10 @@ fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
|||
this@getAccountData.getAccountDataAsync(request)
|
||||
}
|
||||
|
||||
fun BankingClientForCustomer.getAccountData() = runBlocking {
|
||||
fun BankingClientForUser.getAccountData() = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync()
|
||||
}
|
||||
|
||||
fun BankingClientForCustomer.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(options)
|
||||
}
|
|
@ -12,10 +12,10 @@ fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
|||
this@getAccountData.getAccountDataAsync(request)
|
||||
}
|
||||
|
||||
fun BankingClientForCustomer.getAccountData() = runBlocking {
|
||||
fun BankingClientForUser.getAccountData() = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync()
|
||||
}
|
||||
|
||||
fun BankingClientForCustomer.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||
this@getAccountData.getAccountDataAsync(options)
|
||||
}
|
|
@ -63,6 +63,7 @@ kotlin {
|
|||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
|
|
@ -59,6 +59,7 @@ open class AccountTransaction(
|
|||
val relatedReferenceNumber: String? = null,
|
||||
|
||||
var userSetDisplayName: String? = null,
|
||||
var category: String? = null,
|
||||
var notes: String? = null,
|
||||
) {
|
||||
override fun toString() = "${valueDate.dayOfMonth}.${valueDate.monthNumber}.${valueDate.year} ${amount.toString().padStart(4, ' ')} ${if (currency == "EUR") "€" else currency} ${otherPartyName ?: ""} - $reference"
|
||||
|
|
|
@ -25,8 +25,8 @@ open class BankAccount(
|
|||
var haveAllTransactionsBeenRetrieved: Boolean = false,
|
||||
val countDaysForWhichTransactionsAreKept: Int? = null,
|
||||
|
||||
val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
||||
val unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
|
||||
open val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
||||
open val unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
|
||||
|
||||
var userSetDisplayName: String? = null,
|
||||
var displayIndex: Int = 0,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class BankAccountIdentifier(
|
||||
val identifier: String,
|
||||
val subAccountNumber: String?,
|
||||
val iban: String?
|
||||
) {
|
||||
override fun toString() = "$identifier, $iban"
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class CustomerAccount(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
/**
|
||||
* User may decides to not save password .
|
||||
*/
|
||||
var password: String?,
|
||||
|
||||
val bankName: String,
|
||||
val bic: String,
|
||||
|
||||
val customerName: String,
|
||||
val userId: String = loginName,
|
||||
|
||||
val accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
var bankingGroup: BankingGroup? = null,
|
||||
var iconUrl: String? = null,
|
||||
) {
|
||||
|
||||
var wrongCredentialsEntered: Boolean = false
|
||||
|
||||
var userSetDisplayName: String? = null
|
||||
var displayIndex: Int = 0
|
||||
|
||||
|
||||
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
/**
|
||||
* Contains only the basic info of a [CustomerAccount], just enough that a client application can display it to the user
|
||||
* and the user knows exactly which [CustomerAccount] is meant / referred.
|
||||
*/
|
||||
@NoArgConstructor
|
||||
open class CustomerAccountViewInfo(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
val bankName: String
|
||||
) {
|
||||
override fun toString() = "$bankCode $bankName $loginName"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
open class MessageLogEntry(
|
||||
open val type: MessageLogEntryType,
|
||||
open val message: String,
|
||||
open val messageTrace: String? = null,
|
||||
open val error: Throwable? = null,
|
||||
open val time: Instant = Clock.System.now()
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
enum class MessageLogEntryType {
|
||||
Sent,
|
||||
|
||||
Received,
|
||||
|
||||
Error
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package net.codinux.banking.client.model
|
||||
|
||||
import net.codinux.banking.client.model.config.JsonIgnore
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMedium
|
||||
import net.codinux.banking.client.model.tan.TanMethod
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class UserAccount(
|
||||
val bankCode: String,
|
||||
var loginName: String,
|
||||
/**
|
||||
* User may decides to not save password .
|
||||
*/
|
||||
var password: String?,
|
||||
|
||||
val bankName: String,
|
||||
val bic: String,
|
||||
|
||||
val customerName: String,
|
||||
val userId: String = loginName,
|
||||
|
||||
open val accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMethod.
|
||||
*
|
||||
* As [tanMethods] also contains selected TanMethod, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMethod] to get selected TanMethod or iterate over [tanMethods] and filter selected one by this id.
|
||||
*/
|
||||
val selectedTanMethodId: String? = null,
|
||||
open val tanMethods: List<TanMethod> = listOf(),
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMedium.
|
||||
*
|
||||
* As [tanMedia] also contains selected TanMedium, we didn't want to duplicate this object. Use [selectedTanMedium]
|
||||
* to get selected TanMedium or iterate over [tanMedia] and filter selected one by this medium name.
|
||||
*/
|
||||
val selectedTanMediumName: String? = null,
|
||||
open val tanMedia: List<TanMedium> = listOf(),
|
||||
|
||||
var bankingGroup: BankingGroup? = null,
|
||||
var iconUrl: String? = null,
|
||||
) {
|
||||
|
||||
var wrongCredentialsEntered: Boolean = false
|
||||
|
||||
var userSetDisplayName: String? = null
|
||||
var displayIndex: Int = 0
|
||||
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMethod: TanMethod
|
||||
get() = tanMethods.first { it.identifier == selectedTanMethodId }
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMedium: TanMedium?
|
||||
get() = tanMedia.firstOrNull { it.mediumName == selectedTanMediumName }
|
||||
|
||||
|
||||
override fun toString() = "$bankName $loginName, ${accounts.size} accounts"
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -1,15 +1,45 @@
|
|||
package net.codinux.banking.client.model.options
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.BankAccountIdentifier
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
|
||||
@NoArgConstructor
|
||||
open class GetAccountDataOptions(
|
||||
val retrieveBalance: Boolean = true,
|
||||
val retrieveTransactions: RetrieveTransactions = RetrieveTransactions.OfLast90Days,
|
||||
val retrieveTransactionsFrom: LocalDate? = null,
|
||||
val retrieveTransactionsTo: LocalDate? = null,
|
||||
val abortIfTanIsRequired: Boolean = false
|
||||
|
||||
val retrieveBalance: Boolean = true,
|
||||
|
||||
/**
|
||||
* Account(s) may should be excluded from data retrieval, so this option enabled to set for which accounts data
|
||||
* should be retrieved.
|
||||
*/
|
||||
val accounts: List<BankAccountIdentifier> = emptyList(),
|
||||
|
||||
/**
|
||||
* Specifies which [TanMethodType] should be preferred when having to choose between multiple available for user
|
||||
* without requesting the user to choose one.
|
||||
*
|
||||
* By default we don't ask the user which TanMethod she prefers but choose one that could match best. If she really
|
||||
* likes to use a different one, she can select another one in EnterTanDialog.
|
||||
*
|
||||
* By default we prefer non visual TanMethods (like AppTan and SMS) over image based TanMethods (like QR-code and
|
||||
* photoTan) and exclude ChipTanUsb, which is not supported by application, and Flickercode, which is hard to
|
||||
* implement and therefore most applications have not implemented.
|
||||
*
|
||||
* Console apps can only handle non visual TanMethods.
|
||||
* But also graphical applications prefer non visual TanMethods as then they only have to display a text field to input
|
||||
* TAN, and then image based TanMethods as then they additionally only have to display an image.
|
||||
*/
|
||||
val preferredTanMethods: List<TanMethodType>? = TanMethodType.NonVisualOrImageBased,
|
||||
|
||||
val abortIfTanIsRequired: Boolean = false,
|
||||
|
||||
// there's also the option preferredTanMedium, but can hardly find a use case for it as we
|
||||
// cannot know the TanMedium name upfront. In most cases there's only one TanMedium (per TanMethod) anyway.
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "retrieveBalance=$retrieveBalance, retrieveTransactions=$retrieveTransactions, abortIfTanIsRequired=$abortIfTanIsRequired"
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
package net.codinux.banking.client.model.response
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
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 customer: CustomerAccount
|
||||
val user: UserAccount
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
val bookedTransactions: List<AccountTransaction>
|
||||
get() = customer.accounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate }
|
||||
get() = user.accounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate }
|
||||
|
||||
|
||||
override fun toString() = customer.toString()
|
||||
override fun toString() = user.toString()
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import net.codinux.banking.client.model.BankAccountIdentifier
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class MobilePhoneTanMedium(
|
||||
val phoneNumber: String?
|
||||
val phoneNumber: String?,
|
||||
val concealedPhoneNumber: String? = null
|
||||
) {
|
||||
override fun toString() = phoneNumber ?: "No phone number"
|
||||
}
|
|
@ -1,24 +1,61 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import net.codinux.banking.client.model.BankAccountViewInfo
|
||||
import net.codinux.banking.client.model.CustomerAccountViewInfo
|
||||
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
|
||||
|
||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||
@NoArgConstructor
|
||||
open class TanChallenge(
|
||||
val type: TanChallengeType,
|
||||
val forAction: ActionRequiringTan,
|
||||
val messageToShowToUser: String,
|
||||
val tanMethod: TanMethod,
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMethod.
|
||||
*
|
||||
* As [availableTanMethods] also contains selected TanMethod, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMethod] to get selected TanMethod or iterate over [availableTanMethods] and filter selected one by this id.
|
||||
*/
|
||||
val selectedTanMethodId: String,
|
||||
/**
|
||||
* When adding an account, frontend has no UserAccount object in BankingClientCallback to know which TanMethods are
|
||||
* available for User.
|
||||
* Also on other calls to bank server, bank server may returned an updated list of available TanMethods, so that
|
||||
* [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.
|
||||
*/
|
||||
val availableTanMethods: List<TanMethod>,
|
||||
|
||||
/**
|
||||
* Identifier of selected TanMedium.
|
||||
*
|
||||
* As [availableTanMedia] also contains selected TanMedium, we didn't want to duplicate this object. Use
|
||||
* [selectedTanMedium] to get selected TanMedium or iterate over [availableTanMedia] and filter selected one by this medium name.
|
||||
*/
|
||||
val selectedTanMediumName: String? = null,
|
||||
val availableTanMedia: List<TanMedium> = emptyList(),
|
||||
|
||||
val tanImage: TanImage? = null,
|
||||
val flickerCode: FlickerCode? = null,
|
||||
val customer: CustomerAccountViewInfo,
|
||||
val user: UserAccountViewInfo,
|
||||
val account: BankAccountViewInfo? = null
|
||||
// TODO: add availableTanMethods, selectedTanMedium, availableTanMedia
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMethod: TanMethod
|
||||
get() = availableTanMethods.first { it.identifier == selectedTanMethodId }
|
||||
|
||||
@get:JsonIgnore
|
||||
val selectedTanMedium: TanMedium?
|
||||
get() = availableTanMedia.firstOrNull { it.mediumName == selectedTanMediumName }
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$tanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||
return "$selectedTanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||
TanChallengeType.EnterTan -> ""
|
||||
TanChallengeType.Image -> ", Image: $tanImage"
|
||||
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
@NoArgConstructor
|
||||
open class TanGeneratorTanMedium(
|
||||
val cardNumber: String
|
||||
val cardNumber: String,
|
||||
val cardSequenceNumber: String? = null,
|
||||
val cardType: Int? = null,
|
||||
val validFrom: LocalDate? = null,
|
||||
val validTo: LocalDate? = null
|
||||
) {
|
||||
override fun toString() = cardNumber
|
||||
}
|
|
@ -5,7 +5,7 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
@NoArgConstructor
|
||||
open class TanMedium(
|
||||
val type: TanMediumType,
|
||||
val displayName: String,
|
||||
val mediumName: String?,
|
||||
val status: TanMediumStatus,
|
||||
/**
|
||||
* Only set if [type] is [TanMediumType.TanGenerator].
|
||||
|
@ -16,5 +16,5 @@ open class TanMedium(
|
|||
*/
|
||||
val mobilePhone: MobilePhoneTanMedium? = null
|
||||
) {
|
||||
override fun toString() = "$displayName $status"
|
||||
override fun toString() = "$mediumName $status"
|
||||
}
|
|
@ -1,7 +1,25 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
enum class TanMediumStatus {
|
||||
|
||||
/**
|
||||
* Die Bank zeigt an, dass es eine TAN-Verifikation gegen dieses Medium vornimmt.
|
||||
*/
|
||||
Used,
|
||||
|
||||
Available
|
||||
/**
|
||||
* Das Medium kann genutzt werden, muss aber zuvor mit „TAN-Generator an- bzw. ummelden (HKTAU)“ aktiv gemeldet werden.
|
||||
*/
|
||||
Available,
|
||||
|
||||
/**
|
||||
* Mit der ersten Nutzung der Folgekarte wird die zur Zeit aktive Karte gesperrt.
|
||||
*/
|
||||
ActiveFollowUpCard,
|
||||
|
||||
/**
|
||||
* Das Medium kann mit dem Geschäftsvorfall „TAN-Medium an- bzw. ummelden (HKTAU)“ aktiv gemeldet werden.
|
||||
* Die aktuelle Karte kann dann nicht mehr genutzt werden.
|
||||
*/
|
||||
AvailableFollowUpCard
|
||||
}
|
|
@ -20,4 +20,21 @@ enum class TanMethodType {
|
|||
photoTan,
|
||||
|
||||
QrCode
|
||||
;
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
val NonVisual = listOf(TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.ChipTanManuell, TanMethodType.EnterTan)
|
||||
|
||||
val ImageBased = listOf(TanMethodType.QrCode, TanMethodType.ChipTanQrCode, TanMethodType.photoTan, TanMethodType.ChipTanPhotoTanMatrixCode)
|
||||
|
||||
val NonVisualOrImageBased = buildList {
|
||||
addAll(listOf(TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.EnterTan))
|
||||
addAll(ImageBased)
|
||||
addAll(listOf(TanMethodType.ChipTanManuell)) // this is quite inconvenient for user, so i added it at last
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -1,3 +1,5 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -1,3 +1,5 @@
|
|||
package net.codinux.banking.client.model.config
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
actual annotation class JsonIgnore
|
|
@ -9,11 +9,6 @@ plugins {
|
|||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(8)
|
||||
|
||||
|
@ -54,12 +49,13 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
// wasmJs()
|
||||
// wasmJs() // ktor is not available for wasmJs yet
|
||||
|
||||
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
macosX64()
|
||||
|
@ -81,7 +77,7 @@ kotlin {
|
|||
dependencies {
|
||||
api(project(":BankingClient"))
|
||||
|
||||
api("net.codinux.banking:fints4k:1.0.0-Alpha-11")
|
||||
api("net.codinux.banking:fints4k:1.0.0-Alpha-12")
|
||||
|
||||
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||
import net.dankito.banking.fints.model.BankData
|
||||
import net.dankito.banking.fints.model.EnterTanGeneratorAtcResult
|
||||
import net.dankito.banking.fints.model.TanMethod
|
||||
import net.codinux.banking.client.model.MessageLogEntryType
|
||||
import net.codinux.banking.fints.callback.FinTsClientCallback
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||
import net.codinux.banking.fints.model.BankData
|
||||
import net.codinux.banking.fints.model.EnterTanGeneratorAtcResult
|
||||
import net.codinux.banking.fints.model.MessageLogEntry
|
||||
import net.codinux.banking.fints.model.TanMethod
|
||||
|
||||
open class BridgeFintTsToBankingClientCallback(
|
||||
protected val bankingClientCallback: BankingClientCallback,
|
||||
|
@ -16,7 +18,7 @@ open class BridgeFintTsToBankingClientCallback(
|
|||
return suggestedTanMethod
|
||||
}
|
||||
|
||||
override suspend fun enterTan(tanChallenge: net.dankito.banking.fints.model.TanChallenge) {
|
||||
override suspend fun enterTan(tanChallenge: net.codinux.banking.fints.model.TanChallenge) {
|
||||
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
|
||||
if (enterTanResult.enteredTan != null) {
|
||||
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
|
||||
|
@ -29,4 +31,14 @@ open class BridgeFintTsToBankingClientCallback(
|
|||
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
|
||||
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
|
||||
}
|
||||
|
||||
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
|
||||
val mapped = net.codinux.banking.client.model.MessageLogEntry(
|
||||
MessageLogEntryType.valueOf(messageLogEntry.type.name),
|
||||
messageLogEntry.message, messageLogEntry.messageTrace,
|
||||
messageLogEntry.error, messageLogEntry.time
|
||||
)
|
||||
|
||||
bankingClientCallback.messageLogAdded(mapped)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.BankingClientForUserBase
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
|
||||
open class FinTs4KBankingClientForUser(credentials: AccountCredentials, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
|
||||
: BankingClientForUserBase(credentials, FinTs4kBankingClient(config, callback)) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||
: this(bankCode, loginName, password, FinTsClientConfiguration(), callback)
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
|
||||
: this(AccountCredentials(bankCode, loginName, password), config, callback)
|
||||
|
||||
}
|
|
@ -6,15 +6,20 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
|
|||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||
import net.codinux.banking.client.model.response.Response
|
||||
import net.dankito.banking.fints.FinTsClient
|
||||
import net.codinux.banking.fints.FinTsClient
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
|
||||
open class FinTs4kBankingClient(
|
||||
config: FinTsClientConfiguration = FinTsClientConfiguration(),
|
||||
callback: BankingClientCallback
|
||||
) : BankingClient {
|
||||
|
||||
private val mapper = FinTs4kMapper()
|
||||
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
|
||||
|
||||
private val client = FinTsClient(BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
|
||||
protected val mapper = FinTs4kMapper()
|
||||
|
||||
protected val client = FinTsClient(config, BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
|
||||
|
||||
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.BankingClientForCustomerBase
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
|
||||
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, callback: BankingClientCallback)
|
||||
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(callback)) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||
: this(AccountCredentials(bankCode, loginName, password), callback)
|
||||
|
||||
}
|
|
@ -15,53 +15,72 @@ import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
|||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||
import net.dankito.banking.client.model.response.ErrorCode
|
||||
import net.dankito.banking.fints.mapper.FinTsModelMapper
|
||||
import net.dankito.banking.fints.model.*
|
||||
import net.codinux.banking.fints.mapper.FinTsModelMapper
|
||||
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 kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
open class FinTs4kMapper {
|
||||
|
||||
private val fintsModelMapper = FinTsModelMapper()
|
||||
protected val fintsModelMapper = FinTsModelMapper()
|
||||
|
||||
protected val bankingGroupMapper = BankingGroupMapper()
|
||||
|
||||
|
||||
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
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) },
|
||||
abortIfTanIsRequired = options.abortIfTanIsRequired
|
||||
)
|
||||
|
||||
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
|
||||
net.codinux.banking.fints.model.TanMethodType.valueOf(type.name)
|
||||
|
||||
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
||||
|
||||
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
||||
return if (response.successful && response.customerAccount != null) {
|
||||
Response.success(GetAccountDataResponse(mapCustomer(response.customerAccount!!)))
|
||||
Response.success(GetAccountDataResponse(mapUser(response.customerAccount!!)))
|
||||
} else {
|
||||
mapError(response)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mapToCustomerAccountViewInfo(bank: BankData): CustomerAccountViewInfo = CustomerAccountViewInfo(
|
||||
bank.bankCode, bank.customerId, bank.bankName
|
||||
open fun mapToUserAccountViewInfo(bank: BankData): UserAccountViewInfo = UserAccountViewInfo(
|
||||
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
|
||||
)
|
||||
|
||||
fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
|
||||
open fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
|
||||
account.accountIdentifier, account.subAccountAttribute,
|
||||
mapAccountType(fintsModelMapper.map(account.accountType)),
|
||||
account.iban, account.productName
|
||||
)
|
||||
|
||||
|
||||
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): CustomerAccount = CustomerAccount(
|
||||
customer.bankCode, customer.loginName, customer.password,
|
||||
customer.bankName, customer.bic, customer.customerName, customer.userId,
|
||||
customer.accounts.map { mapAccount(it) }
|
||||
protected open fun mapUser(user: net.dankito.banking.client.model.CustomerAccount) = UserAccount(
|
||||
user.bankCode, user.loginName, user.password,
|
||||
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) },
|
||||
|
||||
getBankingGroup(user.bankName, user.bic)
|
||||
)
|
||||
|
||||
protected open fun getBankingGroup(bankName: String, bic: String): BankingGroup? =
|
||||
bankingGroupMapper.getBankingGroup(bankName, bic)
|
||||
|
||||
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
|
||||
|
||||
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, account.isAccountTypeSupportedByApplication,
|
||||
mapFeatures(account),
|
||||
|
@ -71,10 +90,10 @@ open class FinTs4kMapper {
|
|||
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
||||
)
|
||||
|
||||
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||
BankAccountType.valueOf(type.name)
|
||||
|
||||
private fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
|
||||
protected open fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
|
||||
if (account.supportsRetrievingBalance) {
|
||||
add(BankAccountFeatures.RetrieveBalance)
|
||||
}
|
||||
|
@ -90,66 +109,112 @@ open class FinTs4kMapper {
|
|||
}
|
||||
|
||||
|
||||
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
||||
transaction.bookingDate, transaction.valueDate,
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
|
||||
transaction.bookingText, null,
|
||||
transaction.statementNumber, transaction.sequenceNumber,
|
||||
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
|
||||
// TODO: map other properties
|
||||
|
||||
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference,
|
||||
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||
transaction.compensationAmount, transaction.originalAmount,
|
||||
transaction.sepaReference,
|
||||
transaction.deviantOriginator, transaction.deviantRecipient,
|
||||
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
|
||||
|
||||
transaction.currencyType, transaction.bookingKey,
|
||||
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution,
|
||||
transaction.supplementaryDetails,
|
||||
|
||||
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
|
||||
)
|
||||
|
||||
private fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
|
||||
protected open fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
|
||||
|
||||
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
||||
protected open fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
||||
|
||||
|
||||
fun mapTanChallenge(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallenge {
|
||||
open fun mapTanChallenge(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallenge {
|
||||
val type = mapTanChallengeType(challenge)
|
||||
val action = mapActionRequiringTan(challenge.forAction)
|
||||
val tanMethod = mapTanMethod(challenge.tanMethod)
|
||||
val customer = mapToCustomerAccountViewInfo(challenge.bank)
|
||||
|
||||
val tanMethods = challenge.bank.tanMethodsAvailableForUser.map { mapTanMethod(it) }
|
||||
val selectedTanMethodId = challenge.tanMethod.securityFunction.code
|
||||
|
||||
val tanMedia = challenge.bank.tanMedia.map { mapTanMedium(it) }
|
||||
val selectedTanMediumName = challenge.bank.selectedTanMedium?.mediumName
|
||||
|
||||
val user = mapToUserAccountViewInfo(challenge.bank)
|
||||
val account = challenge.account?.let { mapToBankAccountViewInfo(it) }
|
||||
|
||||
val tanImage = if (challenge is ImageTanChallenge) mapTanImage(challenge.image) else null
|
||||
val flickerCode = if (challenge is FlickerCodeTanChallenge) mapFlickerCode(challenge.flickerCode) else null
|
||||
|
||||
return TanChallenge(type, action, challenge.messageToShowToUser, tanMethod, tanImage, flickerCode, customer, account)
|
||||
return TanChallenge(type, action, challenge.messageToShowToUser, selectedTanMethodId, tanMethods, selectedTanMediumName, tanMedia, tanImage, flickerCode, user, account)
|
||||
}
|
||||
|
||||
private fun mapTanChallengeType(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallengeType = when {
|
||||
protected open fun mapTanChallengeType(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallengeType = when {
|
||||
challenge is ImageTanChallenge -> TanChallengeType.Image
|
||||
challenge is FlickerCodeTanChallenge -> TanChallengeType.Flickercode
|
||||
else -> TanChallengeType.EnterTan
|
||||
}
|
||||
|
||||
private fun mapActionRequiringTan(action: net.dankito.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||
protected open fun mapActionRequiringTan(action: net.codinux.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||
ActionRequiringTan.valueOf(action.name)
|
||||
|
||||
private fun mapTanMethod(method: net.dankito.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)
|
||||
)
|
||||
|
||||
private fun mapTanMethodType(type: net.dankito.banking.fints.model.TanMethodType): TanMethodType =
|
||||
protected open fun mapTanMethodType(type: net.codinux.banking.fints.model.TanMethodType): TanMethodType =
|
||||
TanMethodType.valueOf(type.name)
|
||||
|
||||
private fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
|
||||
protected open fun mapAllowedTanFormat(allowedTanFormat: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
|
||||
allowedTanFormat?.let { AllowedTanFormat.valueOf(it.name) } ?: AllowedTanFormat.Alphanumeric
|
||||
|
||||
private fun mapTanImage(image: net.dankito.banking.fints.tan.TanImage): TanImage =
|
||||
protected open fun mapTanImage(image: net.codinux.banking.fints.tan.TanImage): TanImage =
|
||||
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private fun mapToBase64(bytes: ByteArray): String {
|
||||
protected open fun mapToBase64(bytes: ByteArray): String {
|
||||
return Base64.Default.encode(bytes)
|
||||
}
|
||||
|
||||
private fun mapFlickerCode(flickerCode: net.dankito.banking.fints.tan.FlickerCode): FlickerCode =
|
||||
protected open fun mapTanMedium(tanMedium: TanMedium) = net.codinux.banking.client.model.tan.TanMedium(
|
||||
mapTanMediumType(tanMedium), tanMedium.mediumName, mapTanMediumStatus(tanMedium.status),
|
||||
(tanMedium as? TanGeneratorTanMedium)?.let { mapTanGeneratorTanMedium(it) },
|
||||
(tanMedium as? MobilePhoneTanMedium)?.let { mapMobilePhoneTanMedium(it) }
|
||||
)
|
||||
|
||||
protected open fun mapTanMediumStatus(status: TanMediumStatus): net.codinux.banking.client.model.tan.TanMediumStatus = when (status) {
|
||||
TanMediumStatus.Aktiv -> net.codinux.banking.client.model.tan.TanMediumStatus.Used
|
||||
TanMediumStatus.Verfuegbar -> net.codinux.banking.client.model.tan.TanMediumStatus.Available
|
||||
TanMediumStatus.AktivFolgekarte -> net.codinux.banking.client.model.tan.TanMediumStatus.ActiveFollowUpCard
|
||||
TanMediumStatus.VerfuegbarFolgekarte -> net.codinux.banking.client.model.tan.TanMediumStatus.AvailableFollowUpCard
|
||||
}
|
||||
|
||||
protected open fun mapMobilePhoneTanMedium(tanMedium: MobilePhoneTanMedium) = net.codinux.banking.client.model.tan.MobilePhoneTanMedium(
|
||||
tanMedium.phoneNumber, tanMedium.concealedPhoneNumber
|
||||
)
|
||||
|
||||
protected open fun mapTanGeneratorTanMedium(tanMedium: TanGeneratorTanMedium) = net.codinux.banking.client.model.tan.TanGeneratorTanMedium(
|
||||
tanMedium.cardNumber, tanMedium.cardSequenceNumber, tanMedium.cardType,
|
||||
tanMedium.validFrom, tanMedium.validTo
|
||||
)
|
||||
|
||||
protected open fun mapTanMediumType(tanMedium: TanMedium): TanMediumType = when {
|
||||
tanMedium is MobilePhoneTanMedium -> TanMediumType.MobilePhone
|
||||
tanMedium is TanGeneratorTanMedium -> TanMediumType.TanGenerator
|
||||
else -> TanMediumType.Generic
|
||||
}
|
||||
|
||||
protected open fun mapFlickerCode(flickerCode: net.codinux.banking.fints.tan.FlickerCode): FlickerCode =
|
||||
FlickerCode(flickerCode.challengeHHD_UC, flickerCode.parsedDataSet, mapException(flickerCode.decodingError))
|
||||
|
||||
|
||||
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): 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())
|
||||
|
@ -158,7 +223,7 @@ open class FinTs4kMapper {
|
|||
}
|
||||
}
|
||||
|
||||
private fun mapException(exception: Exception?): String? =
|
||||
protected open fun mapException(exception: Exception?): String? =
|
||||
exception?.stackTraceToString()
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package net.dankito.banking.banklistcreator.prettifier
|
||||
|
||||
import net.codinux.banking.client.model.BankingGroup
|
||||
|
||||
// TODO: class has been duplicated from BankListCreator, find a better place for it
|
||||
class BankingGroupMapper {
|
||||
|
||||
fun getBankingGroup(bankName: String, bic: String): BankingGroup? {
|
||||
val lowercase = bankName.lowercase()
|
||||
|
||||
return when {
|
||||
bankName.contains("Sparda") -> BankingGroup.Sparda
|
||||
bankName.contains("PSD") -> BankingGroup.PSD
|
||||
bankName.contains("GLS") -> BankingGroup.GLS
|
||||
// see https://de.wikipedia.org/wiki/Liste_der_Genossenschaftsbanken_in_Deutschland
|
||||
bankName.contains("BBBank") || bankName.contains("Evangelische Bank") || bankName.contains("LIGA Bank")
|
||||
|| bankName.contains("Pax") || bankName.contains("Bank für Kirche und Diakonie") || bankName.contains("Bank im Bistum Essen")
|
||||
|| bankName.contains("Bank für Schiffahrt") || bankName.contains("Bank für Kirche")
|
||||
-> BankingGroup.SonstigeGenossenschaftsbank
|
||||
lowercase.contains("deutsche kreditbank") -> BankingGroup.DKB
|
||||
// may check against https://de.wikipedia.org/wiki/Liste_der_Sparkassen_in_Deutschland
|
||||
lowercase.contains("sparkasse") -> BankingGroup.Sparkasse
|
||||
lowercase.contains("comdirect") -> BankingGroup.Comdirect
|
||||
lowercase.contains("commerzbank") -> BankingGroup.Commerzbank
|
||||
lowercase.contains("targo") -> BankingGroup.Targobank
|
||||
lowercase.contains("santander") -> BankingGroup.Santander
|
||||
bankName.contains("KfW") -> BankingGroup.KfW
|
||||
bankName.contains("N26") -> BankingGroup.N26
|
||||
else -> getBankingGroupByBic(bic)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBankingGroupByBic(bic: String): BankingGroup? {
|
||||
if (bic.length < 4) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (bic.startsWith("CMCIDEDD")) {
|
||||
return BankingGroup.Targobank
|
||||
}
|
||||
|
||||
val bankCodeOfBic = bic.substring(0, 4)
|
||||
|
||||
return when (bankCodeOfBic) {
|
||||
"GENO", "VBMH", "VOHA", "VBRS", "DBPB", "VBGT", "FFVB", "WIBA", "VRBU", "MVBM", "VOBA", "ULMV", "VBRT", "VBRA", "VBPF", "VOLO" -> BankingGroup.VolksUndRaiffeisenbanken
|
||||
"BFSW", // Bank fuer Sozialwirtschaft
|
||||
"BEVO", // Berliner Volksbank
|
||||
"DAAE", // apoBank
|
||||
"MHYP", // Münchener Hypothekenbank
|
||||
"DZBM", // DZB Bank
|
||||
"EDEK" // Edekabank
|
||||
-> BankingGroup.SonstigeGenossenschaftsbank
|
||||
"BYLA", "SOLA", "NOLA", "WELA", "HELA", "MALA", "BRLA", "NASS", "TRIS", "OSDD", "ESSL", "GOPS", "SBCR", "BRUS" -> BankingGroup.Sparkasse // filter out DBK, (Bayr.) Landesbank, ...
|
||||
"OLBO" -> BankingGroup.OldenburgischeLandesbank
|
||||
"DEUT" -> BankingGroup.DeutscheBank
|
||||
"PBNK" -> BankingGroup.Postbank
|
||||
"COBA", "DRES" -> BankingGroup.Commerzbank // COBA could also be comdirect, but we cannot differentiate this at this level, this has to do getBankingGroup()
|
||||
"HYVE" -> BankingGroup.Unicredit
|
||||
"INGB" -> BankingGroup.ING
|
||||
"SCFB" -> BankingGroup.Santander
|
||||
"NORS" -> BankingGroup.Norisbank
|
||||
"DEGU" -> BankingGroup.Degussa
|
||||
"OBKL" -> BankingGroup.Oberbank
|
||||
"MARK" -> BankingGroup.Bundesbank
|
||||
"KFWI", "DTAB" -> BankingGroup.KfW
|
||||
"NTSB" -> BankingGroup.N26
|
||||
"CSDB" -> BankingGroup.Consors
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,7 @@ class FinTs4kBankingClientTest {
|
|||
}
|
||||
|
||||
|
||||
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { customer, tanChallenge ->
|
||||
private val underTest = FinTs4KBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
|
||||
})
|
||||
|
||||
|
|
10
README.md
10
README.md
|
@ -51,7 +51,7 @@ class ShowUsage {
|
|||
|
||||
|
||||
fun getAccountData() {
|
||||
val client = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback())
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback())
|
||||
|
||||
val response = client.getAccountData()
|
||||
|
||||
|
@ -61,12 +61,12 @@ class ShowUsage {
|
|||
|
||||
private fun printReceivedData(response: Response<GetAccountDataResponse>) {
|
||||
response.data?.let { data ->
|
||||
val customer = data.customer
|
||||
println("Kunde: ${customer.customerName} ${customer.accounts.size} Konten @ ${customer.bic} ${customer.bankName}")
|
||||
val user = data.user
|
||||
println("Kunde: ${user.customerName} ${user.accounts.size} Konten @ ${user.bic} ${user.bankName}")
|
||||
|
||||
println()
|
||||
println("Konten:")
|
||||
customer.accounts.sortedBy { it.type }.forEach { account ->
|
||||
user.accounts.sortedBy { it.type }.forEach { account ->
|
||||
println("${account.identifier} ${account.productName} ${account.balance} ${account.currency}")
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ This fetches the booked account transactions of the last 90 days. In most cases
|
|||
In case there is, add TAN handling in Client Callback:
|
||||
|
||||
```kotlin
|
||||
val client = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val tan: String? = null // if a TAN is required, add a UI or ...
|
||||
callback.invoke(EnterTanResult(tan)) // ... set a break point here, get TAN e.g. from your TAN app, set tan variable in debugger view and resume debugger
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("jvm") // or kotlin("multiplatform"), depending on your requirements
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package net.codinux.banking.client.fints4k.example
|
|||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.SimpleBankingClientCallback
|
||||
import net.codinux.banking.client.fints4k.FinTs4kBankingClientForCustomer
|
||||
import net.codinux.banking.client.fints4k.FinTs4kBankingClientForUser
|
||||
import net.codinux.banking.client.getAccountData
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||
|
@ -27,7 +27,7 @@ class ShowUsage {
|
|||
|
||||
|
||||
fun getAccountDataSimpleExample() {
|
||||
val client = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback())
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback())
|
||||
|
||||
val response = client.getAccountData()
|
||||
|
||||
|
@ -35,7 +35,7 @@ class ShowUsage {
|
|||
}
|
||||
|
||||
fun getAccountDataFullExample() {
|
||||
val client = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
|
||||
val tan: String? = null // if a TAN is required, add a UI or ...
|
||||
callback.invoke(EnterTanResult(tan)) // ... set a break point here, get TAN e.g. from your TAN app, set tan variable in debugger view and resume debugger
|
||||
})
|
||||
|
@ -59,12 +59,12 @@ class ShowUsage {
|
|||
|
||||
private fun printReceivedData(response: Response<GetAccountDataResponse>) {
|
||||
response.data?.let { data ->
|
||||
val customer = data.customer
|
||||
println("Kunde: ${customer.customerName} ${customer.accounts.size} Konten @ ${customer.bic} ${customer.bankName}")
|
||||
val user = data.user
|
||||
println("Kunde: ${user.customerName} ${user.accounts.size} Konten @ ${user.bic} ${user.bankName}")
|
||||
|
||||
println()
|
||||
println("Konten:")
|
||||
customer.accounts.sortedBy { it.type }.forEach { account ->
|
||||
user.accounts.sortedBy { it.type }.forEach { account ->
|
||||
println("${account.identifier} ${account.productName} ${account.balance} ${account.currency}")
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,11 @@ buildscript {
|
|||
|
||||
allprojects {
|
||||
group = "net.codinux.banking.client"
|
||||
version = "0.5.1-SNAPSHOT"
|
||||
version = "0.5.2-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven {
|
||||
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
|
||||
}
|
||||
|
@ -33,7 +34,7 @@ tasks.register("publishAllToMavenLocal") {
|
|||
":BankingClientModel:publishToMavenLocal",
|
||||
":BankingClient:publishToMavenLocal",
|
||||
|
||||
":FinTs4jBankingClient:publishToMavenLocal"
|
||||
":fints4k-banking-client:publishToMavenLocal"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -42,6 +43,6 @@ tasks.register("publishAll") {
|
|||
":BankingClientModel:publish",
|
||||
":BankingClient:publish",
|
||||
|
||||
":FinTs4jBankingClient:publish"
|
||||
":fints4k-banking-client:publish"
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue