This commit is contained in:
callebtc 2023-01-05 13:12:27 +01:00
commit 3129a0441e
36 changed files with 205 additions and 155 deletions

View File

@ -88,7 +88,7 @@ class Payment(BaseModel):
preimage: str
payment_hash: str
expiry: Optional[float]
extra: Optional[Dict] = {}
extra: Dict = {}
wallet_id: str
webhook: Optional[str]
webhook_status: Optional[int]

View File

@ -1,5 +1,5 @@
import secrets
from datetime import date, datetime
from datetime import datetime
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
@ -124,7 +124,6 @@ async def get_card_by_otp(otp: str) -> Optional[Card]:
async def delete_card(card_id: str) -> None:
# Delete cards
card = await get_card(card_id)
await db.execute("DELETE FROM boltcards.cards WHERE id = ?", (card_id,))
# Delete hits
hits = await get_hits([card_id])
@ -146,7 +145,7 @@ async def update_card_counter(counter: int, id: str):
async def enable_disable_card(enable: bool, id: str) -> Optional[Card]:
row = await db.execute(
await db.execute(
"UPDATE boltcards.cards SET enable = ? WHERE id = ?",
(enable, id),
)
@ -161,7 +160,7 @@ async def update_card_otp(otp: str, id: str):
async def get_hit(hit_id: str) -> Optional[Hit]:
row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id))
row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id,))
if not row:
return None
@ -182,7 +181,7 @@ async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
return [Hit(**row) for row in rows]
async def get_hits_today(card_id: str) -> Optional[Hit]:
async def get_hits_today(card_id: str) -> List[Hit]:
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id = ?",
(card_id,),
@ -259,7 +258,7 @@ async def create_refund(hit_id, refund_amount) -> Refund:
async def get_refund(refund_id: str) -> Optional[Refund]:
row = await db.fetchone(
f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id)
f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id,)
)
if not row:
return None
@ -267,7 +266,7 @@ async def get_refund(refund_id: str) -> Optional[Refund]:
return Refund.parse_obj(refund)
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
async def get_refunds(hits_ids: List[Hit]) -> List[Refund]:
if len(hits_ids) == 0:
return []

View File

