New Extension: Invoicing (#733)

* initial commit

* add docs

* black & prettier

* mobile styles

* add print view

* prettier

* make format

* initial migrations un-messed

* make migrations work for sqlite

* add invoices table

* clean migrations

* add migration to conv

* fix card size

* hopefully fix test migration

* add missing status

* timestamp

* init testing

* remove draft invoice by default on create

* what should i test

* make format

* raise if not invoice

* new test and renaming

* fix issue reported by @talvasconcelos which prevented users from setting status on creation

* readme

* run black

* trying to make tests work

* make it work again

* send paid amount

* partial pay flow

* good coding

* can't get these test to work

* clean up and commenting

* make format

* validation for 2 decimals

Co-authored-by: ben <ben@arc.wales>
Co-authored-by: Tiago vasconcelos <talvasconcelos@gmail.com>
This commit is contained in:
Lee Salminen 2022-08-13 13:37:44 -06:00 committed by GitHub
parent 197ff7d054
commit c32ff1de59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 2053 additions and 0 deletions

Binary file not shown.

View file

View file

@ -0,0 +1,37 @@
import pytest
import pytest_asyncio
from lnbits.core.crud import create_account, create_wallet
from lnbits.extensions.invoices.crud import (
create_invoice_internal,
create_invoice_items,
)
from lnbits.extensions.invoices.models import CreateInvoiceData
@pytest_asyncio.fixture
async def invoices_wallet():
user = await create_account()
wallet = await create_wallet(user_id=user.id, wallet_name="invoices_test")
return wallet
@pytest_asyncio.fixture
async def accounting_invoice(invoices_wallet):
invoice_data = CreateInvoiceData(
status="open",
currency="USD",
company_name="LNBits, Inc",
first_name="Ben",
last_name="Arc",
items=[{"amount": 10.20, "description": "Item costs 10.20"}],
)
invoice = await create_invoice_internal(
wallet_id=invoices_wallet.id, data=invoice_data
)
items = await create_invoice_items(invoice_id=invoice.id, data=invoice_data.items)
invoice_dict = invoice.dict()
invoice_dict["items"] = items
return invoice_dict

View file

@ -0,0 +1,135 @@
import pytest
import pytest_asyncio
from loguru import logger
from lnbits.core.crud import get_wallet
from tests.conftest import adminkey_headers_from, client, invoice
from tests.extensions.invoices.conftest import accounting_invoice, invoices_wallet
from tests.helpers import credit_wallet
from tests.mocks import WALLET
@pytest.mark.asyncio
async def test_invoices_unknown_invoice(client):
response = await client.get("/invoices/pay/u")
assert response.json() == {"detail": "Invoice does not exist."}
@pytest.mark.asyncio
async def test_invoices_api_create_invoice_valid(client, invoices_wallet):
query = {
"status": "open",
"currency": "EUR",
"company_name": "LNBits, Inc.",
"first_name": "Ben",
"last_name": "Arc",
"email": "ben@legend.arc",
"items": [
{"amount": 2.34, "description": "Item 1"},
{"amount": 0.98, "description": "Item 2"},
],
}
status = query["status"]
currency = query["currency"]
fname = query["first_name"]
total = sum(d["amount"] for d in query["items"])
response = await client.post(
"/invoices/api/v1/invoice",
json=query,
headers={"X-Api-Key": invoices_wallet.inkey},
)
assert response.status_code == 201
data = response.json()
assert data["status"] == status
assert data["wallet"] == invoices_wallet.id
assert data["currency"] == currency
assert data["first_name"] == fname
assert sum(d["amount"] / 100 for d in data["items"]) == total
@pytest.mark.asyncio
async def test_invoices_api_partial_pay_invoice(
client, accounting_invoice, adminkey_headers_from
):
invoice_id = accounting_invoice["id"]
amount_to_pay = int(5.05 * 100) # mock invoice total amount is 10 USD
# ask for an invoice
response = await client.post(
f"/invoices/api/v1/invoice/{invoice_id}/payments?famount={amount_to_pay}"
)
assert response.status_code < 300
data = response.json()
payment_hash = data["payment_hash"]
# pay the invoice
data = {"out": True, "bolt11": data["payment_request"]}
response = await client.post(
"/api/v1/payments", json=data, headers=adminkey_headers_from
)
assert response.status_code < 300
assert len(response.json()["payment_hash"]) == 64
assert len(response.json()["checking_id"]) > 0
# check invoice is paid
response = await client.get(
f"/invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash}"
)
assert response.status_code == 200
assert response.json()["paid"] == True
# check invoice status
response = await client.get(f"/invoices/api/v1/invoice/{invoice_id}")
assert response.status_code == 200
data = response.json()
assert data["status"] == "open"
####
#
# TEST FAILS FOR NOW, AS LISTENERS ARE NOT WORKING ON TESTING
#
###
# @pytest.mark.asyncio
# async def test_invoices_api_full_pay_invoice(client, accounting_invoice, adminkey_headers_to):
# invoice_id = accounting_invoice["id"]
# print(accounting_invoice["id"])
# amount_to_pay = int(10.20 * 100)
# # ask for an invoice
# response = await client.post(
# f"/invoices/api/v1/invoice/{invoice_id}/payments?famount={amount_to_pay}"
# )
# assert response.status_code == 201
# data = response.json()
# payment_hash = data["payment_hash"]
# # pay the invoice
# data = {"out": True, "bolt11": data["payment_request"]}
# response = await client.post(
# "/api/v1/payments", json=data, headers=adminkey_headers_to
# )
# assert response.status_code < 300
# assert len(response.json()["payment_hash"]) == 64
# assert len(response.json()["checking_id"]) > 0
# # check invoice is paid
# response = await client.get(
# f"/invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash}"
# )
# assert response.status_code == 200
# assert response.json()["paid"] == True
# # check invoice status
# response = await client.get(f"/invoices/api/v1/invoice/{invoice_id}")
# assert response.status_code == 200
# data = response.json()
# print(data)
# assert data["status"] == "paid"