taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit daf4176f5b2dea94728251b6138187b83a73e429
parent c6591e06b44a5b95df91e78f2b06f6a114ce0a69
Author: Antoine A <>
Date:   Wed,  4 Nov 2026 10:54:36 +0100

wise: per balance transfer mapping

Diffstat:
Madapters/taler-wise/db/wise-0001.sql | 18++++++++++++------
Madapters/taler-wise/db/wise-procedures.sql | 38+++++++++++++++++++++++---------------
Madapters/taler-wise/src/api.rs | 4++--
Madapters/taler-wise/src/db.rs | 15+++++++++++----
Mtaler-apns-relay/src/api.rs | 1-
5 files changed, 48 insertions(+), 28 deletions(-)

diff --git a/adapters/taler-wise/db/wise-0001.sql b/adapters/taler-wise/db/wise-0001.sql @@ -55,6 +55,7 @@ CREATE TYPE incoming_type AS ENUM COMMENT ON TYPE incoming_type IS 'Types of incoming talerable transactions'; CREATE TABLE taler_in ( + balance_id INT8 NOT NULL, taler_in_id INT8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tx_in_id INT8 NOT NULL UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE, type incoming_type NOT NULL, @@ -64,25 +65,30 @@ CREATE TABLE taler_in ( ); COMMENT ON TABLE taler_in IS 'Incoming talerable transactions'; -CREATE UNIQUE INDEX taler_in_unique_reserve_pub ON taler_in (metadata) WHERE type = 'reserve'; +CREATE UNIQUE INDEX taler_in_unique_reserve_pub ON taler_in (balance_id, metadata) WHERE type = 'reserve'; CREATE TABLE prepared_in ( + balance_id INT8 NOT NULL, type incoming_type NOT NULL, account_pub BYTEA NOT NULL CHECK (LENGTH(account_pub)=32), - authorization_pub BYTEA UNIQUE NOT NULL CHECK (LENGTH(authorization_pub)=32), + authorization_pub BYTEA NOT NULL CHECK (LENGTH(authorization_pub)=32), authorization_sig BYTEA NOT NULL CHECK (LENGTH(authorization_sig)=64), recurrent BOOLEAN NOT NULL, registered_at INT8 NOT NULL, - tx_in_id INT8 UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE + tx_in_id INT8 UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE, + PRIMARY KEY (balance_id, authorization_pub) ); COMMENT ON TABLE prepared_in IS 'Prepared incoming transaction'; CREATE UNIQUE INDEX prepared_in_unique_reserve_pub - ON prepared_in (account_pub) WHERE type = 'reserve'; + ON prepared_in (balance_id, account_pub) WHERE type = 'reserve'; CREATE TABLE pending_recurrent_in( tx_in_id INT8 NOT NULL UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE, - authorization_pub BYTEA NOT NULL REFERENCES prepared_in(authorization_pub) + balance_id INT8 NOT NULL, + authorization_pub BYTEA NOT NULL, + FOREIGN KEY (balance_id, authorization_pub) + REFERENCES prepared_in(balance_id, authorization_pub) ); CREATE INDEX pending_recurrent_inc_auth_pub - ON pending_recurrent_in (authorization_pub); + ON pending_recurrent_in (balance_id, authorization_pub); COMMENT ON TABLE pending_recurrent_in IS 'Pending recurrent incoming transaction'; diff --git a/adapters/taler-wise/db/wise-procedures.sql b/adapters/taler-wise/db/wise-procedures.sql @@ -84,7 +84,7 @@ IF in_type = 'map' THEN tx_in_id IS NOT NULL AND recurrent INTO in_type, in_metadata, local_authorization_pub, local_authorization_sig, out_mapping_reuse, out_pending FROM prepared_in - WHERE authorization_pub = in_metadata; + WHERE balance_id = in_balance_id AND authorization_pub = in_metadata; out_unknown_mapping = NOT FOUND; IF out_unknown_mapping OR out_mapping_reuse THEN RETURN; @@ -93,7 +93,7 @@ END IF; -- Check conflict -out_reserve_pub_reuse=NOT out_pending AND in_type = 'reserve' AND EXISTS(SELECT FROM taler_in WHERE metadata = in_metadata AND type = 'reserve'); +out_reserve_pub_reuse=NOT out_pending AND in_type = 'reserve' AND EXISTS(SELECT FROM taler_in WHERE balance_id = in_balance_id AND metadata = in_metadata AND type = 'reserve'); IF out_reserve_pub_reuse THEN RETURN; END IF; @@ -127,24 +127,26 @@ PERFORM pg_notify('tx_in', in_balance_id || ' ' || out_tx_row_id); IF out_pending THEN -- Delay talerable registration until mapping again - INSERT INTO pending_recurrent_in (tx_in_id, authorization_pub) - VALUES (out_tx_row_id, local_authorization_pub); + INSERT INTO pending_recurrent_in (tx_in_id, balance_id, authorization_pub) + VALUES (out_tx_row_id, in_balance_id, local_authorization_pub); ELSIF in_type IS NOT NULL AND in_debit_payto IS NOT NULL THEN UPDATE prepared_in SET tx_in_id = out_tx_row_id - WHERE ( + WHERE balance_id = in_balance_id AND (( tx_in_id IS NULL AND account_pub = in_metadata AND in_type=type AND type='reserve' - ) OR authorization_pub = local_authorization_pub; + ) OR authorization_pub = local_authorization_pub); -- Lock for incoming history id commit ordering LOCK TABLE taler_in IN SHARE ROW EXCLUSIVE MODE; -- Insert new incoming talerable transaction INSERT INTO taler_in ( + balance_id, tx_in_id, type, metadata, authorization_pub, authorization_sig ) VALUES ( + in_balance_id, out_tx_row_id, in_type, in_metadata, @@ -159,6 +161,7 @@ COMMENT ON FUNCTION register_tx_in IS 'Register an incoming transaction idempote CREATE FUNCTION register_prepared_transfers ( + IN in_balance_id INT8, IN in_type incoming_type, IN in_account_pub BYTEA, IN in_authorization_pub BYTEA, @@ -182,20 +185,20 @@ SELECT type = in_type AND recurrent = in_recurrent INTO idempotent FROM prepared_in -WHERE authorization_pub = in_authorization_pub; +WHERE balance_id = in_balance_id AND authorization_pub = in_authorization_pub; -- Check idempotency and delay garbage collection IF FOUND AND idempotent THEN UPDATE prepared_in SET registered_at=in_timestamp,authorization_sig=in_authorization_sig - WHERE authorization_pub=in_authorization_pub; + WHERE balance_id = in_balance_id AND authorization_pub=in_authorization_pub; RETURN; END IF; -- Check reserve pub reuse out_reserve_pub_reuse=in_type = 'reserve' AND ( - EXISTS(SELECT FROM taler_in WHERE metadata = in_account_pub AND type = 'reserve') - OR EXISTS(SELECT FROM prepared_in WHERE account_pub = in_account_pub AND type = 'reserve' AND authorization_pub != in_authorization_pub) + EXISTS(SELECT FROM taler_in WHERE balance_id = in_balance_id AND metadata = in_account_pub AND type = 'reserve') + OR EXISTS(SELECT FROM prepared_in WHERE balance_id = in_balance_id AND account_pub = in_account_pub AND type = 'reserve' AND authorization_pub != in_authorization_pub) ); IF out_reserve_pub_reuse THEN RETURN; @@ -210,7 +213,9 @@ IF in_recurrent THEN SELECT pending.tx_in_id FROM pending_recurrent_in AS pending JOIN tx_in USING (tx_in_id) - WHERE authorization_pub = in_authorization_pub + WHERE pending.balance_id = in_balance_id + AND tx_in.balance_id = in_balance_id + AND authorization_pub = in_authorization_pub ORDER BY valued_at ASC LIMIT 1 ) @@ -219,8 +224,8 @@ IF in_recurrent THEN IF talerable_tx IS NOT NULL THEN -- Lock for incoming history id commit ordering LOCK TABLE taler_in IN SHARE ROW EXCLUSIVE MODE; - INSERT INTO taler_in (tx_in_id, type, metadata, authorization_pub, authorization_sig) - VALUES (talerable_tx, in_type, in_account_pub, in_authorization_pub, in_authorization_sig) + INSERT INTO taler_in (balance_id, tx_in_id, type, metadata, authorization_pub, authorization_sig) + VALUES (in_balance_id, talerable_tx, in_type, in_account_pub, in_authorization_pub, in_authorization_sig) RETURNING taler_in_id INTO local_taler_in_id; PERFORM pg_notify('taler_in', local_balance_id || ' ' || local_taler_in_id); END IF; @@ -228,6 +233,7 @@ END IF; -- Upsert registration INSERT INTO prepared_in ( + balance_id, type, account_pub, authorization_pub, @@ -236,6 +242,7 @@ INSERT INTO prepared_in ( registered_at, tx_in_id ) VALUES ( + in_balance_id, in_type, in_account_pub, in_authorization_pub, @@ -243,7 +250,7 @@ INSERT INTO prepared_in ( in_recurrent, in_timestamp, talerable_tx -) ON CONFLICT (authorization_pub) +) ON CONFLICT (balance_id, authorization_pub) DO UPDATE SET type = EXCLUDED.type, account_pub = EXCLUDED.account_pub, @@ -254,6 +261,7 @@ DO UPDATE SET END $$; CREATE FUNCTION delete_prepared_transfers ( + IN in_balance_id INT8, IN in_authorization_pub BYTEA, IN in_timestamp INT8, OUT out_found BOOLEAN @@ -263,7 +271,7 @@ BEGIN -- Delete registration DELETE FROM prepared_in -WHERE authorization_pub = in_authorization_pub; +WHERE balance_id = in_balance_id AND authorization_pub = in_authorization_pub; out_found = FOUND; END $$; diff --git a/adapters/taler-wise/src/api.rs b/adapters/taler-wise/src/api.rs @@ -375,7 +375,7 @@ impl PreparedTransfer for WiseBalanceApi { if creditor != self.payto { return Err(failure_code(ErrorCode::BANK_UNKNOWN_CREDITOR)); } - match db::transfer_register(&self.pool, &req).await? { + match db::transfer_register(&self.pool, self.id, &req).await? { db::RegistrationResult::Success => { let simple = TransferSubject::Simple { credit_amount: req.credit_amount, @@ -397,7 +397,7 @@ impl PreparedTransfer for WiseBalanceApi { } async fn unregistration(&self, req: Unregistration) -> ApiResult<bool> { - Ok(db::transfer_unregister(&self.pool, &req).await?) + Ok(db::transfer_unregister(&self.pool, self.id, &req).await?) } } diff --git a/adapters/taler-wise/src/db.rs b/adapters/taler-wise/src/db.rs @@ -302,7 +302,7 @@ pub async fn incoming_history( authorization_pub, authorization_sig FROM taler_in - JOIN tx_in USING (tx_in_id) + JOIN tx_in USING (tx_in_id, balance_id) WHERE balance_id = ", ); @@ -394,13 +394,15 @@ pub enum RegistrationResult { pub async fn transfer_register( db: &PgPool, + balance_id: u32, req: &RegistrationRequest, ) -> sqlx::Result<RegistrationResult> { let ty: IncomingType = req.r#type.into(); serialized!( sqlx::query( - "SELECT out_reserve_pub_reuse FROM register_prepared_transfers($1,$2,$3,$4,$5,$6)" + "SELECT out_reserve_pub_reuse FROM register_prepared_transfers($1,$2,$3,$4,$5,$6,$7)" ) + .bind(i64::from(balance_id)) .bind(ty) .bind(req.account_pub) .bind(req.authorization_pub) @@ -418,9 +420,14 @@ pub async fn transfer_register( ) } -pub async fn transfer_unregister(db: &PgPool, req: &Unregistration) -> sqlx::Result<bool> { +pub async fn transfer_unregister( + db: &PgPool, + balance_id: u32, + req: &Unregistration, +) -> sqlx::Result<bool> { serialized!( - sqlx::query("SELECT out_found FROM delete_prepared_transfers($1,$2)") + sqlx::query("SELECT out_found FROM delete_prepared_transfers($1,$2,$3)") + .bind(i64::from(balance_id)) .bind(req.authorization_pub) .bind_timestamp(&Timestamp::now()) .try_map(|r: PgRow| r.try_get_flag("out_found")) diff --git a/taler-apns-relay/src/api.rs b/taler-apns-relay/src/api.rs @@ -29,7 +29,6 @@ use taler_api::{ error::{ApiResult, failure}, extract::{Path, Req}, }; - use taler_common::error_code::ErrorCode; use crate::db;