exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_do_refund.sql (5578B)


      1 --
      2 -- This file is part of TALER
      3 -- Copyright (C) 2014--2023 Taler Systems SA
      4 --
      5 -- TALER is free software; you can redistribute it and/or modify it under the
      6 -- terms of the GNU General Public License as published by the Free Software
      7 -- Foundation; either version 3, or (at your option) any later version.
      8 --
      9 -- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10 -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11 -- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 --
     13 -- You should have received a copy of the GNU General Public License along with
     14 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 --
     16 
     17 CREATE OR REPLACE FUNCTION exchange_do_refund(
     18   IN in_amount_with_fee taler_amount,
     19   IN in_amount taler_amount,
     20   IN in_deposit_fee taler_amount,
     21   IN in_h_contract_terms BYTEA,
     22   IN in_rtransaction_id INT8,
     23   IN in_deposit_shard INT8,
     24   IN in_known_coin_id INT8,
     25   IN in_coin_pub BYTEA,
     26   IN in_merchant_pub BYTEA,
     27   IN in_merchant_sig BYTEA,
     28   OUT out_not_found BOOLEAN,
     29   OUT out_refund_ok BOOLEAN,
     30   OUT out_gone BOOLEAN,
     31   OUT out_conflict BOOLEAN)
     32 LANGUAGE plpgsql
     33 AS $$
     34 DECLARE
     35   bdsi INT8; -- ID of deposit being refunded
     36 DECLARE
     37   tmp_val INT8; -- total amount refunded
     38 DECLARE
     39   tmp_frac INT8; -- total amount refunded, large fraction to deal with overflows!
     40 DECLARE
     41   tmp taler_amount; -- total amount refunded, normalized
     42 DECLARE
     43   deposit taler_amount; -- amount that was originally deposited
     44 BEGIN
     45 -- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub)
     46 --         INSERT refunds (by coin_pub, rtransaction_id) ON CONFLICT DO NOTHING
     47 --         SELECT refunds (by coin_pub)
     48 --         UPDATE known_coins (by coin_pub)
     49 
     50 SELECT
     51    bdep.batch_deposit_serial_id
     52   ,(cdep.amount_with_fee).val
     53   ,(cdep.amount_with_fee).frac
     54   ,bdep.done
     55  INTO
     56    bdsi
     57   ,deposit.val
     58   ,deposit.frac
     59   ,out_gone
     60  FROM batch_deposits bdep
     61  JOIN coin_deposits cdep
     62    USING (batch_deposit_serial_id)
     63  WHERE cdep.coin_pub=in_coin_pub
     64   AND shard=in_deposit_shard
     65   AND merchant_pub=in_merchant_pub
     66   AND h_contract_terms=in_h_contract_terms;
     67 
     68 IF NOT FOUND
     69 THEN
     70   -- No matching deposit found!
     71   out_refund_ok=FALSE;
     72   out_conflict=FALSE;
     73   out_not_found=TRUE;
     74   out_gone=FALSE;
     75   RETURN;
     76 END IF;
     77 
     78 INSERT INTO refunds
     79   (batch_deposit_serial_id
     80   ,coin_pub
     81   ,merchant_sig
     82   ,rtransaction_id
     83   ,amount_with_fee
     84   )
     85   VALUES
     86   (bdsi
     87   ,in_coin_pub
     88   ,in_merchant_sig
     89   ,in_rtransaction_id
     90   ,in_amount_with_fee
     91   )
     92   ON CONFLICT DO NOTHING;
     93 
     94 IF NOT FOUND
     95 THEN
     96   -- Idempotency check: see if an identical record exists.
     97   -- Note that by checking 'coin_sig', we implicitly check
     98   -- identity over everything that the signature covers.
     99   -- We do select over merchant_pub and h_contract_terms
    100   -- primarily here to maximally use the existing index.
    101    PERFORM
    102    FROM exchange.refunds
    103    WHERE coin_pub=in_coin_pub
    104      AND batch_deposit_serial_id=bdsi
    105      AND rtransaction_id=in_rtransaction_id
    106      AND amount_with_fee=in_amount_with_fee;
    107 
    108   IF NOT FOUND
    109   THEN
    110     -- Deposit exists, but have conflicting refund.
    111     out_refund_ok=FALSE;
    112     out_conflict=TRUE;
    113     out_not_found=FALSE;
    114     RETURN;
    115   END IF;
    116 
    117   -- Idempotent request known, return success.
    118   out_refund_ok=TRUE;
    119   out_conflict=FALSE;
    120   out_not_found=FALSE;
    121   out_gone=FALSE;
    122   RETURN;
    123 END IF;
    124 
    125 IF out_gone
    126 THEN
    127   -- money already sent to the merchant. Tough luck.
    128   out_refund_ok=FALSE;
    129   out_conflict=FALSE;
    130   out_not_found=FALSE;
    131   RETURN;
    132 END IF;
    133 
    134 -- Check refund balance invariant.
    135 SELECT
    136    SUM((refs.amount_with_fee).val) -- overflow here is not plausible
    137   ,SUM(CAST((refs.amount_with_fee).frac AS INT8)) -- compute using 64 bits
    138   INTO
    139    tmp_val
    140   ,tmp_frac
    141   FROM refunds refs
    142   WHERE coin_pub=in_coin_pub
    143     AND batch_deposit_serial_id=bdsi;
    144 IF tmp_val IS NULL
    145 THEN
    146   RAISE NOTICE 'failed to sum up existing refunds';
    147   out_refund_ok=FALSE;
    148   out_conflict=FALSE;
    149   out_not_found=FALSE;
    150   RETURN;
    151 END IF;
    152 
    153 -- Normalize result before continuing
    154 tmp.val = tmp_val + tmp_frac / 100000000;
    155 tmp.frac = tmp_frac % 100000000;
    156 
    157 -- Actually check if the deposits are sufficient for the refund. Verbosely. ;-)
    158 IF (tmp.val < deposit.val)
    159 THEN
    160   out_refund_ok=TRUE;
    161 ELSE
    162   IF (tmp.val = deposit.val) AND (tmp.frac <= deposit.frac)
    163   THEN
    164     out_refund_ok=TRUE;
    165   ELSE
    166     out_refund_ok=FALSE;
    167   END IF;
    168 END IF;
    169 
    170 IF (tmp.val = deposit.val) AND (tmp.frac = deposit.frac)
    171 THEN
    172   -- Refunds have reached the full value of the original
    173   -- deposit. Also refund the deposit fee.
    174   in_amount.frac = in_amount.frac + in_deposit_fee.frac;
    175   in_amount.val = in_amount.val + in_deposit_fee.val;
    176 
    177   -- Normalize result before continuing
    178   in_amount.val = in_amount.val + in_amount.frac / 100000000;
    179   in_amount.frac = in_amount.frac % 100000000;
    180 END IF;
    181 
    182 -- Update balance of the coin.
    183 UPDATE known_coins kc
    184   SET
    185     remaining.frac=(kc.remaining).frac+in_amount.frac
    186        - CASE
    187          WHEN (kc.remaining).frac+in_amount.frac >= 100000000
    188          THEN 100000000
    189          ELSE 0
    190          END,
    191     remaining.val=(kc.remaining).val+in_amount.val
    192        + CASE
    193          WHEN (kc.remaining).frac+in_amount.frac >= 100000000
    194          THEN 1
    195          ELSE 0
    196          END
    197   WHERE coin_pub=in_coin_pub;
    198 
    199 out_conflict=FALSE;
    200 out_not_found=FALSE;
    201 
    202 END $$;
    203 
    204 COMMENT ON FUNCTION exchange_do_refund(taler_amount, taler_amount, taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
    205   IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';