libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit ed271284bd86be1e96bf3e3a9ced302e59e65ec4
parent d735033ef09aad4edd07207eab093b1552f46b42
Author: ms <ms@taler.net>
Date:   Thu,  4 Nov 2021 13:52:44 +0100

Create Ebics subscriber within /demobanks trunk.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt | 1+
Mnexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt | 9---------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt | 8++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt | 35++++++++++++++++++++++++-----------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 59++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mutil/src/main/kotlin/Payto.kt | 10++++++++++
6 files changed, 93 insertions(+), 29 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt @@ -14,6 +14,7 @@ import kotlin.math.abs import kotlin.math.min import io.ktor.content.TextContent import io.ktor.routing.* +import tech.libeufin.util.buildIbanPaytoUri data class AnastasisIncomingBankTransaction( val row_id: Long, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt @@ -107,15 +107,6 @@ fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): List<T> { } } -fun buildIbanPaytoUri( - iban: String, - bic: String, - receiverName: String, -): String { - val nameUrlEnc = URLEncoder.encode(receiverName, "utf-8") - return "payto://iban/$bic/$iban?receiver-name=$nameUrlEnc" -} - /** Builds the comparison operator for history entries based on the sign of 'delta' */ fun getComparisonOperator(delta: Int, start: Long, table: IdTable<Long>): Op<Boolean> { return if (delta < 0) { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt @@ -247,6 +247,14 @@ fun getBankAccountFromIban(iban: String): BankAccountEntity { ) } +fun getBankAccountFromLabel(label: String, demobankName: String): BankAccountEntity { + return transaction { + val demobank: DemobankConfigEntity = DemobankConfigEntity.find { + DemobankConfigsTable.name eq demobankName + }.firstOrNull() ?: throw notFound("Demobank ${demobankName} not found") + getBankAccountFromLabel(label, demobank) + } +} fun getBankAccountFromLabel(label: String, demobank: DemobankConfigEntity): BankAccountEntity { return transaction { BankAccountEntity.find( diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt @@ -20,7 +20,6 @@ package tech.libeufin.sandbox import tech.libeufin.util.PaymentInfo -import tech.libeufin.util.RawPayment data class WithdrawalRequest( /** @@ -60,26 +59,40 @@ data class AccountTransactions( /** * Used to create AND show one Ebics subscriber in the system. */ -data class EbicsSubscriberElement( +data class EbicsSubscriberInfo( val hostID: String, val partnerID: String, val userID: String, - val systemID: String? = null + val systemID: String? = null, + val demobankAccountLabel: String ) data class AdminGetSubscribers( - var subscribers: MutableList<EbicsSubscriberElement> = mutableListOf() + var subscribers: MutableList<EbicsSubscriberInfo> = mutableListOf() ) -data class BankAccountRequest( - val subscriber: EbicsSubscriberElement, +/** + * Some obsolete code creates a bank account and after the + * Ebics subscriber. This doesn't allow to have bank accounts + * without a subscriber associated to it. Demobank should allow + * this instead, because only one user - the exchange - will + * ever need a Ebics subscription at the Sandbox. + * + * The code is obsoleted by a new endpoint that's defined within + * the /demobanks/${demobankId} trunk. This one allows to first create + * a bank account, and only optionally later give a Ebics account to + * it. + */ +data class EbicsSubscriberObsoleteApi( + val hostID: String, + val partnerID: String, + val userID: String, + val systemID: String? = null +) +data class EbicsBankAccountRequest( + val subscriber: EbicsSubscriberObsoleteApi, val iban: String, val bic: String, - /** - * Obsolete: kept around to allow progressive porting of tests. - * This value used to represent a _person_ name, but the new DemobankCustomer - * type is now responsible for that. - */ val name: String, val label: String, ) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -102,7 +102,7 @@ class DefaultExchange : CliktCommand("Set default Taler exchange for a demobank. } } private val exchange by argument("EXCHANGE", "Payto URI of the default exchange") - private val demobank by argument("DEMOBANK", "Which demobank defaults to EXCHANGE") + private val demobank by option("--demobank", help = "Which demobank defaults to EXCHANGE").default("default") override fun run() { val dbConnString = getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME) @@ -654,7 +654,7 @@ val sandboxApp: Application.() -> Unit = { // Associates a new bank account with an existing Ebics subscriber. post("/admin/ebics/bank-accounts") { val username = call.request.basicAuth() - val body = call.receiveJson<BankAccountRequest>() + val body = call.receiveJson<EbicsBankAccountRequest>() if (!validateBic(body.bic)) { throw SandboxError(HttpStatusCode.BadRequest, "invalid BIC (${body.bic})") } @@ -813,10 +813,10 @@ val sandboxApp: Application.() -> Unit = { call.respond(object {}) } - // Creates a new Ebics subscriber. + post("/admin/ebics/subscribers") { call.request.basicAuth() - val body = call.receiveJson<EbicsSubscriberElement>() + val body = call.receiveJson<EbicsSubscriberObsoleteApi>() transaction { EbicsSubscriberEntity.new { partnerId = body.partnerID @@ -839,12 +839,13 @@ val sandboxApp: Application.() -> Unit = { call.request.basicAuth() val ret = AdminGetSubscribers() transaction { - tech.libeufin.sandbox.EbicsSubscriberEntity.all().forEach { + EbicsSubscriberEntity.all().forEach { ret.subscribers.add( - EbicsSubscriberElement( + EbicsSubscriberInfo( userID = it.userId, partnerID = it.partnerId, - hostID = it.hostId + hostID = it.hostId, + demobankAccountLabel = it.bankAccount?.label ?: "not associated yet" ) ) } @@ -1169,9 +1170,12 @@ val sandboxApp: Application.() -> Unit = { get("/accounts/{account_name}") { val username = call.request.basicAuth() val accountAccessed = call.getUriComponent("account_name") + val demobank = ensureDemobank(call) val bankAccount = transaction { val res = BankAccountEntity.find { - BankAccountsTable.label eq accountAccessed + (BankAccountsTable.label eq accountAccessed).and( + BankAccountsTable.demoBank eq demobank.id + ) }.firstOrNull() res } ?: throw notFound("Account '$accountAccessed' not found") @@ -1185,12 +1189,16 @@ val sandboxApp: Application.() -> Unit = { "credit" } val balance = balanceForAccount(bankAccount) - val demobank = ensureDemobank(call) call.respond(object { val balance = { val amount = "${demobank.currency}:${balance}" val credit_debit_indicator = creditDebitIndicator } + val paytoUri = buildIbanPaytoUri( + iban = bankAccount.iban, + bic = bankAccount.bic, + receiverName = getPersonNameFromCustomer(username ?: "admin") + ) }) return@get } @@ -1273,6 +1281,39 @@ val sandboxApp: Application.() -> Unit = { return@post } } + route("/ebics") { + post("/subscribers") { + val user = call.request.basicAuth() + val body = call.receiveJson<EbicsSubscriberInfo>() + /** + * Create or get the Ebics subscriber that is found. + */ + transaction { + val subscriber: EbicsSubscriberEntity = EbicsSubscriberEntity.find { + (EbicsSubscribersTable.partnerId eq body.partnerID).and( + EbicsSubscribersTable.userId eq body.userID + ).and(EbicsSubscribersTable.hostId eq body.hostID) + }.firstOrNull() ?: EbicsSubscriberEntity.new { + partnerId = body.partnerID + userId = body.userID + systemId = null + hostId = body.hostID + state = SubscriberState.NEW + nextOrderID = 1 + } + val bankAccount = getBankAccountFromLabel( + body.demobankAccountLabel, + ensureDemobank(call) + ) + if (bankAccount.owner != user) throw forbidden( + "User cannot access bank account '${bankAccount.label}'" + ) + subscriber.bankAccount = bankAccount + } + call.respond(object {}) + return@post + } + } } } } diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt @@ -2,6 +2,7 @@ package tech.libeufin.util import java.net.URI import java.net.URLDecoder +import java.net.URLEncoder /** * Payto information. @@ -69,4 +70,13 @@ fun parsePayto(paytoLine: String): Payto { message = getQueryParamOrNull("message", params), receiverName = getQueryParamOrNull("receiver-name", params) ) +} + +fun buildIbanPaytoUri( + iban: String, + bic: String, + receiverName: String, +): String { + val nameUrlEnc = URLEncoder.encode(receiverName, "utf-8") + return "payto://iban/$bic/$iban?receiver-name=$nameUrlEnc" } \ No newline at end of file