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';