Adds balance payment feature

Implements a feature that allows users to pay their outstanding balance via Lightning.

The changes include:
- Adds the UI elements for invoice generation and display, including QR code.
- Integrates backend endpoints to generate and record payments.
- Adds polling mechanism to track payments and update balance.
- Creates new database models to support manual payment requests.
This commit is contained in:
padreug 2025-10-22 16:46:46 +02:00
parent eb9a3c1600
commit ef3e2d9e0d
4 changed files with 232 additions and 49 deletions

View file

@ -32,6 +32,8 @@ window.app = Vue.createApp({
payDialog: {
show: false,
amount: null,
paymentRequest: null,
paymentHash: null,
loading: false
},
settingsDialog: {
@ -311,39 +313,108 @@ window.app = Vue.createApp({
async submitPayment() {
this.payDialog.loading = true
try {
// First, generate an invoice for the payment
const invoiceResponse = await LNbits.api.request(
// Generate an invoice on the Castle wallet
const response = await LNbits.api.request(
'POST',
'/api/v1/payments',
'/castle/api/v1/generate-payment-invoice',
this.g.user.wallets[0].inkey,
{
out: false,
amount: this.payDialog.amount,
memo: `Payment to Castle - ${this.payDialog.amount} sats`,
unit: 'sat'
amount: this.payDialog.amount
}
)
// Show the invoice to the user
// Show the payment request in the dialog
this.payDialog.paymentRequest = response.data.payment_request
this.payDialog.paymentHash = response.data.payment_hash
this.$q.notify({
type: 'positive',
message: 'Invoice generated! Pay it to settle your balance.',
timeout: 5000
message: 'Invoice generated! Scan QR code or copy to pay.',
timeout: 3000
})
// TODO: After payment, call /castle/api/v1/pay-balance to record it
// This would typically be done via a webhook or payment verification
this.payDialog.show = false
this.payDialog.amount = null
// Poll for payment completion
this.pollForPayment(response.data.payment_hash)
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.payDialog.loading = false
}
},
async pollForPayment(paymentHash) {
// Poll every 2 seconds for payment status
const checkPayment = async () => {
try {
const response = await LNbits.api.request(
'GET',
`/api/v1/payments/${paymentHash}`,
this.g.user.wallets[0].inkey
)
if (response.data && response.data.paid) {
// Record payment in accounting
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/record-payment',
this.g.user.wallets[0].inkey,
{
payment_hash: paymentHash
}
)
} catch (error) {
console.error('Error recording payment:', error)
}
this.$q.notify({
type: 'positive',
message: 'Payment received! Your balance has been updated.',
timeout: 3000
})
this.payDialog.show = false
this.payDialog.paymentRequest = null
this.payDialog.amount = null
await this.loadBalance()
await this.loadTransactions()
return true
}
return false
} catch (error) {
return false
}
}
// Check every 2 seconds for up to 5 minutes
let attempts = 0
const maxAttempts = 150 // 5 minutes
const intervalId = setInterval(async () => {
attempts++
const paid = await checkPayment()
if (paid || attempts >= maxAttempts) {
clearInterval(intervalId)
}
}, 2000)
},
showManualPaymentOption() {
// TODO: Show manual payment request dialog
this.$q.notify({
type: 'info',
message: 'Manual payment feature coming soon!',
timeout: 3000
})
},
copyToClipboard(text) {
navigator.clipboard.writeText(text)
this.$q.notify({
type: 'positive',
message: 'Copied to clipboard!',
timeout: 1000
})
},
showPayBalanceDialog() {
this.payDialog.amount = Math.abs(this.balance.balance)
this.payDialog.paymentRequest = null
this.payDialog.paymentHash = null
this.payDialog.show = true
},
async showReceivableDialog() {