Skip to content

Commit

Permalink
feat(manual-payments): Add manual payments
Browse files Browse the repository at this point in the history
  • Loading branch information
ivannovosad committed Feb 19, 2025
1 parent 974cf8a commit e12c1c7
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 1 deletion.
5 changes: 5 additions & 0 deletions lago_python_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .organizations.clients import OrganizationClient
from .overdue_balances.clients import OverdueBalanceClient
from .payment_requests.clients import PaymentRequestClient
from .payments.clients import PaymentClient
from .invoice_collections.clients import InvoiceCollectionClient
from .plans.clients import PlanClient
from .subscriptions.clients import SubscriptionClient
Expand Down Expand Up @@ -118,6 +119,10 @@ def overdue_balances(self) -> OverdueBalanceClient:
def payment_requests(self) -> PaymentRequestClient:
return PaymentRequestClient(self.base_api_url, self.api_key)

@callable_cached_property
def payments(self) -> PaymentClient:
return PaymentClient(self.base_api_url, self.api_key)

@callable_cached_property
def invoice_collections(self) -> InvoiceCollectionClient:
return InvoiceCollectionClient(self.base_api_url, self.api_key)
Expand Down
1 change: 1 addition & 0 deletions lago_python_client/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@
UsageThresholdsResponse as UsageThresholdsResponse,
)
from .payment_request import PaymentRequest as PaymentRequest
from .payment import Payment as Payment
from .lifetime_usage import LifetimeUsageResponse as LifetimeUsageResponse
1 change: 1 addition & 0 deletions lago_python_client/models/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class InvoiceResponse(BaseResponseModel):
sub_total_excluding_taxes_amount_cents: int
sub_total_including_taxes_amount_cents: int
total_amount_cents: int
total_due_amount_cents: int
prepaid_credit_amount_cents: int

file_url: Optional[str]
Expand Down
26 changes: 26 additions & 0 deletions lago_python_client/models/payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import List, Optional

from ..base_model import BaseModel, BaseResponseModel


class Payment(BaseModel):
invoice_id: str
amount_cents: int
reference: str
paid_at: Optional[str]


class PaymentResponse(BaseResponseModel):
lago_id: str
invoice_ids: List[str]
amount_cents: int
amount_currency: str
payment_status: str
type: str
reference: str
external_payment_id: Optional[str]
created_at: str


class PaymentsResponse(BaseResponseModel):
__root__: List[PaymentResponse]
Empty file.
16 changes: 16 additions & 0 deletions lago_python_client/payments/clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import ClassVar, Type

from ..base_client import BaseClient
from ..mixins import CreateCommandMixin, FindAllCommandMixin, FindCommandMixin
from ..models.payment import PaymentResponse


