blockstream-satellite-api/server/invoice_helpers.py
Blockstream Satellite 3d7a5b764d Add expired invoice error and rename paid error
- Add new error: INVOICE_ALREADY_EXPIRED.
- Rename ORDER_ALREADY_PAID to INVOICE_ALREADY_PAID.
2021-07-20 12:28:08 -03:00

151 lines
5.6 KiB
Python

from http import HTTPStatus
import datetime
import requests
from sqlalchemy import and_, func
from constants import InvoiceStatus
import constants
from database import db
from error import get_http_error_resp
from models import Invoice
from order_helpers import maybe_mark_order_as_expired, maybe_mark_order_as_paid
from utils import hmac_sha256_digest
def new_invoice(order, bid):
"""Generate a lightning invoice
Args:
order: The order for which this invoice is being generated.
bid: Bid amount.
Returns:
A pair whose first element is a boolean indicating whether
the invoice generation was successful or not. If False, then
the second element is the error key. If True, then the second
element is the newly generated invoice.
"""
try:
bid = int(bid)
# generate Lightning invoice
charged_response = requests.post(f"{constants.CHARGE_ROOT}/invoice",
json={
'msatoshi': bid,
'description':
constants.LN_INVOICE_DESCRIPTION,
'expiry':
constants.LN_INVOICE_EXPIRY,
'metadata': {
'uuid':
order.uuid,
'sha256_message_digest':
order.message_digest
}
},
timeout=(constants.CONNECTION_TIMEOUT,
constants.RESPONSE_TIMEOUT))
except requests.exceptions.RequestException:
return False, get_http_error_resp('LIGHTNING_CHARGE_INVOICE_ERROR')
except ValueError:
return False, get_http_error_resp('PARAM_COERCION', 'bid')
if charged_response.status_code != HTTPStatus.CREATED:
return False, get_http_error_resp('LIGHTNING_CHARGE_INVOICE_ERROR')
lightning_invoice = charged_response.json()
if 'id' not in lightning_invoice:
return False, get_http_error_resp('LIGHTNING_CHARGE_INVOICE_ERROR')
invoice = Invoice(order_id=order.id,
amount=bid,
lid=lightning_invoice['id'],
invoice=charged_response.content,
status=InvoiceStatus.pending.value,
expires_at=datetime.datetime.utcnow() +
datetime.timedelta(seconds=constants.LN_INVOICE_EXPIRY))
try:
# register the webhook
charged_auth_token = hmac_sha256_digest(
constants.LIGHTNING_WEBHOOK_KEY, invoice.lid)
callback_url = f"{constants.CALLBACK_URI_ROOT}/callback\
/{invoice.lid}/{charged_auth_token}"
webhook_registration_response = requests.post(
f"{constants.CHARGE_ROOT}/invoice/{invoice.lid}/webhook",
json={'url': callback_url},
timeout=(constants.CONNECTION_TIMEOUT, constants.RESPONSE_TIMEOUT))
if webhook_registration_response.status_code != HTTPStatus.CREATED:
return False, get_http_error_resp(
'LIGHTNING_CHARGE_WEBHOOK_REGISTRATION_ERROR')
except requests.exceptions.RequestException:
return False, get_http_error_resp(
'LIGHTNING_CHARGE_WEBHOOK_REGISTRATION_ERROR')
return True, invoice
def get_and_authenticate_invoice(lid, charged_auth_token):
invoice = Invoice.query.filter_by(lid=lid).first()
if invoice is None:
return False, get_http_error_resp('INVOICE_ID_NOT_FOUND_ERROR', lid)
db_invoice_charged_auth_token = hmac_sha256_digest(
constants.LIGHTNING_WEBHOOK_KEY, invoice.lid)
if db_invoice_charged_auth_token != charged_auth_token:
return False, get_http_error_resp('INVALID_AUTH_TOKEN')
return True, invoice
def pay_invoice(invoice):
if invoice.status == InvoiceStatus.paid.value:
return get_http_error_resp('INVOICE_ALREADY_PAID')
if invoice.status == InvoiceStatus.expired.value:
return get_http_error_resp('INVOICE_ALREADY_EXPIRED')
invoice.status = InvoiceStatus.paid.value
invoice.paid_at = datetime.datetime.utcnow()
db.session.commit()
maybe_mark_order_as_paid(invoice.order_id)
return None
def get_pending_invoices(order_id):
return Invoice.query.filter(
and_(Invoice.order_id == order_id,
Invoice.status == constants.InvoiceStatus.pending.value)).all()
def expire_invoice(invoice):
if invoice.status == InvoiceStatus.pending.value:
invoice.status = constants.InvoiceStatus.expired.value
db.session.commit()
def expire_unpaid_invoices():
"""Expire unpaid invoices
Expire any unpaid invoice that has reached its expiration time. The
corresponding order may be auto-expired as a result.
Returns:
Tuple with the list of invoices and the list of orders that got expired
by this function.
"""
invoices_to_expire = Invoice.query.filter(
and_(Invoice.status == constants.InvoiceStatus.pending.value,
func.datetime(Invoice.expires_at) <
datetime.datetime.utcnow())).all()
expired_orders = []
for invoice in invoices_to_expire:
expire_invoice(invoice)
expired_order = maybe_mark_order_as_expired(invoice.order_id)
if (expired_order is not None):
expired_orders.append(expired_order)
return invoices_to_expire, expired_orders