diff options
author | Florian Dold <florian.dold@gmail.com> | 2020-01-21 16:54:50 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2020-01-21 16:54:50 +0100 |
commit | b00298b72489214fa27c5ca93e63d05d43b3f820 (patch) | |
tree | 234e9eb4d02d57a3438fa5be79e12b368cea2ade /talerbank/app/views.py | |
parent | 523591ec702a6896c9c5c70dfdab3b5b31f33cd0 (diff) | |
download | bank-b00298b72489214fa27c5ca93e63d05d43b3f820.tar.gz bank-b00298b72489214fa27c5ca93e63d05d43b3f820.tar.bz2 bank-b00298b72489214fa27c5ca93e63d05d43b3f820.zip |
transfer API idempotency
Diffstat (limited to 'talerbank/app/views.py')
-rw-r--r-- | talerbank/app/views.py | 119 |
1 files changed, 71 insertions, 48 deletions
diff --git a/talerbank/app/views.py b/talerbank/app/views.py index 9cd941d..f3fdbcc 100644 --- a/talerbank/app/views.py +++ b/talerbank/app/views.py @@ -27,6 +27,7 @@ import random import re import time import base64 +import uuid from urllib.parse import urlparse import django.contrib.auth import django.contrib.auth.views @@ -716,22 +717,25 @@ def serve_history(request, user_account): return JsonResponse(dict(data=history), status=200) + def expect_json_body_str(request, param_name): - body = json.loads(request.body) # FIXME: cache! + body = json.loads(request.body) # FIXME: cache! val = body[param_name] if not isinstance(val, str): # FIXME: throw right exception to be handled by middleware raise Exception(f"expected string for {param_name}") return val + def expect_json_body_amount(request, param_name): - body = json.loads(request.body) # FIXME: cache! + body = json.loads(request.body) # FIXME: cache! val = body[param_name] if not isinstance(val, str): # FIXME: throw right exception to be handled by middleware raise Exception(f"expected string for {param_name}") return Amount.parse(val) + def expect_param_str(request, param_name): val = request.GET[param_name] if not isinstance(val, str): @@ -770,24 +774,23 @@ def twg_add_incoming(request, user_account, acct_id): if acct_id != user_account.username: # FIXME: respond nicely - raise Exception(f"credentials do not match URL ('{acct_id}' vs '{user_account.username}')") + raise Exception( + f"credentials do not match URL ('{acct_id}' vs '{user_account.username}')" + ) reserve_pub = expect_json_body_str(request, "reserve_pub") debit_account_payto = expect_json_body_str(request, "debit_account") amount = expect_json_body_amount(request, "amount") debit_account_name = get_acct_from_payto(debit_account_payto) - print(f"adding incoming balance to exchange ({acct_id}) from account {debit_account_payto} ({debit_account_name})") + print( + f"adding incoming balance to exchange ({acct_id}) from account {debit_account_payto} ({debit_account_name})" + ) debit_user = User.objects.get(username=debit_account_name) debit_account = BankAccount.objects.get(user=debit_user) subject = f"{reserve_pub}" - wtrans = wire_transfer( - amount, - debit_account, - exchange_account, - subject, - ) + wtrans = wire_transfer(amount, debit_account, exchange_account, subject,) return JsonResponse( { @@ -809,7 +812,9 @@ def twg_transfer(request, user_account, acct_id): if acct_id != user_account.username: # FIXME: respond nicely - raise Exception(f"credentials do not match URL ('{acct_id}' vs '{user_account.username}')") + raise Exception( + f"credentials do not match URL ('{acct_id}' vs '{user_account.username}')" + ) request_uid = expect_json_body_str(request, "request_uid") wtid = expect_json_body_str(request, "wtid") @@ -823,17 +828,14 @@ def twg_transfer(request, user_account, acct_id): except User.DoesNotExist: LOGGER.error(f"credit account '{credit_account_name}' does not exist") # FIXME: use EC from taler-util library - return JsonResponse(dict(code=5110, error="credit account does not exist"), status=404) + return JsonResponse( + dict(code=5110, error="credit account does not exist"), status=404 + ) credit_account = BankAccount.objects.get(user=credit_user) subject = f"{wtid} {exchange_base_url}" - wtrans = wire_transfer( - amount, - exchange_account, - credit_account, - subject, - ) + wtrans = wire_transfer(amount, exchange_account, credit_account, subject, request_uid) return JsonResponse( { @@ -853,6 +855,7 @@ def get_payto_from_account(request, acct): h = get_plain_host(request) return f"payto://x-taler-bank/{h}/{acct.user.username}" + @require_GET @login_via_headers def twg_history_incoming(request, user_account, acct_id): @@ -863,21 +866,18 @@ def twg_history_incoming(request, user_account, acct_id): start = None else: start = int(start_str) - qs = query_history( - user_account.bankaccount, - "credit", - delta, - start, - ) + qs = query_history(user_account.bankaccount, "credit", delta, start,) for item in qs: - history.append(dict( - row_id=item.id, - amount=item.amount.stringify(settings.TALER_DIGITS), - date=dict(t_ms=(int(item.date.timestamp()) * 1000)), - reserve_pub=item.subject, # fixme: parse/truncate? - credit_account=get_payto_from_account(request, item.credit_account), - debit_account=get_payto_from_account(request, item.debit_account), - )) + history.append( + dict( + row_id=item.id, + amount=item.amount.stringify(settings.TALER_DIGITS), + date=dict(t_ms=(int(item.date.timestamp()) * 1000)), + reserve_pub=item.subject, # fixme: parse/truncate? + credit_account=get_payto_from_account(request, item.credit_account), + debit_account=get_payto_from_account(request, item.debit_account), + ) + ) return JsonResponse(dict(incoming_transactions=history), status=200) @@ -891,24 +891,21 @@ def twg_history_outgoing(request, user_account, acct_id): start = None else: start = int(start_str) - qs = query_history( - user_account.bankaccount, - "debit", - delta, - start, - ) + qs = query_history(user_account.bankaccount, "debit", delta, start,) for item in qs: # FIXME: proper parsing, more structure in subject wtid, exchange_base_url = item.subject.split(" ") - history.append(dict( - row_id=item.id, - amount=item.amount.stringify(settings.TALER_DIGITS), - date=dict(t_ms=(int(item.date.timestamp()) * 1000)), - wtid=wtid, - exchange_base_url=exchange_base_url, - credit_account=get_payto_from_account(request, item.credit_account), - debit_account=get_payto_from_account(request, item.debit_account), - )) + history.append( + dict( + row_id=item.id, + amount=item.amount.stringify(settings.TALER_DIGITS), + date=dict(t_ms=(int(item.date.timestamp()) * 1000)), + wtid=wtid, + exchange_base_url=exchange_base_url, + credit_account=get_payto_from_account(request, item.credit_account), + debit_account=get_payto_from_account(request, item.debit_account), + ) + ) return JsonResponse(dict(outgoing_transactions=history), status=200) @@ -1152,7 +1149,7 @@ def confirm_withdrawal(request, withdraw_id): raise Exception("not reached") -def wire_transfer(amount, debit_account, credit_account, subject): +def wire_transfer(amount, debit_account, credit_account, subject, request_uid=None): """ Make a wire transfer between two accounts of this demo bank. """ @@ -1160,6 +1157,31 @@ def wire_transfer(amount, debit_account, credit_account, subject): LOGGER.error("Debit and credit account are the same!") raise SameAccountException() + if request_uid is None: + request_uid = str(uuid.uuid4()) + pass + else: + # check for existing transfer + try: + etx = BankTransaction.objects.get(request_uid=request_uid) + except BankTransaction.DoesNotExist: + # We're good, no existing transaction with the same request_uid exists + pass + else: + if ( + etx.amount != amount + or etx.debit_account != debit_account + or etx.credit_account != debit_account + or etx.subject != subject + ): + return JsonResponse( + data=dict( + hint="conflicting transfer with same request_uid exists", + ec=5600, + ), + status=409, + ) + LOGGER.debug( "transfering %s => %s, %s, %s" % ( @@ -1175,6 +1197,7 @@ def wire_transfer(amount, debit_account, credit_account, subject): credit_account=credit_account, debit_account=debit_account, subject=subject, + request_uid=request_uid, ) if debit_account.user.username == "Bank": |