payment notification webhook
This commit is contained in:
parent
30e0537270
commit
632d35682d
6 changed files with 53 additions and 6 deletions
|
|
@ -27,9 +27,10 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
|
||||||
k0,
|
k0,
|
||||||
k1,
|
k1,
|
||||||
k2,
|
k2,
|
||||||
otp
|
otp,
|
||||||
|
webhook_url
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
card_id,
|
card_id,
|
||||||
|
|
@ -45,6 +46,7 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
|
||||||
data.k1,
|
data.k1,
|
||||||
data.k2,
|
data.k2,
|
||||||
secrets.token_hex(16),
|
secrets.token_hex(16),
|
||||||
|
data.webhook_url,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
card = await get_card(card_id)
|
card = await get_card(card_id)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from io import BytesIO
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
from embit import bech32, compact
|
from embit import bech32, compact
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.param_functions import Query
|
from fastapi.param_functions import Query
|
||||||
|
|
@ -119,12 +120,32 @@ async def lnurl_callback(
|
||||||
invoice = bolt11.decode(pr)
|
invoice = bolt11.decode(pr)
|
||||||
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
|
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
|
||||||
try:
|
try:
|
||||||
await pay_invoice(
|
payment_hash = await pay_invoice(
|
||||||
wallet_id=card.wallet,
|
wallet_id=card.wallet,
|
||||||
payment_request=pr,
|
payment_request=pr,
|
||||||
max_sat=card.tx_limit,
|
max_sat=card.tx_limit,
|
||||||
extra={"tag": "boltcard", "tag": hit.id},
|
extra={"tag": "boltcard", "tag": hit.id},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if card.webhook_url:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
r = await client.post(
|
||||||
|
card.webhook_url,
|
||||||
|
json={
|
||||||
|
"notification": "card_payment",
|
||||||
|
"payment_hash": payment_hash,
|
||||||
|
"payment_request": pr,
|
||||||
|
"card_external_id": card.external_id,
|
||||||
|
"card_name": card.card_name,
|
||||||
|
"amount": int(invoice.amount_msat / 1000),
|
||||||
|
},
|
||||||
|
timeout=40,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
# webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
|
||||||
|
logger.error("Caught exception when dispatching webhook url:", exc)
|
||||||
|
|
||||||
return {"status": "OK"}
|
return {"status": "OK"}
|
||||||
except:
|
except:
|
||||||
return {"status": "ERROR", "reason": f"Payment failed"}
|
return {"status": "ERROR", "reason": f"Payment failed"}
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,11 @@ async def m001_initial(db):
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m002_add_webhook(db):
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE boltcards.cards ADD COLUMN webhook_url TEXT NOT NULL DEFAULT '';
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class Card(BaseModel):
|
||||||
prev_k1: str
|
prev_k1: str
|
||||||
prev_k2: str
|
prev_k2: str
|
||||||
otp: str
|
otp: str
|
||||||
|
webhook_url: str
|
||||||
time: int
|
time: int
|
||||||
|
|
||||||
def from_row(cls, row: Row) -> "Card":
|
def from_row(cls, row: Row) -> "Card":
|
||||||
|
|
@ -56,6 +57,7 @@ class CreateCardData(BaseModel):
|
||||||
prev_k0: str = Query(ZERO_KEY)
|
prev_k0: str = Query(ZERO_KEY)
|
||||||
prev_k1: str = Query(ZERO_KEY)
|
prev_k1: str = Query(ZERO_KEY)
|
||||||
prev_k2: str = Query(ZERO_KEY)
|
prev_k2: str = Query(ZERO_KEY)
|
||||||
|
webhook_url: str = Query(...)
|
||||||
|
|
||||||
|
|
||||||
class Hit(BaseModel):
|
class Hit(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ new Vue({
|
||||||
cardDialog: {
|
cardDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
|
webhook_url: '',
|
||||||
counter: 1,
|
counter: 1,
|
||||||
k0: '',
|
k0: '',
|
||||||
k1: '',
|
k1: '',
|
||||||
|
|
@ -270,7 +271,8 @@ new Vue({
|
||||||
k1: card.k1,
|
k1: card.k1,
|
||||||
k2: card.k2,
|
k2: card.k2,
|
||||||
k3: card.k1,
|
k3: card.k1,
|
||||||
k4: card.k2
|
k4: card.k2,
|
||||||
|
webhook_url: card.webhook_url
|
||||||
}
|
}
|
||||||
this.qrCodeDialog.show = true
|
this.qrCodeDialog.show = true
|
||||||
},
|
},
|
||||||
|
|
@ -398,7 +400,9 @@ new Vue({
|
||||||
let cards = _.findWhere(this.cards, {id: cardId})
|
let cards = _.findWhere(this.cards, {id: cardId})
|
||||||
|
|
||||||
LNbits.utils
|
LNbits.utils
|
||||||
.confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!')
|
.confirmDialog(
|
||||||
|
"Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!"
|
||||||
|
)
|
||||||
.onOk(function () {
|
.onOk(function () {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@
|
||||||
v-model="toggleAdvanced"
|
v-model="toggleAdvanced"
|
||||||
label="Show advanced options"
|
label="Show advanced options"
|
||||||
></q-toggle>
|
></q-toggle>
|
||||||
<div v-show="toggleAdvanced">
|
<div v-show="toggleAdvanced" class="q-gutter-y-md">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
|
|
@ -322,6 +322,14 @@
|
||||||
>Zero if you don't know.</q-tooltip
|
>Zero if you don't know.</q-tooltip
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.number="cardDialog.data.webhook_url"
|
||||||
|
type="text"
|
||||||
|
label="Notification webhook"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -380,6 +388,8 @@
|
||||||
<strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br />
|
<strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br />
|
||||||
<strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br />
|
<strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br />
|
||||||
<strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br />
|
<strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br />
|
||||||
|
<strong>Notification webhook:</strong> {{ qrCodeDialog.data.webhook_url
|
||||||
|
}}<br />
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue