Skip to content

Commit f8648fd

Browse files
authored
Merge pull request #112 from arcbtc/lnaddress
feat: lnaddress funding for pos
2 parents d1cb9f6 + f3334bc commit f8648fd

15 files changed

+251
-43
lines changed

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def tpos_start():
4242
__all__ = [
4343
"db",
4444
"tpos_ext",
45-
"tpos_static_files",
4645
"tpos_start",
46+
"tpos_static_files",
4747
"tpos_stop",
4848
]

crud.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from lnbits.db import Database
55
from lnbits.helpers import urlsafe_short_hash
6+
from loguru import logger
67

78
from .models import CreateTposData, LnurlCharge, Tpos, TposClean
89

@@ -78,9 +79,12 @@ async def get_tposs(wallet_ids: Union[str, list[str]]) -> list[Tpos]:
7879
if isinstance(wallet_ids, str):
7980
wallet_ids = [wallet_ids]
8081
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
81-
return await db.fetchall(
82+
tposs = await db.fetchall(
8283
f"SELECT * FROM tpos.pos WHERE wallet IN ({q})", model=Tpos
8384
)
85+
logger.debug("tposs")
86+
logger.debug(tposs)
87+
return tposs
8488

8589

8690
async def delete_tpos(tpos_id: str) -> None:

helpers.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import httpx
2+
from lnbits.core.views.api import api_lnurlscan
3+
4+
5+
async def get_pr(ln_address, amount):
6+
try:
7+
data = await api_lnurlscan(ln_address)
8+
if data.get("status") == "ERROR":
9+
return
10+
async with httpx.AsyncClient() as client:
11+
response = await client.get(
12+
url=f"{data['callback']}?amount={int(amount) * 1000}"
13+
)
14+
if response.status_code != 200:
15+
return
16+
return response.json()["pr"]
17+
except Exception:
18+
return None

migrations.py

+22
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,25 @@ async def m010_rename_tpos_withdraw_columns(db: Database):
165165
)
166166
await db.execute("DROP TABLE tpos.pos")
167167
await db.execute("ALTER TABLE tpos.pos_backup RENAME TO pos")
168+
169+
170+
async def m011_lnaddress(db: Database):
171+
"""
172+
Add lnaddress to tpos table
173+
"""
174+
await db.execute(
175+
"""
176+
ALTER TABLE tpos.pos ADD lnaddress BOOLEAN DEFAULT false;
177+
"""
178+
)
179+
180+
181+
async def m012_addlnaddress(db: Database):
182+
"""
183+
Add lnaddress_cut to tpos table
184+
"""
185+
await db.execute(
186+
"""
187+
ALTER TABLE tpos.pos ADD lnaddress_cut TEXT NULL;
188+
"""
189+
)

models.py

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class CreateTposInvoice(BaseModel):
1515
memo: Optional[str] = Query(None)
1616
details: Optional[dict] = Query(None)
1717
tip_amount: Optional[int] = Query(None, ge=1)
18+
user_lnaddress: Optional[str] = Query(None)
1819

1920

2021
class CreateTposData(BaseModel):
@@ -32,6 +33,8 @@ class CreateTposData(BaseModel):
3233
withdraw_time_option: Optional[str] = Field(None)
3334
withdraw_premium: Optional[float] = Field(None)
3435
withdraw_pin_disabled: bool = Field(False)
36+
lnaddress: bool = Field(False)
37+
lnaddress_cut: Optional[int] = Field(0)
3538

3639

3740
class TposClean(BaseModel):
@@ -47,6 +50,8 @@ class TposClean(BaseModel):
4750
withdraw_premium: Optional[float] = None
4851
withdraw_pin_disabled: Optional[bool] = None
4952
withdrawn_amount: int = 0
53+
lnaddress: Optional[bool] = None
54+
lnaddress_cut: int = 0
5055
items: Optional[str] = None
5156
tip_options: Optional[str] = None
5257

@@ -89,6 +94,10 @@ class PayLnurlWData(BaseModel):
8994
lnurl: str
9095

9196

97+
class LNaddress(BaseModel):
98+
lnaddress: str
99+
100+
92101
class Item(BaseModel):
93102
image: Optional[str]
94103
price: float

poetry.lock

+36-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ authors = ["Alan Bits <[email protected]>"]
77
[tool.poetry.dependencies]
88
python = "^3.10 | ^3.9"
99
lnbits = {version = "*", allow-prereleases = true}
10+
mypy = "^1.13.0"
1011

1112
[tool.poetry.group.dev.dependencies]
1213
black = "^24.3.0"

