From f8c70969acbde51c89c2ea24729d7d12cf24ba31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:05:29 +0200 Subject: [PATCH 1/4] deprecate(issuing): deprecate the update-card headers and make the revoke and suspend request optional The API no longer documents the return-encrypted-cvv and Encryption-Key headers on the update card operation, so CardUpdateHeaders and the headers argument of update_card are deprecated. They keep working when set and emit a DeprecationWarning; removal is planned for the next major version. The revoke and suspend request bodies are optional in the API, so revoke_card and suspend_card now accept no request and send no body in that case. --- checkout_sdk/issuing/cards.py | 11 ++++++ checkout_sdk/issuing/issuing_client.py | 38 +++++++++++++++---- .../issuing/card_update_headers_wire_test.py | 15 ++++++++ tests/issuing/issuing_client_test.py | 26 ++++++++++++- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/checkout_sdk/issuing/cards.py b/checkout_sdk/issuing/cards.py index d517e36a..b22c447a 100644 --- a/checkout_sdk/issuing/cards.py +++ b/checkout_sdk/issuing/cards.py @@ -185,6 +185,9 @@ class UpdateCardRequest: class CardUpdateHeaders: """The optional HTTP headers accepted when updating a card's details. + Deprecated: the API no longer supports these headers; use the method without headers. + Will be removed in the next major version. When set, they are still sent unchanged. + Header values are stringified by ApiClient, so declare the boolean header as the string "true" rather than a Python bool: str(True) is "True", which is not the value the spec shows. All three existing header classes in this SDK use str for the same reason. @@ -254,8 +257,16 @@ class CardCredentialsQuery: class RevokeRequest: + """Optional request body for POST /issuing/cards/{cardId}/revoke.""" + # The reason for revoking the card. + # [Optional] + # Enum: expired, reported_lost, reported_stolen reason: RevokeReason class SuspendRequest: + """Optional request body for POST /issuing/cards/{cardId}/suspend.""" + # The reason for suspending the card. + # [Optional] + # Enum: suspected_lost, suspected_stolen reason: SuspendReason diff --git a/checkout_sdk/issuing/issuing_client.py b/checkout_sdk/issuing/issuing_client.py index 3a9cfd9a..6db04be4 100644 --- a/checkout_sdk/issuing/issuing_client.py +++ b/checkout_sdk/issuing/issuing_client.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import warnings + from checkout_sdk.api_client import ApiClient from checkout_sdk.authorization_type import AuthorizationType from checkout_sdk.checkout_configuration import CheckoutConfiguration @@ -88,14 +90,18 @@ def update_card(self, card_id: str, update_card_request: UpdateCardRequest, Args: card_id: The card's unique identifier. update_card_request: The card fields to update. - headers: Optional return-encrypted-cvv and Encryption-Key HTTP headers. Setting - return-encrypted-cvv to "true" without Encryption-Key returns a 422 with error - code encryption_key_required. + headers: Deprecated. The API no longer supports these headers; use the method + without headers. Will be removed in the next major version. When set, the + headers are still sent unchanged. Returns: - ResponseWrapper with the update response, including encrypted_cvv when requested. - For a virtual card, the response may also include is_single_use, specifying whether - the card is set to expire after a single use. Physical cards never send it. + ResponseWrapper with the update card response. For a virtual card, the response + may also include is_single_use, specifying whether the card is set to expire after + a single use. Physical cards never send it. """ + if headers is not None: + warnings.warn('The headers argument of update_card is deprecated: the API no longer supports ' + 'these headers; use the method without headers. Will be removed in the next ' + 'major version.', DeprecationWarning, stacklevel=2) return self._api_client.patch(self.build_path(self.__ISSUING, self.__CARDS, card_id), self._sdk_authorization(), update_card_request, @@ -129,12 +135,28 @@ def renew_card(self, card_id: str, renew_card_request: RenewCardRequest): self._sdk_authorization(), renew_card_request) - def revoke_card(self, card_id: str, revoke_request: RevokeRequest): + def revoke_card(self, card_id: str, revoke_request: RevokeRequest = None): + """Revoke an issued card. Revoking is permanent. + + Args: + card_id: The card's unique identifier. + revoke_request: Optional request body with the reason. When omitted, no body is sent. + Returns: + ResponseWrapper with the revoke card response. + """ return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__REVOKE), self._sdk_authorization(), revoke_request) - def suspend_card(self, card_id: str, suspend_request: SuspendRequest): + def suspend_card(self, card_id: str, suspend_request: SuspendRequest = None): + """Suspend an issued card. + + Args: + card_id: The card's unique identifier. + suspend_request: Optional request body with the reason. When omitted, no body is sent. + Returns: + ResponseWrapper with the suspend card response. + """ return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__SUSPEND), self._sdk_authorization(), suspend_request) diff --git a/tests/issuing/card_update_headers_wire_test.py b/tests/issuing/card_update_headers_wire_test.py index 41d4daa2..0a0ee381 100644 --- a/tests/issuing/card_update_headers_wire_test.py +++ b/tests/issuing/card_update_headers_wire_test.py @@ -141,3 +141,18 @@ def test_update_card_response_round_trips_is_single_use_for_virtual_cards(self, response = client.update_card('crd_123', UpdateCardRequest()) assert response.is_single_use is True + + +class TestRevokeAndSuspendWithoutBodyReachTheWire: + """The revoke and suspend request bodies are optional in the spec: omitting them sends no body.""" + + @pytest.mark.parametrize('operation, path', [('revoke_card', 'revoke'), ('suspend_card', 'suspend')]) + def test_no_body_is_sent_when_the_request_is_omitted(self, mock_sdk_configuration, operation, path): + client, http_client = _build(mock_sdk_configuration) + + getattr(client, operation)('crd_123') + + kwargs = http_client.request.call_args.kwargs + assert kwargs['data'] is None + assert kwargs['method'] == 'POST' + assert kwargs['url'].endswith('issuing/cards/crd_123/' + path) diff --git a/tests/issuing/issuing_client_test.py b/tests/issuing/issuing_client_test.py index b87626d8..f6b0d21c 100644 --- a/tests/issuing/issuing_client_test.py +++ b/tests/issuing/issuing_client_test.py @@ -1,3 +1,5 @@ +import warnings + import pytest from tests._assertions import assert_api_call @@ -76,10 +78,18 @@ def test_should_update_card_with_the_encrypted_cvv_headers(self, mocker, client: headers.return_encrypted_cvv = 'true' headers.encryption_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A' - assert client.update_card('card_id', body, headers) == 'response' + with pytest.warns(DeprecationWarning, match='next major version'): + assert client.update_card('card_id', body, headers) == 'response' assert_api_call(mock, 'issuing/cards/card_id', body) assert mock.call_args.kwargs['headers'] is headers + def test_should_not_warn_when_updating_card_without_headers(self, mocker, client: IssuingClient): + mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + + with warnings.catch_warnings(): + warnings.simplefilter('error', DeprecationWarning) + assert client.update_card('card_id', UpdateCardRequest()) == 'response' + def test_should_enroll_three_ds(self, mocker, client: IssuingClient): mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') body = PasswordEnrollmentRequest() @@ -129,6 +139,13 @@ def test_should_revoke_card(self, mocker, client: IssuingClient): assert client.revoke_card('card_id', body) == 'response' assert_api_call(mock, 'issuing/cards/card_id/revoke', body) + def test_should_revoke_card_without_body(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.revoke_card('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id/revoke') + assert mock.call_args.args[2] is None + def test_should_suspend_card(self, mocker, client: IssuingClient): mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') body = SuspendRequest() @@ -136,6 +153,13 @@ def test_should_suspend_card(self, mocker, client: IssuingClient): assert client.suspend_card('card_id', body) == 'response' assert_api_call(mock, 'issuing/cards/card_id/suspend', body) + def test_should_suspend_card_without_body(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.suspend_card('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id/suspend') + assert mock.call_args.args[2] is None + def test_should_get_digital_card(self, mocker, client: IssuingClient): mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') From 0d8c0b44739cf4e2b0a813ae0a729fbd90983a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 22 Oct 2026 18:08:10 +0200 Subject: [PATCH 2/4] test(issuing): cover the revoke and suspend requests and document the update card fields Add serialization tests for the revoke and suspend requests, document the update card status, scheduled revocation date and metadata fields from the specification, assert the deprecation warning in the header wire tests and replace the tests that still described the encrypted CVV behaviour. --- checkout_sdk/issuing/cards.py | 48 ++++++++++++++----- .../issuing/card_update_headers_wire_test.py | 42 +++++----------- .../issuing/cards_issuing_integration_test.py | 4 -- tests/issuing/issuing_client_test.py | 2 +- tests/issuing/issuing_serialization_test.py | 47 +++++++++++++++++- 5 files changed, 96 insertions(+), 47 deletions(-) diff --git a/checkout_sdk/issuing/cards.py b/checkout_sdk/issuing/cards.py index b22c447a..1a68587b 100644 --- a/checkout_sdk/issuing/cards.py +++ b/checkout_sdk/issuing/cards.py @@ -52,10 +52,31 @@ class ShippingInstructions: class CardMetadata: + """User's metadata.""" + # User defined field 1 + # [Optional] + # Pattern: ^.{0,255}$ + # Example: metadata1 udf1: str + # User defined field 2 + # [Optional] + # Pattern: ^.{0,255}$ + # Example: metadata2 udf2: str + # User defined field 3 + # [Optional] + # Pattern: ^.{0,255}$ + # Example: metadata3 udf3: str + # User defined field 4 + # [Optional] + # Pattern: ^.{0,255}$ + # Example: metadata4 udf4: str + # User defined field 5 + # [Optional] + # Pattern: ^.{0,255}$ + # Example: metadata5 udf5: str @@ -114,7 +135,10 @@ class CardRequest: # [Optional] # Example: 2026-06-01T10:00Z scheduled_activation_date: str - # yyyy-mm-dd. The card is revoked at midnight UTC on this date. (IssuingScheduledRevocationDate) + # The card will be revoked at midnight UTC on the date specified. + # [Optional] + # Format: date (YYYY-MM-DD) + # Example: 2027-03-12 scheduled_revocation_date: str def __init__(self, type_p: CardType): @@ -140,9 +164,12 @@ def __init__(self): class UpdateCardRequest: """Request body for PATCH /issuing/cards/{cardId}.""" - # Set to CardStatusUpdate.ACTIVE to reactivate an inactive or suspended card. - # Mutually exclusive with scheduled_activation_date (API returns - # scheduled_activation_date_conflicts_with_activation otherwise). + # Set the card's status to active to activate an inactive or suspended card. If you submit + # this field, you cannot specify a scheduled_activation_date. If you do, you receive a + # scheduled_activation_date_conflicts_with_activation error. + # [Optional] + # Enum: active + # Example: active status: CardStatusUpdate # Your reference. # [Optional] @@ -178,7 +205,10 @@ class UpdateCardRequest: # Format: yyyy-MM-dd (time is midnight UTC) # Example: 2027-03-12 revocation_date: str - # yyyy-mm-dd. The card is revoked at midnight UTC on this date. (IssuingScheduledRevocationDate) + # The card will be revoked at midnight UTC on the date specified. + # [Optional] + # Format: date (YYYY-MM-DD) + # Example: 2027-03-12 scheduled_revocation_date: str @@ -192,16 +222,12 @@ class CardUpdateHeaders: "true" rather than a Python bool: str(True) is "True", which is not the value the spec shows. All three existing header classes in this SDK use str for the same reason. """ - # Set to "true" to retrieve the card's encrypted credentials in the response. Requires an RSA - # public key to be provided in the Encryption-Key header. + # Deprecated: the API no longer supports this header and ignores it. Sent unchanged when set. # [Optional] # Maps to HTTP header return-encrypted-cvv. # Example: "true" return_encrypted_cvv: str - # The RSA public key used to encrypt returned credentials. Required when the - # return-encrypted-cvv header is set to "true". Provide the public key with the - # BEGIN PUBLIC KEY and END PUBLIC KEY headers and any newline characters removed, encoded as - # Base64. + # Deprecated: the API no longer supports this header and ignores it. Sent unchanged when set. # [Optional] # Maps to HTTP header Encryption-Key. encryption_key: str diff --git a/tests/issuing/card_update_headers_wire_test.py b/tests/issuing/card_update_headers_wire_test.py index 0a0ee381..fba0e55e 100644 --- a/tests/issuing/card_update_headers_wire_test.py +++ b/tests/issuing/card_update_headers_wire_test.py @@ -4,7 +4,6 @@ from requests import HTTPError from checkout_sdk.api_client import ApiClient -from checkout_sdk.exception import CheckoutApiException from checkout_sdk.issuing.cards import CardUpdateHeaders, UpdateCardRequest from checkout_sdk.issuing.issuing_client import IssuingClient @@ -49,7 +48,8 @@ def test_both_headers_use_their_exact_swagger_names(self, mock_sdk_configuration headers.return_encrypted_cvv = 'true' headers.encryption_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A' - client.update_card('crd_123', UpdateCardRequest(), headers) + with pytest.warns(DeprecationWarning, match='next major version'): + client.update_card('crd_123', UpdateCardRequest(), headers) sent = _sent_headers(http_client) assert sent['return-encrypted-cvv'] == 'true' @@ -70,7 +70,8 @@ def test_the_key_can_be_sent_without_the_flag(self, mock_sdk_configuration): headers = CardUpdateHeaders() headers.encryption_key = 'MIIBIjAN' - client.update_card('crd_123', UpdateCardRequest(), headers) + with pytest.warns(DeprecationWarning, match='next major version'): + client.update_card('crd_123', UpdateCardRequest(), headers) sent = _sent_headers(http_client) assert sent['Encryption-Key'] == 'MIIBIjAN' @@ -85,39 +86,23 @@ def test_a_python_bool_would_send_the_capitalised_string(self, mock_sdk_configur headers = CardUpdateHeaders() headers.return_encrypted_cvv = True - client.update_card('crd_123', UpdateCardRequest(), headers) - - assert _sent_headers(http_client)['return-encrypted-cvv'] == 'True' - - def test_a_422_surfaces_the_encryption_key_required_error(self, mock_sdk_configuration): - """The API answers 422 with error code encryption_key_required when return-encrypted-cvv - is true without an Encryption-Key header.""" - client, _ = _build(mock_sdk_configuration, status=422, body='{' - '"request_id":"0HLHPN8802NUF:00000003",' - '"error_type":"request_invalid",' - '"error_codes":["encryption_key_required"]}') - headers = CardUpdateHeaders() - headers.return_encrypted_cvv = 'true' - - with pytest.raises(CheckoutApiException) as exc: + with pytest.warns(DeprecationWarning, match='next major version'): client.update_card('crd_123', UpdateCardRequest(), headers) - assert exc.value.error_type == 'request_invalid' - assert 'encryption_key_required' in exc.value.error_details - assert exc.value.request_id == '0HLHPN8802NUF:00000003' + assert _sent_headers(http_client)['return-encrypted-cvv'] == 'True' - def test_a_successful_update_with_the_headers_still_has_no_encrypted_cvv(self, mock_sdk_configuration): - """The 2026-09-17 spec (INT-1700) removed encrypted_cvv from update-card-response - entirely, so return-encrypted-cvv/Encryption-Key no longer make the response carry it. - This test previously asserted the opposite (added by INT-1695, when the field still - existed).""" + def test_the_deprecated_headers_do_not_change_the_response(self, mock_sdk_configuration): + """The API no longer supports these headers and ignores them: an update that sends them + gets the same update card response, with no encrypted credentials.""" client, _ = _build(mock_sdk_configuration, body='{"last_modified_date":"2026-06-01T10:00:00Z"}') headers = CardUpdateHeaders() headers.return_encrypted_cvv = 'true' headers.encryption_key = 'MIIBIjAN' - response = client.update_card('crd_123', UpdateCardRequest(), headers) + with pytest.warns(DeprecationWarning, match='next major version'): + response = client.update_card('crd_123', UpdateCardRequest(), headers) + assert response.http_metadata.status_code == 200 assert response.last_modified_date == '2026-06-01T10:00:00Z' assert not hasattr(response, 'encrypted_cvv') @@ -131,8 +116,7 @@ def test_a_successful_update_without_the_headers_has_no_encrypted_cvv(self, mock assert not hasattr(response, 'encrypted_cvv') def test_update_card_response_round_trips_is_single_use_for_virtual_cards(self, mock_sdk_configuration): - """The 2026-09-23 spec update split update-card-response into a virtual/physical - discriminator; the virtual variant adds is_single_use.""" + """update-card-response has a type discriminator; the virtual variant adds is_single_use.""" client, _ = _build(mock_sdk_configuration, body='{' '"type":"virtual",' '"last_modified_date":"2026-06-01T10:00:00Z",' diff --git a/tests/issuing/cards_issuing_integration_test.py b/tests/issuing/cards_issuing_integration_test.py index 319954d6..ec00551c 100644 --- a/tests/issuing/cards_issuing_integration_test.py +++ b/tests/issuing/cards_issuing_integration_test.py @@ -68,10 +68,6 @@ def test_should_update_card_scheduled_activation_date(self, issuing_checkout_api assert_response(response) assert response.http_metadata.status_code == 200 - # The 2026-09-17 spec (INT-1700) removed encrypted_cvv from update-card-response entirely, - # so return-encrypted-cvv/Encryption-Key no longer make the update response carry it. This - # test previously asserted the opposite (added by INT-1695, when the field still existed). - def test_should_renew_card(self, issuing_checkout_api, card): request = VirtualCardRenewRequest() request.reference = 'RENEW-REF-123' diff --git a/tests/issuing/issuing_client_test.py b/tests/issuing/issuing_client_test.py index f6b0d21c..26891c3f 100644 --- a/tests/issuing/issuing_client_test.py +++ b/tests/issuing/issuing_client_test.py @@ -71,7 +71,7 @@ def test_should_update_card(self, mocker, client: IssuingClient): assert_api_call(mock, 'issuing/cards/card_id', body) assert mock.call_args.kwargs['headers'] is None - def test_should_update_card_with_the_encrypted_cvv_headers(self, mocker, client: IssuingClient): + def test_should_update_card_with_the_deprecated_headers(self, mocker, client: IssuingClient): mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') body = UpdateCardRequest() headers = CardUpdateHeaders() diff --git a/tests/issuing/issuing_serialization_test.py b/tests/issuing/issuing_serialization_test.py index cf60934d..3b6b0940 100644 --- a/tests/issuing/issuing_serialization_test.py +++ b/tests/issuing/issuing_serialization_test.py @@ -1,10 +1,12 @@ import json + +import pytest from datetime import date from checkout_sdk.json_serializer import JsonSerializer from checkout_sdk.api_client import ApiClient from checkout_sdk.issuing.cards import CardRequest, CardType, CardUpdateHeaders, UpdateCardRequest, \ - VirtualCardRequest, CardStatusUpdate + VirtualCardRequest, CardStatusUpdate, RevokeRequest, RevokeReason, SuspendRequest, SuspendReason from checkout_sdk.issuing.disputes import ( IssuingDisputeFraudType, IssuingDisputeFraudDetails, CreateDisputeRequest, EscalateDisputeRequest, AmendDisputeRequest, SubmitDisputeRequest, @@ -80,7 +82,7 @@ def test_create_card_serializes_scheduled_activation_date(self): def test_revocation_date_accepts_a_date_object_through_the_serializer(self): """revocation_date is `format: date`. The SDK convention is to declare it str with a - `# Format: yyyy-MM-dd` comment, and the JsonSerializer date branch added in INT-1699 is + `# Format: yyyy-MM-dd` comment, and the JsonSerializer date branch is the safety net for a caller who passes a real date instead.""" request = UpdateCardRequest() request.revocation_date = date(2027, 3, 12) @@ -225,3 +227,44 @@ def test_submit_dispute_serializes_fields(self): request.amount = 100 assert _serialize(request) == {'reason': '4807', 'amount': 100} + + +class TestRevokeAndSuspendRequestSerialization: + """revoke-card-request and suspend-card-request: one optional `reason` property each.""" + + @pytest.mark.parametrize('reason, wire', [(RevokeReason.EXPIRED, 'expired'), + (RevokeReason.REPORTED_LOST, 'reported_lost'), + (RevokeReason.REPORTED_STOLEN, 'reported_stolen')]) + def test_revoke_request_round_trips_every_reason(self, reason, wire): + request = RevokeRequest() + request.reason = reason + + payload = _serialize(request) + + assert payload == {'reason': wire} + assert RevokeReason(payload['reason']) is reason + + @pytest.mark.parametrize('reason, wire', [(SuspendReason.SUSPECTED_LOST, 'suspected_lost'), + (SuspendReason.SUSPECTED_STOLEN, 'suspected_stolen')]) + def test_suspend_request_round_trips_every_reason(self, reason, wire): + request = SuspendRequest() + request.reason = reason + + payload = _serialize(request) + + assert payload == {'reason': wire} + assert SuspendReason(payload['reason']) is reason + + def test_reason_enums_match_the_swagger_values_exactly(self): + assert [m.value for m in RevokeReason] == ['expired', 'reported_lost', 'reported_stolen'] + assert [m.value for m in SuspendReason] == ['suspected_lost', 'suspected_stolen'] + + def test_swagger_examples_deserialize_into_the_enums(self): + assert RevokeReason(json.loads('{"reason":"reported_lost"}')['reason']) is RevokeReason.REPORTED_LOST + assert SuspendReason(json.loads('{"reason":"suspected_lost"}')['reason']) is SuspendReason.SUSPECTED_LOST + + @pytest.mark.parametrize('request_type', [RevokeRequest, SuspendRequest]) + def test_an_empty_request_serializes_to_an_empty_object(self, request_type): + """An empty request object serializes to {}. Passing no request at all sends no body, + which the wire tests assert.""" + assert json.dumps(request_type(), cls=JsonSerializer) == '{}' From afb98a560976c205ceefcd8191088dc7b09acf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:17:52 +0200 Subject: [PATCH 3/4] test(issuing): rename the deprecated header tests --- checkout_sdk/issuing/cards.py | 8 ++++---- tests/issuing/card_update_headers_wire_test.py | 6 +++--- tests/issuing/issuing_serialization_test.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/checkout_sdk/issuing/cards.py b/checkout_sdk/issuing/cards.py index 1a68587b..f5f14fc2 100644 --- a/checkout_sdk/issuing/cards.py +++ b/checkout_sdk/issuing/cards.py @@ -222,12 +222,12 @@ class CardUpdateHeaders: "true" rather than a Python bool: str(True) is "True", which is not the value the spec shows. All three existing header classes in this SDK use str for the same reason. """ - # Deprecated: the API no longer supports this header and ignores it. Sent unchanged when set. + # Deprecated: no longer documented by the API. Still sent when set. # [Optional] # Maps to HTTP header return-encrypted-cvv. # Example: "true" return_encrypted_cvv: str - # Deprecated: the API no longer supports this header and ignores it. Sent unchanged when set. + # Deprecated: no longer documented by the API. Still sent when set. # [Optional] # Maps to HTTP header Encryption-Key. encryption_key: str @@ -284,7 +284,7 @@ class CardCredentialsQuery: class RevokeRequest: """Optional request body for POST /issuing/cards/{cardId}/revoke.""" - # The reason for revoking the card. + # The reason why the card is being revoked. # [Optional] # Enum: expired, reported_lost, reported_stolen reason: RevokeReason @@ -292,7 +292,7 @@ class RevokeRequest: class SuspendRequest: """Optional request body for POST /issuing/cards/{cardId}/suspend.""" - # The reason for suspending the card. + # The reason why the card is being suspended. # [Optional] # Enum: suspected_lost, suspected_stolen reason: SuspendReason diff --git a/tests/issuing/card_update_headers_wire_test.py b/tests/issuing/card_update_headers_wire_test.py index fba0e55e..369b99e5 100644 --- a/tests/issuing/card_update_headers_wire_test.py +++ b/tests/issuing/card_update_headers_wire_test.py @@ -42,7 +42,7 @@ def _sent_headers(http_client): class TestCardUpdateHeadersReachTheWire: - def test_both_headers_use_their_exact_swagger_names(self, mock_sdk_configuration): + def test_deprecated_headers_are_still_sent_with_their_exact_names(self, mock_sdk_configuration): client, http_client = _build(mock_sdk_configuration) headers = CardUpdateHeaders() headers.return_encrypted_cvv = 'true' @@ -92,8 +92,8 @@ def test_a_python_bool_would_send_the_capitalised_string(self, mock_sdk_configur assert _sent_headers(http_client)['return-encrypted-cvv'] == 'True' def test_the_deprecated_headers_do_not_change_the_response(self, mock_sdk_configuration): - """The API no longer supports these headers and ignores them: an update that sends them - gets the same update card response, with no encrypted credentials.""" + """The headers are no longer documented by the API but are still sent when set: an update + that sends them gets the same update card response, with no encrypted credentials.""" client, _ = _build(mock_sdk_configuration, body='{"last_modified_date":"2026-06-01T10:00:00Z"}') headers = CardUpdateHeaders() headers.return_encrypted_cvv = 'true' diff --git a/tests/issuing/issuing_serialization_test.py b/tests/issuing/issuing_serialization_test.py index 3b6b0940..ca80c628 100644 --- a/tests/issuing/issuing_serialization_test.py +++ b/tests/issuing/issuing_serialization_test.py @@ -134,7 +134,7 @@ def test_update_card_request_from_swagger_example(self): assert payload['scheduled_activation_date'] == '2026-06-01T10:00Z' assert payload['revocation_date'] == '2027-03-12' - def test_card_update_headers_map_to_the_exact_swagger_header_names(self): + def test_deprecated_card_update_headers_keep_their_exact_header_names(self): assert CardUpdateHeaders().get_header_mappings() == { 'return_encrypted_cvv': 'return-encrypted-cvv', 'encryption_key': 'Encryption-Key', From 0e198ef32cb40660c880dc9fe8613b9664ebca45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:34:44 +0200 Subject: [PATCH 4/4] docs(issuing): align the header deprecation message --- checkout_sdk/issuing/cards.py | 2 +- checkout_sdk/issuing/issuing_client.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/checkout_sdk/issuing/cards.py b/checkout_sdk/issuing/cards.py index f5f14fc2..80541081 100644 --- a/checkout_sdk/issuing/cards.py +++ b/checkout_sdk/issuing/cards.py @@ -215,7 +215,7 @@ class UpdateCardRequest: class CardUpdateHeaders: """The optional HTTP headers accepted when updating a card's details. - Deprecated: the API no longer supports these headers; use the method without headers. + Deprecated: The API no longer documents these headers; use the method without headers. Will be removed in the next major version. When set, they are still sent unchanged. Header values are stringified by ApiClient, so declare the boolean header as the string diff --git a/checkout_sdk/issuing/issuing_client.py b/checkout_sdk/issuing/issuing_client.py index 6db04be4..60388ee1 100644 --- a/checkout_sdk/issuing/issuing_client.py +++ b/checkout_sdk/issuing/issuing_client.py @@ -90,7 +90,7 @@ def update_card(self, card_id: str, update_card_request: UpdateCardRequest, Args: card_id: The card's unique identifier. update_card_request: The card fields to update. - headers: Deprecated. The API no longer supports these headers; use the method + headers: Deprecated. The API no longer documents these headers; use the method without headers. Will be removed in the next major version. When set, the headers are still sent unchanged. Returns: @@ -99,9 +99,8 @@ def update_card(self, card_id: str, update_card_request: UpdateCardRequest, a single use. Physical cards never send it. """ if headers is not None: - warnings.warn('The headers argument of update_card is deprecated: the API no longer supports ' - 'these headers; use the method without headers. Will be removed in the next ' - 'major version.', DeprecationWarning, stacklevel=2) + warnings.warn('The API no longer documents these headers; use the method without headers. ' + 'Will be removed in the next major version.', DeprecationWarning, stacklevel=2) return self._api_client.patch(self.build_path(self.__ISSUING, self.__CARDS, card_id), self._sdk_authorization(), update_card_request,