summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2023-10-12 13:14:57 +0000
committerAntoine A <>2023-10-12 13:14:57 +0000
commitcf3fbbcd2b029dac65d628c1ae6654faafaa32f4 (patch)
tree449e9ceeb59d84644d476d221303628397b814f1
parentb2b741902e930adc9f0cfca8d29f78b9cc628ecc (diff)
downloadlibeufin-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.kt106
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt96
-rw-r--r--bank/src/test/kotlin/DatabaseTest.kt7
-rw-r--r--bank/src/test/kotlin/TalerApiTest.kt102
-rw-r--r--database-versioning/procedures.sql56
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,