mirror of
https://github.com/lnbits/lnbits.git
synced 2025-11-23 20:38:13 +01:00
Merge pull request #728 from motorina0/ext_satspay_onchain_fix
`satspay` extension improvements
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
@@ -11,6 +12,14 @@ db = Database("ext_satspay")
|
|||||||
|
|
||||||
satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"])
|
satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"])
|
||||||
|
|
||||||
|
satspay_static_files = [
|
||||||
|
{
|
||||||
|
"path": "/satspay/static",
|
||||||
|
"app": StaticFiles(directory="lnbits/extensions/satspay/static"),
|
||||||
|
"name": "satspay_static",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def satspay_renderer():
|
def satspay_renderer():
|
||||||
return template_renderer(["lnbits/extensions/satspay/templates"])
|
return template_renderer(["lnbits/extensions/satspay/templates"])
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from lnbits.core.services import create_invoice
|
|||||||
from lnbits.core.views.api import api_payment
|
from lnbits.core.views.api import api_payment
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
from ..watchonly.crud import get_fresh_address, get_mempool, get_watch_wallet
|
from ..watchonly.crud import get_config, get_fresh_address
|
||||||
|
|
||||||
# from lnbits.db import open_ext_db
|
# from lnbits.db import open_ext_db
|
||||||
from . import db
|
from . import db
|
||||||
@@ -18,7 +18,6 @@ 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:
|
||||||
wallet = await get_watch_wallet(data.onchainwallet)
|
|
||||||
onchain = await get_fresh_address(data.onchainwallet)
|
onchain = await get_fresh_address(data.onchainwallet)
|
||||||
onchainaddress = onchain.address
|
onchainaddress = onchain.address
|
||||||
else:
|
else:
|
||||||
@@ -89,7 +88,8 @@ async def get_charge(charge_id: str) -> Charges:
|
|||||||
|
|
||||||
async def get_charges(user: str) -> List[Charges]:
|
async def get_charges(user: str) -> List[Charges]:
|
||||||
rows = await db.fetchall(
|
rows = await db.fetchall(
|
||||||
"""SELECT * FROM satspay.charges WHERE "user" = ?""", (user,)
|
"""SELECT * FROM satspay.charges WHERE "user" = ? ORDER BY "timestamp" DESC """,
|
||||||
|
(user,),
|
||||||
)
|
)
|
||||||
return [Charges.from_row(row) for row in rows]
|
return [Charges.from_row(row) for row in rows]
|
||||||
|
|
||||||
@@ -102,14 +102,16 @@ async def check_address_balance(charge_id: str) -> List[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:
|
||||||
mempool = await get_mempool(charge.user)
|
config = await get_config(charge.user)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
mempool.endpoint + "/api/address/" + charge.onchainaddress
|
config.mempool_endpoint
|
||||||
|
+ "/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:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import time
|
from datetime import datetime, timedelta
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -38,12 +38,16 @@ class Charges(BaseModel):
|
|||||||
def from_row(cls, row: Row) -> "Charges":
|
def from_row(cls, row: Row) -> "Charges":
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_left(self):
|
||||||
|
now = datetime.utcnow().timestamp()
|
||||||
|
start = datetime.fromtimestamp(self.timestamp)
|
||||||
|
expiration = (start + timedelta(minutes=self.time)).timestamp()
|
||||||
|
return (expiration - now) / 60
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_elapsed(self):
|
def time_elapsed(self):
|
||||||
if (self.timestamp + (self.time * 60)) >= time.time():
|
return self.time_left < 0
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def paid(self):
|
def paid(self):
|
||||||
|
|||||||
31
lnbits/extensions/satspay/static/js/utils.js
Normal file
31
lnbits/extensions/satspay/static/js/utils.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||||
|
const retryWithDelay = async function (fn, retryCount = 0) {
|
||||||
|
try {
|
||||||
|
await sleep(25)
|
||||||
|
// Do not return the call directly, use result.
|
||||||
|
// Otherwise the error will not be cought in this try-catch block.
|
||||||
|
const result = await fn()
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
if (retryCount > 100) throw err
|
||||||
|
await sleep((retryCount + 1) * 1000)
|
||||||
|
return retryWithDelay(fn, retryCount + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapCharge = (obj, oldObj = {}) => {
|
||||||
|
const charge = _.clone(obj)
|
||||||
|
|
||||||
|
charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time
|
||||||
|
charge.time = minutesToTime(obj.time)
|
||||||
|
charge.timeLeft = minutesToTime(obj.time_left)
|
||||||
|
|
||||||
|
charge.expanded = false
|
||||||
|
charge.displayUrl = ['/satspay/', obj.id].join('')
|
||||||
|
charge.expanded = oldObj.expanded
|
||||||
|
charge.pendingBalance = oldObj.pendingBalance || 0
|
||||||
|
return charge
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutesToTime = min =>
|
||||||
|
min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : ''
|
||||||
@@ -8,172 +8,10 @@
|
|||||||
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<a target="_blank" href="/docs#/satspay" class="text-white"
|
||||||
|
>Swagger REST API Documentation</a
|
||||||
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-expansion-item
|
|
||||||
group="extras"
|
|
||||||
icon="swap_vertical_circle"
|
|
||||||
label="API info"
|
|
||||||
:content-inset-level="0.5"
|
|
||||||
>
|
|
||||||
<q-btn flat label="Swagger API" type="a" href="../docs#/satspay"></q-btn>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Create charge">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">POST</span> /satspay/api/v1/charge</code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<charge_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X POST {{ request.base_url }}satspay/api/v1/charge -d
|
|
||||||
'{"onchainwallet": <string, watchonly_wallet_id>,
|
|
||||||
"description": <string>, "webhook":<string>, "time":
|
|
||||||
<integer>, "amount": <integer>, "lnbitswallet":
|
|
||||||
<string, lnbits_wallet_id>}' -H "Content-type:
|
|
||||||
application/json" -H "X-Api-Key: {{user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Update charge">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">PUT</span>
|
|
||||||
/satspay/api/v1/charge/<charge_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<charge_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X POST {{ request.base_url
|
|
||||||
}}satspay/api/v1/charge/<charge_id> -d '{"onchainwallet":
|
|
||||||
<string, watchonly_wallet_id>, "description": <string>,
|
|
||||||
"webhook":<string>, "time": <integer>, "amount":
|
|
||||||
<integer>, "lnbitswallet": <string, lnbits_wallet_id>}'
|
|
||||||
-H "Content-type: application/json" -H "X-Api-Key:
|
|
||||||
{{user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Get charge">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span>
|
|
||||||
/satspay/api/v1/charge/<charge_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<charge_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url
|
|
||||||
}}satspay/api/v1/charge/<charge_id> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].inkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Get charges">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span> /satspay/api/v1/charges</code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<charge_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url }}satspay/api/v1/charges -H
|
|
||||||
"X-Api-Key: {{ user.wallets[0].inkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Delete a pay link"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-pink">DELETE</span>
|
|
||||||
/satspay/api/v1/charge/<charge_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
|
|
||||||
<code></code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X DELETE {{ request.base_url
|
|
||||||
}}satspay/api/v1/charge/<charge_id> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Get balances"
|
|
||||||
class="q-pb-md"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span>
|
|
||||||
/satspay/api/v1/charges/balance/<charge_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<charge_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url
|
|
||||||
}}satspay/api/v1/charges/balance/<charge_id> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].inkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|||||||
@@ -1,223 +1,299 @@
|
|||||||
{% extends "public.html" %} {% block page %}
|
{% extends "public.html" %} {% block page %}
|
||||||
<div class="q-pa-sm theCard">
|
<div class="row items-center q-mt-md">
|
||||||
<q-card class="my-card">
|
<div class="col-lg-4 col-md-3 col-sm-1"></div>
|
||||||
<div class="column">
|
<div class="col-lg-4 col-md-6 col-sm-10">
|
||||||
<center>
|
<q-card>
|
||||||
<div class="col theHeading">{{ charge.description }}</div>
|
<div class="row q-mb-md">
|
||||||
</center>
|
<div class="col text-center q-mt-md">
|
||||||
<div class="col">
|
<span class="text-h4" v-text="charge.description"></span>
|
||||||
<div
|
|
||||||
class="col"
|
|
||||||
color="white"
|
|
||||||
style="background-color: grey; height: 30px; padding: 5px"
|
|
||||||
v-if="timetoComplete < 1"
|
|
||||||
>
|
|
||||||
<center>Time elapsed</center>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col"
|
|
||||||
color="white"
|
|
||||||
style="background-color: grey; height: 30px; padding: 5px"
|
|
||||||
v-else-if="charge_paid == 'True'"
|
|
||||||
>
|
|
||||||
<center>Charge paid</center>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<q-linear-progress size="30px" :value="newProgress" color="grey">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item style="padding: 3px">
|
|
||||||
<q-spinner color="white" size="0.8em"></q-spinner
|
|
||||||
><span style="font-size: 15px; color: white"
|
|
||||||
><span class="q-pr-xl q-pl-md"> Awaiting payment...</span>
|
|
||||||
<span class="q-pl-xl" style="color: white">
|
|
||||||
{% raw %} {{ newTimeLeft }} {% endraw %}</span
|
|
||||||
></span
|
|
||||||
>
|
|
||||||
</q-item>
|
|
||||||
</q-item-section>
|
|
||||||
</q-linear-progress>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" style="margin: 2px 15px; max-height: 100px">
|
<div class="row">
|
||||||
<center>
|
<div class="col text-center">
|
||||||
<q-btn flat dense outline @click="copyText('{{ charge.id }}')"
|
<div
|
||||||
>Charge ID: {{ charge.id }}</q-btn
|
color="white"
|
||||||
|
style="background-color: grey; height: 30px; padding: 5px"
|
||||||
|
v-if="!charge.timeLeft"
|
||||||
>
|
>
|
||||||
</center>
|
Time elapsed
|
||||||
<span
|
|
||||||
><small
|
|
||||||
>{% raw %} Total to pay: {{ charge_amount }}sats<br />
|
|
||||||
Amount paid: {{ charge_balance }}</small
|
|
||||||
><br />
|
|
||||||
Amount due: {{ charge_amount - charge_balance }}sats {% endraw %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<q-separator></q-separator>
|
|
||||||
<div class="col">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
disable
|
|
||||||
v-if="'{{ charge.lnbitswallet }}' == 'None' || charge_time_elapsed == 'True'"
|
|
||||||
style="color: primary; width: 100%"
|
|
||||||
label="lightning⚡"
|
|
||||||
>
|
|
||||||
<q-tooltip>
|
|
||||||
bitcoin lightning payment method not available
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
v-else
|
|
||||||
@click="payLN"
|
|
||||||
style="color: primary; width: 100%"
|
|
||||||
label="lightning⚡"
|
|
||||||
>
|
|
||||||
<q-tooltip> pay with lightning </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div
|
||||||
<q-btn
|
color="white"
|
||||||
flat
|
style="background-color: grey; height: 30px; padding: 5px"
|
||||||
disable
|
v-else-if="charge.paid"
|
||||||
v-if="'{{ charge.onchainwallet }}' == 'None' || charge_time_elapsed == 'True'"
|
>
|
||||||
style="color: primary; width: 100%"
|
Charge paid
|
||||||
label="onchain⛓️"
|
|
||||||
>
|
|
||||||
<q-tooltip>
|
|
||||||
bitcoin onchain payment method not available
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
v-else
|
|
||||||
@click="payON"
|
|
||||||
style="color: primary; width: 100%"
|
|
||||||
label="onchain⛓️"
|
|
||||||
>
|
|
||||||
<q-tooltip> pay onchain </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-separator></q-separator>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-card class="q-pa-lg" v-if="lnbtc">
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<div class="text-center q-pt-md">
|
|
||||||
<div v-if="timetoComplete < 1 && charge_paid == 'False'">
|
|
||||||
<q-icon
|
|
||||||
name="block"
|
|
||||||
style="color: #ccc; font-size: 21.4em"
|
|
||||||
></q-icon>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="charge_paid == 'True'">
|
|
||||||
<q-icon
|
|
||||||
name="check"
|
|
||||||
style="color: green; font-size: 21.4em"
|
|
||||||
></q-icon>
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
v-if="'{{ charge.webhook }}' != 'None'"
|
|
||||||
type="a"
|
|
||||||
href="{{ charge.completelink }}"
|
|
||||||
label="{{ charge.completelinktext }}"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<center>
|
<q-linear-progress
|
||||||
<span class="text-subtitle2"
|
size="30px"
|
||||||
>Pay this <br />
|
:value="charge.progress"
|
||||||
lightning-network invoice</span
|
color="secondary"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item style="padding: 3px">
|
||||||
|
<q-spinner color="white" size="0.8em"></q-spinner
|
||||||
|
><span style="font-size: 15px; color: white"
|
||||||
|
><span class="q-pr-xl q-pl-md"> Awaiting payment...</span>
|
||||||
|
<span class="q-pl-xl" style="color: white">
|
||||||
|
{% raw %} {{ charge.timeLeft }} {% endraw %}</span
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</q-item>
|
||||||
|
</q-item-section>
|
||||||
|
</q-linear-progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-ml-md q-mt-md q-mb-lg">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 q-pr-lg">Charge Id:</div>
|
||||||
|
<div class="col-8 q-pr-lg">
|
||||||
|
<q-btn flat dense outline @click="copyText(charge.id)"
|
||||||
|
><span v-text="charge.id"></span
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center">
|
||||||
|
<div class="col-4 q-pr-lg">Total to pay:</div>
|
||||||
|
<div class="col-8 q-pr-lg">
|
||||||
|
<q-badge color="blue">
|
||||||
|
<span v-text="charge.amount" class="text-subtitle2"></span> sat
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center q-mt-sm">
|
||||||
|
<div class="col-4 q-pr-lg">Amount paid:</div>
|
||||||
|
<div class="col-8 q-pr-lg">
|
||||||
|
<q-badge color="orange">
|
||||||
|
<span v-text="charge.balance" class="text-subtitle2"></span>
|
||||||
|
sat</q-badge
|
||||||
>
|
>
|
||||||
</center>
|
</div>
|
||||||
<a href="lightning:{{ charge.payment_request }}">
|
</div>
|
||||||
<q-responsive :ratio="1" class="q-mx-md">
|
<div v-if="pendingFunds" class="row items-center q-mt-sm">
|
||||||
<qrcode
|
<div class="col-4 q-pr-lg">Amount pending:</div>
|
||||||
:value="'{{ charge.payment_request }}'"
|
<div class="col-8 q-pr-lg">
|
||||||
:options="{width: 800}"
|
<q-badge color="gray">
|
||||||
class="rounded-borders"
|
<span v-text="pendingFunds" class="text-subtitle2"></span> sat
|
||||||
></qrcode>
|
</q-badge>
|
||||||
</q-responsive>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row items-center q-mt-sm">
|
||||||
<q-btn
|
<div class="col-4 q-pr-lg">Amount due:</div>
|
||||||
outline
|
<div class="col-8 q-pr-lg">
|
||||||
color="grey"
|
<q-badge v-if="charge.amount - charge.balance > 0" color="green">
|
||||||
@click="copyText('{{ charge.payment_request }}')"
|
<span
|
||||||
>Copy invoice</q-btn
|
v-text="charge.amount - charge.balance"
|
||||||
|
class="text-subtitle2"
|
||||||
|
></span>
|
||||||
|
sat
|
||||||
|
</q-badge>
|
||||||
|
<q-badge
|
||||||
|
v-else="charge.amount - charge.balance <= 0"
|
||||||
|
color="green"
|
||||||
|
class="text-subtitle2"
|
||||||
|
>
|
||||||
|
none</q-badge
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
disable
|
||||||
|
v-if="!charge.lnbitswallet || charge.time_elapsed"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="lightning⚡"
|
||||||
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
bitcoin lightning payment method not available
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
v-else
|
||||||
|
@click="payInvoice"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="lightning⚡"
|
||||||
|
>
|
||||||
|
<q-tooltip> pay with lightning </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
disable
|
||||||
|
v-if="!charge.onchainwallet || charge.time_elapsed"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="onchain⛓️"
|
||||||
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
bitcoin onchain payment method not available
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
v-else
|
||||||
|
@click="payOnchain"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="onchain⛓️"
|
||||||
|
>
|
||||||
|
<q-tooltip> pay onchain </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
<q-card class="q-pa-lg" v-if="lnbtc">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<div class="row items-center q-mt-sm">
|
||||||
|
<div class="col-md-2 col-sm-0"></div>
|
||||||
|
<div class="col-md-8 col-sm-12">
|
||||||
|
<div v-if="!charge.timeLeft && !charge.paid">
|
||||||
|
<q-icon
|
||||||
|
name="block"
|
||||||
|
style="color: #ccc; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="charge.paid">
|
||||||
|
<q-icon
|
||||||
|
name="check"
|
||||||
|
style="color: green; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
v-if="charge.webhook"
|
||||||
|
type="a"
|
||||||
|
:href="charge.completelink"
|
||||||
|
:label="charge.completelinktext"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="row text-center q-mb-sm">
|
||||||
|
<div class="col text-center">
|
||||||
|
<span class="text-subtitle2"
|
||||||
|
>Pay this lightning-network invoice:</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a :href="'lightning:'+charge.payment_request">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
:value="charge.payment_request"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</a>
|
||||||
|
<div class="row text-center q-mt-lg">
|
||||||
|
<div class="col text-center">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="copyText(charge.payment_request)"
|
||||||
|
>Copy invoice</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-sm-0"></div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-card class="q-pa-lg" v-if="onbtc">
|
<q-card class="q-pa-lg" v-if="onbtc">
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<div class="text-center q-pt-md">
|
<div v-if="charge.timeLeft && !charge.paid" class="row items-center">
|
||||||
<div v-if="timetoComplete < 1 && charge_paid == 'False'">
|
<div class="col text-center">
|
||||||
<q-icon
|
<a
|
||||||
name="block"
|
style="color: unset"
|
||||||
style="color: #ccc; font-size: 21.4em"
|
:href="mempool_endpoint + '/address/' + charge.onchainaddress"
|
||||||
></q-icon>
|
target="_blank"
|
||||||
</div>
|
><span
|
||||||
<div v-else-if="charge_paid == 'True'">
|
class="text-subtitle1"
|
||||||
<q-icon
|
v-text="charge.onchainaddress"
|
||||||
name="check"
|
></span>
|
||||||
style="color: green; font-size: 21.4em"
|
|
||||||
></q-icon>
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
v-if="'{{ charge.webhook }}' != None"
|
|
||||||
type="a"
|
|
||||||
href="{{ charge.completelink }}"
|
|
||||||
label="{{ charge.completelinktext }}"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<center>
|
|
||||||
<span class="text-subtitle2"
|
|
||||||
>Send {{ charge.amount }}sats<br />
|
|
||||||
to this onchain address</span
|
|
||||||
>
|
|
||||||
</center>
|
|
||||||
<a href="bitcoin:{{ charge.onchainaddress }}">
|
|
||||||
<q-responsive :ratio="1" class="q-mx-md">
|
|
||||||
<qrcode
|
|
||||||
:value="'{{ charge.onchainaddress }}'"
|
|
||||||
:options="{width: 800}"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
</a>
|
</a>
|
||||||
<div class="row q-mt-lg">
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center q-mt-md">
|
||||||
|
<div class="col-md-2 col-sm-0"></div>
|
||||||
|
<div class="col-md-8 col-sm-12 text-center">
|
||||||
|
<div v-if="!charge.timeLeft && !charge.paid">
|
||||||
|
<q-icon
|
||||||
|
name="block"
|
||||||
|
style="color: #ccc; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="charge.paid">
|
||||||
|
<q-icon
|
||||||
|
name="check"
|
||||||
|
style="color: green; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
color="grey"
|
v-if="charge.webhook"
|
||||||
@click="copyText('{{ charge.onchainaddress }}')"
|
type="a"
|
||||||
>Copy address</q-btn
|
:href="charge.completelink"
|
||||||
>
|
:label="charge.completelinktext"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="row items-center q-mb-sm">
|
||||||
|
<div class="col text-center">
|
||||||
|
<span class="text-subtitle2"
|
||||||
|
>Send
|
||||||
|
|
||||||
|
<span v-text="charge.amount"></span>
|
||||||
|
sats to this onchain address</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a :href="'bitcoin:'+charge.onchainaddress">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
:value="charge.onchainaddress"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</a>
|
||||||
|
<div class="row items-center q-mt-lg">
|
||||||
|
<div class="col text-center">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="copyText(charge.onchainaddress)"
|
||||||
|
>Copy address</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-2 col-sm-0"></div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-card>
|
</div>
|
||||||
|
<div class="col-lg- 4 col-md-3 col-sm-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
<style>
|
<script src="https://mempool.space/mempool.js"></script>
|
||||||
.theCard {
|
<script src="{{ url_for('satspay_static', path='js/utils.js') }}"></script>
|
||||||
width: 360px;
|
|
||||||
margin: 10px auto;
|
|
||||||
}
|
|
||||||
.theHeading {
|
|
||||||
margin: 15px;
|
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
@@ -226,16 +302,14 @@
|
|||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
charge: JSON.parse('{{charge_data | tojson}}'),
|
||||||
|
mempool_endpoint: '{{mempool_endpoint}}',
|
||||||
|
pendingFunds: 0,
|
||||||
|
ws: null,
|
||||||
newProgress: 0.4,
|
newProgress: 0.4,
|
||||||
counter: 1,
|
counter: 1,
|
||||||
newTimeLeft: '',
|
|
||||||
timetoComplete: 100,
|
|
||||||
lnbtc: true,
|
lnbtc: true,
|
||||||
onbtc: false,
|
onbtc: false,
|
||||||
charge_time_elapsed: '{{charge.time_elapsed}}',
|
|
||||||
charge_amount: '{{charge.amount}}',
|
|
||||||
charge_balance: '{{charge.balance}}',
|
|
||||||
charge_paid: '{{charge.paid}}',
|
|
||||||
wallet: {
|
wallet: {
|
||||||
inkey: ''
|
inkey: ''
|
||||||
},
|
},
|
||||||
@@ -245,90 +319,141 @@
|
|||||||
methods: {
|
methods: {
|
||||||
startPaymentNotifier() {
|
startPaymentNotifier() {
|
||||||
this.cancelListener()
|
this.cancelListener()
|
||||||
|
if (!this.lnbitswallet) return
|
||||||
this.cancelListener = LNbits.event.onInvoicePaid(
|
this.cancelListener = LNbits.events.onInvoicePaid(
|
||||||
this.wallet,
|
this.wallet,
|
||||||
payment => {
|
payment => {
|
||||||
this.checkBalance()
|
this.checkInvoiceBalance()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
checkBalance: function () {
|
checkBalances: async function () {
|
||||||
var self = this
|
if (!this.charge.hasStaleBalance) await this.refreshCharge()
|
||||||
LNbits.api
|
try {
|
||||||
.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/satspay/api/v1/charges/balance/{{ charge.id }}',
|
`/satspay/api/v1/charge/balance/${this.charge.id}`
|
||||||
'filla'
|
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
this.charge = mapCharge(data, this.charge)
|
||||||
self.charge_time_elapsed = response.data.time_elapsed
|
} catch (error) {
|
||||||
self.charge_amount = response.data.amount
|
LNbits.utils.notifyApiError(error)
|
||||||
self.charge_balance = response.data.balance
|
}
|
||||||
if (self.charge_balance >= self.charge_amount) {
|
|
||||||
self.charge_paid = 'True'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
payLN: function () {
|
refreshCharge: async function () {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
`/satspay/api/v1/charge/${this.charge.id}`
|
||||||
|
)
|
||||||
|
this.charge = mapCharge(data, this.charge)
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkPendingOnchain: async function () {
|
||||||
|
const {
|
||||||
|
bitcoin: {addresses: addressesAPI}
|
||||||
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const utxos = await addressesAPI.getAddressTxsUtxo({
|
||||||
|
address: this.charge.onchainaddress
|
||||||
|
})
|
||||||
|
const newBalance = utxos.reduce((t, u) => t + u.value, 0)
|
||||||
|
this.charge.hasStaleBalance = this.charge.balance === newBalance
|
||||||
|
|
||||||
|
this.pendingFunds = utxos
|
||||||
|
.filter(u => !u.status.confirmed)
|
||||||
|
.reduce((t, u) => t + u.value, 0)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('cannot check pending funds')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payInvoice: function () {
|
||||||
this.lnbtc = true
|
this.lnbtc = true
|
||||||
this.onbtc = false
|
this.onbtc = false
|
||||||
},
|
},
|
||||||
payON: function () {
|
payOnchain: function () {
|
||||||
this.lnbtc = false
|
this.lnbtc = false
|
||||||
this.onbtc = true
|
this.onbtc = true
|
||||||
},
|
},
|
||||||
getTheTime: function () {
|
|
||||||
var timeToComplete =
|
|
||||||
parseInt('{{ charge.time }}') * 60 -
|
|
||||||
(Date.now() / 1000 - parseInt('{{ charge.timestamp }}'))
|
|
||||||
this.timetoComplete = timeToComplete
|
|
||||||
var timeLeft = Quasar.utils.date.formatDate(
|
|
||||||
new Date((timeToComplete - 3600) * 1000),
|
|
||||||
'HH:mm:ss'
|
|
||||||
)
|
|
||||||
this.newTimeLeft = timeLeft
|
|
||||||
},
|
|
||||||
getThePercentage: function () {
|
|
||||||
var timeToComplete =
|
|
||||||
parseInt('{{ charge.time }}') * 60 -
|
|
||||||
(Date.now() / 1000 - parseInt('{{ charge.timestamp }}'))
|
|
||||||
this.newProgress =
|
|
||||||
1 - timeToComplete / (parseInt('{{ charge.time }}') * 60)
|
|
||||||
},
|
|
||||||
|
|
||||||
timerCount: function () {
|
loopRefresh: function () {
|
||||||
self = this
|
// invoice only
|
||||||
var refreshIntervalId = setInterval(function () {
|
const refreshIntervalId = setInterval(async () => {
|
||||||
if (self.charge_paid == 'True' || self.timetoComplete < 1) {
|
if (this.charge.paid || !this.charge.timeLeft) {
|
||||||
clearInterval(refreshIntervalId)
|
clearInterval(refreshIntervalId)
|
||||||
}
|
}
|
||||||
self.getTheTime()
|
if (this.counter % 10 === 0) {
|
||||||
self.getThePercentage()
|
await this.checkBalances()
|
||||||
self.counter++
|
await this.checkPendingOnchain()
|
||||||
if (self.counter % 10 === 0) {
|
|
||||||
self.checkBalance()
|
|
||||||
}
|
}
|
||||||
|
this.counter++
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
},
|
||||||
|
initWs: async function () {
|
||||||
|
const {
|
||||||
|
bitcoin: {websocket}
|
||||||
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws = new WebSocket('wss://mempool.space/api/v1/ws')
|
||||||
|
this.ws.addEventListener('open', x => {
|
||||||
|
if (this.charge.onchainaddress) {
|
||||||
|
this.trackAddress(this.charge.onchainaddress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws.addEventListener('message', async ({data}) => {
|
||||||
|
const res = JSON.parse(data.toString())
|
||||||
|
if (res['address-transactions']) {
|
||||||
|
await this.checkBalances()
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'New payment received!',
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loopPingWs: function () {
|
||||||
|
setInterval(() => {
|
||||||
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) this.initWs()
|
||||||
|
this.ws.send(JSON.stringify({action: 'ping'}))
|
||||||
|
}, 30 * 1000)
|
||||||
|
},
|
||||||
|
trackAddress: async function (address, retry = 0) {
|
||||||
|
try {
|
||||||
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) this.initWs()
|
||||||
|
this.ws.send(JSON.stringify({'track-address': address}))
|
||||||
|
} catch (error) {
|
||||||
|
await sleep(1000)
|
||||||
|
if (retry > 10) throw error
|
||||||
|
this.trackAddress(address, retry + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: async function () {
|
||||||
console.log('{{ charge.onchainaddress }}' == 'None')
|
if (this.charge.lnbitswallet) this.payInvoice()
|
||||||
if ('{{ charge.lnbitswallet }}' == 'None') {
|
else this.payOnchain()
|
||||||
this.lnbtc = false
|
await this.checkBalances()
|
||||||
this.onbtc = true
|
|
||||||
}
|
// empty for onchain
|
||||||
this.wallet.inkey = '{{ wallet_inkey }}'
|
this.wallet.inkey = '{{ wallet_inkey }}'
|
||||||
this.getTheTime()
|
|
||||||
this.getThePercentage()
|
|
||||||
var timerCount = this.timerCount
|
|
||||||
if ('{{ charge.paid }}' == 'False') {
|
|
||||||
timerCount()
|
|
||||||
}
|
|
||||||
this.startPaymentNotifier()
|
this.startPaymentNotifier()
|
||||||
|
|
||||||
|
if (!this.charge.paid) {
|
||||||
|
this.loopRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.charge.onchainaddress) {
|
||||||
|
this.loopPingWs()
|
||||||
|
this.checkPendingOnchain()
|
||||||
|
this.trackAddress(this.charge.onchainaddress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,46 +18,54 @@
|
|||||||
<h5 class="text-subtitle1 q-my-none">Charges</h5>
|
<h5 class="text-subtitle1 q-my-none">Charges</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto">
|
<div class="col q-pr-lg">
|
||||||
<q-input
|
<q-input
|
||||||
borderless
|
borderless
|
||||||
dense
|
dense
|
||||||
debounce="300"
|
debounce="300"
|
||||||
v-model="filter"
|
v-model="filter"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
class="float-right"
|
||||||
>
|
>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search"></q-icon>
|
<q-icon name="search"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-btn flat color="grey" @click="exportchargeCSV"
|
</div>
|
||||||
>Export to CSV</q-btn
|
<div class="col-auto">
|
||||||
>
|
<q-btn outline color="grey" label="...">
|
||||||
|
<q-menu auto-close>
|
||||||
|
<q-list style="min-width: 100px">
|
||||||
|
<q-item clickable>
|
||||||
|
<q-item-section @click="exportchargeCSV"
|
||||||
|
>Export to CSV</q-item-section
|
||||||
|
>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-table
|
<q-table
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
:data="ChargeLinks"
|
:data="chargeLinks"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="ChargesTable.columns"
|
:columns="chargesTable.columns"
|
||||||
:pagination.sync="ChargesTable.pagination"
|
:pagination.sync="chargesTable.pagination"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
>
|
>
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th auto-width></q-th>
|
<q-th auto-width></q-th>
|
||||||
<q-th auto-width></q-th>
|
<q-th auto-width>Status </q-th>
|
||||||
|
<q-th auto-width>Title</q-th>
|
||||||
<q-th
|
<q-th auto-width>Time Left (hh:mm)</q-th>
|
||||||
v-for="col in props.cols"
|
<q-th auto-width>Time To Pay (hh:mm)</q-th>
|
||||||
:key="col.name"
|
<q-th auto-width>Amount To Pay</q-th>
|
||||||
:props="props"
|
<q-th auto-width>Balance</q-th>
|
||||||
auto-width
|
<q-th auto-width>Pending Balance</q-th>
|
||||||
>
|
<q-th auto-width>Onchain Address</q-th>
|
||||||
<div v-if="col.name == 'id'"></div>
|
|
||||||
<div v-else>{{ col.label }}</div>
|
|
||||||
</q-th>
|
|
||||||
<q-th auto-width></q-th>
|
<q-th auto-width></q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,73 +74,179 @@
|
|||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
size="sm"
|
||||||
|
color="accent"
|
||||||
|
round
|
||||||
dense
|
dense
|
||||||
size="xs"
|
@click="props.row.expanded= !props.row.expanded"
|
||||||
icon="link"
|
:icon="props.row.expanded? 'remove' : 'add'"
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
/>
|
||||||
type="a"
|
</q-td>
|
||||||
|
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-badge
|
||||||
|
v-if="props.row.time_elapsed && props.row.balance < props.row.amount"
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>expired</a
|
||||||
|
>
|
||||||
|
</q-badge>
|
||||||
|
|
||||||
|
<q-badge
|
||||||
|
v-else-if="props.row.balance >= props.row.amount"
|
||||||
|
color="green"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>paid</a
|
||||||
|
>
|
||||||
|
</q-badge>
|
||||||
|
|
||||||
|
<q-badge v-else color="blue"
|
||||||
|
><a
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>waiting</a
|
||||||
|
>
|
||||||
|
</q-badge>
|
||||||
|
</q-td>
|
||||||
|
<q-td key="description" :props="props" :class="">
|
||||||
|
<a
|
||||||
:href="props.row.displayUrl"
|
:href="props.row.displayUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>{{props.row.description}}</a
|
||||||
>
|
>
|
||||||
<q-tooltip> Payment link </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td key="timeLeft" :props="props" :class="">
|
||||||
<q-btn
|
<div>{{props.row.timeLeft}}</div>
|
||||||
v-if="props.row.time_elapsed && props.row.balance < props.row.amount"
|
<q-linear-progress
|
||||||
unelevated
|
v-if="props.row.timeLeft"
|
||||||
flat
|
:value="props.row.progress"
|
||||||
dense
|
color="secondary"
|
||||||
size="xs"
|
|
||||||
icon="error"
|
|
||||||
:color="($q.dark.isActive) ? 'red' : 'red'"
|
|
||||||
>
|
>
|
||||||
<q-tooltip> Time elapsed </q-tooltip>
|
</q-linear-progress>
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
v-else-if="props.row.balance >= props.row.amount"
|
|
||||||
unelevated
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="check"
|
|
||||||
:color="($q.dark.isActive) ? 'green' : 'green'"
|
|
||||||
>
|
|
||||||
<q-tooltip> PAID! </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="cached"
|
|
||||||
flat
|
|
||||||
:color="($q.dark.isActive) ? 'blue' : 'blue'"
|
|
||||||
>
|
|
||||||
<q-tooltip> Processing </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="deleteChargeLink(props.row.id)"
|
|
||||||
icon="cancel"
|
|
||||||
color="pink"
|
|
||||||
>
|
|
||||||
<q-tooltip> Delete charge </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td
|
<q-td key="time to pay" :props="props" :class="">
|
||||||
v-for="col in props.cols"
|
<div>{{props.row.time}}</div>
|
||||||
:key="col.name"
|
</q-td>
|
||||||
:props="props"
|
<q-td key="amount" :props="props" :class="">
|
||||||
auto-width
|
<div>{{props.row.amount}}</div>
|
||||||
>
|
</q-td>
|
||||||
<div v-if="col.name == 'id'"></div>
|
<q-td key="balance" :props="props" :class="">
|
||||||
<div v-else>{{ col.value }}</div>
|
<div>{{props.row.balance}}</div>
|
||||||
|
</q-td>
|
||||||
|
<q-td key="pendingBalance" :props="props" :class="">
|
||||||
|
<div>
|
||||||
|
{{props.row.pendingBalance ? props.row.pendingBalance : ''}}
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
<q-td key="onchain address" :props="props" :class="">
|
||||||
|
<a
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>{{props.row.onchainaddress}}</a
|
||||||
|
>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
<q-tr v-show="props.row.expanded" :props="props">
|
||||||
|
<q-td colspan="100%">
|
||||||
|
<div
|
||||||
|
v-if="props.row.onchainwallet"
|
||||||
|
class="row items-center q-mt-md q-mb-lg"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg">Onchain Wallet:</div>
|
||||||
|
<div class="col-4 q-pr-lg">
|
||||||
|
{{getOnchainWalletName(props.row.onchainwallet)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="props.row.lnbitswallet"
|
||||||
|
class="row items-center q-mt-md q-mb-lg"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg">LNbits Wallet:</div>
|
||||||
|
<div class="col-4 q-pr-lg">
|
||||||
|
{{getLNbitsWalletName(props.row.lnbitswallet)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="props.row.completelink || props.row.completelinktext"
|
||||||
|
class="row items-center q-mt-md q-mb-lg"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg">Completed Link:</div>
|
||||||
|
<div class="col-4 q-pr-lg">
|
||||||
|
<a
|
||||||
|
:href="props.row.completelink"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>{{props.row.completelinktext ||
|
||||||
|
props.row.completelink}}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="props.row.webhook"
|
||||||
|
class="row items-center q-mt-md q-mb-lg"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg">Webhook:</div>
|
||||||
|
<div class="col-4 q-pr-lg">
|
||||||
|
<a
|
||||||
|
:href="props.row.webhook"
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset; text-decoration: none"
|
||||||
|
>{{props.row.webhook || props.row.webhook}}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center q-mt-md q-mb-lg">
|
||||||
|
<div class="col-2 q-pr-lg">ID:</div>
|
||||||
|
<div class="col-4 q-pr-lg">{{props.row.id}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center q-mt-md q-mb-lg">
|
||||||
|
<div class="col-2 q-pr-lg"></div>
|
||||||
|
<div class="col-6 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="gray"
|
||||||
|
outline
|
||||||
|
type="a"
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
class="float-left q-mr-lg"
|
||||||
|
>Details</q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="gray"
|
||||||
|
outline
|
||||||
|
type="a"
|
||||||
|
@click="refreshBalance(props.row)"
|
||||||
|
target="_blank"
|
||||||
|
class="float-left"
|
||||||
|
>Refresh Balance</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="pink"
|
||||||
|
icon="cancel"
|
||||||
|
@click="deleteChargeLink(props.row.id)"
|
||||||
|
>Delete</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-4"></div>
|
||||||
|
<div class="col-2 q-pr-lg"></div>
|
||||||
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
@@ -155,11 +269,7 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
<q-dialog
|
<q-dialog v-model="formDialogCharge.show" position="top">
|
||||||
v-model="formDialogCharge.show"
|
|
||||||
position="top"
|
|
||||||
@hide="closeFormDialog"
|
|
||||||
>
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
<q-form @submit="sendFormDataCharge" class="q-gutter-md">
|
<q-form @submit="sendFormDataCharge" class="q-gutter-md">
|
||||||
<q-input
|
<q-input
|
||||||
@@ -246,7 +356,7 @@
|
|||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
emit-value
|
emit-value
|
||||||
v-model="formDialogCharge.data.onchainwallet"
|
v-model="onchainwallet"
|
||||||
:options="walletLinks"
|
:options="walletLinks"
|
||||||
label="Onchain Wallet"
|
label="Onchain Wallet"
|
||||||
/>
|
/>
|
||||||
@@ -284,49 +394,28 @@
|
|||||||
<!-- lnbits/static/vendor
|
<!-- lnbits/static/vendor
|
||||||
<script src="/vendor/vue-qrcode@1.0.2/vue-qrcode.min.js"></script> -->
|
<script src="/vendor/vue-qrcode@1.0.2/vue-qrcode.min.js"></script> -->
|
||||||
<style></style>
|
<style></style>
|
||||||
|
<!-- todo: use config mempool -->
|
||||||
|
<script src="https://mempool.space/mempool.js"></script>
|
||||||
|
<script src="{{ url_for('satspay_static', path='js/utils.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
var mapCharge = obj => {
|
|
||||||
obj._data = _.clone(obj)
|
|
||||||
obj.theTime = obj.time * 60 - (Date.now() / 1000 - obj.timestamp)
|
|
||||||
obj.time = obj.time + 'mins'
|
|
||||||
|
|
||||||
if (obj.time_elapsed) {
|
|
||||||
obj.date = 'Time elapsed'
|
|
||||||
} else {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date((obj.theTime - 3600) * 1000),
|
|
||||||
'HH:mm:ss'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
obj.displayUrl = ['/satspay/', obj.id].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
watchonlyactive: false,
|
|
||||||
balance: null,
|
balance: null,
|
||||||
checker: null,
|
|
||||||
walletLinks: [],
|
walletLinks: [],
|
||||||
ChargeLinks: [],
|
chargeLinks: [],
|
||||||
ChargeLinksObj: [],
|
|
||||||
onchainwallet: '',
|
onchainwallet: '',
|
||||||
currentaddress: '',
|
rescanning: false,
|
||||||
Addresses: {
|
|
||||||
show: false,
|
|
||||||
data: null
|
|
||||||
},
|
|
||||||
mempool: {
|
mempool: {
|
||||||
endpoint: ''
|
endpoint: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
ChargesTable: {
|
chargesTable: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'theId',
|
name: 'theId',
|
||||||
@@ -341,10 +430,10 @@
|
|||||||
field: 'description'
|
field: 'description'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'timeleft',
|
name: 'timeLeft',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Time left',
|
label: 'Time left',
|
||||||
field: 'date'
|
field: 'timeLeft'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'time to pay',
|
name: 'time to pay',
|
||||||
@@ -364,6 +453,12 @@
|
|||||||
label: 'Balance',
|
label: 'Balance',
|
||||||
field: 'balance'
|
field: 'balance'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'pendingBalance',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Pending Balance',
|
||||||
|
field: 'pendingBalance'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'onchain address',
|
name: 'onchain address',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
@@ -393,172 +488,218 @@
|
|||||||
rowsPerPage: 10
|
rowsPerPage: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
formDialogCharge: {
|
formDialogCharge: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
|
onchain: false,
|
||||||
|
onchainwallet: '',
|
||||||
|
lnbits: false,
|
||||||
|
description: '',
|
||||||
|
time: null,
|
||||||
|
amount: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
cancelCharge: function (data) {
|
||||||
|
this.formDialogCharge.data.description = ''
|
||||||
|
this.formDialogCharge.data.onchainwallet = ''
|
||||||
|
this.formDialogCharge.data.lnbitswallet = ''
|
||||||
|
this.formDialogCharge.data.time = null
|
||||||
|
this.formDialogCharge.data.amount = null
|
||||||
|
this.formDialogCharge.data.webhook = ''
|
||||||
|
this.formDialogCharge.data.completelink = ''
|
||||||
|
this.formDialogCharge.show = false
|
||||||
|
},
|
||||||
|
|
||||||
|
getWalletLinks: async function () {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/watchonly/api/v1/wallet',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
this.walletLinks = data.map(w => ({
|
||||||
|
id: w.id,
|
||||||
|
label: w.title + ' - ' + w.id
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getWalletConfig: async function () {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/watchonly/api/v1/config',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
this.mempool.endpoint = data.mempool_endpoint
|
||||||
|
const url = new URL(this.mempool.endpoint)
|
||||||
|
this.mempool.hostname = url.hostname
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getOnchainWalletName: function (walletId) {
|
||||||
|
const wallet = this.walletLinks.find(w => w.id === walletId)
|
||||||
|
if (!wallet) return 'unknown'
|
||||||
|
return wallet.label
|
||||||
|
},
|
||||||
|
getLNbitsWalletName: function (walletId) {
|
||||||
|
const wallet = this.g.user.walletOptions.find(w => w.value === walletId)
|
||||||
|
if (!wallet) return 'unknown'
|
||||||
|
return wallet.label
|
||||||
|
},
|
||||||
|
|
||||||
|
getCharges: async function () {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/charges',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
this.chargeLinks = data.map(c =>
|
||||||
|
mapCharge(
|
||||||
|
c,
|
||||||
|
this.chargeLinks.find(old => old.id === c.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendFormDataCharge: function () {
|
||||||
|
const wallet = this.g.user.wallets[0].inkey
|
||||||
|
const data = this.formDialogCharge.data
|
||||||
|
data.amount = parseInt(data.amount)
|
||||||
|
data.time = parseInt(data.time)
|
||||||
|
data.onchainwallet = this.onchainwallet?.id
|
||||||
|
this.createCharge(wallet, data)
|
||||||
|
},
|
||||||
|
refreshActiveChargesBalance: async function () {
|
||||||
|
try {
|
||||||
|
const activeLinkIds = this.chargeLinks
|
||||||
|
.filter(c => !c.paid && !c.time_elapsed && !c.hasStaleBalance)
|
||||||
|
.map(c => c.id)
|
||||||
|
.join(',')
|
||||||
|
if (activeLinkIds) {
|
||||||
|
await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/charges/balance/' + activeLinkIds,
|
||||||
|
'filla'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
} finally {
|
||||||
|
await this.getCharges()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshBalance: async function (charge) {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/charge/balance/' + charge.id,
|
||||||
|
'filla'
|
||||||
|
)
|
||||||
|
charge.balance = data.balance
|
||||||
|
} catch (error) {}
|
||||||
|
},
|
||||||
|
rescanOnchainAddresses: async function () {
|
||||||
|
if (this.rescanning) return
|
||||||
|
this.rescanning = true
|
||||||
|
|
||||||
|
const {
|
||||||
|
bitcoin: {addresses: addressesAPI}
|
||||||
|
} = mempoolJS({hostname: this.mempool.hostname})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const onchainActiveCharges = this.chargeLinks.filter(
|
||||||
|
c => c.onchainaddress && !c.paid && !c.time_elapsed
|
||||||
|
)
|
||||||
|
for (const charge of onchainActiveCharges) {
|
||||||
|
const fn = async () =>
|
||||||
|
addressesAPI.getAddressTxsUtxo({
|
||||||
|
address: charge.onchainaddress
|
||||||
|
})
|
||||||
|
|
||||||
|
const utxos = await retryWithDelay(fn)
|
||||||
|
const newBalance = utxos.reduce((t, u) => t + u.value, 0)
|
||||||
|
|
||||||
|
charge.pendingBalance = utxos
|
||||||
|
.filter(u => !u.status.confirmed)
|
||||||
|
.reduce((t, u) => t + u.value, 0)
|
||||||
|
|
||||||
|
charge.hasStaleBalance = charge.balance === newBalance
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.rescanning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createCharge: async function (wallet, data) {
|
||||||
|
try {
|
||||||
|
const resp = await LNbits.api.request(
|
||||||
|
'POST',
|
||||||
|
'/satspay/api/v1/charge',
|
||||||
|
wallet,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
this.chargeLinks.unshift(mapCharge(resp.data))
|
||||||
|
this.formDialogCharge.show = false
|
||||||
|
this.formDialogCharge.data = {
|
||||||
onchain: false,
|
onchain: false,
|
||||||
lnbits: false,
|
lnbits: false,
|
||||||
description: '',
|
description: '',
|
||||||
time: null,
|
time: null,
|
||||||
amount: null
|
amount: null
|
||||||
}
|
}
|
||||||
},
|
} catch (error) {
|
||||||
qrCodeDialog: {
|
LNbits.utils.notifyApiError(error)
|
||||||
show: false,
|
|
||||||
data: null
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
cancelCharge: function (data) {
|
|
||||||
var self = this
|
|
||||||
self.formDialogCharge.data.description = ''
|
|
||||||
self.formDialogCharge.data.onchainwallet = ''
|
|
||||||
self.formDialogCharge.data.lnbitswallet = ''
|
|
||||||
self.formDialogCharge.data.time = null
|
|
||||||
self.formDialogCharge.data.amount = null
|
|
||||||
self.formDialogCharge.data.webhook = ''
|
|
||||||
self.formDialogCharge.data.completelink = ''
|
|
||||||
self.formDialogCharge.show = false
|
|
||||||
},
|
|
||||||
|
|
||||||
getWalletLinks: function () {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/watchonly/api/v1/wallet',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
for (i = 0; i < response.data.length; i++) {
|
|
||||||
self.walletLinks.push(response.data[i].id)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
closeFormDialog: function () {
|
|
||||||
this.formDialog.data = {
|
|
||||||
is_unique: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openQrCodeDialog: function (linkId) {
|
|
||||||
var self = this
|
|
||||||
var getAddresses = this.getAddresses
|
|
||||||
getAddresses(linkId)
|
|
||||||
self.current = linkId
|
|
||||||
self.Addresses.show = true
|
|
||||||
},
|
|
||||||
getCharges: function () {
|
|
||||||
var self = this
|
|
||||||
var getAddressBalance = this.getAddressBalance
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/satspay/api/v1/charges',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.ChargeLinks = response.data.map(mapCharge)
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
sendFormDataCharge: function () {
|
|
||||||
var self = this
|
|
||||||
var wallet = this.g.user.wallets[0].inkey
|
|
||||||
var data = this.formDialogCharge.data
|
|
||||||
data.amount = parseInt(data.amount)
|
|
||||||
data.time = parseInt(data.time)
|
|
||||||
this.createCharge(wallet, data)
|
|
||||||
},
|
|
||||||
timerCount: function () {
|
|
||||||
self = this
|
|
||||||
var refreshIntervalId = setInterval(function () {
|
|
||||||
for (i = 0; i < self.ChargeLinks.length - 1; i++) {
|
|
||||||
if (self.ChargeLinks[i]['paid'] == 'True') {
|
|
||||||
setTimeout(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/satspay/api/v1/charges/balance/' +
|
|
||||||
self.ChargeLinks[i]['id'],
|
|
||||||
'filla'
|
|
||||||
)
|
|
||||||
.then(function (response) {})
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.getCharges()
|
|
||||||
}, 20000)
|
|
||||||
},
|
|
||||||
createCharge: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request('POST', '/satspay/api/v1/charge', wallet, data)
|
|
||||||
.then(function (response) {
|
|
||||||
self.ChargeLinks.push(mapCharge(response.data))
|
|
||||||
self.formDialogCharge.show = false
|
|
||||||
self.formDialogCharge.data = {
|
|
||||||
onchain: false,
|
|
||||||
lnbits: false,
|
|
||||||
description: '',
|
|
||||||
time: null,
|
|
||||||
amount: null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteChargeLink: function (chargeId) {
|
deleteChargeLink: function (chargeId) {
|
||||||
var self = this
|
const link = _.findWhere(this.chargeLinks, {id: chargeId})
|
||||||
var link = _.findWhere(this.ChargeLinks, {id: chargeId})
|
|
||||||
LNbits.utils
|
LNbits.utils
|
||||||
.confirmDialog('Are you sure you want to delete this pay link?')
|
.confirmDialog('Are you sure you want to delete this pay link?')
|
||||||
.onOk(function () {
|
.onOk(async () => {
|
||||||
LNbits.api
|
try {
|
||||||
.request(
|
const response = await LNbits.api.request(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'/satspay/api/v1/charge/' + chargeId,
|
'/satspay/api/v1/charge/' + chargeId,
|
||||||
self.g.user.wallets[0].adminkey
|
this.g.user.wallets[0].adminkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
|
||||||
self.ChargeLinks = _.reject(self.ChargeLinks, function (obj) {
|
this.chargeLinks = _.reject(this.chargeLinks, function (obj) {
|
||||||
return obj.id === chargeId
|
return obj.id === chargeId
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
exportchargeCSV: function () {
|
exportchargeCSV: function () {
|
||||||
var self = this
|
LNbits.utils.exportCSV(
|
||||||
LNbits.utils.exportCSV(self.ChargesTable.columns, this.ChargeLinks)
|
this.chargesTable.columns,
|
||||||
|
this.chargeLinks,
|
||||||
|
'charges'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: async function () {
|
||||||
console.log(this.g.user)
|
await this.getCharges()
|
||||||
var self = this
|
await this.getWalletLinks()
|
||||||
var getCharges = this.getCharges
|
await this.getWalletConfig()
|
||||||
getCharges()
|
setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000)
|
||||||
var getWalletLinks = this.getWalletLinks
|
await this.rescanOnchainAddresses()
|
||||||
getWalletLinks()
|
setInterval(() => this.rescanOnchainAddresses(), 10 * 1000)
|
||||||
var timerCount = this.timerCount
|
|
||||||
timerCount()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from starlette.responses import HTMLResponse
|
|||||||
from lnbits.core.crud import get_wallet
|
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.watchonly.crud import get_config
|
||||||
|
|
||||||
from . import satspay_ext, satspay_renderer
|
from . import satspay_ext, satspay_renderer
|
||||||
from .crud import get_charge
|
from .crud import get_charge
|
||||||
@@ -24,14 +25,21 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
|
|||||||
|
|
||||||
|
|
||||||
@satspay_ext.get("/{charge_id}", response_class=HTMLResponse)
|
@satspay_ext.get("/{charge_id}", response_class=HTMLResponse)
|
||||||
async def display(request: Request, charge_id):
|
async def display(request: Request, charge_id: str):
|
||||||
charge = await get_charge(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
if not charge:
|
if not charge:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
||||||
)
|
)
|
||||||
wallet = await get_wallet(charge.lnbitswallet)
|
wallet = await get_wallet(charge.lnbitswallet)
|
||||||
|
onchainwallet_config = await get_config(charge.user)
|
||||||
|
inkey = wallet.inkey if wallet else None
|
||||||
return satspay_renderer().TemplateResponse(
|
return satspay_renderer().TemplateResponse(
|
||||||
"satspay/display.html",
|
"satspay/display.html",
|
||||||
{"request": request, "charge": charge, "wallet_key": wallet.inkey},
|
{
|
||||||
|
"request": request,
|
||||||
|
"charge_data": charge.dict(),
|
||||||
|
"wallet_inkey": inkey,
|
||||||
|
"mempool_endpoint": onchainwallet_config.mempool_endpoint,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import Query
|
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
@@ -31,7 +30,12 @@ async def api_charge_create(
|
|||||||
data: CreateCharge, wallet: WalletTypeInfo = Depends(require_invoice_key)
|
data: CreateCharge, wallet: WalletTypeInfo = Depends(require_invoice_key)
|
||||||
):
|
):
|
||||||
charge = await create_charge(user=wallet.wallet.user, data=data)
|
charge = await create_charge(user=wallet.wallet.user, data=data)
|
||||||
return charge.dict()
|
return {
|
||||||
|
**charge.dict(),
|
||||||
|
**{"time_elapsed": charge.time_elapsed},
|
||||||
|
**{"time_left": charge.time_left},
|
||||||
|
**{"paid": charge.paid},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@satspay_ext.put("/api/v1/charge/{charge_id}")
|
@satspay_ext.put("/api/v1/charge/{charge_id}")
|
||||||
@@ -51,6 +55,7 @@ async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|||||||
{
|
{
|
||||||
**charge.dict(),
|
**charge.dict(),
|
||||||
**{"time_elapsed": charge.time_elapsed},
|
**{"time_elapsed": charge.time_elapsed},
|
||||||
|
**{"time_left": charge.time_left},
|
||||||
**{"paid": charge.paid},
|
**{"paid": charge.paid},
|
||||||
}
|
}
|
||||||
for charge in await get_charges(wallet.wallet.user)
|
for charge in await get_charges(wallet.wallet.user)
|
||||||
@@ -73,6 +78,7 @@ async def api_charge_retrieve(
|
|||||||
return {
|
return {
|
||||||
**charge.dict(),
|
**charge.dict(),
|
||||||
**{"time_elapsed": charge.time_elapsed},
|
**{"time_elapsed": charge.time_elapsed},
|
||||||
|
**{"time_left": charge.time_left},
|
||||||
**{"paid": charge.paid},
|
**{"paid": charge.paid},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +99,18 @@ async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_
|
|||||||
#############################BALANCE##########################
|
#############################BALANCE##########################
|
||||||
|
|
||||||
|
|
||||||
@satspay_ext.get("/api/v1/charges/balance/{charge_id}")
|
@satspay_ext.get("/api/v1/charges/balance/{charge_ids}")
|
||||||
async def api_charges_balance(charge_id):
|
async def api_charges_balance(charge_ids):
|
||||||
|
charge_id_list = charge_ids.split(",")
|
||||||
|
charges = []
|
||||||
|
for charge_id in charge_id_list:
|
||||||
|
charge = await api_charge_balance(charge_id)
|
||||||
|
charges.append(charge)
|
||||||
|
return charges
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.get("/api/v1/charge/balance/{charge_id}")
|
||||||
|
async def api_charge_balance(charge_id):
|
||||||
charge = await check_address_balance(charge_id)
|
charge = await check_address_balance(charge_id)
|
||||||
|
|
||||||
if not charge:
|
if not charge:
|
||||||
@@ -125,23 +140,9 @@ async def api_charges_balance(charge_id):
|
|||||||
)
|
)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
charge.webhook = None
|
charge.webhook = None
|
||||||
return charge.dict()
|
return {
|
||||||
|
**charge.dict(),
|
||||||
|
**{"time_elapsed": charge.time_elapsed},
|
||||||
#############################MEMPOOL##########################
|
**{"time_left": charge.time_left},
|
||||||
|
**{"paid": charge.paid},
|
||||||
|
}
|
||||||
@satspay_ext.put("/api/v1/mempool")
|
|
||||||
async def api_update_mempool(
|
|
||||||
endpoint: str = Query(...), wallet: WalletTypeInfo = Depends(get_key_type)
|
|
||||||
):
|
|
||||||
mempool = await update_mempool(endpoint, user=wallet.wallet.user)
|
|
||||||
return mempool.dict()
|
|
||||||
|
|
||||||
|
|
||||||
@satspay_ext.route("/api/v1/mempool")
|
|
||||||
async def api_get_mempool(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
mempool = await get_mempool(wallet.wallet.user)
|
|
||||||
if not mempool:
|
|
||||||
mempool = await create_mempool(user=wallet.wallet.user)
|
|
||||||
return mempool.dict()
|
|
||||||
|
|||||||
@@ -238,41 +238,3 @@ async def get_config(user: str) -> Optional[Config]:
|
|||||||
"""SELECT json_data FROM watchonly.config WHERE "user" = ?""", (user,)
|
"""SELECT json_data FROM watchonly.config WHERE "user" = ?""", (user,)
|
||||||
)
|
)
|
||||||
return json.loads(row[0], object_hook=lambda d: Config(**d)) if row else None
|
return json.loads(row[0], object_hook=lambda d: Config(**d)) if row else None
|
||||||
|
|
||||||
|
|
||||||
######################MEMPOOL#######################
|
|
||||||
### TODO: fix statspay dependcy and remove
|
|
||||||
async def create_mempool(user: str) -> Optional[Mempool]:
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO watchonly.mempool ("user",endpoint)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""",
|
|
||||||
(user, "https://mempool.space"),
|
|
||||||
)
|
|
||||||
row = await db.fetchone(
|
|
||||||
"""SELECT * FROM watchonly.mempool WHERE "user" = ?""", (user,)
|
|
||||||
)
|
|
||||||
return Mempool.from_row(row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
### TODO: fix statspay dependcy and remove
|
|
||||||
async def update_mempool(user: str, **kwargs) -> Optional[Mempool]:
|
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
f"""UPDATE watchonly.mempool SET {q} WHERE "user" = ?""",
|
|
||||||
(*kwargs.values(), user),
|
|
||||||
)
|
|
||||||
row = await db.fetchone(
|
|
||||||
"""SELECT * FROM watchonly.mempool WHERE "user" = ?""", (user,)
|
|
||||||
)
|
|
||||||
return Mempool.from_row(row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
### TODO: fix statspay dependcy and remove
|
|
||||||
async def get_mempool(user: str) -> Mempool:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"""SELECT * FROM watchonly.mempool WHERE "user" = ?""", (user,)
|
|
||||||
)
|
|
||||||
return Mempool.from_row(row) if row else None
|
|
||||||
|
|||||||
@@ -647,7 +647,9 @@ new Vue({
|
|||||||
getAddressTxsDelayed: async function (addrData) {
|
getAddressTxsDelayed: async function (addrData) {
|
||||||
const {
|
const {
|
||||||
bitcoin: {addresses: addressesAPI}
|
bitcoin: {addresses: addressesAPI}
|
||||||
} = mempoolJS()
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.config.data.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
const fn = async () =>
|
const fn = async () =>
|
||||||
addressesAPI.getAddressTxs({
|
addressesAPI.getAddressTxs({
|
||||||
@@ -660,7 +662,9 @@ new Vue({
|
|||||||
refreshRecommendedFees: async function () {
|
refreshRecommendedFees: async function () {
|
||||||
const {
|
const {
|
||||||
bitcoin: {fees: feesAPI}
|
bitcoin: {fees: feesAPI}
|
||||||
} = mempoolJS()
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.config.data.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
const fn = async () => feesAPI.getFeesRecommended()
|
const fn = async () => feesAPI.getFeesRecommended()
|
||||||
this.payment.recommededFees = await retryWithDelay(fn)
|
this.payment.recommededFees = await retryWithDelay(fn)
|
||||||
@@ -668,7 +672,9 @@ new Vue({
|
|||||||
getAddressTxsUtxoDelayed: async function (address) {
|
getAddressTxsUtxoDelayed: async function (address) {
|
||||||
const {
|
const {
|
||||||
bitcoin: {addresses: addressesAPI}
|
bitcoin: {addresses: addressesAPI}
|
||||||
} = mempoolJS()
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.config.data.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
const fn = async () =>
|
const fn = async () =>
|
||||||
addressesAPI.getAddressTxsUtxo({
|
addressesAPI.getAddressTxsUtxo({
|
||||||
@@ -679,7 +685,9 @@ new Vue({
|
|||||||
fetchTxHex: async function (txId) {
|
fetchTxHex: async function (txId) {
|
||||||
const {
|
const {
|
||||||
bitcoin: {transactions: transactionsAPI}
|
bitcoin: {transactions: transactionsAPI}
|
||||||
} = mempoolJS()
|
} = mempoolJS({
|
||||||
|
hostname: new URL(this.config.data.mempool_endpoint).hostname
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await transactionsAPI.getTxHex({txid: txId})
|
const response = await transactionsAPI.getTxHex({txid: txId})
|
||||||
|
|||||||
@@ -1198,6 +1198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
<!-- todo: use endpoint here -->
|
||||||
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
|
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
||||||
|
|||||||
@@ -15,19 +15,16 @@ from lnbits.extensions.watchonly import watchonly_ext
|
|||||||
from .crud import (
|
from .crud import (
|
||||||
create_config,
|
create_config,
|
||||||
create_fresh_addresses,
|
create_fresh_addresses,
|
||||||
create_mempool,
|
|
||||||
create_watch_wallet,
|
create_watch_wallet,
|
||||||
delete_addresses_for_wallet,
|
delete_addresses_for_wallet,
|
||||||
delete_watch_wallet,
|
delete_watch_wallet,
|
||||||
get_addresses,
|
get_addresses,
|
||||||
get_config,
|
get_config,
|
||||||
get_fresh_address,
|
get_fresh_address,
|
||||||
get_mempool,
|
|
||||||
get_watch_wallet,
|
get_watch_wallet,
|
||||||
get_watch_wallets,
|
get_watch_wallets,
|
||||||
update_address,
|
update_address,
|
||||||
update_config,
|
update_config,
|
||||||
update_mempool,
|
|
||||||
update_watch_wallet,
|
update_watch_wallet,
|
||||||
)
|
)
|
||||||
from .helpers import parse_key
|
from .helpers import parse_key
|
||||||
@@ -281,23 +278,3 @@ async def api_get_config(w: WalletTypeInfo = Depends(get_key_type)):
|
|||||||
if not config:
|
if not config:
|
||||||
config = await create_config(user=w.wallet.user)
|
config = await create_config(user=w.wallet.user)
|
||||||
return config.dict()
|
return config.dict()
|
||||||
|
|
||||||
|
|
||||||
#############################MEMPOOL##########################
|
|
||||||
|
|
||||||
### TODO: fix statspay dependcy and remove
|
|
||||||
@watchonly_ext.put("/api/v1/mempool")
|
|
||||||
async def api_update_mempool(
|
|
||||||
endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key)
|
|
||||||
):
|
|
||||||
mempool = await update_mempool(**{"endpoint": endpoint}, user=w.wallet.user)
|
|
||||||
return mempool.dict()
|
|
||||||
|
|
||||||
|
|
||||||
### TODO: fix statspay dependcy and remove
|
|
||||||
@watchonly_ext.get("/api/v1/mempool")
|
|
||||||
async def api_get_mempool(w: WalletTypeInfo = Depends(require_admin_key)):
|
|
||||||
mempool = await get_mempool(w.wallet.user)
|
|
||||||
if not mempool:
|
|
||||||
mempool = await create_mempool(user=w.wallet.user)
|
|
||||||
return mempool.dict()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user