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
|
|
@ -494,32 +494,55 @@
|
|||
</q-banner>
|
||||
|
||||
<div v-for="(leg, idx) in commissionLegs" :key="idx"
|
||||
class="row q-col-gutter-sm q-mb-sm items-center">
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="leg.wallet_id"
|
||||
:options="walletOptions"
|
||||
label="Wallet"
|
||||
emit-value map-options dense outlined></q-select>
|
||||
class="q-mb-md q-pa-sm"
|
||||
:style="{border: '1px solid rgba(255,255,255,0.08)', borderRadius: '4px'}">
|
||||
<div class="row q-col-gutter-sm items-center q-mb-sm">
|
||||
<div class="col">
|
||||
<q-btn-toggle v-model="leg.targetKind"
|
||||
:options="[
|
||||
{label: 'My wallet', value: 'wallet'},
|
||||
{label: 'Lightning address / LNURL / invoice key', value: 'external'}
|
||||
]"
|
||||
no-caps dense flat
|
||||
toggle-color="primary"
|
||||
color="grey-8"></q-btn-toggle>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn flat dense round icon="delete" color="red-7"
|
||||
@click="commissionLegs.splice(idx, 1)">
|
||||
<q-tooltip>Remove leg</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-7 col-md-4">
|
||||
<q-input v-model="leg.label"
|
||||
label="Label (e.g. employee, maintenance)"
|
||||
dense outlined></q-input>
|
||||
</div>
|
||||
<div class="col-4 col-md-3">
|
||||
<q-input v-model.number="leg.pct"
|
||||
label="% (0..1)"
|
||||
type="number" step="0.01" min="0" max="1"
|
||||
dense outlined>
|
||||
<template v-slot:append>
|
||||
<span :style="{fontSize: '0.75em', opacity: 0.6}"
|
||||
v-text="((leg.pct || 0) * 100).toFixed(1) + '%'"></span>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-1 col-md-1">
|
||||
<q-btn flat dense round icon="delete" color="red-7"
|
||||
@click="commissionLegs.splice(idx, 1)"></q-btn>
|
||||
<div class="row q-col-gutter-sm items-start">
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select v-if="leg.targetKind === 'wallet'"
|
||||
v-model="leg.target"
|
||||
:options="walletOptions"
|
||||
label="Wallet (one of yours)"
|
||||
emit-value map-options dense outlined></q-select>
|
||||
<q-input v-else
|
||||
v-model.trim="leg.target"
|
||||
label="LN address, LNURL, or invoice key"
|
||||
hint="user@domain · LNURL1... · or an LP's invoice key"
|
||||
dense outlined></q-input>
|
||||
</div>
|
||||
<div class="col-7 col-md-3">
|
||||
<q-input v-model="leg.label"
|
||||
label="Label (employee, maintenance, ...)"
|
||||
dense outlined></q-input>
|
||||
</div>
|
||||
<div class="col-5 col-md-3">
|
||||
<q-input v-model.number="leg.pct"
|
||||
label="% (0..1)"
|
||||
type="number" step="0.01" min="0" max="1"
|
||||
dense outlined>
|
||||
<template v-slot:append>
|
||||
<span :style="{fontSize: '0.75em', opacity: 0.6}"
|
||||
v-text="((leg.pct || 0) * 100).toFixed(1) + '%'"></span>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue