From 31ede3283a7d02296a7db5e5edc17f58bc3c31fb 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, 24 Sep 2026 18:09:54 +0200 Subject: [PATCH 1/2] feat(issuing): add scheduled_revocation_date, status and update-card status Swagger 2026-09-17: add-card-request and update-card-request gain scheduled_revocation_date (replaces deprecated revocation_date); update-card-request gains status to reactivate an inactive/suspended card. update-card-response no longer includes encrypted_cvv on the live API; this SDK returns raw arrays with no typed response classes, so tests are updated to reflect the current response shape. --- .../Issuing/Cards/Create/CardRequest.php | 13 +++- .../Cards/Update/UpdateCardRequest.php | 28 ++++++- .../Issuing/Cards/CardUpdateHeadersTest.php | 14 ++-- .../Tests/Issuing/Cards/CardsClientTest.php | 75 +++++++++++++++++++ .../Issuing/Cards/CardsIntegrationTest.php | 38 +--------- 5 files changed, 123 insertions(+), 45 deletions(-) diff --git a/lib/Checkout/Issuing/Cards/Create/CardRequest.php b/lib/Checkout/Issuing/Cards/Create/CardRequest.php index a6a3a181..3c39fb84 100644 --- a/lib/Checkout/Issuing/Cards/Create/CardRequest.php +++ b/lib/Checkout/Issuing/Cards/Create/CardRequest.php @@ -79,13 +79,24 @@ protected function __construct($type) public $metadata; /** + * Deprecated. Use `scheduled_revocation_date` instead. + * * Date scheduling the card's automatic revocation. * [Optional] + * Format: yyyy-MM-dd + * @var string + * @deprecated Use $scheduled_revocation_date instead. + */ + public $revocation_date; + + /** + * The card will be revoked at midnight UTC on the date specified. + * [Optional] * Format: date (YYYY-MM-DD, time is midnight UTC) * Example: 2027-03-12 * @var string|null */ - public $revocation_date; + public $scheduled_revocation_date; /** * Date scheduling the card's first activation. Only applies to the initial activation of a card. diff --git a/lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php b/lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php index 1dcb08d3..d6fb6f8c 100644 --- a/lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php +++ b/lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php @@ -56,11 +56,35 @@ class UpdateCardRequest public $scheduled_activation_date; /** - * Date scheduling the card's automatic revocation. + * Deprecated. Use `scheduled_revocation_date` instead. + * + * Date scheduling the card's automatic revocation. If you provide both fields, the + * `scheduled_revocation_date` value overrides this value. + * [Optional] + * Format: yyyy-MM-dd + * @var string + * @deprecated Use $scheduled_revocation_date instead. + */ + public $revocation_date; + + /** + * The card will be revoked at midnight UTC on the date specified. Overrides the deprecated + * `revocation_date` if both are provided. * [Optional] * Format: date (YYYY-MM-DD, time is midnight UTC) * Example: 2027-03-12 * @var string|null */ - public $revocation_date; + public $scheduled_revocation_date; + + /** + * 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" + * @var string + */ + public $status; } diff --git a/test/Checkout/Tests/Issuing/Cards/CardUpdateHeadersTest.php b/test/Checkout/Tests/Issuing/Cards/CardUpdateHeadersTest.php index def96556..d2e0f7b1 100644 --- a/test/Checkout/Tests/Issuing/Cards/CardUpdateHeadersTest.php +++ b/test/Checkout/Tests/Issuing/Cards/CardUpdateHeadersTest.php @@ -134,7 +134,7 @@ public function shouldForwardTheHeadersToThePatchCall() $response = $this->client->updateCardDetails("crd_12345", $request, $headers); $this->assertNotNull($response); - $this->assertSame("oJMoNMEEUiQKYOsQ4Zd", $response["encrypted_cvv"]); + $this->assertArrayNotHasKey("encrypted_cvv", $response); $this->assertArrayHasKey("last_modified_date", $response); } @@ -193,14 +193,15 @@ public function shouldSurfaceThe422WhenTheEncryptionKeyIsMissing() } /** + * 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). + * * @test */ public function shouldSucceedWhenBothHeadersAreSupplied() { - $client = $this->buildClientReturning(200, '{' - . '"last_modified_date":"2026-06-01T10:00:00Z",' - . '"encrypted_cvv":"oJMoNMEEUiQKYOsQ4Zd"' - . '}'); + $client = $this->buildClientReturning(200, '{"last_modified_date":"2026-06-01T10:00:00Z"}'); $headers = new CardUpdateHeaders(); $headers->return_encrypted_cvv = "true"; @@ -208,7 +209,7 @@ public function shouldSucceedWhenBothHeadersAreSupplied() $response = $client->updateCardDetails("crd_12345", new UpdateCardRequest(), $headers); - $this->assertSame("oJMoNMEEUiQKYOsQ4Zd", $response["encrypted_cvv"]); + $this->assertArrayNotHasKey("encrypted_cvv", $response); $this->assertSame(200, $response["http_metadata"]->getStatusCode()); } @@ -287,7 +288,6 @@ private function buildExpectedUpdateCardResponse(): array { return [ "last_modified_date" => "2026-06-01T10:00:00Z", - "encrypted_cvv" => "oJMoNMEEUiQKYOsQ4Zd", "_links" => [ "self" => ["href" => "https://api.checkout.com/issuing/cards/crd_12345"] ] diff --git a/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php b/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php index e513a5b0..52513aa0 100644 --- a/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php +++ b/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php @@ -10,6 +10,7 @@ use Checkout\Issuing\Cards\Create\VirtualCardRequest; use Checkout\Issuing\Cards\Credentials\CardCredentialsQuery; use Checkout\Issuing\Cards\Suspend\SuspendCardRequest; +use Checkout\Issuing\Cards\Update\UpdateCardRequest; use Checkout\PlatformType; use Checkout\Tests\UnitTestFixture; @@ -50,6 +51,80 @@ public function shouldCreateCard() $this->assertEquals("crd_12345", $response["id"]); } + /** + * @test + * @throws CheckoutApiException + */ + public function shouldCreateCardWithScheduledRevocationDate() + { + $this->apiClient + ->method("post") + ->willReturn([ + "id" => "crd_12345", + "scheduled_revocation_date" => "2027-03-12", + "last_activated_on" => null + ]); + + $request = new VirtualCardRequest(); + $request->scheduled_revocation_date = "2027-03-12"; + + $response = $this->client->createCard($request); + + $this->assertEquals("2027-03-12", $request->scheduled_revocation_date); + $this->assertNotNull($response); + $this->assertEquals("2027-03-12", $response["scheduled_revocation_date"]); + $this->assertNull($response["last_activated_on"]); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldUpdateCardWithStatusAndScheduledRevocationDate() + { + $this->apiClient + ->method("patch") + ->willReturn([ + "id" => "crd_12345", + "status" => "active", + "scheduled_revocation_date" => "2027-03-12", + "last_modified_date" => "2026-09-17T10:00:00Z" + ]); + + $request = new UpdateCardRequest(); + $request->status = "active"; + $request->scheduled_revocation_date = "2027-03-12"; + + $response = $this->client->updateCardDetails("crd_12345", $request); + + $this->assertEquals("active", $request->status); + $this->assertEquals("2027-03-12", $request->scheduled_revocation_date); + $this->assertNotNull($response); + $this->assertEquals("active", $response["status"]); + $this->assertEquals("2027-03-12", $response["scheduled_revocation_date"]); + $this->assertArrayNotHasKey("encrypted_cvv", $response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldActivateCardWithLastActivatedOn() + { + $this->apiClient + ->method("post") + ->willReturn([ + "id" => "crd_12345", + "last_activated_on" => "2026-09-17T10:00:00Z" + ]); + + $response = $this->client->activateCard("crd_12345"); + + $this->assertNotNull($response); + $this->assertArrayHasKey("last_activated_on", $response); + $this->assertEquals("2026-09-17T10:00:00Z", $response["last_activated_on"]); + } + /** * @test * @throws CheckoutApiException diff --git a/test/Checkout/Tests/Issuing/Cards/CardsIntegrationTest.php b/test/Checkout/Tests/Issuing/Cards/CardsIntegrationTest.php index d36810d5..deb644ca 100644 --- a/test/Checkout/Tests/Issuing/Cards/CardsIntegrationTest.php +++ b/test/Checkout/Tests/Issuing/Cards/CardsIntegrationTest.php @@ -17,7 +17,6 @@ use Checkout\Issuing\Cards\Create\CardLifetime; use Checkout\Issuing\Cards\Create\LifetimeUnit; use Checkout\Issuing\Cards\Create\VirtualCardRequest; -use Checkout\Issuing\Cards\Update\CardUpdateHeaders; use Checkout\Issuing\Cards\Update\UpdateCardRequest; use Checkout\Issuing\Cards\Renew\RenewCardRequest; use Checkout\Tests\Issuing\AbstractIssuingIntegrationTest; @@ -301,28 +300,9 @@ public function shouldUpdateCardRevocationDate() $this->assertResponse($updateResponse, "last_modified_date"); } - /** - * @test - * @throws CheckoutApiException - */ - public function shouldUpdateCardDetailsReturningEncryptedCvv() - { - $card = $this->createCard($this->cardholder["id"], true); - - $updateRequest = new UpdateCardRequest(); - $updateRequest->reference = "UPDATED-REF-123"; - - $headers = new CardUpdateHeaders(); - $headers->return_encrypted_cvv = "true"; - $headers->encryption_key = $this->getEncryptionKey(); - - $updateResponse = $this->issuingApi->getIssuingClient() - ->updateCardDetails($card["id"], $updateRequest, $headers); - - $this->assertEquals(200, $updateResponse["http_metadata"]->getStatusCode()); - $this->assertResponse($updateResponse, "last_modified_date", "encrypted_cvv"); - $this->assertNotEmpty($updateResponse["encrypted_cvv"]); - } + // 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). /** * The next round hour in UTC, which is the earliest value the API accepts for a scheduled @@ -335,18 +315,6 @@ private function nextRoundHour(): string return gmdate("Y-m-d\\TH:00\\Z", strtotime("+2 hours")); } - /** - * The RSA public key used to encrypt returned credentials, with the PEM headers and newlines - * removed. Supplied by the environment because it pairs with a private key the test cannot - * hold. - * - * @return string - */ - private function getEncryptionKey(): string - { - return getenv("CHECKOUT_ISSUING_ENCRYPTION_KEY") ?: ""; - } - /** * @test * @throws CheckoutApiException From 8ea7bd2d4818f67ec22883c91db57d6126b9e39b 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, 25 Sep 2026 11:28:08 +0200 Subject: [PATCH 2/2] fix(issuing): document is_single_use on update-card-response for virtual cards Swagger 2026-09-23 split update-card-response into a virtual/physical discriminator; the virtual variant adds is_single_use (specifies whether the card is set to expire after a single use). Physical cards never send it. This SDK returns raw arrays for issuing responses, so this is a doc + test update. --- lib/Checkout/Issuing/IssuingClient.php | 3 +++ .../Tests/Issuing/Cards/CardsClientTest.php | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/Checkout/Issuing/IssuingClient.php b/lib/Checkout/Issuing/IssuingClient.php index d3ce1df0..d565058d 100644 --- a/lib/Checkout/Issuing/IssuingClient.php +++ b/lib/Checkout/Issuing/IssuingClient.php @@ -755,6 +755,9 @@ public function getSingleTransaction(string $transactionId) : array * Update the details of an issued card. * Only the fields for which you provide values will be updated. * + * For virtual cards, the response may include is_single_use, specifying whether + * the card is set to expire after a single use. Physical cards never send it. + * * @param string $cardId - The card's unique identifier. (Required) * @param UpdateCardRequest $updateCardRequest (Required) * @param CardUpdateHeaders|null $headers - The optional return-encrypted-cvv and diff --git a/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php b/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php index 52513aa0..749d7a27 100644 --- a/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php +++ b/test/Checkout/Tests/Issuing/Cards/CardsClientTest.php @@ -105,6 +105,30 @@ public function shouldUpdateCardWithStatusAndScheduledRevocationDate() $this->assertArrayNotHasKey("encrypted_cvv", $response); } + /** + * The 2026-09-23 spec update split update-card-response into a virtual/physical + * discriminator; the virtual variant adds is_single_use. + * + * @test + * @throws CheckoutApiException + */ + public function shouldUpdateVirtualCardWithIsSingleUse() + { + $this->apiClient + ->method("patch") + ->willReturn([ + "type" => "virtual", + "last_modified_date" => "2026-09-17T10:00:00Z", + "is_single_use" => true + ]); + + $request = new UpdateCardRequest(); + $response = $this->client->updateCardDetails("crd_12345", $request); + + $this->assertNotNull($response); + $this->assertTrue($response["is_single_use"]); + } + /** * @test * @throws CheckoutApiException