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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
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 FUNCTION 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_has_debt BOOLEAN;
debtor_balance taler_amount;
debtor_max_debt taler_amount;
creditor_has_debt BOOLEAN;
creditor_balance taler_amount;
potential_balance taler_amount;
potential_balance_check BOOLEAN;
new_debtor_balance taler_amount;
new_creditor_balance taler_amount;
will_debtor_have_debt BOOLEAN;
will_creditor_have_debt BOOLEAN;
spending_capacity taler_amount;
potential_balance_ok BOOLEAN;
BEGIN
-- check debtor exists.
SELECT
has_debt,
(balance).val, (balance).frac,
(max_debt).val, (max_debt).frac
INTO
debtor_has_debt,
debtor_balance.val, debtor_balance.frac,
debtor_max_debt.val, debtor_max_debt.frac
FROM bank_accounts
WHERE bank_account_id=in_debtor_account_id;
IF NOT FOUND
THEN
out_nx_debtor=TRUE;
RETURN;
END IF;
out_nx_debtor=FALSE;
-- check creditor exists. Future versions may skip this
-- due to creditors being hosted at other banks.
SELECT
has_debt,
(balance).val, (balance).frac
INTO
creditor_has_debt,
creditor_balance.val, creditor_balance.frac
FROM bank_accounts
WHERE bank_account_id=in_creditor_account_id;
IF NOT FOUND
THEN
out_nx_creditor=TRUE;
RETURN;
END IF;
out_nx_creditor=FALSE;
-- DEBTOR SIDE
-- check debtor has enough funds.
IF (debtor_has_debt)
THEN -- debt case: simply checking against the max debt allowed.
SELECT
(sum).val, (sum).frac
INTO
potential_balance.val, potential_balance.frac
FROM amount_add(debtor_balance,
in_amount);
SELECT ok
INTO potential_balance_check
FROM amount_left_minus_right(debtor_max_debt,
potential_balance);
IF (NOT potential_balance_check)
THEN
out_balance_insufficient=TRUE;
RETURN;
END IF;
new_debtor_balance=potential_balance;
will_debtor_have_debt=TRUE;
ELSE -- not a debt account
SELECT
ok,
(diff).val, (diff).frac
INTO
potential_balance_ok,
potential_balance.val,
potential_balance.frac
FROM amount_left_minus_right(debtor_balance,
in_amount);
IF (potential_balance_ok) -- debtor has enough funds in the (positive) balance.
THEN
new_debtor_balance=potential_balance;
will_debtor_have_debt=FALSE;
ELSE -- debtor will switch to debt: determine their new negative balance.
SELECT
(diff).val, (diff).frac
INTO
new_debtor_balance.val, new_debtor_balance.frac
FROM amount_left_minus_right(in_amount,
debtor_balance);
will_debtor_have_debt=TRUE;
SELECT ok
INTO potential_balance_check
FROM amount_left_minus_right(debtor_max_debt,
new_debtor_balance);
IF (NOT potential_balance_check)
THEN
out_balance_insufficient=TRUE;
RETURN;
END IF;
END IF;
END IF;
-- 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_has_debt) -- easy case.
THEN
SELECT
(sum).val, (sum).frac
INTO new_creditor_balance.val, new_creditor_balance.frac
FROM amount_add(creditor_balance,
in_amount);
will_creditor_have_debt=FALSE;
ELSE -- creditor had debit but MIGHT switch to credit.
SELECT
(diff).val, (diff).frac
INTO new_creditor_balance.val, new_creditor_balance.frac
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.
THEN
will_creditor_have_debt=TRUE;
ELSE
-- the amount would bring the account back to credit,
-- determine by how much.
SELECT
(diff).val, (diff).frac
INTO new_creditor_balance.val, new_creditor_balance.frac
FROM amount_left_minus_right(in_amount,
creditor_balance);
will_creditor_have_debt=FALSE;
END IF;
END IF;
out_balance_insufficient=FALSE;
-- 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
,transaction_date
,account_servicer_reference
,payment_information_id
,end_to_end_id
,direction
,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
,transaction_date
,account_servicer_reference
,payment_information_id
,end_to_end_id
,direction
,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
);
-- 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;
RETURN;
END $$;
COMMIT;
|