[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: Implementing registration bonus
From: |
gnunet |
Subject: |
[libeufin] branch master updated: Implementing registration bonus |
Date: |
Fri, 22 Sep 2023 13:37:36 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new 25e30f22 Implementing registration bonus
25e30f22 is described below
commit 25e30f2266920b7a7f2f9605e2fa25c550d6cfe9
Author: MS <ms@taler.net>
AuthorDate: Fri Sep 22 13:36:52 2023 +0200
Implementing registration bonus
---
.../src/main/kotlin/tech/libeufin/bank/Database.kt | 26 ++++++++--
.../tech/libeufin/bank/accountsMgmtHandlers.kt | 42 +++++++++++++++--
bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 55 ++++++++++++----------
bank/src/test/kotlin/DatabaseTest.kt | 22 ++++-----
bank/src/test/kotlin/LibeuFinApiTest.kt | 14 +++---
bank/src/test/kotlin/TalerApiTest.kt | 26 +++++-----
6 files changed, 121 insertions(+), 64 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 9bf42dac..d5789396 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -291,8 +291,13 @@ class Database(private val dbConfig: String) {
}
// BANK ACCOUNTS
- // Returns false on conflicts.
- fun bankAccountCreate(bankAccount: BankAccount): Boolean {
+
+ /**
+ * Inserts a new bank account in the database, returning its
+ * row ID in the successful case. If of unique constrain violation,
+ * it returns null and any other error will be thrown as 500.
+ */
+ fun bankAccountCreate(bankAccount: BankAccount): Long? {
reconnect()
if (bankAccount.balance != null)
throw internalServerError(
@@ -307,7 +312,9 @@ class Database(private val dbConfig: String) {
,is_taler_exchange
,max_debt
)
- VALUES (?, ?, ?, ?, (?, ?)::taler_amount)
+ VALUES
+ (?, ?, ?, ?, (?, ?)::taler_amount)
+ RETURNING bank_account_id;
""")
stmt.setString(1, bankAccount.internalPaytoUri)
stmt.setLong(2, bankAccount.owningCustomerId)
@@ -316,7 +323,18 @@ class Database(private val dbConfig: String) {
stmt.setLong(5, bankAccount.maxDebt.value)
stmt.setInt(6, bankAccount.maxDebt.frac)
// using the default zero value for the balance.
- return myExecute(stmt)
+ val res = try {
+ stmt.executeQuery()
+ } catch (e: SQLException) {
+ logger.error(e.message)
+ if (e.errorCode == 0) return null // unique constraint violation.
+ throw e // rethrow on other errors.
+ }
+ res.use {
+ if (!it.next())
+ throw internalServerError("SQL RETURNING gave no
bank_account_id.")
+ return it.getLong("bank_account_id")
+ }
}
fun bankAccountSetMaxDebt(
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
index 08e01c26..e1890b3f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
@@ -9,6 +9,7 @@ import net.taler.common.errorcodes.TalerErrorCode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.getNowUs
import tech.libeufin.util.maybeUriComponent
private val logger: Logger =
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
@@ -95,7 +96,6 @@ fun Routing.accountsMgmtHandlers(db: Database) {
if (this == null) throw internalServerError("Max debt not
configured")
parseTalerAmount(this)
}
- val bonus = db.configGet("registration_bonus")
val newBankAccount = BankAccount(
hasDebt = false,
internalPaytoUri = req.internal_payto_uri ?: genIbanPaytoUri(),
@@ -104,8 +104,44 @@ fun Routing.accountsMgmtHandlers(db: Database) {
isTalerExchange = req.is_taler_exchange,
maxDebt = maxDebt
)
- if (!db.bankAccountCreate(newBankAccount))
- throw internalServerError("Could not INSERT bank account despite
all the checks.")
+ val newBankAccountId = db.bankAccountCreate(newBankAccount)
+ ?: throw internalServerError("Could not INSERT bank account
despite all the checks.")
+
+ /**
+ * The new account got created, now optionally award the registration
+ * bonus to it. The configuration gets either a Taler amount (of the
+ * bonus), or null if no bonus is meant to be awarded.
+ */
+ val bonusAmount = db.configGet("registration_bonus")
+ if (bonusAmount != null) {
+ // Double-checking that the currency is correct.
+ val internalCurrency = db.configGet("internal_currency")
+ ?: throw internalServerError("Bank own currency missing in the
config")
+ val bonusAmountObj = parseTalerAmount2(bonusAmount,
FracDigits.EIGHT)
+ ?: throw internalServerError("Bonus amount found invalid in
the config.")
+ if (bonusAmountObj.currency != internalCurrency)
+ throw internalServerError("Bonus amount has the wrong
currency: ${bonusAmountObj.currency}")
+ val adminCustomer = db.customerGetFromLogin("admin")
+ ?: throw internalServerError("Admin customer not found")
+ val adminBankAccount =
db.bankAccountGetFromOwnerId(adminCustomer.expectRowId())
+ ?: throw internalServerError("Admin bank account not found")
+ val adminPaysBonus = BankInternalTransaction(
+ creditorAccountId = newBankAccountId,
+ debtorAccountId = adminBankAccount.expectRowId(),
+ amount = bonusAmountObj,
+ subject = "Registration bonus.",
+ transactionDate = getNowUs()
+ )
+ when(db.bankTransactionCreate(adminPaysBonus)) {
+ Database.BankTransactionResult.NO_CREDITOR ->
+ throw internalServerError("Bonus impossible: creditor not
found, despite its recent creation.")
+ Database.BankTransactionResult.NO_DEBTOR ->
+ throw internalServerError("Bonus impossible: admin not
found.")
+ Database.BankTransactionResult.CONFLICT ->
+ throw internalServerError("Bonus impossible: admin has
insufficient balance.")
+ Database.BankTransactionResult.SUCCESS -> {/* continue the
execution */}
+ }
+ }
call.respond(HttpStatusCode.Created)
return@post
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 199e5902..9db6f869 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -219,51 +219,33 @@ fun badRequest(
// Generates a new Payto-URI with IBAN scheme.
fun genIbanPaytoUri(): String = "payto://iban/SANDBOXX/${getIban()}"
-/**
- * This helper takes the serialized version of a Taler Amount
- * type and parses it into Libeufin's internal representation.
- * It returns a TalerAmount type, or throws a LibeufinBankException
- * it the input is invalid. Such exception will be then caught by
- * Ktor, transformed into the appropriate HTTP error type, and finally
- * responded to the client.
- */
-fun parseTalerAmount(
+// Parses Taler amount, returning null if the input is invalid.
+fun parseTalerAmount2(
amount: String,
- fracDigits: FracDigits = FracDigits.EIGHT
-): TalerAmount {
+ fracDigits: FracDigits
+): TalerAmount? {
val format = when (fracDigits) {
FracDigits.TWO -> "^([A-Z]+):([0-9]+)(\\.[0-9][0-9]?)?$"
FracDigits.EIGHT ->
"^([A-Z]+):([0-9]+)(\\.[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)?$"
}
- val match = Regex(format).find(amount) ?: throw LibeufinBankException(
- httpStatus = HttpStatusCode.BadRequest,
- talerError = TalerError(
- code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
- hint = "Invalid amount: $amount"
- ))
+ val match = Regex(format).find(amount) ?: return null
val _value = match.destructured.component2()
// Fraction is at most 8 digits, so it's always < than MAX_INT.
val fraction: Int = match.destructured.component3().run {
var frac = 0
var power = FRACTION_BASE
if (this.isNotEmpty())
- // Skips the dot and processes the fractional chars.
+ // Skips the dot and processes the fractional chars.
this.substring(1).forEach { chr ->
power /= 10
frac += power * chr.digitToInt()
- }
+ }
return@run frac
}
val value: Long = try {
_value.toLong()
} catch (e: NumberFormatException) {
- throw LibeufinBankException(
- httpStatus = HttpStatusCode.BadRequest,
- talerError = TalerError(
- code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
- hint = "Invalid amount: ${amount}, could not extract the value
part."
- )
- )
+ return null
}
return TalerAmount(
value = value,
@@ -271,6 +253,27 @@ fun parseTalerAmount(
currency = match.destructured.component1()
)
}
+/**
+ * This helper takes the serialized version of a Taler Amount
+ * type and parses it into Libeufin's internal representation.
+ * It returns a TalerAmount type, or throws a LibeufinBankException
+ * it the input is invalid. Such exception will be then caught by
+ * Ktor, transformed into the appropriate HTTP error type, and finally
+ * responded to the client.
+ */
+fun parseTalerAmount(
+ amount: String,
+ fracDigits: FracDigits = FracDigits.EIGHT
+): TalerAmount {
+ val maybeAmount = parseTalerAmount2(amount, fracDigits)
+ ?: throw LibeufinBankException(
+ httpStatus = HttpStatusCode.BadRequest,
+ talerError = TalerError(
+ code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
+ hint = "Invalid amount: $amount"
+ ))
+ return maybeAmount
+}
private fun normalizeAmount(amt: TalerAmount): TalerAmount {
if (amt.frac > FRACTION_BASE) {
diff --git a/bank/src/test/kotlin/DatabaseTest.kt
b/bank/src/test/kotlin/DatabaseTest.kt
index 9ff10cc2..e1b77937 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -93,8 +93,8 @@ class DatabaseTest {
assert(fooId != null)
val barId = db.customerCreate(customerBar)
assert(barId != null)
- assert(db.bankAccountCreate(bankAccountFoo))
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
+ assert(db.bankAccountCreate(bankAccountBar) != null)
val res = db.talerTransferCreate(
req = exchangeReq,
exchangeBankAccountId = 1L,
@@ -127,8 +127,8 @@ class DatabaseTest {
assert(fooId != null)
val barId = db.customerCreate(customerBar)
assert(barId != null)
- assert(db.bankAccountCreate(bankAccountFoo))
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
+ assert(db.bankAccountCreate(bankAccountBar) != null)
var fooAccount = db.bankAccountGetFromOwnerId(fooId!!)
assert(fooAccount?.hasDebt == false) // Foo has NO debit.
val currency = "KUDOS"
@@ -224,8 +224,8 @@ class DatabaseTest {
val currency = "KUDOS"
assert(db.bankAccountGetFromOwnerId(1L) == null)
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
- assert(!db.bankAccountCreate(bankAccountFoo)) // Triggers conflict.
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
+ assert(db.bankAccountCreate(bankAccountFoo) == null) // Triggers
conflict.
assert(db.bankAccountGetFromOwnerId(1L)?.balance?.equals(TalerAmount(0, 0,
currency)) == true)
}
@@ -235,9 +235,9 @@ class DatabaseTest {
val uuid = UUID.randomUUID()
val currency = "KUDOS"
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null) // plays the exchange.
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
// insert new.
assert(db.talerWithdrawalCreate(
uuid,
@@ -305,9 +305,9 @@ class DatabaseTest {
)
val fooId = db.customerCreate(customerFoo)
assert(fooId != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null)
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
assert(db.cashoutCreate(op))
val fromDb = db.cashoutGetFromUuid(op.cashoutUuid)
assert(fromDb?.subject == op.subject && fromDb.tanConfirmationTime ==
null)
@@ -337,4 +337,4 @@ class DatabaseTest {
assert(db.cashoutDelete(op.cashoutUuid) ==
Database.CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED)
assert(db.cashoutGetFromUuid(op.cashoutUuid) != null) // previous
didn't delete.
}
-}
\ No newline at end of file
+}
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 13ffb835..9aecc484 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -60,9 +60,9 @@ class LibeuFinApiTest {
fun testHistory() {
val db = initDb()
val fooId = db.customerCreate(customerFoo); assert(fooId != null)
- assert(db.bankAccountCreate(genBankAccount(fooId!!)))
+ assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null)
val barId = db.customerCreate(customerBar); assert(barId != null)
- assert(db.bankAccountCreate(genBankAccount(barId!!)))
+ assert(db.bankAccountCreate(genBankAccount(barId!!)) != null)
for (i in 1..10) { db.bankTransactionCreate(genTx("test-$i")) }
testApplication {
application {
@@ -91,10 +91,10 @@ class LibeuFinApiTest {
val db = initDb()
// foo account
val fooId = db.customerCreate(customerFoo); assert(fooId != null)
- assert(db.bankAccountCreate(genBankAccount(fooId!!)))
+ assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null)
// bar account
val barId = db.customerCreate(customerBar); assert(barId != null)
- assert(db.bankAccountCreate(genBankAccount(barId!!)))
+ assert(db.bankAccountCreate(genBankAccount(barId!!)) != null)
// accounts exist, now create one transaction.
testApplication {
application {
@@ -185,7 +185,7 @@ class LibeuFinApiTest {
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = customerRowId!!
)
- ))
+ ) != null)
testApplication {
application {
corebankWebApp(db)
@@ -208,7 +208,7 @@ class LibeuFinApiTest {
internalPaytoUri = "payto://iban/SANDBOXX/ADMIN-IBAN",
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = adminRowId!!
- )))
+ )) != null)
client.get("/accounts/foo") {
expectSuccess = true
basicAuth("admin", "admin")
@@ -290,4 +290,4 @@ class LibeuFinApiTest {
assert(resp.status == HttpStatusCode.Created)
}
}
-}
\ No newline at end of file
+}
diff --git a/bank/src/test/kotlin/TalerApiTest.kt
b/bank/src/test/kotlin/TalerApiTest.kt
index 2f6f3e0a..7cdcd0eb 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -49,9 +49,9 @@ class TalerApiTest {
val db = initDb()
// Creating the exchange and merchant accounts first.
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null)
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
// Give the exchange reasonable debt allowance:
assert(db.bankAccountSetMaxDebt(
1L,
@@ -126,9 +126,9 @@ class TalerApiTest {
fun historyIncoming() {
val db = initDb()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null)
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
// Give Foo reasonable debt allowance:
assert(db.bankAccountSetMaxDebt(
1L,
@@ -161,9 +161,9 @@ class TalerApiTest {
fun addIncoming() {
val db = initDb()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null)
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
// Give Bar reasonable debt allowance:
assert(db.bankAccountSetMaxDebt(
2L,
@@ -192,7 +192,7 @@ class TalerApiTest {
val db = initDb()
val uuid = UUID.randomUUID()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
db.configSet(
"suggested_exchange",
"payto://suggested-exchange"
@@ -224,7 +224,7 @@ class TalerApiTest {
val db = initDb()
val uuid = UUID.randomUUID()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
db.configSet(
"suggested_exchange",
"payto://suggested-exchange"
@@ -251,7 +251,7 @@ class TalerApiTest {
val db = initDb()
val uuid = UUID.randomUUID()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
// insert new.
assert(db.talerWithdrawalCreate(
opUUID = uuid,
@@ -277,7 +277,7 @@ class TalerApiTest {
fun withdrawalCreation() {
val db = initDb()
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
testApplication {
application {
corebankWebApp(db)
@@ -305,9 +305,9 @@ class TalerApiTest {
val db = initDb()
// Creating Foo as the wallet owner and Bar as the exchange.
assert(db.customerCreate(customerFoo) != null)
- assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
assert(db.customerCreate(customerBar) != null)
- assert(db.bankAccountCreate(bankAccountBar))
+ assert(db.bankAccountCreate(bankAccountBar) != null)
// Artificially making a withdrawal operation for Foo.
val uuid = UUID.randomUUID()
@@ -363,4 +363,4 @@ class TalerApiTest {
)
assert(withPort ==
"taler://withdraw/www.example.com:9876/taler-integration/my-id")
}
-}
\ No newline at end of file
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: Implementing registration bonus,
gnunet <=