diff --git a/cuenca/resources/transfers.py b/cuenca/resources/transfers.py index bde76ac3..2738fcf7 100644 --- a/cuenca/resources/transfers.py +++ b/cuenca/resources/transfers.py @@ -2,20 +2,23 @@ from typing import ClassVar, Optional, cast from cuenca_validations.types import ( + TransactionStatus, TransferNetwork, TransferQuery, TransferRequest, + UpdateTransferRequest, ) from cuenca_validations.typing import DictStrAny from requests import HTTPError from ..exc import CuencaException +from ..http import Session, session as global_session from .accounts import Account -from .base import Creatable, Transaction +from .base import Creatable, Transaction, Updateable from .resources import retrieve_uri -class Transfer(Transaction, Creatable): +class Transfer(Transaction, Creatable, Updateable): _resource: ClassVar = 'transfers' _query_params: ClassVar = TransferQuery @@ -71,6 +74,17 @@ def create( ) return cls._create(**req.model_dump()) + @classmethod + def update( + cls, + transfer_id: str, + status: TransactionStatus, + *, + session: Session = global_session, + ) -> 'Transfer': + req = UpdateTransferRequest(status=status) + return cls._update(transfer_id, session=session, **req.model_dump()) + @classmethod def create_many(cls, requests: list[TransferRequest]) -> DictStrAny: transfers: DictStrAny = dict(submitted=[], errors=[]) diff --git a/cuenca/version.py b/cuenca/version.py index 0f7e38a4..a1029370 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.2.0' +__version__ = '2.2.1' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' diff --git a/requirements.txt b/requirements.txt index ff128c6f..3da97f14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.1.31 +cuenca-validations==2.1.34 pydantic-extra-types==2.10.2 diff --git a/setup.py b/setup.py index ab55a35a..cf4d610e 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ python_requires='>=3.9', install_requires=[ 'requests>=2.32.0', - 'cuenca-validations>=2.1.27', + 'cuenca-validations>=2.1.34', 'pydantic-extra-types>=2.10.0', ], classifiers=[ diff --git a/tests/resources/cassettes/test_transfers_update_failed.yaml b/tests/resources/cassettes/test_transfers_update_failed.yaml new file mode 100644 index 00000000..6dc925ca --- /dev/null +++ b/tests/resources/cassettes/test_transfers_update_failed.yaml @@ -0,0 +1,35 @@ +interactions: +- request: + body: '{"status": "failed"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Content-Length: + - '20' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.2.1 + X-Cuenca-Api-Version: + - '2020-03-19' + method: PATCH + uri: https://sandbox.cuenca.com/transfers/TR02 + response: + body: + string: '{"id":"TR02","created_at":"2026-05-26T12:00:00.000000","updated_at":"2026-05-26T12:02:00.000000","account_number":"646180157046685645","recipient_name":"Rogelio + Lopez","amount":10000,"descriptor":"held transfer","idempotency_key":"idem-tr02","status":"failed","network":"internal","destination_uri":"/accounts/LA1CVCZVNLR4KM42kPcqhBLV","tracking_key":null,"user_id":"US4PCNV8rLB2wqBfORzIAXUl","reference_number":null}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/cassettes/test_transfers_update_succeeded.yaml b/tests/resources/cassettes/test_transfers_update_succeeded.yaml new file mode 100644 index 00000000..4b332928 --- /dev/null +++ b/tests/resources/cassettes/test_transfers_update_succeeded.yaml @@ -0,0 +1,35 @@ +interactions: +- request: + body: '{"status": "succeeded"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Content-Length: + - '23' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.2.1 + X-Cuenca-Api-Version: + - '2020-03-19' + method: PATCH + uri: https://sandbox.cuenca.com/transfers/TR01 + response: + body: + string: '{"id":"TR01","created_at":"2026-05-26T12:00:00.000000","updated_at":"2026-05-26T12:01:00.000000","account_number":"646180157046685645","recipient_name":"Rogelio + Lopez","amount":10000,"descriptor":"held transfer","idempotency_key":"idem-tr01","status":"succeeded","network":"internal","destination_uri":"/accounts/LA1CVCZVNLR4KM42kPcqhBLV","tracking_key":"tk01","user_id":"US4PCNV8rLB2wqBfORzIAXUl","reference_number":null}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/test_transfers.py b/tests/resources/test_transfers.py index 327b645d..84fd9dbb 100644 --- a/tests/resources/test_transfers.py +++ b/tests/resources/test_transfers.py @@ -130,3 +130,33 @@ def test_invalid_params(): with pytest.raises(ValidationError) as e: Transfer.one(invalid_param='invalid_param') assert 'Extra inputs are not permitted' in str(e) + + +@pytest.mark.vcr +def test_transfers_update_succeeded(): + transfer = Transfer.update('TR01', status=TransactionStatus.succeeded) + assert transfer.id == 'TR01' + assert transfer.status == TransactionStatus.succeeded + + +@pytest.mark.vcr +def test_transfers_update_failed(): + transfer = Transfer.update('TR02', status=TransactionStatus.failed) + assert transfer.id == 'TR02' + assert transfer.status == TransactionStatus.failed + + +@pytest.mark.parametrize( + 'invalid_status', + [ + TransactionStatus.created, + TransactionStatus.submitted, + TransactionStatus.in_review, + 'cancelled', + 'approve', + 'reject', + ], +) +def test_transfers_update_rejects_invalid_status(invalid_status): + with pytest.raises(ValidationError): + Transfer.update('TR03', status=invalid_status)