Implemented handling TAN in FinTs4kBankingClient
This commit is contained in:
parent
d57f6d8600
commit
d331a4810c
|
@ -0,0 +1,10 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
interface BankingClientCallback {
|
||||
|
||||
fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package net.codinux.banking.client
|
||||
|
||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
|
||||
open class SimpleBankingClientCallback(
|
||||
protected val enterTan: ((tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) -> Unit)? = null
|
||||
) : BankingClientCallback {
|
||||
|
||||
override fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) {
|
||||
if (enterTan != null) {
|
||||
enterTan.invoke(tanChallenge, callback)
|
||||
} else {
|
||||
callback(EnterTanResult(null))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package net.codinux.banking.client.model.tan
|
||||
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||
|
||||
|
@ -7,16 +8,17 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
|||
open class TanChallenge(
|
||||
val type: TanChallengeType,
|
||||
val forAction: ActionRequiringTan,
|
||||
val customer: CustomerAccount,
|
||||
val messageToShowToUser: String,
|
||||
val tanMethod: TanMethod,
|
||||
val tanImage: TanImage? = null,
|
||||
val flickerCode: FlickerCode? = null
|
||||
val flickerCode: FlickerCode? = null,
|
||||
val customer: CustomerAccount,
|
||||
val account: BankAccount? = null
|
||||
// TODO: add availableTanMethods, selectedTanMedium, availableTanMedia
|
||||
) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "$tanMethod: $messageToShowToUser" + when (type) {
|
||||
return "$tanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||
TanChallengeType.EnterTan -> ""
|
||||
TanChallengeType.Image -> ", Image: $tanImage"
|
||||
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
|
||||
open class BridgeFintTsToBankingClientCallback(
|
||||
protected val bankingClientCallback: BankingClientCallback,
|
||||
protected val mapper: FinTs4kMapper
|
||||
) : FinTsClientCallback {
|
||||
|
||||
override suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod? {
|
||||
return suggestedTanMethod
|
||||
}
|
||||
|
||||
override suspend fun enterTan(tanChallenge: net.dankito.banking.fints.model.TanChallenge) {
|
||||
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
|
||||
if (enterTanResult.enteredTan != null) {
|
||||
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
|
||||
} else {
|
||||
tanChallenge.userDidNotEnterTan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
|
||||
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
|
||||
}
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClient
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
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.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
||||
|
||||
open class FinTs4kBankingClient : BankingClient {
|
||||
open class FinTs4kBankingClient(
|
||||
callback: BankingClientCallback
|
||||
) : BankingClient {
|
||||
|
||||
private val mapper = FinTs4kMapper()
|
||||
|
||||
private val client = FinTsClient(SimpleFinTsClientCallback { tanChallenge ->
|
||||
// callback.enterTan()
|
||||
})
|
||||
private val client = FinTsClient(BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||
|
||||
|
||||
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.BankingClientCallback
|
||||
import net.codinux.banking.client.BankingClientForCustomerBase
|
||||
import net.codinux.banking.client.model.AccountCredentials
|
||||
|
||||
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials)
|
||||
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient()) {
|
||||
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, callback: BankingClientCallback)
|
||||
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(callback)) {
|
||||
|
||||
constructor(bankCode: String, loginName: String, password: String)
|
||||
: this(AccountCredentials(bankCode, loginName, password))
|
||||
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||
: this(AccountCredentials(bankCode, loginName, password), callback)
|
||||
|
||||
}
|
|
@ -1,15 +1,29 @@
|
|||
package net.codinux.banking.client.fints4k
|
||||
|
||||
import net.codinux.banking.client.model.*
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.tan.*
|
||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||
import net.codinux.banking.client.model.response.*
|
||||
import net.codinux.banking.client.model.tan.ActionRequiringTan
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
import net.codinux.banking.client.model.tan.TanImage
|
||||
import net.codinux.banking.client.model.tan.TanMethod
|
||||
import net.codinux.banking.client.model.tan.TanMethodType
|
||||
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.model.Money
|
||||
import net.dankito.banking.fints.mapper.FinTsModelMapper
|
||||
import net.dankito.banking.fints.model.*
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
open class FinTs4kMapper {
|
||||
|
||||
private val fintsModelMapper = FinTsModelMapper()
|
||||
|
||||
|
||||
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||
credentials.bankCode, credentials.loginName, credentials.password,
|
||||
null,
|
||||
|
@ -21,33 +35,36 @@ open class FinTs4kMapper {
|
|||
|
||||
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
||||
return if (response.successful && response.customerAccount != null) {
|
||||
Response.success(mapCustomer(response.customerAccount!!))
|
||||
Response.success(GetAccountDataResponse(mapCustomer(response.customerAccount!!)))
|
||||
} else {
|
||||
mapError(response)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): GetAccountDataResponse {
|
||||
val mapped = CustomerAccount(
|
||||
customer.bankCode, customer.loginName, customer.password,
|
||||
customer.bankName, customer.bic, customer.customerName, customer.userId,
|
||||
customer.accounts.map { mapAccount(it) }
|
||||
)
|
||||
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) }
|
||||
)
|
||||
|
||||
return GetAccountDataResponse(mapped)
|
||||
}
|
||||
fun mapCustomer(bank: BankData): CustomerAccount =
|
||||
mapCustomer(fintsModelMapper.map(bank))
|
||||
|
||||
|
||||
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
|
||||
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
|
||||
account.productName, account.currency, account.accountLimit, isAccountTypeSupported(account),
|
||||
account.productName, account.currency, account.accountLimit, account.isAccountTypeSupportedByApplication,
|
||||
mapFeatures(account),
|
||||
mapAmount(account.balance), account.retrievedTransactionsFrom, account.retrievedTransactionsTo,
|
||||
// TODO: map haveAllTransactionsBeenRetrieved and countDaysForWhichTransactionsAreKept
|
||||
// TODO: map haveAllTransactionsBeenRetrieved
|
||||
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
||||
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
||||
)
|
||||
|
||||
fun mapAccount(account: AccountData): BankAccount =
|
||||
mapAccount(fintsModelMapper.map(account))
|
||||
|
||||
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||
BankAccountType.valueOf(type.name)
|
||||
|
||||
|
@ -66,16 +83,6 @@ open class FinTs4kMapper {
|
|||
}
|
||||
}
|
||||
|
||||
private fun isAccountTypeSupported(account: net.dankito.banking.client.model.BankAccount): Boolean {
|
||||
// TODO:
|
||||
// open val isAccountTypeSupportedByApplication: Boolean
|
||||
// get() = FinTsClient.SupportedAccountTypes.contains(accountType)
|
||||
// || allowedJobNames.contains(CustomerSegmentId.Balance.id)
|
||||
// || allowedJobNames.contains(CustomerSegmentId.AccountTransactionsMt940.id)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
||||
|
@ -92,6 +99,50 @@ open class FinTs4kMapper {
|
|||
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
||||
|
||||
|
||||
fun mapTanChallenge(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallenge {
|
||||
val type = mapTanChallengeType(challenge)
|
||||
val action = mapActionRequiringTan(challenge.forAction)
|
||||
val tanMethod = mapTanMethod(challenge.tanMethod)
|
||||
val customer = mapCustomer(challenge.bank)
|
||||
val account = challenge.account?.let { mapAccount(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)
|
||||
}
|
||||
|
||||
private fun mapTanChallengeType(challenge: net.dankito.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 =
|
||||
ActionRequiringTan.valueOf(action.name)
|
||||
|
||||
private fun mapTanMethod(method: net.dankito.banking.fints.model.TanMethod): TanMethod = TanMethod(
|
||||
method.displayName, mapTanMethodType(method.type), method.securityFunction.code, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
|
||||
)
|
||||
|
||||
private fun mapTanMethodType(type: net.dankito.banking.fints.model.TanMethodType): TanMethodType =
|
||||
TanMethodType.valueOf(type.name)
|
||||
|
||||
private fun mapAllowedTanFormat(allowedTanFormat: net.dankito.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 =
|
||||
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private fun mapToBase64(bytes: ByteArray): String {
|
||||
return Base64.Default.encode(bytes)
|
||||
}
|
||||
|
||||
private fun mapFlickerCode(flickerCode: net.dankito.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> {
|
||||
return if (response.error != null) {
|
||||
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
|
||||
|
@ -101,4 +152,7 @@ open class FinTs4kMapper {
|
|||
}
|
||||
}
|
||||
|
||||
private fun mapException(exception: Exception?): String? =
|
||||
exception?.stackTraceToString()
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
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
|
||||
|
@ -18,7 +19,9 @@ class FinTs4kBankingClientTest {
|
|||
}
|
||||
|
||||
|
||||
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password)
|
||||
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { customer, tanChallenge ->
|
||||
|
||||
})
|
||||
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue