cuenca-validations 2.1.31.dev1__py3-none-any.whl → 2.1.32.dev1__py3-none-any.whl

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.
@@ -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, Self, 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: StrictPositiveInt | None = None
317
+ reason: NonEmptyStr | None = None
318
+ request_id: NonEmptyStr | None = None
319
+ requested_by: NonEmptyStr | None = 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: NonEmptyStr | None = None
343
+ amount: StrictPositiveInt | None = None
344
+ clave_rastreo: NonEmptyStr | None = None
345
+ reason_code: NonEmptyStr | None = None
346
+ message: NonEmptyStr | None = None
347
+ completed_at: dt.datetime
348
+
349
+ @model_validator(mode='after')
350
+ def validate_payload(self) -> Self:
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
@@ -1 +1 @@
1
- __version__ = '2.1.31.dev1'
1
+ __version__ = '2.1.32.dev1'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuenca_validations
3
- Version: 2.1.31.dev1
3
+ Version: 2.1.32.dev1
4
4
  Summary: Cuenca common validations
5
5
  Home-page: https://github.com/cuenca-mx/cuenca-validations
6
6
  Author: Cuenca
@@ -4,8 +4,8 @@ cuenca_validations/errors.py,sha256=OtM8EgiKqYdz9Hn66AbBO96orL1or7efkyt0vh0Zxbs,
4
4
  cuenca_validations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cuenca_validations/typing.py,sha256=1QCu81IbVZZpyInjyeAuO-nF36gpT5Gi4o6V9PozuOU,204
6
6
  cuenca_validations/validators.py,sha256=zXSnU5-EMbQZBD-PfFXgP4Z6G7cm7KFDWXB7Nie6WUk,2682
7
- cuenca_validations/version.py,sha256=XkU16FsSKKjz0G9A4S-b9cWBXeznI_yz9kVIlEuDcs0,28
8
- cuenca_validations/types/__init__.py,sha256=llL7obyGbxbwmcyiSXX9ZZ5P1REbD9H2tOP34klwKYE,5519
7
+ cuenca_validations/version.py,sha256=bKjtKPCh0nfbVnODHo_ttPR_qPm2Sq7b8X9RsEZfpdA,28
8
+ cuenca_validations/types/__init__.py,sha256=Kds2j53zUHdjz8LtjDh8CG6d7CN2yTXs0tvi9VBLZ5U,5737
9
9
  cuenca_validations/types/card.py,sha256=UGzz8NTFAverUmdUKAK1oGHnOnjSNTpIRUm93vKSSGY,1295
10
10
  cuenca_validations/types/enums.py,sha256=f-qMUdxLZsAqRvEwh3EcUsB2K-loufvPzdcK0JFQ1JY,20586
11
11
  cuenca_validations/types/files.py,sha256=2CszbwF9ytXV9suFFwyDjYG4XxY8UhCjRw3HttVXXNw,269
@@ -14,17 +14,17 @@ cuenca_validations/types/helpers.py,sha256=4veeLZbugHHqZk0ezSim978VhH6Vq8XTrEhe1
14
14
  cuenca_validations/types/identities.py,sha256=j2xxh3UYHJe6IbAwr9yNXJkebTth_-g3SUmHeiPez8M,5513
15
15
  cuenca_validations/types/morals.py,sha256=davabh5hAnFVQyM7-yCyDaT5ewXnm0cr1BtqDIwzkX8,1833
16
16
  cuenca_validations/types/queries.py,sha256=iVr6Z8ahXon0rlqQLu7aqRY6WtRxkN-1C7A2zeVt2-4,5314
17
- cuenca_validations/types/requests.py,sha256=DZOxi1fWgruUvxt-3nNLKiV4IyiG7sG__pp_pl356dc,24064
18
- cuenca_validations-2.1.31.dev1.dist-info/licenses/LICENSE,sha256=wR76FmxBbfnQpwELkkE5iMF8sFIafEMgXLTE4N4WPTc,1063
17
+ cuenca_validations/types/requests.py,sha256=Ct5C3k41YsqNPzBuj7m_lBAjaQviX5JSLddIQb9AXqU,26105
18
+ cuenca_validations-2.1.32.dev1.dist-info/licenses/LICENSE,sha256=wR76FmxBbfnQpwELkkE5iMF8sFIafEMgXLTE4N4WPTc,1063
19
19
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  tests/test_card.py,sha256=QAfRz7e11gWICPnFJZ2tiYgUsFV3C9TwzJXrDnDNXFw,1202
21
21
  tests/test_errors.py,sha256=ixiIgEuBuzfsL5p4uCFdF32XqFRtTPF6EVhGJ0keOrI,930
