diff --git a/README.md b/README.md index fa3fd49..998a321 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ - [V2 Endpoints](#v2-endpoints) - [Nested actions or resources](#nested-actions-or-resources) - [Entity onboardings](#entity-onboardings) + - [Transfer batches](#transfer-batches) - [Webhook Signature Validation](#webhook-signature-validation) - [Idempotency Keys](#idempotency-keys) - [Generate the JWS Signature](#gnerate-the-jws-signature) @@ -262,6 +263,35 @@ onboarding can be sent for review: onboarding = client.v2.entities.onboardings.submit(onboarding_id, entity_id=entity_id) ``` +#### Transfer batches + +Transfer batches let you send several transfers in a single request. Creating one +requires the [JWS Signature](#generate-the-jws-signature): + +```python +transfer_batch = client.v2.transfer_batches.create( + description="Payroll October", + currency="mxn", + transfers=[ + { + "account_id": "acc_123545", + "amount": 49523, + "counterparty": {"account_number": "014180655091438298"}, + "comment": "Salary", + "metadata": {"employee_id": "123"}, + }, + ], +) +``` + +To list them, filtering by creation date: + +```python +transfer_batches = client.v2.transfer_batches.list( + since="2026-01-01T00:00:00Z", until="2026-02-01T00:00:00Z" +) +``` + ### Webhook Signature Validation To ensure the authenticity of incoming webhooks from Fintoc, you should always validate the signature. The SDK provides a `WebhookSignature` class to verify the `Fintoc-Signature` header diff --git a/fintoc/core.py b/fintoc/core.py index f032832..7d33a4e 100644 --- a/fintoc/core.py +++ b/fintoc/core.py @@ -29,7 +29,7 @@ from fintoc.managers.v2 import PaymentIntentsManager as PaymentIntentsManagerV2 from fintoc.managers.v2 import PaymentMethodsManager, ProductsManager, SimulateManager from fintoc.managers.v2 import SubscriptionsManager as SubscriptionsManagerV2 -from fintoc.managers.v2 import TransfersManager +from fintoc.managers.v2 import TransferBatchesManager, TransfersManager from fintoc.version import __version__ @@ -77,6 +77,7 @@ def __init__(self, api_key, api_version=None, jws_private_key=None): class _FintocV2: def __init__(self, client): self.transfers = TransfersManager("/v2/transfers", client) + self.transfer_batches = TransferBatchesManager("/v2/transfer_batches", client) self.accounts = AccountsManagerV2("/v2/accounts", client) self.account_numbers = AccountNumbersManager("/v2/account_numbers", client) self.account_verifications = AccountVerificationsManager( diff --git a/fintoc/managers/v2/__init__.py b/fintoc/managers/v2/__init__.py index 32b8d43..4abc66f 100644 --- a/fintoc/managers/v2/__init__.py +++ b/fintoc/managers/v2/__init__.py @@ -17,4 +17,5 @@ from .simulate_manager import SimulateManager from .subscription_items_manager import SubscriptionItemsManager from .subscriptions_manager import SubscriptionsManager +from .transfer_batches_manager import TransferBatchesManager from .transfers_manager import TransfersManager diff --git a/fintoc/managers/v2/transfer_batches_manager.py b/fintoc/managers/v2/transfer_batches_manager.py new file mode 100644 index 0000000..7eb1a81 --- /dev/null +++ b/fintoc/managers/v2/transfer_batches_manager.py @@ -0,0 +1,10 @@ +"""Module to hold the transfer batches manager.""" + +from fintoc.mixins import ManagerMixin + + +class TransferBatchesManager(ManagerMixin): + """Represents a transfer batches manager.""" + + resource = "transfer_batch" + methods = ["list", "get", "create"] diff --git a/fintoc/resources/__init__.py b/fintoc/resources/__init__.py index c7194b8..d9037a5 100644 --- a/fintoc/resources/__init__.py +++ b/fintoc/resources/__init__.py @@ -40,4 +40,5 @@ from .v2.subscription import Subscription as SubscriptionV2 from .v2.subscription_item import SubscriptionItem from .v2.transfer import Transfer +from .v2.transfer_batch import TransferBatch from .webhook_endpoint import WebhookEndpoint diff --git a/fintoc/resources/v2/__init__.py b/fintoc/resources/v2/__init__.py index ab0b1b0..f17e7a1 100644 --- a/fintoc/resources/v2/__init__.py +++ b/fintoc/resources/v2/__init__.py @@ -17,3 +17,4 @@ from .subscription import Subscription from .subscription_item import SubscriptionItem from .transfer import Transfer +from .transfer_batch import TransferBatch diff --git a/fintoc/resources/v2/transfer_batch.py b/fintoc/resources/v2/transfer_batch.py new file mode 100644 index 0000000..4308a17 --- /dev/null +++ b/fintoc/resources/v2/transfer_batch.py @@ -0,0 +1,7 @@ +"""Module to hold the TransferBatch resource.""" + +from fintoc.mixins import ResourceMixin + + +class TransferBatch(ResourceMixin): + """Represents a Fintoc Transfer Batch.""" diff --git a/tests/test_integration.py b/tests/test_integration.py index c396d6a..3bebbfd 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -855,6 +855,72 @@ def test_v2_transfer_create(self): idempotency_key_header = getattr(transfer.headers, "idempotency-key") assert idempotency_key_header == "123456" + def test_v2_transfer_batches_list(self): + """Test getting all transfer batches using v2 API.""" + status = "succeeded" + since = "2026-01-01" + until = "2026-02-01" + transfer_batches = list( + self.fintoc.v2.transfer_batches.list( + status=status, since=since, until=until + ) + ) + + assert len(transfer_batches) > 0 + for transfer_batch in transfer_batches: + assert transfer_batch.method == "get" + assert transfer_batch.url == "v2/transfer_batches" + assert transfer_batch.params.status == status + assert transfer_batch.params.since == since + assert transfer_batch.params.until == until + + def test_v2_transfer_batch_get(self): + """Test getting a specific transfer batch using v2 API.""" + transfer_batch_id = "test_transfer_batch_id" + + transfer_batch = self.fintoc.v2.transfer_batches.get(transfer_batch_id) + + assert transfer_batch.method == "get" + assert transfer_batch.url == f"v2/transfer_batches/{transfer_batch_id}" + + def test_v2_transfer_batch_create(self): + """Test creating a transfer batch using v2 API.""" + description = "Test transfer batch" + currency = "MXN" + transfers = [ + { + "account_id": "test_account_id", + "amount": 10000, + "counterparty": {"account_number": "014180655091438298"}, + "comment": "Test transfer", + "metadata": {"test_key": "test_value"}, + } + ] + + transfer_batch = self.fintoc.v2.transfer_batches.create( + description=description, currency=currency, transfers=transfers + ) + + assert transfer_batch.method == "post" + assert transfer_batch.url == "v2/transfer_batches" + assert transfer_batch.json.description == description + assert transfer_batch.json.currency == currency + assert transfer_batch.json.transfers[0].amount == transfers[0]["amount"] + assert transfer_batch.json.transfers[0].account_id == transfers[0]["account_id"] + + idempotency_key_header = getattr(transfer_batch.headers, "idempotency-key") + assert idempotency_key_header is not None and idempotency_key_header != "" + + idempotency_key = "123456" + transfer_batch = self.fintoc.v2.transfer_batches.create( + description=description, + currency=currency, + transfers=transfers, + idempotency_key=idempotency_key, + ) + idempotency_key_header = getattr(transfer_batch.headers, "idempotency-key") + assert idempotency_key_header == "123456" + def test_v2_simulate_receive_transfer(self): """Test simulating receiving a transfer using v2 API.""" account_number_id = "test_account_number_id"