mirror of
https://github.com/lnbits/lnbits.git
synced 2025-10-10 20:42:32 +02:00
Satspay mostly working
This commit is contained in:
@@ -11,7 +11,6 @@ import httpx
|
|||||||
from lnbits.core.services import create_invoice, check_invoice_status
|
from lnbits.core.services import create_invoice, check_invoice_status
|
||||||
from ..watchonly.crud import get_watch_wallet, get_derive_address, get_mempool
|
from ..watchonly.crud import get_watch_wallet, get_derive_address, get_mempool
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
###############CHARGES##########################
|
###############CHARGES##########################
|
||||||
|
|
||||||
@@ -45,13 +44,12 @@ async def create_charge(user: str, description: Optional[str] = None, onchainwal
|
|||||||
webhook,
|
webhook,
|
||||||
time,
|
time,
|
||||||
amount,
|
amount,
|
||||||
balance,
|
balance
|
||||||
paid
|
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(charge_id, user, description, onchainwallet, onchainaddress, lnbitswallet,
|
(charge_id, user, description, onchainwallet, onchainaddress, lnbitswallet,
|
||||||
payment_request, payment_hash, webhook, time, amount, 0, False),
|
payment_request, payment_hash, webhook, time, amount, 0),
|
||||||
)
|
)
|
||||||
return await get_charge(charge_id)
|
return await get_charge(charge_id)
|
||||||
|
|
||||||
@@ -79,21 +77,24 @@ async def delete_charge(charge_id: str) -> None:
|
|||||||
|
|
||||||
async def check_address_balance(charge_id: str) -> List[Charges]:
|
async def check_address_balance(charge_id: str) -> List[Charges]:
|
||||||
charge = await get_charge(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
if charge.onchainaddress:
|
print(charge.balance)
|
||||||
mempool = await get_mempool(charge.user)
|
if not charge.paid:
|
||||||
try:
|
if charge.onchainaddress:
|
||||||
async with httpx.AsyncClient() as client:
|
mempool = await get_mempool(charge.user)
|
||||||
r = await client.get(mempool.endpoint + "/api/address/" + charge.onchainaddress)
|
try:
|
||||||
respAmount = r.json()['chain_stats']['funded_txo_sum']
|
async with httpx.AsyncClient() as client:
|
||||||
if (charge.balance + respAmount) >= charge.balance:
|
r = await client.get(mempool.endpoint + "/api/address/" + charge.onchainaddress)
|
||||||
return await update_charge(charge_id=charge_id, balance=(charge.balance + respAmount), paid=True)
|
respAmount = r.json()['chain_stats']['funded_txo_sum']
|
||||||
else:
|
print(respAmount)
|
||||||
return await update_charge(charge_id=charge_id, balance=(charge.balance + respAmount), paid=False)
|
if respAmount >= charge.balance:
|
||||||
except Exception:
|
await update_charge(charge_id=charge_id, balance=respAmount)
|
||||||
pass
|
except Exception:
|
||||||
if charge.lnbitswallet:
|
pass
|
||||||
invoice_status = await check_invoice_status(charge.lnbitswallet, charge.payment_hash)
|
if charge.lnbitswallet:
|
||||||
if invoice_status.paid:
|
invoice_status = await check_invoice_status(charge.lnbitswallet, charge.payment_hash)
|
||||||
return await update_charge(charge_id=charge_id, balance=charge.balance, paid=True)
|
print(invoice_status)
|
||||||
|
if invoice_status.paid:
|
||||||
|
print("paid")
|
||||||
|
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
||||||
row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (charge_id,))
|
row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (charge_id,))
|
||||||
return Charges.from_row(row) if row else None
|
return Charges.from_row(row) if row else None
|
||||||
|
@@ -3,7 +3,6 @@ async def m001_initial(db):
|
|||||||
Initial wallet table.
|
Initial wallet table.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS charges (
|
CREATE TABLE IF NOT EXISTS charges (
|
||||||
@@ -19,7 +18,6 @@ async def m001_initial(db):
|
|||||||
time INTEGER,
|
time INTEGER,
|
||||||
amount INTEGER,
|
amount INTEGER,
|
||||||
balance INTEGER DEFAULT 0,
|
balance INTEGER DEFAULT 0,
|
||||||
paid BOOLEAN,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
|
timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
class Charges(NamedTuple):
|
class Charges(NamedTuple):
|
||||||
@@ -12,12 +13,25 @@ class Charges(NamedTuple):
|
|||||||
payment_request: str
|
payment_request: str
|
||||||
payment_hash: str
|
payment_hash: str
|
||||||
webhook: str
|
webhook: str
|
||||||
time: str
|
time: int
|
||||||
amount: int
|
amount: int
|
||||||
balance: int
|
balance: int
|
||||||
paid: bool
|
|
||||||
timestamp: int
|
timestamp: int
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
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_elapsed(self):
|
||||||
|
if (self.timestamp + (self.time * 60)) >= time.time():
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def paid(self):
|
||||||
|
if self.balance >= self.amount:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
@@ -117,80 +117,6 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Create a charge 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": <invoice_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>
|
|
||||||
{ "deliveryId": <string>, "description": <string>,
|
|
||||||
"webhookId": <string>, "originalDeliveryId": <string>,
|
|
||||||
"isRedelivery": <boolean>, "type": <string>,
|
|
||||||
"timestamp": <int>, "paytime": <int>, "storeId":
|
|
||||||
<string>, "invoiceId": <string>, "manuallyMarked":
|
|
||||||
<boolean>, "overPaid": <boolean>, "afterExpiration":
|
|
||||||
<boolean>, "partiallyPaid": <boolean> }<br /><small
|
|
||||||
>"type" can be InvoiceReceivedPayment, InvoicePaidInFull,
|
|
||||||
InvoiceExpired, InvoiceConfirmed, and InvoiceInvalid</small
|
|
||||||
>
|
|
||||||
</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
|
<q-expansion-item
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
@@ -202,7 +128,7 @@
|
|||||||
<q-card-section>
|
<q-card-section>
|
||||||
<code
|
<code
|
||||||
><span class="text-pink">DELETE</span>
|
><span class="text-pink">DELETE</span>
|
||||||
/pay/api/v1/links/<pay_id></code
|
/satspay/api/v1/charge/<charge_id></code
|
||||||
>
|
>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||||
@@ -211,11 +137,36 @@
|
|||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X DELETE {{ request.url_root
|
>curl -X DELETE {{ request.url_root
|
||||||
}}pay/api/v1/links/<pay_id> -H "X-Api-Key: {{
|
}}api/v1/charge/<charge_id> -H "X-Api-Key: {{
|
||||||
g.user.wallets[0].adminkey }}"
|
g.user.wallets[0].adminkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Get balances">
|
||||||
|
<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">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 GET {{ request.url_root
|
||||||
|
}}api/v1/charges/balance/<charge_id> -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].inkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@@ -1,74 +1,241 @@
|
|||||||
{% extends "public.html" %} {% block page %}
|
{% extends "public.html" %} {% block page %}
|
||||||
|
<div class="q-pa-sm theCard">
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-card class="my-card">
|
<q-card class="my-card">
|
||||||
<q-card-section>
|
<div class="column">
|
||||||
<div class="text-h6">Our Changing Planet</div>
|
<center> <div class="col theHeading">{{ charge.description }}</div></center>
|
||||||
<div class="text-subtitle2">by John Doe</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-tabs v-model="tab" class="text-teal">
|
<div class="col">
|
||||||
<q-tab label="Tab one" name="one" />
|
<div class="col" color="white" style="background-color:grey; height: 30px; padding: 5px" v-if="charge_time_elapsed == 'True'">
|
||||||
<q-tab label="Tab two" name="two" />
|
<center>Time elapsed</center>
|
||||||
</q-tabs>
|
</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-separator />
|
<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-section>
|
||||||
|
</q-item>
|
||||||
|
</q-linear-progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-tab-panels v-model="tab" animated>
|
|
||||||
<q-tab-panel name="Lightning">
|
<div class="col" style="margin: 2px 15px; max-height:100px" >
|
||||||
<div class="text-center">
|
<center>
|
||||||
<a href="lightning:{{ charge.id }}">
|
<q-btn flat dense outline @click="copyText('{{ charge.id }}')"
|
||||||
|
>Charge ID: {{ charge.id }}</q-btn
|
||||||
|
>
|
||||||
|
</center>
|
||||||
|
<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 onchain 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 class="col">
|
||||||
|
<q-btn flat disable v-if="'{{ charge.onchainwallet }}' == 'None' || charge_time_elapsed == 'True'" style="color: primary; width: 100%" label="onchain⛓️" >
|
||||||
|
<q-tooltip>
|
||||||
|
bitcoin lightning 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="charge_time_elapsed == 'True' && 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>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<center>
|
||||||
|
<span class="text-subtitle2">Pay this <br/> lightning-network invoice</span>
|
||||||
|
</center>
|
||||||
|
<a href="lightning://{{ charge.payment_request }}">
|
||||||
<q-responsive :ratio="1" class="q-mx-md">
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
<qrcode
|
<qrcode
|
||||||
value="{{ charge.id }}"
|
:value="'{{ charge.payment_request }}'"
|
||||||
:options="{width: 300}"
|
:options="{width: 800}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode>
|
></qrcode>
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn outline color="grey" @click="copyText('{{ charge.id }}')"
|
<q-btn outline color="grey" @click="copyText('{{ charge.payment_request }}')"
|
||||||
>Copy address</q-btn
|
>Copy invoice</q-btn
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</q-tab-panel>
|
</div></div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
<q-tab-panel name="Onchain">
|
|
||||||
<div class="text-center">
|
<q-card class="q-pa-lg" v-if="onbtc">
|
||||||
<a href="lightning:{{ charge.id }}">
|
<q-card-section class="q-pa-none">
|
||||||
|
|
||||||
|
<div class="text-center q-pt-md">
|
||||||
|
<div v-if="charge_time_elapsed == 'True' && 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>
|
||||||
|
</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">
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
<qrcode
|
<qrcode
|
||||||
value="{{ charge.id }}"
|
:value="'{{ charge.onchainaddress }}'"
|
||||||
:options="{width: 300}"
|
:options="{width: 800}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode>
|
></qrcode>
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn outline color="grey" @click="copyText('{{ charge.id }}')"
|
<q-btn outline color="grey" @click="copyText('{{ charge.onchainaddress }}')"
|
||||||
>Copy address</q-btn
|
>Copy address</q-btn
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</q-tab-panel>
|
</div></div>
|
||||||
</q-tab-panels>
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<style>
|
||||||
|
.theCard {
|
||||||
|
width: 360px;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
.theHeading {
|
||||||
|
margin: 15px;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin]
|
mixins: [windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
newProgress: 0.4,
|
||||||
|
counter: 1,
|
||||||
|
newTimeLeft: '',
|
||||||
|
lnbtc: true,
|
||||||
|
onbtc: false,
|
||||||
|
charge_time_elapsed: '{{charge.time_elapsed}}',
|
||||||
|
charge_amount: '{{charge.amount}}',
|
||||||
|
charge_balance: '{{charge.balance}}',
|
||||||
|
charge_paid: '{{charge.paid}}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkBalance: function () {
|
||||||
|
var self = this
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/charges/balance/{{ charge.id }}',
|
||||||
|
"filla"
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log(response.data)
|
||||||
|
self.charge_time_elapsed = response.data.time_elapsed
|
||||||
|
self.charge_amount = response.data.amount
|
||||||
|
self.charge_balance = response.data.balance
|
||||||
|
if (self.charge_balance >= self.charge_amount ){
|
||||||
|
self.charge_paid = "True"
|
||||||
|
}
|
||||||
|
console.log(self.charge_paid)
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
payLN: function(){
|
||||||
|
this.lnbtc = true
|
||||||
|
this.onbtc = false
|
||||||
|
},
|
||||||
|
payON: function(){
|
||||||
|
this.lnbtc = false
|
||||||
|
this.onbtc = true
|
||||||
|
},
|
||||||
|
getTheTime: function () {
|
||||||
|
var timeToComplete = (parseInt('{{ charge.time }}') * 60) - (Date.now()/1000 - parseInt('{{ charge.timestamp }}') )
|
||||||
|
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 () {
|
||||||
|
self = this
|
||||||
|
setInterval(function () {
|
||||||
|
self.getTheTime()
|
||||||
|
self.getThePercentage()
|
||||||
|
self.counter++
|
||||||
|
if (self.counter% 10 === 0){
|
||||||
|
self.checkBalance()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.getTheTime()
|
||||||
|
this.getThePercentage()
|
||||||
|
var timerCount = this.timerCount
|
||||||
|
console.log('{{ charge.paid }}')
|
||||||
|
if('{{ charge.paid }}' == 'False'){
|
||||||
|
timerCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -64,33 +64,53 @@
|
|||||||
type="a"
|
type="a"
|
||||||
:href="props.row.displayUrl"
|
:href="props.row.displayUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
></q-btn>
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
Payment link
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
|
|
||||||
<q-icon v-if="props.row.timeleft < 1 && props.row.amount_paid < props.row.amount"
|
<q-btn v-if="props.row.time_elapsed && props.row.balance < props.row.amount"
|
||||||
#unelevated
|
unelevated
|
||||||
|
flat
|
||||||
dense
|
dense
|
||||||
size="xs"
|
size="xs"
|
||||||
name="error"
|
icon="error"
|
||||||
:color="($q.dark.isActive) ? 'red' : 'red'"
|
:color="($q.dark.isActive) ? 'red' : 'red'"
|
||||||
></q-icon>
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
Time elapsed
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
<q-icon v-else-if="props.row.balance > props.row.amount"
|
<q-btn v-else-if="props.row.balance >= props.row.amount"
|
||||||
#unelevated
|
unelevated
|
||||||
|
flat
|
||||||
dense
|
dense
|
||||||
size="xs"
|
size="xs"
|
||||||
name="check"
|
icon="check"
|
||||||
:color="($q.dark.isActive) ? 'green' : 'green'"
|
:color="($q.dark.isActive) ? 'green' : 'green'"
|
||||||
></q-icon>
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
PAID!
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
<q-icon v-else="props.row.amount_paid < props.row.amount && props.row.timeleft > 1"
|
<q-btn v-else
|
||||||
#unelevated
|
unelevated
|
||||||
dense
|
dense
|
||||||
size="xs"
|
size="xs"
|
||||||
name="cached"
|
icon="cached"
|
||||||
|
flat
|
||||||
|
@click="getBalance(props.row.id)"
|
||||||
:color="($q.dark.isActive) ? 'blue' : 'blue'"
|
:color="($q.dark.isActive) ? 'blue' : 'blue'"
|
||||||
></q-icon>
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
Check balance
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
@@ -175,7 +195,8 @@
|
|||||||
dense
|
dense
|
||||||
v-model.trim="formDialogCharge.data.time"
|
v-model.trim="formDialogCharge.data.time"
|
||||||
type="number"
|
type="number"
|
||||||
label="Time (secs)"
|
max="1440"
|
||||||
|
label="Mins valid for (max 1440)"
|
||||||
> </q-input>
|
> </q-input>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
@@ -281,26 +302,27 @@
|
|||||||
)
|
)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
var mapCharge = function (obj) {
|
|
||||||
obj._data = _.clone(obj)
|
var mapCharge = obj => {
|
||||||
obj.theDate = ( obj.time + obj.timestamp - ((Date.now()/1000)))
|
obj._data = _.clone(obj)
|
||||||
console.log(obj.theDate)
|
obj.theTime = (obj.time * 60) - (Date.now()/1000 - obj.timestamp)
|
||||||
if(obj.theDate < 0){
|
console.log(obj.theTime)
|
||||||
|
obj.time = obj.time + "mins"
|
||||||
|
|
||||||
|
if(obj.time_elapsed){
|
||||||
obj.date = "Time elapsed"
|
obj.date = "Time elapsed"
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
obj.date = Quasar.utils.date.formatDate(
|
||||||
|
new Date((obj.theTime - 3600)*1000),
|
||||||
new Date(obj.theDate * 1000),
|
|
||||||
'HH:mm:ss'
|
'HH:mm:ss'
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
obj.displayUrl = ['/satspay/', obj.id].join('')
|
obj.displayUrl = ['/satspay/', obj.id].join('')
|
||||||
|
console.log(obj.date)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
@@ -326,8 +348,8 @@
|
|||||||
ChargesTable: {
|
ChargesTable: {
|
||||||
columns: [
|
columns: [
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'theId',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'ID',
|
label: 'ID',
|
||||||
field: 'id'
|
field: 'id'
|
||||||
@@ -344,6 +366,12 @@
|
|||||||
label: 'Time left',
|
label: 'Time left',
|
||||||
field: 'date'
|
field: 'date'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'time to pay',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Time to Pay',
|
||||||
|
field: 'time'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'amount',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
@@ -356,12 +384,6 @@
|
|||||||
label: 'Balance',
|
label: 'Balance',
|
||||||
field: 'balance'
|
field: 'balance'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'time to pay',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Time to Pay',
|
|
||||||
field: 'time'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'onchain address',
|
name: 'onchain address',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
@@ -394,11 +416,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateCountdowns: function () {
|
|
||||||
setInterval(function(){
|
|
||||||
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
|
|
||||||
getWalletLinks: function () {
|
getWalletLinks: function () {
|
||||||
var self = this
|
var self = this
|
||||||
@@ -410,13 +427,9 @@
|
|||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
console.log(response.data)
|
|
||||||
for (i = 0; i < response.data.length; i++) {
|
for (i = 0; i < response.data.length; i++) {
|
||||||
self.walletLinks.push(response.data[i].id)
|
self.walletLinks.push(response.data[i].id)
|
||||||
}
|
}
|
||||||
console.log(self.walletLinks)
|
|
||||||
|
|
||||||
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
@@ -463,25 +476,8 @@
|
|||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
console.log(response.data)
|
console.log(response.data[0].time_elapsed)
|
||||||
var i
|
self.ChargeLinks = response.data.map(mapCharge)
|
||||||
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) {
|
.catch(function (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
@@ -491,7 +487,6 @@
|
|||||||
var self = this
|
var self = this
|
||||||
var wallet = self.g.user.wallets[0].adminkey
|
var wallet = self.g.user.wallets[0].adminkey
|
||||||
var data = self.formDialogCharge.data
|
var data = self.formDialogCharge.data
|
||||||
console.log(data)
|
|
||||||
data.amount = parseInt(data.amount)
|
data.amount = parseInt(data.amount)
|
||||||
data.time = parseInt(data.time)
|
data.time = parseInt(data.time)
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
@@ -509,7 +504,7 @@
|
|||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
'/satspay/api/v1/Charge/' + data.id,
|
'/satspay/api/v1/charge/' + data.id,
|
||||||
wallet.adminkey, data)
|
wallet.adminkey, data)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
self.Charge = _.reject(self.Charge, function (obj) {
|
self.Charge = _.reject(self.Charge, function (obj) {
|
||||||
@@ -531,7 +526,7 @@
|
|||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
console.log(response.data)
|
this.ChargeLinks = response.data.map(mapCharge)
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
@@ -556,14 +551,13 @@
|
|||||||
deleteCharge: function (linkId) {
|
deleteCharge: function (linkId) {
|
||||||
var self = this
|
var self = this
|
||||||
var link = _.findWhere(this.Charge, {id: linkId})
|
var link = _.findWhere(this.Charge, {id: linkId})
|
||||||
console.log(self.g.user.wallets[0].adminkey)
|
|
||||||
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(function () {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'/satspay/api/v1/Charge/' + linkId,
|
'/satspay/api/v1/charge/' + linkId,
|
||||||
self.g.user.wallets[0].inkey
|
self.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
@@ -603,7 +597,6 @@
|
|||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
self.walletLinks.push(mapWalletLink(response.data))
|
self.walletLinks.push(mapWalletLink(response.data))
|
||||||
self.formDialog.show = false
|
self.formDialog.show = false
|
||||||
console.log(response.data[1][1])
|
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
@@ -612,7 +605,6 @@
|
|||||||
deleteWalletLink: function (linkId) {
|
deleteWalletLink: function (linkId) {
|
||||||
var self = this
|
var self = this
|
||||||
var link = _.findWhere(this.walletLinks, {id: linkId})
|
var link = _.findWhere(this.walletLinks, {id: linkId})
|
||||||
console.log(self.g.user.wallets[0].adminkey)
|
|
||||||
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(function () {
|
||||||
@@ -642,15 +634,6 @@
|
|||||||
getCharges()
|
getCharges()
|
||||||
var getWalletLinks = this.getWalletLinks
|
var getWalletLinks = this.getWalletLinks
|
||||||
getWalletLinks()
|
getWalletLinks()
|
||||||
var getBalance = this.getBalance
|
|
||||||
setTimeout(function(){
|
|
||||||
for (i = 0; i < self.ChargeLinks.length; i++) {
|
|
||||||
getBalance(self.ChargeLinks[i].id)
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
getCharges()
|
|
||||||
var updateCountdowns = this.updateCountdowns
|
|
||||||
updateCountdowns()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from quart import g, abort, render_template
|
from quart import g, abort, render_template, jsonify
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
@@ -16,6 +16,6 @@ async def index():
|
|||||||
|
|
||||||
@satspay_ext.route("/<charge_id>")
|
@satspay_ext.route("/<charge_id>")
|
||||||
async def display(charge_id):
|
async def display(charge_id):
|
||||||
charge = get_charge(charge_id) or abort(HTTPStatus.NOT_FOUND, "Charge link does not exist.")
|
charge = await get_charge(charge_id) or abort(
|
||||||
|
HTTPStatus.NOT_FOUND, "Charge link does not exist.")
|
||||||
return await render_template("satspay/display.html", charge=charge)
|
return await render_template("satspay/display.html", charge=charge)
|
@@ -46,38 +46,32 @@ async def api_charge_create_or_update(charge_id=None):
|
|||||||
@satspay_ext.route("/api/v1/charges", methods=["GET"])
|
@satspay_ext.route("/api/v1/charges", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
async def api_charges_retrieve():
|
async def api_charges_retrieve():
|
||||||
|
try:
|
||||||
charges = await get_charges(g.wallet.user)
|
return jsonify([{**charge._asdict(), **{"time_elapsed": charge.time_elapsed}, **{"paid": charge.paid}}for charge in await get_charges(g.wallet.user)]), HTTPStatus.OK
|
||||||
if not charges:
|
except:
|
||||||
return (
|
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"])
|
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
async def api_charge_retrieve(charge_id):
|
async def api_charge_retrieve(charge_id):
|
||||||
charge = await get_charge(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
print(charge)
|
|
||||||
|
|
||||||
if not charge:
|
if not charge:
|
||||||
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
|
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
return jsonify(charge._asdict()), HTTPStatus.OK
|
return jsonify({**charge._asdict(), **{"time_elapsed": charge.time_elapsed}, **{"paid": charge.paid}}), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["DELETE"])
|
@satspay_ext.route("/api/v1/charge/<charge_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
async def api_charge_delete(charge_id):
|
async def api_charge_delete(charge_id):
|
||||||
charge = await get_watch_wallet(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
|
|
||||||
if not charge:
|
if not charge:
|
||||||
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
await delete_watch_wallet(charge_id)
|
await delete_charge(charge_id)
|
||||||
|
|
||||||
return "", HTTPStatus.NO_CONTENT
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
@@ -85,15 +79,12 @@ async def api_charge_delete(charge_id):
|
|||||||
#############################BALANCE##########################
|
#############################BALANCE##########################
|
||||||
|
|
||||||
@satspay_ext.route("/api/v1/charges/balance/<charge_id>", methods=["GET"])
|
@satspay_ext.route("/api/v1/charges/balance/<charge_id>", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
|
||||||
async def api_charges_balance(charge_id):
|
async def api_charges_balance(charge_id):
|
||||||
|
|
||||||
charge = await check_address_balance(charge_id)
|
charge = await check_address_balance(charge_id)
|
||||||
|
|
||||||
if not charge:
|
if not charge:
|
||||||
return (
|
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
jsonify(""),
|
|
||||||
HTTPStatus.OK
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return jsonify(charge._asdict()), HTTPStatus.OK
|
return jsonify(charge._asdict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user