Enables balance payments via invoice
Adds functionality for users to pay their Castle balance by generating and paying a Lightning invoice. This includes: - Adding API endpoints for invoice generation and payment recording. - Updating the frontend to allow users to initiate the invoice payment process. - Passing the wallet's `inkey` to the frontend for payment status checks.
This commit is contained in:
parent
ef3e2d9e0d
commit
854164614f
3 changed files with 35 additions and 9 deletions
12
models.py
12
models.py
|
|
@ -153,3 +153,15 @@ class CreateManualPaymentRequest(BaseModel):
|
||||||
|
|
||||||
amount: int
|
amount: int
|
||||||
description: str
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratePaymentInvoice(BaseModel):
|
||||||
|
"""Generate payment invoice request"""
|
||||||
|
|
||||||
|
amount: int
|
||||||
|
|
||||||
|
|
||||||
|
class RecordPayment(BaseModel):
|
||||||
|
"""Record a payment"""
|
||||||
|
|
||||||
|
payment_hash: str
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ window.app = Vue.createApp({
|
||||||
amount: null,
|
amount: null,
|
||||||
paymentRequest: null,
|
paymentRequest: null,
|
||||||
paymentHash: null,
|
paymentHash: null,
|
||||||
|
checkWalletKey: null,
|
||||||
loading: false
|
loading: false
|
||||||
},
|
},
|
||||||
settingsDialog: {
|
settingsDialog: {
|
||||||
|
|
@ -326,6 +327,7 @@ window.app = Vue.createApp({
|
||||||
// Show the payment request in the dialog
|
// Show the payment request in the dialog
|
||||||
this.payDialog.paymentRequest = response.data.payment_request
|
this.payDialog.paymentRequest = response.data.payment_request
|
||||||
this.payDialog.paymentHash = response.data.payment_hash
|
this.payDialog.paymentHash = response.data.payment_hash
|
||||||
|
this.payDialog.checkWalletKey = response.data.check_wallet_key
|
||||||
|
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
|
|
@ -334,21 +336,21 @@ window.app = Vue.createApp({
|
||||||
})
|
})
|
||||||
|
|
||||||
// Poll for payment completion
|
// Poll for payment completion
|
||||||
this.pollForPayment(response.data.payment_hash)
|
this.pollForPayment(response.data.payment_hash, response.data.check_wallet_key)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
} finally {
|
} finally {
|
||||||
this.payDialog.loading = false
|
this.payDialog.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async pollForPayment(paymentHash) {
|
async pollForPayment(paymentHash, checkWalletKey) {
|
||||||
// Poll every 2 seconds for payment status
|
// Poll every 2 seconds for payment status
|
||||||
const checkPayment = async () => {
|
const checkPayment = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await LNbits.api.request(
|
const response = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
`/api/v1/payments/${paymentHash}`,
|
`/api/v1/payments/${paymentHash}`,
|
||||||
this.g.user.wallets[0].inkey
|
checkWalletKey
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.data && response.data.paid) {
|
if (response.data && response.data.paid) {
|
||||||
|
|
@ -415,6 +417,7 @@ window.app = Vue.createApp({
|
||||||
this.payDialog.amount = Math.abs(this.balance.balance)
|
this.payDialog.amount = Math.abs(this.balance.balance)
|
||||||
this.payDialog.paymentRequest = null
|
this.payDialog.paymentRequest = null
|
||||||
this.payDialog.paymentHash = null
|
this.payDialog.paymentHash = null
|
||||||
|
this.payDialog.checkWalletKey = null
|
||||||
this.payDialog.show = true
|
this.payDialog.show = true
|
||||||
},
|
},
|
||||||
async showReceivableDialog() {
|
async showReceivableDialog() {
|
||||||
|
|
|
||||||
23
views_api.py
23
views_api.py
|
|
@ -34,8 +34,10 @@ from .models import (
|
||||||
CreateEntryLine,
|
CreateEntryLine,
|
||||||
CreateJournalEntry,
|
CreateJournalEntry,
|
||||||
ExpenseEntry,
|
ExpenseEntry,
|
||||||
|
GeneratePaymentInvoice,
|
||||||
JournalEntry,
|
JournalEntry,
|
||||||
ReceivableEntry,
|
ReceivableEntry,
|
||||||
|
RecordPayment,
|
||||||
RevenueEntry,
|
RevenueEntry,
|
||||||
UserBalance,
|
UserBalance,
|
||||||
UserWalletSettings,
|
UserWalletSettings,
|
||||||
|
|
@ -446,13 +448,14 @@ async def api_get_all_balances(
|
||||||
|
|
||||||
@castle_api_router.post("/api/v1/generate-payment-invoice")
|
@castle_api_router.post("/api/v1/generate-payment-invoice")
|
||||||
async def api_generate_payment_invoice(
|
async def api_generate_payment_invoice(
|
||||||
amount: int,
|
data: GeneratePaymentInvoice,
|
||||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Generate an invoice on the Castle wallet for user to pay their balance.
|
Generate an invoice on the Castle wallet for user to pay their balance.
|
||||||
User can then pay this invoice to settle their debt.
|
User can then pay this invoice to settle their debt.
|
||||||
"""
|
"""
|
||||||
|
from lnbits.core.crud.wallets import get_wallet
|
||||||
from lnbits.core.models import CreateInvoice
|
from lnbits.core.models import CreateInvoice
|
||||||
from lnbits.core.services import create_payment_request
|
from lnbits.core.services import create_payment_request
|
||||||
|
|
||||||
|
|
@ -462,7 +465,7 @@ async def api_generate_payment_invoice(
|
||||||
# Create invoice on castle wallet
|
# Create invoice on castle wallet
|
||||||
invoice_data = CreateInvoice(
|
invoice_data = CreateInvoice(
|
||||||
out=False,
|
out=False,
|
||||||
amount=amount,
|
amount=data.amount,
|
||||||
memo=f"Payment from user {wallet.wallet.user[:8]} to Castle",
|
memo=f"Payment from user {wallet.wallet.user[:8]} to Castle",
|
||||||
unit="sat",
|
unit="sat",
|
||||||
extra={"user_id": wallet.wallet.user, "type": "castle_payment"},
|
extra={"user_id": wallet.wallet.user, "type": "castle_payment"},
|
||||||
|
|
@ -470,17 +473,25 @@ async def api_generate_payment_invoice(
|
||||||
|
|
||||||
payment = await create_payment_request(castle_wallet_id, invoice_data)
|
payment = await create_payment_request(castle_wallet_id, invoice_data)
|
||||||
|
|
||||||
|
# Get castle wallet to return its inkey for payment checking
|
||||||
|
castle_wallet = await get_wallet(castle_wallet_id)
|
||||||
|
if not castle_wallet:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Castle wallet not found"
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"payment_hash": payment.payment_hash,
|
"payment_hash": payment.payment_hash,
|
||||||
"payment_request": payment.bolt11,
|
"payment_request": payment.bolt11,
|
||||||
"amount": amount,
|
"amount": data.amount,
|
||||||
"memo": invoice_data.memo,
|
"memo": invoice_data.memo,
|
||||||
|
"check_wallet_key": castle_wallet.inkey, # Key to check payment status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@castle_api_router.post("/api/v1/record-payment")
|
@castle_api_router.post("/api/v1/record-payment")
|
||||||
async def api_record_payment(
|
async def api_record_payment(
|
||||||
payment_hash: str,
|
data: RecordPayment,
|
||||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|
@ -490,7 +501,7 @@ async def api_record_payment(
|
||||||
from lnbits.core.crud.payments import get_standalone_payment
|
from lnbits.core.crud.payments import get_standalone_payment
|
||||||
|
|
||||||
# Get the payment details
|
# Get the payment details
|
||||||
payment = await get_standalone_payment(payment_hash)
|
payment = await get_standalone_payment(data.payment_hash)
|
||||||
if not payment:
|
if not payment:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Payment not found"
|
status_code=HTTPStatus.NOT_FOUND, detail="Payment not found"
|
||||||
|
|
@ -518,7 +529,7 @@ async def api_record_payment(
|
||||||
# This reduces what the user owes
|
# This reduces what the user owes
|
||||||
entry_data = CreateJournalEntry(
|
entry_data = CreateJournalEntry(
|
||||||
description=f"Lightning payment from user {wallet.wallet.user[:8]}",
|
description=f"Lightning payment from user {wallet.wallet.user[:8]}",
|
||||||
reference=payment_hash,
|
reference=data.payment_hash,
|
||||||
lines=[
|
lines=[
|
||||||
CreateEntryLine(
|
CreateEntryLine(
|
||||||
account_id=lightning_account.id,
|
account_id=lightning_account.id,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue