/* * This file is part of LibEuFin. * Copyright (C) 2023 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3, or * (at your option) any later version. * LibEuFin is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General * Public License for more details. * You should have received a copy of the GNU Affero General Public * License along with LibEuFin; see the file COPYING. If not, see * */ package tech.libeufin.bank.db import tech.libeufin.bank.* import tech.libeufin.common.* import tech.libeufin.common.db.* import java.time.Instant /** Data access logic for cashout operations */ class CashoutDAO(private val db: Database) { /** Result of cashout operation creation */ sealed interface CashoutCreationResult { data class Success(val id: Long): CashoutCreationResult data object UnderMin: CashoutCreationResult data object BadConversion: CashoutCreationResult data object AccountNotFound: CashoutCreationResult data object AccountIsExchange: CashoutCreationResult data object BalanceInsufficient: CashoutCreationResult data object RequestUidReuse: CashoutCreationResult data object NoCashoutPayto: CashoutCreationResult data object TanRequired: CashoutCreationResult } /** Create a new cashout operation */ suspend fun create( login: String, requestUid: ShortHashCode, amountDebit: TalerAmount, amountCredit: TalerAmount, subject: String, now: Instant, is2fa: Boolean ): CashoutCreationResult = db.serializable { conn -> val stmt = conn.prepareStatement(""" SELECT out_bad_conversion, out_account_not_found, out_account_is_exchange, out_balance_insufficient, out_request_uid_reuse, out_no_cashout_payto, out_tan_required, out_cashout_id, out_under_min FROM cashout_create(?,?,(?,?)::taler_amount,(?,?)::taler_amount,?,?,?) """) stmt.setString(1, login) stmt.setBytes(2, requestUid.raw) stmt.setLong(3, amountDebit.value) stmt.setInt(4, amountDebit.frac) stmt.setLong(5, amountCredit.value) stmt.setInt(6, amountCredit.frac) stmt.setString(7, subject) stmt.setLong(8, now.micros()) stmt.setBoolean(9, is2fa) stmt.executeQuery().use { when { !it.next() -> throw internalServerError("No result from DB procedure cashout_create") it.getBoolean("out_under_min") -> CashoutCreationResult.UnderMin it.getBoolean("out_bad_conversion") -> CashoutCreationResult.BadConversion it.getBoolean("out_account_not_found") -> CashoutCreationResult.AccountNotFound it.getBoolean("out_account_is_exchange") -> CashoutCreationResult.AccountIsExchange it.getBoolean("out_balance_insufficient") -> CashoutCreationResult.BalanceInsufficient it.getBoolean("out_request_uid_reuse") -> CashoutCreationResult.RequestUidReuse it.getBoolean("out_no_cashout_payto") -> CashoutCreationResult.NoCashoutPayto it.getBoolean("out_tan_required") -> CashoutCreationResult.TanRequired else -> CashoutCreationResult.Success(it.getLong("out_cashout_id")) } } } /** Get status of cashout operation [id] owned by [login] */ suspend fun get(id: Long, login: String): CashoutStatusResponse? = db.conn { conn -> val stmt = conn.prepareStatement(""" SELECT (amount_debit).val as amount_debit_val ,(amount_debit).frac as amount_debit_frac ,(amount_credit).val as amount_credit_val ,(amount_credit).frac as amount_credit_frac ,cashout_operations.subject ,creation_time ,transaction_date as confirmation_date ,tan_channel ,CASE tan_channel WHEN 'sms' THEN phone WHEN 'email' THEN email END as tan_info FROM cashout_operations JOIN bank_accounts ON bank_account=bank_account_id JOIN customers ON owning_customer_id=customer_id LEFT JOIN bank_account_transactions ON local_transaction=bank_transaction_id WHERE cashout_id=? AND login=? """) stmt.setLong(1, id) stmt.setString(2, login) stmt.oneOrNull { CashoutStatusResponse( status = CashoutStatus.confirmed, amount_debit = it.getAmount("amount_debit", db.bankCurrency), amount_credit = it.getAmount("amount_credit", db.fiatCurrency!!), subject = it.getString("subject"), creation_time = it.getTalerTimestamp("creation_time"), confirmation_time = when (val timestamp = it.getLong("confirmation_date")) { 0L -> null else -> TalerProtocolTimestamp(timestamp.asInstant()) }, tan_channel = it.getString("tan_channel")?.run { TanChannel.valueOf(this) }, tan_info = it.getString("tan_info"), ) } } /** Get a page of all cashout operations */ suspend fun pageAll(params: PageParams): List = db.page(params, "cashout_id", """ SELECT cashout_id ,login FROM cashout_operations JOIN bank_accounts ON bank_account=bank_account_id JOIN customers ON owning_customer_id=customer_id WHERE """) { GlobalCashoutInfo( cashout_id = it.getLong("cashout_id"), username = it.getString("login"), status = CashoutStatus.confirmed ) } /** Get a page of all cashout operations owned by [login] */ suspend fun pageForUser(params: PageParams, login: String): List = db.page(params, "cashout_id", """ SELECT cashout_id FROM cashout_operations JOIN bank_accounts ON bank_account=bank_account_id JOIN customers ON owning_customer_id=customer_id WHERE login = ? AND """, bind = { setString(1, login) 1 } ) { CashoutInfo( cashout_id = it.getLong("cashout_id"), status = CashoutStatus.confirmed ) } }