exchange

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

exchange_do_deposit.sql (7864B)


      1 --
      2 -- This file is part of TALER
      3 -- Copyright (C) 2014--2026 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 DROP FUNCTION IF EXISTS exchange_do_deposit;
     18 CREATE FUNCTION exchange_do_deposit(
     19   -- For batch_deposits
     20   IN in_shard INT8,
     21   IN in_merchant_pub BYTEA,
     22   IN in_merchant_sig BYTEA,
     23   IN in_wallet_timestamp INT8,
     24   IN in_exchange_timestamp INT8,
     25   IN in_refund_deadline INT8,
     26   IN in_wire_deadline INT8,
     27   IN in_h_contract_terms BYTEA,
     28   IN in_wallet_data_hash BYTEA, -- can be NULL
     29   IN in_wire_salt BYTEA,
     30   IN in_wire_target_h_payto BYTEA,
     31   IN in_h_normalized_payto BYTEA,
     32   IN in_policy_details_serial_id INT8, -- can be NULL
     33   IN in_policy_blocked BOOLEAN,
     34   -- For wire_targets
     35   IN in_receiver_wire_account TEXT,
     36   -- For coin_deposits
     37   IN ina_coin_pub BYTEA[],
     38   IN ina_coin_sig BYTEA[],
     39   IN ina_amount_with_fee taler_amount[],
     40   IN ina_deposit_fee taler_amount[],
     41   IN in_total_amount taler_amount,
     42   IN in_total_without_fee taler_amount,
     43   IN in_is_wallet BOOL,
     44   IN in_extra_wire_subject_metadata TEXT,
     45   OUT out_exchange_timestamp INT8,
     46   OUT out_accumulated_total_without_fee taler_amount,
     47   OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad balance, NULL if none
     48   OUT out_conflict BOOL
     49  )
     50 LANGUAGE plpgsql
     51 AS $$
     52 DECLARE
     53   wtsi INT8; -- wire target serial id
     54   bdsi INT8; -- batch_deposits serial id
     55   i INT4;
     56   my_record RECORD;
     57   ini_amount_with_fee taler_amount;
     58   ini_deposit_fee taler_amount;
     59   ini_coin_pub BYTEA;
     60   ini_coin_sig BYTEA;
     61   my_update BOOL := FALSE;
     62   my_total taler_amount;
     63   my_total_without_fee taler_amount;
     64 BEGIN
     65 -- Shards:
     66 --         INSERT wire_targets (by h_payto), ON CONFLICT DO NOTHING;
     67 --         INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT idempotency check;
     68 --         INSERT[] coin_deposits (by coin_pub), ON CONFLICT idempotency check;
     69 --         UPDATE[] known_coins (by coin_pub)
     70 
     71 
     72 -- Make sure the kyc_target entry exists
     73 INSERT INTO kyc_targets
     74   (h_normalized_payto
     75   ,is_wallet
     76   ) VALUES (
     77    in_h_normalized_payto
     78   ,in_is_wallet
     79   )
     80   ON CONFLICT DO NOTHING;
     81 
     82 -- First, get or create the 'wtsi'
     83 INSERT INTO wire_targets
     84   (wire_target_h_payto
     85   ,h_normalized_payto
     86   ,payto_uri
     87   ) VALUES (
     88    in_wire_target_h_payto
     89   ,in_h_normalized_payto
     90   ,in_receiver_wire_account
     91   )
     92   ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
     93   RETURNING wire_target_serial_id
     94        INTO wtsi;
     95 
     96 IF NOT FOUND
     97 THEN
     98   SELECT wire_target_serial_id
     99     INTO wtsi
    100     FROM wire_targets
    101    WHERE wire_target_h_payto=in_wire_target_h_payto;
    102 END IF;
    103 
    104 
    105 -- Second, create the batch_deposits entry
    106 INSERT INTO batch_deposits
    107   (shard
    108   ,merchant_pub
    109   ,merchant_sig
    110   ,wallet_timestamp
    111   ,exchange_timestamp
    112   ,refund_deadline
    113   ,wire_deadline
    114   ,h_contract_terms
    115   ,wallet_data_hash
    116   ,wire_salt
    117   ,wire_target_h_payto
    118   ,policy_details_serial_id
    119   ,policy_blocked
    120   ,total_amount
    121   ,total_without_fee
    122   ,extra_wire_subject_metadata
    123   ) VALUES (
    124    in_shard
    125   ,in_merchant_pub
    126   ,in_merchant_sig
    127   ,in_wallet_timestamp
    128   ,in_exchange_timestamp
    129   ,in_refund_deadline
    130   ,in_wire_deadline
    131   ,in_h_contract_terms
    132   ,in_wallet_data_hash
    133   ,in_wire_salt
    134   ,in_wire_target_h_payto
    135   ,in_policy_details_serial_id
    136   ,in_policy_blocked
    137   ,in_total_amount
    138   ,in_total_without_fee
    139   ,in_extra_wire_subject_metadata)
    140   ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms)
    141   RETURNING
    142     batch_deposit_serial_id
    143   INTO
    144     bdsi;
    145 
    146 IF FOUND
    147 THEN
    148   -- First batch deposit, we can skip all the 'my_update' logic!
    149   out_accumulated_total_without_fee = in_total_without_fee;
    150 ELSE
    151   -- Idempotency check: see if an identical record exists.
    152   -- We do select over merchant_pub, h_contract_terms and wire_target_h_payto
    153   -- first to maximally increase the chance of using the existing index.
    154   SELECT
    155       exchange_timestamp
    156      ,batch_deposit_serial_id
    157      ,total_amount
    158      ,total_without_fee
    159    INTO
    160       my_record
    161    FROM batch_deposits
    162    WHERE shard=in_shard
    163      AND merchant_pub=in_merchant_pub
    164      AND h_contract_terms=in_h_contract_terms
    165      AND wire_target_h_payto=in_wire_target_h_payto
    166      -- now check the rest, too
    167      AND ( (wallet_data_hash=in_wallet_data_hash) OR
    168            (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
    169      AND wire_salt=in_wire_salt
    170      AND wallet_timestamp=in_wallet_timestamp
    171      AND refund_deadline=in_refund_deadline
    172      AND wire_deadline=in_wire_deadline
    173      AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
    174            (policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) );
    175   IF NOT FOUND
    176   THEN
    177     -- Deposit exists, but with *strange* differences. Not allowed.
    178     out_conflict=TRUE;
    179     RETURN;
    180   END IF;
    181   out_exchange_timestamp = my_record.exchange_timestamp;
    182   bdsi = my_record.batch_deposit_serial_id;
    183   my_total = my_record.total_amount;
    184   my_total_without_fee = my_record.total_without_fee;
    185   my_update = TRUE;
    186 END IF;
    187 
    188 out_conflict=FALSE;
    189 
    190 -- Deposit each coin
    191 
    192 FOR i IN 1..array_length(ina_coin_pub,1)
    193 LOOP
    194   ini_coin_pub = ina_coin_pub[i];
    195   ini_coin_sig = ina_coin_sig[i];
    196   ini_amount_with_fee = ina_amount_with_fee[i];
    197   ini_deposit_fee = ina_deposit_fee[i];
    198 
    199   INSERT INTO coin_deposits
    200     (batch_deposit_serial_id
    201     ,coin_pub
    202     ,coin_sig
    203     ,amount_with_fee
    204     ) VALUES (
    205      bdsi
    206     ,ini_coin_pub
    207     ,ini_coin_sig
    208     ,ini_amount_with_fee
    209     )
    210     ON CONFLICT DO NOTHING;
    211 
    212   IF FOUND
    213   THEN
    214     -- Insert did happen, update balance in known_coins!
    215     UPDATE known_coins kc
    216       SET
    217         remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac
    218           + CASE
    219               WHEN (kc.remaining).frac < ini_amount_with_fee.frac
    220               THEN 100000000
    221               ELSE 0
    222             END,
    223         remaining.val=(kc.remaining).val-ini_amount_with_fee.val
    224           - CASE
    225               WHEN (kc.remaining).frac < ini_amount_with_fee.frac
    226               THEN 1
    227               ELSE 0
    228             END
    229       WHERE coin_pub=ini_coin_pub
    230         AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR
    231               ( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND
    232                 ((kc.remaining).val >= ini_amount_with_fee.val) ) );
    233 
    234     IF NOT FOUND
    235     THEN
    236       -- Insufficient balance.
    237       -- Note: C arrays are 0 indexed, but i started at 1
    238       out_insufficient_balance_coin_index=i-1;
    239       RETURN;
    240     END IF;
    241 
    242     IF my_update
    243     THEN
    244       -- Prepare to update totals in batch_deposits
    245       SELECT *
    246         INTO my_total
    247         FROM amount_add (my_total,
    248                          ini_amount_with_fee);
    249       SELECT *
    250         INTO my_total_without_fee
    251         FROM amount_add (my_total_without_fee,
    252                          ini_amount_with_fee);
    253       SELECT diff
    254         INTO my_total_without_fee
    255         FROM amount_left_minus_right (my_total_without_fee,
    256                                       ini_deposit_fee);
    257     END IF;
    258   END IF;
    259 END LOOP; -- end FOR all coins
    260 
    261 IF my_update
    262 THEN
    263   UPDATE batch_deposits
    264      SET total_amount = my_total
    265         ,total_without_fee = my_total_without_fee
    266    WHERE batch_deposit_serial_id = bdsi
    267      AND merchant_pub = in_merchant_pub
    268      AND h_contract_terms = in_h_contract_terms;
    269   out_accumulated_total_without_fee = my_total_without_fee;
    270 END IF;
    271 
    272 END $$;