From 2d5937600d27627b7ad856193630f10ec0ea4e2b Mon Sep 17 00:00:00 2001 From: tlarrain Date: Tue, 7 Jul 2026 09:15:34 -0400 Subject: [PATCH] feat(v2): add legal representative document upload endpoint Adds upload_legal_representative_document to the onboardings manager (PUT .../legal_representatives/{id}/documents/{slot_key}) and the OnboardingLegalRepresentative resource, mapped from the onboarding's new legal_representatives array. Onboarding create now takes legal_representatives as an array, replacing the singular legal_representative object. --- fintoc/managers/v2/onboardings_manager.py | 11 +++ fintoc/resources/__init__.py | 1 + fintoc/resources/v2/__init__.py | 1 + fintoc/resources/v2/onboarding.py | 1 + .../v2/onboarding_legal_representative.py | 9 +++ tests/test_integration.py | 70 +++++++++++++++++-- 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 fintoc/resources/v2/onboarding_legal_representative.py diff --git a/fintoc/managers/v2/onboardings_manager.py b/fintoc/managers/v2/onboardings_manager.py index ce2f3c2..3c0abbf 100644 --- a/fintoc/managers/v2/onboardings_manager.py +++ b/fintoc/managers/v2/onboardings_manager.py @@ -17,6 +17,7 @@ class OnboardingsManager(ManagerMixin): "submit", "upload_document", "upload_shareholder_document", + "upload_legal_representative_document", ] def _submit(self, identifier, **kwargs): @@ -37,6 +38,16 @@ def _upload_shareholder_document(self, identifier, shareholder_id, file, **kwarg ) return self._upload(path, {"file": self._build_file_payload(file)}) + def _upload_legal_representative_document( + self, identifier, legal_representative_id, slot_key, file, **kwargs + ): + """Upload a document for a legal representative of an onboarding.""" + path = ( + f"{self._build_path(**kwargs)}/{identifier}" + f"/legal_representatives/{legal_representative_id}/documents/{slot_key}" + ) + return self._upload(path, {"file": self._build_file_payload(file)}) + @staticmethod def _build_file_payload(file): """ diff --git a/fintoc/resources/__init__.py b/fintoc/resources/__init__.py index bb8cac4..c7194b8 100644 --- a/fintoc/resources/__init__.py +++ b/fintoc/resources/__init__.py @@ -34,6 +34,7 @@ from .v2.line import Line from .v2.onboarding import Onboarding from .v2.onboarding_document import OnboardingDocument +from .v2.onboarding_legal_representative import OnboardingLegalRepresentative from .v2.onboarding_shareholder import OnboardingShareholder from .v2.payment_method import PaymentMethod from .v2.subscription import Subscription as SubscriptionV2 diff --git a/fintoc/resources/v2/__init__.py b/fintoc/resources/v2/__init__.py index 12d2306..ab0b1b0 100644 --- a/fintoc/resources/v2/__init__.py +++ b/fintoc/resources/v2/__init__.py @@ -10,6 +10,7 @@ from .movement import Movement from .onboarding import Onboarding from .onboarding_document import OnboardingDocument +from .onboarding_legal_representative import OnboardingLegalRepresentative from .onboarding_shareholder import OnboardingShareholder from .payment_method import PaymentMethod from .product import Product diff --git a/fintoc/resources/v2/onboarding.py b/fintoc/resources/v2/onboarding.py index 50f6083..6a224f6 100644 --- a/fintoc/resources/v2/onboarding.py +++ b/fintoc/resources/v2/onboarding.py @@ -7,6 +7,7 @@ class Onboarding(ResourceMixin): """Represents a Fintoc Onboarding.""" mappings = { + "legal_representatives": "onboarding_legal_representative", "shareholders": "onboarding_shareholder", "documents": "onboarding_document", } diff --git a/fintoc/resources/v2/onboarding_legal_representative.py b/fintoc/resources/v2/onboarding_legal_representative.py new file mode 100644 index 0000000..1ae2e99 --- /dev/null +++ b/fintoc/resources/v2/onboarding_legal_representative.py @@ -0,0 +1,9 @@ +"""Module to hold the OnboardingLegalRepresentative resource.""" + +from fintoc.mixins import ResourceMixin + + +class OnboardingLegalRepresentative(ResourceMixin): + """Represents a Fintoc Onboarding Legal Representative.""" + + mappings = {"documents": "onboarding_document"} diff --git a/tests/test_integration.py b/tests/test_integration.py index c200322..5232145 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1297,7 +1297,7 @@ def test_v2_entity_onboarding_create(self): """Test creating an onboarding for an entity using v2 API.""" entity_id = "ent_12345" company_information = {"legal_name": "Acme SpA"} - legal_representative = {"first_name": "Ada", "last_name": "Lovelace"} + legal_representatives = [{"first_name": "Ada", "last_name": "Lovelace"}] transactional_profile = {"monthly_amount_range": "0-1000"} shareholders = [ { @@ -1313,7 +1313,7 @@ def test_v2_entity_onboarding_create(self): onboarding = self.fintoc.v2.entities.onboardings.create( entity_id=entity_id, company_information=company_information, - legal_representative=legal_representative, + legal_representatives=legal_representatives, transactional_profile=transactional_profile, shareholders=shareholders, ) @@ -1325,8 +1325,8 @@ def test_v2_entity_onboarding_create(self): == company_information["legal_name"] ) assert ( - onboarding.json.legal_representative.first_name - == legal_representative["first_name"] + onboarding.json.legal_representatives[0].first_name + == legal_representatives[0]["first_name"] ) assert ( onboarding.json.transactional_profile.monthly_amount_range @@ -1403,10 +1403,39 @@ def test_v2_entity_onboarding_upload_shareholder_document(self, tmp_path): ) assert getattr(result.headers, "content-type").startswith("multipart/form-data") + def test_v2_entity_onboarding_upload_legal_representative_document(self, tmp_path): + """Test uploading a legal representative document using v2 API.""" + entity_id = "ent_12345" + onboarding_id = "onbprc_12345" + legal_representative_id = "onblr_12345" + slot_key = "identification" + file_path = tmp_path / "id.pdf" + file_path.write_bytes(b"%PDF-1.4 test file") + + onboardings = self.fintoc.v2.entities.onboardings + result = onboardings.upload_legal_representative_document( + onboarding_id, + legal_representative_id, + slot_key, + str(file_path), + entity_id=entity_id, + ) + + assert result.method == "put" + assert result.url == ( + f"v2/entities/{entity_id}/onboardings/{onboarding_id}" + f"/legal_representatives/{legal_representative_id}/documents/{slot_key}" + ) + assert getattr(result.headers, "content-type").startswith("multipart/form-data") + assert result.json.multipart is True + def test_v2_onboarding_objetization(self): """Test that a full onboarding payload is objetized with nested resources.""" from fintoc.resources.v2.onboarding import Onboarding from fintoc.resources.v2.onboarding_document import OnboardingDocument + from fintoc.resources.v2.onboarding_legal_representative import ( + OnboardingLegalRepresentative, + ) from fintoc.resources.v2.onboarding_shareholder import OnboardingShareholder from fintoc.utils import objetize @@ -1420,6 +1449,27 @@ def test_v2_onboarding_objetization(self): "reviewed_at": None, "submittable": True, "data": {"company_information": {"legal_name": "Acme SpA"}}, + "legal_representatives": [ + { + "id": "onblr_12345", + "object": "onboarding_legal_representative", + "first_name": "Ada", + "last_name": "Lovelace", + "email": "rep@example.com", + "nationality": "mx", + "identification_number": "AAAA010101HDFAAA01", + "position": "Director General", + "documents": [ + { + "slot_key": "identification", + "status": "uploaded", + "filename": "id.pdf", + "uploaded_at": "2026-01-15T14:30:00Z", + }, + {"slot_key": "power_of_attorney", "status": "missing"}, + ], + } + ], "shareholders": [ { "id": "onbsh_12345", @@ -1446,6 +1496,18 @@ def test_v2_onboarding_objetization(self): assert onboarding.object == "onboarding" assert onboarding.status == "in_progress" assert onboarding.submittable is True + assert isinstance( + onboarding.legal_representatives[0], OnboardingLegalRepresentative + ) + assert onboarding.legal_representatives[0].first_name == "Ada" + assert isinstance( + onboarding.legal_representatives[0].documents[0], OnboardingDocument + ) + assert ( + onboarding.legal_representatives[0].documents[0].slot_key + == "identification" + ) + assert onboarding.legal_representatives[0].documents[1].status == "missing" assert isinstance(onboarding.shareholders[0], OnboardingShareholder) assert onboarding.shareholders[0].name == "Ada" assert isinstance(onboarding.shareholders[0].document, OnboardingDocument)