summaryrefslogtreecommitdiff
path: root/database-versioning/new/procedures.sql
blob: 798ac53f3c6ed318c2b4e774b9ca6169b844a908 (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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
BEGIN;
SET search_path TO libeufin_bank;

CREATE OR REPLACE FUNCTION amount_normalize(
    IN amount taler_amount
  ,OUT normalized taler_amount
)
LANGUAGE plpgsql
AS $$
BEGIN
  normalized.val = amount.val + amount.frac / 100000000;
  normalized.frac = amount.frac % 100000000;
END $$;
COMMENT ON FUNCTION amount_normalize
  IS 'Returns the normalized amount by adding to the .val the value of (.frac / 100000000) and removing the modulus 100000000 from .frac.';

CREATE OR REPLACE FUNCTION amount_add(
   IN a taler_amount
  ,IN b taler_amount
  ,OUT sum taler_amount
)
LANGUAGE plpgsql
AS $$
BEGIN
  sum = (a.val + b.val, a.frac + b.frac);
  CALL amount_normalize(sum ,sum);
  IF (sum.val > (1<<52))
  THEN
    RAISE EXCEPTION 'addition overflow';
  END IF;
END $$;
COMMENT ON FUNCTION amount_add
  IS 'Returns the normalized sum of two amounts. It raises an exception when the resulting .val is larger than 2^52';

CREATE OR REPLACE FUNCTION amount_left_minus_right(
  IN l taler_amount
 ,IN r taler_amount
 ,OUT diff taler_amount
 ,OUT ok BOOLEAN
)
LANGUAGE plpgsql
AS $$
BEGIN
IF (l.val > r.val)
THEN
  ok = TRUE;
  IF (l.frac >= r.frac)
  THEN
    diff.val = l.val - r.val;
    diff.frac = l.frac - r.frac;
  ELSE
    diff.val = l.val - r.val - 1;
    diff.frac = l.frac + 100000000 - r.frac;
  END IF;
ELSE
  IF (l.val = r.val) AND (l.frac >= r.frac)
  THEN
    diff.val = 0;
    diff.frac = l.frac - r.frac;
    ok = TRUE;
  ELSE
    diff = (-1, -1);
    ok = FALSE;
  END IF;
END IF;
RETURN;
END $$;
COMMENT ON FUNCTION amount_left_minus_right
  IS 'Subtracts the right amount from the left and returns the difference and TRUE, if the left amount is larger than the right, or an invalid amount and FALSE otherwise.';

CREATE OR REPLACE PROCEDURE bank_set_config(
  IN in_key TEXT,
  IN in_value TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE configuration SET config_value=in_value WHERE config_key=in_key;
IF NOT FOUND
THEN
  INSERT INTO configuration (config_key, config_value) VALUES (in_key, in_value);
END IF;

END $$;
COMMENT ON PROCEDURE bank_set_config(TEXT, TEXT)
  IS 'Update or insert configuration values';

CREATE OR REPLACE PROCEDURE bank_wire_transfer(
  IN in_creditor_account_id BIGINT,
  IN in_debtor_account_id BIGINT,
  IN in_subject TEXT,
  IN in_amount taler_amount,
  IN in_transaction_date BIGINT, -- GNUnet microseconds.
  IN in_account_servicer_reference TEXT,
  IN in_payment_information_id TEXT,
  IN in_end_to_end_id TEXT,
  OUT out_nx_creditor BOOLEAN,
  OUT out_nx_debtor BOOLEAN,
  OUT out_balance_insufficient BOOLEAN
)
LANGUAGE plpgsql
AS $$
DECLARE
debtor_account RECORD;
creditor_account RECORD;
BEGIN
-- check debtor exists.
SELECT
  INTO debtor_account
  FROM bank_accounts
  WHERE bank_account_id=in_debtor_account_id
IF NOT FOUND
  out_nx_debtor=FALSE
  out_nx_creditor=NULL
  out_balance_insufficient=NULL
  RETURN;
END IF;
-- check creditor exists.  Future versions may skip this
-- due to creditors being hosted at other banks.
SELECT
  INTO creditor_account
  FROM bank_accounts
  WHERE bank_account_id=in_creditor_account_id
IF NOT FOUND
  out_nx_debtor=TRUE
  out_nx_creditor=FALSE
  out_balance_insufficient=NULL
  RETURN;
END IF;
-- DEBTOR SIDE
-- check debtor has enough funds.
IF (debtor_account.has_debt)
THEN -- debt case: simply checking against the max debt allowed.
SELECT
  INTO potential_balance
  FROM amount_add(debtor_account.balance
                  in_amount);
SELECT *
INTO potential_balance_check
FROM amount_left_minus_right(debtor_account.max_debt,
                             potential_balance);
IF (NOT potential_balance_check.ok)
THEN
out_nx_creditor=TRUE;
out_nx_debtor=TRUE;
out_balance_insufficient=TRUE;
RETURN;
new_debtor_balance=potential_balance_check.diff;
will_debtor_have_debt=TRUE;
END IF;
ELSE -- not a debt account
SELECT -- checking first funds availability.
  INTO spending_capacity
  FROM amount_add(debtor_account.balance,
                  debtor_account.max_debt);
IF (NOT spending_capacity.ok)
THEN
out_nx_creditor=TRUE;
out_nx_debtor=TRUE;
out_balance_insufficient=TRUE;
RETURN;
END IF;
-- debtor has enough funds, now determine the new
-- balance and whether they go to debit.
SELECT
  INTO potential_balance
  FROM amount_left_minus_right(debtor_account.balance,
                               in_amount);
IF (potential_balance.ok) -- debtor has enough funds in the (positive) balance.
THEN
new_debtor_balance=potential_balance.diff;
will_debtor_have_debt=FALSE;
ELSE -- debtor will switch to debt: determine their new negative balance.
SELECT diff
  INTO new_debtor_balance
  FROM amount_left_minus_right(in_amount,
                               debtor_account.balance);
will_debtor_have_debt=TRUE;
END IF; -- closes has_debt.
-- CREDITOR SIDE.
-- Here we figure out whether the creditor would switch
-- from debit to a credit situation, and adjust the balance
-- accordingly.
IF (NOT creditor_account.has_debt) -- easy case.
THEN
SELECT
  INTO new_creditor_balance
  FROM amount_add(creditor_account.balance,
                  in_amount);
will_creditor_have_debit=FALSE;
ELSE -- creditor had debit but MIGHT switch to credit.
SELECT
  INTO new_creditor_balance
  FROM amount_left_minus_right(creditor_account.balance,
                               in_amount);
IF (new_debtor_balance.ok)
-- the debt is bigger than the amount, keep
-- this last calculated balance but stay debt.
will_creditor_have_debit=TRUE;
END IF;
-- the amount would bring the account back to credit,
-- determine by how much.
SELECT
  INTO new_creditor_balance
  FROM amount_left_minus_right(in_amount,
                               creditor_account.balance);
will_creditor_have_debit=FALSE;

-- checks and balances set up, now update bank accounts.
UPDATE bank_accounts
SET
  balance=new_debtor_balance
  has_debt=will_debtor_have_debt
WHERE bank_account_id=in_debtor_account_id;

UPDATE bank_accounts
SET
  balance=new_creditor_balance
  has_debt=will_creditor_have_debt
WHERE bank_account_id=in_creditor_account_id;

-- now actually create the bank transaction.
-- debtor side:
INSERT INTO bank_account_transactions (
  ,creditor_iban 
  ,creditor_bic
  ,creditor_name
  ,debtor_iban 
  ,debtor_bic
  ,debtor_name
  ,subject
  ,amount taler_amount
  ,transaction_date
  ,account_servicer_reference
  ,payment_information_id
  ,end_to_end_id
  ,direction direction_enum
  ,bank_account_id
  )
VALUES (
  creditor_account.iban,
  creditor_account.bic,
  creditor_account.name,
  debtor_account.iban,
  debtor_account.bic,
  debtor_account.name,
  in_subject,
  in_amount,
  in_transaction_date,
  in_account_servicer_reference,
  in_payment_information_id,
  in_end_to_end_id,
  "debit",
  in_debtor_account_id
);

-- debtor side:
INSERT INTO bank_account_transactions (
  ,creditor_iban
  ,creditor_bic
  ,creditor_name
  ,debtor_iban
  ,debtor_bic
  ,debtor_name
  ,subject
  ,amount taler_amount
  ,transaction_date
  ,account_servicer_reference
  ,payment_information_id
  ,end_to_end_id
  ,direction direction_enum
  ,bank_account_id
  )
VALUES (
  creditor_account.iban,
  creditor_account.bic,
  creditor_account.name,
  debtor_account.iban,
  debtor_account.bic,
  debtor_account.name,
  in_subject,
  in_amount,
  in_transaction_date,
  in_account_servicer_reference,
  in_payment_information_id,
  in_end_to_end_id, -- does this interest the receiving party?
  "credit",
  in_creditor_account_id
);
out_nx_debtor=TRUE;
out_nx_creditor=TRUE;
out_balance_insufficient=FALSE;
END $$;
COMMIT;