libeufin

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

commit 677d30af69503e7bb0a895a9dec8f2694b763e1f
parent 46e4838f80db6f0b086c579c4bede065fc630025
Author: MS <ms@taler.net>
Date:   Fri, 29 Sep 2023 10:14:52 +0200

Stop using amounts as strings.

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt | 25++++++++++++++-----------
Mbank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt | 25++++++++++++-------------
Mbank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt | 7++-----
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt | 11+++++------
Mbank/src/test/kotlin/JsonTest.kt | 6++++++
6 files changed, 40 insertions(+), 36 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt @@ -19,6 +19,7 @@ package tech.libeufin.bank +import CreditDebitIndicator import io.ktor.http.* import io.ktor.server.application.* import kotlinx.serialization.Serializable @@ -357,12 +358,14 @@ data class Config( val fiat_currency: String? = null ) +enum class CorebankCreditDebitInfo { + credit, debit +} + @Serializable data class Balance( - // FIXME: Should not be a string - val amount: String, - // FIXME: Should not be a string - val credit_debit_indicator: String, + val amount: TalerAmount, + val credit_debit_indicator: CorebankCreditDebitInfo, ) /** @@ -384,7 +387,7 @@ data class AccountData( @Serializable data class BankAccountTransactionCreate( val payto_uri: String, - val amount: String + val amount: TalerAmount ) /* History element, either from GET /transactions/T_ID @@ -393,7 +396,7 @@ data class BankAccountTransactionCreate( data class BankAccountTransactionInfo( val creditor_payto_uri: String, val debtor_payto_uri: String, - val amount: String, + val amount: TalerAmount, val direction: TransactionDirection, val subject: String, val row_id: Long, // is T_ID @@ -409,7 +412,7 @@ data class BankAccountTransactionsResponse( // Taler withdrawal request. @Serializable data class BankAccountCreateWithdrawalRequest( - val amount: String + val amount: TalerAmount ) // Taler withdrawal response. @@ -422,7 +425,7 @@ data class BankAccountCreateWithdrawalResponse( // Taler withdrawal details response @Serializable data class BankAccountGetWithdrawalResponse( - val amount: String, + val amount: TalerAmount, val aborted: Boolean, val confirmation_done: Boolean, val selection_done: Boolean, @@ -504,7 +507,7 @@ data class BankWithdrawalOperationStatus( /* Amount that will be withdrawn with this operation (raw amount without fee considerations). */ - val amount: String, + val amount: TalerAmount, /* Bank account of the customer that is withdrawing, as a ``payto`` URI. */ @@ -547,7 +550,7 @@ data class BankWithdrawalOperationPostResponse( */ @Serializable data class AddIncomingRequest( - val amount: String, + val amount: TalerAmount, val reserve_pub: String, val debit_account: String ) @@ -585,7 +588,7 @@ data class IncomingReserveTransaction( val type: String = "RESERVE", val row_id: Long, // DB row ID of the payment. val date: TalerProtocolTimestamp, - val amount: String, + val amount: TalerAmount, val debit_account: String, // Payto of the sender. val reserve_pub: String ) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt @@ -207,10 +207,11 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { val bankAccountData = db.bankAccountGetFromOwnerId(customerInternalId) ?: throw internalServerError("Customer '${c.login} had no bank account despite they are customer.'") val balance = Balance( - amount = bankAccountData.balance.toString(), credit_debit_indicator = if (bankAccountData.hasDebt) { - "debit" + amount = bankAccountData.balance ?: throw internalServerError("Account '${c.login}' lacks balance!"), + credit_debit_indicator = if (bankAccountData.hasDebt) { + CorebankCreditDebitInfo.debit } else { - "credit" + CorebankCreditDebitInfo.credit } ) call.respond( @@ -236,9 +237,8 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { val req = call.receive<BankAccountCreateWithdrawalRequest>() // Checking that the user has enough funds. val b = db.bankAccountGetFromOwnerId(c.expectRowId()) ?: throw internalServerError("Customer '${c.login}' lacks bank account.") - val withdrawalAmount = parseTalerAmount(req.amount) if (!isBalanceEnough( - balance = b.expectBalance(), due = withdrawalAmount, maxDebt = b.maxDebt, hasBalanceDebt = b.hasDebt + balance = b.expectBalance(), due = req.amount, maxDebt = b.maxDebt, hasBalanceDebt = b.hasDebt ) ) throw forbidden( hint = "Insufficient funds to withdraw with Taler", @@ -246,7 +246,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { ) // Auth and funds passed, create the operation now! val opId = UUID.randomUUID() if (!db.talerWithdrawalCreate( - opId, b.expectRowId(), withdrawalAmount + opId, b.expectRowId(), req.amount ) ) throw internalServerError("Bank failed at creating the withdraw operation.") @@ -263,7 +263,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id")) call.respond( BankAccountGetWithdrawalResponse( - amount = op.amount.toString(), + amount = op.amount, aborted = op.aborted, confirmation_done = op.confirmationDone, selection_done = op.selectionDone, @@ -354,7 +354,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { debtor_payto_uri = it.debtorPaytoUri, creditor_payto_uri = it.creditorPaytoUri, subject = it.subject, - amount = it.amount.toString(), + amount = it.amount, direction = it.direction, date = TalerProtocolTimestamp(it.transactionDate), row_id = it.dbRowId ?: throw internalServerError( @@ -382,15 +382,14 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { val creditorCustomerData = db.bankAccountGetFromInternalPayto(paytoWithoutParams) ?: throw notFound( "Creditor account not found", TalerErrorCode.TALER_EC_END // FIXME: define this EC. ) - val amount = parseTalerAmount(txData.amount) - if (amount.currency != ctx.currency) throw badRequest( - "Wrong currency: ${amount.currency}", talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH + if (txData.amount.currency != ctx.currency) throw badRequest( + "Wrong currency: ${txData.amount.currency}", talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH ) val dbInstructions = BankInternalTransaction( debtorAccountId = debtorId, creditorAccountId = creditorCustomerData.owningCustomerId, subject = subject, - amount = amount, + amount = txData.amount, transactionDate = Instant.now() ) val res = db.bankTransactionCreate(dbInstructions) @@ -428,7 +427,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) { if (tx.bankAccountId != customerBankAccount.bankAccountId) throw forbidden("Client has no rights over the bank transaction: $tId") // auth and rights, respond. call.respond( BankAccountTransactionInfo( - amount = "${tx.amount.currency}:${tx.amount.value}.${tx.amount.frac}", + amount = tx.amount, creditor_payto_uri = tx.creditorPaytoUri, debtor_payto_uri = tx.debtorPaytoUri, date = TalerProtocolTimestamp(tx.transactionDate), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt @@ -40,11 +40,8 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx: BankApplicationContext) val wopid = call.expectUriComponent("wopid") val op = getWithdrawal(db, wopid) // throws 404 if not found. val relatedBankAccount = db.bankAccountGetFromOwnerId(op.walletBankAccount) - if (relatedBankAccount == null) throw internalServerError("Bank has a withdrawal not related to any bank account.") + ?: throw internalServerError("Bank has a withdrawal not related to any bank account.") val suggestedExchange = ctx.suggestedWithdrawalExchange - val walletCustomer = db.customerGetFromRowId(relatedBankAccount.owningCustomerId) - if (walletCustomer == null) - throw internalServerError("Could not get the username that owns this withdrawal") val confirmUrl = if (ctx.spaCaptchaURL == null) null else getWithdrawalConfirmUrl( baseUrl = ctx.spaCaptchaURL, @@ -55,7 +52,7 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx: BankApplicationContext) aborted = op.aborted, selection_done = op.selectionDone, transfer_done = op.confirmationDone, - amount = op.amount.toString(), + amount = op.amount, sender_wire = relatedBankAccount.internalPaytoUri, suggested_exchange = suggestedExchange, confirm_transfer_url = confirmUrl diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -239,7 +239,7 @@ object TalerAmountSerializer : KSerializer<TalerAmount> { PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: TalerAmount) { - throw internalServerError("Encoding of TalerAmount not implemented.") // API doesn't require this. + encoder.encodeString(value.toString()) } override fun deserialize(decoder: Decoder): TalerAmount { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt @@ -59,7 +59,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) resp.incoming_transactions.add( IncomingReserveTransaction( row_id = it.expectRowId(), - amount = it.amount.toString(), + amount = it.amount, date = TalerProtocolTimestamp(it.transactionDate), debit_account = it.debtorPaytoUri, reserve_pub = it.subject @@ -133,9 +133,8 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: throw unauthorized() if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) throw forbidden() val req = call.receive<AddIncomingRequest>() - val amount = parseTalerAmount(req.amount) val internalCurrency = ctx.currency - if (amount.currency != internalCurrency) + if (req.amount.currency != internalCurrency) throw badRequest( "Currency mismatch", TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH @@ -155,7 +154,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) val txTimestamp = Instant.now() val op = BankInternalTransaction( debtorAccountId = walletAccount.expectRowId(), - amount = amount, + amount = req.amount, creditorAccountId = exchangeAccount.expectRowId(), transactionDate = txTimestamp, subject = req.reserve_pub @@ -180,5 +179,4 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) ) return@post } -} - +} +\ No newline at end of file diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt @@ -46,4 +46,10 @@ class JsonTest { val foreverSerial = Json.encodeToString(forever) assert(Json.decodeFromString<RelativeTime>(foreverSerial).d_us == forever.d_us) } + + @Test + fun enumSerializer() { + assert("\"credit\"" == Json.encodeToString(CorebankCreditDebitInfo.credit)) + assert("\"debit\"" == Json.encodeToString(CorebankCreditDebitInfo.debit)) + } } \ No newline at end of file