mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-03-13 19:37:42 +01:00
feat: store onchain charge config with the charge
This commit is contained in:
parent
bb16870c66
commit
871d716539
9 changed files with 73 additions and 47 deletions
|
@ -1,6 +1,8 @@
|
||||||
|
import json
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.core.views.api import api_payment
|
from lnbits.core.views.api import api_payment
|
||||||
|
@ -18,6 +20,10 @@ from .models import Charges, CreateCharge
|
||||||
async def create_charge(user: str, data: CreateCharge) -> Charges:
|
async def create_charge(user: str, data: CreateCharge) -> Charges:
|
||||||
charge_id = urlsafe_short_hash()
|
charge_id = urlsafe_short_hash()
|
||||||
if data.onchainwallet:
|
if data.onchainwallet:
|
||||||
|
config = await get_config(user)
|
||||||
|
data.extra = json.dumps(
|
||||||
|
{"mempool_endpoint": config.mempool_endpoint, "network": config.network}
|
||||||
|
)
|
||||||
onchain = await get_fresh_address(data.onchainwallet)
|
onchain = await get_fresh_address(data.onchainwallet)
|
||||||
onchainaddress = onchain.address
|
onchainaddress = onchain.address
|
||||||
else:
|
else:
|
||||||
|
@ -48,9 +54,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
|
||||||
completelinktext,
|
completelinktext,
|
||||||
time,
|
time,
|
||||||
amount,
|
amount,
|
||||||
balance
|
balance,
|
||||||
|
extra
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
charge_id,
|
charge_id,
|
||||||
|
@ -67,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
|
||||||
data.time,
|
data.time,
|
||||||
data.amount,
|
data.amount,
|
||||||
0,
|
0,
|
||||||
|
data.extra,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return await get_charge(charge_id)
|
return await get_charge(charge_id)
|
||||||
|
@ -100,31 +108,27 @@ async def delete_charge(charge_id: str) -> None:
|
||||||
|
|
||||||
async def check_address_balance(charge_id: str) -> Optional[Charges]:
|
async def check_address_balance(charge_id: str) -> Optional[Charges]:
|
||||||
charge = await get_charge(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
|
|
||||||
if not charge.paid:
|
if not charge.paid:
|
||||||
if charge.onchainaddress:
|
if charge.onchainaddress:
|
||||||
config = await get_charge_config(charge_id)
|
endpoint = (
|
||||||
|
f"{charge.config['mempool_endpoint']}/testnet"
|
||||||
|
if charge.config["network"] == "Testnet"
|
||||||
|
else charge.config["mempool_endpoint"]
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
config.mempool_endpoint
|
endpoint + "/api/address/" + charge.onchainaddress
|
||||||
+ "/api/address/"
|
|
||||||
+ charge.onchainaddress
|
|
||||||
)
|
)
|
||||||
respAmount = r.json()["chain_stats"]["funded_txo_sum"]
|
respAmount = r.json()["chain_stats"]["funded_txo_sum"]
|
||||||
if respAmount > charge.balance:
|
if respAmount > charge.balance:
|
||||||
await update_charge(charge_id=charge_id, balance=respAmount)
|
await update_charge(charge_id=charge_id, balance=respAmount)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass
|
logger.warning(e)
|
||||||
if charge.lnbitswallet:
|
if charge.lnbitswallet:
|
||||||
invoice_status = await api_payment(charge.payment_hash)
|
invoice_status = await api_payment(charge.payment_hash)
|
||||||
|
|
||||||
if invoice_status["paid"]:
|
if invoice_status["paid"]:
|
||||||
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
||||||
return await get_charge(charge_id)
|
return await get_charge(charge_id)
|
||||||
|
|
||||||
|
|
||||||
async def get_charge_config(charge_id: str):
|
|
||||||
row = await db.fetchone(
|
|
||||||
"""SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,)
|
|
||||||
)
|
|
||||||
return await get_config(row.user)
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from .models import Charges
|
from .models import Charges
|
||||||
|
|
||||||
|
|
||||||
def compact_charge(charge: Charges):
|
def public_charge(charge: Charges):
|
||||||
return {
|
c = {
|
||||||
"id": charge.id,
|
"id": charge.id,
|
||||||
"description": charge.description,
|
"description": charge.description,
|
||||||
"onchainaddress": charge.onchainaddress,
|
"onchainaddress": charge.onchainaddress,
|
||||||
|
@ -13,5 +13,12 @@ def compact_charge(charge: Charges):
|
||||||
"balance": charge.balance,
|
"balance": charge.balance,
|
||||||
"paid": charge.paid,
|
"paid": charge.paid,
|
||||||
"timestamp": charge.timestamp,
|
"timestamp": charge.timestamp,
|
||||||
"completelink": charge.completelink, # should be secret?
|
"time_elapsed": charge.time_elapsed,
|
||||||
|
"time_left": charge.time_left,
|
||||||
|
"paid": charge.paid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if charge.paid:
|
||||||
|
c["completelink"] = charge.completelink
|
||||||
|
|
||||||
|
return c
|
||||||
|
|
|
@ -26,3 +26,14 @@ async def m001_initial(db):
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m002_add_charge_extra_data(db):
|
||||||
|
"""
|
||||||
|
Add 'exta' for storing various config about the charge
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
"""ALTER TABLE satspay.charges
|
||||||
|
ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}';
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -15,6 +16,7 @@ class CreateCharge(BaseModel):
|
||||||
completelinktext: str = Query(None)
|
completelinktext: str = Query(None)
|
||||||
time: int = Query(..., ge=1)
|
time: int = Query(..., ge=1)
|
||||||
amount: int = Query(..., ge=1)
|
amount: int = Query(..., ge=1)
|
||||||
|
extra: str = "{}"
|
||||||
|
|
||||||
|
|
||||||
class Charges(BaseModel):
|
class Charges(BaseModel):
|
||||||
|
@ -28,6 +30,7 @@ class Charges(BaseModel):
|
||||||
webhook: Optional[str]
|
webhook: Optional[str]
|
||||||
completelink: Optional[str]
|
completelink: Optional[str]
|
||||||
completelinktext: Optional[str] = "Back to Merchant"
|
completelinktext: Optional[str] = "Back to Merchant"
|
||||||
|
extra: str = "{}"
|
||||||
time: int
|
time: int
|
||||||
amount: int
|
amount: int
|
||||||
balance: int
|
balance: int
|
||||||
|
@ -54,3 +57,7 @@ class Charges(BaseModel):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self):
|
||||||
|
return json.loads(self.extra)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge
|
||||||
from lnbits.helpers import get_current_extension_name
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .helpers import compact_charge
|
from .helpers import public_charge
|
||||||
from .models import Charges
|
from .models import Charges
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ async def call_webhook(charge: Charges):
|
||||||
try:
|
try:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
charge.webhook,
|
charge.webhook,
|
||||||
json=compact_charge(charge),
|
json=public_charge(charge),
|
||||||
timeout=40,
|
timeout=40,
|
||||||
)
|
)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
disable
|
disable
|
||||||
v-if="!charge.lnbitswallet || charge.time_elapsed"
|
v-if="!charge.payment_request || charge.time_elapsed"
|
||||||
style="color: primary; width: 100%"
|
style="color: primary; width: 100%"
|
||||||
label="lightning⚡"
|
label="lightning⚡"
|
||||||
>
|
>
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
disable
|
disable
|
||||||
v-if="!charge.onchainwallet || charge.time_elapsed"
|
v-if="!charge.onchainaddress || charge.time_elapsed"
|
||||||
style="color: primary; width: 100%"
|
style="color: primary; width: 100%"
|
||||||
label="onchain⛓️"
|
label="onchain⛓️"
|
||||||
>
|
>
|
||||||
|
@ -222,7 +222,7 @@
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a
|
<a
|
||||||
style="color: unset"
|
style="color: unset"
|
||||||
:href="mempoolEndpoint + '/address/' + charge.onchainaddress"
|
:href="'https://' + mempoolHostname + '/address/' + charge.onchainaddress"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
><span
|
><span
|
||||||
class="text-subtitle1"
|
class="text-subtitle1"
|
||||||
|
@ -336,7 +336,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkBalances: async function () {
|
checkBalances: async function () {
|
||||||
if (!this.charge.lnbitswallet && this.charge.hasOnchainStaleBalance)
|
if (!this.charge.payment_request && this.charge.hasOnchainStaleBalance)
|
||||||
return
|
return
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
|
@ -438,7 +438,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
if (this.charge.lnbitswallet) this.payInvoice()
|
if (this.charge.payment_request) this.payInvoice()
|
||||||
else this.payOnchain()
|
else this.payOnchain()
|
||||||
|
|
||||||
await this.checkBalances()
|
await this.checkBalances()
|
||||||
|
|
|
@ -412,7 +412,8 @@
|
||||||
onchainwallet: null,
|
onchainwallet: null,
|
||||||
rescanning: false,
|
rescanning: false,
|
||||||
mempool: {
|
mempool: {
|
||||||
endpoint: ''
|
endpoint: '',
|
||||||
|
network: 'Mainnet'
|
||||||
},
|
},
|
||||||
|
|
||||||
chargesTable: {
|
chargesTable: {
|
||||||
|
@ -519,7 +520,7 @@
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/watchonly/api/v1/wallet',
|
`/watchonly/api/v1/wallet?network=${this.mempool.network}`,
|
||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
this.walletLinks = data.map(w => ({
|
this.walletLinks = data.map(w => ({
|
||||||
|
@ -538,7 +539,9 @@
|
||||||
'/watchonly/api/v1/config',
|
'/watchonly/api/v1/config',
|
||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
|
console.log('### data', data)
|
||||||
this.mempool.endpoint = data.mempool_endpoint
|
this.mempool.endpoint = data.mempool_endpoint
|
||||||
|
this.mempool.network = data.network || 'Mainnet'
|
||||||
const url = new URL(this.mempool.endpoint)
|
const url = new URL(this.mempool.endpoint)
|
||||||
this.mempool.hostname = url.hostname
|
this.mempool.hostname = url.hostname
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -697,8 +700,8 @@
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
await this.getCharges()
|
await this.getCharges()
|
||||||
await this.getWalletLinks()
|
|
||||||
await this.getWalletConfig()
|
await this.getWalletConfig()
|
||||||
|
await this.getWalletLinks()
|
||||||
setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000)
|
setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000)
|
||||||
await this.rescanOnchainAddresses()
|
await this.rescanOnchainAddresses()
|
||||||
setInterval(() => this.rescanOnchainAddresses(), 10 * 1000)
|
setInterval(() => this.rescanOnchainAddresses(), 10 * 1000)
|
||||||
|
|
|
@ -6,12 +6,12 @@ from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
from lnbits.core.crud import get_wallet
|
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
|
from lnbits.extensions.satspay.helpers import public_charge
|
||||||
|
|
||||||
from . import satspay_ext, satspay_renderer
|
from . import satspay_ext, satspay_renderer
|
||||||
from .crud import get_charge, get_charge_config
|
from .crud import get_charge
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
@ -31,16 +31,15 @@ async def display(request: Request, charge_id: str):
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
onchainwallet_config = await get_charge_config(charge_id)
|
view_data = {
|
||||||
if onchainwallet_config:
|
"request": request,
|
||||||
mempool_endpoint = onchainwallet_config.mempool_endpoint
|
"charge_data": public_charge(charge),
|
||||||
network = onchainwallet_config.network
|
}
|
||||||
|
if "mempool_endpoint" in charge.config:
|
||||||
|
view_data["mempool_endpoint"] = charge.config["mempool_endpoint"]
|
||||||
|
view_data["network"] = charge.config["network"]
|
||||||
|
|
||||||
return satspay_renderer().TemplateResponse(
|
return satspay_renderer().TemplateResponse(
|
||||||
"satspay/display.html",
|
"satspay/display.html",
|
||||||
{
|
view_data,
|
||||||
"request": request,
|
|
||||||
"charge_data": charge.dict(),
|
|
||||||
"mempool_endpoint": mempool_endpoint,
|
|
||||||
"network": network,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,7 +19,7 @@ from .crud import (
|
||||||
get_charges,
|
get_charges,
|
||||||
update_charge,
|
update_charge,
|
||||||
)
|
)
|
||||||
from .helpers import compact_charge
|
from .helpers import public_charge
|
||||||
from .models import CreateCharge
|
from .models import CreateCharge
|
||||||
|
|
||||||
#############################CHARGES##########################
|
#############################CHARGES##########################
|
||||||
|
@ -118,9 +118,4 @@ async def api_charge_balance(charge_id):
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {**public_charge(charge)}
|
||||||
**compact_charge(charge),
|
|
||||||
**{"time_elapsed": charge.time_elapsed},
|
|
||||||
**{"time_left": charge.time_left},
|
|
||||||
**{"paid": charge.paid},
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue