merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit ca30b4e11c847d6082555ae69628e5e169999be9
parent 1f75da20b85e367377c7bc7b60a7659014c609f2
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon,  9 Mar 2026 17:14:38 +0100

fix #11214

Diffstat:
Msrc/backenddb/merchant-0032.sql | 74--------------------------------------------------------------------------
Asrc/backenddb/pg_base32_crockford.sql | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_deposit_confirmation.sql | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_uri_excape.sql | 37+++++++++++++++++++++++++++++++++++++
Msrc/backenddb/procedures.sql.in | 2++
5 files changed, 273 insertions(+), 74 deletions(-)

diff --git a/src/backenddb/merchant-0032.sql b/src/backenddb/merchant-0032.sql @@ -46,80 +46,6 @@ COMMENT ON FUNCTION random_bytea(INT) IS 'Create BYTEA of the given length and fill it with random bytes'; -CREATE FUNCTION base32_crockford(data BYTEA) -RETURNS TEXT -LANGUAGE plpgsql -IMMUTABLE -STRICT -PARALLEL SAFE -AS $$ -DECLARE - alphabet TEXT := '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; - chars TEXT[] := '{}'; - bit_buf BIGINT := 0; -- sliding window of accumulated bits - bit_count INT := 0; -- how many valid bits are in bit_buf - b INT; - i INT; -BEGIN - IF length(data) = 0 THEN - RETURN ''; - END IF; - - -- Process each byte, emitting 5-bit groups as we go - FOR i IN 0 .. length(data) - 1 - LOOP - b := get_byte(data, i); - bit_buf := (bit_buf << 8) | b; - bit_count := bit_count + 8; - - WHILE bit_count >= 5 - LOOP - bit_count := bit_count - 5; - chars := array_append( - chars, - substr(alphabet, ((bit_buf >> bit_count) & 31) + 1, 1) - ); - END LOOP; - END LOOP; - - -- Flush any remaining bits (zero-padded to 5) - IF bit_count > 0 - THEN - chars := array_append( - chars, - substr(alphabet, ((bit_buf << (5 - bit_count)) & 31) + 1, 1) - ); - END IF; - RETURN array_to_string(chars, ''); -END; -$$; -COMMENT ON FUNCTION base32_crockford(BYTEA) - IS 'Encodes binary data using Crockford Base32'; - - -CREATE FUNCTION uri_escape(input TEXT) -RETURNS TEXT -LANGUAGE sql -IMMUTABLE -PARALLEL SAFE -AS $$ -SELECT COALESCE(string_agg( - CASE - WHEN chr(get_byte(b, i)) ~ '[A-Za-z0-9._~-]' - THEN chr(get_byte(b, i)) - ELSE '%' || lpad(upper(to_hex(get_byte(b, i))), 2, '0') - END, - '' -), '') -FROM ( - SELECT convert_to(input, 'UTF8') AS b -) s, -generate_series(0, length(b) - 1) AS i; -$$; -COMMENT ON FUNCTION uri_escape(TEXT) - IS 'Percent-encodes all characters except RFC 3986 unreserved chars: A-Z a-z 0-9 - . _ ~'; - - CREATE OR REPLACE PROCEDURE merchant_send_kyc_notification( in_account_serial INT8 ,in_exchange_url TEXT diff --git a/src/backenddb/pg_base32_crockford.sql b/src/backenddb/pg_base32_crockford.sql @@ -0,0 +1,66 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION base32_crockford; +CREATE FUNCTION base32_crockford(data BYTEA) +RETURNS TEXT +LANGUAGE plpgsql +IMMUTABLE +STRICT +PARALLEL SAFE +AS $$ +DECLARE + alphabet TEXT := '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; + chars TEXT[] := '{}'; + bit_buf BIGINT := 0; -- sliding window of accumulated bits + bit_count INT := 0; -- how many valid bits are in bit_buf + b INT; + i INT; +BEGIN + IF length(data) = 0 THEN + RETURN ''; + END IF; + + -- Process each byte, emitting 5-bit groups as we go + FOR i IN 0 .. length(data) - 1 + LOOP + b := get_byte(data, i); + bit_buf := (bit_buf << 8) | b; + bit_count := bit_count + 8; + + WHILE bit_count >= 5 + LOOP + bit_count := bit_count - 5; + chars := array_append( + chars, + substr(alphabet, ((bit_buf >> bit_count) & 31)::INT + 1, 1) + ); + END LOOP; + END LOOP; + + -- Flush any remaining bits (zero-padded to 5) + IF bit_count > 0 + THEN + chars := array_append( + chars, + substr(alphabet, ((bit_buf << (5 - bit_count)) & 31)::INT + 1, 1) + ); + END IF; + RETURN array_to_string(chars, ''); +END; +$$; +COMMENT ON FUNCTION base32_crockford(BYTEA) + IS 'Encodes binary data using Crockford Base32'; diff --git a/src/backenddb/pg_insert_deposit_confirmation.sql b/src/backenddb/pg_insert_deposit_confirmation.sql @@ -0,0 +1,168 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant_do_insert_deposit_confirmation; +CREATE FUNCTION merchant_do_insert_deposit_confirmation ( + IN in_instance_id TEXT, + IN in_h_contract_terms BYTEA, + IN in_deposit_timestamp INT8, + IN in_exchange_url TEXT, + IN in_total_without_fees taler_amount_currency, + IN in_wire_fee taler_amount_currency, + IN in_h_wire BYTEA, + IN in_exchange_sig BYTEA, + IN in_exchange_pub BYTEA, + IN in_wire_transfer_deadline INT8, + IN in_notify_arg_str TEXT, + OUT out_no_instance BOOL, + OUT out_no_order BOOL, + OUT out_no_account BOOL, + OUT out_no_signkey BOOL, + OUT out_conflict BOOL, + OUT out_deposit_confirmation_serial) +LANGUAGE plpgsql +AS $$ +DECLARE + my_merchant_id INT8; + my_order_serial INT8; + my_account_serial INT8; + my_signkey_serial INT8; + my_record RECORD; + my_bank_serial_id INT8; + my_credit_amount taler_amount_currency; +BEGIN + +out_no_instance=TRUE; +out_no_deposit=TRUE; +out_no_account=TRUE; +out_no_signkey=TRUE; +out_conflict=FALSE; + +-- Which instance are we using? +SELECT merchant_serial + INTO my_merchant_id + FROM merchant_instances + WHERE merchant_id=in_instance_id; +IF NOT FOUND +THEN + RETURN; +END IF; +out_no_instance=FALSE; + +SELECT account_serial + INTO my_account_serial + FROM merchant_accounts + WHERE merchant_serial=my_merchant_id + AND h_wire=in_h_wire; +IF NOT FOUND +THEN + RETURN; +END IF; +out_no_account=FALSE; + +SELECT account_serial + INTO my_account_serial + FROM merchant_exchange_signing_keys + WHERE exchange_pub=in_exchange_pub; + ORDER BY start_date DESC + LIMIT 1; +IF NOT FOUND +THEN + RETURN; +END IF; +out_no_signkey=FALSE; + +SELECT order_serial + INTO my_order_serial + FROM merchant_contract_terms + WHERE merchant_serial=my_merchant_id + AND h_contract_terms=in_h_contract_terms; +IF NOT FOUND +THEN + RETURN; +END IF; +out_no_order=FALSE; + +SELECT deposit_confirmation_serial + ,deposit_timestamp + ,exchange_url + ,total_without_fee + ,wire_fee + ,wire_transfer_deadline + ,account_serial + INTO my_record + FROM merchant_deposit_confirmations + WHERE order_serial=my_order_serial + AND exchange_url=in_exchange_url; +IF NOT FOUND +THEN + INSERT INTO merchant_deposit_confirmations + (order_serial + ,deposit_timestamp + ,exchange_url + ,total_without_fee + ,wire_fee + ,exchange_sig + ,wire_transfer_deadline + ,signkey_serial + ,account_serial + ) VALUES ( + my_order_serial + ,in_deposit_timestamp + ,in_exchange_url + ,in_total_without_fee + ,in_wire_fee + ,in_exchange_sig + ,in_wire_transfer_deadline + ,my_signkey_serial + ,my_account_serial + ) RETURNING deposit_confirmation_serial + INTO out_deposit_confirmation_serial; +ELSE + IF ( (in_deposit_timestamp != my_record.deposit_timestamp) || + (in_wire_transfer_deadline != my_record.wire_transfer_deadline) || + ((in_wire_fee).val != (my_record.wire_fee).val) || + ((in_wire_frac).val != (my_record.wire_fee).frac) || + (in_wire_transfer_deadline != my_record.wire_transfer_deadline) || + (my_account_serial != my_record.account_serial) ) + THEN + out_conflict = TRUE; + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + RETURN; + END IF; + IF ( ((in_total_without_fee).val < (my_record.total_without_fee).val) || + ( ((in_total_without_fee).val = (my_record.total_without_fee).val) && + ((in_total_without_fee).frac <= (my_record.total_without_fee).frac) ) ) + THEN + -- new amount smaller or did not change, do NOT update. + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + RETURN; + END IF; + + -- Same deposit, but total amount increased, store this! + UPDATE merchant_deposit_confirmations + SET total_without_fee = in_total_without_fee + ,exchange_sig = in_exchange_sig + ,signkey_serial = my_signkey_serial; + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + +END IF; + +-- Do notify on TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE +NOTIFY XFIXME, + in_notify_arg_str; + +END $$; diff --git a/src/backenddb/pg_uri_excape.sql b/src/backenddb/pg_uri_excape.sql @@ -0,0 +1,37 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- +DROP FUNCTION uri_escape; +CREATE FUNCTION uri_escape(input TEXT) +RETURNS TEXT +LANGUAGE sql +IMMUTABLE +PARALLEL SAFE +AS $$ +SELECT COALESCE(string_agg( + CASE + WHEN chr(get_byte(b, i)) ~ '[A-Za-z0-9._~-]' + THEN chr(get_byte(b, i)) + ELSE '%' || lpad(upper(to_hex(get_byte(b, i))), 2, '0') + END, + '' +), '') +FROM ( + SELECT convert_to(input, 'UTF8') AS b +) s, +generate_series(0, length(b) - 1) AS i; +$$; +COMMENT ON FUNCTION uri_escape(TEXT) + IS 'Percent-encodes all characters except RFC 3986 unreserved chars: A-Z a-z 0-9 - . _ ~'; diff --git a/src/backenddb/procedures.sql.in b/src/backenddb/procedures.sql.in @@ -35,6 +35,8 @@ SET search_path TO merchant; #include "pg_increment_money_pots.sql" #include "pg_account_kyc_get_status.sql" #include "pg_insert_transfer.sql" +#include "pg_insert_deposit_confirmation.sql" +#include "pg_base32_crockford.sql" DROP PROCEDURE IF EXISTS merchant_do_gc; CREATE PROCEDURE merchant_do_gc(in_now INT8)