summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--database-versioning/new/procedures.sql20
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt123
-rw-r--r--sandbox/src/test/kotlin/DatabaseTest.kt72
3 files changed, 186 insertions, 29 deletions
diff --git a/database-versioning/new/procedures.sql b/database-versioning/new/procedures.sql
index 17fbde32..8fd3c8c1 100644
--- a/database-versioning/new/procedures.sql
+++ b/database-versioning/new/procedures.sql
@@ -330,4 +330,24 @@ SET
WHERE bank_account_id=in_creditor_account_id;
RETURN;
END $$;
+
+CREATE OR REPLACE FUNCTION cashout_delete(
+ IN in_cashout_uuid UUID,
+ OUT out_already_confirmed BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM
+ FROM cashout_operations
+ WHERE cashout_uuid=in_cashout_uuid AND tan_confirmation_time IS NOT NULL;
+ IF FOUND
+ THEN
+ out_already_confirmed=TRUE;
+ RETURN;
+ END IF;
+ out_already_confirmed=FALSE;
+ DELETE FROM cashout_operations WHERE cashout_uuid=in_cashout_uuid;
+ RETURN;
+END $$;
COMMIT;
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
index ec6a3196..243efe2c 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
@@ -330,9 +330,18 @@ class Database(private val dbConfig: String) {
val rs = stmt.executeQuery()
rs.use {
if (!rs.next()) throw internalServerError("Bank transaction didn't properly return")
- if (rs.getBoolean("out_nx_debtor")) return BankTransactionResult.NO_DEBTOR
- if (rs.getBoolean("out_nx_creditor")) return BankTransactionResult.NO_CREDITOR
- if (rs.getBoolean("out_balance_insufficient")) return BankTransactionResult.CONFLICT
+ if (rs.getBoolean("out_nx_debtor")) {
+ logger.error("No debtor account found")
+ return BankTransactionResult.NO_DEBTOR
+ }
+ if (rs.getBoolean("out_nx_creditor")) {
+ logger.error("No creditor account found")
+ return BankTransactionResult.NO_CREDITOR
+ }
+ if (rs.getBoolean("out_balance_insufficient")) {
+ logger.error("Balance insufficient")
+ return BankTransactionResult.CONFLICT
+ }
return BankTransactionResult.SUCCESS
}
}
@@ -509,7 +518,7 @@ class Database(private val dbConfig: String) {
,bank_account
,cashout_address
,cashout_currency
- )
+ )
VALUES (
?
,(?,?)::taler_amount
@@ -548,6 +557,112 @@ class Database(private val dbConfig: String) {
return myExecute(stmt)
}
+ fun cashoutConfirm(
+ opUuid: UUID,
+ tanConfirmationTimestamp: Long,
+ bankTransaction: Long // regional payment backing the operation
+ ): Boolean {
+ reconnect()
+ val stmt = prepare("""
+ UPDATE cashout_operations
+ SET tan_confirmation_time = ?, local_transaction = ?
+ WHERE cashout_uuid=?;
+ """)
+ stmt.setLong(1, tanConfirmationTimestamp)
+ stmt.setLong(2, bankTransaction)
+ stmt.setObject(3, opUuid)
+ return myExecute(stmt)
+ }
+ // used by /abort
+ enum class CashoutDeleteResult {
+ SUCCESS,
+ CONFLICT_ALREADY_CONFIRMED
+ }
+ fun cashoutDelete(opUuid: UUID): CashoutDeleteResult {
+ val stmt = prepare("""
+ SELECT out_already_confirmed
+ FROM cashout_delete(?)
+ """)
+ stmt.setObject(1, opUuid)
+ stmt.executeQuery().use {
+ if (!it.next()) {
+ throw internalServerError("Cashout deletion gave no result")
+ }
+ if (it.getBoolean("out_already_confirmed")) return CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED
+ return CashoutDeleteResult.SUCCESS
+ }
+ }
+ fun cashoutGetFromUuid(opUuid: UUID): Cashout? {
+ val stmt = prepare("""
+ 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
+ ,buy_at_ratio
+ ,(buy_in_fee).val as buy_in_fee_val
+ ,(buy_in_fee).frac as buy_in_fee_frac
+ ,sell_at_ratio
+ ,(sell_out_fee).val as sell_out_fee_val
+ ,(sell_out_fee).frac as sell_out_fee_frac
+ ,subject
+ ,creation_time
+ ,tan_channel
+ ,tan_code
+ ,bank_account
+ ,cashout_address
+ ,cashout_currency
+ ,tan_confirmation_time
+ ,local_transaction
+ FROM cashout_operations
+ WHERE cashout_uuid=?;
+ """)
+ stmt.setObject(1, opUuid)
+ stmt.executeQuery().use {
+ if (!it.next()) return null
+ return Cashout(
+ amountDebit = TalerAmount(
+ value = it.getLong("amount_debit_val"),
+ frac = it.getInt("amount_debit_frac")
+ ),
+ amountCredit = TalerAmount(
+ value = it.getLong("amount_credit_val"),
+ frac = it.getInt("amount_credit_frac")
+ ),
+ bankAccount = it.getLong("bank_account"),
+ buyAtRatio = it.getInt("buy_at_ratio"),
+ buyInFee = TalerAmount(
+ value = it.getLong("buy_in_fee_val"),
+ frac = it.getInt("buy_in_fee_frac")
+ ),
+ cashoutAddress = it.getString("cashout_address"),
+ cashoutCurrency = it.getString("cashout_currency"),
+ cashoutUuid = opUuid,
+ creationTime = it.getLong("creation_time"),
+ sellAtRatio = it.getInt("sell_at_ratio"),
+ sellOutFee = TalerAmount(
+ value = it.getLong("sell_out_fee_val"),
+ frac = it.getInt("sell_out_fee_frac")
+ ),
+ subject = it.getString("subject"),
+ tanChannel = it.getString("tan_channel").run {
+ when(this) {
+ "sms" -> TanChannel.sms
+ "email" -> TanChannel.email
+ "file" -> TanChannel.file
+ else -> throw internalServerError("TAN channel $this unsupported")
+ }
+ },
+ tanCode = it.getString("tan_code"),
+ localTransaction = it.getLong("local_transaction"),
+ tanConfirmationTime = it.getLong("tan_confirmation_time").run {
+ if (this == 0L) return@run null
+ return@run this
+ }
+ )
+ }
+ }
+
// NOTE: EBICS not needed for BFH and NB.
}
diff --git a/sandbox/src/test/kotlin/DatabaseTest.kt b/sandbox/src/test/kotlin/DatabaseTest.kt
index cbe59d08..7716cd26 100644
--- a/sandbox/src/test/kotlin/DatabaseTest.kt
+++ b/sandbox/src/test/kotlin/DatabaseTest.kt
@@ -50,28 +50,27 @@ class DatabaseTest {
throwIfFails = true
)
val db = Database("jdbc:postgresql:///libeufincheck")
- // Need accounts first.
- db.customerCreate(customerFoo)
- db.customerCreate(customerBar)
- db.bankAccountCreate(bankAccountFoo)
- db.bankAccountCreate(bankAccountBar)
- db.bankAccountSetMaxDebt(
- "foo",
- TalerAmount(100, 0)
- )
- db.bankAccountSetMaxDebt(
- "bar",
- TalerAmount(50, 0)
- )
return db
}
@Test
fun bankTransactionsTest() {
val db = initDb()
+ assert(db.customerCreate(customerFoo))
+ assert(db.customerCreate(customerBar))
+ assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.bankAccountCreate(bankAccountBar))
var fooAccount = db.bankAccountGetFromLabel("foo")
assert(fooAccount?.hasDebt == false) // Foo has NO debit.
// Preparing the payment data.
+ db.bankAccountSetMaxDebt(
+ "foo",
+ TalerAmount(100, 0)
+ )
+ db.bankAccountSetMaxDebt(
+ "bar",
+ TalerAmount(50, 0)
+ )
val fooPaysBar = BankInternalTransaction(
creditorAccountId = 2,
debtorAccountId = 1,
@@ -163,17 +162,9 @@ class DatabaseTest {
fun bankAccountTest() {
val db = initDb()
assert(db.bankAccountGetFromLabel("foo") == null)
- val bankAccount = BankAccount(
- iban = "not used",
- bic = "not used",
- bankAccountLabel = "foo",
- lastNexusFetchRowId = 1L,
- owningCustomerId = 1L,
- hasDebt = false
- )
- db.customerCreate(customerFoo) // Satisfies the REFERENCE
- assert(db.bankAccountCreate(bankAccount))
- assert(!db.bankAccountCreate(bankAccount)) // Triggers conflict.
+ assert(db.customerCreate(customerFoo))
+ assert(db.bankAccountCreate(bankAccountFoo))
+ assert(!db.bankAccountCreate(bankAccountFoo)) // Triggers conflict.
assert(db.bankAccountGetFromLabel("foo")?.bankAccountLabel == "foo")
assert(db.bankAccountGetFromLabel("foo")?.balance?.equals(TalerAmount(0, 0)) == true)
}
@@ -182,6 +173,8 @@ class DatabaseTest {
fun withdrawalTest() {
val db = initDb()
val uuid = UUID.randomUUID()
+ assert(db.customerCreate(customerFoo))
+ assert(db.bankAccountCreate(bankAccountFoo))
// insert new.
assert(db.talerWithdrawalCreate(
uuid,
@@ -234,6 +227,35 @@ class DatabaseTest {
tanChannel = TanChannel.sms,
tanCode = "secret",
)
+ assert(db.customerCreate(customerFoo))
+ assert(db.bankAccountCreate(bankAccountFoo))
+ assert(db.customerCreate(customerBar))
+ assert(db.bankAccountCreate(bankAccountBar))
assert(db.cashoutCreate(op))
- }
+ val fromDb = db.cashoutGetFromUuid(op.cashoutUuid)
+ assert(fromDb?.subject == op.subject && fromDb.tanConfirmationTime == null)
+ assert(db.cashoutDelete(op.cashoutUuid) == Database.CashoutDeleteResult.SUCCESS)
+ assert(db.cashoutCreate(op))
+ db.bankAccountSetMaxDebt(
+ "foo",
+ TalerAmount(100, 0)
+ )
+ assert(db.bankTransactionCreate(BankInternalTransaction(
+ creditorAccountId = 2,
+ debtorAccountId = 1,
+ subject = "backing the cash-out",
+ amount = TalerAmount(10, 0),
+ accountServicerReference = "acct-svcr-ref",
+ endToEndId = "end-to-end-id",
+ paymentInformationId = "pmtinfid",
+ transactionDate = 100000L
+ )) == Database.BankTransactionResult.SUCCESS)
+ // Confirming the cash-out
+ assert(db.cashoutConfirm(op.cashoutUuid, 1L, 1L))
+ // Checking the confirmation took place.
+ assert(db.cashoutGetFromUuid(op.cashoutUuid)?.tanConfirmationTime != null)
+ // Deleting the operation.
+ assert(db.cashoutDelete(op.cashoutUuid) == Database.CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED)
+ assert(db.cashoutGetFromUuid(op.cashoutUuid) != null) // previous didn't delete.
+ }
} \ No newline at end of file