satspay initial converstion

This commit is contained in:
Tiago vasconcelos 2021-10-14 11:45:56 +01:00
parent ec89244d7f
commit e939666107
6 changed files with 130 additions and 115 deletions

View file

@ -1,13 +1,25 @@
from quart import Blueprint import asyncio
from fastapi import APIRouter
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer
db = Database("ext_satspay") db = Database("ext_satspay")
satspay_ext: Blueprint = Blueprint( satspay_ext: APIRouter = APIRouter(
"satspay", __name__, static_folder="static", template_folder="templates" prefix="/satspay",
tags=["satspay"]
) )
def satspay_renderer():
return template_renderer(
[
"lnbits/extensions/satspay/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa
from .views import * # noqa from .views import * # noqa

View file

@ -2,11 +2,10 @@ from typing import List, Optional, Union
# from lnbits.db import open_ext_db # from lnbits.db import open_ext_db
from . import db from . import db
from .models import Charges from .models import Charges, CreateCharge
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from quart import jsonify
import httpx import httpx
from lnbits.core.services import create_invoice, check_invoice_status from lnbits.core.services import create_invoice, check_invoice_status
from ..watchonly.crud import get_watch_wallet, get_fresh_address, get_mempool from ..watchonly.crud import get_watch_wallet, get_fresh_address, get_mempool
@ -17,25 +16,27 @@ from ..watchonly.crud import get_watch_wallet, get_fresh_address, get_mempool
async def create_charge( async def create_charge(
user: str, user: str,
description: str = None, data: CreateCharge
onchainwallet: Optional[str] = None, # user: str,
lnbitswallet: Optional[str] = None, # description: str = None,
webhook: Optional[str] = None, # onchainwallet: Optional[str] = None,
completelink: Optional[str] = None, # lnbitswallet: Optional[str] = None,
completelinktext: Optional[str] = "Back to Merchant", # webhook: Optional[str] = None,
time: Optional[int] = None, # completelink: Optional[str] = None,
amount: Optional[int] = None, # completelinktext: Optional[str] = "Back to Merchant",
# time: Optional[int] = None,
# amount: Optional[int] = None,
) -> Charges: ) -> Charges:
charge_id = urlsafe_short_hash() charge_id = urlsafe_short_hash()
if onchainwallet: if data.onchainwallet:
wallet = await get_watch_wallet(onchainwallet) wallet = await get_watch_wallet(data.onchainwallet)
onchain = await get_fresh_address(onchainwallet) onchain = await get_fresh_address(data.onchainwallet)
onchainaddress = onchain.address onchainaddress = onchain.address
else: else:
onchainaddress = None onchainaddress = None
if lnbitswallet: if data.lnbitswallet:
payment_hash, payment_request = await create_invoice( payment_hash, payment_request = await create_invoice(
wallet_id=lnbitswallet, amount=amount, memo=charge_id wallet_id=data.lnbitswallet, amount=data.amount, memo=charge_id
) )
else: else:
payment_hash = None payment_hash = None
@ -63,17 +64,17 @@ async def create_charge(
( (
charge_id, charge_id,
user, user,
description, data.description,
onchainwallet, data.onchainwallet,
onchainaddress, onchainaddress,
lnbitswallet, data.lnbitswallet,
payment_request, payment_request,
payment_hash, payment_hash,
webhook, data.webhook,
completelink, data.completelink,
completelinktext, data.completelinktext,
time, data.time,
amount, data.amount,
0, 0,
), ),
) )

View file

@ -1,9 +1,19 @@
from sqlite3 import Row from sqlite3 import Row
from typing import NamedTuple from fastapi.param_functions import Query
from pydantic import BaseModel
import time import time
class CreateCharge(BaseModel):
onchainwallet: str = Query(None)
lnbitswallet: str = Query(None)
description: str = Query(...)
webhook: str = Query(None)
completelink: str = Query(None)
completelinktext: str = Query(None)
time: int = Query(..., ge=1)
amount: int = Query(..., ge=1)
class Charges(NamedTuple): class Charges(BaseModel):
id: str id: str
user: str user: str
description: str description: str

View file

@ -90,7 +90,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code <code
>curl -X GET {{ request.url_root }}api/v1/charge/&lt;charge_id&gt; >curl -X GET {{ request.url_root }}api/v1/charge/&lt;charge_id&gt;
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}" -H "X-Api-Key: {{ user.wallets[0].inkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -113,7 +113,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code <code
>curl -X GET {{ request.url_root }}api/v1/charges -H "X-Api-Key: {{ >curl -X GET {{ request.url_root }}api/v1/charges -H "X-Api-Key: {{
g.user.wallets[0].inkey }}" user.wallets[0].inkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -139,7 +139,7 @@
<code <code
>curl -X DELETE {{ request.url_root >curl -X DELETE {{ request.url_root
}}api/v1/charge/&lt;charge_id&gt; -H "X-Api-Key: {{ }}api/v1/charge/&lt;charge_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].adminkey }}" user.wallets[0].adminkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -162,7 +162,7 @@
<code <code
>curl -X GET {{ request.url_root >curl -X GET {{ request.url_root
}}api/v1/charges/balance/&lt;charge_id&gt; -H "X-Api-Key: {{ }}api/v1/charges/balance/&lt;charge_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}" user.wallets[0].inkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>

View file

@ -1,22 +1,29 @@
from quart import g, abort, render_template, jsonify from fastapi.param_functions import Depends
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.core.crud import get_wallet
from lnbits.decorators import check_user_exists
from http import HTTPStatus from http import HTTPStatus
from lnbits.decorators import check_user_exists, validate_uuids from fastapi.templating import Jinja2Templates
from . import satspay_ext from . import satspay_ext, satspay_renderer
from .crud import get_charge from .crud import get_charge
templates = Jinja2Templates(directory="templates")
@satspay_ext.route("/") @satspay_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True) async def index(request: Request, user: User = Depends(check_user_exists)):
@check_user_exists() return satspay_renderer().TemplateResponse("satspay/index.html", {"request": request,"user": user.dict()})
async def index():
return await render_template("satspay/index.html", user=g.user)
@satspay_ext.route("/<charge_id>") @satspay_ext.get("/{charge_id}", response_class=HTMLResponse)
async def display(charge_id): async def display(request: Request, charge_id):
charge = await get_charge(charge_id) or abort( charge = await get_charge(charge_id)
HTTPStatus.NOT_FOUND, "Charge link does not exist." if not charge:
) raise HTTPException(
return await render_template("satspay/display.html", charge=charge) status_code=HTTPStatus.NOT_FOUND,
detail="Charge link does not exist."
)
return satspay_renderer().TemplateResponse("satspay/display.html", {"request": request, "charge": charge})

View file

@ -1,13 +1,21 @@
import hashlib import hashlib
from quart import g, jsonify, url_for
from http import HTTPStatus from http import HTTPStatus
import httpx import httpx
from fastapi import Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import WalletTypeInfo, get_key_type
from lnbits.extensions.satspay import satspay_ext from lnbits.extensions.satspay import satspay_ext
from .models import CreateCharge
from .crud import ( from .crud import (
create_charge, create_charge,
update_charge, update_charge,
@ -20,94 +28,78 @@ from .crud import (
#############################CHARGES########################## #############################CHARGES##########################
@satspay_ext.route("/api/v1/charge", methods=["POST"]) @satspay_ext.post("/api/v1/charge")
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["PUT"]) @satspay_ext.put("/api/v1/charge/{charge_id}")
@api_check_wallet_key("admin")
@api_validate_post_request( async def api_charge_create_or_update(data: CreateCharge, wallet: WalletTypeInfo = Depends(get_key_type), charge_id=None):
schema={
"onchainwallet": {"type": "string"},
"lnbitswallet": {"type": "string"},
"description": {"type": "string", "empty": False, "required": True},
"webhook": {"type": "string"},
"completelink": {"type": "string"},
"completelinktext": {"type": "string"},
"time": {"type": "integer", "min": 1, "required": True},
"amount": {"type": "integer", "min": 1, "required": True},
}
)
async def api_charge_create_or_update(charge_id=None):
if not charge_id: if not charge_id:
charge = await create_charge(user=g.wallet.user, **g.data) charge = await create_charge(user=wallet.wallet.user, **data)
return jsonify(charge._asdict()), HTTPStatus.CREATED return charge.dict()
else: else:
charge = await update_charge(charge_id=charge_id, **g.data) charge = await update_charge(charge_id=charge_id, **data)
return jsonify(charge._asdict()), HTTPStatus.OK return charge.dict()
@satspay_ext.route("/api/v1/charges", methods=["GET"]) @satspay_ext.get("/api/v1/charges")
@api_check_wallet_key("invoice") async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_charges_retrieve():
try: try:
return ( return [
jsonify(
[
{ {
**charge._asdict(), **charge.dict(),
**{"time_elapsed": charge.time_elapsed}, **{"time_elapsed": charge.time_elapsed},
**{"paid": charge.paid}, **{"paid": charge.paid},
} }
for charge in await get_charges(g.wallet.user) for charge in await get_charges(wallet.wallet.user)
] ]
),
HTTPStatus.OK,
)
except: except:
return "" return ""
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["GET"]) @satspay_ext.get("/api/v1/charge/{charge_id}")
@api_check_wallet_key("invoice") async def api_charge_retrieve(charge_id, wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_charge_retrieve(charge_id):
charge = await get_charge(charge_id) charge = await get_charge(charge_id)
if not charge: if not charge:
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Charge does not exist."
)
return ( return {
jsonify( **charge.dict(),
{
**charge._asdict(),
**{"time_elapsed": charge.time_elapsed}, **{"time_elapsed": charge.time_elapsed},
**{"paid": charge.paid}, **{"paid": charge.paid},
} }
),
HTTPStatus.OK,
)
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["DELETE"]) @satspay_ext.delete("/api/v1/charge/{charge_id}")
@api_check_wallet_key("invoice") async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_charge_delete(charge_id):
charge = await get_charge(charge_id) charge = await get_charge(charge_id)
if not charge: if not charge:
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Charge does not exist."
)
await delete_charge(charge_id) await delete_charge(charge_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
return "", HTTPStatus.NO_CONTENT
#############################BALANCE########################## #############################BALANCE##########################
@satspay_ext.route("/api/v1/charges/balance/<charge_id>", methods=["GET"]) @satspay_ext.get("/api/v1/charges/balance/{charge_id}")
async def api_charges_balance(charge_id): async def api_charges_balance(charge_id):
charge = await check_address_balance(charge_id) charge = await check_address_balance(charge_id)
if not charge: if not charge:
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Charge does not exist."
)
if charge.paid and charge.webhook: if charge.paid and charge.webhook:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
@ -130,28 +122,21 @@ async def api_charges_balance(charge_id):
) )
except AssertionError: except AssertionError:
charge.webhook = None charge.webhook = None
return jsonify(charge._asdict()), HTTPStatus.OK return charge.dict()
#############################MEMPOOL########################## #############################MEMPOOL##########################
@satspay_ext.route("/api/v1/mempool", methods=["PUT"]) @satspay_ext.put("/api/v1/mempool")
@api_check_wallet_key("invoice") async def api_update_mempool(endpoint: str = Query(...), wallet: WalletTypeInfo = Depends(get_key_type)):
@api_validate_post_request( mempool = await update_mempool(endpoint, user=wallet.wallet.user)
schema={ return mempool.dict()
"endpoint": {"type": "string", "empty": False, "required": True},
}
)
async def api_update_mempool():
mempool = await update_mempool(user=g.wallet.user, **g.data)
return jsonify(mempool._asdict()), HTTPStatus.OK
@satspay_ext.route("/api/v1/mempool", methods=["GET"]) @satspay_ext.route("/api/v1/mempool")
@api_check_wallet_key("invoice") async def api_get_mempool(wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_get_mempool(): mempool = await get_mempool(wallet.wallet.user)
mempool = await get_mempool(g.wallet.user)
if not mempool: if not mempool:
mempool = await create_mempool(user=g.wallet.user) mempool = await create_mempool(user=wallet.wallet.user)
return jsonify(mempool._asdict()), HTTPStatus.OK return mempool.dict()