diff --git a/lago_python_client/client.py b/lago_python_client/client.py index e86119df..40777a31 100644 --- a/lago_python_client/client.py +++ b/lago_python_client/client.py @@ -9,6 +9,7 @@ from .customers.applied_coupons_client import CustomerAppliedCouponsClient from .customers.credit_notes_client import CustomerCreditNotesClient from .customers.invoices_client import CustomerInvoicesClient +from .customers.payment_methods_client import CustomerPaymentMethodsClient from .customers.payments_client import CustomerPaymentsClient from .customers.payment_requests_client import CustomerPaymentRequestsClient from .customers.subscriptions_client import CustomerSubscriptionsClient @@ -111,6 +112,10 @@ def customer_credit_notes(self) -> CustomerCreditNotesClient: def customer_invoices(self) -> CustomerInvoicesClient: return CustomerInvoicesClient(self.base_api_url, self.api_key) + @callable_cached_property + def customer_payment_methods(self) -> CustomerPaymentMethodsClient: + return CustomerPaymentMethodsClient(self.base_api_url, self.api_key) + @callable_cached_property def customer_payments(self) -> CustomerPaymentsClient: return CustomerPaymentsClient(self.base_api_url, self.api_key) diff --git a/lago_python_client/customers/payment_methods_client.py b/lago_python_client/customers/payment_methods_client.py new file mode 100644 index 00000000..1c8b478b --- /dev/null +++ b/lago_python_client/customers/payment_methods_client.py @@ -0,0 +1,49 @@ +from typing import ClassVar, Type + +from ..base_client import BaseClient +from ..customers.clients import CustomerClient +from ..mixins import FindAllChildrenCommandMixin +from ..models.payment_method import PaymentMethodResponse +from ..services.request import make_headers, make_url, send_delete_request, send_put_request +from ..services.response import get_response_data, prepare_object_response, Response + + +class CustomerPaymentMethodsClient(FindAllChildrenCommandMixin[PaymentMethodResponse], BaseClient): + PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE + API_RESOURCE: ClassVar[str] = "payment_methods" + RESPONSE_MODEL: ClassVar[Type[PaymentMethodResponse]] = PaymentMethodResponse + ROOT_NAME: ClassVar[str] = "payment_method" + + def destroy(self, customer_external_id: str, payment_method_id: str) -> PaymentMethodResponse: + api_response: Response = send_delete_request( + url=make_url( + origin=self.base_url, + path_parts=(self.PARENT_API_RESOURCE, customer_external_id, self.API_RESOURCE, payment_method_id), + ), + headers=make_headers(api_key=self.api_key), + ) + + return prepare_object_response( + response_model=self.RESPONSE_MODEL, + data=get_response_data(response=api_response, key=self.ROOT_NAME), + ) + + def set_as_default(self, customer_external_id: str, payment_method_id: str) -> PaymentMethodResponse: + api_response: Response = send_put_request( + url=make_url( + origin=self.base_url, + path_parts=( + self.PARENT_API_RESOURCE, + customer_external_id, + self.API_RESOURCE, + payment_method_id, + "set_as_default", + ), + ), + headers=make_headers(api_key=self.api_key), + ) + + return prepare_object_response( + response_model=self.RESPONSE_MODEL, + data=get_response_data(response=api_response, key=self.ROOT_NAME), + ) diff --git a/lago_python_client/invoices/clients.py b/lago_python_client/invoices/clients.py index bb6feddd..33abc92c 100644 --- a/lago_python_client/invoices/clients.py +++ b/lago_python_client/invoices/clients.py @@ -8,6 +8,7 @@ CreateCommandMixin, ) from ..models.invoice import InvoicePreview, InvoiceResponse +from ..models.payment_method import PaymentMethod from ..services.json import to_json from ..services.request import ( make_headers, @@ -47,12 +48,19 @@ def download(self, resource_id: str) -> Optional[InvoiceResponse]: data=response_data, ) - def retry_payment(self, resource_id: str) -> Optional[InvoiceResponse]: + def retry_payment( + self, resource_id: str, payment_method: Optional[PaymentMethod] = None + ) -> Optional[InvoiceResponse]: + payload = {} + if payment_method: + payload["payment_method"] = payment_method.dict(exclude_none=True) + api_response: Response = send_post_request( url=make_url( origin=self.base_url, path_parts=(self.API_RESOURCE, resource_id, "retry_payment"), ), + content=to_json(payload) if payload else None, headers=make_headers(api_key=self.api_key), ) diff --git a/lago_python_client/models/__init__.py b/lago_python_client/models/__init__.py index edf45c97..ca90f79c 100644 --- a/lago_python_client/models/__init__.py +++ b/lago_python_client/models/__init__.py @@ -129,4 +129,5 @@ ) from .payment_request import PaymentRequest as PaymentRequest from .payment import Payment as Payment +from .payment_method import PaymentMethod as PaymentMethod, PaymentMethodResponse as PaymentMethodResponse from .lifetime_usage import LifetimeUsageResponse as LifetimeUsageResponse diff --git a/lago_python_client/models/invoice.py b/lago_python_client/models/invoice.py index 5c8215a2..d01c8497 100644 --- a/lago_python_client/models/invoice.py +++ b/lago_python_client/models/invoice.py @@ -7,6 +7,7 @@ from .credit import CreditsResponse from .customer import Customer, CustomerResponse from .fee import FeesResponse +from .payment_method import PaymentMethod from .subscription import Subscriptions, SubscriptionsResponse from .error_detail import ErrorDetailsResponse from ..base_model import BaseResponseModel @@ -53,6 +54,7 @@ class OneOffInvoice(BaseModel): currency: Optional[str] fees: Optional[InvoiceFeesList] error_details: Optional[ErrorDetailsResponse] + payment_method: Optional[PaymentMethod] class InvoicePreview(BaseModel): diff --git a/lago_python_client/models/payment_method.py b/lago_python_client/models/payment_method.py new file mode 100644 index 00000000..6f125045 --- /dev/null +++ b/lago_python_client/models/payment_method.py @@ -0,0 +1,20 @@ +from typing import Optional + +from lago_python_client.base_model import BaseModel + +from ..base_model import BaseResponseModel + + +class PaymentMethod(BaseModel): + payment_method_type: Optional[str] + payment_method_id: Optional[str] + + +class PaymentMethodResponse(BaseResponseModel): + lago_id: str + is_default: Optional[bool] + payment_provider_code: Optional[str] + payment_provider_name: Optional[str] + payment_provider_type: Optional[str] + provider_method_id: Optional[str] + created_at: Optional[str] diff --git a/lago_python_client/models/subscription.py b/lago_python_client/models/subscription.py index 538ffe53..294128f9 100644 --- a/lago_python_client/models/subscription.py +++ b/lago_python_client/models/subscription.py @@ -1,6 +1,7 @@ from typing import List, Optional from lago_python_client.base_model import BaseModel +from .payment_method import PaymentMethod from .plan import PlanOverrides from ..base_model import BaseResponseModel @@ -14,6 +15,7 @@ class Subscription(BaseModel): billing_time: Optional[str] ending_at: Optional[str] plan_overrides: Optional[PlanOverrides] + payment_method: Optional[PaymentMethod] class Subscriptions(BaseModel): @@ -48,6 +50,7 @@ class SubscriptionResponse(BaseResponseModel): current_billing_period_ending_at: Optional[str] on_termination_credit_note: Optional[str] on_termination_invoice: Optional[str] + payment_method: Optional[PaymentMethod] class SubscriptionsResponse(BaseResponseModel): diff --git a/lago_python_client/models/wallet.py b/lago_python_client/models/wallet.py index 68cbe5be..5a6dfa73 100644 --- a/lago_python_client/models/wallet.py +++ b/lago_python_client/models/wallet.py @@ -2,6 +2,7 @@ from lago_python_client.base_model import BaseModel +from .payment_method import PaymentMethod from ..base_model import BaseResponseModel @@ -19,6 +20,7 @@ class RecurringTransactionRule(BaseModel): transaction_metadata: Optional[List[Dict[str, str]]] transaction_name: Optional[str] ignore_paid_top_up_limits: Optional[bool] + payment_method: Optional[PaymentMethod] class RecurringTransactionRuleResponse(BaseModel): @@ -37,6 +39,7 @@ class RecurringTransactionRuleResponse(BaseModel): transaction_metadata: Optional[List[Dict[str, str]]] transaction_name: Optional[str] ignore_paid_top_up_limits: Optional[bool] + payment_method: Optional[PaymentMethod] class RecurringTransactionRuleList(BaseModel): @@ -71,6 +74,7 @@ class Wallet(BaseModel): paid_top_up_min_amount_cents: Optional[int] ignore_paid_top_up_limits_on_creation: Optional[bool] metadata: Optional[Dict[str, Optional[str]]] + payment_method: Optional[PaymentMethod] class WalletResponse(BaseResponseModel): @@ -101,3 +105,4 @@ class WalletResponse(BaseResponseModel): paid_top_up_max_amount_cents: Optional[int] paid_top_up_min_amount_cents: Optional[int] metadata: Optional[Dict[str, Optional[str]]] + payment_method: Optional[PaymentMethod] diff --git a/lago_python_client/models/wallet_transaction.py b/lago_python_client/models/wallet_transaction.py index 8a1335d5..1024e1fc 100644 --- a/lago_python_client/models/wallet_transaction.py +++ b/lago_python_client/models/wallet_transaction.py @@ -2,6 +2,7 @@ from lago_python_client.base_model import BaseModel +from .payment_method import PaymentMethod from ..base_model import BaseResponseModel @@ -14,6 +15,7 @@ class WalletTransaction(BaseModel): metadata: Optional[List[Dict[str, str]]] name: Optional[str] ignore_paid_top_up_limits: Optional[bool] + payment_method: Optional[PaymentMethod] class WalletTransactionResponse(BaseResponseModel): @@ -31,3 +33,4 @@ class WalletTransactionResponse(BaseResponseModel): metadata: Optional[List[Dict[str, str]]] name: Optional[str] invoice_requires_successful_payment: Optional[bool] + payment_method: Optional[PaymentMethod] diff --git a/tests/fixtures/payment_method.json b/tests/fixtures/payment_method.json new file mode 100644 index 00000000..7e119b27 --- /dev/null +++ b/tests/fixtures/payment_method.json @@ -0,0 +1,11 @@ +{ + "payment_method": { + "lago_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "is_default": true, + "payment_provider_code": "stripe_1", + "payment_provider_name": "Stripe", + "payment_provider_type": "stripe", + "provider_method_id": "pm_1234567890", + "created_at": "2024-01-01T00:00:00Z" + } +} diff --git a/tests/fixtures/payment_method_index.json b/tests/fixtures/payment_method_index.json new file mode 100644 index 00000000..bfdec8b6 --- /dev/null +++ b/tests/fixtures/payment_method_index.json @@ -0,0 +1,29 @@ +{ + "payment_methods": [ + { + "lago_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "is_default": true, + "payment_provider_code": "stripe_1", + "payment_provider_name": "Stripe", + "payment_provider_type": "stripe", + "provider_method_id": "pm_1234567890", + "created_at": "2024-01-01T00:00:00Z" + }, + { + "lago_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "is_default": false, + "payment_provider_code": "adyen_1", + "payment_provider_name": "Adyen", + "payment_provider_type": "adyen", + "provider_method_id": "pm_0987654321", + "created_at": "2024-01-02T00:00:00Z" + } + ], + "meta": { + "current_page": 1, + "next_page": null, + "prev_page": null, + "total_pages": 1, + "total_count": 2 + } +} diff --git a/tests/fixtures/subscription.json b/tests/fixtures/subscription.json index 1e938802..10a8e899 100644 --- a/tests/fixtures/subscription.json +++ b/tests/fixtures/subscription.json @@ -20,6 +20,10 @@ "next_plan_code": null, "downgrade_plan_date": null, "current_billing_period_started_at": "2022-04-29T08:59:51Z", - "current_billing_period_ending_at": "2022-05-29T23:59:59Z" + "current_billing_period_ending_at": "2022-05-29T23:59:59Z", + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" + } } } diff --git a/tests/fixtures/wallet.json b/tests/fixtures/wallet.json index 028c61a3..1602da3b 100644 --- a/tests/fixtures/wallet.json +++ b/tests/fixtures/wallet.json @@ -27,7 +27,11 @@ "started_at": null, "target_ongoing_balance": "200.0", "transaction_name": "Recurring Transaction Rule", - "ignore_paid_top_up_limits": true + "ignore_paid_top_up_limits": true, + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" + } } ], "ongoing_balance_cents": 800, @@ -43,6 +47,10 @@ "metadata": { "key1": "value1", "key2": null + }, + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" } } } diff --git a/tests/fixtures/wallet_transaction.json b/tests/fixtures/wallet_transaction.json index ea0371b3..9242d72c 100644 --- a/tests/fixtures/wallet_transaction.json +++ b/tests/fixtures/wallet_transaction.json @@ -11,7 +11,11 @@ "credit_amount": "10", "settled_at": "2022-04-29T08:59:51Z", "created_at": "2022-04-29T08:59:51Z", - "name": "Transaction Name" + "name": "Transaction Name", + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" + } }, { "lago_id": "b7ab2926-1de8-4428-9bcd-779314ac1222", @@ -24,7 +28,11 @@ "credit_amount": "10", "settled_at": "2022-04-29T08:59:51Z", "created_at": "2022-04-29T08:59:51Z", - "name": "Transaction Name" + "name": "Transaction Name", + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" + } }, { "lago_id": "b7ab2926-1de8-4428-9bcd-779314ac1333", @@ -38,7 +46,11 @@ "settled_at": "2022-04-29T08:59:51Z", "failed_at": "2022-04-29T08:59:51Z", "created_at": "2022-04-29T08:59:51Z", - "name": "Transaction Name" + "name": "Transaction Name", + "payment_method": { + "payment_method_type": "card", + "payment_method_id": "pm_123" + } } ] } diff --git a/tests/test_customer_payment_methods_client.py b/tests/test_customer_payment_methods_client.py new file mode 100644 index 00000000..0889d7bb --- /dev/null +++ b/tests/test_customer_payment_methods_client.py @@ -0,0 +1,140 @@ +import pytest +from pytest_httpx import HTTPXMock + +from lago_python_client.client import Client +from lago_python_client.exceptions import LagoApiError + +from .utils.mixin import mock_response + + +def test_valid_find_all_customer_payment_methods_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/customers/external_customer_id/payment_methods", + content=mock_response(mock="payment_method_index"), + ) + response = client.customer_payment_methods.find_all(resource_id="external_customer_id") + + assert response["payment_methods"][0].lago_id == "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + assert response["payment_methods"][0].is_default is True + assert response["payment_methods"][0].payment_provider_code == "stripe_1" + assert response["payment_methods"][0].payment_provider_name == "Stripe" + assert response["payment_methods"][0].payment_provider_type == "stripe" + assert response["payment_methods"][0].provider_method_id == "pm_1234567890" + assert response["payment_methods"][0].created_at == "2024-01-01T00:00:00Z" + assert response["meta"]["current_page"] == 1 + + +def test_valid_find_all_customer_payment_methods_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/customers/external_customer_id/payment_methods?per_page=2&page=1", + content=mock_response(mock="payment_method_index"), + ) + response = client.customer_payment_methods.find_all( + resource_id="external_customer_id", options={"per_page": 2, "page": 1} + ) + + assert response["payment_methods"][0].lago_id == "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + assert response["payment_methods"][0].is_default is True + assert response["payment_methods"][0].payment_provider_code == "stripe_1" + assert response["payment_methods"][0].payment_provider_name == "Stripe" + assert response["payment_methods"][0].payment_provider_type == "stripe" + assert response["payment_methods"][0].provider_method_id == "pm_1234567890" + assert response["payment_methods"][0].created_at == "2024-01-01T00:00:00Z" + assert response["meta"]["current_page"] == 1 + + +def test_invalid_find_all_customer_payment_methods_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/customers/invalid_customer_id/payment_methods", + status_code=404, + content=b'{"status": 404, "error": "Not Found", "code": "customer_not_found"}', + ) + + with pytest.raises(LagoApiError) as exc_info: + client.customer_payment_methods.find_all(resource_id="invalid_customer_id") + + assert exc_info.value.status_code == 404 + assert exc_info.value.response["code"] == "customer_not_found" + + +def test_valid_destroy_customer_payment_method_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/customers/external_customer_id/payment_methods/a1b2c3d4-e5f6-7890-abcd-ef1234567890", + content=mock_response(mock="payment_method"), + ) + response = client.customer_payment_methods.destroy("external_customer_id", "a1b2c3d4-e5f6-7890-abcd-ef1234567890") + + assert response.lago_id == "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + assert response.is_default is True + assert response.payment_provider_code == "stripe_1" + assert response.payment_provider_name == "Stripe" + assert response.payment_provider_type == "stripe" + assert response.provider_method_id == "pm_1234567890" + assert response.created_at == "2024-01-01T00:00:00Z" + + +def test_invalid_destroy_customer_payment_method_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/customers/external_customer_id/payment_methods/invalid_id", + status_code=404, + content=b'{"status": 404, "error": "Not Found", "code": "payment_method_not_found"}', + ) + + with pytest.raises(LagoApiError) as exc_info: + client.customer_payment_methods.destroy("external_customer_id", "invalid_id") + + assert exc_info.value.status_code == 404 + assert exc_info.value.response["code"] == "payment_method_not_found" + + +def test_valid_set_as_default_customer_payment_method_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/customers/external_customer_id/payment_methods/a1b2c3d4-e5f6-7890-abcd-ef1234567890/set_as_default", + content=mock_response(mock="payment_method"), + ) + response = client.customer_payment_methods.set_as_default( + "external_customer_id", "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ) + + assert response.lago_id == "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + assert response.is_default is True + assert response.payment_provider_code == "stripe_1" + assert response.payment_provider_name == "Stripe" + assert response.payment_provider_type == "stripe" + assert response.provider_method_id == "pm_1234567890" + assert response.created_at == "2024-01-01T00:00:00Z" + + +def test_invalid_set_as_default_customer_payment_method_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/customers/external_customer_id/payment_methods/invalid_id/set_as_default", + status_code=404, + content=b'{"status": 404, "error": "Not Found", "code": "payment_method_not_found"}', + ) + + with pytest.raises(LagoApiError) as exc_info: + client.customer_payment_methods.set_as_default("external_customer_id", "invalid_id") + + assert exc_info.value.status_code == 404 + assert exc_info.value.response["code"] == "payment_method_not_found" diff --git a/tests/test_invoice_client.py b/tests/test_invoice_client.py index e97f7fe2..d1350f84 100644 --- a/tests/test_invoice_client.py +++ b/tests/test_invoice_client.py @@ -14,6 +14,7 @@ OneOffInvoice, InvoiceFeesList, InvoiceFee, + PaymentMethod, ) @@ -88,6 +89,46 @@ def test_valid_create_invoice_request(httpx_mock: HTTPXMock): assert response.fees.__root__[0].amount_details == {} +def test_valid_create_invoice_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/invoices", + content=mock_response(mock="one_off_invoice"), + ) + invoice = one_off_invoice_object() + invoice.payment_method = payment_method + response = client.invoices.create(invoice) + + assert response.lago_id == "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + assert response.invoice_type == "one_off" + + +def test_invalid_create_invoice_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/invoices", + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + invoice = one_off_invoice_object() + invoice.payment_method = payment_method + + with pytest.raises(LagoApiError): + client.invoices.create(invoice) + + def test_invalid_create_invoice_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") @@ -278,6 +319,21 @@ def test_valid_retry_payment_invoice_request(httpx_mock: HTTPXMock): assert response.lago_id == "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" +def test_valid_retry_payment_invoice_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/invoices/5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba/retry_payment", + status_code=200, + content=b"", + ) + response = client.invoices.retry_payment("5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba", payment_method=payment_method) + + assert response is None + + def test_valid_payment_url_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") diff --git a/tests/test_subscription_client.py b/tests/test_subscription_client.py index a08ca7c3..c65e5dbb 100644 --- a/tests/test_subscription_client.py +++ b/tests/test_subscription_client.py @@ -5,7 +5,7 @@ from lago_python_client.client import Client from lago_python_client.exceptions import LagoApiError -from lago_python_client.models import Subscription +from lago_python_client.models import Subscription, PaymentMethod from lago_python_client.models.alert import Alert, AlertThreshold, AlertsList @@ -78,6 +78,25 @@ def test_valid_create_subscriptions_request(httpx_mock: HTTPXMock): assert response.ending_at == "2022-08-29T08:59:51Z" +def test_valid_create_subscriptions_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/subscriptions", + content=mock_response(), + ) + subscription = create_subscription() + subscription.payment_method = payment_method + response = client.subscriptions.create(subscription) + + assert response.external_customer_id == "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + assert response.status == "active" + assert response.payment_method.payment_method_type == "card" + assert response.payment_method.payment_method_id == "pm_123" + + def test_invalid_create_subscriptions_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") @@ -92,6 +111,28 @@ def test_invalid_create_subscriptions_request(httpx_mock: HTTPXMock): client.subscriptions.create(create_subscription()) +def test_invalid_create_subscriptions_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/subscriptions", + status_code=404, + json={ + "status": 404, + "error": "Not Found", + "code": "resource_not_found", + }, + ) + + subscription = create_subscription() + subscription.payment_method = payment_method + + with pytest.raises(LagoApiError): + client.subscriptions.create(subscription) + + def test_valid_update_subscription_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") identifier = "sub_id" @@ -110,6 +151,24 @@ def test_valid_update_subscription_request(httpx_mock: HTTPXMock): assert response.subscription_at == "2022-04-29T08:59:51Z" +def test_valid_update_subscription_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + identifier = "sub_id" + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/subscriptions/" + identifier, + content=mock_response(), + ) + response = client.subscriptions.update(Subscription(name="name", payment_method=payment_method), identifier) + + assert response.external_customer_id == "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + assert response.status == "active" + assert response.payment_method.payment_method_type == "card" + assert response.payment_method.payment_method_id == "pm_123" + + def test_invalid_update_subscription_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") identifier = "invalid" @@ -125,6 +184,27 @@ def test_invalid_update_subscription_request(httpx_mock: HTTPXMock): client.subscriptions.update(Subscription(name="name"), identifier) +def test_invalid_update_subscription_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + identifier = "sub_id" + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/subscriptions/" + identifier, + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + with pytest.raises(LagoApiError): + client.subscriptions.update(Subscription(name="name", payment_method=payment_method), identifier) + + def test_valid_destroy_subscription_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") identifier = "sub_id" diff --git a/tests/test_wallet_client.py b/tests/test_wallet_client.py index 8b5f4647..597906d4 100644 --- a/tests/test_wallet_client.py +++ b/tests/test_wallet_client.py @@ -10,6 +10,7 @@ RecurringTransactionRule, RecurringTransactionRuleList, AppliesTo, + PaymentMethod, ) @@ -98,6 +99,7 @@ def test_valid_create_wallet_request(httpx_mock: HTTPXMock): "transaction_metadata": None, "transaction_name": "Recurring Transaction Rule", "ignore_paid_top_up_limits": True, + "payment_method": None, } ], "transaction_metadata": None, @@ -105,6 +107,7 @@ def test_valid_create_wallet_request(httpx_mock: HTTPXMock): "transaction_name": "Transaction Name", "applies_to": {"fee_types": ["charge"], "billable_metric_codes": ["usage"]}, "metadata": None, + "payment_method": None, } }, ) @@ -121,6 +124,85 @@ def test_valid_create_wallet_request(httpx_mock: HTTPXMock): assert response.paid_top_up_min_amount_cents == 500 +def test_valid_create_wallet_request_with_payment_method_on_wallet(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/wallets", + content=mock_response(), + ) + wallet = wallet_object() + wallet.payment_method = payment_method + response = client.wallets.create(wallet) + + assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" + assert response.payment_method.payment_method_type == "card" + assert response.payment_method.payment_method_id == "pm_123" + + +def test_invalid_create_wallet_request_with_payment_method_on_wallet(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/wallets", + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + wallet = wallet_object() + wallet.payment_method = payment_method + + with pytest.raises(LagoApiError): + client.wallets.create(wallet) + + +def test_valid_create_wallet_request_with_payment_method_on_recurring_transaction_rule(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/wallets", + content=mock_response(), + ) + response = client.wallets.create(wallet_object()) + + assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" + assert response.recurring_transaction_rules.__root__[0].payment_method.payment_method_type == "card" + assert response.recurring_transaction_rules.__root__[0].payment_method.payment_method_id == "pm_123" + + +def test_invalid_create_wallet_request_with_payment_method_on_recurring_transaction_rule(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/wallets", + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + wallet = wallet_object() + wallet.recurring_transaction_rules.__root__[0].payment_method = payment_method + + with pytest.raises(LagoApiError): + client.wallets.create(wallet) + + def test_invalid_create_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") @@ -137,89 +219,172 @@ def test_invalid_create_wallet_request(httpx_mock: HTTPXMock): def test_valid_update_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") - arg = "b7ab2926-1de8-4428-9bcd-779314ac129b" + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, + content=mock_response(), + ) + response = client.wallets.update(wallet_object(), wallet_id) + + assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" + + +def test_valid_update_wallet_request_with_payment_method_on_wallet(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, + content=mock_response(), + ) + wallet = wallet_object() + wallet.payment_method = payment_method + response = client.wallets.update(wallet, wallet_id) + + assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" + assert response.payment_method.payment_method_type == "card" + assert response.payment_method.payment_method_id == "pm_123" + + +def test_invalid_update_wallet_request_with_payment_method_on_wallet(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + wallet = wallet_object() + wallet.payment_method = payment_method + + with pytest.raises(LagoApiError): + client.wallets.update(wallet, wallet_id) + + +def test_valid_update_wallet_request_with_payment_method_on_recurring_transaction_rule(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" httpx_mock.add_response( method="PUT", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, content=mock_response(), ) - response = client.wallets.update(wallet_object(), arg) + response = client.wallets.update(wallet_object(), wallet_id) assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" + assert response.recurring_transaction_rules.__root__[0].payment_method.payment_method_type == "card" + assert response.recurring_transaction_rules.__root__[0].payment_method.payment_method_id == "pm_123" + + +def test_invalid_update_wallet_request_with_payment_method_on_recurring_transaction_rule(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + wallet = wallet_object() + wallet.recurring_transaction_rules.__root__[0].payment_method = payment_method + + with pytest.raises(LagoApiError): + client.wallets.update(wallet, wallet_id) def test_invalid_update_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") - arg = "invalid" + wallet_id = "invalid" httpx_mock.add_response( method="PUT", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, status_code=401, content=b"", ) with pytest.raises(LagoApiError): - client.wallets.update(wallet_object(), arg) + client.wallets.update(wallet_object(), wallet_id) def test_valid_find_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") - arg = "b7ab2926-1de8-4428-9bcd-779314ac129b" + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" httpx_mock.add_response( method="GET", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, content=mock_response(), ) - response = client.wallets.find(arg) + response = client.wallets.find(wallet_id) assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" def test_invalid_find_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") - arg = "invalid" + wallet_id = "invalid" httpx_mock.add_response( method="GET", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, status_code=404, content=b"", ) with pytest.raises(LagoApiError): - client.wallets.find(arg) + client.wallets.find(wallet_id) def test_valid_destroy_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") - arg = "b7ab2926-1de8-4428-9bcd-779314ac129b" + wallet_id = "b7ab2926-1de8-4428-9bcd-779314ac129b" httpx_mock.add_response( method="DELETE", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, content=mock_response(), ) - response = client.wallets.destroy(arg) + response = client.wallets.destroy(wallet_id) assert response.lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b" def test_invalid_destroy_wallet_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid") - arg = "invalid" + wallet_id = "invalid" httpx_mock.add_response( method="DELETE", - url="https://api.getlago.com/api/v1/wallets/" + arg, + url="https://api.getlago.com/api/v1/wallets/" + wallet_id, status_code=404, content=b"", ) with pytest.raises(LagoApiError): - client.wallets.destroy(arg) + client.wallets.destroy(wallet_id) def test_valid_find_all_wallet_request(httpx_mock: HTTPXMock): diff --git a/tests/test_wallet_transaction_client.py b/tests/test_wallet_transaction_client.py index dc933f22..67d7db3d 100644 --- a/tests/test_wallet_transaction_client.py +++ b/tests/test_wallet_transaction_client.py @@ -5,7 +5,7 @@ from lago_python_client.client import Client from lago_python_client.exceptions import LagoApiError -from lago_python_client.models import WalletTransaction +from lago_python_client.models import WalletTransaction, PaymentMethod def wallet_transaction_object(): @@ -64,6 +64,52 @@ def test_valid_create_wallet_transaction_request(httpx_mock: HTTPXMock): assert response["wallet_transactions"][2].name == "Transaction Name" +def test_valid_create_wallet_transaction_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="card", payment_method_id="pm_123") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/wallet_transactions", + content=mock_response(), + ) + transaction = wallet_transaction_object() + transaction.payment_method = payment_method + response = client.wallet_transactions.create(transaction) + + assert response["wallet_transactions"][0].lago_id == "b7ab2926-1de8-4428-9bcd-779314ac1111" + assert response["wallet_transactions"][0].status == "settled" + assert response["wallet_transactions"][0].payment_method.payment_method_type == "card" + assert response["wallet_transactions"][0].payment_method.payment_method_id == "pm_123" + assert response["wallet_transactions"][1].payment_method.payment_method_type == "card" + assert response["wallet_transactions"][1].payment_method.payment_method_id == "pm_123" + assert response["wallet_transactions"][2].payment_method.payment_method_type == "card" + assert response["wallet_transactions"][2].payment_method.payment_method_id == "pm_123" + + +def test_invalid_create_wallet_transaction_request_with_payment_method(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + payment_method = PaymentMethod(payment_method_type="provider", payment_method_id="invalid-id") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/wallet_transactions", + status_code=422, + json={ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": {"payment_method": ["invalid_payment_method"]}, + }, + ) + + transaction = wallet_transaction_object() + transaction.payment_method = payment_method + + with pytest.raises(LagoApiError): + client.wallet_transactions.create(transaction) + + def test_invalid_create_wallet_transaction_request(httpx_mock: HTTPXMock): client = Client(api_key="invalid")