SatsPayServer works apart from updating charges

This commit is contained in:
Ben Arc 2021-04-07 22:51:33 +01:00
parent 90319bfd8c
commit 2d8f85f77c
8 changed files with 151 additions and 117 deletions

View File

@ -1,5 +1,5 @@
{
"name": "SatsPay Server",
"name": "SatsPayServer",
"short_description": "Create onchain and LN charges",
"icon": "payment",
"contributors": [

View File

@ -9,17 +9,19 @@ from lnbits.helpers import urlsafe_short_hash
from quart import jsonify
import httpx
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, update_watch_wallet
###############CHARGES##########################
async def create_charge(user: str, description: Optional[str] = None, onchainwallet: Optional[str] = None, lnbitswallet: Optional[str] = None, webhook: Optional[str] = None, time: Optional[int] = None, amount: Optional[int] = None) -> Charges:
async def create_charge(user: str, description: str = None, onchainwallet: Optional[str] = None, lnbitswallet: Optional[str] = None, webhook: Optional[str] = None, completelink: Optional[str] = None, completelinktext: Optional[str] = None, time: Optional[int] = None, amount: Optional[int] = None) -> Charges:
charge_id = urlsafe_short_hash()
if onchainwallet:
wallet = await get_watch_wallet(onchainwallet)
onchainaddress = await get_derive_address(onchainwallet, wallet[4] + 1)
onchainaddress = await get_derive_address(onchainwallet, int(wallet[4]) + 1)
await update_watch_wallet(wallet_id=onchainwallet, address_no=int(wallet[4]) + 1)
print(onchainaddress)
else:
onchainaddress = None
if lnbitswallet:
@ -42,14 +44,16 @@ async def create_charge(user: str, description: Optional[str] = None, onchainwal
payment_request,
payment_hash,
webhook,
completelink,
completelinktext,
time,
amount,
balance
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(charge_id, user, description, onchainwallet, onchainaddress, lnbitswallet,
payment_request, payment_hash, webhook, time, amount, 0),
payment_request, payment_hash, webhook, completelink, completelinktext, time, amount, 0),
)
return await get_charge(charge_id)
@ -77,7 +81,6 @@ async def delete_charge(charge_id: str) -> None:
async def check_address_balance(charge_id: str) -> List[Charges]:
charge = await get_charge(charge_id)
print(charge.balance)
if not charge.paid:
if charge.onchainaddress:
mempool = await get_mempool(charge.user)
@ -85,16 +88,13 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
async with httpx.AsyncClient() as client:
r = await client.get(mempool.endpoint + "/api/address/" + charge.onchainaddress)
respAmount = r.json()['chain_stats']['funded_txo_sum']
print(respAmount)
if respAmount >= charge.balance:
await update_charge(charge_id=charge_id, balance=respAmount)
except Exception:
pass
if charge.lnbitswallet:
invoice_status = await check_invoice_status(charge.lnbitswallet, charge.payment_hash)
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,))
return Charges.from_row(row) if row else None

View File

@ -15,6 +15,8 @@ async def m001_initial(db):
payment_request TEXT,
payment_hash TEXT,
webhook TEXT,
completelink TEXT,
completelinktext TEXT,
time INTEGER,
amount INTEGER,
balance INTEGER DEFAULT 0,

View File

@ -13,6 +13,8 @@ class Charges(NamedTuple):
payment_request: str
payment_hash: str
webhook: str
completelink: str
completelinktext: str
time: int
amount: int
balance: int

View File

@ -1,7 +1,9 @@
<q-card>
<q-card-section>
<p>
SatsPay: Create Onchain/LN charges. Includes webhooks!<br />
SatsPayServer, create Onchain/LN charges.<br />WARNING: If using with the
WatchOnly extension, we highly reccomend using a fresh extended public Key
specifically for SatsPayServer!<br />
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
>

View File

