libeufin

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

commit df3a7c52244a7febee27b4c1254c1643683d8bba
parent aecf4112ab5d8c56a9be3a0458f227f2b61a4d2b
Author: Antoine A <>
Date:   Tue, 24 Oct 2023 15:26:45 +0000

Prepare core bank cashout API

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 40++++++++++++++++++++++++++++++++++++++--
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mbank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 13++++++++++---
4 files changed, 103 insertions(+), 8 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -31,6 +31,7 @@ fun Routing.coreBankApi(db: Database, ctx: BankApplicationContext) { coreBankAccountsMgmtApi(db, ctx) coreBankTransactionsApi(db, ctx) coreBankWithdrawalApi(db, ctx) + coreBankCashoutApi(db, ctx) } private fun Routing.coreBankTokenApi(db: Database) { @@ -384,7 +385,7 @@ private fun Routing.coreBankTransactionsApi(db: Database, ctx: BankApplicationCo val subject = tx.payto_uri.message ?: throw badRequest("Wire transfer lacks subject") val amount = tx.payto_uri.amount ?: tx.amount ?: throw badRequest("Wire transfer lacks amount") - checkInternalCurrency(ctx, amount) + ctx.checkInternalCurrency(amount) val result = db.bankTransaction( creditAccountPayto = tx.payto_uri, debitAccountUsername = login, @@ -419,7 +420,7 @@ fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankApplicationContext) { val (login, _) = call.authCheck(db, TokenScope.readwrite) val req = call.receive<BankAccountCreateWithdrawalRequest>() // Checking that the user has enough funds. - checkInternalCurrency(ctx, req.amount) + ctx.checkInternalCurrency(req.amount) val opId = UUID.randomUUID() when (db.talerWithdrawalCreate(login, opId, req.amount)) { @@ -511,4 +512,39 @@ fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankApplicationContext) { WithdrawalConfirmationResult.SUCCESS -> call.respond(HttpStatusCode.NoContent) } } +} + +fun Routing.coreBankCashoutApi(db: Database, ctx: BankApplicationContext) { + post("/accounts/{USERNAME}/cashouts") { + val (login, _) = call.authCheck(db, TokenScope.readwrite) + val req = call.receive<CashoutRequest>() // Checking that the user has enough funds. + + ctx.checkInternalCurrency(req.amount_debit) + ctx.checkCashoutCurrency(req.amount_credit) + + // TODO + } + post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/abort") { + val (login, _) = call.authCheck(db, TokenScope.readwrite) + // TODO + } + post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/confirm") { + val (login, _) = call.authCheck(db, TokenScope.readwrite) + // TODO + } + get("/accounts/{USERNAME}/cashouts") { + val (login, _) = call.authCheck(db, TokenScope.readonly) + // TODO + } + get("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}") { + val (login, _) = call.authCheck(db, TokenScope.readonly) + // TODO + } + get("/cashouts") { + call.authAdmin(db, TokenScope.readonly) + // TODO + } + get("/cashout-rate") { + // TODO + } } \ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -40,13 +40,17 @@ enum class FracDigits { TWO, EIGHT } - // Allowed values for bank transactions directions. enum class TransactionDirection { credit, debit } +enum class CashoutStatus { + pending, + confirmed +} + /** * HTTP response type of successful token refresh. * access_token is the Crockford encoding of the 32 byte @@ -506,6 +510,54 @@ data class BankWithdrawalOperationPostResponse( val confirm_transfer_url: String? = null ) +@Serializable +data class CashoutRequest( + val subject: String?, + val amount_debit: TalerAmount, + val amount_credit: TalerAmount, + val tan_channel: TanChannel? +) + +@Serializable +data class Cashouts( + val cashouts: List<CashoutInfo>, +) + +@Serializable +data class CashoutInfo( + val cashout_id: String, + val status: CashoutStatus, +) + + +@Serializable +data class GlobalCashouts( + val cashouts: List<GlobalCashoutInfo>, +) + +@Serializable +data class GlobalCashoutInfo( + val cashout_id: String, + val username: String, + val status: CashoutStatus, +) + +@Serializable +data class CashoutStatusResponse( + val status: CashoutStatus, + val amount_debit: TalerAmount, + val amount_credit: TalerAmount, + val subject: String, + val credit_payto_uri: IbanPayTo, + val creation_time: TalerProtocolTimestamp, + val confirmation_time: TalerProtocolTimestamp?, +) + +@Serializable +data class CashoutConfirm( + val tan: String +) + /** * Request to an /admin/add-incoming request from * the Taler Wire Gateway API. diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt @@ -45,7 +45,7 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankApplicationContext) { post("/accounts/{USERNAME}/taler-wire-gateway/transfer") { val (login, _) = call.authCheck(db, TokenScope.readwrite) val req = call.receive<TransferRequest>() - checkInternalCurrency(ctx, req.amount) + ctx.checkInternalCurrency(req.amount) val dbRes = db.talerTransferCreate( req = req, username = login, @@ -124,7 +124,7 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankApplicationContext) { post("/accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming") { val (login, _) = call.authCheck(db, TokenScope.readwrite) // TODO authAdmin ? val req = call.receive<AddIncomingRequest>() - checkInternalCurrency(ctx, req.amount) + ctx.checkInternalCurrency(req.amount) val timestamp = Instant.now() val dbRes = db.talerAddIncomingCreate( req = req, diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -116,9 +116,16 @@ fun badRequest( ) ) -fun checkInternalCurrency(ctx: BankApplicationContext, amount: TalerAmount) { - if (amount.currency != ctx.currency) throw badRequest( - "Wrong currency: expected internal currency ${ctx.currency} got ${amount.currency}", +fun BankApplicationContext.checkInternalCurrency(amount: TalerAmount) { + if (amount.currency != currency) throw badRequest( + "Wrong currency: expected internal currency $currency got ${amount.currency}", + talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH + ) +} + +fun BankApplicationContext.checkCashoutCurrency(amount: TalerAmount) { + if (amount.currency != cashoutCurrency) throw badRequest( + "Wrong currency: expected cashout currency $cashoutCurrency got ${amount.currency}", talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH ) }