libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 66fe30878986a6accc16712474c699a53e8705bb
parent 5490a93b97307481d72bea3142ba1e6abafced7d
Author: Antoine A <>
Date:   Mon, 10 Jun 2024 14:31:39 +0200

bank: support wire transfer fees

Diffstat:
M.gitignore | 1+
MAPI_CHANGES.md | 1+
Abank/conf/test_with_fees.conf | 18++++++++++++++++++
Mbank/src/main/kotlin/tech/libeufin/bank/Config.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/Constants.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 3++-
Mbank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 15+++++++++++----
Mbank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt | 8+++++---
Mbank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt | 5++++-
Mbank/src/test/kotlin/AmountTest.kt | 3++-
Mbank/src/test/kotlin/BankIntegrationApiTest.kt | 1+
Mbank/src/test/kotlin/CoreBankApiTest.kt | 43+++++++++++++++++++++++++++++++++++++++++++
Mbank/src/test/kotlin/GcTest.kt | 4++--
Mdatabase-versioning/libeufin-bank-procedures.sql | 205++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
15 files changed, 229 insertions(+), 86 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -6,6 +6,7 @@ testbench/config.json configure build/ .gradle +.kotlin out *.sqlite3 *.swp diff --git a/API_CHANGES.md b/API_CHANGES.md @@ -56,6 +56,7 @@ This files contains all the API changes for the current release: - PATCH /accounts/USERNAME: add min_cashout field for the custom minimum cashout amount - GET /accounts: add min_cashout field for the custom minimum cashout amount - GET /accounts/USERNAME: add min_cashout field for the custom minimum cashout amount +- GET /config: new wire_transfer_fees field for transaction fees ## bank cli diff --git a/bank/conf/test_with_fees.conf b/bank/conf/test_with_fees.conf @@ -0,0 +1,18 @@ +[libeufin-bank] +CURRENCY = KUDOS +WIRE_TYPE = iban +IBAN_PAYTO_BIC = SANDBOXX +DEFAULT_DEBT_LIMIT = KUDOS:100 +SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com +ALLOW_REGISTRATION = yes +ALLOW_ACCOUNT_DELETION = yes +ALLOW_EDIT_NAME = yes +ALLOW_EDIT_CASHOUT_PAYTO_URI = yes +allow_conversion = YES +FIAT_CURRENCY = EUR +tan_sms = libeufin-tan-file.sh +tan_email = libeufin-tan-file.sh +wire_transfer_fees = KUDOS:0.1 + +[libeufin-bankdb-postgres] +CONFIG = postgresql:///libeufincheck diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -35,7 +35,7 @@ data class BankConfig( val baseUrl: String?, val regionalCurrency: String, val regionalCurrencySpec: CurrencySpecification, - val wireTransferFees: TalerAmount?, + val wireTransferFees: TalerAmount, val allowRegistration: Boolean, val allowAccountDeletion: Boolean, val allowEditName: Boolean, @@ -127,7 +127,7 @@ fun TalerConfig.loadBankConfig(): BankConfig { allowConversion = allowConversion, defaultDebtLimit = amount("libeufin-bank", "default_debt_limit", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), registrationBonus = amount("libeufin-bank", "registration_bonus", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), - wireTransferFees = amount("libeufin-bank", "wire_transfer_fees", regionalCurrency), + wireTransferFees = amount("libeufin-bank", "wire_transfer_fees", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), suggestedWithdrawalExchange = lookupString("libeufin-bank", "suggested_withdrawal_exchange"), spaPath = lookupPath("libeufin-bank", "spa"), baseUrl = lookupString("libeufin-bank", "base_url"), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt @@ -37,6 +37,6 @@ val RESERVED_ACCOUNTS = setOf("admin", "bank") const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5 // API version -const val COREBANK_API_VERSION: String = "4:8:1" +const val COREBANK_API_VERSION: String = "4:9:1" const val CONVERSION_API_VERSION: String = "0:1:0" const val INTEGRATION_API_VERSION: String = "2:0:3" diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -302,7 +302,8 @@ data class Config( val allow_edit_cashout_payto_uri: Boolean, val default_debit_threshold: TalerAmount, val supported_tan_channels: Set<TanChannel>, - val wire_type: WireMethod + val wire_type: WireMethod, + val wire_transfer_fees: TalerAmount ) { val name: String = "libeufin-bank" val version: String = COREBANK_API_VERSION diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -62,7 +62,8 @@ fun Routing.coreBankApi(db: Database, ctx: BankConfig) { supported_tan_channels = ctx.tanChannels.keys, allow_edit_name = ctx.allowEditName, allow_edit_cashout_payto_uri = ctx.allowEditCashout, - wire_type = ctx.wireMethod + wire_type = ctx.wireMethod, + wire_transfer_fees = ctx.wireTransferFees ) ) } @@ -455,7 +456,8 @@ private fun Routing.coreBankTransactionsApi(db: Database, ctx: BankConfig) { amount = amount, timestamp = Instant.now(), requestUid = req.request_uid, - is2fa = challenge != null + is2fa = challenge != null, + wireTransferFees = ctx.wireTransferFees ) when (res) { BankTransactionResult.UnknownDebtor -> throw unknownAccount(username) @@ -489,9 +491,14 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { auth(db, TokenScope.readwrite) { post("/accounts/{USERNAME}/withdrawals") { val req = call.receive<BankAccountCreateWithdrawalRequest>() - // TODO check card_fees req.amount?.run(ctx::checkRegionalCurrency) req.suggested_amount?.run(ctx::checkRegionalCurrency) + if (req.card_fees != null) { + throw conflict( + "card_fees differ from wire_transfer_fees", + TalerErrorCode.END + ) + } val opId = UUID.randomUUID() when (db.withdrawal.create( username, @@ -522,7 +529,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/confirm") { val id = call.uuidPath("withdrawal_id") val challenge = call.checkChallenge(db, Operation.withdrawal) - when (db.withdrawal.confirm(username, id, Instant.now(), challenge != null)) { + when (db.withdrawal.confirm(username, id, ctx.wireTransferFees, Instant.now(), challenge != null)) { WithdrawalConfirmationResult.UnknownOperation -> throw notFound( "Withdrawal operation $id not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -164,7 +164,7 @@ class AccountDAO(private val db: Database) { if (bonus.value != 0L || bonus.frac != 0) { conn.prepareStatement(""" SELECT out_balance_insufficient - FROM bank_transaction(?,'admin','bonus',(?,?)::taler_amount,?,true,NULL) + FROM bank_transaction(?,'admin','bonus',(?,?)::taler_amount,?,true,NULL,(0, 0)::taler_amount) """).run { setString(1, internalPayto.canonical) setLong(2, bonus.value) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt @@ -51,6 +51,7 @@ class TransactionDAO(private val db: Database) { timestamp: Instant, is2fa: Boolean, requestUid: ShortHashCode?, + wireTransferFees: TalerAmount ): BankTransactionResult = db.serializable { conn -> val now = timestamp.micros() conn.transaction { @@ -70,7 +71,7 @@ class TransactionDAO(private val db: Database) { ,out_debtor_is_exchange ,out_creditor_admin ,out_idempotent - FROM bank_transaction(?,?,?,(?,?)::taler_amount,?,?,?) + FROM bank_transaction(?,?,?,(?,?)::taler_amount,?,?,?,(?,?)::taler_amount) """ ) stmt.setString(1, creditAccountPayto.canonical) @@ -81,6 +82,8 @@ class TransactionDAO(private val db: Database) { stmt.setLong(6, now) stmt.setBoolean(7, is2fa) stmt.setBytes(8, requestUid?.raw) + stmt.setLong(9, wireTransferFees.value) + stmt.setInt(10, wireTransferFees.frac) stmt.executeQuery().use { when { !it.next() -> throw internalServerError("Bank transaction didn't properly return") @@ -125,8 +128,7 @@ class TransactionDAO(private val db: Database) { // No error can happens because an opposite transaction already took place in the same transaction conn.prepareStatement(""" SELECT bank_wire_transfer( - ?, ?, ?, (?, ?)::taler_amount, ?, - NULL, NULL, NULL + ?, ?, ?, (?, ?)::taler_amount, ?, (0, 0)::taler_amount ); """ ).run { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt @@ -179,6 +179,7 @@ class WithdrawalDAO(private val db: Database) { suspend fun confirm( login: String, uuid: UUID, + wireTransferFees: TalerAmount, now: Instant, is2fa: Boolean ): WithdrawalConfirmationResult = db.serializable { conn -> @@ -190,13 +191,15 @@ class WithdrawalDAO(private val db: Database) { out_not_selected, out_aborted, out_tan_required - FROM confirm_taler_withdrawal(?,?,?,?); + FROM confirm_taler_withdrawal(?,?,?,?,(?,?)::taler_amount); """ ) stmt.setString(1, login) stmt.setObject(2, uuid) stmt.setLong(3, now.micros()) stmt.setBoolean(4, is2fa) + stmt.setLong(5, wireTransferFees.value) + stmt.setInt(6, wireTransferFees.frac) stmt.executeQuery().use { when { !it.next() -> diff --git a/bank/src/test/kotlin/AmountTest.kt b/bank/src/test/kotlin/AmountTest.kt @@ -54,7 +54,8 @@ class AmountTest { amount = due, timestamp = Instant.now(), is2fa = false, - requestUid = null + requestUid = null, + wireTransferFees = TalerAmount("KUDOS:0") ) val txBool = when (txRes) { BankTransactionResult.BalanceInsufficient -> false diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt @@ -58,6 +58,7 @@ class BankIntegrationApiTest { assert(!it.selection_done) assert(!it.aborted) assert(!it.transfer_done) + assertEquals(it.card_fees, TalerAmount("KUDOS:0")) assertEquals(amount, it.amount) assertEquals(suggested, it.suggested_amount) assertEquals(listOf("iban"), it.wire_types) diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -1133,6 +1133,49 @@ class CoreBankTransactionsApiTest { } }.assertConflict(TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED) } + + @Test + fun createWithFee() = bankSetup(conf = "test_with_fees.conf") { + // Init state + assertBalance("merchant", "+KUDOS:0") + assertBalance("customer", "+KUDOS:0") + assertBalance("admin", "+KUDOS:0") + + // Check fee are sent to admin + tx("merchant", "KUDOS:3", "customer") + assertBalance("merchant", "-KUDOS:3.1") + assertBalance("customer", "+KUDOS:3") + assertBalance("admin", "+KUDOS:0.1") + + // Check amount with fee are checked + for (amount in listOf("KUDOS:7", "KUDOS:6.9")) { + println(amount) + client.postA("/accounts/merchant/transactions") { + json { + "payto_uri" to "$customerPayto?message=payout2&amount=$amount" + } + }.assertConflict(TalerErrorCode.BANK_UNALLOWED_DEBIT) + } + + // Check empty account + tx("merchant", "KUDOS:6.8", "customer") + assertBalance("merchant", "-KUDOS:10") + assertBalance("customer", "+KUDOS:9.8") + assertBalance("admin", "+KUDOS:0.2") + + // Admin check no fee + tx("admin", "KUDOS:0.35", "merchant") + assertBalance("merchant", "-KUDOS:9.65") + assertBalance("admin", "-KUDOS:0.15") + + // Admin recover from debt + tx("customer", "KUDOS:1", "merchant") + assertBalance("admin", "-KUDOS:0.05") + tx("customer", "KUDOS:1", "merchant") + assertBalance("merchant", "-KUDOS:7.65") + assertBalance("customer", "+KUDOS:7.6") + assertBalance("admin", "+KUDOS:0.05") + } } class CoreBankWithdrawalApiTest { diff --git a/bank/src/test/kotlin/GcTest.kt b/bank/src/test/kotlin/GcTest.kt @@ -105,14 +105,14 @@ class GcTest { db.withdrawal.setDetails(uuid, exchangePayto, EddsaPublicKey.rand(), null) ) assertEquals( - db.withdrawal.confirm(account, uuid, time, false), + db.withdrawal.confirm(account, uuid, TalerAmount("KUDOS:0"), time, false), WithdrawalConfirmationResult.Success ) assertIs<CashoutCreationResult.Success>( db.cashout.create(account, ShortHashCode.rand(), from, to, "", time, false), ) assertIs<BankTransactionResult.Success>( - db.transaction.create(customerPayto, account, "", from, time, false, ShortHashCode.rand()), + db.transaction.create(customerPayto, account, "", from, time, false, ShortHashCode.rand(), TalerAmount("KUDOS:0")), ) } for (time in listOf(now, abort, clean, delete)) { diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -320,9 +320,7 @@ SELECT in_subject, in_amount, in_timestamp, - NULL, - NULL, - NULL + (0, 0)::taler_amount ) as transfer; IF out_exchange_balance_insufficient THEN RETURN; @@ -398,9 +396,7 @@ SELECT in_subject, in_amount, in_timestamp, - NULL, - NULL, - NULL + (0, 0)::taler_amount ) as transfer; IF out_debitor_balance_insufficient THEN RETURN; @@ -418,6 +414,7 @@ CREATE FUNCTION bank_transaction( IN in_timestamp INT8, IN in_is_tan BOOLEAN, IN in_request_uid BYTEA, + IN in_wire_transfer_fees taler_amount, -- Error status OUT out_creditor_not_found BOOLEAN, OUT out_debtor_not_found BOOLEAN, @@ -489,12 +486,11 @@ SELECT in_subject, in_amount, in_timestamp, - NULL, - NULL, - NULL + in_wire_transfer_fees ) as transfer; - --- TODO wire transfer fees +IF out_balance_insufficient THEN + RETURN; +END IF; -- Store operation IF in_request_uid IS NOT NULL THEN @@ -653,6 +649,7 @@ CREATE FUNCTION confirm_taler_withdrawal( IN in_withdrawal_uuid uuid, IN in_confirmation_date INT8, IN in_is_tan BOOLEAN, + IN in_wire_transfer_fees taler_amount, OUT out_no_op BOOLEAN, OUT out_balance_insufficient BOOLEAN, OUT out_creditor_not_found BOOLEAN, @@ -719,16 +716,12 @@ FROM bank_wire_transfer( subject_local, amount_local, in_confirmation_date, - NULL, - NULL, - NULL + in_wire_transfer_fees ) as transfer; IF out_balance_insufficient THEN RETURN; END IF; --- TODO wire transfer fees - -- Confirm operation UPDATE taler_withdrawal_operations SET confirmation_done = true @@ -749,9 +742,7 @@ CREATE FUNCTION bank_wire_transfer( IN in_subject TEXT, IN in_amount taler_amount, IN in_transaction_date INT8, - IN in_account_servicer_reference TEXT, - IN in_payment_information_id TEXT, - IN in_end_to_end_id TEXT, + IN in_fee taler_amount, -- Error status OUT out_balance_insufficient BOOLEAN, -- Success return @@ -760,6 +751,12 @@ CREATE FUNCTION bank_wire_transfer( ) LANGUAGE plpgsql AS $$ DECLARE +amount_with_fee taler_amount; +admin_account_id INT8; +admin_has_debt BOOLEAN; +admin_balance taler_amount; +admin_payto_uri TEXT; +admin_name TEXT; debtor_has_debt BOOLEAN; debtor_balance taler_amount; debtor_max_debt taler_amount; @@ -769,12 +766,7 @@ creditor_has_debt BOOLEAN; creditor_balance taler_amount; creditor_payto_uri TEXT; creditor_name TEXT; -potential_balance taler_amount; -new_debtor_balance taler_amount; -new_debtor_balance_ok BOOLEAN; -new_creditor_balance taler_amount; -will_debtor_have_debt BOOLEAN; -will_creditor_have_debt BOOLEAN; +tmp_balance taler_amount; BEGIN -- Retrieve debtor info SELECT @@ -791,7 +783,7 @@ SELECT JOIN customers ON customer_id=owning_customer_id WHERE bank_account_id=in_debtor_account_id; IF NOT FOUND THEN - RAISE EXCEPTION 'fuck debtor'; + RAISE EXCEPTION 'Unknown debtor %', in_debtor_account_id; END IF; -- Retrieve creditor info SELECT @@ -806,7 +798,30 @@ SELECT JOIN customers ON customer_id=owning_customer_id WHERE bank_account_id=in_creditor_account_id; IF NOT FOUND THEN - RAISE EXCEPTION 'fuck creditor %', in_creditor_account_id; + RAISE EXCEPTION 'Unknown creditor %', in_creditor_account_id; +END IF; +-- Retrieve admin info +SELECT + bank_account_id, has_debt, + (balance).val, (balance).frac, + internal_payto_uri, customers.name + INTO + admin_account_id, admin_has_debt, + admin_balance.val, admin_balance.frac, + admin_payto_uri, admin_name + FROM bank_accounts + JOIN customers ON customer_id=owning_customer_id + WHERE login = 'admin'; +IF NOT FOUND THEN + RAISE EXCEPTION 'No admin'; +END IF; + +IF in_fee != (0, 0)::taler_amount AND admin_account_id != in_debtor_account_id THEN + SELECT sum.val, sum.frac + INTO amount_with_fee.val, amount_with_fee.frac + FROM amount_add(in_amount, in_fee) as sum; +ELSE + amount_with_fee = in_amount; END IF; -- DEBTOR SIDE @@ -814,48 +829,44 @@ END IF; IF debtor_has_debt THEN -- debt case: simply checking against the max debt allowed. SELECT sum.val, sum.frac - INTO potential_balance.val, potential_balance.frac - FROM amount_add(debtor_balance, in_amount) as sum; + INTO debtor_balance.val, debtor_balance.frac + FROM amount_add(debtor_balance, amount_with_fee) as sum; SELECT NOT ok INTO out_balance_insufficient FROM amount_left_minus_right(debtor_max_debt, - potential_balance); + debtor_balance); IF out_balance_insufficient THEN RETURN; END IF; - new_debtor_balance=potential_balance; - will_debtor_have_debt=TRUE; ELSE -- not a debt account SELECT NOT ok, (diff).val, (diff).frac INTO out_balance_insufficient, - potential_balance.val, - potential_balance.frac + tmp_balance.val, + tmp_balance.frac FROM amount_left_minus_right(debtor_balance, - in_amount); + amount_with_fee); IF NOT out_balance_insufficient THEN -- debtor has enough funds in the (positive) balance. - new_debtor_balance=potential_balance; - will_debtor_have_debt=FALSE; + debtor_balance=tmp_balance; ELSE -- debtor will switch to debt: determine their new negative balance. SELECT (diff).val, (diff).frac INTO - new_debtor_balance.val, new_debtor_balance.frac - FROM amount_left_minus_right(in_amount, + debtor_balance.val, debtor_balance.frac + FROM amount_left_minus_right(amount_with_fee, debtor_balance); - will_debtor_have_debt=TRUE; + debtor_has_debt=TRUE; SELECT NOT ok INTO out_balance_insufficient FROM amount_left_minus_right(debtor_max_debt, - new_debtor_balance); + debtor_balance); IF out_balance_insufficient THEN RETURN; END IF; END IF; END IF; -out_balance_insufficient=FALSE; -- CREDITOR SIDE. -- Here we figure out whether the creditor would switch @@ -863,29 +874,57 @@ out_balance_insufficient=FALSE; -- accordingly. IF NOT creditor_has_debt THEN -- easy case. SELECT sum.val, sum.frac - INTO new_creditor_balance.val, new_creditor_balance.frac + INTO creditor_balance.val, creditor_balance.frac FROM amount_add(creditor_balance, in_amount) as sum; - will_creditor_have_debt=FALSE; ELSE -- creditor had debit but MIGHT switch to credit. SELECT (diff).val, (diff).frac, NOT ok INTO - new_creditor_balance.val, new_creditor_balance.frac, - will_creditor_have_debt + tmp_balance.val, tmp_balance.frac, + creditor_has_debt FROM amount_left_minus_right(in_amount, creditor_balance); - IF will_creditor_have_debt THEN + IF NOT creditor_has_debt THEN + creditor_balance=tmp_balance; + ELSE -- the amount is not enough to bring the receiver -- to a credit state, switch operators to calculate the new balance. SELECT (diff).val, (diff).frac - INTO new_creditor_balance.val, new_creditor_balance.frac + INTO creditor_balance.val, creditor_balance.frac FROM amount_left_minus_right(creditor_balance, in_amount); END IF; END IF; +-- ADMIN SIDE. +-- Here we figure out whether the administrator would switch +-- from debit to a credit situation, and adjust the balance +-- accordingly. +IF amount_with_fee != in_amount THEN + IF NOT admin_has_debt THEN -- easy case. + SELECT sum.val, sum.frac + INTO admin_balance.val, admin_balance.frac + FROM amount_add(admin_balance, in_fee) as sum; + ELSE -- creditor had debit but MIGHT switch to credit. + SELECT (diff).val, (diff).frac, NOT ok + INTO + tmp_balance.val, tmp_balance.frac, + admin_has_debt + FROM amount_left_minus_right(in_fee, admin_balance); + IF NOT admin_has_debt THEN + admin_balance=tmp_balance; + ELSE + -- the amount is not enough to bring the receiver + -- to a credit state, switch operators to calculate the new balance. + SELECT (diff).val, (diff).frac + INTO admin_balance.val, admin_balance.frac + FROM amount_left_minus_right(admin_balance, in_fee); + END IF; + END IF; +END IF; + -- now actually create the bank transaction. -- debtor side: INSERT INTO bank_account_transactions ( @@ -896,9 +935,6 @@ INSERT INTO bank_account_transactions ( ,subject ,amount ,transaction_date - ,account_servicer_reference - ,payment_information_id - ,end_to_end_id ,direction ,bank_account_id ) @@ -910,9 +946,6 @@ VALUES ( in_subject, in_amount, in_transaction_date, - in_account_servicer_reference, - in_payment_information_id, - in_end_to_end_id, 'debit', in_debtor_account_id ) RETURNING bank_transaction_id INTO out_debit_row_id; @@ -926,9 +959,6 @@ INSERT INTO bank_account_transactions ( ,subject ,amount ,transaction_date - ,account_servicer_reference - ,payment_information_id - ,end_to_end_id ,direction ,bank_account_id ) @@ -940,9 +970,6 @@ VALUES ( in_subject, in_amount, in_transaction_date, - in_account_servicer_reference, - in_payment_information_id, - in_end_to_end_id, -- does this interest the receiving party? 'credit', in_creditor_account_id ) RETURNING bank_transaction_id INTO out_credit_row_id; @@ -950,16 +977,58 @@ VALUES ( -- checks and balances set up, now update bank accounts. UPDATE bank_accounts SET - balance=new_debtor_balance, - has_debt=will_debtor_have_debt + balance=debtor_balance, + has_debt=debtor_has_debt WHERE bank_account_id=in_debtor_account_id; UPDATE bank_accounts SET - balance=new_creditor_balance, - has_debt=will_creditor_have_debt + balance=creditor_balance, + has_debt=creditor_has_debt WHERE bank_account_id=in_creditor_account_id; +-- Fee part +IF amount_with_fee != in_amount THEN + INSERT INTO bank_account_transactions ( + creditor_payto_uri + ,creditor_name + ,debtor_payto_uri + ,debtor_name + ,subject + ,amount + ,transaction_date + ,direction + ,bank_account_id + ) + VALUES ( + admin_payto_uri, + admin_name, + debtor_payto_uri, + debtor_name, + 'transaction fee of ' || out_debit_row_id, + in_fee, + in_transaction_date, + 'debit', + in_debtor_account_id + ), ( + admin_payto_uri, + admin_name, + debtor_payto_uri, + debtor_name, + 'transaction fee of ' || out_debit_row_id, + in_amount, + in_transaction_date, + 'credit', + admin_account_id + ); + + UPDATE bank_accounts + SET + balance=admin_balance, + has_debt=admin_has_debt + WHERE bank_account_id=admin_account_id; +END IF; + -- notify new transaction PERFORM pg_notify('bank_tx', in_debtor_account_id || ' ' || in_creditor_account_id || ' ' || out_debit_row_id || ' ' || out_credit_row_id); END $$; @@ -1025,9 +1094,7 @@ SELECT in_subject, converted_amount, in_now_date, - NULL, - NULL, - NULL + (0, 0)::taler_amount ) as transfer; IF out_balance_insufficient THEN RETURN; @@ -1130,9 +1197,7 @@ FROM bank_wire_transfer( in_subject, in_amount_debit, in_now_date, - NULL, - NULL, - NULL + (0, 0)::taler_amount ) as transfer; IF out_balance_insufficient THEN RETURN;