merchant

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

commit 36a08455d67c67ce2fb27a38faee45600979dbed
parent 0e4fda08fd481b02d49ac814e96c46a529219ebd
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  5 Aug 2026 23:29:48 +0200

only one lock per lock_uuid and product, re-posting the lock is supposed to update, not create another one

Diffstat:
Asrc/backenddb/insert_inventory_lock.sql | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 135 insertions(+), 0 deletions(-)

diff --git a/src/backenddb/insert_inventory_lock.sql b/src/backenddb/insert_inventory_lock.sql @@ -0,0 +1,135 @@ +-- +-- 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_inventory_lock; +CREATE FUNCTION merchant_do_insert_inventory_lock ( + IN in_product_id TEXT, + IN in_lock_uuid BYTEA, + IN in_total_locked INT8, + IN in_total_locked_frac INT4, + IN in_expiration INT8, + OUT out_no_product BOOL, + OUT out_insufficient_stock BOOL) +LANGUAGE plpgsql +AS $$ +DECLARE + my_rec RECORD; + my_requested NUMERIC; + my_locked NUMERIC; + my_available NUMERIC; +BEGIN + +SELECT product_serial + ,total_stock + ,total_stock_frac + ,total_sold + ,total_sold_frac + ,total_lost + ,total_lost_frac + ,allow_fractional_quantity + INTO my_rec + FROM merchant_inventory + WHERE product_id = in_product_id; + +IF NOT FOUND +THEN + out_no_product = TRUE; + out_insufficient_stock = FALSE; + RETURN; +END IF; +out_no_product = FALSE; + +IF ( (NOT my_rec.allow_fractional_quantity) AND + (in_total_locked_frac <> 0) ) +THEN + -- Product is not sold in fractional quantities. + out_insufficient_stock = TRUE; + RETURN; +END IF; + +-- Per the API specification, locking a quantity of zero releases +-- the lock held under this UUID. +IF ( (in_total_locked = 0) AND + (in_total_locked_frac = 0) ) +THEN + DELETE FROM merchant_inventory_locks + WHERE product_serial = my_rec.product_serial + AND lock_uuid = in_lock_uuid; + out_insufficient_stock = FALSE; + RETURN; +END IF; + +-- INT64_MAX in total_stock is the sentinel for "unlimited stock", +-- for which no availability check is made. +IF (my_rec.total_stock <> 9223372036854775807) +THEN + my_requested = in_total_locked::NUMERIC * 1000000 + + in_total_locked_frac::NUMERIC; + -- Note that a lock already held under _this_ UUID is replaced and + -- thus must not count against the available stock. + SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000 + + total_locked_frac::NUMERIC), 0) + INTO my_locked + FROM merchant_inventory_locks + WHERE product_serial = my_rec.product_serial + AND lock_uuid <> in_lock_uuid; + my_locked = my_locked + + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000 + + total_locked_frac::NUMERIC), 0) + FROM merchant_order_locks + WHERE product_serial = my_rec.product_serial); + my_available = (my_rec.total_stock::NUMERIC * 1000000 + + my_rec.total_stock_frac::NUMERIC) + - (my_rec.total_sold::NUMERIC * 1000000 + + my_rec.total_sold_frac::NUMERIC) + - (my_rec.total_lost::NUMERIC * 1000000 + + my_rec.total_lost_frac::NUMERIC); + IF (my_available < my_requested + my_locked) + THEN + out_insufficient_stock = TRUE; + RETURN; + END IF; +END IF; +out_insufficient_stock = FALSE; + +INSERT INTO merchant_inventory_locks + (product_serial + ,lock_uuid + ,total_locked + ,total_locked_frac + ,expiration) + VALUES + (my_rec.product_serial + ,in_lock_uuid + ,in_total_locked + ,in_total_locked_frac + ,in_expiration) + ON CONFLICT (product_serial + ,lock_uuid) + DO UPDATE + SET total_locked = EXCLUDED.total_locked + ,total_locked_frac = EXCLUDED.total_locked_frac + ,expiration = EXCLUDED.expiration; + +END $$; + +COMMENT ON FUNCTION merchant_do_insert_inventory_lock + IS 'Locks in_total_locked(_frac) units of the given product under the' + ' given lock UUID. A lock already held under the same UUID is' + ' replaced (not added to), a quantity of zero releases it. Sets' + ' out_no_product if the product does not exist and' + ' out_insufficient_stock if not enough unlocked stock remains.';