Big, broken refactor payments/addresses to charges

This commit is contained in:
benarc 2020-12-04 09:59:42 +00:00
parent 257c9c1c64
commit 54c0c9f3a8
6 changed files with 142 additions and 281 deletions

View File

@ -2,7 +2,7 @@ from typing import List, Optional, Union
#from lnbits.db import open_ext_db
from . import db
from .models import Wallets, Payments, Addresses, Mempool
from .models import Wallets, charges, Addresses, Mempool
from lnbits.helpers import urlsafe_short_hash
@ -20,46 +20,7 @@ from embit import script
from embit import ec
from embit.networks import NETWORKS
from binascii import unhexlify, hexlify, a2b_base64, b2a_base64
########################ADDRESSES#######################
async def get_derive_address(wallet_id: str, num: int):
wallet = await get_watch_wallet(wallet_id)
k = bip32.HDKey.from_base58(str(wallet[2]))
child = k.derive([0, num])
address = script.p2wpkh(child).address()
return address
async def get_fresh_address(wallet_id: str) -> Addresses:
wallet = await get_watch_wallet(wallet_id)
address = await get_derive_address(wallet_id, wallet[4] + 1)
await update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1)
await db.execute(
"""
INSERT INTO addresses (
address,
wallet,
amount
)
VALUES (?, ?, ?)
""",
(address, wallet_id, 0),
)
return await get_address(address)
async def get_address(address: str) -> Addresses:
row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,))
return Addresses.from_row(row) if row else None
async def get_addresses(wallet_id: str) -> List[Addresses]:
rows = await db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,))
return [Addresses(**row) for row in rows]
import requests
##########################WALLETS####################
@ -74,7 +35,7 @@ async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Walle
masterpub,
title,
address_no,
amount
balance
)
VALUES (?, ?, ?, ?, ?, ?)
""",
@ -106,43 +67,56 @@ async def delete_watch_wallet(wallet_id: str) -> None:
await db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,))
###############PAYMENTS##########################
###############charges##########################
async def create_payment(*, walletid: str, user: str, title: str, time: str, amount: int) -> Payments:
async def create_charge(*, walletid: str, user: str, title: str, time: str, amount: int) -> charges:
address = await get_fresh_address(walletid)
payment_id = urlsafe_short_hash()
charge_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO payments (
INSERT INTO charges (
id,
user,
title,
wallet,
address,
time_to_pay,
amount
amount,
balance
)
VALUES (?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(payment_id, user, title, walletid, address.address, time, amount),
(charge_id, user, title, walletid, address.address, time, amount, 0),
)
return await get_payment(payment_id)
return await get_charge(charge_id)
async def get_payment(payment_id: str) -> Payments:
row = await db.fetchone("SELECT * FROM payments WHERE id = ?", (payment_id,))
return Payments.from_row(row) if row else None
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_payments(user: str) -> List[Payments]:
rows = await db.fetchall("SELECT * FROM payments WHERE user = ?", (user,))
return [Payments.from_row(row) for row in rows]
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_payment(payment_id: str) -> None:
await db.execute("DELETE FROM payments WHERE id = ?", (payment_id,))
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[Addresses]:
address_data = await get_address(address)
mempool = await get_mempool(address_data.user)
r = requests.get(mempool.endpoint + "/api/address/" + address)
amount_paid = r.json()['chain_stats']['funded_txo_sum'] - r.json()['chain_stats']['spent_txo_sum']
print(amount_paid)
await db.execute("UPDATE addresses SET amount_paid = ? WHERE address = ?", (amount_paid, address))
row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,))
return Addresses.from_row(row) if row else None
######################MEMPOOL#######################

View File

@ -10,24 +10,14 @@ async def m001_initial(db):
masterpub TEXT NOT NULL,
title TEXT NOT NULL,
address_no INTEGER NOT NULL DEFAULT 0,
amount INTEGER NOT NULL
balance INTEGER NOT NULL
);
"""
)
await db.execute(
"""
CREATE TABLE IF NOT EXISTS addresses (
address TEXT NOT NULL PRIMARY KEY,
wallet TEXT NOT NULL,
amount INTEGER NOT NULL
);
"""
)
await db.execute(
"""
CREATE TABLE IF NOT EXISTS payments (
CREATE TABLE IF NOT EXISTS charges (
id TEXT NOT NULL PRIMARY KEY,
user TEXT,
title TEXT,
@ -35,7 +25,7 @@ async def m001_initial(db):
address TEXT NOT NULL,
time_to_pay INTEGER NOT NULL,
amount INTEGER NOT NULL,
amount_paid INTEGER DEFAULT 0,
balance INTEGER DEFAULT 0,
time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
);
"""

View File

@ -7,13 +7,13 @@ class Wallets(NamedTuple):
masterpub: str
title: str
address_no: int
amount: int
balance: int
@classmethod
def from_row(cls, row: Row) -> "Wallets":
return cls(**dict(row))
class Payments(NamedTuple):
class Charges(NamedTuple):
id: str
user: str
wallet: str
@ -21,22 +21,13 @@ class Payments(NamedTuple):
address: str
time_to_pay: str
amount: int
amount_paid: int
balance: int
time: int
@classmethod
def from_row(cls, row: Row) -> "Payments":
return cls(**dict(row))
class Addresses(NamedTuple):
address: str
wallet: str
amount: int
@classmethod
def from_row(cls, row: Row) -> "Addresses":
return cls(**dict(row))
class Mempool(NamedTuple):
user: str
endpoint: str

View File

@ -89,11 +89,11 @@
size="xs"
icon="toll"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="formDialogPayment.show = true, formDialogPayment.data.walletid = props.row.id"
@click="formDialogCharge.show = true, formDialogCharge.data.walletid = props.row.id"
>
<q-tooltip>
Payment link
Charge link
</q-tooltip>
</q-btn>
<q-btn
@ -163,10 +163,10 @@
<q-table
flat
dense
:data="paymentLinks"
:data="ChargeLinks"
row-key="id"
:columns="PaymentsTable.columns"
:pagination.sync="PaymentsTable.pagination"
:columns="ChargesTable.columns"
:pagination.sync="ChargesTable.pagination"
:filter="filter"
>
@ -190,6 +190,33 @@
<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
@ -209,7 +236,6 @@
</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>
@ -217,7 +243,6 @@
</div>
</q-td>
</q-tr>
</template>
{% endraw %}
@ -299,14 +324,14 @@
</q-dialog>
<q-dialog v-model="formDialogPayment.show" position="top" @hide="closeFormDialog">
<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="sendFormDataPayment" class="q-gutter-md">
<q-form @submit="sendFormDataCharge" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialogPayment.data.title"
v-model.trim="formDialogCharge.data.title"
type="text"
label="Title"
></q-input>
@ -314,7 +339,7 @@
<q-input
filled
dense
v-model.trim="formDialogPayment.data.amount"
v-model.trim="formDialogCharge.data.amount"
type="number"
label="Amount (sats)"
></q-input>
@ -322,14 +347,14 @@
<q-input
filled
dense
v-model.trim="formDialogPayment.data.time"
v-model.trim="formDialogCharge.data.time"
type="number"
label="Time (secs)"
> </q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialogPayment.data.id"
v-if="formDialogCharge.data.id"
unelevated
color="deep-purple"
type="submit"
@ -340,8 +365,8 @@
unelevated
color="deep-purple"
:disable="
formDialogPayment.data.time == null ||
formDialogPayment.data.amount == null"
formDialogCharge.data.time == null ||
formDialogCharge.data.amount == null"
type="submit"
>Create Paylink</q-btn
>
@ -444,7 +469,7 @@
)
return obj
}
var mapPayment = function (obj) {
var mapCharge = function (obj) {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
@ -463,7 +488,7 @@
balance: null,
checker: null,
walletLinks: [],
paymentLinks: [],
ChargeLinks: [],
currentaddress: "",
Addresses: {
show: false,
@ -498,7 +523,7 @@
rowsPerPage: 10
}
},
PaymentsTable: {
ChargesTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{
@ -510,14 +535,14 @@
{
name: 'amount',
align: 'left',
label: 'Amount',
label: 'Amount to pay',
field: 'amount'
},
{
name: 'balance',
align: 'left',
label: 'Paid',
field: 'getAddressBalance("1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC")'
label: 'Balance',
field: 'amount_paid'
},
{
name: 'address',
@ -528,13 +553,13 @@
{
name: 'time to pay',
align: 'left',
label: 'Time to Pay (secs)',
label: 'Time to Pay',
field: 'time_to_pay'
},
{
name: 'timeleft',
align: 'left',
label: 'Time left (secs)',
label: 'Time left',
field: 'timeleft'
},
],
@ -542,30 +567,11 @@
rowsPerPage: 10
}
},
AddressTable: {
columns: [
{
name: 'address',
align: 'left',
label: 'Address',
field: 'address'
},
{
name: 'amount',
align: 'left',
label: 'Amount',
field: 'amount'
},
],
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
data: {}
},
formDialogPayment: {
formDialogCharge: {
show: false,
data: {}
},
@ -579,45 +585,7 @@
methods: {
getAddresses: function (walletID) {
var self = this
LNbits.api
.request(
'GET',
'/watchonly/api/v1/addresses/' + walletID,
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.Addresses.data = response.data
self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address
console.log(self.currentaddress)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
getFreshAddress: function (walletID) {
var self = this
LNbits.api
.request(
'GET',
'/watchonly/api/v1/address/' + walletID,
this.g.user.wallets[0].inkey
)
.then(function (response) {
console.log(response.data)
self.Addresses.data = response.data
self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address
})
},
addressRedirect: function (address){
chargeRedirect: function (address){
window.location.href = this.mempool.endpoint + "/address/" + address;
},
getMempool: function () {
@ -703,14 +671,14 @@
}
},
getPayments: function () {
getCharges: function () {
var self = this
var getAddressBalance = this.getAddressBalance
LNbits.api
.request(
'GET',
'/watchonly/api/v1/payment',
'/watchonly/api/v1/ChargeLinks',
this.g.user.wallets[0].inkey
)
.then(function (response) {
@ -724,67 +692,64 @@
else{
response.data[i].timeleft = timeleft
}
getAddressBalance("1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC")
console.log(this.balance)
response.data[i].balance = this.balance
}
self.paymentLinks = response.data.map(function (obj) {
return mapPayment(obj)
self.ChargeLinks = response.data.map(function (obj) {
return mapCharge(obj)
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
sendFormDataPayment: function () {
sendFormDataCharge: function () {
var self = this
var wallet = self.g.user.wallets[0]
var data = self.formDialogPayment.data
var data = self.formDialogCharge.data
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
if (data.id) {
this.updatePayment(wallet, data)
this.updateCharge(wallet, data)
} else {
this.createPayment(wallet, data)
this.createCharge(wallet, data)
}
},
updatePayment: function (wallet, data) {
updateCharge: function (wallet, data) {
var self = this
LNbits.api
.request(
'PUT',
'/watchonly/api/v1/payment/' + data.id,
'/watchonly/api/v1/Charge/' + data.id,
wallet.inkey, data)
.then(function (response) {
self.payment = _.reject(self.payment, function (obj) {
self.Charge = _.reject(self.Charge, function (obj) {
return obj.id === data.id
})
self.payment.push(mapPayment(response.data))
self.Charge.push(mapCharge(response.data))
self.formDialogPayLink.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
createPayment: function (wallet, data) {
createCharge: function (wallet, data) {
var self = this
LNbits.api
.request('POST', '/watchonly/api/v1/payment', wallet.inkey, data)
.request('POST', '/watchonly/api/v1/Charge', wallet.inkey, data)
.then(function (response) {
self.paymentLinks.push(mapPayment(response.data))
self.formDialogPayment.show = false
self.ChargeLinks.push(mapCharge(response.data))
self.formDialogCharge.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deletePayment: function (linkId) {
deleteCharge: function (linkId) {
var self = this
var link = _.findWhere(this.payment, {id: linkId})
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?')
@ -792,11 +757,11 @@
LNbits.api
.request(
'DELETE',
'/watchonly/api/v1/payment/' + linkId,
'/watchonly/api/v1/Charge/' + linkId,
self.g.user.wallets[0].inkey
)
.then(function (response) {
self.payment = _.reject(self.payment, function (obj) {
self.Charge = _.reject(self.Charge, function (obj) {
return obj.id === linkId
})})
.catch(function (error) {
@ -804,24 +769,7 @@
})
})
},
getAddressBalance: function (address) {
var self = this
LNbits.api
.request(
'GET',
'/watchonly/api/v1/mempool/' + address,
this.g.user.wallets[0].inkey
)
.then(function (response) {
this.balance = response.data.balance
console.log(this.balance)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
updateWalletLink: function (wallet, data) {
var self = this
@ -886,8 +834,8 @@
if (this.g.user.wallets.length) {
var getWalletLinks = this.getWalletLinks
getWalletLinks()
var getPayments = this.getPayments
getPayments()
var getCharges = this.getCharges
getCharges()
var getMempool = this.getMempool
getMempool()

View File

@ -14,7 +14,7 @@ async def index():
return await render_template("watchonly/index.html", user=g.user)
@watchonly_ext.route("/<payment_id>")
@watchonly_ext.route("/<charge_id>")
async def display(payment_id):
link = get_payment(payment_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")

View File

@ -2,7 +2,7 @@ import hashlib
from quart import g, jsonify, request, url_for
from http import HTTPStatus
import httpx
import requests
from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
@ -14,10 +14,10 @@ from .crud import (
get_watch_wallets,
update_watch_wallet,
delete_watch_wallet,
create_payment,
get_payment,
get_payments,
delete_payment,
create_charge,
get_charge,
get_charges,
delete_charge,
create_mempool,
update_mempool,
get_mempool,
@ -85,67 +85,35 @@ async def api_wallet_delete(wallet_id):
return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT
#############################ADDRESSES##########################
#############################CHARGES##########################
@watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"])
@watchonly_ext.route("/api/v1/charges", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_fresh_address(wallet_id):
await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id)
async def api_charges_retrieve():
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
@watchonly_ext.route("/api/v1/addresses/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_get_addresses(wallet_id):
print(wallet_id)
wallet = await get_watch_wallet(wallet_id)
if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
addresses = await get_addresses(wallet_id)
if not addresses:
await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id)
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
#############################PAYEMENTS##########################
@watchonly_ext.route("/api/v1/payment", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_payments_retrieve():
payments = await get_payments(g.wallet.user)
print(payments)
if not payments:
charges = await get_charges(g.wallet.user)
if not charges:
return (
jsonify(""),
HTTPStatus.OK
)
else:
return jsonify([payment._asdict() for payment in payments]), HTTPStatus.OK
return jsonify([charge._asdict() for charge in charges]), HTTPStatus.OK
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["GET"])
@watchonly_ext.route("/api/v1/charge/<charge_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_payment_retrieve(payment_id):
payment = get_payment(payment_id)
async def api_charge_retrieve(charge_id):
charge = get_charge(charge_id)
if not payment:
return jsonify({"message": "payment does not exist"}), HTTPStatus.NOT_FOUND
if not charge:
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
return jsonify({payment}), HTTPStatus.OK
return jsonify({charge}), HTTPStatus.OK
@watchonly_ext.route("/api/v1/payment", methods=["POST"])
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["PUT"])
@watchonly_ext.route("/api/v1/charge", methods=["POST"])
@watchonly_ext.route("/api/v1/charge/<charge_id>", methods=["PUT"])
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
@ -155,26 +123,26 @@ async def api_payment_retrieve(payment_id):
"amount": {"type": "integer", "min": 1, "required": True},
}
)
async def api_payment_create_or_update(payment_id=None):
async def api_charge_create_or_update(charge_id=None):
if not payment_id:
payment = await create_payment(user = g.wallet.user, **g.data)
return jsonify(payment), HTTPStatus.CREATED
if not charge_id:
charge = await create_charge(user = g.wallet.user, **g.data)
return jsonify(charge), HTTPStatus.CREATED
else:
payment = await update_payment(user = g.wallet.user, **g.data)
return jsonify(payment), HTTPStatus.OK
charge = await update_charge(user = g.wallet.user, **g.data)
return jsonify(charge), HTTPStatus.OK
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["DELETE"])
@watchonly_ext.route("/api/v1/charge/<charge_id>", methods=["DELETE"])
@api_check_wallet_key("invoice")
async def api_payment_delete(payment_id):
payment = await get_watch_wallet(payment_id)
async def api_charge_delete(charge_id):
charge = await get_watch_wallet(charge_id)
if not payment:
if not charge:
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
await delete_watch_wallet(payment_id)
await delete_watch_wallet(charge_id)
return "", HTTPStatus.NO_CONTENT
@ -197,14 +165,4 @@ 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
@watchonly_ext.route("/api/v1/mempool/<address>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_get_mempool_address_balance(address):
mempool = await get_mempool(g.wallet.user)
print(mempool.endpoint)
r = requests.get(mempool.endpoint + "/api/address/" + address)
balance = r.json()['chain_stats']['funded_txo_sum'] - r.json()['chain_stats']['spent_txo_sum']
return jsonify({"balance":balance}), HTTPStatus.OK
return jsonify(mempool._asdict()), HTTPStatus.OK