Adds an optional `extra` (JSON) field to a withdraw link. When the link
is claimed, that `extra` is merged onto the payout payment's `extra`, so
a caller can tag the resulting payment with metadata an external listener
keys on — the link is the only place to attach it (the customer-facing
LNURL-withdraw payout otherwise carries just `{tag, withdrawal_link_id}`).
Motivating use: bitSpire cash-in settlements. The operator's spirekeeper
listener fires a `cash_in` settlement (fee split to the platform) only on
an outbound payment stamped `source=bitspire`; before this there was no
way to stamp an LNURL-withdraw payout, so cash-ins never settled. bitSpire
now creates the cash-in link for the NET amount with
`extra={source, type:cash_in, principal_sats, fee_sats, ...}` and the
settlement fires on claim.
- models: `extra: dict | None` on CreateWithdrawData + WithdrawLink.
LNbits' db layer (de)serializes dict columns to/from JSON natively
(same as Payment.extra) — no per-field validator needed.
- migrations_fork.py: `withdraw_link.extra TEXT` under `withdraw_fork`,
keeping the upstream-tracked migrations.py byte-identical for clean
rebases (aiolabs/lnbits#8 pattern).
- views_lnurl: `extra={**(link.extra or {}), "tag": ..., "withdrawal_link_id": ...}`
— the withdraw extension's own keys are written last so a caller cannot
clobber them.
Verified end-to-end on the dev stack: a stamped link's payout carries the
merged extra and drives a spirekeeper cash_in settlement + super-fee payout.
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
from datetime import datetime
|
|
|
|
from fastapi import Query
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class CreateWithdrawData(BaseModel):
|
|
title: str = Query(...)
|
|
min_withdrawable: int = Query(..., ge=1)
|
|
max_withdrawable: int = Query(..., ge=1)
|
|
uses: int = Query(..., ge=1)
|
|
wait_time: int = Query(..., ge=1)
|
|
is_unique: bool
|
|
webhook_url: str = Query(None)
|
|
webhook_headers: str = Query(None)
|
|
webhook_body: str = Query(None)
|
|
custom_url: str = Query(None)
|
|
enabled: bool = Query(True)
|
|
# Arbitrary JSON merged into the payout payment's `extra` when this link is
|
|
# claimed (see views_lnurl). Lets a caller tag the resulting payment with
|
|
# settlement/attribution metadata an external listener can key on — e.g.
|
|
# bitSpire stamps {source, type, principal_sats, fee_sats, ...} so the
|
|
# spirekeeper cash-in settlement fires off an LNURL-withdraw payout.
|
|
extra: dict | None = None
|
|
|
|
|
|
class WithdrawLink(BaseModel):
|
|
id: str
|
|
wallet: str = Query(None)
|
|
title: str = Query(None)
|
|
min_withdrawable: int = Query(0)
|
|
max_withdrawable: int = Query(0)
|
|
uses: int = Query(0)
|
|
wait_time: int = Query(0)
|
|
is_unique: bool = Query(False)
|
|
unique_hash: str = Query(0)
|
|
k1: str = Query(None)
|
|
open_time: int = Query(0)
|
|
used: int = Query(0)
|
|
usescsv: str = Query(None)
|
|
number: int = Field(default=0, no_database=True)
|
|
webhook_url: str = Query(None)
|
|
webhook_headers: str = Query(None)
|
|
webhook_body: str = Query(None)
|
|
custom_url: str = Query(None)
|
|
# Persisted as TEXT (JSON); merged into the payout payment's `extra` on
|
|
# claim. LNbits' db layer (de)serializes dict-typed columns to/from JSON
|
|
# natively (same as Payment.extra) — no per-field validator needed.
|
|
extra: dict | None = None
|
|
created_at: datetime
|
|
enabled: bool = Query(True)
|
|
lnurl: str | None = Field(
|
|
default=None,
|
|
no_database=True,
|
|
deprecated=True,
|
|
description=(
|
|
"Deprecated: Instead of using this bech32 encoded string, dynamically "
|
|
"generate your own static link (lud17/bech32) on the client side. "
|
|
"Example: lnurlw://${window.location.hostname}/lnurlw/${id}"
|
|
),
|
|
)
|
|
lnurl_url: str | None = Field(
|
|
default=None,
|
|
no_database=True,
|
|
description="The raw LNURL callback URL (use for QR code generation)",
|
|
)
|
|
|
|
@property
|
|
def is_spent(self) -> bool:
|
|
return self.used >= self.uses
|
|
|
|
|
|
class HashCheck(BaseModel):
|
|
hash: bool
|
|
lnurl: bool
|
|
|
|
|
|
class PaginatedWithdraws(BaseModel):
|
|
data: list[WithdrawLink]
|
|
total: int
|