[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 02/03: Adding cash-out operations to the CLI.
From: |
gnunet |
Subject: |
[libeufin] 02/03: Adding cash-out operations to the CLI. |
Date: |
Thu, 05 Jan 2023 17:05:25 +0100 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
commit f1e1f63691b74e26cbb4605f2e5810e644f45145
Author: MS <ms@taler.net>
AuthorDate: Thu Jan 5 17:02:37 2023 +0100
Adding cash-out operations to the CLI.
---
cli/bin/circuit_test.sh | 112 +++++++++++++++++++++++++
cli/bin/libeufin-cli | 216 +++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 308 insertions(+), 20 deletions(-)
diff --git a/cli/bin/circuit_test.sh b/cli/bin/circuit_test.sh
new file mode 100755
index 00000000..b8e10fa3
--- /dev/null
+++ b/cli/bin/circuit_test.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+
+# Tests successful cases of the CLI acting
+# as the client of the Circuit API.
+
+set -eu
+
+DB_PATH=/tmp/circuit-test.sqlite3
+export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:$DB_PATH
+export LIBEUFIN_CASHOUT_TEST_TAN=secret-tan
+
+echo -n Delete previous data..
+rm -f $DB_PATH
+echo DONE
+echo -n Configure the default demobank...
+libeufin-sandbox config default
+echo DONE
+echo -n Start the bank...
+libeufin-sandbox serve &> sandbox.log &
+SANDBOX_PID=$!
+trap "echo -n 'killing the bank (pid $SANDBOX_PID)...'; kill $SANDBOX_PID;
wait; echo DONE" EXIT
+echo DONE
+echo -n Wait for the bank...
+curl --max-time 2 --retry-connrefused --retry-delay 1 --retry 10
http://localhost:5000/ &> /dev/null
+echo DONE
+echo Ask Circuit API /config...
+curl http://localhost:5000/demobanks/default/circuit-api/config &> /dev/null
+echo DONE
+echo -n "Register new account..."
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=secret
+export LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD=foo
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-register --name eee --username www \
+ --cashout-address payto://iban/FIAT --internal-iban LOCAL
+echo DONE
+echo -n Reconfigure account specifying a phone number..
+# Give phone number.
+export LIBEUFIN_SANDBOX_USERNAME=www
+export LIBEUFIN_SANDBOX_PASSWORD=foo
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-reconfig --cashout-address payto://iban/WWW --phone +999
+echo DONE
+echo -n Create a cash-out operation...
+CASHOUT_RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout --amount-debit=EUR:1 --amount-credit=CHF:0.95)
+echo DONE
+echo -n Extract the cash-out UUID...
+CASHOUT_UUID=$(echo ${CASHOUT_RESP} | jq --raw-output '.uuid')
+echo DONE
+echo -n Get cash-out details...
+RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-details \
+ --uuid $CASHOUT_UUID
+)
+OPERATION_STATUS=$(echo $RESP | jq --raw-output '.status')
+if ! test "$OPERATION_STATUS" = "PENDING"; then
+ echo Unexpected cash-out operation status found: $OPERATION_STATUS
+ exit 1
+fi
+echo DONE
+echo -n Delete the cash-out operation...
+RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-abort \
+ --uuid $CASHOUT_UUID
+)
+echo DONE
+echo -n Create another cash-out operation...
+CASHOUT_RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout --amount-debit=EUR:1 --amount-credit=CHF:0.95)
+CASHOUT_UUID=$(echo ${CASHOUT_RESP} | jq --raw-output '.uuid')
+echo DONE
+echo -n Confirm the last cash-out operation...
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-confirm --uuid $CASHOUT_UUID --tan secret-tan
+echo DONE
+# The user now has -1 balance. Let the bank
+# award EUR:1 to them, in order to bring their
+# balance to zero.
+echo -n Bring the account to 0 balance...
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=secret
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ new-transaction \
+ --bank-account admin \
+ --payto-with-subject "payto://iban/SANDBOXX/LOCAL?message=bring-to-zero" \
+ --amount EUR:1
+echo DONE
+echo -n Delete the account...
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=secret
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-delete-account --username www
+echo DONE
diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli
index 0f52f915..d4e0d943 100755
--- a/cli/bin/libeufin-cli
+++ b/cli/bin/libeufin-cli
@@ -14,16 +14,8 @@ from requests import post, get, auth, delete, patch
from urllib.parse import urljoin
from getpass import getpass
-# Extracts the circuit account username by processing
-# the arguments or the environment. It gives precedence
-# to the username met on the CLI, defaulting to the environment
-# when that is missing. Returns the username, or None if that
-# could not be found. This function helps when a username
-# is a 'resource name'. Namely, when such username points
-# at a customer username; therefore, the value 'admin' is not
-# accepted since that's never used in that way.
-def get_circuit_username(usernameCli, usernameEnv):
- maybeUsername = usernameCli
+def get_account_name(accountNameCli, usernameEnv):
+ maybeUsername = accountNameCli
if not maybeUsername:
maybeUsername = usernameEnv
if not maybeUsername:
@@ -42,6 +34,8 @@ def check_response_status(resp, expected_status_code=200):
print("Response: {}".format(resp.text), file=sys.stderr)
sys.exit(1)
+# Prints unexpected responses without exiting
+# and optionally prints expected respones.
def tell_user(resp, expected_status_code=200, withsuccess=False):
if resp.status_code != expected_status_code:
print(resp.content.decode("utf-8"), file=sys.stderr)
@@ -1535,6 +1529,128 @@ def simulate_incoming_transaction(
# The commands below request to the latest CIRCUIT API.
+@sandbox_demobank.command(
+ "circuit-cashout-confirm",
+ help="Confirm a cash-out operation. Only the author is allowed (no admin)."
+)
+@click.option(
+ "--tan",
+ help="TAN that authorizes the cash-out operaion.",
+ required=True,
+ prompt=True
+)
+@click.option(
+ "--uuid",
+ help="UUID of the cash-out operation to confirm.",
+ required=True,
+ prompt=True
+)
+@click.pass_obj
+def circuit_cashout_confirm(obj, tan, uuid):
+ cashout_confirm_endpoint = obj.circuit_api_url(f"cashouts/{uuid}/confirm")
+ req = dict(tan=tan)
+ try:
+ resp = post(
+ cashout_confirm_endpoint,
+ json=req,
+ auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception as e:
+ print(e)
+ print("Could not reach the bank at " + cashout_abort_endpoint)
+ exit(1)
+
+ check_response_status(resp, 204)
+
+
+@sandbox_demobank.command(
+ "circuit-cashout-abort",
+ help="Abort a cash-out operation. Admin and author are allowed to request."
+)
+@click.option(
+ "--uuid",
+ help="UUID of the cash-out operation to abort.",
+ required=True,
+ prompt=True
+)
+@click.pass_obj
+def circuit_cashout_abort(obj, uuid):
+ cashout_abort_endpoint = obj.circuit_api_url(f"cashouts/{uuid}/abort")
+ try:
+ resp = post(
+ cashout_abort_endpoint,
+ auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception as e:
+ print(e)
+ print("Could not reach the bank at " + cashout_abort_endpoint)
+ exit(1)
+
+ check_response_status(resp, 204)
+
+@sandbox_demobank.command(
+ "circuit-cashout-details",
+ help="Retrieve status information about one cash-out operation. Admin and
author are allowed to request."
+)
+@click.option(
+ "--uuid",
+ help="UUID of the cash-out operation to retrieve.",
+ required=True,
+ prompt=True
+)
+@click.pass_obj
+def circuit_cashout_info(obj, uuid):
+ cashout_info_endpoint = obj.circuit_api_url(f"cashouts/{uuid}")
+ try:
+ resp = get(
+ cashout_info_endpoint,
+ auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception as e:
+ print(e)
+ print("Could not reach the bank at " + cashout_info_endpoint)
+ exit(1)
+
+ check_response_status(resp)
+ tell_user(resp, withsuccess=True)
+
+@sandbox_demobank.command(
+ "circuit-delete-account",
+ help="Delete one account. Only available to the administrator and for
accounts with zero balance."
+)
+@click.option(
+ "--username",
+ help="account to delete",
+ required=True,
+ prompt=True
+)
+@click.pass_obj
+def circuit_delete(obj, username):
+
+ # Check that admin wasn't specified.
+ # Note: even if 'admin' gets through here,
+ # the bank can't delete it because its profile
+ # doesn't get a ordinary entry into the database.
+ if username == "admin":
+ print("Won't delete 'admin'", file=sys.stderr)
+ exit(1)
+
+ # Do not check credentials to let the --no-auth case
+ # function, in case the bank allows it.
+ account_deletion_endpoint = obj.circuit_api_url(f"accounts/{username}")
+ try:
+ resp = delete(
+ account_deletion_endpoint,
+ auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception as e:
+ print(e)
+ print("Could not reach sandbox at " + account_deletion_endpoint)
+ exit(1)
+
+ check_response_status(resp, expected_status_code=204)
+
+
@sandbox_demobank.command(
"circuit-register",
help="Register a new account with cash-out capabilities. It needs
administrator credentials, and the new account password exported in
LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD."
@@ -1565,14 +1681,19 @@ def simulate_incoming_transaction(
"--email",
help="E-mail address where to send the cash-out TAN.",
)
+@click.option(
+ "--internal-iban",
+ help="Which IBAN to associate to this account. The IBAN participates only
in the local currency circuit. If missing, the bank generates one.",
+)
@click.pass_obj
def circuit_register(
- obj,
- username,
- cashout_address,
- name,
- phone,
- email
+ obj,
+ username,
+ cashout_address,
+ name,
+ phone,
+ email,
+ internal_iban
):
# Check admin is requesting.
if (obj.username != "admin"):
@@ -1588,8 +1709,6 @@ def circuit_register(
# Get the bank base URL.
registration_endpoint = obj.circuit_api_url("accounts")
- # Craft the request.
-
contact_data = dict()
if (phone):
contact_data.update(phone=phone)
@@ -1602,6 +1721,8 @@ def circuit_register(
name=name,
cashout_address=cashout_address
)
+ if internal_iban:
+ req.update(internal_iban=internal_iban)
try:
resp = post(
registration_endpoint,
@@ -1646,7 +1767,7 @@ def circuit_reconfig(
cashout_address,
username
):
- resource_name = get_circuit_username(username, obj.username)
+ resource_name = get_account_name(username, obj.username)
if not resource_name:
print("Could not find any username to reconfigure.", file=sys.stderr)
reconfig_endpoint = obj.circuit_api_url(f"accounts/{resource_name}")
@@ -1682,7 +1803,7 @@ def circuit_reconfig(
)
@click.pass_obj
def password_reconfig(obj, username):
- resource_name = get_circuit_username(username, obj.username)
+ resource_name = get_account_name(username, obj.username)
if not resource_name:
print(
"Couldn't find the username whose password should change.",
@@ -1718,4 +1839,59 @@ def password_reconfig(obj, username):
check_response_status(resp, expected_status_code=204)
+
+@sandbox_demobank.command(
+ "circuit-cashout",
+ help="Create a cash-out operation. If successful, the user gets a TAN."
+)
+@click.option(
+ "--subject",
+ help="Payment subject to associate to the outgoing and incoming payments
that are associated with this cash-out operation.",
+ required=False
+)
+@click.option(
+ "--amount-debit",
+ help="Amount that will debited to the local currency account, in the
<currency>:X.Y format.",
+ required=True,
+ prompt=True
+)
+@click.option(
+ "--amount-credit",
+ help="Amount that will credited to the fiat currency account, in the
<currency>:X.Y format.",
+ required=True,
+ prompt=True
+)
+@click.option(
+ "--tan-channel",
+ help="Indicates how to send the TAN to the user: only 'sms' or 'email' are
valid values. If missing, the bank defaults to SMS",
+ required=False
+)
+@click.pass_obj
+def circuit_cashout(obj, subject, amount_debit, amount_credit, tan_channel):
+ # (not) resorting auth credentials, if they're None, request fails at the
server.
+ # Craft the request.
+ req = dict(
+ amount_debit=amount_debit,
+ amount_credit=amount_credit
+ )
+ if subject:
+ req.update(subject=subject)
+ if tan_channel:
+ req.update(tan_channel=tan_channel)
+
+ cashout_creation_endpoint = obj.circuit_api_url("cashouts")
+ try:
+ resp = post(
+ cashout_creation_endpoint,
+ json=req,
+ auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception as e:
+ print(e)
+ print("Could not reach sandbox at " + cashout_creation_endpoint)
+ exit(1)
+
+ check_response_status(resp, expected_status_code=202)
+ tell_user(resp, 202, withsuccess=True) # Communicates back the operation
UUID.
+
cli()
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.