do_purse_merge.sql (6120B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2014--2022 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_purse_merge( 18 IN in_purse_pub BYTEA, 19 IN in_merge_sig BYTEA, 20 IN in_merge_timestamp INT8, 21 IN in_reserve_sig BYTEA, 22 IN in_partner_url TEXT, 23 IN in_reserve_pub BYTEA, 24 IN in_wallet_h_payto BYTEA, 25 IN in_expiration_date INT8, 26 OUT out_no_partner BOOLEAN, 27 OUT out_no_balance BOOLEAN, 28 OUT out_conflict BOOLEAN) 29 LANGUAGE plpgsql 30 AS $$ 31 DECLARE 32 my_amount taler_amount; 33 DECLARE 34 my_purse_fee taler_amount; 35 DECLARE 36 my_partner_serial_id INT8; 37 DECLARE 38 my_in_reserve_quota BOOLEAN; 39 DECLARE 40 rval RECORD; 41 DECLARE 42 reserve_bal RECORD; 43 DECLARE 44 balance taler_amount; 45 BEGIN 46 47 -- Initialize reserve, if not yet exists. 48 INSERT INTO reserves 49 (reserve_pub 50 ,expiration_date 51 ,gc_date) 52 VALUES 53 (in_reserve_pub 54 ,in_expiration_date 55 ,in_expiration_date) 56 ON CONFLICT DO NOTHING; 57 58 59 IF in_partner_url IS NULL 60 THEN 61 my_partner_serial_id=NULL; 62 ELSE 63 SELECT 64 partner_serial_id 65 INTO 66 my_partner_serial_id 67 FROM partners 68 WHERE partner_base_url=in_partner_url 69 AND start_date <= in_merge_timestamp 70 AND end_date > in_merge_timestamp; 71 IF NOT FOUND 72 THEN 73 out_no_partner=TRUE; 74 out_conflict=FALSE; 75 -- Must be assigned: the C result spec reads it without allow_null, so 76 -- leaving it NULL turns the 404 the handler has ready for an unknown 77 -- partner into a failed extraction and an HTTP 500. 78 out_no_balance=FALSE; 79 RETURN; 80 END IF; 81 END IF; 82 83 out_no_partner=FALSE; 84 85 -- Check purse is 'full'. 86 SELECT amount_with_fee 87 ,purse_fee 88 ,in_reserve_quota 89 INTO rval 90 FROM purse_requests pr 91 WHERE purse_pub=in_purse_pub 92 AND (pr.balance).val >= (pr.amount_with_fee).val 93 AND ( (pr.balance).frac >= (pr.amount_with_fee).frac OR 94 (pr.balance).val > (pr.amount_with_fee).val ); 95 IF NOT FOUND 96 THEN 97 out_no_balance=TRUE; 98 out_conflict=FALSE; 99 RETURN; 100 END IF; 101 102 -- We use rval as workaround as we cannot select 103 -- directly into the amount due to Postgres limitations. 104 my_amount := rval.amount_with_fee; 105 my_purse_fee := rval.purse_fee; 106 my_in_reserve_quota := rval.in_reserve_quota; 107 108 out_no_balance=FALSE; 109 110 -- Store purse merge signature, checks for purse_pub uniqueness 111 INSERT INTO purse_merges 112 (partner_serial_id 113 ,reserve_pub 114 ,purse_pub 115 ,merge_sig 116 ,merge_timestamp) 117 VALUES 118 (my_partner_serial_id 119 ,in_reserve_pub 120 ,in_purse_pub 121 ,in_merge_sig 122 ,in_merge_timestamp) 123 ON CONFLICT DO NOTHING; 124 125 IF NOT FOUND 126 THEN 127 -- Idempotency check: see if an identical record exists. 128 -- Note that by checking 'merge_sig', we implicitly check 129 -- identity over everything that the signature covers. 130 PERFORM 131 FROM purse_merges 132 WHERE purse_pub=in_purse_pub 133 AND merge_sig=in_merge_sig; 134 IF NOT FOUND 135 THEN 136 -- Purse was merged, but to some other reserve. Not allowed. 137 out_conflict=TRUE; 138 RETURN; 139 END IF; 140 141 -- "success" 142 out_conflict=FALSE; 143 RETURN; 144 END IF; 145 146 147 -- Remember how this purse was finished. This will conflict 148 -- if the purse was already decided previously. 149 INSERT INTO purse_decision 150 (purse_pub 151 ,action_timestamp 152 ,refunded) 153 VALUES 154 (in_purse_pub 155 ,in_merge_timestamp 156 ,FALSE) 157 ON CONFLICT DO NOTHING; 158 159 IF NOT FOUND 160 THEN 161 -- Purse was already decided (possibly deleted or merged differently). 162 out_conflict=TRUE; 163 RETURN; 164 END IF; 165 166 out_conflict=FALSE; 167 168 169 170 IF (my_in_reserve_quota) 171 THEN 172 UPDATE reserves 173 SET purses_active=purses_active-1 174 WHERE reserve_pub IN 175 (SELECT reserve_pub 176 FROM purse_merges 177 WHERE purse_pub=in_purse_pub 178 LIMIT 1); 179 END IF; 180 181 -- Store account merge signature. 182 INSERT INTO account_merges 183 (reserve_pub 184 ,reserve_sig 185 ,purse_pub 186 ,wallet_h_payto) 187 VALUES 188 (in_reserve_pub 189 ,in_reserve_sig 190 ,in_purse_pub 191 ,in_wallet_h_payto); 192 193 -- If we need a wad transfer, mark purse ready for it. 194 IF (0 != my_partner_serial_id) 195 THEN 196 -- The taler-exchange-router will take care of this. 197 UPDATE purse_actions 198 SET action_date=0 --- "immediately" 199 ,partner_serial_id=my_partner_serial_id 200 WHERE purse_pub=in_purse_pub; 201 ELSE 202 -- This is a local reserve, update reserve balance immediately. 203 204 -- Refund the purse fee, by adding it to the purse value: 205 my_amount.val = my_amount.val + my_purse_fee.val; 206 my_amount.frac = my_amount.frac + my_purse_fee.frac; 207 -- normalize result 208 my_amount.val = my_amount.val + my_amount.frac / 100000000; 209 my_amount.frac = my_amount.frac % 100000000; 210 211 SELECT current_balance 212 INTO reserve_bal 213 FROM reserves 214 WHERE reserve_pub=in_reserve_pub; 215 216 balance = reserve_bal.current_balance; 217 balance.val=balance.val+my_amount.val 218 + CASE 219 WHEN balance.frac + my_amount.frac >= 100000000 220 THEN 1 221 ELSE 0 222 END; 223 balance.frac=balance.frac+my_amount.frac 224 - CASE 225 WHEN balance.frac + my_amount.frac >= 100000000 226 THEN 100000000 227 ELSE 0 228 END; 229 230 UPDATE reserves 231 SET current_balance=balance 232 WHERE reserve_pub=in_reserve_pub; 233 234 END IF; 235 236 RETURN; 237 238 END $$; 239 240 COMMENT ON FUNCTION exchange_do_purse_merge(BYTEA, BYTEA, INT8, BYTEA, TEXT, BYTEA, BYTEA, INT8) 241 IS 'Checks that the partner exists, the purse has not been merged with a different reserve and that the purse is full. If so, persists the merge data and either merges the purse with the reserve or marks it as ready for the taler-exchange-router. Caller MUST abort the transaction on failures so as to not persist data by accident.';