Skip to content

Commit b1c29ab

Browse files
authored
Adding dynamic cvv to card (#302)
* adding dcvv fields * fixing * bump cv * lint * fix * version * version * comments
1 parent a97b911 commit b1c29ab

File tree

8 files changed

+170
-32
lines changed

8 files changed

+170
-32
lines changed

cuenca/resources/cards.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime as dt
12
from typing import ClassVar, Optional, cast
23

34
from cuenca_validations.types import (
@@ -30,9 +31,12 @@ class Card(Retrievable, Queryable, Creatable, Updateable):
3031
status: CardStatus
3132
issuer: CardIssuer
3233
funding_type: CardFundingType
34+
dcvv: Optional[str] = None
35+
dcvv_expires_at: Optional[dt.datetime] = None
3336
pin_attempts_failed: Optional[int] = None
3437
platform_id: Optional[str] = None
3538
card_holder_user_id: Optional[str] = None
39+
is_dynamic_cvv: bool = False
3640

3741
@property
3842
def last_4_digits(self):
@@ -57,6 +61,7 @@ def create(
5761
funding_type: CardFundingType,
5862
user_id: str = 'me',
5963
card_holder_user_id: Optional[str] = None,
64+
is_dynamic_cvv: bool = False,
6065
*,
6166
session: Session = global_session,
6267
) -> 'Card':
@@ -74,6 +79,7 @@ def create(
7479
issuer=issuer,
7580
funding_type=funding_type,
7681
card_holder_user_id=card_holder_user_id,
82+
is_dynamic_cvv=is_dynamic_cvv,
7783
)
7884
return cast('Card', cls._create(session=session, **req.dict()))
7985

@@ -83,6 +89,7 @@ def update(
8389
card_id: str,
8490
status: Optional[CardStatus] = None,
8591
pin_block: Optional[str] = None,
92+
is_dynamic_cvv: bool = False,
8693
*,
8794
session: Session = global_session,
8895
) -> 'Card':
@@ -96,7 +103,9 @@ def update(
96103
:param session:
97104
:return: Updated card object
98105
"""
99-
req = CardUpdateRequest(status=status, pin_block=pin_block)
106+
req = CardUpdateRequest(
107+
status=status, pin_block=pin_block, is_dynamic_cvv=is_dynamic_cvv
108+
)
100109
resp = cls._update(card_id, session=session, **req.dict())
101110
return cast('Card', resp)
102111

cuenca/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = '0.14.6'
1+
__version__ = '0.15.0'
22
CLIENT_VERSION = __version__
33
API_VERSION = '2020-03-19'

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
requests==2.27.1
2-
cuenca-validations==0.11.4
2+
cuenca-validations==0.11.5
33
dataclasses>=0.7;python_version<"3.7"

tests/resources/cassettes/test_card_create.yaml

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
interactions:
22
- request:
3-
body: '{"user_id": "US1237", "issuer": "cuenca", "funding_type": "credit"}'
3+
body: '{"user_id": "US1237", "issuer": "cuenca", "funding_type":
4+
"credit", "is_dynamic_cvv": false}'
45
headers:
56
Accept:
67
- '*/*'
@@ -11,43 +12,41 @@ interactions:
1112
Connection:
1213
- keep-alive
1314
Content-Length:
14-
- '67'
15+
- '110'
1516
Content-Type:
1617
- application/json
1718
User-Agent:
18-
- cuenca-python/0.6.3.dev2
19+
- cuenca-python/0.15.0
1920
X-Cuenca-Api-Version:
2021
- '2020-03-19'
2122
method: POST
2223
uri: https://sandbox.cuenca.com/cards
2324
response:
2425
body:
25-
string: '{"id":"CAycvo_X9TQoKOKsaAvdqn3w","created_at":"2021-02-21T16:03:37.590964","updated_at":"2021-02-21T16:03:37.590972","user_id":"US1237","number":"5448750078699849","exp_month":2,"exp_year":2026,"cvv2":"353","type":"virtual","status":"active","batch":null,"manufacturer":null,"cvv":null,"icvv":null,"pin":null,"issuer":"cuenca","funding_type":"debit","_cls":"BaseCard.CuencaCard"}'
26+
string: '{"id":"CAvrnMlojFRLuMyPPveULAoA","created_at":"2022-09-27T22:32:34.616733","updated_at":"2022-09-27T22:32:35.129133","user_id":"US1237","platform_id":"PTCHzhZ0ILRN-oxwd0u2coiQ","number":"5448750030324585","exp_month":9,"exp_year":27,"cvv2":"783","type":"virtual","status":"active","pin":null,"issuer":"cuenca","funding_type":"credit","card_holder_user_id":"US118qfWNLSaOaDSPxbPzdIg","manufacturer":null,"cvv":"540","icvv":null,"is_dynamic_cvv":false,"pin_block":null,"pin_block_switch":null,"pin_attempts_failed":0}'
2627
headers:
2728
Connection:
2829
- keep-alive
2930
Content-Length:
30-
- '379'
31+
- '533'
3132
Content-Type:
3233
- application/json
3334
Date:
34-
- Sun, 21 Feb 2021 16:03:40 GMT
35-
X-Amzn-Trace-Id:
36-
- Root=1-60328452-3d9fd97d6169e99371e444b7;Sampled=0
35+
- Tue, 27 Sep 2022 22:32:35 GMT
36+
X-Request-Time:
37+
- 'value: 0.870'
3738
x-amz-apigw-id:
38-
- bGmc6EwFCYcFueQ=
39+
- ZJAAaEt7CYcFcyA=
3940
x-amzn-Remapped-Connection:
4041
- keep-alive
4142
x-amzn-Remapped-Content-Length:
42-
- '379'
43+
- '533'
4344
x-amzn-Remapped-Date:
44-
- Sun, 21 Feb 2021 16:03:40 GMT
45+
- Tue, 27 Sep 2022 22:32:35 GMT
4546
x-amzn-Remapped-Server:
46-
- nginx/1.18.0
47-
x-amzn-Remapped-x-amzn-RequestId:
48-
- 6e42147f-60fc-420a-aea2-5896717f36b1
47+
- nginx/1.22.0
4948
x-amzn-RequestId:
50-
- 3e8b6d93-7d77-40b6-95be-e4b7c9a1fc57
49+
- f6dfb169-9440-4b5a-b2ae-267e87945de7
5150
status:
5251
code: 201
5352
message: Created
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
interactions:
2+
- request:
3+
body: '{"user_id": "US1237", "issuer": "cuenca", "funding_type":
4+
"credit", "is_dynamic_cvv": true}'
5+
headers:
6+
Accept:
7+
- '*/*'
8+
Accept-Encoding:
9+
- gzip, deflate
10+
Authorization:
11+
- DUMMY
12+
Connection:
13+
- keep-alive
14+
Content-Length:
15+
- '109'
16+
Content-Type:
17+
- application/json
18+
User-Agent:
19+
- cuenca-python/0.15.0
20+
X-Cuenca-Api-Version:
21+
- '2020-03-19'
22+
method: POST
23+
uri: https://sandbox.cuenca.com/cards
24+
response:
25+
body:
26+
string: '{"id":"CAXhjbtkPtR0mK4mB-qxAfTg","created_at":"2022-09-27T22:40:32.407778","updated_at":"2022-09-27T22:40:32.924951","user_id":"US1237","platform_id":"PTCHzhZ0ILRN-oxwd0u2coiQ","number":"5448750085957396","exp_month":9,"exp_year":27,"cvv2":"847","type":"virtual","status":"active","pin":null,"issuer":"cuenca","funding_type":"credit","card_holder_user_id":"US118qfWNLSaOaDSPxbPzdIg","manufacturer":null,"cvv":"323","icvv":null,"is_dynamic_cvv":true,"pin_block":null,"pin_block_switch":null,"pin_attempts_failed":0}'
27+
headers:
28+
Connection:
29+
- keep-alive
30+
Content-Length:
31+
- '532'
32+
Content-Type:
33+
- application/json
34+
Date:
35+
- Tue, 27 Sep 2022 22:40:33 GMT
36+
X-Request-Time:
37+
- 'value: 0.953'
38+
x-amz-apigw-id:
39+
- ZJBLEF_5CYcFSBA=
40+
x-amzn-Remapped-Connection:
41+
- keep-alive
42+
x-amzn-Remapped-Content-Length:
43+
- '532'
44+
x-amzn-Remapped-Date:
45+
- Tue, 27 Sep 2022 22:40:33 GMT
46+
x-amzn-Remapped-Server:
47+
- nginx/1.22.0
48+
x-amzn-RequestId:
49+
- b1364573-21c6-4c12-bcf7-fb294c66012e
50+
status:
51+
code: 201
52+
message: Created
53+
version: 1

tests/resources/cassettes/test_card_retrieve.yaml

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,37 @@ interactions:
1111
Connection:
1212
- keep-alive
1313
User-Agent:
14-
- cuenca-python/0.6.3.dev2
14+
- cuenca-python/0.15.0
1515
X-Cuenca-Api-Version:
1616
- '2020-03-19'
1717
method: GET
1818
uri: https://sandbox.cuenca.com/cards/CAycvo_X9TQoKOKsaAvdqn3w
1919
response:
2020
body:
21-
string: '{"id":"CAycvo_X9TQoKOKsaAvdqn3w","created_at":"2021-02-21T16:03:37.590000","updated_at":"2021-02-21T16:03:37.590000","user_id":"US1237","number":"5448750078699849","exp_month":2,"exp_year":2026,"cvv2":"353","type":"virtual","status":"active","batch":null,"manufacturer":null,"cvv":null,"icvv":null,"pin":null,"issuer":"cuenca","funding_type":"debit","_cls":"BaseCard.CuencaCard"}'
21+
string: '{"id":"CAycvo_X9TQoKOKsaAvdqn3w","created_at":"2022-09-27T22:49:23.404000","updated_at":"2022-09-27T22:49:23.907000","user_id":"US118qfWNLSaOaDSPxbPzdIg","platform_id":"PTCHzhZ0ILRN-oxwd0u2coiQ","number":"5448750051287265","exp_month":9,"exp_year":27,"cvv2":"657","type":"virtual","status":"active","pin":null,"issuer":"cuenca","funding_type":"credit","card_holder_user_id":"US118qfWNLSaOaDSPxbPzdIg","manufacturer":null,"cvv":"100","icvv":null,"is_dynamic_cvv":false,"pin_block":null,"pin_block_switch":null,"pin_attempts_failed":0}'
2222
headers:
2323
Connection:
2424
- keep-alive
2525
Content-Length:
26-
- '379'
26+
- '533'
2727
Content-Type:
2828
- application/json
2929
Date:
30-
- Mon, 22 Feb 2021 22:06:23 GMT
31-
X-Amzn-Trace-Id:
32-
- Root=1-60342ade-474422113c7c4e36041bd343;Sampled=0
30+
- Tue, 27 Sep 2022 22:49:54 GMT
31+
X-Request-Time:
32+
- 'value: 0.507'
3333
x-amz-apigw-id:
34-
- bKuixEr3iYcFQ3w=
34+
- ZJCi2GpqCYcFT5Q=
3535
x-amzn-Remapped-Connection:
3636
- keep-alive
3737
x-amzn-Remapped-Content-Length:
38-
- '379'
38+
- '533'
3939
x-amzn-Remapped-Date:
40-
- Mon, 22 Feb 2021 22:06:23 GMT
40+
- Tue, 27 Sep 2022 22:49:54 GMT
4141
x-amzn-Remapped-Server:
42-
- nginx/1.18.0
43-
x-amzn-Remapped-x-amzn-RequestId:
44-
- 23a73914-2022-4a0f-970e-ce7321d26c1a
42+
- nginx/1.22.0
4543
x-amzn-RequestId:
46-
- 5d785911-f1b1-4b9a-954c-8aabf89ac457
44+
- 17b3414e-88e4-41d6-8d3e-358a6cd6fd48
4745
status:
4846
code: 200
4947
message: OK
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Authorization:
10+
- DUMMY
11+
Connection:
12+
- keep-alive
13+
User-Agent:
14+
- cuenca-python/0.15.0
15+
X-Cuenca-Api-Version:
16+
- '2020-03-19'
17+
method: GET
18+
uri: https://sandbox.cuenca.com/cards/CAycvo_X9TQoKOKsaAvdqn3w
19+
response:
20+
body:
21+
string: '{"id":"CAycvo_X9TQoKOKsaAvdqn3w","created_at":"2022-09-27T22:49:23.404000","updated_at":"2022-09-27T22:51:24.249000","user_id":"US118qfWNLSaOaDSPxbPzdIg","platform_id":"PTCHzhZ0ILRN-oxwd0u2coiQ","number":"5448750051287265","exp_month":9,"exp_year":27,"cvv2":"657","type":"virtual","status":"active","pin":null,"issuer":"cuenca","funding_type":"credit","card_holder_user_id":"US118qfWNLSaOaDSPxbPzdIg","manufacturer":null,"cvv":"100","icvv":null,"is_dynamic_cvv":true,"pin_block":null,"pin_block_switch":null,"pin_attempts_failed":0,"dcvv":"829","dcvv_expires_at":"2022-09-27T22:59:10.066677"}'
22+
headers:
23+
Connection:
24+
- keep-alive
25+
Content-Length:
26+
- '592'
27+
Content-Type:
28+
- application/json
29+
Date:
30+
- Tue, 27 Sep 2022 22:54:10 GMT
31+
X-Request-Time:
32+
- 'value: 0.706'
33+
x-amz-apigw-id:
34+
- ZJDKvFfBCYcFRDg=
35+
x-amzn-Remapped-Connection:
36+
- keep-alive
37+
x-amzn-Remapped-Content-Length:
38+
- '592'
39+
x-amzn-Remapped-Date:
40+
- Tue, 27 Sep 2022 22:54:10 GMT
41+
x-amzn-Remapped-Server:
42+
- nginx/1.22.0
43+
x-amzn-RequestId:
44+
- 29809dfb-52f8-4ed9-8acc-c6335348c7db
45+
status:
46+
code: 200
47+
message: OK
48+
version: 1

tests/resources/test_cards.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datetime as dt
2+
13
import pytest
24
from cuenca_validations.types import (
35
CardFundingType,
@@ -20,6 +22,19 @@ def test_card_create():
2022
assert len(card.number) == 16
2123
assert card.type == CardType.virtual
2224
assert card.user_id == user_id
25+
assert card.is_dynamic_cvv is False
26+
27+
28+
@pytest.mark.vcr
29+
def test_card_create_dcvv():
30+
card = Card.create(
31+
CardIssuer.cuenca, CardFundingType.credit, user_id, is_dynamic_cvv=True
32+
)
33+
assert card.id
34+
assert len(card.number) == 16
35+
assert card.type == CardType.virtual
36+
assert card.user_id == user_id
37+
assert card.is_dynamic_cvv is True
2338

2439

2540
@pytest.mark.vcr
@@ -34,10 +49,26 @@ def test_card_retrieve():
3449
card: Card = Card.retrieve(card_id)
3550
assert card.id == card_id
3651
assert len(card.number) == 16
37-
assert card.last_4_digits == '9849'
52+
assert card.last_4_digits == '7265'
53+
assert card.bin == '544875'
54+
assert card.type == CardType.virtual
55+
assert not card.pin_attempts_exceeded
56+
assert card.is_dynamic_cvv is False
57+
assert card.dcvv is None
58+
59+
60+
@pytest.mark.vcr
61+
def test_card_retrieve_dcvv():
62+
card: Card = Card.retrieve(card_id)
63+
assert card.id == card_id
64+
assert len(card.number) == 16
65+
assert card.last_4_digits == '7265'
3866
assert card.bin == '544875'
3967
assert card.type == CardType.virtual
4068
assert not card.pin_attempts_exceeded
69+
assert card.is_dynamic_cvv is True
70+
assert card.dcvv.isdigit()
71+
assert isinstance(card.dcvv_expires_at, dt.datetime)
4172

4273

4374
@pytest.mark.vcr

0 commit comments

Comments
 (0)