From f75fd216c4e100826c7a8f17daaac24946d5331f Mon Sep 17 00:00:00 2001 From: Andrew Kozin Date: Mon, 1 Dec 2025 11:05:53 +0000 Subject: [PATCH] Support metadata in credit notes --- lago_python_client/credit_notes/clients.py | 54 +++++++++++++++++- lago_python_client/models/credit_note.py | 5 +- lago_python_client/services/request.py | 1 + tests/fixtures/credit_note.json | 4 ++ tests/test_credit_note_client.py | 65 ++++++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/lago_python_client/credit_notes/clients.py b/lago_python_client/credit_notes/clients.py index d652b1e4..997361b2 100644 --- a/lago_python_client/credit_notes/clients.py +++ b/lago_python_client/credit_notes/clients.py @@ -1,4 +1,4 @@ -from typing import ClassVar, Optional, Type +from typing import ClassVar, Dict, Optional, Type from ..base_client import BaseClient from ..mixins import ( @@ -16,6 +16,8 @@ from ..services.request import ( make_headers, make_url, + send_delete_request, + send_patch_request, send_post_request, send_put_request, ) @@ -84,3 +86,53 @@ def estimate(self, input_object: CreditNoteEstimate) -> CreditNoteEstimatedRespo response_model=CreditNoteEstimatedResponse, data=get_response_data(response=api_response, key=self.ESTIMATE_API_RESOURCE), ) + + def replace_metadata( + self, resource_id: str, metadata: Dict[str, Optional[str]] + ) -> Optional[Dict[str, Optional[str]]]: + api_response: Response = send_post_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, resource_id, "metadata"), + ), + content=to_json({"metadata": metadata}), + headers=make_headers(api_key=self.api_key), + ) + + return get_response_data(response=api_response, key="metadata") + + def merge_metadata( + self, resource_id: str, metadata: Dict[str, Optional[str]] + ) -> Optional[Dict[str, Optional[str]]]: + api_response: Response = send_patch_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, resource_id, "metadata"), + ), + content=to_json({"metadata": metadata}), + headers=make_headers(api_key=self.api_key), + ) + + return get_response_data(response=api_response, key="metadata") + + def delete_all_metadata(self, resource_id: str) -> Optional[Dict[str, Optional[str]]]: + api_response: Response = send_delete_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, resource_id, "metadata"), + ), + headers=make_headers(api_key=self.api_key), + ) + + return get_response_data(response=api_response, key="metadata") + + def delete_metadata_key(self, resource_id: str, key: str) -> Optional[Dict[str, Optional[str]]]: + api_response: Response = send_delete_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, resource_id, "metadata", key), + ), + headers=make_headers(api_key=self.api_key), + ) + + return get_response_data(response=api_response, key="metadata") diff --git a/lago_python_client/models/credit_note.py b/lago_python_client/models/credit_note.py index 49bf8ff1..bac0dd3e 100644 --- a/lago_python_client/models/credit_note.py +++ b/lago_python_client/models/credit_note.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Dict, List, Optional from lago_python_client.base_model import BaseModel @@ -58,6 +58,7 @@ class CreditNoteResponse(BaseResponseModel): updated_at: Optional[str] items: Optional[ItemsResponse] applied_taxes: Optional[CreditNoteAppliedTaxes] + metadata: Optional[Dict[str, Optional[str]]] class Item(BaseModel): @@ -72,10 +73,12 @@ class Items(BaseModel): class CreditNote(BaseModel): reason: Optional[str] items: Optional[Items] + metadata: Optional[Dict[str, Optional[str]]] class CreditNoteUpdate(BaseModel): refund_status: Optional[str] + metadata: Optional[Dict[str, Optional[str]]] class EstimatedItemResponse(BaseResponseModel): diff --git a/lago_python_client/services/request.py b/lago_python_client/services/request.py index 76f18308..350e73ed 100644 --- a/lago_python_client/services/request.py +++ b/lago_python_client/services/request.py @@ -54,4 +54,5 @@ def make_headers(*, api_key: str) -> Mapping[str, str]: send_get_request = httpx.get send_post_request = httpx.post send_put_request = httpx.put +send_patch_request = httpx.patch send_delete_request = httpx.delete diff --git a/tests/fixtures/credit_note.json b/tests/fixtures/credit_note.json index 4e8fd390..9ede2350 100644 --- a/tests/fixtures/credit_note.json +++ b/tests/fixtures/credit_note.json @@ -19,6 +19,10 @@ "coupons_adjustment_amount_cents": 0, "created_at": "2022-10-04 16:21:00", "updated_at": "2022-10-04 16:21:00", + "metadata": { + "foo": "bar", + "baz": "qux" + }, "items": [ { "lago_id": "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba", diff --git a/tests/test_credit_note_client.py b/tests/test_credit_note_client.py index d49ab1cb..e7ca4f87 100644 --- a/tests/test_credit_note_client.py +++ b/tests/test_credit_note_client.py @@ -192,3 +192,68 @@ def test_valid_estimate_credit_note_request(httpx_mock: HTTPXMock): response = client.credit_notes.estimate(estimate_credit_note()) assert response.lago_invoice_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + + +def mock_metadata_response(): + return b'{"metadata": {"foo": "bar", "baz": null}}' + + +def mock_null_metadata_response(): + return b'{"metadata": null}' + + +def test_valid_replace_metadata_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + credit_note_id = "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/credit_notes/" + credit_note_id + "/metadata", + content=mock_metadata_response(), + ) + response = client.credit_notes.replace_metadata(credit_note_id, {"foo": "bar", "baz": None}) + + assert response == {"foo": "bar", "baz": None} + + +def test_valid_merge_metadata_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + credit_note_id = "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + + httpx_mock.add_response( + method="PATCH", + url="https://api.getlago.com/api/v1/credit_notes/" + credit_note_id + "/metadata", + content=mock_metadata_response(), + ) + response = client.credit_notes.merge_metadata(credit_note_id, {"foo": "qux"}) + + assert response == {"foo": "bar", "baz": None} + + +def test_valid_delete_all_metadata_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + credit_note_id = "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/credit_notes/" + credit_note_id + "/metadata", + content=mock_null_metadata_response(), + ) + response = client.credit_notes.delete_all_metadata(credit_note_id) + + assert response is None + + +def test_valid_delete_metadata_key_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + credit_note_id = "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" + key = "foo" + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/credit_notes/" + credit_note_id + "/metadata/" + key, + content=b'{"metadata": {"baz": "qux"}}', + ) + response = client.credit_notes.delete_metadata_key(credit_note_id, key) + + assert response == {"baz": "qux"}