Enables manual settlement with fiat currencies

Adds support for settling receivables with fiat currencies
like EUR and USD, in addition to sats.

Updates the settlement dialog to handle fiat amounts and
exchange rates, defaulting to cash payment when a fiat balance
exists.

Modifies the API to accept currency and amount_sats parameters
and adjust the journal entry accordingly, converting the fiat amount
to minor units (e.g., cents) for accounting purposes.
This commit is contained in:
padreug 2025-10-23 04:19:26 +02:00
parent 49f21da55a
commit 70013d1c29
4 changed files with 120 additions and 20 deletions

View file

@ -96,7 +96,9 @@ window.app = Vue.createApp({
invoice: null,
paymentHash: null,
checkWalletKey: null,
pollIntervalId: null
pollIntervalId: null,
exchangeRate: 3571.43, // sats per EUR (TODO: fetch from API)
originalCurrency: 'BTC' // Track original receivable currency
}
}
},
@ -114,6 +116,25 @@ window.app = Vue.createApp({
clearInterval(this.settleReceivableDialog.pollIntervalId)
this.settleReceivableDialog.pollIntervalId = null
}
},
'settleReceivableDialog.payment_method': function(newVal, oldVal) {
// Convert amount when payment method changes between cash and lightning
if (!oldVal) return
const isOldCash = ['cash', 'bank_transfer', 'check'].includes(oldVal)
const isNewCash = ['cash', 'bank_transfer', 'check'].includes(newVal)
// Only convert if switching between cash and lightning
if (isOldCash === isNewCash) return
// Convert from fiat to sats (when switching from cash to lightning)
if (isOldCash && !isNewCash) {
this.settleReceivableDialog.amount = this.settleReceivableDialog.maxAmount
}
// Convert from sats to fiat (when switching from lightning to cash)
else if (!isOldCash && isNewCash) {
this.settleReceivableDialog.amount = this.settleReceivableDialog.maxAmountFiat || 0
}
}
},
computed: {
@ -135,6 +156,30 @@ window.app = Vue.createApp({
}
return 'Amount (sats) *'
},
settlementAmountLabel() {
const isCashPayment = ['cash', 'bank_transfer', 'check'].includes(
this.settleReceivableDialog.payment_method
)
if (isCashPayment && this.settleReceivableDialog.fiatCurrency) {
return `Settlement Amount (${this.settleReceivableDialog.fiatCurrency}) *`
}
return 'Settlement Amount (sats) *'
},
settlementMaxAmount() {
const isCashPayment = ['cash', 'bank_transfer', 'check'].includes(
this.settleReceivableDialog.payment_method
)
if (isCashPayment && this.settleReceivableDialog.maxAmountFiat) {
return this.settleReceivableDialog.maxAmountFiat
}
return this.settleReceivableDialog.maxAmount
},
settlementAmountStep() {
const isCashPayment = ['cash', 'bank_transfer', 'check'].includes(
this.settleReceivableDialog.payment_method
)
return isCashPayment ? '0.01' : '1'
},
currencyOptions() {
const options = [{label: 'Satoshis (default)', value: null}]
this.currencies.forEach(curr => {
@ -883,20 +928,29 @@ window.app = Vue.createApp({
clearInterval(this.settleReceivableDialog.pollIntervalId)
}
// Extract fiat balances (e.g., EUR)
const fiatBalances = userBalance.fiat_balances || {}
const fiatCurrency = Object.keys(fiatBalances)[0] || null // Get first fiat currency (e.g., 'EUR')
const fiatAmount = fiatCurrency ? Math.abs(fiatBalances[fiatCurrency]) : 0
this.settleReceivableDialog = {
show: true,
user_id: userBalance.user_id,
username: userBalance.username,
maxAmount: Math.abs(userBalance.balance), // Convert negative to positive
amount: Math.abs(userBalance.balance), // Default to full amount
payment_method: 'lightning',
maxAmount: Math.abs(userBalance.balance), // Sats amount
maxAmountFiat: fiatAmount, // EUR or other fiat amount
fiatCurrency: fiatCurrency, // 'EUR', 'USD', etc.
amount: fiatCurrency ? fiatAmount : Math.abs(userBalance.balance), // Default to fiat if available, otherwise sats
payment_method: fiatCurrency ? 'cash' : 'lightning', // Default to cash if fiat balance exists
description: `Payment from ${userBalance.username}`,
reference: '',
loading: false,
invoice: null,
paymentHash: null,
checkWalletKey: null,
pollIntervalId: null
pollIntervalId: null,
exchangeRate: fiatAmount > 0 ? Math.abs(userBalance.balance) / fiatAmount : 3571.43, // Calculate rate from actual amounts
originalCurrency: fiatCurrency || 'BTC'
}
},
async generateSettlementInvoice() {
@ -1018,17 +1072,30 @@ window.app = Vue.createApp({
async submitSettleReceivable() {
this.settleReceivableDialog.loading = true
try {
// Determine if this is a fiat payment
const isCashPayment = ['cash', 'bank_transfer', 'check'].includes(
this.settleReceivableDialog.payment_method
)
const payload = {
user_id: this.settleReceivableDialog.user_id,
amount: this.settleReceivableDialog.amount,
payment_method: this.settleReceivableDialog.payment_method,
description: this.settleReceivableDialog.description,
reference: this.settleReceivableDialog.reference || null,
}
// Add currency info for fiat payments
if (isCashPayment && this.settleReceivableDialog.fiatCurrency) {
payload.currency = this.settleReceivableDialog.fiatCurrency
payload.amount_sats = this.settleReceivableDialog.maxAmount
}
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/receivables/settle',
this.g.user.wallets[0].adminkey,
{
user_id: this.settleReceivableDialog.user_id,
amount: this.settleReceivableDialog.amount,
payment_method: this.settleReceivableDialog.payment_method,
description: this.settleReceivableDialog.description,
reference: this.settleReceivableDialog.reference || null
}
payload
)
this.$q.notify({