feat: merge a link's extra into the payout payment (v1.2.2-aio.2) #3

Merged
padreug merged 1 commit from feat/extra-passthrough into main 2026-06-21 15:27:43 +00:00
5 changed files with 66 additions and 2 deletions

View file

@ -2,7 +2,7 @@
"name": "Withdraw Links",
"short_description": "Make LNURL withdraw links",
"tile": "/withdraw/static/image/lnurl-withdraw.png",
"version": "1.2.2",
"version": "1.2.2-aio.2",
"min_lnbits_version": "1.3.0",
"contributors": [
{

View file

@ -32,6 +32,7 @@ async def create_withdraw_link(
webhook_headers=data.webhook_headers,
webhook_body=data.webhook_body,
custom_url=data.custom_url,
extra=data.extra,
number=0,
)
await db.insert("withdraw.withdraw_link", withdraw_link)

44
migrations_fork.py Normal file
View file

@ -0,0 +1,44 @@
"""
Fork-specific database migrations for the aiolabs withdraw extension.
These migrations are tracked separately under `withdraw_fork` in the
`dbversions` table (loaded by `lnbits/core/helpers.py:migrate_extension_database`),
so they do not collide with upstream's `m{NNN}_*` numbering in
`migrations.py`. Keeping the upstream-tracked file untouched means
`git pull upstream` stays rebase-clean for schema changes.
Conventions:
- Sequential numbering starting from m001.
- Each migration is `async def m{NNN}_<description>(db)`.
- DDL must be idempotent: a fresh install runs every migration; an
install that already carries the column must not crash. Use
`_alter_add_column_safe` so re-runs are no-ops.
"""
async def _alter_add_column_safe(db, sql: str) -> None:
"""ALTER TABLE ADD COLUMN that swallows duplicate-column errors, so a
re-run on a DB that already has the column is a silent no-op."""
try:
await db.execute(sql)
except Exception as exc:
msg = str(exc).lower()
if "duplicate column" in msg or "already exists" in msg:
return
raise
async def m001_aio_withdraw_schema(db):
"""
Apply every aiolabs schema delta on top of upstream withdraw.
`withdraw_link.extra` arbitrary JSON merged into the payout payment's
`extra` when the 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. Stored as TEXT; (de)serialized to a dict by the WithdrawLink model.
"""
await _alter_add_column_safe(
db, "ALTER TABLE withdraw.withdraw_link ADD COLUMN extra TEXT"
)

View file

@ -16,6 +16,12 @@ class CreateWithdrawData(BaseModel):
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):
@ -37,6 +43,10 @@ class WithdrawLink(BaseModel):
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(

View file

@ -141,7 +141,16 @@ async def api_lnurl_callback(
wallet_id=link.wallet,
payment_request=pr,
max_sat=link.max_withdrawable,
extra={"tag": "withdraw", "withdrawal_link_id": link.id},
# Merge the link's caller-supplied `extra` onto the payout so an
# external listener can key on it (e.g. bitSpire cash-in
# settlements via spirekeeper). The withdraw extension's own
# `tag`/`withdrawal_link_id` are written last so a caller cannot
# clobber them.
extra={
**(link.extra or {}),
"tag": "withdraw",
"withdrawal_link_id": link.id,
},
)
await increment_withdraw_link(link)
# If the payment succeeds, delete the record with the unique_hash.