Adds manual payment request functionality

Enables users to request manual payments from the Castle and provides admin functions to approve or reject these requests.

Introduces the `manual_payment_requests` table and related CRUD operations.
Adds API endpoints for creating, retrieving, approving, and rejecting manual payment requests.
Updates the UI to allow users to request payments and for admins to review pending requests.
This commit is contained in:
padreug 2025-10-22 18:02:07 +02:00
parent 3a26d963dc
commit c2d9b39f29
5 changed files with 520 additions and 11 deletions

View file

@ -57,7 +57,14 @@ window.app = Vue.createApp({
reference: '',
currency: null,
loading: false
}
},
manualPaymentDialog: {
show: false,
amount: null,
description: '',
loading: false
},
manualPaymentRequests: []
}
},
watch: {
@ -104,6 +111,9 @@ window.app = Vue.createApp({
})
})
return options
},
pendingManualPaymentRequests() {
return this.manualPaymentRequests.filter(r => r.status === 'pending')
}
},
methods: {
@ -422,13 +432,101 @@ window.app = Vue.createApp({
}, 2000)
},
showManualPaymentOption() {
// TODO: Show manual payment request dialog
// This is for when user wants to pay their debt manually
// For now, just notify them to contact castle
this.$q.notify({
type: 'info',
message: 'Manual payment feature coming soon!',
message: 'Please contact Castle directly to arrange manual payment.',
timeout: 3000
})
},
showManualPaymentDialog() {
// This is for when Castle owes user and they want to request manual payment
this.manualPaymentDialog.amount = Math.abs(this.balance.balance)
this.manualPaymentDialog.description = ''
this.manualPaymentDialog.show = true
},
async submitManualPaymentRequest() {
this.manualPaymentDialog.loading = true
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/manual-payment-request',
this.g.user.wallets[0].inkey,
{
amount: this.manualPaymentDialog.amount,
description: this.manualPaymentDialog.description
}
)
this.$q.notify({
type: 'positive',
message: 'Manual payment request submitted successfully!'
})
this.manualPaymentDialog.show = false
this.manualPaymentDialog.amount = null
this.manualPaymentDialog.description = ''
await this.loadManualPaymentRequests()
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.manualPaymentDialog.loading = false
}
},
async loadManualPaymentRequests() {
try {
// If super user, load all requests; otherwise load user's own requests
const endpoint = this.isSuperUser
? '/castle/api/v1/manual-payment-requests/all'
: '/castle/api/v1/manual-payment-requests'
const key = this.isSuperUser
? this.g.user.wallets[0].adminkey
: this.g.user.wallets[0].inkey
const response = await LNbits.api.request(
'GET',
endpoint,
key,
this.isSuperUser ? {status: 'pending'} : {}
)
this.manualPaymentRequests = response.data
} catch (error) {
console.error('Error loading manual payment requests:', error)
}
},
async approveManualPaymentRequest(requestId) {
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/manual-payment-requests/${requestId}/approve`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
type: 'positive',
message: 'Manual payment request approved!'
})
await this.loadManualPaymentRequests()
await this.loadBalance()
await this.loadTransactions()
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
async rejectManualPaymentRequest(requestId) {
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/manual-payment-requests/${requestId}/reject`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
type: 'warning',
message: 'Manual payment request rejected'
})
await this.loadManualPaymentRequests()
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
copyToClipboard(text) {
navigator.clipboard.writeText(text)
this.$q.notify({
@ -558,6 +656,7 @@ window.app = Vue.createApp({
await this.loadTransactions()
await this.loadAccounts()
await this.loadCurrencies()
await this.loadManualPaymentRequests()
// Load users if super user (for receivable dialog)
if (this.isSuperUser) {
await this.loadUsers()