feat(v2): commission split target accepts wallet id, invoice key, LN address, or LNURL
Closes the operator-facing limitation that commission split legs could
only target the operator's own LNbits wallets. Adopting the splitpayments
pattern — a single `target` string accepts:
- LNbits wallet id (UUID-shaped) — direct internal pay
- LNbits wallet invoice key — resolved via get_wallet_for_key, then
internal pay (lets the operator split to any LNbits user who shares
their invoice key)
- Lightning address (user@domain) — resolved via LNURL-pay
- LNURL string (LNURL1...) — resolved via LNURL-pay
Schema (m001 update — fresh-install only; no operator data in production):
dca_commission_splits.wallet_id → target
Backend (distribution.py):
- New _pay_split_leg helper: routes the leg by target type. External
targets (@ or LNURL prefix) go through get_pr_from_lnurl + pay_invoice;
internal targets go through create_invoice + pay_invoice (the original
path), with get_wallet_for_key as the first resolution step so
invoice keys work as well as wallet ids.
- _pay_operator_splits delegates per-leg payment to the new helper.
- dca_payments rows still record the leg as leg_type='operator_split';
external targets land destination_ln_address (the human-readable
target), internal targets land destination_wallet_id.
- Errors are caught and surfaced via the existing failed-leg path
so /retry can re-run them.
Frontend (commission tab):
- Each leg gets a per-row q-btn-toggle: "My wallet" vs "Lightning
address / LNURL / invoice key". Wallet mode shows the q-select of
the operator's own wallets (previous behaviour); external mode
shows a free-text q-input.
- On load, targetKind is inferred from whether the stored target
matches one of the operator's wallet ids (renders as 'wallet')
or not (renders as 'external'). The kind is UI-only, not persisted.
- Leg row laid out in a bordered card so the toggle + 3-column layout
don't crowd at narrow widths.
Refs: aiolabs/satmachineadmin#9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8968c0ae07
commit
5de9cd5205
6 changed files with 196 additions and 40 deletions
|
|
@ -1080,8 +1080,12 @@ window.app = Vue.createApp({
|
|||
const {data} = await LNbits.api.request(
|
||||
'GET', `${COMMISSION_SPLITS_PATH}${params}`
|
||||
)
|
||||
// targetKind is a UI-only hint derived from the stored target string.
|
||||
// It's not persisted server-side; the server resolves the target
|
||||
// at payment time regardless.
|
||||
this.commissionLegs = (data || []).map(leg => ({
|
||||
wallet_id: leg.wallet_id,
|
||||
target: leg.target || '',
|
||||
targetKind: this._inferTargetKind(leg.target),
|
||||
label: leg.label || '',
|
||||
pct: Number(leg.pct) || 0
|
||||
}))
|
||||
|
|
@ -1091,9 +1095,19 @@ window.app = Vue.createApp({
|
|||
}
|
||||
},
|
||||
|
||||
_inferTargetKind(target) {
|
||||
// If the value matches one of the operator's own wallet ids, render
|
||||
// the row in 'wallet' mode (q-select). Otherwise treat as external
|
||||
// (free-text q-input).
|
||||
if (!target) return 'wallet'
|
||||
const ownIds = new Set(this.walletOptions.map(w => w.value))
|
||||
return ownIds.has(target) ? 'wallet' : 'external'
|
||||
},
|
||||
|
||||
addCommissionLeg() {
|
||||
this.commissionLegs.push({
|
||||
wallet_id: this.walletOptions[0]?.value || null,
|
||||
target: this.walletOptions[0]?.value || '',
|
||||
targetKind: 'wallet',
|
||||
label: '',
|
||||
pct: 0
|
||||
})
|
||||
|
|
@ -1110,7 +1124,7 @@ window.app = Vue.createApp({
|
|||
const body = {
|
||||
machine_id: this.commissionScope,
|
||||
legs: this.commissionLegs.map((leg, idx) => ({
|
||||
wallet_id: leg.wallet_id,
|
||||
target: (leg.target || '').toString().trim(),
|
||||
label: leg.label || null,
|
||||
pct: Number(leg.pct),
|
||||
sort_order: idx
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue