summaryrefslogtreecommitdiff
path: root/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
diff options
context:
space:
mode:
Diffstat (limited to 'bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt')
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt174
1 files changed, 48 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 fcbe3a36..985ba707 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -562,134 +562,53 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
+suspend fun ApplicationCall.cashoutHttp(db: Database, ctx: BankConfig, req: CashoutRequest, is2fa: Boolean) {
+ ctx.checkRegionalCurrency(req.amount_debit)
+ ctx.checkFiatCurrency(req.amount_credit)
+
+ val res = db.cashout.create(
+ login = username,
+ requestUid = req.request_uid,
+ amountDebit = req.amount_debit,
+ amountCredit = req.amount_credit,
+ subject = req.subject ?: "", // TODO default subject
+ now = Instant.now(),
+ is2fa = is2fa
+ )
+ when (res) {
+ CashoutCreationResult.AccountNotFound -> throw unknownAccount(username)
+ CashoutCreationResult.BadConversion -> throw conflict(
+ "Wrong currency conversion",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
+ CashoutCreationResult.AccountIsExchange -> throw conflict(
+ "Exchange account cannot perform cashout operation",
+ TalerErrorCode.BANK_ACCOUNT_IS_EXCHANGE
+ )
+ CashoutCreationResult.BalanceInsufficient -> throw conflict(
+ "Insufficient funds to withdraw with Taler",
+ TalerErrorCode.BANK_UNALLOWED_DEBIT
+ )
+ CashoutCreationResult.RequestUidReuse -> throw conflict(
+ "request_uid used already",
+ TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED
+ )
+ CashoutCreationResult.NoCashoutPayto -> throw conflict(
+ "Missing cashout payto uri",
+ TalerErrorCode.BANK_CONFIRM_INCOMPLETE
+ )
+ CashoutCreationResult.TanRequired -> {
+ respondChallenge(db, Operation.cashout, req)
+ }
+ is CashoutCreationResult.Success -> respond(CashoutResponse(res.id))
+ }
+}
+
private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditional(ctx.allowConversion) {
auth(db, TokenScope.readwrite) {
post("/accounts/{USERNAME}/cashouts") {
val req = call.receive<CashoutRequest>()
-
- ctx.checkRegionalCurrency(req.amount_debit)
- ctx.checkFiatCurrency(req.amount_credit)
-
- val tanChannel = req.tan_channel ?: TanChannel.sms
- val tanScript = ctx.tanChannels.get(tanChannel) ?: throw libeufinError(
- HttpStatusCode.NotImplemented,
- "Unsupported tan channel $tanChannel",
- TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED
- )
-
- val res = db.cashout.create(
- login = username,
- requestUid = req.request_uid,
- amountDebit = req.amount_debit,
- amountCredit = req.amount_credit,
- subject = req.subject ?: "", // TODO default subject
- tanChannel = tanChannel,
- tanCode = Tan.genCode(),
- now = Instant.now(),
- retryCounter = TAN_RETRY_COUNTER,
- validityPeriod = TAN_VALIDITY_PERIOD
- )
- when (res) {
- is CashoutCreationResult.AccountNotFound -> throw unknownAccount(username)
- is CashoutCreationResult.BadConversion -> throw conflict(
- "Wrong currency conversion",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- is CashoutCreationResult.AccountIsExchange -> throw conflict(
- "Exchange account cannot perform cashout operation",
- TalerErrorCode.BANK_ACCOUNT_IS_EXCHANGE
- )
- is CashoutCreationResult.BalanceInsufficient -> throw conflict(
- "Insufficient funds to withdraw with Taler",
- TalerErrorCode.BANK_UNALLOWED_DEBIT
- )
- is CashoutCreationResult.MissingTanInfo -> throw conflict(
- "Account '$username' missing info for tan channel ${req.tan_channel}",
- TalerErrorCode.BANK_MISSING_TAN_INFO
- )
- is CashoutCreationResult.RequestUidReuse -> throw conflict(
- "request_uid used already",
- TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED
- )
- is CashoutCreationResult.Success -> {
- res.tanCode?.run {
- val exitValue = withContext(Dispatchers.IO) {
- val process = ProcessBuilder(tanScript, res.tanInfo).start()
- try {
- process.outputWriter().use { it.write(res.tanCode) }
- process.onExit().await()
- } catch (e: Exception) {
- process.destroy()
- }
- process.exitValue()
- }
- if (exitValue != 0) {
- throw libeufinError(
- HttpStatusCode.BadGateway,
- "Tan channel script failure with exit value $exitValue",
- TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED
- )
- }
- db.cashout.markSent(res.id, Instant.now(), TAN_RETRANSMISSION_PERIOD, tanChannel, res.tanInfo)
- }
- call.respond(CashoutPending(res.id))
- }
- }
- }
- post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/abort") {
- val id = call.longUriComponent("CASHOUT_ID")
- when (db.cashout.abort(id, username)) {
- AbortResult.UnknownOperation -> throw notFound(
- "Cashout operation $id not found",
- TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
- )
- AbortResult.AlreadyConfirmed -> throw conflict(
- "Cannot abort confirmed cashout",
- TalerErrorCode.BANK_ABORT_CONFIRM_CONFLICT
- )
- AbortResult.Success -> call.respond(HttpStatusCode.NoContent)
- }
- }
- post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/confirm") {
- val req = call.receive<CashoutConfirm>()
- val id = call.longUriComponent("CASHOUT_ID")
- when (db.cashout.confirm(
- id = id,
- login = username,
- tanCode = req.tan,
- timestamp = Instant.now()
- )) {
- CashoutConfirmationResult.OP_NOT_FOUND -> throw notFound(
- "Cashout operation $id not found",
- TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
- )
- CashoutConfirmationResult.ABORTED -> throw conflict(
- "Cannot confirm an aborted cashout",
- TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT
- )
- CashoutConfirmationResult.BAD_TAN_CODE -> throw conflict(
- "Incorrect TAN code",
- TalerErrorCode.BANK_TAN_CHALLENGE_FAILED
- )
- CashoutConfirmationResult.NO_RETRY -> throw libeufinError(
- HttpStatusCode.TooManyRequests,
- "Too many failed confirmation attempt",
- TalerErrorCode.BANK_TAN_RATE_LIMITED
- )
- CashoutConfirmationResult.NO_CASHOUT_PAYTO -> throw conflict(
- "Missing cashout payto uri",
- TalerErrorCode.BANK_CONFIRM_INCOMPLETE
- )
- CashoutConfirmationResult.BALANCE_INSUFFICIENT -> throw conflict(
- "Insufficient funds",
- TalerErrorCode.BANK_UNALLOWED_DEBIT
- )
- CashoutConfirmationResult.BAD_CONVERSION -> throw conflict(
- "Wrong currency conversion",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- CashoutConfirmationResult.SUCCESS -> call.respond(HttpStatusCode.NoContent)
- }
+ call.cashoutHttp(db, ctx, req, false)
}
}
auth(db, TokenScope.readonly) {
@@ -724,7 +643,6 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio
}
}
-
private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) {
auth(db, TokenScope.readwrite) {
post("/accounts/{USERNAME}/challenge/{CHALLENGE_ID}") {
@@ -774,7 +692,7 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) {
}
post("/accounts/{USERNAME}/challenge/{CHALLENGE_ID}/confirm") {
val id = call.longUriComponent("CHALLENGE_ID")
- val req = call.receive<CashoutConfirm>()
+ val req = call.receive<ChallengeSolve>()
val res = db.tan.solve(
id = id,
login = username,
@@ -811,6 +729,10 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) {
val req = Json.decodeFromString<TransactionCreateRequest>(res.body)
call.bankTransactionHttp(db, ctx, req, true)
}
+ Operation.cashout -> {
+ val req = Json.decodeFromString<CashoutRequest>(res.body)
+ call.cashoutHttp(db, ctx, req, true)
+ }
}
}
}