static/js/index.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ window.app = Vue.createApp({
4747
align: 'left',
4848
label: 'atm pin disabled',
4949
field: 'withdraw_pin_disabled'
50+
},
51+
{
52+
name: 'lnaddress',
53+
align: 'left',
54+
label: 'LNaddress',
55+
field: 'lnaddress'
56+
},
57+
{
58+
name: 'lnaddress_cut',
59+
align: 'left',
60+
label: 'LNaddress Cut',
61+
field: 'lnaddress_cut'
5062
}
5163
],
5264
pagination: {
@@ -71,7 +83,9 @@ window.app = Vue.createApp({
7183
withdraw_between: 10,
7284
withdraw_time_option: '',
7385
withdraw_pin_disabled: false,
74-
tax_inclusive: true
86+
tax_inclusive: true,
87+
lnaddress: false,
88+
lnaddress_cut: 2
7589
},
7690
advanced: {
7791
tips: false,
@@ -170,7 +184,6 @@ window.app = Vue.createApp({
170184
},
171185
getTposs: function () {
172186
var self = this
173-
174187
LNbits.api
175188
.request(
176189
'GET',

static/js/tpos.js

+59-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ window.app = Vue.createApp({
2121
atmMode: false,
2222
atmToken: '',
2323
nfcTagReading: false,
24+
lnaddressDialog: {
25+
show: false,
26+
lnaddress: ''
27+
},
2428
lastPaymentsDialog: {
2529
show: false,
2630
data: []
@@ -283,6 +287,29 @@ window.app = Vue.createApp({
283287
LNbits.utils.notifyApiError(error)
284288
})
285289
},
290+
lnaddressSubmit() {
291+
LNbits.api
292+
.request(
293+
'GET',
294+
`/tpos/api/v1/tposs/lnaddresscheck?lnaddress=${encodeURIComponent(this.lnaddressDialog.lnaddress)}`,
295+
null
296+
)
297+
.then(response => {
298+
if (response.data) {
299+
this.$q.localStorage.set(
300+
'tpos.lnaddress',
301+
this.lnaddressDialog.lnaddress
302+
)
303+
this.lnaddressDialog.show = false
304+
this.lnaddress = true
305+
}
306+
})
307+
.catch(error => {
308+
const errorMessage =
309+
error.response?.data?.detail || 'An unknown error occurred.'
310+
LNbits.utils.notifyApiError(errorMessage)
311+
})
312+
},
286313
atmGetWithdraw() {
287314
var dialog = this.invoiceDialog
288315
if (this.sat > this.withdrawMaximum) {
@@ -452,7 +479,9 @@ window.app = Vue.createApp({
452479
taxValue: this.cartTax
453480
}
454481
}
455-
482+
if (this.lnaddress) {
483+
params.user_lnaddress = this.lnaddressDialog.lnaddress
484+
}
456485
axios
457486
.post(`/tpos/api/v1/tposs/${this.tposId}/invoices`, params)
458487
.then(response => {
@@ -630,6 +659,7 @@ window.app = Vue.createApp({
630659
.request('GET', `/tpos/api/v1/rate/${this.currency}`)
631660
.then(response => {
632661
this.exchangeRate = response.data.rate
662+
console.log(this.exchangeRate)
633663
Quasar.Loading.hide()
634664
})
635665
.catch(e => console.error(e))
@@ -669,6 +699,15 @@ window.app = Vue.createApp({
669699
handleColorScheme(val) {
670700
this.$q.localStorage.set('lnbits.tpos.color', val)
671701
},
702+
clearLNaddress() {
703+
this.$q.localStorage.remove('tpos.lnaddress')
704+
this.lnaddressDialog.lnaddress = ''
705+
const url = new URL(window.location.href)
706+
url.searchParams.delete('lnaddress')
707+
window.history.replaceState({}, document.title, url.toString())
708+
this.lnaddressDialog.show = true
709+
this.lnaddress = false
710+
},
672711
extractCategories(items) {
673712
let categories = new Set()
674713
items
@@ -718,6 +757,8 @@ window.app = Vue.createApp({
718757
this.pinDisabled = tpos.withdraw_pin_disabled
719758
this.taxInclusive = tpos.tax_inclusive
720759
this.taxDefault = tpos.tax_default
760+
this.tposLNaddress = tpos.lnaddress
761+
this.tposLNaddressCut = tpos.lnaddress_cut
721762

722763
this.tip_options = tpos.tip_options == 'null' ? null : tpos.tip_options
723764

@@ -735,6 +776,23 @@ window.app = Vue.createApp({
735776
this.showPoS = false
736777
this.categories = this.extractCategories(this.items)
737778
}
779+
if (this.tposLNaddress) {
780+
this.lnaddressDialog.lnaddress =
781+
this.$q.localStorage.getItem('tpos.lnaddress')
782+
if (lnaddressparam != '') {
783+
this.lnaddressDialog.lnaddress = lnaddressparam
784+
this.$q.localStorage.set(
785+
'tpos.lnaddress',
786+
this.lnaddressDialog.lnaddress
787+
)
788+
this.lnaddress = true
789+
} else if (!this.lnaddressDialog.lnaddress) {
790+
this.lnaddress = false
791+
this.lnaddressDialog.show = true
792+
} else {
793+
this.lnaddress = true
794+
}
795+
}
738796

739797
window.addEventListener('keyup', event => {
740798
// do nothing if the event was already processed

tasks.py

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from loguru import logger
77

88
from .crud import get_tpos
9+
from .helpers import get_pr
910

1011

1112
async def wait_for_paid_invoices():
@@ -40,6 +41,17 @@ async def on_invoice_paid(payment: Payment) -> None:
4041

4142
tpos = await get_tpos(tpos_id)
4243
assert tpos
44+
if payment.extra.get("lnaddress") and payment.extra["lnaddress"] != "":
45+
calc_amount = payment.amount - ((payment.amount / 100) * tpos.lnaddress_cut)
46+
pr = await get_pr(payment.extra.get("lnaddress"), calc_amount / 1000)
47+
if pr:
48+
payment.extra["lnaddress"] = ""
49+
paid_payment = await pay_invoice(
50+
payment_request=pr,
51+
wallet_id=payment.wallet_id,
52+
extra={**payment.extra},
53+
)
54+
logger.debug(f"tpos: LNaddress paid cut: {paid_payment.checking_id}")
4355

4456
await websocket_updater(tpos_id, str(stripped_payment))
4557

0 commit comments

Comments
 (0)