Splitting into x2 extensions
Splitting extension into 2, SatsPay Server, a BTCPay Server type extension, and WatchOnly
This commit is contained in:
parent
9724f33111
commit
ecfcc167f0
11 changed files with 1380 additions and 0 deletions
4
lnbits/extensions/satspay/README.md
Normal file
4
lnbits/extensions/satspay/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# SatsPay Server
|
||||||
|
|
||||||
|
Create onchain and LN charges. Includes webhooks!
|
||||||
|
|
||||||
11
lnbits/extensions/satspay/__init__.py
Normal file
11
lnbits/extensions/satspay/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from quart import Blueprint
|
||||||
|
from lnbits.db import Database
|
||||||
|
|
||||||
|
db = Database("ext_satspay")
|
||||||
|
|
||||||
|
|
||||||
|
satspay_ext: Blueprint = Blueprint("satspay", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
||||||
|
|
||||||
|
from .views_api import * # noqa
|
||||||
|
from .views import * # noqa
|
||||||
8
lnbits/extensions/satspay/config.json
Normal file
8
lnbits/extensions/satspay/config.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "SatsPay Server",
|
||||||
|
"short_description": "Create onchain and LN charges",
|
||||||
|
"icon": "visibility",
|
||||||
|
"contributors": [
|
||||||
|
"arcbtc"
|
||||||
|
]
|
||||||
|
}
|
||||||
80
lnbits/extensions/satspay/crud.py
Normal file
80
lnbits/extensions/satspay/crud.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
#from lnbits.db import open_ext_db
|
||||||
|
from . import db
|
||||||
|
from .models import Charges
|
||||||
|
|
||||||
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
|
from embit import bip32
|
||||||
|
from embit import ec
|
||||||
|
from embit.networks import NETWORKS
|
||||||
|
from embit import base58
|
||||||
|
from embit.util import hashlib
|
||||||
|
import io
|
||||||
|
from embit.util import secp256k1
|
||||||
|
from embit import hashes
|
||||||
|
from binascii import hexlify
|
||||||
|
from quart import jsonify
|
||||||
|
from embit import script
|
||||||
|
from embit import ec
|
||||||
|
from embit.networks import NETWORKS
|
||||||
|
from binascii import unhexlify, hexlify, a2b_base64, b2a_base64
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
###############CHARGES##########################
|
||||||
|
|
||||||
|
|
||||||
|
async def create_charge(walletid: str, user: str, title: Optional[str] = None, time: Optional[int] = None, amount: Optional[int] = None) -> Charges:
|
||||||
|
wallet = await get_watch_wallet(walletid)
|
||||||
|
address = await get_derive_address(walletid, wallet[4] + 1)
|
||||||
|
|
||||||
|
charge_id = urlsafe_short_hash()
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO charges (
|
||||||
|
id,
|
||||||
|
user,
|
||||||
|
title,
|
||||||
|
wallet,
|
||||||
|
address,
|
||||||
|
time_to_pay,
|
||||||
|
amount,
|
||||||
|
balance
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(charge_id, user, title, walletid, address, time, amount, 0),
|
||||||
|
)
|
||||||
|
return await get_charge(charge_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_charge(charge_id: str) -> Charges:
|
||||||
|
row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (charge_id,))
|
||||||
|
return Charges.from_row(row) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_charges(user: str) -> List[Charges]:
|
||||||
|
rows = await db.fetchall("SELECT * FROM charges WHERE user = ?", (user,))
|
||||||
|
for row in rows:
|
||||||
|
await check_address_balance(row.address)
|
||||||
|
rows = await db.fetchall("SELECT * FROM charges WHERE user = ?", (user,))
|
||||||
|
return [charges.from_row(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_charge(charge_id: str) -> None:
|
||||||
|
await db.execute("DELETE FROM charges WHERE id = ?", (charge_id,))
|
||||||
|
|
||||||
|
async def check_address_balance(address: str) -> List[Charges]:
|
||||||
|
address_data = await get_address(address)
|
||||||
|
mempool = await get_mempool(address_data.user)
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(mempool.endpoint + "/api/address/" + address)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
amount_paid = r.json()['chain_stats']['funded_txo_sum'] - r.json()['chain_stats']['spent_txo_sum']
|
||||||
|
print(amount_paid)
|
||||||
40
lnbits/extensions/satspay/migrations.py
Normal file
40
lnbits/extensions/satspay/migrations.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
async def m001_initial(db):
|
||||||
|
"""
|
||||||
|
Initial wallet table.
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS wallets (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
user TEXT,
|
||||||
|
masterpub TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
address_no INTEGER NOT NULL DEFAULT 0,
|
||||||
|
balance INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS charges (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
user TEXT,
|
||||||
|
title TEXT,
|
||||||
|
wallet TEXT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
time_to_pay INTEGER,
|
||||||
|
amount INTEGER,
|
||||||
|
balance INTEGER DEFAULT 0,
|
||||||
|
time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS mempool (
|
||||||
|
user TEXT NOT NULL,
|
||||||
|
endpoint TEXT NOT NULL
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
17
lnbits/extensions/satspay/models.py
Normal file
17
lnbits/extensions/satspay/models.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from sqlite3 import Row
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
class Charges(NamedTuple):
|
||||||
|
id: str
|
||||||
|
user: str
|
||||||
|
wallet: str
|
||||||
|
title: str
|
||||||
|
address: str
|
||||||
|
time_to_pay: str
|
||||||
|
amount: int
|
||||||
|
balance: int
|
||||||
|
time: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row) -> "Payments":
|
||||||
|
return cls(**dict(row))
|
||||||
141
lnbits/extensions/satspay/templates/watchonly/_api_docs.html
Normal file
141
lnbits/extensions/satspay/templates/watchonly/_api_docs.html
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<p>SatsPay: Create Onchain/LN charges. Includes webhooks!<br />
|
||||||
|
<small>
|
||||||
|
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<q-expansion-item
|
||||||
|
group="extras"
|
||||||
|
icon="swap_vertical_circle"
|
||||||
|
label="API info"
|
||||||
|
:content-inset-level="0.5"
|
||||||
|
>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="List pay links">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code><span class="text-blue">GET</span> /pay/api/v1/links</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>[<pay_link_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X GET {{ request.url_root }}pay/api/v1/links -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].inkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Get a pay link">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">GET</span>
|
||||||
|
/pay/api/v1/links/<pay_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 201 CREATED (application/json)
|
||||||
|
</h5>
|
||||||
|
<code>{"lnurl": <string>}</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X GET {{ request.url_root }}pay/api/v1/links/<pay_id> -H
|
||||||
|
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item
|
||||||
|
group="api"
|
||||||
|
dense
|
||||||
|
expand-separator
|
||||||
|
label="Create a pay link"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code><span class="text-green">POST</span> /pay/api/v1/links</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>
|
||||||
|
<code>{"description": <string> "amount": <integer>}</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
|
Returns 201 CREATED (application/json)
|
||||||
|
</h5>
|
||||||
|
<code>{"lnurl": <string>}</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X POST {{ request.url_root }}pay/api/v1/links -d
|
||||||
|
'{"description": <string>, "amount": <integer>}' -H
|
||||||
|
"Content-type: application/json" -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item
|
||||||
|
group="api"
|
||||||
|
dense
|
||||||
|
expand-separator
|
||||||
|
label="Update a pay link"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-green">PUT</span>
|
||||||
|
/pay/api/v1/links/<pay_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>
|
||||||
|
<code>{"description": <string>, "amount": <integer>}</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
|
Returns 200 OK (application/json)
|
||||||
|
</h5>
|
||||||
|
<code>{"lnurl": <string>}</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X PUT {{ request.url_root }}pay/api/v1/links/<pay_id> -d
|
||||||
|
'{"description": <string>, "amount": <integer>}' -H
|
||||||
|
"Content-type: application/json" -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item
|
||||||
|
group="api"
|
||||||
|
dense
|
||||||
|
expand-separator
|
||||||
|
label="Delete a pay link"
|
||||||
|
class="q-pb-md"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-pink">DELETE</span>
|
||||||
|
/pay/api/v1/links/<pay_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.url_root }}pay/api/v1/links/<pay_id>
|
||||||
|
-H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-expansion-item>
|
||||||
54
lnbits/extensions/satspay/templates/watchonly/display.html
Normal file
54
lnbits/extensions/satspay/templates/watchonly/display.html
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends "public.html" %} {% block page %}
|
||||||
|
<div class="row q-col-gutter-md justify-center">
|
||||||
|
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
|
||||||
|
<q-card class="q-pa-lg">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="lightning:{{ link.lnurl }}">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
value="{{ link.lnurl }}"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')"
|
||||||
|
>Copy LNURL</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h6 class="text-subtitle1 q-mb-sm q-mt-none">
|
||||||
|
LNbits LNURL-pay link
|
||||||
|
</h6>
|
||||||
|
<p class="q-my-none">
|
||||||
|
Use a LNURL compatible bitcoin wallet to claim the sats.
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-list>
|
||||||
|
{% include "lnurlp/_lnurl.html" %}
|
||||||
|
</q-list>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
847
lnbits/extensions/satspay/templates/watchonly/index.html
Normal file
847
lnbits/extensions/satspay/templates/watchonly/index.html
Normal file
|
|
@ -0,0 +1,847 @@
|
||||||
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
|
%} {% block page %}
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
{% raw %}
|
||||||
|
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
|
||||||
|
>New wallet </q-btn
|
||||||
|
>
|
||||||
|
<q-btn unelevated color="deep-purple"
|
||||||
|
icon="edit">
|
||||||
|
<div class="cursor-pointer">
|
||||||
|
<q-tooltip>
|
||||||
|
Point to another Mempool
|
||||||
|
</q-tooltip>
|
||||||
|
{{ this.mempool.endpoint }}
|
||||||
|
<q-popup-edit v-model="mempool.endpoint">
|
||||||
|
<q-input color="accent" v-model="mempool.endpoint">
|
||||||
|
</q-input>
|
||||||
|
<center><q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
@click="updateMempool()"
|
||||||
|
v-close-popup
|
||||||
|
>set</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
v-close-popup
|
||||||
|
>cancel</q-btn>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
</q-popup-edit>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-btn
|
||||||
|
>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-subtitle1 q-my-none">Wallets</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search"></q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-table
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
:data="walletLinks"
|
||||||
|
row-key="id"
|
||||||
|
:columns="WalletsTable.columns"
|
||||||
|
:pagination.sync="WalletsTable.pagination"
|
||||||
|
:filter="filter"
|
||||||
|
>
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||||
|
<div v-if="col.name == 'id'"></div>
|
||||||
|
<div v-else>
|
||||||
|
{{ col.label }}
|
||||||
|
</div>
|
||||||
|
</q-th>
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
|
||||||
|
<q-td auto-width>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="toll"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="formDialogCharge.show = true, formDialogCharge.data.walletid = props.row.id"
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-tooltip>
|
||||||
|
Charge link
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="dns"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="openQrCodeDialog(props.row.id)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-tooltip>
|
||||||
|
Adresses
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="openUpdateDialog(props.row.id)"
|
||||||
|
icon="edit"
|
||||||
|
color="light-blue"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="deleteWalletLink(props.row.id)"
|
||||||
|
icon="cancel"
|
||||||
|
color="pink"
|
||||||
|
></q-btn>
|
||||||
|
|
||||||
|
|
||||||
|
</q-td>
|
||||||
|
</q-td>
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||||
|
<div v-if="col.name == 'id'"></div>
|
||||||
|
<div v-else>
|
||||||
|
{{ col.value }}
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
|
||||||
|
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-subtitle1 q-my-none">Paylinks</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search"></q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-table
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
:data="ChargeLinks"
|
||||||
|
row-key="id"
|
||||||
|
:columns="ChargesTable.columns"
|
||||||
|
:pagination.sync="ChargesTable.pagination"
|
||||||
|
:filter="filter"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||||
|
<div v-if="col.name == 'id'"></div>
|
||||||
|
<div v-else>
|
||||||
|
{{ col.label }}
|
||||||
|
</div>
|
||||||
|
</q-th>
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
|
||||||
|
<q-td auto-width>
|
||||||
|
|
||||||
|
<q-icon v-if="props.row.timeleft < 1 && props.row.amount_paid < props.row.amount"
|
||||||
|
#unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
name="error"
|
||||||
|
:color="($q.dark.isActive) ? 'red' : 'red'"
|
||||||
|
></q-icon>
|
||||||
|
|
||||||
|
<q-icon v-else-if="props.row.amount_paid > props.row.amount"
|
||||||
|
#unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
name="check"
|
||||||
|
:color="($q.dark.isActive) ? 'green' : 'green'"
|
||||||
|
></q-icon>
|
||||||
|
|
||||||
|
<q-icon v-else="props.row.amount_paid < props.row.amount && props.row.timeleft > 1"
|
||||||
|
#unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
name="cached"
|
||||||
|
:color="($q.dark.isActive) ? 'blue' : 'blue'"
|
||||||
|
></q-icon>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="openUpdateDialog(props.row.id)"
|
||||||
|
icon="edit"
|
||||||
|
color="light-blue"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="deleteWalletLink(props.row.id)"
|
||||||
|
icon="cancel"
|
||||||
|
color="pink"
|
||||||
|
></q-btn>
|
||||||
|
|
||||||
|
|
||||||
|
</q-td>
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||||
|
<div v-if="col.name == 'id'"></div>
|
||||||
|
<div v-else>
|
||||||
|
{{ col.value }}
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 col-md-5 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h6 class="text-subtitle1 q-my-none">
|
||||||
|
LNbits satspay Extension
|
||||||
|
</h6>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-list>
|
||||||
|
{% include "satspay/_api_docs.html" %}
|
||||||
|
</q-list>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<q-form @submit="sendFormData" class="q-gutter-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialog.data.title"
|
||||||
|
type="text"
|
||||||
|
label="Title"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="textarea"
|
||||||
|
v-model="formDialog.data.masterpub"
|
||||||
|
height="50px"
|
||||||
|
autogrow
|
||||||
|
label="Master Public Key, either xpub, ypub, zpub"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="formDialog.data.id"
|
||||||
|
unelevated
|
||||||
|
color="deep-purple"
|
||||||
|
type="submit"
|
||||||
|
>Update Watch-only Wallet</q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
unelevated
|
||||||
|
color="deep-purple"
|
||||||
|
:disable="
|
||||||
|
formDialog.data.masterpub == null ||
|
||||||
|
formDialog.data.title == null"
|
||||||
|
type="submit"
|
||||||
|
>Create Watch-only Wallet</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||||
|
>Cancel</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
<q-dialog v-model="formDialogCharge.show" position="top" @hide="closeFormDialog">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<q-form @submit="sendFormDataCharge" class="q-gutter-md">
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.title"
|
||||||
|
type="text"
|
||||||
|
label="Title"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.amount"
|
||||||
|
type="number"
|
||||||
|
label="Amount (sats)"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.time"
|
||||||
|
type="number"
|
||||||
|
label="Time (secs)"
|
||||||
|
> </q-input>
|
||||||
|
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="formDialogCharge.data.id"
|
||||||
|
unelevated
|
||||||
|
color="deep-purple"
|
||||||
|
type="submit"
|
||||||
|
>Update Paylink</q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
unelevated
|
||||||
|
color="deep-purple"
|
||||||
|
:disable="
|
||||||
|
formDialogCharge.data.time == null ||
|
||||||
|
formDialogCharge.data.amount == null"
|
||||||
|
type="submit"
|
||||||
|
>Create Paylink</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||||
|
>Cancel</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
<q-dialog v-model="Addresses.show" position="top">
|
||||||
|
<q-card v-if="Addresses.data" class="q-pa-lg lnbits__dialog-card">
|
||||||
|
{% raw %}
|
||||||
|
<h5 class="text-subtitle1 q-my-none">Addresses</h5>
|
||||||
|
<q-separator></q-separator><br/>
|
||||||
|
<p><strong>Current:</strong>
|
||||||
|
{{ currentaddress }}
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="ms"
|
||||||
|
icon="visibility"
|
||||||
|
type="a"
|
||||||
|
:href="mempool.endpoint + '/address/' + currentaddress"
|
||||||
|
target="_blank"
|
||||||
|
></q-btn>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
|
<qrcode
|
||||||
|
:value="currentaddress"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
<p style="word-break: break-all;">
|
||||||
|
|
||||||
|
<q-scroll-area style="height: 200px; max-width: 100%;">
|
||||||
|
<q-list bordered v-for="data in Addresses.data.slice().reverse()">
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>{{ data.address }}</q-item-section>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="ms"
|
||||||
|
icon="visibility"
|
||||||
|
type="a"
|
||||||
|
:href="mempool.endpoint + '/address/' + data.address"
|
||||||
|
target="_blank"
|
||||||
|
></q-btn>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row q-mt-lg q-gutter-sm">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="getFreshAddress(current)"
|
||||||
|
class="q-ml-sm"
|
||||||
|
>Get fresh address</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
Vue.filter('reverse', function(value) {
|
||||||
|
// slice to make a copy of array, then reverse the copy
|
||||||
|
return value.slice().reverse();
|
||||||
|
});
|
||||||
|
var locationPath = [
|
||||||
|
window.location.protocol,
|
||||||
|
'//',
|
||||||
|
window.location.hostname,
|
||||||
|
window.location.pathname
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
var mapWalletLink = function (obj) {
|
||||||
|
obj._data = _.clone(obj)
|
||||||
|
obj.date = Quasar.utils.date.formatDate(
|
||||||
|
new Date(obj.time * 1000),
|
||||||
|
'YYYY-MM-DD HH:mm'
|
||||||
|
)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var mapCharge = function (obj) {
|
||||||
|
obj._data = _.clone(obj)
|
||||||
|
obj.date = Quasar.utils.date.formatDate(
|
||||||
|
new Date(obj.time * 1000),
|
||||||
|
'YYYY-MM-DD HH:mm'
|
||||||
|
)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
filter: '',
|
||||||
|
balance: null,
|
||||||
|
checker: null,
|
||||||
|
walletLinks: [],
|
||||||
|
ChargeLinks: [],
|
||||||
|
currentaddress: "",
|
||||||
|
Addresses: {
|
||||||
|
show: false,
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
mempool:{
|
||||||
|
endpoint:""
|
||||||
|
},
|
||||||
|
WalletsTable: {
|
||||||
|
columns: [
|
||||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Title',
|
||||||
|
field: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Amount',
|
||||||
|
field: 'amount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'masterpub',
|
||||||
|
align: 'left',
|
||||||
|
label: 'MasterPub',
|
||||||
|
field: 'masterpub'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChargesTable: {
|
||||||
|
columns: [
|
||||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Title',
|
||||||
|
field: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Amount to pay',
|
||||||
|
field: 'amount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'balance',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Balance',
|
||||||
|
field: 'amount_paid'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'address',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Address',
|
||||||
|
field: 'address'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'time to pay',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Time to Pay',
|
||||||
|
field: 'time_to_pay'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timeleft',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Time left',
|
||||||
|
field: 'timeleft'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
formDialogCharge: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
qrCodeDialog: {
|
||||||
|
show: false,
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
|
||||||
|
chargeRedirect: function (address){
|
||||||
|
window.location.href = this.mempool.endpoint + "/address/" + address;
|
||||||
|
},
|
||||||
|
getMempool: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/mempool',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log(response.data.endpoint)
|
||||||
|
self.mempool.endpoint = response.data.endpoint
|
||||||
|
console.log(this.mempool.endpoint)
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMempool: function () {
|
||||||
|
var self = this
|
||||||
|
var wallet = this.g.user.wallets[0]
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
'/satspay/api/v1/mempool',
|
||||||
|
wallet.inkey, self.mempool)
|
||||||
|
.then(function (response) {
|
||||||
|
self.mempool.endpoint = response.data.endpoint
|
||||||
|
self.walletLinks.push(mapwalletLink(response.data))
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getWalletLinks: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/wallet',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.walletLinks = response.data.map(function (obj) {
|
||||||
|
|
||||||
|
return mapWalletLink(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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
|
||||||
|
},
|
||||||
|
|
||||||
|
openUpdateDialog: function (linkId) {
|
||||||
|
var link = _.findWhere(this.walletLinks, {id: linkId})
|
||||||
|
this.formDialog.data = _.clone(link._data)
|
||||||
|
this.formDialog.show = true
|
||||||
|
},
|
||||||
|
sendFormData: function () {
|
||||||
|
var wallet = this.g.user.wallets[0]
|
||||||
|
var data = _.omit(this.formDialog.data, 'wallet')
|
||||||
|
|
||||||
|
if (data.id) {
|
||||||
|
this.updateWalletLink(wallet, data)
|
||||||
|
} else {
|
||||||
|
this.createWalletLink(wallet, data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getCharges: function () {
|
||||||
|
var self = this
|
||||||
|
var getAddressBalance = this.getAddressBalance
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/ChargeLinks',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
var i
|
||||||
|
var now = parseInt(new Date() / 1000)
|
||||||
|
for (i = 0; i < response.data.length; i++) {
|
||||||
|
timeleft = response.data[i].time_to_pay - (now - response.data[i].time)
|
||||||
|
if (timeleft < 1) {
|
||||||
|
response.data[i].timeleft = 0
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
response.data[i].timeleft = timeleft
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
self.ChargeLinks = response.data.map(function (obj) {
|
||||||
|
return mapCharge(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendFormDataCharge: function () {
|
||||||
|
var self = this
|
||||||
|
var wallet = self.g.user.wallets[0]
|
||||||
|
var data = self.formDialogCharge.data
|
||||||
|
data.amount = parseInt(data.amount)
|
||||||
|
data.time = parseInt(data.time)
|
||||||
|
if (data.id) {
|
||||||
|
this.updateCharge(wallet, data)
|
||||||
|
} else {
|
||||||
|
this.createCharge(wallet, data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCharge: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
'/satspay/api/v1/Charge/' + data.id,
|
||||||
|
wallet.inkey, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.Charge = _.reject(self.Charge, function (obj) {
|
||||||
|
return obj.id === data.id
|
||||||
|
})
|
||||||
|
self.Charge.push(mapCharge(response.data))
|
||||||
|
self.formDialogPayLink.show = false
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createCharge: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', '/satspay/api/v1/Charge', wallet.inkey, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.ChargeLinks.push(mapCharge(response.data))
|
||||||
|
self.formDialogCharge.show = false
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCharge: function (linkId) {
|
||||||
|
var self = this
|
||||||
|
var link = _.findWhere(this.Charge, {id: linkId})
|
||||||
|
console.log(self.g.user.wallets[0].adminkey)
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this pay link?')
|
||||||
|
.onOk(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/satspay/api/v1/Charge/' + linkId,
|
||||||
|
self.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.Charge = _.reject(self.Charge, function (obj) {
|
||||||
|
return obj.id === linkId
|
||||||
|
})})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWalletLink: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
'/satspay/api/v1/wallet/' + data.id,
|
||||||
|
wallet.inkey, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.walletLinks = _.reject(self.walletLinks, function (obj) {
|
||||||
|
return obj.id === data.id
|
||||||
|
})
|
||||||
|
self.walletLinks.push(mapWalletLink(response.data))
|
||||||
|
self.formDialog.show = false
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createWalletLink: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', '/satspay/api/v1/wallet', wallet.inkey, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.walletLinks.push(mapWalletLink(response.data))
|
||||||
|
self.formDialog.show = false
|
||||||
|
console.log(response.data[1][1])
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteWalletLink: function (linkId) {
|
||||||
|
var self = this
|
||||||
|
var link = _.findWhere(this.walletLinks, {id: linkId})
|
||||||
|
console.log(self.g.user.wallets[0].adminkey)
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this pay link?')
|
||||||
|
.onOk(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/satspay/api/v1/wallet/' + linkId,
|
||||||
|
self.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.walletLinks = _.reject(self.walletLinks, function (obj) {
|
||||||
|
return obj.id === linkId
|
||||||
|
})})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exportCSV: function () {
|
||||||
|
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
if (this.g.user.wallets.length) {
|
||||||
|
var getWalletLinks = this.getWalletLinks
|
||||||
|
getWalletLinks()
|
||||||
|
var getCharges = this.getCharges
|
||||||
|
getCharges()
|
||||||
|
var getMempool = this.getMempool
|
||||||
|
getMempool()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
21
lnbits/extensions/satspay/views.py
Normal file
21
lnbits/extensions/satspay/views.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
from quart import g, abort, render_template
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
||||||
|
from . import satspay_ext
|
||||||
|
from .crud import get_charge
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/")
|
||||||
|
@validate_uuids(["usr"], required=True)
|
||||||
|
@check_user_exists()
|
||||||
|
async def index():
|
||||||
|
return await render_template("satspay/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/<charge_id>")
|
||||||
|
async def display(charge_id):
|
||||||
|
link = get_payment(charge_id) or abort(HTTPStatus.NOT_FOUND, "Charge link does not exist.")
|
||||||
|
|
||||||
|
return await render_template("satspay/display.html", link=link)
|
||||||
157
lnbits/extensions/satspay/views_api.py
Normal file
157
lnbits/extensions/satspay/views_api.py
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import hashlib
|
||||||
|
from quart import g, jsonify, url_for
|
||||||
|
from http import HTTPStatus
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
from lnbits.core.crud import get_user
|
||||||
|
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||||
|
|
||||||
|
from lnbits.extensions.satspay import satspay_ext
|
||||||
|
from .crud import (
|
||||||
|
create_charge,
|
||||||
|
get_charge,
|
||||||
|
get_charges,
|
||||||
|
delete_charge,
|
||||||
|
)
|
||||||
|
|
||||||
|
###################WALLETS#############################
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/wallet", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_wallets_retrieve():
|
||||||
|
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
jsonify([wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)]), HTTPStatus.OK
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_wallet_retrieve(wallet_id):
|
||||||
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
|
if not wallet:
|
||||||
|
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
return jsonify({wallet}), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/wallet", methods=["POST"])
|
||||||
|
@satspay_ext.route("/api/v1/wallet/<wallet_id>", methods=["PUT"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"masterpub": {"type": "string", "empty": False, "required": True},
|
||||||
|
"title": {"type": "string", "empty": False, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def api_wallet_create_or_update(wallet_id=None):
|
||||||
|
print("g.data")
|
||||||
|
if not wallet_id:
|
||||||
|
wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"])
|
||||||
|
mempool = await get_mempool(g.wallet.user)
|
||||||
|
if not mempool:
|
||||||
|
create_mempool(user=g.wallet.user)
|
||||||
|
return jsonify(wallet._asdict()), HTTPStatus.CREATED
|
||||||
|
else:
|
||||||
|
wallet = await update_watch_wallet(wallet_id=wallet_id, **g.data)
|
||||||
|
return jsonify(wallet._asdict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/wallet/<wallet_id>", methods=["DELETE"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_wallet_delete(wallet_id):
|
||||||
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
|
if not wallet:
|
||||||
|
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
await delete_watch_wallet(wallet_id)
|
||||||
|
|
||||||
|
return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
#############################CHARGES##########################
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/charges", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_charges_retrieve():
|
||||||
|
|
||||||
|
charges = await get_charges(g.wallet.user)
|
||||||
|
if not charges:
|
||||||
|
return (
|
||||||
|
jsonify(""),
|
||||||
|
HTTPStatus.OK
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return jsonify([charge._asdict() for charge in charges]), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_charge_retrieve(charge_id):
|
||||||
|
charge = get_charge(charge_id)
|
||||||
|
|
||||||
|
if not charge:
|
||||||
|
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
return jsonify({charge}), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/charge", methods=["POST"])
|
||||||
|
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["PUT"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"walletid": {"type": "string", "empty": False, "required": True},
|
||||||
|
"title": {"type": "string", "empty": False, "required": True},
|
||||||
|
"time": {"type": "integer", "min": 1, "required": True},
|
||||||
|
"amount": {"type": "integer", "min": 1, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def api_charge_create_or_update(charge_id=None):
|
||||||
|
|
||||||
|
if not charge_id:
|
||||||
|
charge = await create_charge(user = g.wallet.user, **g.data)
|
||||||
|
return jsonify(charge), HTTPStatus.CREATED
|
||||||
|
|
||||||
|
else:
|
||||||
|
charge = await update_charge(user = g.wallet.user, **g.data)
|
||||||
|
return jsonify(charge), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["DELETE"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_charge_delete(charge_id):
|
||||||
|
charge = await get_watch_wallet(charge_id)
|
||||||
|
|
||||||
|
if not charge:
|
||||||
|
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
await delete_watch_wallet(charge_id)
|
||||||
|
|
||||||
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
#############################MEMPOOL##########################
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/mempool", methods=["PUT"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"endpoint": {"type": "string", "empty": False, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def api_update_mempool():
|
||||||
|
mempool = await update_mempool(user=g.wallet.user, **g.data)
|
||||||
|
return jsonify(mempool._asdict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
@satspay_ext.route("/api/v1/mempool", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_get_mempool():
|
||||||
|
mempool = await get_mempool(g.wallet.user)
|
||||||
|
if not mempool:
|
||||||
|
mempool = await create_mempool(user=g.wallet.user)
|
||||||
|
return jsonify(mempool._asdict()), HTTPStatus.OK
|
||||||
Loading…
Add table
Add a link
Reference in a new issue