From 2db1da294108da23731fc24424fefdf9f63d606e 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:11:33 +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 endpoint, so CardUpdateHeaders and the headers argument of update_card are deprecated. They keep working when set and will be removed in the next major version. The revoke and suspend card request body is optional in the API, so revoke_card and suspend_card can now be called without a request. --- .../issuing/card_update_headers.rb | 3 ++ lib/checkout_sdk/issuing/issuing_client.rb | 28 ++++++++++------- spec/checkout_sdk/api_client_spec.rb | 12 ++++++++ .../issuing/issuing_extras_spec.rb | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/lib/checkout_sdk/issuing/card_update_headers.rb b/lib/checkout_sdk/issuing/card_update_headers.rb index 4e32316..856f1fe 100644 --- a/lib/checkout_sdk/issuing/card_update_headers.rb +++ b/lib/checkout_sdk/issuing/card_update_headers.rb @@ -4,6 +4,9 @@ module CheckoutSdk module Issuing # 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. Headers that are set are still sent unchanged. + # # Modelled as its own class rather than added to {CheckoutSdk::Common::Headers} because these # two headers belong to one endpoint, and Common::Headers is shared by every client. # diff --git a/lib/checkout_sdk/issuing/issuing_client.rb b/lib/checkout_sdk/issuing/issuing_client.rb index 23c031e..dea02c3 100644 --- a/lib/checkout_sdk/issuing/issuing_client.rb +++ b/lib/checkout_sdk/issuing/issuing_client.rb @@ -90,13 +90,13 @@ def get_card_details(card_id) # # @param [String] card_id # @param [Hash, UpdateCardRequest] update_card_request - # @param [CardUpdateHeaders, nil] headers the optional return-encrypted-cvv and - # Encryption-Key HTTP headers. Setting return_encrypted_cvv without encryption_key returns - # a 422 with error code encryption_key_required. - # @return [OpenStruct] the update response, carrying encrypted_cvv when requested. For a - # virtual card the response may also carry is_single_use (boolean, default false), - # specifying whether the card is set to expire after a single use. Physical cards never - # send it. + # @param [CardUpdateHeaders, nil] 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 return-encrypted-cvv and Encryption-Key headers are still sent unchanged. + # @return [OpenStruct] the update-card-response: the updated card details and _links. The + # type field discriminates the variant; a virtual card response also carries + # is_single_use (boolean, default false), whether the card expires after a single use. + # Physical card responses never carry it. def update_card(card_id, update_card_request, headers = nil) api_client.invoke_patch( build_path(ISSUING, CARDS, card_id), @@ -134,15 +134,21 @@ def get_card_credentials(card_id, query) api_client.invoke_get(build_path(ISSUING, CARDS, card_id, CREDENTIALS), sdk_authorization, query) end + # Revoke a card. POST /issuing/cards/{cardId}/revoke. The request body is optional; when + # omitted an empty JSON object is sent. # @param [String] card_id - # @param [Hash] revoke_request - def revoke_card(card_id, revoke_request) + # @param [Hash, nil] revoke_request optional, may carry reason (expired, reported_lost, + # reported_stolen) + def revoke_card(card_id, revoke_request = nil) api_client.invoke_post(build_path(ISSUING, CARDS, card_id, REVOKE), sdk_authorization, revoke_request) end + # Suspend a card. POST /issuing/cards/{cardId}/suspend. The request body is optional; when + # omitted an empty JSON object is sent. # @param [String] card_id - # @param [Hash] suspend_request - def suspend_card(card_id, suspend_request) + # @param [Hash, nil] suspend_request optional, may carry reason (suspected_lost, + # suspected_stolen) + def suspend_card(card_id, suspend_request = nil) api_client.invoke_post(build_path(ISSUING, CARDS, card_id, SUSPEND), sdk_authorization, suspend_request) end diff --git a/spec/checkout_sdk/api_client_spec.rb b/spec/checkout_sdk/api_client_spec.rb index c508dd8..a112d14 100644 --- a/spec/checkout_sdk/api_client_spec.rb +++ b/spec/checkout_sdk/api_client_spec.rb @@ -254,6 +254,18 @@ expect(captured['return-encrypted-cvv']).to eq('true') end + it 'sends an empty JSON object when a POST has no body, as revoke and suspend card do' do + captured = nil + allow(http_client).to receive(:run_request) do |_method, _path, body, _headers| + captured = body + response + end + + api_client.invoke_post('issuing/cards/crd_1/revoke', auth) + + expect(captured).to eq('{}') + end + it 'omits the If-Match header when no headers container is passed' do captured = nil allow(http_client).to receive(:run_request) do |_method, _path, _body, headers| diff --git a/spec/checkout_sdk/issuing/issuing_extras_spec.rb b/spec/checkout_sdk/issuing/issuing_extras_spec.rb index 8212e7a..d81b642 100644 --- a/spec/checkout_sdk/issuing/issuing_extras_spec.rb +++ b/spec/checkout_sdk/issuing/issuing_extras_spec.rb @@ -27,6 +27,36 @@ end end + describe '#revoke_card' do + it 'POSTs issuing/cards/{id}/revoke with no body' do + expect(api_client_mock).to receive(:invoke_post) + .with('issuing/cards/crd_1/revoke', 'secret_key', nil).and_return('r') + expect(client.revoke_card('crd_1')).to eq('r') + end + + it 'POSTs issuing/cards/{id}/revoke with a reason' do + req = { reason: 'reported_lost' } + expect(api_client_mock).to receive(:invoke_post) + .with('issuing/cards/crd_1/revoke', 'secret_key', req).and_return('r') + expect(client.revoke_card('crd_1', req)).to eq('r') + end + end + + describe '#suspend_card' do + it 'POSTs issuing/cards/{id}/suspend with no body' do + expect(api_client_mock).to receive(:invoke_post) + .with('issuing/cards/crd_1/suspend', 'secret_key', nil).and_return('r') + expect(client.suspend_card('crd_1')).to eq('r') + end + + it 'POSTs issuing/cards/{id}/suspend with a reason' do + req = { reason: 'suspected_stolen' } + expect(api_client_mock).to receive(:invoke_post) + .with('issuing/cards/crd_1/suspend', 'secret_key', req).and_return('r') + expect(client.suspend_card('crd_1', req)).to eq('r') + end + end + describe '#renew_card' do it 'POSTs issuing/cards/{id}/renew' do expect(api_client_mock).to receive(:invoke_post) From c1160ee07205ecdf9926df99454cd8d0c7e2f97a 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:07:16 +0200 Subject: [PATCH 2/4] docs(issuing): drop the old encrypted CVV wording and document revoke and suspend returns --- lib/checkout_sdk/api_client.rb | 5 +-- .../issuing/card_update_headers.rb | 20 +++++------ lib/checkout_sdk/issuing/issuing_client.rb | 3 ++ spec/checkout_sdk/api_client_spec.rb | 3 +- .../identities_response_shape_spec.rb | 34 ++++++++----------- .../issuing_cards_serialization_spec.rb | 2 +- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/lib/checkout_sdk/api_client.rb b/lib/checkout_sdk/api_client.rb index bc513fc..bbeea66 100644 --- a/lib/checkout_sdk/api_client.rb +++ b/lib/checkout_sdk/api_client.rb @@ -68,8 +68,9 @@ def default_headers(authorization) # Attribute name to HTTP header name, for every typed headers container the SDK supports. # - # The header names are case sensitive in the spec: return-encrypted-cvv is lower case where a - # naive conversion would produce Return-Encrypted-Cvv. + # The header names are emitted with an exact spelling: return-encrypted-cvv (sent by the + # deprecated CardUpdateHeaders) is lower case where a naive conversion would produce + # Return-Encrypted-Cvv. # # The values are Strings, not Symbols, and that is load bearing. Faraday::Utils::Headers # preserves a String key verbatim but rewrites a Symbol key as diff --git a/lib/checkout_sdk/issuing/card_update_headers.rb b/lib/checkout_sdk/issuing/card_update_headers.rb index 856f1fe..63ed6ec 100644 --- a/lib/checkout_sdk/issuing/card_update_headers.rb +++ b/lib/checkout_sdk/issuing/card_update_headers.rb @@ -11,22 +11,18 @@ module Issuing # two headers belong to one endpoint, and Common::Headers is shared by every client. # # @!attribute return_encrypted_cvv - # Set to "true" to retrieve the card's encrypted credentials in the response. Requires an - # RSA public key in the Encryption-Key header, otherwise the API answers 422 with error code - # encryption_key_required. - # - # A String rather than a boolean: the value is emitted verbatim as an HTTP header, and "true" - # is what the spec shows. + # @deprecated The API no longer supports this header; use the method without headers. Will + # be removed in the next major version. + # No longer documented by the API, which ignores it: the update response does not change + # when it is set. Still sent unchanged as the return-encrypted-cvv HTTP header when set. # [Optional] - # Maps to HTTP header return-encrypted-cvv. - # Example: "true" # @return [String] # @!attribute encryption_key - # The RSA public key used to encrypt returned credentials. Required when - # return_encrypted_cvv is set. 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; use the method without headers. Will + # be removed in the next major version. + # No longer documented by the API, which ignores it. Still sent unchanged as the + # Encryption-Key HTTP header when set. # [Optional] - # Maps to HTTP header Encryption-Key. # @return [String] class CardUpdateHeaders attr_accessor :return_encrypted_cvv, :encryption_key diff --git a/lib/checkout_sdk/issuing/issuing_client.rb b/lib/checkout_sdk/issuing/issuing_client.rb index dea02c3..11a0617 100644 --- a/lib/checkout_sdk/issuing/issuing_client.rb +++ b/lib/checkout_sdk/issuing/issuing_client.rb @@ -139,6 +139,7 @@ def get_card_credentials(card_id, query) # @param [String] card_id # @param [Hash, nil] revoke_request optional, may carry reason (expired, reported_lost, # reported_stolen) + # @return [OpenStruct] the revoke-card-response, carrying _links with self and controls. def revoke_card(card_id, revoke_request = nil) api_client.invoke_post(build_path(ISSUING, CARDS, card_id, REVOKE), sdk_authorization, revoke_request) end @@ -148,6 +149,8 @@ def revoke_card(card_id, revoke_request = nil) # @param [String] card_id # @param [Hash, nil] suspend_request optional, may carry reason (suspected_lost, # suspected_stolen) + # @return [OpenStruct] the suspend-card-response, carrying _links with self, activate, + # revoke and controls. def suspend_card(card_id, suspend_request = nil) api_client.invoke_post(build_path(ISSUING, CARDS, card_id, SUSPEND), sdk_authorization, suspend_request) end diff --git a/spec/checkout_sdk/api_client_spec.rb b/spec/checkout_sdk/api_client_spec.rb index a112d14..f5160fd 100644 --- a/spec/checkout_sdk/api_client_spec.rb +++ b/spec/checkout_sdk/api_client_spec.rb @@ -199,7 +199,8 @@ api_client.invoke_patch('issuing/cards/crd_1', auth, {}, extra) - # return-encrypted-cvv is lower case in the spec and Encryption-Key is title case. String + # The deprecated headers are still sent when set: return-encrypted-cvv is lower case and + # Encryption-Key is title case. String # keys are what preserves both; Symbol keys would have become Return-encrypted-cvv and # Encryption-key by the time Faraday wrote the request. expect(captured['return-encrypted-cvv']).to eq('true') diff --git a/spec/checkout_sdk/identities/identities_response_shape_spec.rb b/spec/checkout_sdk/identities/identities_response_shape_spec.rb index 42b1175..25217db 100644 --- a/spec/checkout_sdk/identities/identities_response_shape_spec.rb +++ b/spec/checkout_sdk/identities/identities_response_shape_spec.rb @@ -158,14 +158,13 @@ def parse(body, status: 200) end end - # Part D: the API answers 422 with error code encryption_key_required when return-encrypted-cvv - # is set to "true" without an Encryption-Key header. parse_response raises for status >= 400, so - # the error body has to survive into the exception for a caller to act on it. - describe 'the card update 422' do - it 'raises CheckoutApiException carrying the encryption_key_required code' do + # Part D: an API error body has to survive into the exception for a caller to act on it. + # transition_invalid is what the API answers when activating a revoked card. + describe 'a card management 422' do + it 'raises CheckoutApiException carrying the error codes' do body = '{"request_id":"0HLHPN8802NUF:00000003",' \ - '"error_type":"request_invalid",' \ - '"error_codes":["encryption_key_required"]}' + '"error_type":"invalid_request",' \ + '"error_codes":["transition_invalid"]}' response = double('Response', status: 422, body: body, headers: { 'Content-Type' => 'application/json' }) allow(CheckoutSdk::CheckoutUtils).to receive(:map_to_http_metadata).with(response).and_return( @@ -175,27 +174,22 @@ def parse(body, status: 200) expect { api_client.send(:parse_response, response) } .to raise_error(CheckoutSdk::CheckoutApiException) { |error| expect(error.http_metadata.status_code).to eq(422) - expect(error.error_details.error_type).to eq('request_invalid') - expect(error.error_details.error_codes).to include('encryption_key_required') + expect(error.error_details.error_type).to eq('invalid_request') + expect(error.error_details.error_codes).to include('transition_invalid') } end end - # The success counterpart: encrypted_cvv is returned only when the headers ask for it, so it is - # absent on an ordinary update. + # The update card response carries no encrypted credentials, with or without the deprecated + # headers. describe 'the card update response' do - it 'exposes encrypted_cvv when the headers requested it' do - response = parse('{"last_modified_date":"2026-06-01T10:00:00Z","encrypted_cvv":"oJMoNMEEUiQKYOsQ4Zd"}') + it 'parses the update response fields' do + response = parse('{"id":"crd_1","status":"active","last_modified_date":"2026-06-01T10:00:00Z"}') - expect(response.encrypted_cvv).to eq('oJMoNMEEUiQKYOsQ4Zd') + expect(response.id).to eq('crd_1') + expect(response.status).to eq('active') expect(response.last_modified_date).to eq('2026-06-01T10:00:00Z') end - - it 'leaves encrypted_cvv nil on an ordinary update' do - response = parse('{"last_modified_date":"2026-06-01T10:00:00Z"}') - - expect(response.encrypted_cvv).to be_nil - end end # Part C is a no-op in ruby because responses are OpenStruct, but the key still changed, so the diff --git a/spec/checkout_sdk/issuing/issuing_cards_serialization_spec.rb b/spec/checkout_sdk/issuing/issuing_cards_serialization_spec.rb index e161951..90523e6 100644 --- a/spec/checkout_sdk/issuing/issuing_cards_serialization_spec.rb +++ b/spec/checkout_sdk/issuing/issuing_cards_serialization_spec.rb @@ -72,7 +72,7 @@ def serialize(request) end describe CheckoutSdk::Issuing::CardUpdateHeaders do - it 'exposes the two headers the endpoint accepts' do + it 'still exposes the two deprecated headers' do headers = described_class.new expect(headers).to respond_to(:return_encrypted_cvv=) expect(headers).to respond_to(:encryption_key=) From f9359c3746ecd137161c04200d87f663c19e5c28 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:14:42 +0200 Subject: [PATCH 3/4] docs(issuing): use the no longer documented wording for the deprecated headers --- lib/checkout_sdk/issuing/card_update_headers.rb | 8 ++++---- .../identities/identities_response_shape_spec.rb | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/checkout_sdk/issuing/card_update_headers.rb b/lib/checkout_sdk/issuing/card_update_headers.rb index 63ed6ec..24eef92 100644 --- a/lib/checkout_sdk/issuing/card_update_headers.rb +++ b/lib/checkout_sdk/issuing/card_update_headers.rb @@ -13,15 +13,15 @@ module Issuing # @!attribute return_encrypted_cvv # @deprecated The API no longer supports this header; use the method without headers. Will # be removed in the next major version. - # No longer documented by the API, which ignores it: the update response does not change - # when it is set. Still sent unchanged as the return-encrypted-cvv HTTP header when set. + # No longer documented by the API. Still sent unchanged as the return-encrypted-cvv HTTP + # header when set. # [Optional] # @return [String] # @!attribute encryption_key # @deprecated The API no longer supports this header; use the method without headers. Will # be removed in the next major version. - # No longer documented by the API, which ignores it. Still sent unchanged as the - # Encryption-Key HTTP header when set. + # No longer documented by the API. Still sent unchanged as the Encryption-Key HTTP header + # when set. # [Optional] # @return [String] class CardUpdateHeaders diff --git a/spec/checkout_sdk/identities/identities_response_shape_spec.rb b/spec/checkout_sdk/identities/identities_response_shape_spec.rb index 25217db..346ff66 100644 --- a/spec/checkout_sdk/identities/identities_response_shape_spec.rb +++ b/spec/checkout_sdk/identities/identities_response_shape_spec.rb @@ -159,7 +159,8 @@ def parse(body, status: 200) end # Part D: an API error body has to survive into the exception for a caller to act on it. - # transition_invalid is what the API answers when activating a revoked card. + # transition_invalid is what the API answers when activating a revoked card. The error_type + # invalid_request matches what the live API returns. describe 'a card management 422' do it 'raises CheckoutApiException carrying the error codes' do body = '{"request_id":"0HLHPN8802NUF:00000003",' \ From 2a25e2acdc2691e8194f37fffbf3a03877bcf351 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:19:37 +0200 Subject: [PATCH 4/4] docs(issuing): align the header deprecation message --- lib/checkout_sdk/issuing/card_update_headers.rb | 10 +++++----- lib/checkout_sdk/issuing/issuing_client.rb | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/checkout_sdk/issuing/card_update_headers.rb b/lib/checkout_sdk/issuing/card_update_headers.rb index 24eef92..8a86ef0 100644 --- a/lib/checkout_sdk/issuing/card_update_headers.rb +++ b/lib/checkout_sdk/issuing/card_update_headers.rb @@ -4,22 +4,22 @@ module CheckoutSdk module Issuing # 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 + # @deprecated The API no longer documents these headers; use the method without headers. Will # be removed in the next major version. Headers that are set are still sent unchanged. # # Modelled as its own class rather than added to {CheckoutSdk::Common::Headers} because these # two headers belong to one endpoint, and Common::Headers is shared by every client. # # @!attribute return_encrypted_cvv - # @deprecated The API no longer supports this header; use the method without headers. Will - # be removed in the next major version. + # @deprecated The API no longer documents these headers; use the method without headers. + # Will be removed in the next major version. # No longer documented by the API. Still sent unchanged as the return-encrypted-cvv HTTP # header when set. # [Optional] # @return [String] # @!attribute encryption_key - # @deprecated The API no longer supports this header; use the method without headers. Will - # be removed in the next major version. + # @deprecated The API no longer documents these headers; use the method without headers. + # Will be removed in the next major version. # No longer documented by the API. Still sent unchanged as the Encryption-Key HTTP header # when set. # [Optional] diff --git a/lib/checkout_sdk/issuing/issuing_client.rb b/lib/checkout_sdk/issuing/issuing_client.rb index 11a0617..212854f 100644 --- a/lib/checkout_sdk/issuing/issuing_client.rb +++ b/lib/checkout_sdk/issuing/issuing_client.rb @@ -90,7 +90,7 @@ def get_card_details(card_id) # # @param [String] card_id # @param [Hash, UpdateCardRequest] update_card_request - # @param [CardUpdateHeaders, nil] headers deprecated, the API no longer supports these + # @param [CardUpdateHeaders, nil] 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 return-encrypted-cvv and Encryption-Key headers are still sent unchanged. # @return [OpenStruct] the update-card-response: the updated card details and _links. The