@ -81,6 +81,7 @@
</div>
<div v-else-if="charge_paid == 'True'">
<q-icon name="check" style="color:green; font-size: 21.4em;" ></q-icon>
<q-btn outline v-if="'{{ charge.webhook }}' != 'None'" type="a" href="{{ charge.completelink }}" label="{{ charge.completelinktext }}"></q-btn>
</div>
<div v-else>
<center>
@ -115,6 +116,7 @@
</div>
<div v-else-if="charge_paid == 'True'">
<q-icon name="check" style="color:green; font-size: 21.4em;" ></q-icon>
<q-btn outline v-if="'{{ charge.webhook }}' != 'None'" type="a" href="{{ charge.completelink }}" label="{{ charge.completelinktext }}"></q-btn>
</div>
<div v-else>
<center>
@ -217,7 +219,11 @@
timerCount: function () {
self = this
setInterval(function () {
var refreshIntervalId = setInterval(function () {
if(self.charge_paid == "True"){
console.log("did this work?")
clearInterval(refreshIntervalId)
}
self.getTheTime()
self.getThePercentage()
self.counter++
@ -228,6 +234,10 @@
}
},
created: function () {
if('{{ charge.lnbitswallet }}' == 'None'){
this.lnbtc = false
this.onbtc = true
}
this.getTheTime()
this.getThePercentage()
var timerCount = this.timerCount

View File

@ -104,11 +104,10 @@
size="xs"
icon="cached"
flat
@click="getBalance(props.row.id)"
:color="($q.dark.isActive) ? 'blue' : 'blue'"
>
<q-tooltip>
Check balance
Processing
</q-tooltip>
</q-btn>
<q-btn
@ -118,15 +117,23 @@
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
>
<q-tooltip>
Edit charge
</q-tooltip>
</q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteWalletLink(props.row.id)"
@click="deleteChargeLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
>
<q-tooltip>
Delete charge
</q-tooltip>
</q-btn>
</q-td>
@ -179,7 +186,7 @@
dense
v-model.trim="formDialogCharge.data.description"
type="text"
label="Description"
label="*Description"
></q-input>
<q-input
@ -187,7 +194,7 @@
dense
v-model.trim="formDialogCharge.data.amount"
type="number"
label="Amount (sats)"
label="*Amount (sats)"
></q-input>
<q-input
@ -196,7 +203,7 @@
v-model.trim="formDialogCharge.data.time"
type="number"
max="1440"
label="Mins valid for (max 1440)"
label="*Mins valid for (max 1440)"
> </q-input>
<q-input
@ -204,7 +211,22 @@
dense
v-model.trim="formDialogCharge.data.webhook"
type="url"
label="Webhook"
label="Webhook (URL to send transaction data to once paid)"
> </q-input>
<q-input
filled
dense
v-model.trim="formDialogCharge.data.completelink"
type="url"
label="Completed button URL"
> </q-input>
<q-input
filled
dense
v-model.trim="formDialogCharge.data.completelinktext"
type="text"
label="Completed button text (ie 'Back to merchant')"
> </q-input>
<div class="row">
@ -263,9 +285,9 @@
formDialogCharge.data.time == null ||
formDialogCharge.data.amount == null"
type="submit"
>Create Paylink</q-btn
>Create Charge</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
<q-btn @click="cancelCharge" flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
@ -283,30 +305,9 @@
<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 = obj => {
obj._data = _.clone(obj)
obj.theTime = (obj.time * 60) - (Date.now()/1000 - obj.timestamp)
console.log(obj.theTime)
obj.time = obj.time + "mins"
if(obj.time_elapsed){
@ -318,7 +319,6 @@
'HH:mm:ss'
)}
obj.displayUrl = ['/satspay/', obj.id].join('')
console.log(obj.date)
return obj
}
@ -396,6 +396,18 @@
label: 'LNbits wallet',
field: 'lnbitswallet'
},
{
name: 'Webhook link',
align: 'left',
label: 'Webhook link',
field: 'webhook'
},
{
name: 'Paid link',
align: 'left',
label: 'Paid link',
field: 'completelink'
},
],
pagination: {
rowsPerPage: 10
@ -407,7 +419,11 @@
},
formDialogCharge: {
show: false,
data: {onchain: false,lnbits:false}
data: {
onchain: false,
lnbits:false,
description: ""
}
},
qrCodeDialog: {
show: false,
@ -416,6 +432,17 @@
}
},
methods: {
cancelCharge: function (data) {
var self = this
self.formDialogCharge.data.description = ""
self.formDialogCharge.data.onchainwallet = ""
self.formDialogCharge.data.lnbitswallet = ""
self.formDialogCharge.data.time = null
self.formDialogCharge.data.amount = null
self.formDialogCharge.data.webhook = ""
self.formDialogCharge.data.completelink = ""
self.formDialogCharge.show = false
},
getWalletLinks: function () {
var self = this
@ -464,7 +491,6 @@
this.createWalletLink(wallet, data)
}
},
getCharges: function () {
var self = this
var getAddressBalance = this.getAddressBalance
@ -476,7 +502,6 @@
this.g.user.wallets[0].inkey
)
.then(function (response) {
console.log(response.data[0].time_elapsed)
self.ChargeLinks = response.data.map(mapCharge)
})
.catch(function (error) {
@ -485,8 +510,8 @@
},
sendFormDataCharge: function () {
var self = this
var wallet = self.g.user.wallets[0].adminkey
var data = self.formDialogCharge.data
var wallet = this.g.user.wallets[0].adminkey
var data = this.formDialogCharge.data
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
if (data.id) {
@ -494,9 +519,6 @@
} else {
this.createCharge(wallet, data)
}
this.getCharges()
this.formDialogCharge.show = false
this.formDialogCharge.data = null
},
updateCharge: function (wallet, data) {
var self = this
@ -517,20 +539,25 @@
LNbits.utils.notifyApiError(error)
})
},
getBalance: function (walletId) {
var self = this
timerCount: function () {
self = this
var refreshIntervalId = setInterval(function () {
for (i = 0; i < self.ChargeLinks.length - 1; i++) {
if(self.ChargeLinks[i]["paid"] == 'True'){
setTimeout(function(){
LNbits.api
.request(
'GET',
'/satspay/api/v1/charges/balance/' + walletId,
this.g.user.wallets[0].inkey
'/satspay/api/v1/charges/balance/' + self.ChargeLinks[i]["id"],
"filla"
)
.then(function (response) {
this.ChargeLinks = response.data.map(mapCharge)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, 2000);
}
}
self.getCharges()
}, 20000)
},
createCharge: function (wallet, data) {
var self = this
@ -538,10 +565,8 @@
LNbits.api
.request('POST', '/satspay/api/v1/charge', wallet, data)
.then(function (response) {
this.formDialogCharge.show = false
this.formDialogCharge.data = null
self.ChargeLinks.push(mapCharge(response.data))
self.formDialogCharge.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@ -570,53 +595,21 @@
})
},
updateWalletLink: function (wallet, data) {
deleteChargeLink: function (chargeId) {
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
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteWalletLink: function (linkId) {
var self = this
var link = _.findWhere(this.walletLinks, {id: linkId})
var link = _.findWhere(this.chargeLinks, {id: chargeId})
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
'/satspay/api/v1/charge/' + chargeId,
self.g.user.wallets[0].adminkey
)
.then(function (response) {
self.walletLinks = _.reject(self.walletLinks, function (obj) {
return obj.id === linkId
self.chargeLinks = _.reject(self.chargeLinks, function (obj) {
return obj.id === chargeId
})})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@ -624,7 +617,7 @@
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
LNbits.utils.exportCSV(this.ChargesTable.columns, this.chargeLinks)
},
},
@ -634,6 +627,8 @@
getCharges()
var getWalletLinks = this.getWalletLinks
getWalletLinks()
var timerCount = this.timerCount
timerCount()
}
})
</script>

View File

@ -28,7 +28,9 @@ from .crud import (
"onchainwallet": {"type": "string"},
"lnbitswallet": {"type": "string"},
"description": {"type": "string", "empty": False, "required": True},
"webhook": {"type": "string", "empty": False, "required": True},
"webhook": {"type": "string"},
"completelink": {"type": "string"},
"completelinktext": {"type": "string"},
"time": {"type": "integer", "min": 1, "required": True},
"amount": {"type": "integer", "min": 1, "required": True},
}
@ -85,15 +87,36 @@ async def api_charges_balance(charge_id):
if not charge:
return jsonify({"message": "charge does not exist"}), HTTPStatus.NOT_FOUND
else:
if charge.paid and charge.webhook:
async with httpx.AsyncClient() as client:
try:
r = await client.post(
charge.webhook,
json={
"id": charge.id,
"description": charge.description,
"onchainaddress": charge.onchainaddress,
"payment_request": charge.payment_request,
"payment_hash": charge.payment_hash,
"time": charge.time,
"amount": charge.amount,
"balance": charge.balance,
"paid": charge.paid,
"timestamp": charge.timestamp,
"completelink": charge.completelink,
},
timeout=40,
)
except AssertionError:
charge.webhook = None
return jsonify(charge._asdict()), HTTPStatus.OK
#############################MEMPOOL##########################
@satspay_ext.route("/api/v1/mempool", methods=["PUT"])
@api_check_wallet_key("invoice")
@api_validate_post_request(
@ 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},
}
@ -103,8 +126,8 @@ async def api_update_mempool():
return jsonify(mempool._asdict()), HTTPStatus.OK
@satspay_ext.route("/api/v1/mempool", methods=["GET"])
@api_check_wallet_key("invoice")
@ 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: