Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion fintoc/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__


Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions fintoc/managers/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions fintoc/managers/v2/transfer_batches_manager.py
Original file line number Diff line number Diff line change
@@ -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"]
1 change: 1 addition & 0 deletions fintoc/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions fintoc/resources/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
from .subscription import Subscription
from .subscription_item import SubscriptionItem
from .transfer import Transfer
from .transfer_batch import TransferBatch
7 changes: 7 additions & 0 deletions fintoc/resources/v2/transfer_batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Module to hold the TransferBatch resource."""

from fintoc.mixins import ResourceMixin


class TransferBatch(ResourceMixin):
"""Represents a Fintoc Transfer Batch."""
66 changes: 66 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading