Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 0 additions & 68 deletions src/lib/api/strike.py

This file was deleted.

157 changes: 132 additions & 25 deletions src/lib/payments.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
from abc import ABC, abstractmethod
from .utils import http_request, try_get
from lib.utils import try_get
import httpx
from env import PAYMENT_PROCESSOR_KIND, PAYMENT_PROCESSOR_TOKEN, LNBITS_BASE_URL


def init_payment_processor():
available_processors = ["strike", "lnbits", "opennode"]

if (
PAYMENT_PROCESSOR_KIND is None
or PAYMENT_PROCESSOR_KIND not in available_processors
):
raise Exception(
f"PAYMENT_PROCESSOR_KIND must be one of {', '.join(available_processors)}"
)

if not PAYMENT_PROCESSOR_TOKEN.trim():
raise Exception("PAYMENT_PROCESSOR_TOKEN must be a valid API token")

if PAYMENT_PROCESSOR_KIND == "strike":
return Strike(PAYMENT_PROCESSOR_TOKEN)
elif PAYMENT_PROCESSOR_KIND == "lnbits":
return LNbits(LNBITS_BASE_URL, PAYMENT_PROCESSOR_TOKEN)
elif PAYMENT_PROCESSOR_KIND == "opennode":
return OpenNode(PAYMENT_PROCESSOR_TOKEN)


class Processor(ABC):
Expand All @@ -25,52 +49,135 @@ class Strike(Processor):
A Strike payment processor
"""

_base_url = "https://api.strike.me/v1"

def __init__(self, api_key):
super().__init__()
assert (api_key is not None, "a Strike API key must be supplied")
self.api_key = api_key
self._client = httpx.AsyncClient(
base_url="https://api.strike.me/v1",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Bearer {api_key}",
},
)

def get_invoice(self, correlation_id, description):
invoice_resp = self.__make_request(
"POST",
f"{self._base_url}/invoices",
{
async def get_invoice(self, correlation_id, description):
invoice_resp = await self._client.post(
"/invoices",
json={
"correlationId": correlation_id,
"description": description,
"amount": {"amount": "1.00", "currency": "USD"},
},
)
invoice_id = try_get(invoice_resp, "invoiceId")
quote_resp = self.__make_request(
"POST", f"{self._base_url}/invoices/{invoice_id}/quote"
)
quote_resp = await self._client.post(f"/invoices/{invoice_id}/quote")

return (
invoice_id,
try_get(quote_resp, "lnInvoice"),
try_get(quote_resp, "expirationInSec"),
)

def invoice_is_paid(self, invoice_id):
resp = self.__make_request("GET", f"{self._base_url}/invoices/{invoice_id}")
async def invoice_is_paid(self, invoice_id):
resp = await self._client.get(f"/invoices/{invoice_id}")
return try_get(resp, "state") == "PAID"

def expire_invoice(self, invoice_id):
resp = self.__make_request(
"PATCH", f"{self._base_url}/invoices/{invoice_id}/cancel"
)
async def expire_invoice(self, invoice_id):
resp = await self._client.patch(f"/invoices/{invoice_id}/cancel")
return try_get(resp, "state") == "CANCELLED"

def __make_request(self, method, path, body):
return http_request(
{

class LNbits(Processor):
"""
An LNbits payment processor
"""

def __init__(self, base_url, api_key):
super().__init__()
assert (base_url is not None, "an LNbits base URL must be supplied")
assert (api_key is not None, "an LNbits API key must be supplied")
self._client = httpx.AsyncClient(
base_url=f"{base_url}/api/v1",
headers={
"Content-Type": "application/json",
"X-Api-Key": api_key,
},
)

async def get_invoice(self, correlation_id, description):
create_resp = await self._client.post(
"/payments",
json={
"out": False,
"amount": 1,
"unit": "USD",
"unhashed_description": description.encode("utf-8").hex(),
"extra": {
"correlationId": correlation_id,
},
},
)
payment_request = try_get(create_resp, "payment_request")
payment_hash = try_get(create_resp, "payment_hash")
payment_resp = await self._client.get(f"/payments/{payment_hash}")

return (
payment_hash,
payment_request,
int(try_get(payment_resp, "details", "expiry")),
)

async def invoice_is_paid(self, payment_hash):
resp = await self._client.get(f"/payments/{payment_hash}")
return try_get(resp, "paid")

async def expire_invoice(self, invoice_id):
"""LNbits doesn't seem to have an explicit way to expire an invoice"""
pass


class OpenNode(Processor):
"""
An OpenNode payment processor
"""

def __init__(self, api_key):
super().__init__()
assert (
api_key is not None,
"an OpenNode API key with invoice permissions must be supplied",
)
self._client = httpx.AsyncClient(
base_url="https://api.opennode.com",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Bearer {self.api_key}",
"Authorization": api_key,
},
)

async def get_invoice(self, correlation_id, description):
resp = await self._client.post(
"/v1/charges",
json={
"amount": 1,
"currency": "USD",
"order_id": correlation_id,
"description": description,
},
method,
path,
body,
)

return (
try_get(resp, "data", "id"),
try_get(resp, "data", "lightning_invoice", "payreq"),
try_get(resp, "data", "ttl"),
)

async def invoice_is_paid(self, invoice_id):
resp = await self._client.get(f"/v2/charge/{invoice_id}")
return try_get(resp, "data", "status") == "paid"

async def expire_invoice(self, invoice_id):
"""OpenNode doesn't seem to have an explicit way to expire an invoice"""
pass