From 87198f124c989d014adc9a2bae5098cf80555d62 Mon Sep 17 00:00:00 2001
From: Christian Grothoff <christian@grothoff.org>
Date: Tue, 6 Dec 2022 13:29:23 +0100
Subject: refactor procedures.sql

---
 src/exchangedb/exchange_do_refund.sql | 211 ++++++++++++++++++++++++++++++++++
 1 file changed, 211 insertions(+)
 create mode 100644 src/exchangedb/exchange_do_refund.sql

(limited to 'src/exchangedb/exchange_do_refund.sql')

diff --git a/src/exchangedb/exchange_do_refund.sql b/src/exchangedb/exchange_do_refund.sql
new file mode 100644
index 000000000..ceaabfe16
--- /dev/null
+++ b/src/exchangedb/exchange_do_refund.sql
@@ -0,0 +1,211 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 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/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_refund(
+  IN in_amount_with_fee_val INT8,
+  IN in_amount_with_fee_frac INT4,
+  IN in_amount_val INT8,
+  IN in_amount_frac INT4,
+  IN in_deposit_fee_val INT8,
+  IN in_deposit_fee_frac INT4,
+  IN in_h_contract_terms BYTEA,
+  IN in_rtransaction_id INT8,
+  IN in_deposit_shard INT8,
+  IN in_known_coin_id INT8,
+  IN in_coin_pub BYTEA,
+  IN in_merchant_pub BYTEA,
+  IN in_merchant_sig BYTEA,
+  OUT out_not_found BOOLEAN,
+  OUT out_refund_ok BOOLEAN,
+  OUT out_gone BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  dsi INT8; -- ID of deposit being refunded
+DECLARE
+  tmp_val INT8; -- total amount refunded
+DECLARE
+  tmp_frac INT8; -- total amount refunded
+DECLARE
+  deposit_val INT8; -- amount that was originally deposited
+DECLARE
+  deposit_frac INT8; -- amount that was originally deposited
+BEGIN
+-- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub)
+--         INSERT refunds (by coin_pub, rtransaction_id) ON CONFLICT DO NOTHING
+--         SELECT refunds (by coin_pub)
+--         UPDATE known_coins (by coin_pub)
+
+SELECT
+   deposit_serial_id
+  ,amount_with_fee_val
+  ,amount_with_fee_frac
+  ,done
+INTO
+   dsi
+  ,deposit_val
+  ,deposit_frac
+  ,out_gone
+FROM exchange.deposits
+ WHERE coin_pub=in_coin_pub
+  AND shard=in_deposit_shard
+  AND merchant_pub=in_merchant_pub
+  AND h_contract_terms=in_h_contract_terms;
+
+IF NOT FOUND
+THEN
+  -- No matching deposit found!
+  out_refund_ok=FALSE;
+  out_conflict=FALSE;
+  out_not_found=TRUE;
+  out_gone=FALSE;
+  RETURN;
+END IF;
+
+INSERT INTO exchange.refunds
+  (deposit_serial_id
+  ,coin_pub
+  ,merchant_sig
+  ,rtransaction_id
+  ,amount_with_fee_val
+  ,amount_with_fee_frac
+  )
+  VALUES
+  (dsi
+  ,in_coin_pub
+  ,in_merchant_sig
+  ,in_rtransaction_id
+  ,in_amount_with_fee_val
+  ,in_amount_with_fee_frac)
+  ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+  -- Idempotency check: see if an identical record exists.
+  -- Note that by checking 'coin_sig', we implicitly check
+  -- identity over everything that the signature covers.
+  -- We do select over merchant_pub and h_contract_terms
+  -- primarily here to maximally use the existing index.
+   PERFORM
+   FROM exchange.refunds
+   WHERE coin_pub=in_coin_pub
+     AND deposit_serial_id=dsi
+     AND rtransaction_id=in_rtransaction_id
+     AND amount_with_fee_val=in_amount_with_fee_val
+     AND amount_with_fee_frac=in_amount_with_fee_frac;
+
+  IF NOT FOUND
+  THEN
+    -- Deposit exists, but have conflicting refund.
+    out_refund_ok=FALSE;
+    out_conflict=TRUE;
+    out_not_found=FALSE;
+    RETURN;
+  END IF;
+
+  -- Idempotent request known, return success.
+  out_refund_ok=TRUE;
+  out_conflict=FALSE;
+  out_not_found=FALSE;
+  out_gone=FALSE;
+  RETURN;
+END IF;
+
+IF out_gone
+THEN
+  -- money already sent to the merchant. Tough luck.
+  out_refund_ok=FALSE;
+  out_conflict=FALSE;
+  out_not_found=FALSE;
+  RETURN;
+END IF;
+
+-- Check refund balance invariant.
+SELECT
+   SUM(amount_with_fee_val) -- overflow here is not plausible
+  ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
+  INTO
+   tmp_val
+  ,tmp_frac
+  FROM exchange.refunds
+  WHERE coin_pub=in_coin_pub
+    AND deposit_serial_id=dsi;
+IF tmp_val IS NULL
+THEN
+  RAISE NOTICE 'failed to sum up existing refunds';
+  out_refund_ok=FALSE;
+  out_conflict=FALSE;
+  out_not_found=FALSE;
+  RETURN;
+END IF;
+
+-- Normalize result before continuing
+tmp_val = tmp_val + tmp_frac / 100000000;
+tmp_frac = tmp_frac % 100000000;
+
+-- Actually check if the deposits are sufficient for the refund. Verbosely. ;-)
+IF (tmp_val < deposit_val)
+THEN
+  out_refund_ok=TRUE;
+ELSE
+  IF (tmp_val = deposit_val) AND (tmp_frac <= deposit_frac)
+  THEN
+    out_refund_ok=TRUE;
+  ELSE
+    out_refund_ok=FALSE;
+  END IF;
+END IF;
+
+IF (tmp_val = deposit_val) AND (tmp_frac = deposit_frac)
+THEN
+  -- Refunds have reached the full value of the original
+  -- deposit. Also refund the deposit fee.
+  in_amount_frac = in_amount_frac + in_deposit_fee_frac;
+  in_amount_val = in_amount_val + in_deposit_fee_val;
+
+  -- Normalize result before continuing
+  in_amount_val = in_amount_val + in_amount_frac / 100000000;
+  in_amount_frac = in_amount_frac % 100000000;
+END IF;
+
+-- Update balance of the coin.
+UPDATE known_coins
+  SET
+    remaining_frac=remaining_frac+in_amount_frac
+       - CASE
+         WHEN remaining_frac+in_amount_frac >= 100000000
+         THEN 100000000
+         ELSE 0
+         END,
+    remaining_val=remaining_val+in_amount_val
+       + CASE
+         WHEN remaining_frac+in_amount_frac >= 100000000
+         THEN 1
+         ELSE 0
+         END
+  WHERE coin_pub=in_coin_pub;
+
+
+out_conflict=FALSE;
+out_not_found=FALSE;
+
+END $$;
+
+-- COMMENT ON FUNCTION exchange_do_refund(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
+--  IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';
+
+
-- 
cgit v1.2.3