@ -3,13 +3,9 @@ import secrets
from http import HTTPStatus
from urllib.parse import urlparse
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends, Query
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from starlette.exceptions import HTTPException
from starlette.requests import Request
from fastapi import HTTPException, Query, Request
from lnurl import encode as lnurl_encode
from lnurl.types import LnurlPayMetadata
from starlette.responses import HTMLResponse
from lnbits import bolt11
@ -28,14 +24,13 @@ from .crud import (
update_card_counter,
update_card_otp,
)
from .models import CreateCardData
from .nxp424 import decryptSUN, getSunMAC
###############LNURLWITHDRAW#################
# /boltcards/api/v1/scan?p=00000000000000000000000000000000&c=0000000000000000
@boltcards_ext.get("/api/v1/scan/{external_id}")
async def api_scan(p, c, request: Request, external_id: str = None):
async def api_scan(p, c, request: Request, external_id: str = Query(None)):
# some wallets send everything as lower case, no bueno
p = p.upper()
c = c.upper()
@ -63,6 +58,7 @@ async def api_scan(p, c, request: Request, external_id: str = None):
await update_card_counter(ctr_int, card.id)
# gathering some info for hit record
assert request.client
ip = request.client.host
if "x-real-ip" in request.headers:
ip = request.headers["x-real-ip"]
@ -95,7 +91,6 @@ async def api_scan(p, c, request: Request, external_id: str = None):
name="boltcards.lnurl_callback",
)
async def lnurl_callback(
request: Request,
pr: str = Query(None),
k1: str = Query(None),
):
@ -120,7 +115,9 @@ async def lnurl_callback(
return {"status": "ERROR", "reason": "Failed to decode payment request"}
card = await get_card(hit.card_id)
assert card
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
assert hit
try:
await pay_invoice(
wallet_id=card.wallet,
@ -155,7 +152,7 @@ async def api_auth(a, request: Request):
response = {
"card_name": card.card_name,
"id": 1,
"id": str(1),
"k0": card.k0,
"k1": card.k1,
"k2": card.k2,
@ -163,7 +160,7 @@ async def api_auth(a, request: Request):
"k4": card.k2,
"lnurlw_base": "lnurlw://" + lnurlw_base,
"protocol_name": "new_bolt_card_response",
"protocol_version": 1,
"protocol_version": str(1),
}
return response
@ -179,7 +176,9 @@ async def api_auth(a, request: Request):
)
async def lnurlp_response(req: Request, hit_id: str = Query(None)):
hit = await get_hit(hit_id)
assert hit
card = await get_card(hit.card_id)
assert card
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
if not card.enable:
@ -199,17 +198,17 @@ async def lnurlp_response(req: Request, hit_id: str = Query(None)):
response_class=HTMLResponse,
name="boltcards.lnurlp_callback",
)
async def lnurlp_callback(
req: Request, hit_id: str = Query(None), amount: str = Query(None)
):
async def lnurlp_callback(hit_id: str = Query(None), amount: str = Query(None)):
hit = await get_hit(hit_id)
assert hit
card = await get_card(hit.card_id)
assert card
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
payment_hash, payment_request = await create_invoice(
_, payment_request = await create_invoice(
wallet_id=card.wallet,
amount=int(amount) / 1000,
amount=int(int(amount) / 1000),
memo=f"Refund {hit_id}",
unhashed_description=LnurlPayMetadata(
json.dumps([["text/plain", "Refund"]])

View File

@ -1,14 +1,11 @@
import json
from sqlite3 import Row
from typing import Optional
from fastapi import Request
from fastapi.params import Query
from fastapi import Query, Request
from lnurl import Lnurl
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from lnurl import encode as lnurl_encode
from lnurl.types import LnurlPayMetadata
from pydantic import BaseModel
from pydantic.main import BaseModel
ZERO_KEY = "00000000000000000000000000000000"
@ -32,6 +29,7 @@ class Card(BaseModel):
otp: str
time: int
@classmethod
def from_row(cls, row: Row) -> "Card":
return cls(**dict(row))
@ -40,7 +38,7 @@ class Card(BaseModel):
return lnurl_encode(url)
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
return LnurlPayMetadata(json.dumps([["text/plain", self.card_name]]))
class CreateCardData(BaseModel):
@ -69,6 +67,7 @@ class Hit(BaseModel):
amount: int
time: int
@classmethod
def from_row(cls, row: Row) -> "Hit":
return cls(**dict(row))
@ -79,5 +78,6 @@ class Refund(BaseModel):
refund_amount: int
time: int
@classmethod
def from_row(cls, row: Row) -> "Refund":
return cls(**dict(row))

View File

@ -1,8 +1,6 @@
import asyncio
import json
import httpx
from lnbits.core import db as core_db
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
@ -21,22 +19,25 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra.get("refund"):
return
if payment.extra.get("wh_status"):
# this webhook has already been sent
return
hit = await get_hit(payment.extra.get("refund"))
hit = await get_hit(str(payment.extra.get("refund")))
if hit:
refund = await create_refund(
hit_id=hit.id, refund_amount=(payment.amount / 1000)
)
await create_refund(hit_id=hit.id, refund_amount=(payment.amount / 1000))
await mark_webhook_sent(payment, 1)
async def mark_webhook_sent(payment: Payment, status: int) -> None:
if not payment.extra:
return
payment.extra["wh_status"] = status
await core_db.execute(

View File

@ -1,5 +1,4 @@
from fastapi import FastAPI, Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse

View File

@ -1,10 +1,6 @@
import secrets
from http import HTTPStatus
from fastapi.params import Depends, Query
from loguru import logger
from starlette.exceptions import HTTPException
from starlette.requests import Request
from fastapi import Depends, HTTPException, Query
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
@ -15,13 +11,11 @@ from .crud import (
delete_card,
enable_disable_card,
get_card,
get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
update_card_otp,
)
from .models import CreateCardData
@ -33,7 +27,8 @@ async def api_cards(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [card.dict() for card in await get_cards(wallet_ids)]
@ -41,9 +36,8 @@ async def api_cards(
@boltcards_ext.post("/api/v1/cards", status_code=HTTPStatus.CREATED)
@boltcards_ext.put("/api/v1/cards/{card_id}", status_code=HTTPStatus.OK)
async def api_card_create_or_update(
# req: Request,
data: CreateCardData,
card_id: str = None,
card_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
@ -95,6 +89,7 @@ async def api_card_create_or_update(
status_code=HTTPStatus.BAD_REQUEST,
)
card = await create_card(wallet_id=wallet.wallet.id, data=data)
assert card
return card.dict()
@ -110,6 +105,7 @@ async def enable_card(
if card.wallet != wallet.wallet.id:
raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN)
card = await enable_disable_card(enable=enable, id=card_id)
assert card
return card.dict()
@ -136,7 +132,8 @@ async def api_hits(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
cards = await get_cards(wallet_ids)
cards_ids = []
@ -153,15 +150,13 @@ async def api_refunds(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
cards = await get_cards(wallet_ids)
cards_ids = []
for card in cards:
cards_ids.append(card.id)
hits = await get_hits(cards_ids)
hits_ids = []
for hit in hits:
hits_ids.append(hit.id)
return [refund.dict() for refund in await get_refunds(hits_ids)]
return [refund.dict() for refund in await get_refunds(hits)]

View File

@ -28,6 +28,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra and not payment.extra.get("tag") == "cashu":
if payment.extra.get("tag") != "cashu":
return
return

View File

@ -24,12 +24,12 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
webhook = None
data = None
if not payment.extra or payment.extra.get("tag") != "copilot":
if payment.extra.get("tag") != "copilot":
# not an copilot invoice
return
webhook = None
data = None
copilot = await get_copilot(payment.extra.get("copilotid", -1))
if not copilot:

View File

@ -25,9 +25,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "invoices":
return

View File

@ -17,8 +17,8 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra:
if payment.extra.get("tag") != "jukebox":
# not a jukebox invoice
return
await update_jukebox_payment(payment.payment_hash, paid=True)
if payment.extra.get("tag") != "jukebox":
# not a jukebox invoice
return
await update_jukebox_payment(payment.payment_hash, paid=True)

View File

@ -16,7 +16,7 @@ async def cloudflare_create_record(domain: Domains, ip: str):
"Content-Type": "application/json",
}
cf_response = ""
cf_response = {}
async with httpx.AsyncClient() as client:
try:
r = await client.post(
@ -31,9 +31,9 @@ async def cloudflare_create_record(domain: Domains, ip: str):
},
timeout=40,
)
cf_response = json.loads(r.text)
cf_response = r.json()
except AssertionError:
cf_response = "Error occured"
cf_response = {"error": "Error occured"}
return cf_response
@ -53,3 +53,4 @@ async def cloudflare_deleterecord(domain: Domains, domain_id: str):
cf_response = r.text
except AssertionError:
cf_response = "Error occured"
return cf_response

View File

@ -128,6 +128,7 @@ async def get_addresses(wallet_ids: Union[str, List[str]]) -> List[Addresses]:
async def set_address_paid(payment_hash: str) -> Addresses:
address = await get_address(payment_hash)
assert address
if address.paid == False:
await db.execute(
@ -146,6 +147,7 @@ async def set_address_paid(payment_hash: str) -> Addresses:
async def set_address_renewed(address_id: str, duration: int):
address = await get_address(address_id)
assert address
extend_duration = int(address.duration) + duration
await db.execute(

View File

@ -1,17 +1,9 @@
import hashlib
import json
from datetime import datetime, timedelta
import httpx
from fastapi.params import Query
from lnurl import ( # type: ignore
LnurlErrorResponse,
LnurlPayActionResponse,
LnurlPayResponse,
)
from fastapi import Query, Request
from lnurl import LnurlErrorResponse
from loguru import logger
from starlette.requests import Request
from starlette.responses import HTMLResponse
from . import lnaddress_ext
from .crud import get_address, get_address_by_username, get_domain
@ -52,6 +44,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)):
amount_received = amount
domain = await get_domain(address.domain)
assert domain
base_url = (
address.wallet_endpoint[:-1]
@ -79,7 +72,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)):
)
r = call.json()
except AssertionError as e:
except Exception:
return LnurlErrorResponse(reason="ERROR")
# resp = LnurlPayActionResponse(pr=r["payment_request"], routes=[])

View File

@ -1,9 +1,9 @@
import json
from typing import Optional
from fastapi.params import Query
from fastapi import Query
from lnurl.types import LnurlPayMetadata
from pydantic.main import BaseModel
from pydantic import BaseModel
class CreateDomain(BaseModel):

View File

@ -1,6 +1,7 @@
import asyncio
import httpx
from loguru import logger
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
@ -21,7 +22,9 @@ async def wait_for_paid_invoices():
async def call_webhook_on_paid(payment_hash):
### Use webhook to notify about cloudflare registration
address = await get_address(payment_hash)
assert address
domain = await get_domain(address.domain)
assert domain
if not domain.webhook:
return
@ -39,24 +42,23 @@ async def call_webhook_on_paid(payment_hash):
},
timeout=40,
)
except AssertionError:
webhook = None
r.raise_for_status()
except Exception as e:
logger.error(f"lnaddress: error calling webhook on paid: {str(e)}")
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") == "lnaddress":
if payment.extra.get("tag") == "lnaddress":
await payment.set_pending(False)
await set_address_paid(payment_hash=payment.payment_hash)
await call_webhook_on_paid(payment_hash=payment.payment_hash)
elif payment.extra.get("tag") == "renew lnaddress":
await payment.set_pending(False)
await set_address_renewed(
address_id=payment.extra["id"], duration=payment.extra["duration"]
)
await call_webhook_on_paid(payment_hash=payment.payment_hash)
else:
return

View File

@ -1,10 +1,8 @@
from http import HTTPStatus
from urllib.parse import urlparse
from fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, HTTPException, Request
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.crud import get_wallet
@ -35,6 +33,7 @@ async def display(domain_id, request: Request):
await purge_addresses(domain_id)
wallet = await get_wallet(domain.wallet)
assert wallet
url = urlparse(str(request.url))
return lnaddress_renderer().TemplateResponse(

View File

@ -1,9 +1,7 @@
from http import HTTPStatus
from urllib.parse import urlparse
from fastapi import Request
from fastapi.params import Depends, Query
from starlette.exceptions import HTTPException
from fastapi import Depends, HTTPException, Query, Request
from lnbits.core.crud import get_user
from lnbits.core.services import check_transaction_status, create_invoice
@ -11,7 +9,7 @@ from lnbits.decorators import WalletTypeInfo, get_key_type
from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain
from . import lnaddress_ext
from .cloudflare import cloudflare_create_record, cloudflare_deleterecord
from .cloudflare import cloudflare_create_record
from .crud import (
check_address_available,
create_address,
@ -35,7 +33,8 @@ async def api_domains(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [domain.dict() for domain in await get_domains(wallet_ids)]
@ -69,7 +68,7 @@ async def api_domain_create(
cf_response = await cloudflare_create_record(domain=domain, ip=root_url)
if not cf_response or cf_response["success"] != True:
if not cf_response or not cf_response["success"]:
await delete_domain(domain.id)
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@ -106,7 +105,8 @@ async def api_addresses(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [address.dict() for address in await get_addresses(wallet_ids)]
@ -227,7 +227,9 @@ async def api_lnaddress_make_address(
@lnaddress_ext.get("/api/v1/addresses/{payment_hash}")
async def api_address_send_address(payment_hash):
address = await get_address(payment_hash)
assert address
domain = await get_domain(address.domain)
assert domain
try:
status = await check_transaction_status(domain.wallet, payment_hash)
is_paid = not status.pending

View File

@ -19,7 +19,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra or payment.extra.get("tag") != "lnticket":
if payment.extra.get("tag") != "lnticket":
# not a lnticket invoice
return

View File

@ -1,19 +1,18 @@
from typing import List, Optional, Union
from lnbits.db import SQLITE
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import CreatePayLinkData, PayLink
async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
link_id = urlsafe_short_hash()[:6]
returning = "" if db.type == SQLITE else "RETURNING ID"
method = db.execute if db.type == SQLITE else db.fetchone
result = await (method)(
result = await db.execute(
f"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
@ -29,10 +28,10 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
currency,
fiat_base_multiplier
)
VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
{returning}
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
link_id,
wallet_id,
data.description,
data.min,
@ -47,17 +46,13 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
data.fiat_base_multiplier,
),
)
if db.type == SQLITE:
link_id = result._result_proxy.lastrowid
else:
link_id = result[0]
link = await get_pay_link(link_id)
assert link, "Newly created link couldn't be retrieved"
return link
async def get_pay_link(link_id: int) -> Optional[PayLink]:
async def get_pay_link(link_id: str) -> Optional[PayLink]:
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None

View File

@ -68,3 +68,76 @@ async def m005_webhook_headers_and_body(db):
"""
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;")
async def m006_redux(db):
"""
Add UUID ID's to links and migrates existing data
"""
await db.execute("ALTER TABLE lnurlp.pay_links RENAME TO pay_links_old")
await db.execute(
f"""
CREATE TABLE lnurlp.pay_links (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
description TEXT NOT NULL,
min INTEGER NOT NULL,
max INTEGER,
currency TEXT,
fiat_base_multiplier INTEGER DEFAULT 1,
served_meta INTEGER NOT NULL,
served_pr INTEGER NOT NULL,
webhook_url TEXT,
success_text TEXT,
success_url TEXT,
comment_chars INTEGER DEFAULT 0,
webhook_headers TEXT,
webhook_body TEXT
);
"""
)
for row in [
list(row) for row in await db.fetchall("SELECT * FROM lnurlp.pay_links_old")
]:
await db.execute(
"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
served_meta,
served_pr,
webhook_url,
success_text,
success_url,
currency,
comment_chars,
max,
fiat_base_multiplier,
webhook_headers,
webhook_body
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
row[7],
row[8],
row[9],
row[10],
row[11],
row[12],
row[13],
row[14],
),
)
await db.execute("DROP TABLE lnurlp.pay_links_old")

View File

@ -26,7 +26,7 @@ class CreatePayLinkData(BaseModel):
class PayLink(BaseModel):
id: int
id: str
wallet: str
description: str
min: float

View File

@ -22,7 +22,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment):
if not payment.extra or payment.extra.get("tag") != "lnurlp":
if payment.extra.get("tag") != "lnurlp":
return
if payment.extra.get("wh_status"):

View File

@ -23,9 +23,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "market":
return

View File

@ -18,8 +18,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "nostrnip5":
return

View File

@ -12,14 +12,13 @@ from . import db
from .helpers import fetch_onchain_balance
from .models import Charges, CreateCharge, SatsPayThemes
###############CHARGES##########################
async def create_charge(user: str, data: CreateCharge) -> Charges:
async def create_charge(user: str, data: CreateCharge) -> Optional[Charges]:
data = CreateCharge(**data.dict())
charge_id = urlsafe_short_hash()
if data.onchainwallet:
config = await get_config(user)
assert config
data.extra = json.dumps(
{"mempool_endpoint": config.mempool_endpoint, "network": config.network}
)
@ -92,7 +91,7 @@ async def update_charge(charge_id: str, **kwargs) -> Optional[Charges]:
return Charges.from_row(row) if row else None
async def get_charge(charge_id: str) -> Charges:
async def get_charge(charge_id: str) -> Optional[Charges]:
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
return Charges.from_row(row) if row else None
@ -111,6 +110,7 @@ async def delete_charge(charge_id: str) -> None:
async def check_address_balance(charge_id: str) -> Optional[Charges]:
charge = await get_charge(charge_id)
assert charge
if not charge.paid:
if charge.onchainaddress:
@ -131,7 +131,7 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]:
################## SETTINGS ###################
async def save_theme(data: SatsPayThemes, css_id: str = None):
async def save_theme(data: SatsPayThemes, css_id: Optional[str]):
# insert or update
if css_id:
await db.execute(
@ -162,7 +162,7 @@ async def save_theme(data: SatsPayThemes, css_id: str = None):
return await get_theme(css_id)
async def get_theme(css_id: str) -> SatsPayThemes:
async def get_theme(css_id: str) -> Optional[SatsPayThemes]:
row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,))
return SatsPayThemes.from_row(row) if row else None

View File

@ -32,6 +32,7 @@ def public_charge(charge: Charges):
async def call_webhook(charge: Charges):
async with httpx.AsyncClient() as client:
try:
assert charge.webhook
r = await client.post(
charge.webhook,
json=public_charge(charge),
@ -54,6 +55,8 @@ async def fetch_onchain_balance(charge: Charges):
if charge.config.network == "Testnet"
else charge.config.mempool_endpoint
)
assert endpoint
assert charge.onchainaddress
async with httpx.AsyncClient() as client:
r = await client.get(endpoint + "/api/address/" + charge.onchainaddress)
return r.json()["chain_stats"]["funded_txo_sum"]

View File

@ -22,10 +22,12 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") != "charge":
# not a charge invoice
return
assert payment.memo
charge = await get_charge(payment.memo)
if not charge:
logger.error("this should never happen", payment)
@ -33,6 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None:
await payment.set_pending(False)
charge = await check_address_balance(charge_id=charge.id)
assert charge
if charge.must_call_webhook():
resp = await call_webhook(charge)

View File

@ -1,10 +1,7 @@
from http import HTTPStatus
from fastapi import Response
from fastapi.param_functions import Depends
from fastapi import Depends, HTTPException, Request, Response
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse
from lnbits.core.models import User

View File

@ -1,9 +1,8 @@
import json
from http import HTTPStatus
from fastapi import Depends, Query
from fastapi import Depends, HTTPException, Query
from loguru import logger
from starlette.exceptions import HTTPException
from lnbits.decorators import (
WalletTypeInfo,
@ -29,8 +28,6 @@ from .crud import (
from .helpers import call_webhook, public_charge
from .models import CreateCharge, SatsPayThemes
#############################CHARGES##########################
@satspay_ext.post("/api/v1/charge")
async def api_charge_create(
@ -38,6 +35,7 @@ async def api_charge_create(
):
try:
charge = await create_charge(user=wallet.wallet.user, data=data)
assert charge
return {
**charge.dict(),
**{"time_elapsed": charge.time_elapsed},
@ -51,13 +49,15 @@ async def api_charge_create(
)
@satspay_ext.put("/api/v1/charge/{charge_id}")
@satspay_ext.put(
"/api/v1/charge/{charge_id}", dependencies=[Depends(require_admin_key)]
)
async def api_charge_update(
data: CreateCharge,
wallet: WalletTypeInfo = Depends(require_admin_key),
charge_id=None,
charge_id: str,
):
charge = await update_charge(charge_id=charge_id, data=data)
assert charge
return charge.dict()
@ -78,10 +78,8 @@ async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
return ""
@satspay_ext.get("/api/v1/charge/{charge_id}")
async def api_charge_retrieve(
charge_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
@satspay_ext.get("/api/v1/charge/{charge_id}", dependencies=[Depends(get_key_type)])
async def api_charge_retrieve(charge_id: str):
charge = await get_charge(charge_id)
if not charge:
@ -97,8 +95,8 @@ async def api_charge_retrieve(
}
@satspay_ext.delete("/api/v1/charge/{charge_id}")
async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_type)):
@satspay_ext.delete("/api/v1/charge/{charge_id}", dependencies=[Depends(get_key_type)])
async def api_charge_delete(charge_id: str):
charge = await get_charge(charge_id)
if not charge:
@ -155,7 +153,7 @@ async def api_themes_save(
theme = await save_theme(css_id=css_id, data=data)
else:
data.user = wallet.wallet.user
theme = await save_theme(data=data)
theme = await save_theme(data=data, css_id="no_id")
return theme
@ -169,8 +167,8 @@ async def api_themes_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
return ""
@satspay_ext.delete("/api/v1/themes/{theme_id}")
async def api_theme_delete(theme_id, wallet: WalletTypeInfo = Depends(get_key_type)):
@satspay_ext.delete("/api/v1/themes/{theme_id}", dependencies=[Depends(get_key_type)])
async def api_theme_delete(theme_id):
theme = await get_theme(theme_id)
if not theme:

View File

@ -27,7 +27,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment):
# (avoid loops)
if payment.extra and payment.extra.get("tag") == "scrubed":
if payment.extra.get("tag") == "scrubed":
# already scrubbed
return

View File

@ -20,7 +20,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra or payment.extra.get("tag") == "splitpayments":
if payment.extra.get("tag") == "splitpayments":
# already a splitted payment, ignore
return

View File

@ -20,7 +20,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra or payment.extra.get("tag") != "lnsubdomain":
if payment.extra.get("tag") != "lnsubdomain":
# not an lnurlp invoice
return

View File

@ -20,8 +20,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "tpos":
return

View File

@ -12,7 +12,7 @@ from .models import CreateWithdrawData, HashCheck, WithdrawLink
async def create_withdraw_link(
data: CreateWithdrawData, wallet_id: str
) -> WithdrawLink:
link_id = urlsafe_short_hash()
link_id = urlsafe_short_hash()[:6]
available_links = ",".join([str(i) for i in range(data.uses)])
await db.execute(
"""

View File

@ -91,10 +91,8 @@ files = "lnbits"
exclude = """(?x)(
^lnbits/extensions/bleskomat.
| ^lnbits/extensions/boltz.
| ^lnbits/extensions/boltcards.
| ^lnbits/extensions/lnaddress.
| ^lnbits/extensions/livestream.
| ^lnbits/extensions/lnurldevice.
| ^lnbits/extensions/satspay.
| ^lnbits/extensions/watchonly.
| ^lnbits/wallets/lnd_grpc_files.
)"""