SatsPayServer works apart from updating charges
This commit is contained in:
parent
90319bfd8c
commit
2d8f85f77c
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "SatsPay Server",
|
||||
"name": "SatsPayServer",
|
||||
"short_description": "Create onchain and LN charges",
|
||||
"icon": "payment",
|
||||
"contributors": [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,9 +211,24 @@
|
|||
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">
|
||||
<div class="col">
|
||||
<div v-if="walletLinks.length > 0">
|
||||
|
@ -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
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/satspay/api/v1/charges/balance/' + walletId,
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
this.ChargeLinks = response.data.map(mapCharge)
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
timerCount: function () {
|
||||
self = this
|
||||
var refreshIntervalId = setInterval(function () {
|
||||
for (i = 0; i < self.ChargeLinks.length - 1; i++) {
|
||||
if(self.ChargeLinks[i]["paid"] == 'True'){
|
||||
setTimeout(function(){
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/satspay/api/v1/charges/balance/' + self.ChargeLinks[i]["id"],
|
||||
"filla"
|
||||
)
|
||||
.then(function (response) {
|
||||
})
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
self.getCharges()
|
||||
}, 20000)
|
||||
},
|
||||
createCharge: function (wallet, data) {
|
||||
var self = this
|
||||
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
return jsonify(charge._asdict()), HTTPStatus.OK
|
||||
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:
|
||||
|
|
Loading…
Reference in New Issue
Block a user