diff --git a/static/display.js b/static/display.js new file mode 100644 index 0000000..eda8a22 --- /dev/null +++ b/static/display.js @@ -0,0 +1,250 @@ +const padString = num => num.toString().padStart(2, '0') + +const secondsToTime = seconds => { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + return `${padString(hours)}:${padString(minutes)}:${padString(secs)}` +} + +const progress = (startSeconds, currentSeconds) => { + return 1 - (startSeconds - currentSeconds) / startSeconds +} + +const mapCharge = (obj, oldObj = {}) => { + let charge = {...oldObj, ...obj} + charge.displayUrl = ['/satspay/', obj.id].join('') + charge.expanded = oldObj.expanded || false + charge.extra = + charge.extra && typeof charge.extra == 'string' + ? JSON.parse(charge.extra) + : charge.extra + const now = new Date().getTime() / 1000 + const then = new Date(charge.timestamp).getTime() / 1000 + const chargeTimeSeconds = charge.time * 60 + const secondsSinceCreated = chargeTimeSeconds - now + then + charge.timeSecondsLeft = chargeTimeSeconds - now + then + charge.timeLeft = + charge.timeSecondsLeft <= 0 + ? '00:00:00' + : secondsToTime(charge.timeSecondsLeft) + charge.progress = progress(charge.time * 60, secondsSinceCreated) + return charge +} + +if (window.app) { + window.app.component('satspay-paid', { + props: ['charge'], + template: ` +
+ +
+
+ +

+ Redirecting after 5 seconds +

+
+
+
` + }) + + window.app.component('satspay-show-qr', { + props: ['charge-amount', 'type', 'value', 'href'], + mixins: [windowMixin], + template: ` +
+
+
+ Send + + BTC + + to this onchain address + Pay this lightning-network invoice: + Scan QR with a wallet supporting BIP21: +
+
+
+ +
+
+
+ Copy address +
+
+
`, + computed: { + chargeAmountBtc() { + return (this.chargeAmount / 1e8).toFixed(8) + } + } + }) + + window.app.component('satspay-time-elapsed', { + props: ['charge'], + data() { + return { + timeSeconds: 0, + timeLeft: 60, + progress: 0, + barColor: 'grey' + } + }, + template: ` +
+ +
+ Payment received + Time elapsed +
+ + + Awaiting payment... + + {{timeLeft}} +
+
+
+
`, + created() { + this.timeSeconds = this.charge.timeSecondsLeft + this.timeLeft = this.charge.timeLeft + this.progress = this.charge.progress + if (this.charge.paid) { + this.barColor = 'positive' + this.progress = 1 + } + if (!this.charge.paid && this.timeSeconds > 0) { + this.barColor = 'secondary' + setInterval(() => { + if (!this.charge.paid && this.timeSeconds > 0) { + this.timeSeconds -= 1 + this.timeLeft = secondsToTime(this.timeSeconds) + this.progress = progress(this.charge.time * 60, this.timeSeconds) + } + if (this.charge.paid) { + this.barColor = 'positive' + this.progress = 1 + } + }, 1000) + } + } + }) +} + +window.PageSatspayPublic = { + template: '#page-satspay-public', + data() { + return { + charge: null, + mempool_url: 'https://mempool.space', + ws: null, + tab: 'uqr' + } + }, + computed: { + mempoolLink() { + const url = this.mempool_url.replace(/\/$/, '') + return `${url}/address/${this.charge.onchainaddress}` + }, + unifiedQR() { + const bitcoin = (this.charge.onchainaddress ?? '').toUpperCase() + let queryString = `bitcoin:${bitcoin}?amount=${(this.charge.amount / 1e8).toFixed(8)}` + if (this.charge.payment_request) { + queryString += `&lightning=${this.charge.payment_request.toUpperCase()}` + } + return queryString + }, + hasEnded() { + const chargeTimeSeconds = this.charge.time * 60 + const now = new Date().getTime() / 1000 + const then = new Date(this.charge.timestamp).getTime() / 1000 + const timeSecondsLeft = chargeTimeSeconds - now + then + return timeSecondsLeft <= 0 || this.charge.paid + } + }, + methods: { + async getCharge() { + const chargeId = this.$route.params.charge_id + try { + const {data} = await LNbits.api.request( + 'GET', + `/satspay/api/v1/charge/public/${chargeId}` + ) + this.mempool_url = data.mempool_url || this.mempool_url + this.charge = mapCharge(data) + if (!this.charge.onchainaddress) { + this.tab = 'ln' + } + if (this.charge.custom_css) { + document.body.setAttribute('id', 'custom-css') + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = `/satspay/css/${this.charge.custom_css}` + document.head.appendChild(link) + } + this.initWs() + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, + async initWs() { + const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws' + const url = `${protocol}://${window.location.host}/satspay/${this.charge.id}/ws` + this.ws = new WebSocket(url) + this.ws.addEventListener('message', async ({data}) => { + const res = JSON.parse(data.toString()) + this.charge.balance = res.balance + this.charge.pending = res.pending + this.charge.paid = res.paid + this.charge.completelink = res.completelink + if (this.charge.paid) { + this.charge.progress = 1 + this.charge.paid = true + if (this.charge.completelink) { + setTimeout(() => { + window.location.href = this.charge.completelink + }, 5000) + } + this.$q.notify({ + type: 'positive', + message: 'Payment received', + timeout: 10000 + }) + } + }) + this.ws.addEventListener('close', async () => { + this.$q.notify({ + type: 'negative', + message: 'WebSocket connection closed. Retrying...', + timeout: 1000 + }) + setTimeout(() => { + this.initWs() + }, 3000) + }) + } + }, + async created() { + await this.getCharge() + } +} diff --git a/static/display.vue b/static/display.vue new file mode 100644 index 0000000..fee00bf --- /dev/null +++ b/static/display.vue @@ -0,0 +1,195 @@ + diff --git a/static/js/index.js b/static/index.js similarity index 74% rename from static/js/index.js rename to static/index.js index 1d6db95..304b15d 100644 --- a/static/js/index.js +++ b/static/index.js @@ -1,14 +1,56 @@ -window.app = Vue.createApp({ - el: '#vue', - mixins: [window.windowMixin], +const mapCharge = (obj, oldObj = {}) => { + let charge = {...oldObj, ...obj} + charge.displayUrl = ['/satspay/', obj.id].join('') + charge.expanded = oldObj.expanded || false + charge.extra = + charge.extra && typeof charge.extra == 'string' + ? JSON.parse(charge.extra) + : charge.extra + const now = new Date().getTime() / 1000 + const then = new Date(charge.timestamp).getTime() / 1000 + const chargeTimeSeconds = charge.time * 60 + const secondsSinceCreated = chargeTimeSeconds - now + then + charge.timeSecondsLeft = chargeTimeSeconds - now + then + charge.timeLeft = + charge.timeSecondsLeft <= 0 + ? '00:00:00' + : secondsToTime(charge.timeSecondsLeft) + charge.progress = progress(charge.time * 60, secondsSinceCreated) + return charge +} + +const mapCSS = (obj, oldObj = {}) => { + return _.clone(obj) +} + +const padString = num => num.toString().padStart(2, '0') + +const secondsToTime = seconds => { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + return `${padString(hours)}:${padString(minutes)}:${padString(secs)}` +} + +const progress = (startSeconds, currentSeconds) => { + return 1 - (startSeconds - currentSeconds) / startSeconds +} + +window.PageSatspay = { + template: '#page-satspay', computed: { endpoint() { return `/satspay/api/v1/settings?usr=${this.g.user.id}` + }, + currencies() { + return [ + 'satoshis', + ...(this.g.allowedCurrencies || this.g.currencies || []) + ] } }, - data: function () { + data() { return { - currencies: [], fiatRates: {}, settings: [ { @@ -31,8 +73,7 @@ window.app = Vue.createApp({ } ], filter: '', - admin: admin, - network: network, + network: 'Mainnet', balance: null, walletLinks: [], chargeLinks: [], @@ -43,18 +84,8 @@ window.app = Vue.createApp({ showAdvanced: false, chargesTable: { columns: [ - { - name: 'theId', - align: 'left', - label: 'ID', - field: 'id' - }, - { - name: 'name', - align: 'left', - label: 'Name', - field: 'name' - }, + {name: 'theId', align: 'left', label: 'ID', field: 'id'}, + {name: 'name', align: 'left', label: 'Name', field: 'name'}, { name: 'timeLeft', align: 'left', @@ -73,12 +104,7 @@ window.app = Vue.createApp({ label: 'Amount to pay', field: 'amount' }, - { - name: 'balance', - align: 'left', - label: 'Balance', - field: 'balance' - }, + {name: 'balance', align: 'left', label: 'Balance', field: 'balance'}, { name: 'pending', align: 'left', @@ -110,28 +136,14 @@ window.app = Vue.createApp({ field: 'completelink' } ], - pagination: { - rowsPerPage: 10 - } + pagination: {rowsPerPage: 10} }, customCSSTable: { columns: [ - { - name: 'title', - align: 'left', - label: 'Title', - field: 'title' - }, - { - name: 'css_id', - align: 'left', - label: 'ID', - field: 'css_id' - } + {name: 'title', align: 'left', label: 'Title', field: 'title'}, + {name: 'css_id', align: 'left', label: 'ID', field: 'css_id'} ], - pagination: { - rowsPerPage: 10 - } + pagination: {rowsPerPage: 10} }, formDialogCharge: { show: false, @@ -150,20 +162,18 @@ window.app = Vue.createApp({ }, formDialogThemes: { show: false, - data: { - custom_css: '' - } + data: {custom_css: ''} }, showWebhookResponse: false, webhookResponse: '' } }, methods: { - cancelThemes: function (data) { + cancelThemes() { this.formDialogCharge.data.custom_css = '' this.formDialogThemes.show = false }, - cancelCharge: function (data) { + cancelCharge() { this.formDialogCharge.data.description = '' this.formDialogCharge.data.onchain = false this.formDialogCharge.data.onchainwallet = '' @@ -177,7 +187,7 @@ window.app = Vue.createApp({ this.formDialogCharge.show = false }, - getWalletLinks: async function () { + async getWalletLinks() { try { let {data} = await LNbits.api.request( 'GET', @@ -193,18 +203,18 @@ window.app = Vue.createApp({ LNbits.utils.notifyApiError(error) } }, - getOnchainWalletName: function (walletId) { + getOnchainWalletName(walletId) { const wallet = this.walletLinks.find(w => w.id === walletId) if (!wallet) return 'unknown' return wallet.label }, - getLNbitsWalletName: function (walletId) { + getLNbitsWalletName(walletId) { const wallet = this.g.user.walletOptions.find(w => w.value === walletId) if (!wallet) return 'unknown' return wallet.label }, - getCharges: async function () { + async getCharges() { try { const {data} = await LNbits.api.request( 'GET', @@ -221,7 +231,7 @@ window.app = Vue.createApp({ LNbits.utils.notifyApiError(error) } }, - getThemes: async function () { + async getThemes() { try { const {data} = await LNbits.api.request( 'GET', @@ -243,12 +253,12 @@ window.app = Vue.createApp({ } }, - sendFormDataThemes: function () { + sendFormDataThemes() { const wallet = this.g.user.wallets[0].adminkey const data = this.formDialogThemes.data this.createTheme(wallet, data) }, - sendFormDataCharge: function () { + sendFormDataCharge() { this.formDialogCharge.data.custom_css = this.formDialogCharge.data.custom_css?.id const data = this.formDialogCharge.data @@ -259,14 +269,14 @@ window.app = Vue.createApp({ data.onchainwallet = data.onchain ? this.onchainwallet?.id : null this.createCharge(wallet, data) }, - updateformDialog: function (themeId) { + updateformDialog(themeId) { const theme = _.findWhere(this.themeLinks, {css_id: themeId}) this.formDialogThemes.data.css_id = theme.css_id this.formDialogThemes.data.title = theme.title this.formDialogThemes.data.custom_css = theme.custom_css this.formDialogThemes.show = true }, - createTheme: async function (wallet, data) { + async createTheme(wallet, data) { try { if (data.css_id) { const resp = await LNbits.api.request( @@ -275,9 +285,10 @@ window.app = Vue.createApp({ wallet, data ) - this.themeLinks = _.reject(this.themeLinks, function (obj) { - return obj.css_id === data.css_id - }) + this.themeLinks = _.reject( + this.themeLinks, + obj => obj.css_id === data.css_id + ) this.themeLinks.unshift(mapCSS(resp.data)) } else { const resp = await LNbits.api.request( @@ -289,16 +300,13 @@ window.app = Vue.createApp({ this.themeLinks.unshift(mapCSS(resp.data)) } this.formDialogThemes.show = false - this.formDialogThemes.data = { - title: '', - custom_css: '' - } + this.formDialogThemes.data = {title: '', custom_css: ''} } catch (error) { LNbits.utils.notifyApiError(error) } }, - deleteTheme: function (themeId) { + deleteTheme(themeId) { LNbits.utils .confirmDialog('Are you sure you want to delete this theme?') .onOk(async () => { @@ -308,15 +316,16 @@ window.app = Vue.createApp({ `/satspay/api/v1/themes/${themeId}`, this.g.user.wallets[0].adminkey ) - this.themeLinks = _.reject(this.themeLinks, function (obj) { - return obj.css_id === themeId - }) + this.themeLinks = _.reject( + this.themeLinks, + obj => obj.css_id === themeId + ) } catch (error) { LNbits.utils.notifyApiError(error) } }) }, - createCharge: async function (wallet, data) { + async createCharge(wallet, data) { try { const resp = await LNbits.api.request( 'POST', @@ -339,7 +348,7 @@ window.app = Vue.createApp({ LNbits.utils.notifyApiError(error) } }, - deleteChargeLink: function (chargeId) { + deleteChargeLink(chargeId) { LNbits.utils .confirmDialog('Are you sure you want to delete this pay link?') .onOk(async () => { @@ -349,34 +358,30 @@ window.app = Vue.createApp({ `/satspay/api/v1/charge/${chargeId}`, this.g.user.wallets[0].adminkey ) - - this.chargeLinks = _.reject(this.chargeLinks, function (obj) { - return obj.id === chargeId - }) + this.chargeLinks = _.reject( + this.chargeLinks, + obj => obj.id === chargeId + ) } catch (error) { LNbits.utils.notifyApiError(error) } }) }, - sendWebhook: function (chargeId) { + sendWebhook(chargeId) { LNbits.api .request( 'GET', `/satspay/api/v1/charge/webhook/${chargeId}`, this.g.user.wallets[0].adminkey ) - .then(response => { - console.log(response) - this.$q.notify({ - message: 'Webhook sent', - color: 'positive' - }) + .then(() => { + this.$q.notify({message: 'Webhook sent', color: 'positive'}) }) .catch(err => { LNbits.utils.notifyApiError(err) }) }, - checkChargeBalance: function (chargeId) { + checkChargeBalance(chargeId) { LNbits.api .request( 'PUT', @@ -391,10 +396,7 @@ window.app = Vue.createApp({ const index = this.chargeLinks.findIndex(c => c.id === chargeId) this.chargeLinks[index] = mapCharge(charge, this.chargeLinks[index]) if (charge.paid) { - this.$q.notify({ - message: 'Charge paid', - color: 'positive' - }) + this.$q.notify({message: 'Charge paid', color: 'positive'}) } else { this.$q.notify({ message: 'Charge still pending...', @@ -403,7 +405,6 @@ window.app = Vue.createApp({ } }) .catch(err => { - console.log(err) LNbits.utils.notifyApiError(err) }) }, @@ -411,7 +412,7 @@ window.app = Vue.createApp({ this.webhookResponse = webhookResponse this.showWebhookResponse = true }, - exportchargeCSV: function () { + exportchargeCSV() { LNbits.utils.exportCSV( this.chargesTable.columns, this.chargeLinks, @@ -429,18 +430,18 @@ window.app = Vue.createApp({ .catch(LNbits.utils.notifyApiError) } }, - created: async function () { - if (this.admin == 'True') { + async created() { + try { + const {data} = await LNbits.api.request( + 'GET', + '/satspay/api/v1/settings/public' + ) + this.network = data.network + } catch (e) {} + if (this.g.user.admin) { await this.getThemes() } await this.getCharges() await this.getWalletLinks() - LNbits.api - .request('GET', '/api/v1/currencies') - .then(response => { - this.currencies = ['satoshis', ...response.data] - this.formDialogCharge.data.currency = 'satoshis' - }) - .catch(LNbits.utils.notifyApiError) } -}) +} diff --git a/static/index.vue b/static/index.vue new file mode 100644 index 0000000..bec5878 --- /dev/null +++ b/static/index.vue @@ -0,0 +1,673 @@ + diff --git a/static/js/components.js b/static/js/components.js deleted file mode 100644 index 9c69464..0000000 --- a/static/js/components.js +++ /dev/null @@ -1,117 +0,0 @@ -window.app.component('satspay-paid', { - props: ['charge'], - template: ` -
- -
-
- {%raw%}{%endraw%} -

- Redirecting after 5 seconds -

-
-
-
` -}) - -window.app.component('satspay-show-qr', { - props: ['charge-amount', 'type', 'value', 'href'], - mixins: [windowMixin], - template: ` -
-
-
- Send - - BTC - - to this onchain address - Pay this lightning-network invoice: - Scan QR with a wallet supporting BIP21: -
-
-
- -
-
-
- Copy address -
-
-
`, - computed: { - chargeAmountBtc() { - return (this.chargeAmount / 1e8).toFixed(8) - } - } -}) - -window.app.component('satspay-time-elapsed', { - props: ['charge'], - data() { - return { - timeSeconds: 0, - timeLeft: 60, - progress: 0, - barColor: 'grey' - } - }, - template: ` -
- -
- Payment received - Time elapsed -
- - - Awaiting payment... - - {{timeLeft}} -
-
-
-
`, - created() { - this.timeSeconds = this.charge.timeSecondsLeft - this.timeLeft = this.charge.timeLeft - this.progress = this.charge.progress - if (this.charge.paid) { - this.barColor = 'positive' - this.progress = 1 - } - if (!this.charge.paid && this.timeSeconds > 0) { - this.barColor = 'secondary' - setInterval(() => { - if (!this.charge.paid && this.timeSeconds > 0) { - this.timeSeconds -= 1 - this.timeLeft = secondsToTime(this.timeSeconds) - this.progress = progress(this.charge.time * 60, this.timeSeconds) - } - if (this.charge.paid) { - this.barColor = 'positive' - this.progress = 1 - } - }, 1000) - } - } -}) diff --git a/static/js/display.js b/static/js/display.js deleted file mode 100644 index 0305ad5..0000000 --- a/static/js/display.js +++ /dev/null @@ -1,87 +0,0 @@ -window.app = Vue.createApp({ - el: '#vue', - mixins: [window.windowMixin], - data() { - return { - charge: mapCharge(charge_data), - mempool_url: mempool_url, - ws: null, - wallet: { - inkey: '' - }, - tab: 'uqr' - } - }, - computed: { - mempoolLink() { - // remove trailing slash - const url = this.mempool_url.replace(/\/$/, '') - return `${url}/address/${this.charge.onchainaddress}` - }, - unifiedQR() { - const bitcoin = (this.charge.onchainaddress ?? '').toUpperCase() - let queryString = `bitcoin:${bitcoin}?amount=${( - this.charge.amount / 1e8 - ).toFixed(8)}` - if (this.charge.payment_request) { - queryString += `&lightning=${this.charge.payment_request.toUpperCase()}` - } - return queryString - }, - hasEnded() { - const chargeTimeSeconds = this.charge.time * 60 - const now = new Date().getTime() / 1000 - const then = new Date(this.charge.timestamp).getTime() / 1000 - const timeSecondsLeft = chargeTimeSeconds - now + then - return timeSecondsLeft <= 0 || this.charge.paid - } - }, - methods: { - async initWs() { - const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws' - const url = `${protocol}://${window.location.host}/satspay/${this.charge.id}/ws` - this.ws = new WebSocket(url) - this.ws.addEventListener('message', async ({data}) => { - const res = JSON.parse(data.toString()) - this.charge.balance = res.balance - this.charge.pending = res.pending - this.charge.paid = res.paid - this.charge.completelink = res.completelink - if (this.charge.paid) { - this.charge.progress = 1 - this.charge.paid = true - if (this.charge.completelink) { - setTimeout(() => { - window.location.href = this.charge.completelink - }, 5000) - } - this.$q.notify({ - type: 'positive', - message: 'Payment received', - timeout: 10000 - }) - } - }) - this.ws.addEventListener('close', async () => { - this.$q.notify({ - type: 'negative', - message: 'WebSocket connection closed. Retrying...', - timeout: 1000 - }) - setTimeout(() => { - this.initWs() - }, 3000) - }) - } - }, - async created() { - if (!this.charge.onchainaddress) { - this.tab = 'ln' - } - // add custom-css to make overrides easier - if (this.charge.custom_css) { - document.body.setAttribute('id', 'custom-css') - } - this.initWs() - } -}) diff --git a/static/js/utils.js b/static/js/utils.js deleted file mode 100644 index b371311..0000000 --- a/static/js/utils.js +++ /dev/null @@ -1,53 +0,0 @@ -const sleep = ms => new Promise(r => setTimeout(r, ms)) -const retryWithDelay = async function (fn, retryCount = 0) { - try { - await sleep(25) - // Do not return the call directly, use result. - // Otherwise the error will not be cought in this try-catch block. - const result = await fn() - return result - } catch (err) { - if (retryCount > 100) throw err - await sleep((retryCount + 1) * 1000) - return retryWithDelay(fn, retryCount + 1) - } -} - -const mapCharge = (obj, oldObj = {}) => { - let charge = {...oldObj, ...obj} - charge.displayUrl = ['/satspay/', obj.id].join('') - charge.expanded = oldObj.expanded || false - charge.extra = - charge.extra && typeof charge.extra == 'string' - ? JSON.parse(charge.extra) - : charge.extra - const now = new Date().getTime() / 1000 - const then = new Date(charge.timestamp).getTime() / 1000 - const chargeTimeSeconds = charge.time * 60 - const secondsSinceCreated = chargeTimeSeconds - now + then - charge.timeSecondsLeft = chargeTimeSeconds - now + then - charge.timeLeft = - charge.timeSecondsLeft <= 0 - ? '00:00:00' - : secondsToTime(charge.timeSecondsLeft) - charge.progress = progress(charge.time * 60, secondsSinceCreated) - return charge -} - -const mapCSS = (obj, oldObj = {}) => { - const theme = _.clone(obj) - return theme -} - -const padString = num => num.toString().padStart(2, '0') - -const secondsToTime = seconds => { - const hours = Math.floor(seconds / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - const secs = Math.floor(seconds % 60) - return `${padString(hours)}:${padString(minutes)}:${padString(secs)}` -} - -const progress = (startSeconds, currentSeconds) => { - return 1 - (startSeconds - currentSeconds) / startSeconds -} diff --git a/static/routes.json b/static/routes.json new file mode 100644 index 0000000..8acd2da --- /dev/null +++ b/static/routes.json @@ -0,0 +1,14 @@ +[ + { + "path": "/satspay/", + "name": "PageSatspay", + "template": "/satspay/static/index.vue", + "component": "/satspay/static/index.js" + }, + { + "path": "/satspay/:charge_id", + "name": "PageSatspayPublic", + "template": "/satspay/static/display.vue", + "component": "/satspay/static/display.js" + } +] diff --git a/templates/satspay/_api_docs.html b/templates/satspay/_api_docs.html deleted file mode 100644 index 2adab8c..0000000 --- a/templates/satspay/_api_docs.html +++ /dev/null @@ -1,29 +0,0 @@ - - -

- SatsPayServer, create Onchain/LN charges.
WARNING: If using with the - WatchOnly extension, we highly reccomend using a fresh extended public Key - specifically for SatsPayServer!
- - Created by, - Ben Arc, - motorina0 -

-
-
- Swagger REST API Documentation -
-
diff --git a/templates/satspay/display.html b/templates/satspay/display.html deleted file mode 100644 index 9e42428..0000000 --- a/templates/satspay/display.html +++ /dev/null @@ -1,205 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
-
-
- - - - Charge ID: - - - - - Total to pay: - - - - - - -  sats - - - - - - - Amount paid: - - - - - -  sats - - - - - - - Amount pending: - - - - - -  sats - - - - - - - Amount due: - - - - - -  sats - - - - - - - -
-
-
- -
- -
-
-
- - - - - - - - - - -
-
- -
-
-
- - -
-
- -
-
-
- - -
-
- - -
-
-
-
- -
-
-
-
-
-
-
-
-{% endblock %} {% block styles %} {% if custom_css %} - -{% endif %} - -{% endblock %} {% block scripts %} - - - - -{% endblock %} diff --git a/templates/satspay/index.html b/templates/satspay/index.html deleted file mode 100644 index 38350ef..0000000 --- a/templates/satspay/index.html +++ /dev/null @@ -1,625 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New charge - - - New CSS Theme - - New CSS Theme - For security reason, custom css is only available to server - admins. - - - - - - -
-
-
Charges
-
- -
- - - -
-
- - - - - Export to CSV - - - - -
-
- - - - - -
-
- - - -
-
-
Themes
-
-
- - - - - -
-
-
- -
- - -
Satspay Extension
-
- - - {% include "satspay/_api_docs.html" %} - -
-
- - - - - - - -
-
- -
-
- - - - - - - - -
-
-
- -
-
- - - Onchain Wallet (watch-only) extension MUST be activated and - have a wallet - - -
-
-
-
- -
-
-
- -
- - - - - - - Zeroconf (0-conf) payments - - Allow payments with 0 confirmations. This is less secure but - faster. Payments will be marked paid once they are seen on the - mempool. - - - - - - - - - Fastrack redirect in checkout without 0-conf. - - If enabled, the user will be redirected from the checkout page - once the onchain payment is detected (0-conf). Unlike 0-conf it - will wait for the onchain to be confirmed to send a webhook with - charge is paid. - - - -
- - - - -
-
- - - - - - - - - -
-
-
- Create Charge - Cancel -
-
-
-
- - - - - - - -
- Update CSS theme - Save CSS theme - Cancel -
-
-
-
- - - - - -
- Close -
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - - - -{% endblock %} diff --git a/views.py b/views.py index 7bbaacc..eb7dd74 100644 --- a/views.py +++ b/views.py @@ -1,62 +1,29 @@ -import json from http import HTTPStatus from fastapi import ( APIRouter, Depends, HTTPException, - Request, Response, WebSocket, WebSocketDisconnect, ) -from fastapi.responses import HTMLResponse -from lnbits.core.models import User +from lnbits.core.views.generic import index, index_public from lnbits.decorators import check_user_exists -from lnbits.helpers import template_renderer from lnbits.settings import settings -from .crud import get_charge, get_or_create_satspay_settings, get_theme +from .crud import get_charge, get_theme from .tasks import public_ws_listeners satspay_generic_router = APIRouter() +satspay_generic_router.add_api_route( + "/", methods=["GET"], endpoint=index, dependencies=[Depends(check_user_exists)] +) -def satspay_renderer(): - return template_renderer(["satspay/templates"]) - - -@satspay_generic_router.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - settings = await get_or_create_satspay_settings() - return satspay_renderer().TemplateResponse( - "satspay/index.html", - { - "request": request, - "user": user.json(), - "admin": user.admin, - "network": settings.network, - }, - ) - - -@satspay_generic_router.get("/{charge_id}", response_class=HTMLResponse) -async def display_charge(request: Request, charge_id: str): - charge = await get_charge(charge_id) - if not charge: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." - ) - settings = await get_or_create_satspay_settings() - return satspay_renderer().TemplateResponse( - "satspay/display.html", - { - "request": request, - "charge_data": json.dumps(charge.public), - "custom_css": charge.custom_css, - "mempool_url": settings.mempool_url, - }, - ) +satspay_generic_router.add_api_route( + "/{charge_id}", methods=["GET"], endpoint=index_public +) @satspay_generic_router.websocket("/{charge_id}/ws") @@ -71,7 +38,6 @@ async def websocket_charge(websocket: WebSocket, charge_id: str): public_ws_listeners[charge_id] = [] public_ws_listeners[charge_id].append(websocket) try: - # Keep the connection alive while settings.lnbits_running: await websocket.receive_text() except WebSocketDisconnect: diff --git a/views_api.py b/views_api.py index be45a4b..6b7e01e 100644 --- a/views_api.py +++ b/views_api.py @@ -196,6 +196,26 @@ async def api_charge_delete(charge_id: str): await delete_charge(charge_id) +@satspay_api_router.get("/api/v1/settings/public") +async def api_get_public_settings() -> dict: + satspay_settings = await get_or_create_satspay_settings() + return { + "network": satspay_settings.network, + "mempool_url": satspay_settings.mempool_url, + } + + +@satspay_api_router.get("/api/v1/charge/public/{charge_id}") +async def api_get_charge_public(charge_id: str) -> dict: + charge = await get_charge(charge_id) + if not charge: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist." + ) + satspay_settings = await get_or_create_satspay_settings() + return {**charge.public, "mempool_url": satspay_settings.mempool_url} + + @satspay_api_router.get("/api/v1/settings", dependencies=[Depends(check_admin)]) async def api_get_or_create_settings() -> SatspaySettings: return await get_or_create_satspay_settings()