summaryrefslogtreecommitdiff
path: root/talerbank/app/views.py
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-01-21 16:54:50 +0100
committerFlorian Dold <florian.dold@gmail.com>2020-01-21 16:54:50 +0100
commitb00298b72489214fa27c5ca93e63d05d43b3f820 (patch)
tree234e9eb4d02d57a3438fa5be79e12b368cea2ade /talerbank/app/views.py
parent523591ec702a6896c9c5c70dfdab3b5b31f33cd0 (diff)
downloadbank-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.py119
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":