summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2023-10-16 11:45:45 +0000
committerAntoine A <>2023-10-16 11:45:45 +0000
commitdc895a89e77636bb46964ffbfd2d4d9194c6f286 (patch)
tree825f8fbd8011d8c3007ca98484f9d62161c22757
parent3de53c1b7ed09bb61d375f33770dd5dc109576cc (diff)
downloadlibeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.tar.gz
libeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.tar.bz2
libeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.zip
Improve and fix transaction creation endpoint
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt52
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt6
-rw-r--r--bank/src/test/kotlin/CoreBankApiTest.kt (renamed from bank/src/test/kotlin/LibeuFinApiTest.kt)177
3 files changed, 109 insertions, 126 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index 3526e8c3..220ad654 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -118,16 +118,9 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
val publicAccounts = db.accountsGetPublic(ctx.currency)
if (publicAccounts.isEmpty()) {
call.respond(HttpStatusCode.NoContent)
- return@get
+ } else {
+ call.respond(PublicAccountsResponse(publicAccounts))
}
- call.respond(
- PublicAccountsResponse().apply {
- publicAccounts.forEach {
- this.public_accounts.add(it)
- }
- }
- )
- return@get
}
get("/accounts") {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized()
@@ -138,19 +131,12 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
val queryParam = if (maybeFilter != null) {
"%${maybeFilter}%"
} else "%"
- val dbRes = db.accountsGetForAdmin(queryParam)
- if (dbRes.isEmpty()) {
+ val accounts = db.accountsGetForAdmin(queryParam)
+ if (accounts.isEmpty()) {
call.respond(HttpStatusCode.NoContent)
- return@get
+ } else {
+ call.respond(ListBankAccountsResponse(accounts))
}
- call.respond(
- ListBankAccountsResponse().apply {
- dbRes.forEach { element ->
- this.accounts.add(element)
- }
- }
- )
- return@get
}
post("/accounts") { // check if only admin is allowed to create new accounts
if (ctx.restrictRegistration) {
@@ -514,21 +500,17 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
val resourceName = call.expectUriComponent("USERNAME") // admin has no rights here.
if ((c.login != resourceName) && (call.getAuthToken() == null)) throw forbidden()
val tx = call.receive<BankAccountTransactionCreate>()
+
val subject = tx.payto_uri.message ?: throw badRequest("Wire transfer lacks subject")
- val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
- ?: throw internalServerError("Debtor bank account not found")
- if (tx.amount.currency != ctx.currency) throw badRequest(
- "Wrong currency: ${tx.amount.currency}",
+ val amount = tx.payto_uri.amount ?: tx.amount
+ if (amount == null) throw badRequest("Wire transfer lacks amount")
+ if (amount.currency != ctx.currency) throw badRequest(
+ "Wrong currency: ${amount.currency}",
talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
)
- if (!isBalanceEnough(
- balance = debtorBankAccount.expectBalance(),
- due = tx.amount,
- hasBalanceDebt = debtorBankAccount.hasDebt,
- maxDebt = debtorBankAccount.maxDebt
- ))
- throw conflict(hint = "Insufficient balance.", talerEc = TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT)
- logger.info("creditor payto: ${tx.payto_uri}")
+ // TODO rewrite all thos database query in a single database function
+ val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
+ ?: throw internalServerError("Debtor bank account not found")
val creditorBankAccount = db.bankAccountGetFromInternalPayto(tx.payto_uri)
?: throw notFound(
"Creditor account not found",
@@ -538,11 +520,10 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
debtorAccountId = debtorBankAccount.expectRowId(),
creditorAccountId = creditorBankAccount.expectRowId(),
subject = subject,
- amount = tx.amount,
+ amount = amount,
transactionDate = Instant.now()
)
- val res = db.bankTransactionCreate(dbInstructions)
- when (res) {
+ when (db.bankTransactionCreate(dbInstructions)) {
BankTransactionResult.BALANCE_INSUFFICIENT -> throw conflict(
"Insufficient funds",
TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT
@@ -555,7 +536,6 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) {
BankTransactionResult.NO_DEBTOR -> throw internalServerError("Debtor not found despite the request was authenticated.")
BankTransactionResult.SUCCESS -> call.respond(HttpStatusCode.OK)
}
- return@post
}
get("/accounts/{USERNAME}/transactions/{T_ID}") {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized()
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index 8b410061..0222c78b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -335,7 +335,7 @@ data class AccountMinimalData(
*/
@Serializable
data class ListBankAccountsResponse(
- val accounts: MutableList<AccountMinimalData> = mutableListOf()
+ val accounts: List<AccountMinimalData>
)
/**
@@ -357,7 +357,7 @@ data class AccountData(
@Serializable
data class BankAccountTransactionCreate(
val payto_uri: IbanPayTo,
- val amount: TalerAmount
+ val amount: TalerAmount?
)
/* History element, either from GET /transactions/T_ID
@@ -602,7 +602,7 @@ data class TransferResponse(
*/
@Serializable
data class PublicAccountsResponse(
- val public_accounts: MutableList<PublicAccount> = mutableListOf()
+ val public_accounts: List<PublicAccount>
)
/**
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
index 17e60dcc..ea8cc055 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -49,15 +49,11 @@ class LibeuFinApiTest {
)
@Test
- fun getConfig() = setup { db, ctx ->
- testApplication {
- application { corebankWebApp(db, ctx) }
- val r = client.get("/config") {
- expectSuccess = true
- }
- println(r.bodyAsText())
- }
-
+ fun getConfig() = bankSetup { _ ->
+ val r = client.get("/config") {
+ expectSuccess = true
+ }.assertOk()
+ println(r.bodyAsText())
}
/**
@@ -99,85 +95,92 @@ class LibeuFinApiTest {
// Testing the creation of bank transactions.
@Test
- fun postTransactionsTest() = setup { db, ctx ->
- // foo account
- val fooId = db.customerCreate(customerFoo);
- assert(fooId != null)
- assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null)
- // bar account
- val barId = db.customerCreate(customerBar);
- assert(barId != null)
- assert(db.bankAccountCreate(genBankAccount(barId!!)) != null)
- // accounts exist, now create one transaction.
- testApplication {
- application {
- corebankWebApp(db, ctx)
- }
- client.post("/accounts/foo/transactions") {
- expectSuccess = true
- basicAuth("foo", "pw")
- contentType(ContentType.Application.Json)
- // expectSuccess = true
- setBody(
- """{
- "payto_uri": "payto://iban/AC${barId}?message=payout",
- "amount": "KUDOS:3.3"
- }
- """.trimIndent()
- )
- }
- // Getting the only tx that exists in the DB, hence has ID == 1.
- val r = client.get("/accounts/foo/transactions/1") {
- basicAuth("foo", "pw")
- expectSuccess = true
- }
- val obj: BankAccountTransactionInfo = Json.decodeFromString(r.bodyAsText())
- assert(obj.subject == "payout")
- // Testing the wrong currency.
- val wrongCurrencyResp = client.post("/accounts/foo/transactions") {
- expectSuccess = false
- basicAuth("foo", "pw")
- contentType(ContentType.Application.Json)
- // expectSuccess = true
- setBody(
- """{
- "payto_uri": "payto://iban/AC${barId}?message=payout",
- "amount": "EUR:3.3"
- }
- """.trimIndent()
- )
- }
- assert(wrongCurrencyResp.status == HttpStatusCode.BadRequest)
- // Surpassing the debt limit.
- val unallowedDebtResp = client.post("/accounts/foo/transactions") {
- expectSuccess = false
- basicAuth("foo", "pw")
- contentType(ContentType.Application.Json)
- // expectSuccess = true
- setBody(
- """{
- "payto_uri": "payto://iban/AC${barId}?message=payout",
- "amount": "KUDOS:555"
- }
- """.trimIndent()
- )
- }
- assert(unallowedDebtResp.status == HttpStatusCode.Conflict)
- val bigAmount = client.post("/accounts/foo/transactions") {
- expectSuccess = false
- basicAuth("foo", "pw")
- contentType(ContentType.Application.Json)
- // expectSuccess = true
- setBody(
- """{
- "payto_uri": "payto://iban/AC${barId}?message=payout",
- "amount": "KUDOS:${"5".repeat(200)}"
- }
- """.trimIndent()
- )
- }
- assert(bigAmount.status == HttpStatusCode.BadRequest)
+ fun postTransactionsTest() = bankSetup { _ ->
+ val valid_req = json {
+ "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout"
+ "amount" to "KUDOS:0.3"
+ }
+
+ // Check ok
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ jsonBody(valid_req)
+ }.assertOk()
+ client.get("/accounts/merchant/transactions/1") {
+ basicAuth("merchant", "merchant-password")
+ }.assertOk().run {
+ val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText())
+ assertEquals("payout", tx.subject)
+ assertEquals(TalerAmount("KUDOS:0.3"), tx.amount)
+ }
+ // Check amount in payto_uri
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ jsonBody(json {
+ "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout2&amount=KUDOS:1.05"
+ })
+ }.assertOk()
+ client.get("/accounts/merchant/transactions/3") {
+ basicAuth("merchant", "merchant-password")
+ }.assertOk().run {
+ val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText())
+ assertEquals("payout2", tx.subject)
+ assertEquals(TalerAmount("KUDOS:1.05"), tx.amount)
+ }
+ // Check amount in payto_uri precedence
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ jsonBody(json {
+ "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout3&amount=KUDOS:1.05"
+ "amount" to "KUDOS:10.003"
+ })
+ }.assertOk()
+ client.get("/accounts/merchant/transactions/5") {
+ basicAuth("merchant", "merchant-password")
+ }.assertOk().run {
+ val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText())
+ assertEquals("payout3", tx.subject)
+ assertEquals(TalerAmount("KUDOS:1.05"), tx.amount)
}
+ // Testing the wrong currency
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ jsonBody(json(valid_req) {
+ "amount" to "EUR:3.3"
+ })
+ }.assertBadRequest()
+ // Surpassing the debt limit
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ contentType(ContentType.Application.Json)
+ jsonBody(json(valid_req) {
+ "amount" to "KUDOS:555"
+ })
+ }.assertStatus(HttpStatusCode.Conflict)
+ // Missing message
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ contentType(ContentType.Application.Json)
+ jsonBody(json(valid_req) {
+ "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ"
+ })
+ }.assertBadRequest()
+ // Unknown account
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ contentType(ContentType.Application.Json)
+ jsonBody(json(valid_req) {
+ "payto_uri" to "payto://iban/UNKNOWN-IBAN-XYZ?message=payout"
+ })
+ }.assertStatus(HttpStatusCode.NotFound)
+ // Transaction to self
+ client.post("/accounts/merchant/transactions") {
+ basicAuth("merchant", "merchant-password")
+ contentType(ContentType.Application.Json)
+ jsonBody(json(valid_req) {
+ "payto_uri" to "payto://iban/MERCHANT-IBAN-XYZ?message=payout"
+ })
+ }.assertStatus(HttpStatusCode.Conflict)
}
@Test