class PaymentClient(
CreateCommandMixin[PaymentResponse],
FindAllCommandMixin[PaymentResponse],
FindCommandMixin[PaymentResponse],
BaseClient,
):
API_RESOURCE: ClassVar[str] = "payments"
RESPONSE_MODEL: ClassVar[Type[PaymentResponse]] = PaymentResponse
ROOT_NAME: ClassVar[str] = "payment"
1 change: 1 addition & 0 deletions tests/fixtures/invoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"sub_total_including_taxes_amount_cents": 120,
"prepaid_credit_amount_cents": 0,
"total_amount_cents": 120,
"total_due_amount_cents": 120,
"metadata": [
{
"key": "key",
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/invoice_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"sub_total_including_taxes_amount_cents": 120,
"prepaid_credit_amount_cents": 0,
"total_amount_cents": 120,
"total_due_amount_cents": 120,
"applied_taxes": [
{
"lago_id": "1a901a90-1a90-1a90-1a90-1a901a901a90",
Expand Down Expand Up @@ -59,6 +60,7 @@
"sub_total_including_taxes_amount_cents": 120,
"prepaid_credit_amount_cents": 0,
"total_amount_cents": 120,
"total_due_amount_cents": 120,
"applied_taxes": [
{
"lago_id": "1a901a90-1a90-1a90-1a90-1a901a901a90",
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/one_off_invoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"sub_total_including_taxes_amount_cents": 120,
"prepaid_credit_amount_cents": 0,
"total_amount_cents": 120,
"total_due_amount_cents": 120,
"metadata": [
{
"key": "key",
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/payment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"payment": {
"lago_id": "8e5d5ec2-bdc7-4c43-a944-5ababae775d4",
"invoice_ids": ["f8e194df-5d90-4382-b146-c881d2c67f28"],
"amount_cents": 100,
"amount_currency": "USD",
"payment_status": "succeeded",
"type": "manual",
"reference": "123",
"external_payment_id": null,
"created_at": "2025-02-18T18:31:13Z"
}
}
20 changes: 20 additions & 0 deletions tests/fixtures/payment_index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"payments": [
{
"lago_id": "8e5d5ec2-bdc7-4c43-a944-5ababae775d4",
"invoice_ids": [
"ed267c66-8170-4d23-83e8-6d6e4fc735ef"
],
"amount_cents": 100,
"amount_currency": "USD",
"payment_status": "succeeded",
"type": "manual",
"reference": "123",
"external_payment_id": null,
"created_at": "2025-02-18T18:31:13Z"
}
],
"meta": {
"current_page": 1
}
}
3 changes: 2 additions & 1 deletion tests/fixtures/payment_request_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"sub_total_excluding_taxes_amount_cents": 100,
"sub_total_including_taxes_amount_cents": 120,
"prepaid_credit_amount_cents": 0,
"total_amount_cents": 120
"total_amount_cents": 120,
"total_due_amount_cents": 120
}
]
}
Expand Down
108 changes: 108 additions & 0 deletions tests/test_payment_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os

import pytest
from pytest_httpx import HTTPXMock

from lago_python_client.client import Client
from lago_python_client.exceptions import LagoApiError
from lago_python_client.models import Payment


def payment_object():
return Payment(
amount_cents=100,
reference="ref1",
invoice_id="f8e194df-5d90-4382-b146-c881d2c67f28",
paid_at="2025-01-01T13:00:00Z"
)


def mock_response():
this_dir = os.path.dirname(os.path.abspath(__file__))
data_path = os.path.join(this_dir, "fixtures/payment.json")

with open(data_path, "rb") as payment_response:
return payment_response.read()


def mock_collection_response():
current_dir = os.path.dirname(os.path.abspath(__file__))
data_path = os.path.join(current_dir, "fixtures/payment_index.json")

with open(data_path, "rb") as payments_response:
return payments_response.read()


def test_valid_find_all_payments_request(httpx_mock: HTTPXMock):
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")

httpx_mock.add_response(
method="GET",
url="https://api.getlago.com/api/v1/payments",
content=mock_collection_response(),
)
response = client.payments.find_all()

assert response["payments"][0].lago_id == "8e5d5ec2-bdc7-4c43-a944-5ababae775d4"
assert response["meta"]["current_page"] == 1


def test_valid_find_all_payments_request_with_options(httpx_mock: HTTPXMock):
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")

httpx_mock.add_response(
method="GET",
url="https://api.getlago.com/api/v1/payments?per_page=2&page=1",
content=mock_collection_response(),
)
response = client.payments.find_all({"per_page": 2, "page": 1})

assert response["payments"][0].lago_id == "8e5d5ec2-bdc7-4c43-a944-5ababae775d4"
assert response["meta"]["current_page"] == 1


def test_invalid_find_all_payment_request(httpx_mock: HTTPXMock):
client = Client(api_key="invalid")

httpx_mock.add_response(
method="GET",
url="https://api.getlago.com/api/v1/payments",
status_code=404,
content=b"",
)

with pytest.raises(LagoApiError):
client.payments.find_all()


def test_valid_create_payment_request(httpx_mock: HTTPXMock):
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")

httpx_mock.add_response(
method="POST",
url="https://api.getlago.com/api/v1/payments",
content=mock_response(),
)
response = client.payments.create(payment_object())

assert response is not None
assert response.lago_id == "8e5d5ec2-bdc7-4c43-a944-5ababae775d4"
assert response.amount_cents == 100
assert response.amount_currency == "USD"
assert response.payment_status == "succeeded"
assert len(response.invoice_ids) == 1
assert response.invoice_ids[0] == "f8e194df-5d90-4382-b146-c881d2c67f28"


def test_invalid_create_payment_request(httpx_mock: HTTPXMock):
client = Client(api_key="invalid")

httpx_mock.add_response(
method="POST",
url="https://api.getlago.com/api/v1/payments",
status_code=401,
content=b"",
)

with pytest.raises(LagoApiError):
client.payments.create(payment_object())

0 comments on commit e12c1c7

Please sign in to comment.