22
22
  tests/test_helpers.py,sha256=fx2R7y6oQO8XeQzevJe826NuHkBgvtznZicomchB3Gg,899
23
- tests/test_requests.py,sha256=0ERUPtTZz9ST1dMZM_Dm21BbyJQGN_elGR5pvJ_TE2w,3763
23
+ tests/test_requests.py,sha256=lQgOPjLsogpMphl5wg--vxj9JF2DJF0A2DzQVG5dOEU,3072
24
24
  tests/test_statement.py,sha256=IOE0rRRBgBZSJv_FLaETEyn5NzzXKMNTqgjv99GX-68,1436
25
- tests/test_types.py,sha256=_gaLCEH-P_C6hC-NuIDwHmjReVspD6cpG6TtGlj5aec,21435
25
+ tests/test_types.py,sha256=yh4VNab8goZVURop-ZxYdBHUNe7n6-ONbC1WxBYTxU8,23947
26
26
  tests/test_validators.py,sha256=Jjr9gWTT4cRntGiKvQK4fncqx3JkEuTWkKm1VqpRHTs,1829
27
- cuenca_validations-2.1.31.dev1.dist-info/METADATA,sha256=ToRsDABRN4qM8-RHY-1ANipMGA_gG1XtxrNuC-ozHDs,1600
28
- cuenca_validations-2.1.31.dev1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
29
- cuenca_validations-2.1.31.dev1.dist-info/top_level.txt,sha256=4233xdOs2HtuT-GFRjcDcwK0IwdwvWdczOtk0fPB6Gw,25
30
- cuenca_validations-2.1.31.dev1.dist-info/RECORD,,
27
+ cuenca_validations-2.1.32.dev1.dist-info/METADATA,sha256=v1o0DCoOVtAKNIg-DEkTTY7ThyrAqgFXW8CzOyNf-vs,1600
28
+ cuenca_validations-2.1.32.dev1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
29
+ cuenca_validations-2.1.32.dev1.dist-info/top_level.txt,sha256=4233xdOs2HtuT-GFRjcDcwK0IwdwvWdczOtk0fPB6Gw,25
30
+ cuenca_validations-2.1.32.dev1.dist-info/RECORD,,
tests/test_requests.py CHANGED
@@ -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,44 +38,15 @@ def test_file_cuenca_url_invalid() -> None:
40
38
  UserTOSAgreementRequest(**request_data)
41
39
 
42
40
 
43
- _VALID_COORDINATE = (19.432607, -99.133209)
44
-
45
-
46
- @pytest.mark.parametrize(
47
- ('payload', 'expected_dump'),
48
- [
49
- pytest.param(
50
- {'location': _VALID_COORDINATE},
51
- {
52
- 'location': {
53
- 'latitude': 19.432607,
54
- 'longitude': -99.133209,
55
- },
56
- },
57
- id='serializes',
58
- ),
59
- pytest.param(
60
- {},
61
- None,
62
- id='missing_location',
63
- ),
64
- pytest.param(
65
- {'location': (91.0, 0.0)},
66
- None,
67
- id='bad_coordinate',
68
- ),
69
- ],
70
- )
71
- def test_password_reset_request(
72
- payload: DictStrAny,
73
- expected_dump: Optional[DictStrAny],
74
- ) -> None:
75
- if expected_dump is None:
76
- with pytest.raises(ValidationError):
77
- PasswordResetRequest.model_validate(payload)
78
- else:
79
- req = PasswordResetRequest.model_validate(payload)
80
- 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
+ }
81
50
 
82
51
 
83
52
  def test_update_user_requires_at_least_one_param():
tests/test_types.py CHANGED
@@ -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
  [