aboutsummaryrefslogtreecommitdiff
path: root/src/exchangedb/exchange_do_refund.sql
blob: a9574612763a93b433b654bd64acdf37ceccd338 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
--
-- This file is part of TALER
-- Copyright (C) 2014--2023 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 taler_amount,
  IN in_amount taler_amount,
  IN in_deposit_fee taler_amount,
  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
  bdsi INT8; -- ID of deposit being refunded
DECLARE
  tmp_val INT8; -- total amount refunded
DECLARE
  tmp_frac INT8; -- total amount refunded, large fraction to deal with overflows!
DECLARE
  tmp taler_amount; -- total amount refunded, normalized
DECLARE
  deposit taler_amount; -- 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
   bdep.batch_deposit_serial_id
  ,(cdep.amount_with_fee).val
  ,(cdep.amount_with_fee).frac
  ,bdep.done
 INTO
   bdsi
  ,deposit.val
  ,deposit.frac
  ,out_gone
 FROM batch_deposits bdep
 JOIN coin_deposits cdep
   USING (batch_deposit_serial_id)
 WHERE cdep.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 refunds
  (batch_deposit_serial_id
  ,coin_pub
  ,merchant_sig
  ,rtransaction_id
  ,amount_with_fee
  )
  VALUES
  (bdsi
  ,in_coin_pub
  ,in_merchant_sig
  ,in_rtransaction_id
  ,in_amount_with_fee
  )
  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 batch_deposit_serial_id=bdsi
     AND rtransaction_id=in_rtransaction_id
     AND amount_with_fee=in_amount_with_fee;

  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((refs.amount_with_fee).val) -- overflow here is not plausible
  ,SUM(CAST((refs.amount_with_fee).frac AS INT8)) -- compute using 64 bits
  INTO
   tmp_val
  ,tmp_frac
  FROM refunds refs
  WHERE coin_pub=in_coin_pub
    AND batch_deposit_serial_id=bdsi;
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 kc
  SET
    remaining.frac=(kc.remaining).frac+in_amount.frac
       - CASE
         WHEN (kc.remaining).frac+in_amount.frac >= 100000000
         THEN 100000000
         ELSE 0
         END,
    remaining.val=(kc.remaining).val+in_amount.val
       + CASE
         WHEN (kc.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(taler_amount, taler_amount, taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
  IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';