diff options
author | Antoine A <> | 2023-10-12 13:14:57 +0000 |
---|---|---|
committer | Antoine A <> | 2023-10-12 13:14:57 +0000 |
commit | cf3fbbcd2b029dac65d628c1ae6654faafaa32f4 (patch) | |
tree | 449e9ceeb59d84644d476d221303628397b814f1 | |
parent | b2b741902e930adc9f0cfca8d29f78b9cc628ecc (diff) | |
download | libeufin-cf3fbbcd2b029dac65d628c1ae6654faafaa32f4.tar.gz libeufin-cf3fbbcd2b029dac65d628c1ae6654faafaa32f4.tar.bz2 libeufin-cf3fbbcd2b029dac65d628c1ae6654faafaa32f4.zip |
Move more logic into database
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Database.kt | 106 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt | 96 | ||||
-rw-r--r-- | bank/src/test/kotlin/DatabaseTest.kt | 7 | ||||
-rw-r--r-- | bank/src/test/kotlin/TalerApiTest.kt | 102 | ||||
-rw-r--r-- | database-versioning/procedures.sql | 56 |
5 files changed, 212 insertions, 155 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt index 59da5b23..ad60ed85 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt @@ -659,6 +659,33 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos } } + data class BankInfo( + val id: Long, + val isTalerExchange: Boolean, + val internalPaytoUri: String + ) + + fun bankAccountInfoFromCustomerLogin(login: String): BankInfo? = conn { conn -> + val stmt = conn.prepareStatement(""" + SELECT + bank_account_id + ,is_taler_exchange + ,internal_payto_uri + FROM bank_accounts + JOIN customers + ON customer_id=owning_customer_id + WHERE login=? + """) + stmt.setString(1, login) + stmt.oneOrNull { + BankInfo( + id = it.getLong(1), + isTalerExchange = it.getBoolean(2), + internalPaytoUri = it.getString(3), + ) + } + } + fun bankAccountGetFromInternalPayto(internalPayto: String): BankAccount? = conn { conn -> val stmt = conn.prepareStatement(""" SELECT @@ -1437,54 +1464,20 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos val wtid: ShortHashCode, val creditAccount: String ) - /** - * Gets a Taler transfer request, given its UID. - */ - fun talerTransferGetFromUid(requestUid: HashCode): TalerTransferFromDb? = conn { conn -> - val stmt = conn.prepareStatement(""" - SELECT - wtid - ,exchange_base_url - ,(txs.amount).val AS amount_value - ,(txs.amount).frac AS amount_frac - ,txs.creditor_payto_uri - ,tfr.bank_transaction - ,txs.transaction_date AS timestamp - FROM taler_exchange_outgoing AS tfr - JOIN bank_account_transactions AS txs - ON bank_transaction=txs.bank_transaction_id - WHERE request_uid = ?; - """) - stmt.setBytes(1, requestUid.raw) - stmt.oneOrNull { - TalerTransferFromDb( - wtid = ShortHashCode(it.getBytes("wtid")), - amount = TalerAmount( - value = it.getLong("amount_value"), - frac = it.getInt("amount_frac"), - getCurrency() - ), - creditAccount = it.getString("creditor_payto_uri"), - exchangeBaseUrl = it.getString("exchange_base_url"), - requestUid = requestUid, - debitTxRowId = it.getLong("bank_transaction"), - timestamp = it.getLong("timestamp") - ) - } - } /** * Holds the result of inserting a Taler transfer request * into the database. */ data class TalerTransferCreationResult( - val txResult: BankTransactionResult, + val txResult: TalerTransferResult, /** * bank transaction that backs this Taler transfer request. * This is the debit transactions associated to the exchange * bank account. */ - val txRowId: Long? = null + val txRowId: Long? = null, + val timestamp: TalerProtocolTimestamp? = null ) /** * This function calls the SQL function that (1) inserts the TWG @@ -1499,7 +1492,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos */ fun talerTransferCreate( req: TransferRequest, - exchangeBankAccountId: Long, + username: String, timestamp: Instant, acctSvcrRef: String = "not used", pmtInfId: String = "not used", @@ -1509,10 +1502,14 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos val stmt = conn.prepareStatement(""" SELECT out_exchange_balance_insufficient + ,out_nx_debitor + ,out_nx_exchange ,out_nx_creditor ,out_tx_row_id + ,out_request_uid_reuse + ,out_timestamp FROM - taler_transfer ( + taler_transfer ( ?, ?, ?, @@ -1534,7 +1531,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos stmt.setInt(5, req.amount.frac) stmt.setString(6, req.exchange_base_url) stmt.setString(7, stripIbanPayto(req.credit_account) ?: throw badRequest("credit_account payto URI is invalid")) - stmt.setLong(8, exchangeBankAccountId) + stmt.setString(8, username) stmt.setLong(9, timestamp.toDbMicros() ?: throw faultyTimestampByBank()) stmt.setString(10, acctSvcrRef) stmt.setString(11, pmtInfId) @@ -1544,15 +1541,23 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos when { !it.next() -> throw internalServerError("SQL function taler_transfer did not return anything.") - it.getBoolean("out_nx_creditor") -> - TalerTransferCreationResult(BankTransactionResult.NO_CREDITOR) + it.getBoolean("out_nx_debitor") -> + TalerTransferCreationResult(TalerTransferResult.NO_DEBITOR) + it.getBoolean("out_nx_exchange") -> + TalerTransferCreationResult(TalerTransferResult.NOT_EXCHANGE) + it.getBoolean("out_request_uid_reuse") -> + TalerTransferCreationResult(TalerTransferResult.REQUEST_UID_REUSE) it.getBoolean("out_exchange_balance_insufficient") -> - TalerTransferCreationResult(BankTransactionResult.CONFLICT) + TalerTransferCreationResult(TalerTransferResult.BALANCE_INSUFFICIENT) + it.getBoolean("out_nx_creditor") -> + TalerTransferCreationResult(TalerTransferResult.NO_CREDITOR) else -> { - val txRowId = it.getLong("out_tx_row_id") TalerTransferCreationResult( - txResult = BankTransactionResult.SUCCESS, - txRowId = txRowId + txResult = TalerTransferResult.SUCCESS, + txRowId = it.getLong("out_tx_row_id"), + timestamp = TalerProtocolTimestamp( + it.getLong("out_timestamp").microsToJavaInstant() ?: throw faultyTimestampByBank() + ) ) } } @@ -1560,6 +1565,15 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos } } +enum class TalerTransferResult { + NO_DEBITOR, + NOT_EXCHANGE, + NO_CREDITOR, + REQUEST_UID_REUSE, + BALANCE_INSUFFICIENT, + SUCCESS +} + private data class Notification(val rowId: Long) private class NotificationWatcher(private val pgSource: PGSimpleDataSource) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt index 8610a230..7dfbe1c5 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt @@ -45,16 +45,13 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) return username } - /** Retrieve the bank account for the selected username*/ - suspend fun ApplicationCall.bankAccount(): BankAccount { + /** Retrieve the bank account info for the selected username*/ + suspend fun ApplicationCall.bankAccount(): Database.BankInfo { val username = getResourceName("USERNAME") - val customer = db.customerGetFromLogin(username) ?: throw notFound( + return db.bankAccountInfoFromCustomerLogin(username) ?: throw notFound( hint = "Customer $username not found", talerEc = TalerErrorCode.TALER_EC_END // FIXME: need EC. ) - val bankAccount = db.bankAccountGetFromOwnerId(customer.expectRowId()) - ?: throw internalServerError("Exchange does not have a bank account") - return bankAccount } get("/taler-wire-gateway/config") { @@ -63,61 +60,45 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) } post("/accounts/{USERNAME}/taler-wire-gateway/transfer") { - call.authCheck(TokenScope.readwrite, true) + val username = call.authCheck(TokenScope.readwrite, true) val req = call.receive<TransferRequest>() - // Checking for idempotency. - val maybeDoneAlready = db.talerTransferGetFromUid(req.request_uid) - val creditAccount = stripIbanPayto(req.credit_account) - if (maybeDoneAlready != null) { - val isIdempotent = - maybeDoneAlready.amount == req.amount - && maybeDoneAlready.creditAccount == creditAccount - && maybeDoneAlready.exchangeBaseUrl == req.exchange_base_url - && maybeDoneAlready.wtid == req.wtid - if (isIdempotent) { - call.respond( - TransferResponse( - timestamp = TalerProtocolTimestamp.fromMicroseconds(maybeDoneAlready.timestamp), - row_id = maybeDoneAlready.debitTxRowId - ) - ) - return@post - } - throw conflict( - hint = "request_uid used already", - talerEc = TalerErrorCode.TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED + if (req.amount.currency != ctx.currency) + throw badRequest( + "Currency mismatch", + TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH ) - } - // Legitimate request, go on. - val internalCurrency = ctx.currency - if (internalCurrency != req.amount.currency) - throw badRequest("Currency mismatch: $internalCurrency vs ${req.amount.currency}") - val exchangeBankAccount = call.bankAccount() - val transferTimestamp = Instant.now() val dbRes = db.talerTransferCreate( req = req, - exchangeBankAccountId = exchangeBankAccount.expectRowId(), - timestamp = transferTimestamp + username = username, + timestamp = Instant.now() ) - if (dbRes.txResult == BankTransactionResult.CONFLICT) - throw conflict( - "Insufficient balance for exchange", - TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT - ) - if (dbRes.txResult == BankTransactionResult.NO_CREDITOR) - throw notFound( + when (dbRes.txResult) { + TalerTransferResult.NO_DEBITOR -> + throw notFound( + hint = "Customer $username not found", + talerEc = TalerErrorCode.TALER_EC_END // FIXME: need EC. + ) + TalerTransferResult.NOT_EXCHANGE -> + throw forbidden("$username is not an exchange account.") + TalerTransferResult.NO_CREDITOR -> throw notFound( "Creditor account was not found", TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT ) - val debitRowId = dbRes.txRowId - ?: throw internalServerError("Database did not return the debit tx row ID") - call.respond( - TransferResponse( - timestamp = TalerProtocolTimestamp(transferTimestamp), - row_id = debitRowId + TalerTransferResult.REQUEST_UID_REUSE -> throw conflict( + hint = "request_uid used already", + talerEc = TalerErrorCode.TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED ) - ) - return@post + TalerTransferResult.BALANCE_INSUFFICIENT -> throw conflict( + "Insufficient balance for exchange", + TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT + ) + TalerTransferResult.SUCCESS -> call.respond( + TransferResponse( + timestamp = dbRes.timestamp!!, + row_id = dbRes.txRowId!! + ) + ) + } } suspend fun <T> historyEndpoint( @@ -130,7 +111,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) val bankAccount = call.bankAccount() if (!bankAccount.isTalerExchange) throw forbidden("History is not related to a Taler exchange.") - val items = db.dbLambda(params, bankAccount.expectRowId()); + val items = db.dbLambda(params, bankAccount.id); if (items.isEmpty()) { call.respond(HttpStatusCode.NoContent) @@ -148,10 +129,9 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) } post("/accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming") { - call.authCheck(TokenScope.readwrite, false); + call.authCheck(TokenScope.readwrite, false); val req = call.receive<AddIncomingRequest>() - val internalCurrency = ctx.currency - if (req.amount.currency != internalCurrency) + if (req.amount.currency != ctx.currency) throw badRequest( "Currency mismatch", TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH @@ -170,11 +150,13 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: BankApplicationContext) TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT ) val exchangeAccount = call.bankAccount() + if (!exchangeAccount.isTalerExchange) throw forbidden("Expected taler exchange bank account.") + val txTimestamp = Instant.now() val op = BankInternalTransaction( debtorAccountId = walletAccount.expectRowId(), amount = req.amount, - creditorAccountId = exchangeAccount.expectRowId(), + creditorAccountId = exchangeAccount.id, transactionDate = txTimestamp, subject = req.reserve_pub.encoded() ) diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt index 35e10ab1..077eca0c 100644 --- a/bank/src/test/kotlin/DatabaseTest.kt +++ b/bank/src/test/kotlin/DatabaseTest.kt @@ -72,7 +72,8 @@ class DatabaseTest { lastNexusFetchRowId = 1L, owningCustomerId = 1L, hasDebt = false, - maxDebt = TalerAmount(10, 1, "KUDOS") + maxDebt = TalerAmount(10, 1, "KUDOS"), + isTalerExchange = true ) private val bankAccountBar = BankAccount( internalPaytoUri = "payto://iban/BAR-IBAN-ABC".lowercase(), @@ -126,10 +127,10 @@ class DatabaseTest { assert(db.bankAccountCreate(bankAccountBar) != null) val res = db.talerTransferCreate( req = exchangeReq, - exchangeBankAccountId = 1L, + username = "foo", timestamp = Instant.now() ) - assert(res.txResult == BankTransactionResult.SUCCESS) + assert(res.txResult == TalerTransferResult.SUCCESS) } @Test diff --git a/bank/src/test/kotlin/TalerApiTest.kt b/bank/src/test/kotlin/TalerApiTest.kt index 0d645c08..9aa3b562 100644 --- a/bank/src/test/kotlin/TalerApiTest.kt +++ b/bank/src/test/kotlin/TalerApiTest.kt @@ -33,15 +33,8 @@ class TalerApiTest { lastNexusFetchRowId = 1L, owningCustomerId = 1L, hasDebt = false, - maxDebt = TalerAmount(10, 1, "KUDOS") - ) - val bankAccountBar = BankAccount( - internalPaytoUri = stripIbanPayto("payto://iban/BAR-IBAN-ABC")!!, - lastNexusFetchRowId = 1L, - owningCustomerId = 2L, - hasDebt = false, maxDebt = TalerAmount(10, 1, "KUDOS"), - isTalerExchange = true + isTalerExchange = false ) val customerBar = Customer( login = "bar", @@ -52,8 +45,17 @@ class TalerApiTest { cashoutPayto = "payto://external-IBAN", cashoutCurrency = "KUDOS" ) + val bankAccountBar = BankAccount( + internalPaytoUri = stripIbanPayto("payto://iban/BAR-IBAN-ABC")!!, + lastNexusFetchRowId = 1L, + owningCustomerId = 2L, + hasDebt = false, + maxDebt = TalerAmount(10, 1, "KUDOS"), + isTalerExchange = true + ) + - suspend fun Database.genTransfer(from: Long, to: BankAccount) { + suspend fun Database.genTransfer(from: String, to: BankAccount) { talerTransferCreate( req = TransferRequest( request_uid = randHashCode(), @@ -62,7 +64,7 @@ class TalerApiTest { wtid = randShortHashCode(), credit_account ="${stripIbanPayto(to.internalPaytoUri)}" ), - exchangeBankAccountId = from, + username = from, timestamp = Instant.now() ) } @@ -125,6 +127,8 @@ class TalerApiTest { corebankWebApp(db, ctx) } + // TODO what to do when creditor and debtor are both exchanges + authRoutine(client, "/accounts/foo/taler-wire-gateway/transfer") val valid_req = json { @@ -132,34 +136,40 @@ class TalerApiTest { "amount" to "KUDOS:55" "exchange_base_url" to "http://exchange.example.com/" "wtid" to randShortHashCode() - "credit_account" to "${stripIbanPayto(bankAccountBar.internalPaytoUri)}" + "credit_account" to "${stripIbanPayto(bankAccountFoo.internalPaytoUri)}" }; - // Checking exchange debt constraint. + // Checking exchange account constraint. client.post("/accounts/foo/taler-wire-gateway/transfer") { basicAuth("foo", "pw") jsonBody(valid_req) + }.assertStatus(HttpStatusCode.Forbidden) + + // Checking exchange debt constraint. + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") + jsonBody(valid_req) }.assertStatus(HttpStatusCode.Conflict) // Giving debt allowance and checking the OK case. assert(db.bankAccountSetMaxDebt( - 1L, + 2L, TalerAmount(1000, 0, "KUDOS") )) - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody(valid_req) }.assertOk() // check idempotency - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody(valid_req) }.assertOk() // Trigger conflict due to reused request_uid - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "wtid" to randShortHashCode() @@ -169,8 +179,8 @@ class TalerApiTest { }.assertStatus(HttpStatusCode.Conflict) // Triggering currency mismatch - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "request_uid" to randHashCode() @@ -180,9 +190,21 @@ class TalerApiTest { ) }.assertBadRequest() + // Unknown account currency mismatch + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") + jsonBody( + json(valid_req) { + "request_uid" to randHashCode() + "wtid" to randShortHashCode() + "credit_account" to "payto://iban/UNKNOWN-IBAN-XYZ" + } + ) + }.assertStatus(HttpStatusCode.NotFound) + // Bad BASE32 wtid - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "wtid" to "I love chocolate" @@ -191,8 +213,8 @@ class TalerApiTest { }.assertBadRequest() // Bad BASE32 len wtid - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "wtid" to randBase32Crockford(31) @@ -201,8 +223,8 @@ class TalerApiTest { }.assertBadRequest() // Bad BASE32 request_uid - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "request_uid" to "I love chocolate" @@ -211,8 +233,8 @@ class TalerApiTest { }.assertBadRequest() // Bad BASE32 len wtid - client.post("/accounts/foo/taler-wire-gateway/transfer") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/transfer") { + basicAuth("bar", "secret") jsonBody( json(valid_req) { "request_uid" to randBase32Crockford(65) @@ -412,7 +434,7 @@ class TalerApiTest { // Bar pays Foo three time repeat(3) { - db.genTransfer(2, bankAccountFoo) + db.genTransfer("bar", bankAccountFoo) } // Should not show up in the taler wire gateway API history db.bankTransactionCreate(genTx("bogus foobar", 1, 2)).assertSuccess() @@ -420,7 +442,7 @@ class TalerApiTest { db.bankTransactionCreate(genTx("payout")).assertSuccess() // Bar pays Foo twice, we should see five valid transactions repeat(2) { - db.genTransfer(2, bankAccountFoo) + db.genTransfer("bar", bankAccountFoo) } // Check ignore bogus subject @@ -477,14 +499,14 @@ class TalerApiTest { }, launch { delay(200) - db.genTransfer(2, bankAccountFoo) + db.genTransfer("bar", bankAccountFoo) } ) } // Testing ranges. repeat(300) { - db.genTransfer(2, bankAccountFoo) + db.genTransfer("bar", bankAccountFoo) } // forward range: @@ -519,22 +541,28 @@ class TalerApiTest { "reserve_pub" to randEddsaPublicKey() "debit_account" to "${"payto://iban/BAR-IBAN-ABC"}" }; + client.post("/accounts/foo/taler-wire-gateway/admin/add-incoming") { basicAuth("foo", "pw") jsonBody(valid_req, deflate = true) + }.assertStatus(HttpStatusCode.Forbidden) + + client.post("/accounts/bar/taler-wire-gateway/admin/add-incoming") { + basicAuth("bar", "secret") + jsonBody(valid_req, deflate = true) }.assertOk() // Bad BASE32 reserve_pub - client.post("/accounts/foo/taler-wire-gateway/admin/add-incoming") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/admin/add-incoming") { + basicAuth("bar", "secret") jsonBody(json(valid_req) { "reserve_pub" to "I love chocolate" }) }.assertBadRequest() // Bad BASE32 len reserve_pub - client.post("/accounts/foo/taler-wire-gateway/admin/add-incoming") { - basicAuth("foo", "pw") + client.post("/accounts/bar/taler-wire-gateway/admin/add-incoming") { + basicAuth("bar", "secret") jsonBody(json(valid_req) { "reserve_pub" to randBase32Crockford(31) }) diff --git a/database-versioning/procedures.sql b/database-versioning/procedures.sql index c3cab648..afcdbdec 100644 --- a/database-versioning/procedures.sql +++ b/database-versioning/procedures.sql @@ -140,7 +140,6 @@ THEN UPDATE customers SET name=in_name WHERE customer_id = my_customer_id; END IF; END $$; - COMMENT ON FUNCTION account_reconfig(TEXT, TEXT, TEXT, TEXT, TEXT, BOOLEAN) IS 'Updates values on customer and bank account rows based on the input data.'; @@ -202,25 +201,56 @@ CREATE OR REPLACE FUNCTION taler_transfer( IN in_amount taler_amount, IN in_exchange_base_url TEXT, IN in_credit_account_payto TEXT, - IN in_exchange_bank_account_id BIGINT, + IN in_username TEXT, IN in_timestamp BIGINT, IN in_account_servicer_reference TEXT, IN in_payment_information_id TEXT, IN in_end_to_end_id TEXT, + OUT out_request_uid_reuse BOOLEAN, OUT out_exchange_balance_insufficient BOOLEAN, + OUT out_nx_debitor BOOLEAN, + OUT out_nx_exchange BOOLEAN, OUT out_nx_creditor BOOLEAN, - OUT out_tx_row_id BIGINT + OUT out_tx_row_id BIGINT, + OUT out_timestamp BIGINT ) LANGUAGE plpgsql AS $$ DECLARE +exchange_bank_account_id BIGINT; receiver_bank_account_id BIGINT; BEGIN - --- First creating the bank transaction, then updating --- the transfer request table, because that needs to point --- at the bank transaction. - +-- Check for idempotence and conflict +SELECT (amount != in_amount + OR creditor_payto_uri != in_credit_account_payto + OR exchange_base_url != in_exchange_base_url + OR wtid != in_wtid) + ,bank_transaction_id, transaction_date + INTO out_request_uid_reuse, out_tx_row_id, out_timestamp + FROM taler_exchange_outgoing + JOIN bank_account_transactions AS txs + ON bank_transaction=txs.bank_transaction_id + WHERE request_uid = in_request_uid; +IF found THEN + RETURN; +END IF; +-- Find exchange bank account id +SELECT + bank_account_id, NOT is_taler_exchange + INTO exchange_bank_account_id, out_nx_exchange + FROM bank_accounts + JOIN customers + ON customer_id=owning_customer_id + WHERE login = in_username; +IF NOT FOUND THEN + out_nx_debitor=TRUE; + RETURN; +ELSIF out_nx_exchange THEN + RETURN; +END IF; +-- Find receiver bank account id +-- TODO handle bounce when receiver is exchange ? +-- TODO handle transfer to self ? SELECT bank_account_id INTO receiver_bank_account_id @@ -231,7 +261,7 @@ THEN out_nx_creditor=TRUE; RETURN; END IF; -out_nx_creditor=FALSE; +-- Perform bank transfer SELECT out_balance_insufficient, out_debit_row_id @@ -240,7 +270,7 @@ SELECT out_tx_row_id FROM bank_wire_transfer( receiver_bank_account_id, - in_exchange_bank_account_id, + exchange_bank_account_id, in_subject, in_amount, in_timestamp, @@ -251,6 +281,7 @@ SELECT IF out_exchange_balance_insufficient THEN RETURN; END IF; +-- Register outgoing transaction INSERT INTO taler_exchange_outgoing ( request_uid, @@ -263,8 +294,9 @@ INSERT in_exchange_base_url, out_tx_row_id ); +out_timestamp=in_timestamp; -- notify new transaction -PERFORM pg_notify('outgoing_tx', in_exchange_bank_account_id || ' ' || out_tx_row_id); +PERFORM pg_notify('outgoing_tx', exchange_bank_account_id || ' ' || out_tx_row_id); END $$; COMMENT ON FUNCTION taler_transfer( bytea, @@ -273,7 +305,7 @@ COMMENT ON FUNCTION taler_transfer( taler_amount, text, text, - bigint, + text, bigint, text, text, |