diff --git a/lnbits/extensions/satspay/README.md b/lnbits/extensions/satspay/README.md
deleted file mode 100644
index 4fb249806..000000000
--- a/lnbits/extensions/satspay/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# SatsPay Server
-
-## Create onchain and LN charges. Includes webhooks!
-
-Easilly create invoices that support Lightning Network and on-chain BTC payment.
-
-1. Create a "NEW CHARGE"\
- 
-2. Fill out the invoice fields
- - set a descprition for the payment
- - the amount in sats
- - the time, in minutes, the invoice is valid for, after this period the invoice can't be payed
- - set a webhook that will get the transaction details after a successful payment
- - set to where the user should redirect after payment
- - set the text for the button that will show after payment (not setting this, will display "NONE" in the button)
- - select if you want onchain payment, LN payment or both
- - depending on what you select you'll have to choose the respective wallets where to receive your payment\
- 
-3. The charge will appear on the _Charges_ section\
- 
-4. Your customer/payee will get the payment page
- - they can choose to pay on LN\
- 
- - or pay on chain\
- 
-5. You can check the state of your charges in LNbits\
- 
diff --git a/lnbits/extensions/satspay/__init__.py b/lnbits/extensions/satspay/__init__.py
deleted file mode 100644
index 8f115a3cf..000000000
--- a/lnbits/extensions/satspay/__init__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import asyncio
-
-from fastapi import APIRouter
-from fastapi.staticfiles import StaticFiles
-
-from lnbits.db import Database
-from lnbits.helpers import template_renderer
-from lnbits.tasks import catch_everything_and_restart
-
-db = Database("ext_satspay")
-
-
-satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"])
-
-satspay_static_files = [
- {
- "path": "/satspay/static",
- "app": StaticFiles(directory="lnbits/extensions/satspay/static"),
- "name": "satspay_static",
- }
-]
-
-
-def satspay_renderer():
- return template_renderer(["lnbits/extensions/satspay/templates"])
-
-
-from .tasks import wait_for_paid_invoices
-from .views import * # noqa: F401,F403
-from .views_api import * # noqa: F401,F403
-
-
-def satspay_start():
- loop = asyncio.get_event_loop()
- loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/satspay/config.json b/lnbits/extensions/satspay/config.json
deleted file mode 100644
index 6104d3609..000000000
--- a/lnbits/extensions/satspay/config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "SatsPay Server",
- "short_description": "Create onchain and LN charges",
- "tile": "/satspay/static/image/satspay.png",
- "contributors": ["arcbtc"]
-}
diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py
deleted file mode 100644
index c13d0a4b8..000000000
--- a/lnbits/extensions/satspay/crud.py
+++ /dev/null
@@ -1,181 +0,0 @@
-import json
-from typing import List, Optional
-
-from loguru import logger
-
-from lnbits.core.services import create_invoice
-from lnbits.core.views.api import api_payment
-from lnbits.helpers import urlsafe_short_hash
-
-from ..watchonly.crud import get_config, get_fresh_address # type: ignore
-from . import db
-from .helpers import fetch_onchain_balance
-from .models import Charges, CreateCharge, SatsPayThemes
-
-
-async def create_charge(user: str, data: CreateCharge) -> Charges:
- data = CreateCharge(**data.dict())
- charge_id = urlsafe_short_hash()
- if data.onchainwallet:
- config = await get_config(user)
- assert config
- data.extra = json.dumps(
- {"mempool_endpoint": config.mempool_endpoint, "network": config.network}
- )
- onchain = await get_fresh_address(data.onchainwallet)
- if not onchain:
- raise Exception(f"Wallet '{data.onchainwallet}' can no longer be accessed.")
- onchainaddress = onchain.address
- else:
- onchainaddress = None
- if data.lnbitswallet:
- payment_hash, payment_request = await create_invoice(
- wallet_id=data.lnbitswallet,
- amount=data.amount,
- memo=charge_id,
- extra={"tag": "charge"},
- )
- else:
- payment_hash = None
- payment_request = None
- await db.execute(
- """
- INSERT INTO satspay.charges (
- id,
- "user",
- description,
- onchainwallet,
- onchainaddress,
- lnbitswallet,
- payment_request,
- payment_hash,
- webhook,
- completelink,
- completelinktext,
- time,
- amount,
- balance,
- extra,
- custom_css
- )
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- charge_id,
- user,
- data.description,
- data.onchainwallet,
- onchainaddress,
- data.lnbitswallet,
- payment_request,
- payment_hash,
- data.webhook,
- data.completelink,
- data.completelinktext,
- data.time,
- data.amount,
- 0,
- data.extra,
- data.custom_css,
- ),
- )
- charge = await get_charge(charge_id)
- assert charge, "Newly created charge does not exist"
- return charge
-
-
-async def update_charge(charge_id: str, **kwargs) -> Optional[Charges]:
- q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- await db.execute(
- f"UPDATE satspay.charges SET {q} WHERE id = ?", (*kwargs.values(), charge_id)
- )
- row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
- return Charges.from_row(row) if row else None
-
-
-async def get_charge(charge_id: str) -> Optional[Charges]:
- row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
- return Charges.from_row(row) if row else None
-
-
-async def get_charges(user: str) -> List[Charges]:
- rows = await db.fetchall(
- """SELECT * FROM satspay.charges WHERE "user" = ? ORDER BY "timestamp" DESC """,
- (user,),
- )
- return [Charges.from_row(row) for row in rows]
-
-
-async def delete_charge(charge_id: str) -> None:
- await db.execute("DELETE FROM satspay.charges WHERE id = ?", (charge_id,))
-
-
-async def check_address_balance(charge_id: str) -> Optional[Charges]:
- charge = await get_charge(charge_id)
- assert charge
-
- if not charge.paid:
- if charge.onchainaddress:
- try:
- respAmount = await fetch_onchain_balance(charge)
- if respAmount > charge.balance:
- await update_charge(charge_id=charge_id, balance=respAmount)
- except Exception as e:
- logger.warning(e)
- if charge.lnbitswallet:
- invoice_status = await api_payment(charge.payment_hash)
-
- if invoice_status["paid"]:
- return await update_charge(charge_id=charge_id, balance=charge.amount)
- return await get_charge(charge_id)
-
-
-################## SETTINGS ###################
-
-
-async def save_theme(data: SatsPayThemes, css_id: Optional[str]):
- # insert or update
- if css_id:
- await db.execute(
- """
- UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ?
- """,
- (data.custom_css, data.title, css_id),
- )
- else:
- css_id = urlsafe_short_hash()
- await db.execute(
- """
- INSERT INTO satspay.themes (
- css_id,
- title,
- "user",
- custom_css
- )
- VALUES (?, ?, ?, ?)
- """,
- (
- css_id,
- data.title,
- data.user,
- data.custom_css,
- ),
- )
- return await get_theme(css_id)
-
-
-async def get_theme(css_id: str) -> Optional[SatsPayThemes]:
- row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,))
- return SatsPayThemes.from_row(row) if row else None
-
-
-async def get_themes(user_id: str) -> List[SatsPayThemes]:
- rows = await db.fetchall(
- """SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "title" DESC """,
- (user_id,),
- )
- return [SatsPayThemes.from_row(row) for row in rows]
-
-
-async def delete_theme(theme_id: str) -> None:
- await db.execute("DELETE FROM satspay.themes WHERE css_id = ?", (theme_id,))
diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py
deleted file mode 100644
index 1967c79d9..000000000
--- a/lnbits/extensions/satspay/helpers.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import httpx
-from loguru import logger
-
-from .models import Charges
-
-
-def public_charge(charge: Charges):
- c = {
- "id": charge.id,
- "description": charge.description,
- "onchainaddress": charge.onchainaddress,
- "payment_request": charge.payment_request,
- "payment_hash": charge.payment_hash,
- "time": charge.time,
- "amount": charge.amount,
- "balance": charge.balance,
- "paid": charge.paid,
- "timestamp": charge.timestamp,
- "time_elapsed": charge.time_elapsed,
- "time_left": charge.time_left,
- "custom_css": charge.custom_css,
- }
-
- if charge.paid:
- c["completelink"] = charge.completelink
- c["completelinktext"] = charge.completelinktext
-
- return c
-
-
-async def call_webhook(charge: Charges):
- async with httpx.AsyncClient() as client:
- try:
- assert charge.webhook
- r = await client.post(
- charge.webhook,
- json=public_charge(charge),
- timeout=40,
- )
- return {
- "webhook_success": r.is_success,
- "webhook_message": r.reason_phrase,
- "webhook_response": r.text,
- }
- except Exception as e:
- logger.warning(f"Failed to call webhook for charge {charge.id}")
- logger.warning(e)
- return {"webhook_success": False, "webhook_message": str(e)}
-
-
-async def fetch_onchain_balance(charge: Charges):
- endpoint = (
- f"{charge.config.mempool_endpoint}/testnet"
- if charge.config.network == "Testnet"
- else charge.config.mempool_endpoint
- )
- assert endpoint
- assert charge.onchainaddress
- async with httpx.AsyncClient() as client:
- r = await client.get(endpoint + "/api/address/" + charge.onchainaddress)
- return r.json()["chain_stats"]["funded_txo_sum"]
diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py
deleted file mode 100644
index 488754699..000000000
--- a/lnbits/extensions/satspay/migrations.py
+++ /dev/null
@@ -1,64 +0,0 @@
-async def m001_initial(db):
- """
- Initial wallet table.
- """
-
- await db.execute(
- f"""
- CREATE TABLE satspay.charges (
- id TEXT NOT NULL PRIMARY KEY,
- "user" TEXT,
- description TEXT,
- onchainwallet TEXT,
- onchainaddress TEXT,
- lnbitswallet TEXT,
- payment_request TEXT,
- payment_hash TEXT,
- webhook TEXT,
- completelink TEXT,
- completelinktext TEXT,
- time INTEGER,
- amount {db.big_int},
- balance {db.big_int} DEFAULT 0,
- timestamp TIMESTAMP NOT NULL DEFAULT """
- + db.timestamp_now
- + """
- );
- """
- )
-
-
-async def m002_add_charge_extra_data(db):
- """
- Add 'extra' column for storing various config about the charge (JSON format)
- """
- await db.execute(
- """ALTER TABLE satspay.charges
- ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}';
- """
- )
-
-
-async def m003_add_themes_table(db):
- """
- Themes table
- """
-
- await db.execute(
- """
- CREATE TABLE satspay.themes (
- css_id TEXT NOT NULL PRIMARY KEY,
- "user" TEXT,
- title TEXT,
- custom_css TEXT
- );
- """
- )
-
-
-async def m004_add_custom_css_to_charges(db):
- """
- Add custom css option column to the 'charges' table
- """
-
- await db.execute("ALTER TABLE satspay.charges ADD COLUMN custom_css TEXT;")
diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py
deleted file mode 100644
index c9da401a2..000000000
--- a/lnbits/extensions/satspay/models.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import json
-from datetime import datetime, timedelta
-from sqlite3 import Row
-from typing import Optional
-
-from fastapi.param_functions import Query
-from pydantic import BaseModel
-
-DEFAULT_MEMPOOL_CONFIG = (
- '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}'
-)
-
-
-class CreateCharge(BaseModel):
- onchainwallet: str = Query(None)
- lnbitswallet: str = Query(None)
- description: str = Query(...)
- webhook: str = Query(None)
- completelink: str = Query(None)
- completelinktext: str = Query(None)
- custom_css: Optional[str]
- time: int = Query(..., ge=1)
- amount: int = Query(..., ge=1)
- extra: str = DEFAULT_MEMPOOL_CONFIG
-
-
-class ChargeConfig(BaseModel):
- mempool_endpoint: Optional[str]
- network: Optional[str]
- webhook_success: Optional[bool] = False
- webhook_message: Optional[str]
-
-
-class Charges(BaseModel):
- id: str
- description: Optional[str]
- onchainwallet: Optional[str]
- onchainaddress: Optional[str]
- lnbitswallet: Optional[str]
- payment_request: Optional[str]
- payment_hash: Optional[str]
- webhook: Optional[str]
- completelink: Optional[str]
- completelinktext: Optional[str] = "Back to Merchant"
- custom_css: Optional[str]
- extra: str = DEFAULT_MEMPOOL_CONFIG
- time: int
- amount: int
- balance: int
- timestamp: int
-
- @classmethod
- def from_row(cls, row: Row) -> "Charges":
- return cls(**dict(row))
-
- @property
- def time_left(self):
- now = datetime.utcnow().timestamp()
- start = datetime.fromtimestamp(self.timestamp)
- expiration = (start + timedelta(minutes=self.time)).timestamp()
- return (expiration - now) / 60
-
- @property
- def time_elapsed(self):
- return self.time_left < 0
-
- @property
- def paid(self):
- if self.balance >= self.amount:
- return True
- else:
- return False
-
- @property
- def config(self) -> ChargeConfig:
- charge_config = json.loads(self.extra)
- return ChargeConfig(**charge_config)
-
- def must_call_webhook(self):
- return self.webhook and self.paid and self.config.webhook_success is False
-
-
-class SatsPayThemes(BaseModel):
- css_id: str = Query(None)
- title: str = Query(None)
- custom_css: str = Query(None)
- user: Optional[str]
-
- @classmethod
- def from_row(cls, row: Row) -> "SatsPayThemes":
- return cls(**dict(row))
diff --git a/lnbits/extensions/satspay/static/image/satspay.png b/lnbits/extensions/satspay/static/image/satspay.png
deleted file mode 100644
index 827914075..000000000
Binary files a/lnbits/extensions/satspay/static/image/satspay.png and /dev/null differ
diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js
deleted file mode 100644
index 5317673f0..000000000
--- a/lnbits/extensions/satspay/static/js/utils.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const sleep = ms => new Promise(r => setTimeout(r, ms))
-const retryWithDelay = async function (fn, retryCount = 0) {
- try {
- await sleep(25)
- // Do not return the call directly, use result.
- // Otherwise the error will not be cought in this try-catch block.
- const result = await fn()
- return result
- } catch (err) {
- if (retryCount > 100) throw err
- await sleep((retryCount + 1) * 1000)
- return retryWithDelay(fn, retryCount + 1)
- }
-}
-
-const mapCharge = (obj, oldObj = {}) => {
- const charge = {...oldObj, ...obj}
-
- charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time
- charge.time = minutesToTime(obj.time)
- charge.timeLeft = minutesToTime(obj.time_left)
-
- charge.displayUrl = ['/satspay/', obj.id].join('')
- charge.expanded = oldObj.expanded || false
- charge.pendingBalance = oldObj.pendingBalance || 0
- charge.extra = charge.extra ? JSON.parse(charge.extra) : charge.extra
- return charge
-}
-
-const mapCSS = (obj, oldObj = {}) => {
- const theme = _.clone(obj)
- return theme
-}
-
-const minutesToTime = min =>
- min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : ''
diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py
deleted file mode 100644
index 2c636351b..000000000
--- a/lnbits/extensions/satspay/tasks.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import asyncio
-import json
-
-from loguru import logger
-
-from lnbits.core.models import Payment
-from lnbits.helpers import get_current_extension_name
-from lnbits.tasks import register_invoice_listener
-
-from .crud import check_address_balance, get_charge, update_charge
-from .helpers import call_webhook
-
-
-async def wait_for_paid_invoices():
- invoice_queue = asyncio.Queue()
- register_invoice_listener(invoice_queue, get_current_extension_name())
-
- while True:
- payment = await invoice_queue.get()
- await on_invoice_paid(payment)
-
-
-async def on_invoice_paid(payment: Payment) -> None:
-
- if payment.extra.get("tag") != "charge":
- # not a charge invoice
- return
-
- assert payment.memo
- charge = await get_charge(payment.memo)
- if not charge:
- logger.error("this should never happen", payment)
- return
-
- await payment.set_pending(False)
- charge = await check_address_balance(charge_id=charge.id)
- assert charge
-
- if charge.must_call_webhook():
- resp = await call_webhook(charge)
- extra = {**charge.config.dict(), **resp}
- await update_charge(charge_id=charge.id, extra=json.dumps(extra))
diff --git a/lnbits/extensions/satspay/templates/satspay/_api_docs.html b/lnbits/extensions/satspay/templates/satspay/_api_docs.html
deleted file mode 100644
index 2adab8c1f..000000000
--- a/lnbits/extensions/satspay/templates/satspay/_api_docs.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
- SatsPayServer, create Onchain/LN charges.
WARNING: If using with the
- WatchOnly extension, we highly reccomend using a fresh extended public Key
- specifically for SatsPayServer!
-
- Created by,
- Ben Arc,
- motorina0
-
-
- Swagger REST API Documentation
-