Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion lib/Checkout/Issuing/Cards/Create/CardRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,24 @@
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;

Check warning on line 99 in lib/Checkout/Issuing/Cards/Create/CardRequest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "$scheduled_revocation_date" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=checkout_checkout-sdk-php&issues=AaDULwFxwQeN8hyjYKmU&open=AaDULwFxwQeN8hyjYKmU&pullRequest=383

/**
* Date scheduling the card's first activation. Only applies to the initial activation of a card.
Expand Down
28 changes: 26 additions & 2 deletions lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,35 @@
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;

Check warning on line 78 in lib/Checkout/Issuing/Cards/Update/UpdateCardRequest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "$scheduled_revocation_date" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=checkout_checkout-sdk-php&issues=AaDULwJ1wQeN8hyjYKmV&open=AaDULwJ1wQeN8hyjYKmV&pullRequest=383

/**
* 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;
}
3 changes: 3 additions & 0 deletions lib/Checkout/Issuing/IssuingClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions test/Checkout/Tests/Issuing/Cards/CardUpdateHeadersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -193,22 +193,23 @@ 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";
$headers->encryption_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A";

$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());
}

Expand Down Expand Up @@ -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"]
]
Expand Down
99 changes: 99 additions & 0 deletions test/Checkout/Tests/Issuing/Cards/CardsClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -50,6 +51,104 @@ 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);
}

/**
* 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
*/
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
Expand Down
38 changes: 3 additions & 35 deletions test/Checkout/Tests/Issuing/Cards/CardsIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading