Merge branch 'lnbits:main' into main

This commit is contained in:
Uthpala 2023-01-06 00:34:57 +01:00 committed by GitHub
commit 453d65a91d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 221 additions and 174 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,23 @@ 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:
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

@ -122,7 +122,7 @@ async def get_mempool_info(endPoint: str, gerty) -> dict:
),
)
return response.json()
if int(time.time()) - row.time > 20:
if float(time.time()) - row.time > 20:
async with httpx.AsyncClient() as client:
response = await client.get(gerty.mempool_endpoint + url)
await db.execute(

View File

@ -1,3 +1,6 @@
import time
async def m001_initial(db):
"""
Initial Gertys table.
@ -57,3 +60,46 @@ async def m005_add_gerty_model_col(db):
support for Gerty model col
"""
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN urls TEXT;")
async def m006_add_gerty_model_col(db):
"""
Add UUID ID's to links and migrates existing data
"""
await db.execute("ALTER TABLE gerty.mempool RENAME TO mempool_old")
await db.execute(
f"""
CREATE TABLE gerty.mempool (
id TEXT PRIMARY KEY,
mempool_endpoint TEXT NOT NULL,
endpoint TEXT NOT NULL,
data TEXT NOT NULL,
time FLOAT
);
"""
)
for row in [
list(row) for row in await db.fetchall("SELECT * FROM gerty.mempool_old")
]:
await db.execute(
"""
INSERT INTO gerty.mempool (
id,
mempool_endpoint,
endpoint,
data,
time
)
VALUES (?, ?, ?, ?, ?)
""",
(
row[0],
row[1],
row[2],
row[3],
time.time(),
),
)
await db.execute("DROP TABLE gerty.mempool_old")

View File

@ -6,7 +6,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
v-if="fun_exchange_market_rate || fun_satoshi_quotes"
>
<q-card
v-if="fun_exchange_market_rate"
v-if="fun_exchange_market_rate['unit'] != ''"
unelevated
class="q-pa-sm"
style="background: none !important"
@ -96,7 +96,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</p>
</q-card-section>
</q-card>
<q-card class="q-pa-sm" v-if="url_checker" unelevated class="q-pa-sm">
<q-card class="q-pa-sm" v-if="url_checker[0]" unelevated class="q-pa-sm">
<q-card-section>
<div class="text-h6">Servers to check</div>
</q-card-section>
@ -126,7 +126,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
v-else-if="item[1].value >= 300"
square
size="sm"
color="yellow"
color="orange"
text-color="white"
icon="sentiment_dissatisfied"
>

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

@ -120,7 +120,11 @@ async def create_lnurldevicepayment(
payhash: Optional[str] = None,
sats: Optional[int] = 0,
) -> lnurldevicepayment:
lnurldevicepayment_id = urlsafe_short_hash()
device = await get_lnurldevice(deviceid)
if device.device == "atm":
lnurldevicepayment_id = shortuuid.uuid(name=payload)
else:
lnurldevicepayment_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO lnurldevice.lnurldevicepayment (

View File

@ -5,12 +5,14 @@ from http import HTTPStatus
from io import BytesIO
from typing import Optional
import shortuuid
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
from loguru import logger
from starlette.exceptions import HTTPException
from lnbits import bolt11
from lnbits.core.services import create_invoice
from lnbits.core.views.api import pay_invoice
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
@ -102,11 +104,6 @@ async def lnurl_v1_params(
"status": "ERROR",
"reason": f"lnurldevice {device_id} not found on this server",
}
paymentcheck = await get_lnurlpayload(p)
if device.device == "atm":
if paymentcheck:
if paymentcheck.payhash != "payment_hash":
return {"status": "ERROR", "reason": f"Payment already claimed"}
if device.device == "switch":
price_msat = (
await fiat_amount_as_satoshis(float(profit), device.currency)
@ -163,13 +160,21 @@ async def lnurl_v1_params(
if device.device != "atm":
return {"status": "ERROR", "reason": "Not ATM device."}
price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000)
lnurldevicepayment = await create_lnurldevicepayment(
deviceid=device.id,
payload=p,
sats=price_msat * 1000,
pin=pin,
payhash="payment_hash",
)
lnurldevicepayment = await get_lnurldevicepayment(shortuuid.uuid(name=p))
if lnurldevicepayment:
logger.debug("lnurldevicepayment")
logger.debug(lnurldevicepayment)
logger.debug("lnurldevicepayment")
if lnurldevicepayment.payload == lnurldevicepayment.payhash:
return {"status": "ERROR", "reason": f"Payment already claimed"}
else:
lnurldevicepayment = await create_lnurldevicepayment(
deviceid=device.id,
payload=p,
sats=price_msat * 1000,
pin=pin,
payhash="payment_hash",
)
if not lnurldevicepayment:
return {"status": "ERROR", "reason": "Could not create payment."}
return {
@ -222,15 +227,20 @@ async def lnurl_callback(
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
)
if device.device == "atm":
if lnurldevicepayment.payload == lnurldevicepayment.payhash:
return {"status": "ERROR", "reason": f"Payment already claimed"}
if not pr:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="No payment request"
)
invoice = bolt11.decode(pr)
if not invoice.payment_hash:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request"
)
else:
if lnurldevicepayment.payload != k1:
return {"status": "ERROR", "reason": "Bad K1"}
if lnurldevicepayment.payhash != "payment_hash":
return {"status": "ERROR", "reason": f"Payment already claimed"}
lnurldevicepayment = await update_lnurldevicepayment(
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
)

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

@ -25,15 +25,20 @@ async def get_charge_details(service_id):
These might be different depending for services implemented in the future.
"""
details = {"time": 1440}
service = await get_service(service_id)
assert service
wallet_id = service.wallet
wallet = await get_wallet(wallet_id)
assert wallet
user = wallet.user
details["user"] = user
details["lnbitswallet"] = wallet_id
details["onchainwallet"] = service.onchain
return details
return {
"time": 1440,
"user": user,
"lnbitswallet": wallet_id,
"onchainwallet": service.onchain,
}
async def create_donation(
@ -71,7 +76,7 @@ async def create_donation(
return donation
async def post_donation(donation_id: str) -> tuple:
async def post_donation(donation_id: str) -> dict:
"""Post donations to their respective third party APIs
If the donation has already been posted, it will not be posted again.
@ -97,7 +102,6 @@ async def post_donation(donation_id: str) -> tuple:
}
async with httpx.AsyncClient() as client:
response = await client.post(url, data=data)
status = [s for s in list(HTTPStatus) if s == response.status_code][0]
elif service.servicename == "StreamElements":
return {"message": "StreamElements not yet supported!"}
else:

View File

@ -1,8 +1,8 @@
from sqlite3 import Row
from typing import Optional
from fastapi.params import Query
from pydantic.main import BaseModel
from fastapi import Query
from pydantic import BaseModel
class CreateService(BaseModel):

View File

@ -1,6 +1,6 @@
from http import HTTPStatus
from fastapi.param_functions import Depends
from fastapi import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.requests import Request

View File

@ -1,6 +1,6 @@
from http import HTTPStatus
from fastapi.params import Depends, Query
from fastapi import Depends, Query
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import RedirectResponse
@ -84,6 +84,8 @@ async def api_authenticate_service(
"""
service = await get_service(service_id)
assert service
if service.state != state:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="State doesn't match!"
@ -113,6 +115,7 @@ async def api_create_donation(data: CreateDonation, request: Request):
webhook_base = request.url.scheme + "://" + request.headers["Host"]
service_id = data.service
service = await get_service(service_id)
assert service
charge_details = await get_charge_details(service.id)
name = data.name if data.name else "Anonymous"
@ -157,7 +160,8 @@ async def api_post_donation(request: Request, data: ValidateDonation):
@streamalerts_ext.get("/api/v1/services")
async def api_get_services(g: WalletTypeInfo = Depends(get_key_type)):
"""Return list of all services assigned to wallet with given invoice key"""
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 []
services = []
for wallet_id in wallet_ids:
new_services = await get_services(wallet_id)
@ -170,7 +174,8 @@ async def api_get_donations(g: WalletTypeInfo = Depends(get_key_type)):
"""Return list of all donations assigned to wallet with given invoice
key
"""
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 []
donations = []
for wallet_id in wallet_ids:
new_donations = await get_donations(wallet_id)

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

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