v10.8.0: Add `--wait/--no-wait` support to `sporestack token create/topup` and more
This commit is contained in:
parent
7a4f228625
commit
7398ebd1a2
|
@ -1,12 +1,4 @@
|
||||||
pipeline:
|
pipeline:
|
||||||
python-3.7:
|
|
||||||
group: test
|
|
||||||
image: python:3.7-alpine
|
|
||||||
commands:
|
|
||||||
- pip install pipenv==2023.10.24
|
|
||||||
- pipenv install --dev --deploy
|
|
||||||
- pipenv run almake test-pytest # We only test with pytest on 3.7
|
|
||||||
|
|
||||||
python-3.8:
|
python-3.8:
|
||||||
group: test
|
group: test
|
||||||
image: python:3.8
|
image: python:3.8
|
||||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -15,6 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- Nothing yet.
|
- Nothing yet.
|
||||||
|
|
||||||
|
## [10.8.0 - 2024-01-03]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Support for paying invoices without polling.
|
||||||
|
- `--qr/--no-qr` to `sporestack token topup` and `sporestack token create`.
|
||||||
|
- `--wait/--no-wait` to `sporestack token topup` and `sporestack token create`.
|
||||||
|
- `sporestack token invoice` support to view an individual invoice.
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
|
||||||
|
- Python 3.7 support.
|
||||||
|
|
||||||
## [10.7.0 - 2023-10-31]
|
## [10.7.0 - 2023-10-31]
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* Python 3.7-3.11 (or maybe newer)
|
* Python 3.8-3.11 (and likely newer)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ sporestack token info realtestingtoken
|
||||||
sporestack token messages realtestingtoken
|
sporestack token messages realtestingtoken
|
||||||
sporestack token servers realtestingtoken
|
sporestack token servers realtestingtoken
|
||||||
sporestack token invoices realtestingtoken
|
sporestack token invoices realtestingtoken
|
||||||
|
sporestack token topup realtestingtoken --currency xmr --dollars 26 --no-wait
|
||||||
|
|
||||||
sporestack server list --token realtestingtoken
|
sporestack server list --token realtestingtoken
|
||||||
sporestack server launch --no-quote --token realtestingtoken --operating-system debian-11 --days 1 --hostname sporestackpythonintegrationtestdelme
|
sporestack server launch --no-quote --token realtestingtoken --operating-system debian-11 --days 1 --hostname sporestackpythonintegrationtestdelme
|
||||||
|
@ -71,6 +72,8 @@ sporestack server rebuild --token realtestingtoken --hostname sporestackpythonin
|
||||||
sporestack server delete --token realtestingtoken --hostname sporestackpythonintegrationtestdelme
|
sporestack server delete --token realtestingtoken --hostname sporestackpythonintegrationtestdelme
|
||||||
sporestack server forget --token realtestingtoken --hostname sporestackpythonintegrationtestdelme
|
sporestack server forget --token realtestingtoken --hostname sporestackpythonintegrationtestdelme
|
||||||
|
|
||||||
|
sporestack token create newtoken --currency xmr --dollars 27 --no-wait
|
||||||
|
|
||||||
rm -r $SPORESTACK_DIR
|
rm -r $SPORESTACK_DIR
|
||||||
|
|
||||||
echo Success
|
echo Success
|
||||||
|
|
|
@ -24,7 +24,7 @@ unfixable = [
|
||||||
"F841", # Unused variable
|
"F841", # Unused variable
|
||||||
]
|
]
|
||||||
|
|
||||||
target-version = "py37"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
show_missing = true
|
show_missing = true
|
||||||
|
@ -48,7 +48,7 @@ warn_untyped_fields = true
|
||||||
name = "sporestack"
|
name = "sporestack"
|
||||||
authors = [ {name = "SporeStack", email="support@sporestack.com"} ]
|
authors = [ {name = "SporeStack", email="support@sporestack.com"} ]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = "~=3.7"
|
requires-python = "~=3.8"
|
||||||
dynamic = ["version", "description"]
|
dynamic = ["version", "description"]
|
||||||
keywords = ["bitcoin", "monero", "vps", "server"]
|
keywords = ["bitcoin", "monero", "vps", "server"]
|
||||||
license = {file = "LICENSE.txt"}
|
license = {file = "LICENSE.txt"}
|
||||||
|
@ -60,7 +60,7 @@ dependencies = [
|
||||||
"rich",
|
"rich",
|
||||||
]
|
]
|
||||||
|
|
||||||
# These will be made mandatory for v11
|
# You will have to specify sporestack[cli] for v11+.
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
cli = [
|
cli = [
|
||||||
"segno",
|
"segno",
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
__all__ = ["api", "api_client", "client", "exceptions"]
|
__all__ = ["api", "api_client", "client", "exceptions"]
|
||||||
|
|
||||||
__version__ = "10.7.2"
|
__version__ = "10.8.0"
|
||||||
|
|
|
@ -25,6 +25,7 @@ class TokenAdd:
|
||||||
"""BREAKING: This will change to models.Currency in version 11."""
|
"""BREAKING: This will change to models.Currency in version 11."""
|
||||||
dollars: int
|
dollars: int
|
||||||
affiliate_token: Union[str, None] = None
|
affiliate_token: Union[str, None] = None
|
||||||
|
legacy_polling: bool = True
|
||||||
|
|
||||||
class Response(BaseModel):
|
class Response(BaseModel):
|
||||||
token: Annotated[str, Field(deprecated=True)]
|
token: Annotated[str, Field(deprecated=True)]
|
||||||
|
|
|
@ -274,10 +274,13 @@ class APIClient:
|
||||||
token: str,
|
token: str,
|
||||||
dollars: int,
|
dollars: int,
|
||||||
currency: str,
|
currency: str,
|
||||||
|
legacy_polling: bool = True,
|
||||||
) -> api.TokenAdd.Response:
|
) -> api.TokenAdd.Response:
|
||||||
"""Add balance (money) to a token."""
|
"""Add balance (money) to a token."""
|
||||||
url = self.api_endpoint + api.TokenAdd.url.format(token=token)
|
url = self.api_endpoint + api.TokenAdd.url.format(token=token)
|
||||||
request = api.TokenAdd.Request(dollars=dollars, currency=currency)
|
request = api.TokenAdd.Request(
|
||||||
|
dollars=dollars, currency=currency, legacy_polling=legacy_polling
|
||||||
|
)
|
||||||
response = self._httpx_client.post(url, json=request.dict())
|
response = self._httpx_client.post(url, json=request.dict())
|
||||||
_handle_response(response)
|
_handle_response(response)
|
||||||
response_object = api.TokenAdd.Response.parse_obj(response.json())
|
response_object = api.TokenAdd.Response.parse_obj(response.json())
|
||||||
|
@ -313,6 +316,14 @@ class APIClient:
|
||||||
response = self._httpx_client.post(url=url, json={"message": message})
|
response = self._httpx_client.post(url=url, json={"message": message})
|
||||||
_handle_response(response)
|
_handle_response(response)
|
||||||
|
|
||||||
|
def token_invoice(self, token: str, invoice: str) -> Invoice:
|
||||||
|
"""Get a particular invoice."""
|
||||||
|
url = self.api_endpoint + f"/token/{token}/invoices/{invoice}"
|
||||||
|
response = self._httpx_client.get(url=url)
|
||||||
|
_handle_response(response)
|
||||||
|
|
||||||
|
return parse_obj_as(Invoice, response.json())
|
||||||
|
|
||||||
def token_invoices(self, token: str) -> List[Invoice]:
|
def token_invoices(self, token: str) -> List[Invoice]:
|
||||||
"""Get token invoices."""
|
"""Get token invoices."""
|
||||||
url = self.api_endpoint + f"/token/{token}/invoices"
|
url = self.api_endpoint + f"/token/{token}/invoices"
|
||||||
|
|
|
@ -85,27 +85,11 @@ def get_api_client() -> "APIClient":
|
||||||
return APIClient(api_endpoint=get_api_endpoint())
|
return APIClient(api_endpoint=get_api_endpoint())
|
||||||
|
|
||||||
|
|
||||||
def make_payment(invoice: "Invoice") -> None:
|
def invoice_qr(invoice: "Invoice") -> None:
|
||||||
import segno
|
import segno
|
||||||
|
|
||||||
from ._cli_utils import cents_to_usd
|
qr = segno.make(invoice.payment_uri)
|
||||||
|
|
||||||
uri = invoice.payment_uri
|
|
||||||
usd = cents_to_usd(invoice.amount)
|
|
||||||
expires = epoch_to_human(invoice.expires)
|
|
||||||
|
|
||||||
message = f"""Invoice: {invoice.id}
|
|
||||||
Invoice expires: {expires} (payment must be confirmed by this time)
|
|
||||||
Payment URI: {uri}
|
|
||||||
Pay *exactly* the specified amount. No more, no less.
|
|
||||||
Resize your terminal and try again if QR code above is not readable.
|
|
||||||
Press ctrl+c to abort."""
|
|
||||||
qr = segno.make(uri)
|
|
||||||
# This typer.echos.
|
|
||||||
qr.terminal()
|
qr.terminal()
|
||||||
typer.echo(message)
|
|
||||||
typer.echo(f"Approximate price in USD: {usd}")
|
|
||||||
input("[Press enter once you have made payment.]")
|
|
||||||
|
|
||||||
|
|
||||||
@server_cli.command()
|
@server_cli.command()
|
||||||
|
@ -304,17 +288,13 @@ def epoch_to_human(epoch: int) -> str:
|
||||||
|
|
||||||
def print_machine_info(info: "api.ServerInfo.Response") -> None:
|
def print_machine_info(info: "api.ServerInfo.Response") -> None:
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
|
||||||
console = Console(width=None if sys.stdout.isatty() else 10**9)
|
console = Console(width=None if sys.stdout.isatty() else 10**9)
|
||||||
|
|
||||||
output = ""
|
output = ""
|
||||||
|
|
||||||
if info.hostname != "":
|
output = ""
|
||||||
output += f"Hostname: {info.hostname}\n"
|
|
||||||
else:
|
|
||||||
output += "Hostname: (none) (No hostname set)\n"
|
|
||||||
|
|
||||||
output += f"Machine ID (keep this secret!): {info.machine_id}\n"
|
|
||||||
if info.ipv6 != "":
|
if info.ipv6 != "":
|
||||||
output += f"IPv6: {info.ipv6}\n"
|
output += f"IPv6: {info.ipv6}\n"
|
||||||
else:
|
else:
|
||||||
|
@ -345,7 +325,22 @@ def print_machine_info(info: "api.ServerInfo.Response") -> None:
|
||||||
output += f"Expiration: {epoch_to_human(info.expiration)}\n"
|
output += f"Expiration: {epoch_to_human(info.expiration)}\n"
|
||||||
output += f"Autorenew: {info.autorenew}"
|
output += f"Autorenew: {info.autorenew}"
|
||||||
|
|
||||||
console.print(output)
|
title = f"Machine ID: [italic]{info.machine_id}[/italic] "
|
||||||
|
if info.hostname != "":
|
||||||
|
title += f"[bold]({info.hostname})[/bold]"
|
||||||
|
else:
|
||||||
|
title += "(No hostname set)"
|
||||||
|
|
||||||
|
if info.autorenew:
|
||||||
|
subtitle = "Server is set to automatically renew. Watch your token balance!"
|
||||||
|
else:
|
||||||
|
subtitle = (
|
||||||
|
f"Server will expire: [italic]{epoch_to_human(info.expiration)}[/italic]"
|
||||||
|
)
|
||||||
|
|
||||||
|
panel = Panel(output, title=title, subtitle=subtitle)
|
||||||
|
|
||||||
|
console.print(panel)
|
||||||
|
|
||||||
|
|
||||||
@server_cli.command(name="list")
|
@server_cli.command(name="list")
|
||||||
|
@ -759,60 +754,34 @@ def token_create(
|
||||||
dollars: Annotated[int, typer.Option()],
|
dollars: Annotated[int, typer.Option()],
|
||||||
currency: Annotated[str, typer.Option()],
|
currency: Annotated[str, typer.Option()],
|
||||||
token: Annotated[str, typer.Argument()] = DEFAULT_TOKEN,
|
token: Annotated[str, typer.Argument()] = DEFAULT_TOKEN,
|
||||||
|
wait: bool = typer.Option(True, help="Wait for the payment to be confirmed."),
|
||||||
|
qr: bool = typer.Option(True, help="Show a QR code for the payment URI."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Enables a new token.
|
Enables a new token.
|
||||||
|
|
||||||
Dollars is starting balance.
|
Dollars is starting balance.
|
||||||
"""
|
"""
|
||||||
from httpx import HTTPError
|
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
_token = utils.random_token()
|
|
||||||
|
|
||||||
typer.echo(f"Generated key {_token} for use with token {token}", err=True)
|
|
||||||
|
|
||||||
if Path(SPORESTACK_DIR / "tokens" / f"{token}.json").exists():
|
if Path(SPORESTACK_DIR / "tokens" / f"{token}.json").exists():
|
||||||
typer.echo("Token already created! Did you mean to `topup`?", err=True)
|
typer.echo("Token already created! Did you mean to `topup`?", err=True)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
from .api_client import APIClient
|
_token = utils.random_token()
|
||||||
from .exceptions import SporeStackServerError
|
typer.echo(f"Generated key {_token} for use with token {token}", err=True)
|
||||||
|
|
||||||
api_client = APIClient(api_endpoint=get_api_endpoint())
|
save_token(token, _token)
|
||||||
|
token_add(
|
||||||
response = api_client.token_add(
|
|
||||||
token=_token,
|
token=_token,
|
||||||
dollars=dollars,
|
dollars=dollars,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
|
wait=wait,
|
||||||
|
token_name=token,
|
||||||
|
qr=qr,
|
||||||
)
|
)
|
||||||
|
typer.echo(f"{token}'s key is {_token}.")
|
||||||
make_payment(response.invoice)
|
typer.echo("Save it, don't share it, and don't lose it!")
|
||||||
|
|
||||||
tries = 360 * 2
|
|
||||||
while tries > 0:
|
|
||||||
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
|
||||||
tries = tries - 1
|
|
||||||
# FIXME: Wait two hours in a smarter way.
|
|
||||||
# Waiting for payment to set in.
|
|
||||||
time.sleep(10)
|
|
||||||
try:
|
|
||||||
response = api_client.token_add(
|
|
||||||
token=_token,
|
|
||||||
dollars=dollars,
|
|
||||||
currency=currency,
|
|
||||||
)
|
|
||||||
except (SporeStackServerError, HTTPError):
|
|
||||||
typer.echo("Received 500 HTTP status, will try again.", err=True)
|
|
||||||
continue
|
|
||||||
if response.invoice.paid:
|
|
||||||
typer.echo(f"{token} has been enabled with ${dollars}.")
|
|
||||||
typer.echo(f"{token}'s key is {_token}.")
|
|
||||||
typer.echo("Save it, don't share it, and don't lose it!")
|
|
||||||
save_token(token, _token)
|
|
||||||
return
|
|
||||||
raise ValueError(f"{token} did not get enabled in time.")
|
|
||||||
|
|
||||||
|
|
||||||
@token_cli.command(name="import")
|
@token_cli.command(name="import")
|
||||||
|
@ -824,51 +793,80 @@ def token_import(
|
||||||
save_token(name, key)
|
save_token(name, key)
|
||||||
|
|
||||||
|
|
||||||
|
def token_add(
|
||||||
|
token: str, dollars: int, currency: str, wait: bool, token_name: str, qr: bool
|
||||||
|
) -> None:
|
||||||
|
from httpx import HTTPError
|
||||||
|
|
||||||
|
from .api_client import APIClient
|
||||||
|
from .client import Client
|
||||||
|
from .exceptions import SporeStackServerError
|
||||||
|
|
||||||
|
api_client = APIClient(api_endpoint=get_api_endpoint())
|
||||||
|
client = Client(api_client=api_client, client_token=token)
|
||||||
|
|
||||||
|
invoice = client.token.add(dollars, currency=currency, legacy_polling=False)
|
||||||
|
|
||||||
|
if qr:
|
||||||
|
invoice_qr(invoice)
|
||||||
|
typer.echo()
|
||||||
|
typer.echo(
|
||||||
|
"Resize your terminal and try again if QR code above is not readable."
|
||||||
|
)
|
||||||
|
typer.echo()
|
||||||
|
invoice_panel(invoice, token=token, token_name=token_name)
|
||||||
|
typer.echo("Pay *exactly* the specified amount. No more, no less.")
|
||||||
|
|
||||||
|
if not wait:
|
||||||
|
typer.echo("--no-wait: Not waiting for payment to be confirmed.", err=True)
|
||||||
|
typer.echo(
|
||||||
|
(
|
||||||
|
f"Check status with: sporestack token invoice {token_name} "
|
||||||
|
f"--invoice-id {invoice.id}"
|
||||||
|
),
|
||||||
|
err=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
typer.echo("Press ctrl+c to abort.")
|
||||||
|
|
||||||
|
while invoice.expired is False or invoice.paid is False:
|
||||||
|
try:
|
||||||
|
invoice = client.token.invoice(invoice=invoice.id)
|
||||||
|
except (SporeStackServerError, HTTPError):
|
||||||
|
typer.echo("Received 500 HTTP status, will try again.", err=True)
|
||||||
|
continue
|
||||||
|
if invoice.paid:
|
||||||
|
typer.echo(
|
||||||
|
f"Added ${dollars} to {token_name} ({token}) with TXID {invoice.txid}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
|
if invoice.expired:
|
||||||
|
raise ValueError("Invoice has expired.")
|
||||||
|
|
||||||
|
|
||||||
@token_cli.command(name="topup")
|
@token_cli.command(name="topup")
|
||||||
def token_topup(
|
def token_topup(
|
||||||
token: str = typer.Argument(DEFAULT_TOKEN),
|
token: str = typer.Argument(DEFAULT_TOKEN),
|
||||||
dollars: int = typer.Option(...),
|
dollars: int = typer.Option(...),
|
||||||
currency: str = typer.Option(...),
|
currency: str = typer.Option(...),
|
||||||
|
wait: bool = typer.Option(True, help="Wait for the payment to be confirmed."),
|
||||||
|
qr: bool = typer.Option(True, help="Show a QR code for the payment URI."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Adds balance to an existing token."""
|
"""Adds balance to an existing token."""
|
||||||
token = load_token(token)
|
real_token = load_token(token)
|
||||||
|
token_add(
|
||||||
from httpx import HTTPError
|
token=real_token,
|
||||||
|
dollars=dollars,
|
||||||
from .api_client import APIClient
|
|
||||||
from .exceptions import SporeStackServerError
|
|
||||||
|
|
||||||
api_client = APIClient(api_endpoint=get_api_endpoint())
|
|
||||||
|
|
||||||
response = api_client.token_add(
|
|
||||||
token,
|
|
||||||
dollars,
|
|
||||||
currency=currency,
|
currency=currency,
|
||||||
|
wait=wait,
|
||||||
|
token_name=token,
|
||||||
|
qr=qr,
|
||||||
)
|
)
|
||||||
|
|
||||||
make_payment(response.invoice)
|
|
||||||
|
|
||||||
tries = 360 * 2
|
|
||||||
while tries > 0:
|
|
||||||
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
|
||||||
tries = tries - 1
|
|
||||||
# FIXME: Wait two hours in a smarter way.
|
|
||||||
try:
|
|
||||||
response = api_client.token_add(
|
|
||||||
token=token,
|
|
||||||
dollars=dollars,
|
|
||||||
currency=currency,
|
|
||||||
)
|
|
||||||
except (SporeStackServerError, HTTPError):
|
|
||||||
typer.echo("Received 500 HTTP status, will try again.", err=True)
|
|
||||||
continue
|
|
||||||
# Waiting for payment to set in.
|
|
||||||
time.sleep(10)
|
|
||||||
if response.invoice.paid:
|
|
||||||
typer.echo(f"Added {dollars} dollars to {token}")
|
|
||||||
return
|
|
||||||
raise ValueError(f"{token} did not get enabled in time.")
|
|
||||||
|
|
||||||
|
|
||||||
@token_cli.command()
|
@token_cli.command()
|
||||||
def balance(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
|
def balance(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
|
||||||
|
@ -1003,6 +1001,58 @@ def token_invoices(token: Annotated[str, typer.Argument()] = DEFAULT_TOKEN) -> N
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
def invoice_panel(invoice: "Invoice", token: str, token_name: str) -> None:
|
||||||
|
from rich import print
|
||||||
|
from rich.panel import Panel
|
||||||
|
|
||||||
|
if invoice.paid != 0:
|
||||||
|
subtitle = f"[bold]Paid[/bold] with TXID: {invoice.txid}"
|
||||||
|
elif invoice.expired:
|
||||||
|
subtitle = "[bold]Expired[/bold]"
|
||||||
|
else:
|
||||||
|
subtitle = f"Unpaid. Expires: {epoch_to_human(invoice.expires)}"
|
||||||
|
|
||||||
|
content = (
|
||||||
|
f"Invoice created: {epoch_to_human(invoice.created)}\n"
|
||||||
|
f"Payment URI: [link={invoice.payment_uri}]{invoice.payment_uri}[/link]\n"
|
||||||
|
f"Cryptocurrency: {invoice.cryptocurrency.value.upper()}\n"
|
||||||
|
f"Cryptocurrency rate: [green]${invoice.fiat_per_coin}[/green]\n"
|
||||||
|
f"Dollars to add to token: [green]${invoice.amount // 100}[/green]"
|
||||||
|
)
|
||||||
|
panel = Panel(
|
||||||
|
content,
|
||||||
|
title=(
|
||||||
|
f"SporeStack Invoice ID [italic]{invoice.id}[/italic] "
|
||||||
|
f"for token [bold]{token_name}[/bold] ([italic]{token}[/italic])"
|
||||||
|
),
|
||||||
|
subtitle=subtitle,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(panel)
|
||||||
|
|
||||||
|
|
||||||
|
@token_cli.command(name="invoice")
|
||||||
|
def token_invoice(
|
||||||
|
token: Annotated[str, typer.Argument()] = DEFAULT_TOKEN,
|
||||||
|
invoice_id: str = typer.Option(help="Invoice's ID."),
|
||||||
|
qr: bool = typer.Option(False, help="Show a QR code for the payment URI."),
|
||||||
|
) -> None:
|
||||||
|
"""Show a particular invoice."""
|
||||||
|
_token = load_token(token)
|
||||||
|
|
||||||
|
from .api_client import APIClient
|
||||||
|
from .client import Client
|
||||||
|
|
||||||
|
api_client = APIClient(api_endpoint=get_api_endpoint())
|
||||||
|
client = Client(api_client=api_client, client_token=_token)
|
||||||
|
|
||||||
|
invoice = client.token.invoice(invoice_id)
|
||||||
|
if qr:
|
||||||
|
invoice_qr(invoice)
|
||||||
|
typer.echo()
|
||||||
|
invoice_panel(invoice, token=_token, token_name=token)
|
||||||
|
|
||||||
|
|
||||||
@token_cli.command()
|
@token_cli.command()
|
||||||
def messages(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
|
def messages(token: str = typer.Argument(DEFAULT_TOKEN)) -> None:
|
||||||
"""Show support messages."""
|
"""Show support messages."""
|
||||||
|
|
|
@ -70,9 +70,15 @@ class Token:
|
||||||
token: str = field(default_factory=random_token)
|
token: str = field(default_factory=random_token)
|
||||||
api_client: APIClient = field(default_factory=APIClient)
|
api_client: APIClient = field(default_factory=APIClient)
|
||||||
|
|
||||||
def add(self, dollars: int, currency: str) -> None:
|
def add(self, dollars: int, currency: str, legacy_polling: bool = True) -> Invoice:
|
||||||
"""Add to token"""
|
"""Add to token"""
|
||||||
self.api_client.token_add(token=self.token, dollars=dollars, currency=currency)
|
response = self.api_client.token_add(
|
||||||
|
token=self.token,
|
||||||
|
dollars=dollars,
|
||||||
|
currency=currency,
|
||||||
|
legacy_polling=legacy_polling,
|
||||||
|
)
|
||||||
|
return response.invoice
|
||||||
|
|
||||||
def balance(self) -> int:
|
def balance(self) -> int:
|
||||||
"""Returns the token's balance in cents."""
|
"""Returns the token's balance in cents."""
|
||||||
|
@ -82,6 +88,10 @@ class Token:
|
||||||
"""Returns information about a token."""
|
"""Returns information about a token."""
|
||||||
return self.api_client.token_info(token=self.token)
|
return self.api_client.token_info(token=self.token)
|
||||||
|
|
||||||
|
def invoice(self, invoice: str) -> Invoice:
|
||||||
|
"""Returns the specified token's invoice."""
|
||||||
|
return self.api_client.token_invoice(token=self.token, invoice=invoice)
|
||||||
|
|
||||||
def invoices(self) -> List[Invoice]:
|
def invoices(self) -> List[Invoice]:
|
||||||
"""Returns invoices for adding balance to the token."""
|
"""Returns invoices for adding balance to the token."""
|
||||||
return self.api_client.token_invoices(token=self.token)
|
return self.api_client.token_invoices(token=self.token)
|
||||||
|
|
|
@ -79,7 +79,7 @@ class Region(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Invoice(BaseModel):
|
class Invoice(BaseModel):
|
||||||
id: Union[int, str]
|
id: str
|
||||||
payment_uri: Annotated[
|
payment_uri: Annotated[
|
||||||
str, Field(description="Cryptocurrency URI for the payment.")
|
str, Field(description="Cryptocurrency URI for the payment.")
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue