[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 02/02: implement per-account transaction listing in sandbox
From: |
gnunet |
Subject: |
[libeufin] 02/02: implement per-account transaction listing in sandbox |
Date: |
Wed, 13 Jan 2021 22:13:54 +0100 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
commit 2cd810f110e64c7024ce21866f1c705e1c2f032c
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Jan 13 22:13:47 2021 +0100
implement per-account transaction listing in sandbox
---
cli/bin/libeufin-cli | 65 +++++++++-
.../src/main/kotlin/tech/libeufin/sandbox/DB.kt | 4 +-
.../main/kotlin/tech/libeufin/sandbox/Helpers.kt | 11 ++
.../src/main/kotlin/tech/libeufin/sandbox/JSON.kt | 3 +-
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 137 ++++++++++++++++++++-
5 files changed, 210 insertions(+), 10 deletions(-)
diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli
index ed79436..803a60a 100755
--- a/cli/bin/libeufin-cli
+++ b/cli/bin/libeufin-cli
@@ -334,9 +334,9 @@ def show(obj):
print(resp.content.decode("utf-8"))
@accounts.command(help="prepare payment debiting 'account-name'")
-@click.option("--credit-iban", help="IBAN that will receive the payment",
required=True)
-@click.option("--credit-bic", help="BIC that will receive the payment",
required=False)
-@click.option("--credit-name", help="Legal name that will receive the
payment", required=True)
+@click.option("--creditor-iban", help="IBAN that will receive the payment",
required=True)
+@click.option("--creditor-bic", help="BIC that will receive the payment",
required=False)
+@click.option("--creditor-name", help="Legal name that will receive the
payment", required=True)
@click.option("--payment-amount", help="Amount to be paid (<currency>:X.Y)",
required=True)
@click.option("--payment-subject", help="Subject of this payment",
required=True)
@click.argument("account-name")
@@ -448,18 +448,30 @@ def new_facade(obj, facade_name, connection_name,
account_name):
print(resp.content.decode("utf-8"))
-
@sandbox.group("ebicshost", help="manage EBICS hosts")
@click.pass_context
def sandbox_ebicshost(ctx):
pass
+@sandbox.command("check", help="check sandbox status")
+@click.pass_obj
+def check_sandbox_status(obj):
+ sandbox_base_url = obj.require_sandbox_base_url()
+ url = urljoin(sandbox_base_url, "/config")
+ try:
+ resp = get(url)
+ except Exception:
+ print("Could not reach sandbox")
+ exit(1)
+ print(resp.content.decode("utf-8"))
+
+
@sandbox_ebicshost.command("create", help="Create an EBICS host")
@click.option("--host-id", help="EBICS host ID", required=True, prompt=True)
@click.pass_obj
def make_ebics_host(obj, host_id):
sandbox_base_url = obj.require_sandbox_base_url()
- url = urljoin(sandbox_base_url, "/admin/ebics/host")
+ url = urljoin(sandbox_base_url, "/admin/ebics/hosts")
try:
resp = post(url, json=dict(hostID=host_id, ebicsVersion="2.5"))
except Exception:
@@ -517,6 +529,7 @@ def sandbox_ebicsbankaccount(ctx):
pass
@sandbox_ebicsbankaccount.command("create", help="Create a bank account
associated to an EBICS subscriber.")
+@click.option("--currency", help="currency", prompt=True)
@click.option("--iban", help="IBAN", required=True)
@click.option("--bic", help="BIC", required=True)
@click.option("--person-name", help="bank account owner name", required=True)
@@ -525,11 +538,12 @@ def sandbox_ebicsbankaccount(ctx):
@click.option("--ebics-host-id", help="host ID of the Ebics subscriber",
required=True)
@click.option("--ebics-partner-id", help="partner ID of the Ebics subscriber",
required=True)
@click.pass_obj
-def associate_bank_account(obj, iban, bic, person_name, account_name,
+def associate_bank_account(obj, currency, iban, bic, person_name, account_name,
ebics_user_id, ebics_host_id, ebics_partner_id):
sandbox_base_url = obj.require_sandbox_base_url()
url = urljoin(sandbox_base_url, "/admin/ebics/bank-accounts")
body = dict(
+ currency=currency,
subscriber=dict(userID=ebics_user_id, partnerID=ebics_partner_id,
hostID=ebics_host_id),
iban=iban, bic=bic, name=person_name, label=account_name
)
@@ -546,6 +560,45 @@ def associate_bank_account(obj, iban, bic, person_name,
account_name,
def sandbox_bankaccount(ctx):
pass
+@sandbox_bankaccount.command("list", help="List accounts")
+@click.pass_obj
+def bankaccount_list(obj):
+ sandbox_base_url = obj.require_sandbox_base_url()
+ url = urljoin(sandbox_base_url, f"/admin/bank-accounts")
+ try:
+ resp = get(url)
+ except Exception:
+ print("Could not reach sandbox")
+ exit(1)
+ print(resp.content.decode("utf-8"))
+
+@sandbox_bankaccount.command("transactions", help="List transactions")
+@click.argument("account-label")
+@click.pass_obj
+def bankaccount_list(obj, account_label):
+ sandbox_base_url = obj.require_sandbox_base_url()
+ url = urljoin(sandbox_base_url,
f"/admin/bank-accounts/{account_label}/transactions")
+ try:
+ resp = get(url)
+ except Exception:
+ print("Could not reach sandbox")
+ exit(1)
+ print(resp.content.decode("utf-8"))
+
+@sandbox_bankaccount.command("generate-transactions", help="Generate test
transactions")
+@click.argument("account-label")
+@click.pass_obj
+def bankaccount_generate_transactions(obj, account_label):
+ sandbox_base_url = obj.require_sandbox_base_url()
+ url = urljoin(sandbox_base_url,
f"/admin/bank-accounts/{account_label}/generate-transactions")
+ try:
+ resp = post(url)
+ except Exception:
+ print("Could not reach sandbox")
+ exit(1)
+ print(resp.content.decode("utf-8"))
+
+
@sandbox_bankaccount.command(help="book a payment in the sandbox")
@click.option("--creditor-iban", help="IBAN receiving the payment",
prompt=True)
@click.option("--creditor-bic", help="BIC receiving the payment", prompt=True)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 82124f9..9ea9a0e 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -273,8 +273,9 @@ object BankAccountsTable : IntIdTable() {
val iban = text("iban")
val bic = text("bic")
val name = text("name")
- val label = text("label")
+ val label = text("label").uniqueIndex("accountLabelIndex")
val subscriber = reference("subscriber", EbicsSubscribersTable)
+ val currency = text("currency")
}
class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) {
@@ -285,6 +286,7 @@ class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) {
var name by BankAccountsTable.name
var label by BankAccountsTable.label
var subscriber by EbicsSubscriberEntity referencedOn
BankAccountsTable.subscriber
+ var currency by BankAccountsTable.currency
}
object BankAccountStatementsTable : IntIdTable() {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index 0ea1927..bc9c908 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -53,6 +53,17 @@ fun getBankAccountFromIban(iban: String): BankAccountEntity {
)
}
+fun getBankAccountFromLabel(label: String): BankAccountEntity {
+ return transaction {
+ BankAccountEntity.find(
+ BankAccountsTable.label eq label
+ )
+ }.firstOrNull() ?: throw SandboxError(
+ HttpStatusCode.NotFound,
+ "Did not find a bank account for label ${label}"
+ )
+}
+
fun getBankAccountFromSubscriber(subscriber: EbicsSubscriberEntity):
BankAccountEntity {
return transaction {
BankAccountEntity.find(BankAccountsTable.subscriber eq subscriber.id)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index ad46123..69804e7 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -69,7 +69,8 @@ data class BankAccountRequest(
val iban: String,
val bic: String,
val name: String,
- val label: String
+ val label: String,
+ val currency: String
)
data class DateRange(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 7152169..404ac70 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -77,6 +77,7 @@ import
tech.libeufin.sandbox.BankAccountTransactionsTable.direction
import tech.libeufin.util.*
import tech.libeufin.util.ebics_h004.EbicsResponse
import tech.libeufin.util.ebics_h004.EbicsTypes
+import java.util.*
import kotlin.random.Random
const val DEFAULT_DB_CONNECTION = "jdbc:sqlite:/tmp/libeufin-sandbox.sqlite3"
@@ -86,6 +87,7 @@ class BadInputData(inputData: String?) : Exception("Customer
provided invalid in
class UnacceptableFractional(badNumber: BigDecimal) : Exception(
"Unacceptable fractional part ${badNumber}"
)
+
lateinit var LOGGER: Logger
data class SandboxError(val statusCode: HttpStatusCode, val reason: String) :
Exception()
@@ -102,6 +104,7 @@ class ResetTables : CliktCommand("Drop all the tables from
the database") {
helpFormatter = CliktHelpFormatter(showDefaultValues = true)
}
}
+
private val dbConnString by option().default(DEFAULT_DB_CONNECTION)
override fun run() {
execThrowableOrTerminate {
@@ -117,6 +120,7 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
helpFormatter = CliktHelpFormatter(showDefaultValues = true)
}
}
+
private val dbConnString by option().default(DEFAULT_DB_CONNECTION)
private val logLevel by option()
private val port by option().int().default(5000)
@@ -160,6 +164,17 @@ data class EbicsHostPublicInfo(
val authenticationPublicKey: RSAPublicKey
)
+data class BankAccountInfo(
+ val label: String,
+ val name: String,
+ val iban: String,
+ val bic: String
+)
+
+data class BankAccountsListReponse(
+ val accounts: List<BankAccountInfo>
+)
+
inline fun <reified T> Document.toObject(): T {
val jc = JAXBContext.newInstance(T::class.java)
val m = jc.createUnmarshaller()
@@ -171,6 +186,12 @@ fun BigDecimal.signToString(): String {
// minus sign is added by default already.
}
+fun ensureNonNull(param: String?): String {
+ return param ?: throw SandboxError(
+ HttpStatusCode.BadRequest, "Bad ID given: $param"
+ )
+}
+
fun main(args: Array<String>) {
SandboxCommand()
.subcommands(Serve(), ResetTables())
@@ -259,6 +280,14 @@ fun serverMain(dbName: String, port: Int) {
get("/") {
call.respondText("Hello, this is Sandbox\n",
ContentType.Text.Plain)
}
+ get("/config") {
+ call.respond(object {
+ val name = "libeufin-sandbox"
+
+ // FIXME: use actual version here!
+ val version = "0.0.0-dev.0"
+ })
+ }
// only reason for a post is to hide the iban (to some degree.)
post("/admin/payments/camt") {
val body = call.receive<CamtParams>()
@@ -268,6 +297,7 @@ fun serverMain(dbName: String, port: Int) {
call.respondText(camt53, ContentType.Text.Xml,
HttpStatusCode.OK)
return@post
}
+ // FIXME: This returns *all* payments for all accounts. Is that
really useful/required?
get("/admin/payments") {
val ret = PaymentsResponse()
transaction {
@@ -342,11 +372,114 @@ fun serverMain(dbName: String, port: Int) {
bic = body.bic
name = body.name
label = body.label
+ currency = body.currency.toUpperCase(Locale.ROOT)
}
}
call.respondText("Bank account created")
return@post
}
+ get("/admin/bank-accounts") {
+ val accounts = mutableListOf<BankAccountInfo>()
+ val accountsResp = BankAccountsListReponse(
+ accounts = accounts
+ )
+ transaction {
+ BankAccountEntity.all().forEach {
+ accounts.add(
+ BankAccountInfo(
+ label = it.label,
+ name = it.name,
+ bic = it.bic,
+ iban = it.iban
+ )
+ )
+ }
+ }
+ call.respond(accounts)
+ }
+ get("/admin/bank-accounts/{label}/transactions") {
+ val ret = PaymentsResponse()
+ transaction {
+ val accountLabel = ensureNonNull(call.parameters["label"])
+ transaction {
+ val account = getBankAccountFromLabel(accountLabel)
+ BankAccountTransactionsTable.select {
BankAccountTransactionsTable.account eq account.id }
+ .forEach {
+ ret.payments.add(
+ RawPayment(
+ creditorIban = it[creditorIban],
+ debitorIban = it[debitorIban],
+ subject =
it[BankAccountTransactionsTable.subject],
+ date = it[date].toHttpDateString(),
+ amount = it[amount],
+ creditorBic = it[creditorBic],
+ creditorName = it[creditorName],
+ debitorBic = it[debitorBic],
+ debitorName = it[debitorName],
+ currency = it[currency],
+ direction = it[direction]
+ )
+ )
+ }
+ }
+ }
+ call.respond(
+ object {
+ val payments = ret
+ }
+ )
+ }
+ post("/admin/bank-accounts/{label}/generate-transactions") {
+ transaction {
+ val accountLabel = ensureNonNull(call.parameters["label"])
+ val account = getBankAccountFromLabel(accountLabel)
+
+ run {
+ val random = Random.nextLong()
+ val amount = Random.nextLong(5, 25)
+
+ BankAccountTransactionsTable.insert {
+ it[creditorIban] = account.iban
+ it[creditorBic] = account.bic
+ it[creditorName] = account.name
+ it[debitorIban] = "DE64500105178797276788"
+ it[debitorBic] = "FOBADEM001"
+ it[debitorName] = "Max Mustermann"
+ it[subject] = "sample transaction $random"
+ it[BankAccountTransactionsTable.amount] =
amount.toString()
+ it[currency] = account.currency
+ it[date] = Instant.now().toEpochMilli()
+ it[pmtInfId] = random.toString()
+ it[msgId] = random.toString()
+ it[BankAccountTransactionsTable.account] =
account.id
+ it[direction] = "CRDT"
+ }
+ }
+
+ run {
+ val random = Random.nextLong()
+ val amount = Random.nextLong(5, 25)
+
+ BankAccountTransactionsTable.insert {
+ it[debitorIban] = account.iban
+ it[debitorBic] = account.bic
+ it[debitorName] = account.name
+ it[creditorIban] = "DE64500105178797276788"
+ it[creditorBic] = "FOBADEM001"
+ it[creditorName] = "Max Mustermann"
+ it[subject] = "sample transaction $random"
+ it[BankAccountTransactionsTable.amount] =
amount.toString()
+ it[currency] = account.currency
+ it[date] = Instant.now().toEpochMilli()
+ it[pmtInfId] = random.toString()
+ it[msgId] = random.toString()
+ it[BankAccountTransactionsTable.account] =
account.id
+ it[direction] = "DBIT"
+ }
+ }
+ }
+ call.respond(object {})
+ }
/**
* Creates a new Ebics subscriber.
*/
@@ -372,7 +505,7 @@ fun serverMain(dbName: String, port: Int) {
* Shows all the Ebics subscribers' details.
*/
get("/admin/ebics/subscribers") {
- var ret = AdminGetSubscribers()
+ val ret = AdminGetSubscribers()
transaction {
EbicsSubscriberEntity.all().forEach {
ret.subscribers.add(
@@ -431,4 +564,4 @@ fun serverMain(dbName: String, port: Int) {
}
LOGGER.info("Up and running")
server.start(wait = true)
-}
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.