cuenca-validations 2.1.31.dev0__tar.gz → 2.1.32.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/PKG-INFO +1 -1
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/__init__.py +6 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/requests.py +65 -3
- cuenca_validations-2.1.32.dev0/cuenca_validations/version.py +1 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/PKG-INFO +1 -1
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_requests.py +9 -48
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_types.py +93 -0
- cuenca_validations-2.1.31.dev0/cuenca_validations/version.py +0 -1
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/LICENSE +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/README.md +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/__init__.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/card_bins.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/errors.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/py.typed +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/card.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/enums.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/files.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/general.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/helpers.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/identities.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/morals.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/queries.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/typing.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/validators.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/SOURCES.txt +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/dependency_links.txt +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/requires.txt +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations.egg-info/top_level.txt +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/setup.cfg +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/setup.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/__init__.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_card.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_errors.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_helpers.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_statement.py +0 -0
- {cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/tests/test_validators.py +0 -0
|
@@ -44,6 +44,9 @@ __all__ = [
|
|
|
44
44
|
'FileBatchUploadRequest',
|
|
45
45
|
'FileRequest',
|
|
46
46
|
'FileUploadRequest',
|
|
47
|
+
'FraudFundsTransferAcceptedResponse',
|
|
48
|
+
'FraudFundsTransferRequest',
|
|
49
|
+
'FraudFundsTransferResultEvent',
|
|
47
50
|
'Gender',
|
|
48
51
|
'IncomeType',
|
|
49
52
|
'IssuerNetwork',
|
|
@@ -230,6 +233,9 @@ from .requests import (
|
|
|
230
233
|
FileBatchUploadRequest,
|
|
231
234
|
FileRequest,
|
|
232
235
|
FileUploadRequest,
|
|
236
|
+
FraudFundsTransferAcceptedResponse,
|
|
237
|
+
FraudFundsTransferRequest,
|
|
238
|
+
FraudFundsTransferResultEvent,
|
|
233
239
|
KYCValidationRequest,
|
|
234
240
|
LimitedWalletRequest,
|
|
235
241
|
PartnerRequest,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
from typing import Annotated, Any, Optional, Union
|
|
2
|
+
from typing import Annotated, Any, Literal, Optional, Union
|
|
3
3
|
|
|
4
|
-
from clabe import Clabe
|
|
4
|
+
from clabe import Clabe, validate_clabe
|
|
5
5
|
from pydantic import (
|
|
6
6
|
BaseModel,
|
|
7
7
|
ConfigDict,
|
|
@@ -309,6 +309,69 @@ class WalletTransactionRequest(BaseRequest):
|
|
|
309
309
|
amount: StrictPositiveInt
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
class FraudFundsTransferRequest(BaseRequest):
|
|
313
|
+
user_id: NonEmptyStr
|
|
314
|
+
clabe: Clabe
|
|
315
|
+
concepto: NonEmptyStr
|
|
316
|
+
amount: Optional[StrictPositiveInt] = None
|
|
317
|
+
reason: Optional[NonEmptyStr] = None
|
|
318
|
+
request_id: Optional[NonEmptyStr] = None
|
|
319
|
+
requested_by: Optional[NonEmptyStr] = None
|
|
320
|
+
|
|
321
|
+
@field_validator('clabe', mode='before')
|
|
322
|
+
@classmethod
|
|
323
|
+
def validate_clabe_format(cls, clabe: str) -> str:
|
|
324
|
+
if not validate_clabe(clabe):
|
|
325
|
+
raise ValueError('La CLABE ingresada no es valida')
|
|
326
|
+
return clabe
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class FraudFundsTransferAcceptedResponse(BaseRequest):
|
|
330
|
+
request_id: NonEmptyStr
|
|
331
|
+
status: Literal['queued']
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class FraudFundsTransferResultEvent(BaseRequest):
|
|
335
|
+
schema_version: NonEmptyStr
|
|
336
|
+
event_type: Literal[
|
|
337
|
+
'fraud_funds_transfer.succeeded',
|
|
338
|
+
'fraud_funds_transfer.failed',
|
|
339
|
+
]
|
|
340
|
+
request_id: NonEmptyStr
|
|
341
|
+
user_id: NonEmptyStr
|
|
342
|
+
transaction_id: Optional[NonEmptyStr] = None
|
|
343
|
+
amount: Optional[StrictPositiveInt] = None
|
|
344
|
+
clave_rastreo: Optional[NonEmptyStr] = None
|
|
345
|
+
reason_code: Optional[NonEmptyStr] = None
|
|
346
|
+
message: Optional[NonEmptyStr] = None
|
|
347
|
+
completed_at: dt.datetime
|
|
348
|
+
|
|
349
|
+
@model_validator(mode='after')
|
|
350
|
+
def validate_payload(self) -> 'FraudFundsTransferResultEvent':
|
|
351
|
+
if self.event_type == 'fraud_funds_transfer.succeeded':
|
|
352
|
+
required = {
|
|
353
|
+
'transaction_id': self.transaction_id,
|
|
354
|
+
'amount': self.amount,
|
|
355
|
+
}
|
|
356
|
+
missing = [
|
|
357
|
+
field for field, value in required.items() if value is None
|
|
358
|
+
]
|
|
359
|
+
if missing:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
f'{", ".join(missing)} required for succeeded event'
|
|
362
|
+
)
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
required = {
|
|
366
|
+
'reason_code': self.reason_code,
|
|
367
|
+
'message': self.message,
|
|
368
|
+
}
|
|
369
|
+
missing = [field for field, value in required.items() if value is None]
|
|
370
|
+
if missing:
|
|
371
|
+
raise ValueError(f'{", ".join(missing)} required for failed event')
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
|
|
312
375
|
class FraudValidationRequest(BaseModel):
|
|
313
376
|
amount: StrictPositiveInt
|
|
314
377
|
merchant_name: str
|
|
@@ -456,7 +519,6 @@ class UserTOSAgreementRequest(BaseRequest):
|
|
|
456
519
|
|
|
457
520
|
|
|
458
521
|
class PasswordResetRequest(BaseRequest):
|
|
459
|
-
verification_id: str
|
|
460
522
|
location: Coordinate
|
|
461
523
|
|
|
462
524
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.1.32.dev0'
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
import pytest
|
|
4
2
|
from pydantic import ValidationError
|
|
5
3
|
from pydantic_extra_types.phone_numbers import PhoneNumber
|
|
@@ -40,52 +38,15 @@ def test_file_cuenca_url_invalid() -> None:
|
|
|
40
38
|
UserTOSAgreementRequest(**request_data)
|
|
41
39
|
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'verification_id': _VERIFICATION_ID,
|
|
53
|
-
'location': _VALID_COORDINATE,
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
'verification_id': _VERIFICATION_ID,
|
|
57
|
-
'location': {
|
|
58
|
-
'latitude': 19.432607,
|
|
59
|
-
'longitude': -99.133209,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
id='serializes',
|
|
63
|
-
),
|
|
64
|
-
pytest.param(
|
|
65
|
-
{'location': _VALID_COORDINATE},
|
|
66
|
-
None,
|
|
67
|
-
id='missing_verification_id',
|
|
68
|
-
),
|
|
69
|
-
pytest.param(
|
|
70
|
-
{
|
|
71
|
-
'verification_id': _VERIFICATION_ID,
|
|
72
|
-
'location': (91.0, 0.0),
|
|
73
|
-
},
|
|
74
|
-
None,
|
|
75
|
-
id='bad_coordinate',
|
|
76
|
-
),
|
|
77
|
-
],
|
|
78
|
-
)
|
|
79
|
-
def test_password_reset_request(
|
|
80
|
-
payload: DictStrAny,
|
|
81
|
-
expected_dump: Optional[DictStrAny],
|
|
82
|
-
) -> None:
|
|
83
|
-
if expected_dump is None:
|
|
84
|
-
with pytest.raises(ValidationError):
|
|
85
|
-
PasswordResetRequest.model_validate(payload)
|
|
86
|
-
else:
|
|
87
|
-
req = PasswordResetRequest.model_validate(payload)
|
|
88
|
-
assert req.model_dump() == expected_dump
|
|
41
|
+
def test_password_reset_request_serializes() -> None:
|
|
42
|
+
payload: DictStrAny = {'location': (19.432607, -99.133209)}
|
|
43
|
+
req = PasswordResetRequest.model_validate(payload)
|
|
44
|
+
assert req.model_dump() == {
|
|
45
|
+
'location': {
|
|
46
|
+
'latitude': 19.432607,
|
|
47
|
+
'longitude': -99.133209,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
89
50
|
|
|
90
51
|
|
|
91
52
|
def test_update_user_requires_at_least_one_param():
|
|
@@ -12,6 +12,9 @@ from pydantic.fields import FieldInfo
|
|
|
12
12
|
|
|
13
13
|
from cuenca_validations.types import (
|
|
14
14
|
CardQuery,
|
|
15
|
+
FraudFundsTransferAcceptedResponse,
|
|
16
|
+
FraudFundsTransferRequest,
|
|
17
|
+
FraudFundsTransferResultEvent,
|
|
15
18
|
JSONEncoder,
|
|
16
19
|
QueryParams,
|
|
17
20
|
SantizedDict,
|
|
@@ -636,6 +639,96 @@ def test_bank_account_validation_clabe_request():
|
|
|
636
639
|
assert BankAccountValidationRequest(account_number='646180157098510917')
|
|
637
640
|
|
|
638
641
|
|
|
642
|
+
def test_fraud_funds_transfer_models():
|
|
643
|
+
request = FraudFundsTransferRequest(
|
|
644
|
+
user_id='US123',
|
|
645
|
+
clabe='646180157098510917',
|
|
646
|
+
concepto=' fondos fraude ',
|
|
647
|
+
amount=100,
|
|
648
|
+
request_id='REQ123',
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
assert request.concepto == 'fondos fraude'
|
|
652
|
+
assert request.model_dump() == {
|
|
653
|
+
'user_id': 'US123',
|
|
654
|
+
'clabe': '646180157098510917',
|
|
655
|
+
'concepto': 'fondos fraude',
|
|
656
|
+
'amount': 100,
|
|
657
|
+
'request_id': 'REQ123',
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
response = FraudFundsTransferAcceptedResponse(
|
|
661
|
+
request_id='REQ123',
|
|
662
|
+
status='queued',
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
assert response.status == 'queued'
|
|
666
|
+
|
|
667
|
+
succeeded_event = FraudFundsTransferResultEvent(
|
|
668
|
+
schema_version='1.0',
|
|
669
|
+
event_type='fraud_funds_transfer.succeeded',
|
|
670
|
+
request_id='REQ123',
|
|
671
|
+
user_id='US123',
|
|
672
|
+
transaction_id='TR123',
|
|
673
|
+
amount=100,
|
|
674
|
+
clave_rastreo='RASTREO123',
|
|
675
|
+
completed_at=now,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
assert succeeded_event.transaction_id == 'TR123'
|
|
679
|
+
|
|
680
|
+
failed_event = FraudFundsTransferResultEvent(
|
|
681
|
+
schema_version='1.0',
|
|
682
|
+
event_type='fraud_funds_transfer.failed',
|
|
683
|
+
request_id='REQ123',
|
|
684
|
+
user_id='US123',
|
|
685
|
+
reason_code='insufficient_funds',
|
|
686
|
+
message='Insufficient funds',
|
|
687
|
+
completed_at=now,
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
assert failed_event.reason_code == 'insufficient_funds'
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def test_fraud_funds_transfer_request_invalid_clabe():
|
|
694
|
+
with pytest.raises(ValidationError) as ex:
|
|
695
|
+
FraudFundsTransferRequest(
|
|
696
|
+
user_id='US123',
|
|
697
|
+
clabe='not-a-clabe',
|
|
698
|
+
concepto='fondos fraude',
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
assert 'La CLABE ingresada no es valida' in str(ex.value)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@pytest.mark.parametrize(
|
|
705
|
+
'event_type,expected_error',
|
|
706
|
+
[
|
|
707
|
+
(
|
|
708
|
+
'fraud_funds_transfer.succeeded',
|
|
709
|
+
'transaction_id, amount required for succeeded event',
|
|
710
|
+
),
|
|
711
|
+
(
|
|
712
|
+
'fraud_funds_transfer.failed',
|
|
713
|
+
'reason_code, message required for failed event',
|
|
714
|
+
),
|
|
715
|
+
],
|
|
716
|
+
)
|
|
717
|
+
def test_fraud_funds_transfer_result_event_requires_payload(
|
|
718
|
+
event_type, expected_error
|
|
719
|
+
):
|
|
720
|
+
with pytest.raises(ValidationError) as ex:
|
|
721
|
+
FraudFundsTransferResultEvent(
|
|
722
|
+
schema_version='1.0',
|
|
723
|
+
event_type=event_type,
|
|
724
|
+
request_id='REQ123',
|
|
725
|
+
user_id='US123',
|
|
726
|
+
completed_at=now,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
assert expected_error in str(ex.value)
|
|
730
|
+
|
|
731
|
+
|
|
639
732
|
@pytest.mark.parametrize(
|
|
640
733
|
'input_data',
|
|
641
734
|
[
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.1.31.dev0'
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/__init__.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/card_bins.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/errors.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/py.typed
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/card.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/enums.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/types/morals.py
RENAMED
|
File without changes
|
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/typing.py
RENAMED
|
File without changes
|
{cuenca_validations-2.1.31.dev0 → cuenca_validations-2.1.32.dev0}/cuenca_validations/validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|