diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index 67e8b96a..c21a13d1 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -84,7 +84,7 @@ jobs: "7.1" { $downloadUrl = "https://downloads.php.net/~windows/releases/archives/php-7.1.33-Win32-VC14-x64.zip" } "7.4" { $downloadUrl = "https://downloads.php.net/~windows/releases/php-7.4.33-Win32-vc15-x64.zip" } "8.1" { $downloadUrl = "https://downloads.php.net/~windows/releases/php-8.1.34-Win32-vs16-x64.zip" } - "8.4" { $downloadUrl = "https://downloads.php.net/~windows/releases/php-8.4.20-Win32-vs17-x64.zip" } + "8.4" { $downloadUrl = "https://downloads.php.net/~windows/releases/php-8.4.21-Win32-vs17-x64.zip" } default { throw "Unsupported PHP version: $phpVersion" } } diff --git a/lib/Checkout/Accounts/AccountsClient.php b/lib/Checkout/Accounts/AccountsClient.php index 07789b7d..34f18fcf 100644 --- a/lib/Checkout/Accounts/AccountsClient.php +++ b/lib/Checkout/Accounts/AccountsClient.php @@ -23,6 +23,7 @@ class AccountsClient extends Client const PAYMENT_INSTRUMENTS_PATH = "payment-instruments"; const MEMBERS_PATH = "members"; const RESERVE_RULES_PATH = "reserve-rules"; + const REQUIREMENTS_PATH = "requirements"; private $filesApiClient; @@ -353,4 +354,59 @@ public function retrieveFile($entityId, $fileId) $this->sdkAuthorization() ); } + + /** + * Get entity requirements + * + * Retrieve the list of pending requirements for a sub-entity. + * + * @param string $entityId The sub-entity's ID (Required) + * @return array + * @throws CheckoutApiException + */ + public function getEntityRequirements($entityId) + { + return $this->apiClient->get( + $this->buildPath(self::ACCOUNTS_PATH, self::ENTITIES_PATH, $entityId, self::REQUIREMENTS_PATH), + $this->sdkAuthorization() + ); + } + + /** + * Get entity requirement details + * + * Retrieve detailed information about a specific requirement. + * + * @param string $entityId The sub-entity's ID (Required) + * @param string $requirementId The requirement ID (Required) + * @return array + * @throws CheckoutApiException + */ + public function getEntityRequirementDetails($entityId, $requirementId) + { + return $this->apiClient->get( + $this->buildPath(self::ACCOUNTS_PATH, self::ENTITIES_PATH, $entityId, self::REQUIREMENTS_PATH, $requirementId), + $this->sdkAuthorization() + ); + } + + /** + * Resolve entity requirement + * + * Submit a response to resolve a pending requirement. + * + * @param string $entityId The sub-entity's ID (Required) + * @param string $requirementId The requirement ID (Required) + * @param EntityRequirementUpdateRequest $request The requirement resolution request (Required) + * @return array + * @throws CheckoutApiException + */ + public function resolveEntityRequirement($entityId, $requirementId, EntityRequirementUpdateRequest $request) + { + return $this->apiClient->put( + $this->buildPath(self::ACCOUNTS_PATH, self::ENTITIES_PATH, $entityId, self::REQUIREMENTS_PATH, $requirementId), + $request, + $this->sdkAuthorization() + ); + } } diff --git a/lib/Checkout/Accounts/EntityRequirementUpdateRequest.php b/lib/Checkout/Accounts/EntityRequirementUpdateRequest.php new file mode 100644 index 00000000..8e484554 --- /dev/null +++ b/lib/Checkout/Accounts/EntityRequirementUpdateRequest.php @@ -0,0 +1,18 @@ +getBaseApiClient($configuration); @@ -146,6 +149,7 @@ public function __construct(CheckoutConfiguration $configuration) $this->standaloneAccountUpdaterClient = new StandaloneAccountUpdaterClient($baseApiClient, $configuration); $this->applePayClient = new ApplePayClient($baseApiClient, $configuration); $this->agenticCommerceClient = new AgenticCommerceClient($baseApiClient, $configuration); + $this->onboardingSimulatorClient = new OnboardingSimulatorClient($baseApiClient, $configuration); $this->complianceRequestsClient = new ComplianceRequestsClient($baseApiClient, $configuration); $this->googlePayClient = new GooglePayClient($baseApiClient, $configuration); } @@ -428,7 +432,17 @@ public function getGooglePayClient() return $this->googlePayClient; } + /**Client for account onboarding simulator endpoints (sandbox testing). + * + * @return OnboardingSimulatorClient + */ + public function getOnboardingSimulatorClient() + { + return $this->onboardingSimulatorClient; + } + /** + * * @param CheckoutConfiguration $configuration * @return ApiClient */ diff --git a/lib/Checkout/Disputes/CompellingEvidence.php b/lib/Checkout/Disputes/CompellingEvidence.php new file mode 100644 index 00000000..2a82aa3e --- /dev/null +++ b/lib/Checkout/Disputes/CompellingEvidence.php @@ -0,0 +1,83 @@ +sdkAuthorization()); } + /** + * Revoke an instrument, preventing it from being used for future transactions. + * + * @param $instrumentId + * @return array + * @throws CheckoutApiException + */ + public function revoke($instrumentId): array + { + return $this->apiClient->patch($this->buildPath( + self::INSTRUMENTS_PATH, + $instrumentId, + self::REVOKE_PATH + ), null, $this->sdkAuthorization()); + } + /** * @param $country_code * @param $currency diff --git a/lib/Checkout/Instruments/Update/UpdateBankInstrumentRequest.php b/lib/Checkout/Instruments/Update/UpdateBankInstrumentRequest.php index ddb71629..2874a9ea 100644 --- a/lib/Checkout/Instruments/Update/UpdateBankInstrumentRequest.php +++ b/lib/Checkout/Instruments/Update/UpdateBankInstrumentRequest.php @@ -71,7 +71,7 @@ public function __construct() /** * @var BankDetails */ - public $bank_details; + public $bank; /** * @var UpdateCustomerRequest diff --git a/lib/Checkout/Issuing/Cards/Create/CardRequest.php b/lib/Checkout/Issuing/Cards/Create/CardRequest.php index 818999d3..c911bbc5 100644 --- a/lib/Checkout/Issuing/Cards/Create/CardRequest.php +++ b/lib/Checkout/Issuing/Cards/Create/CardRequest.php @@ -43,4 +43,14 @@ protected function __construct($type) * @var bool */ public $activate_card; + + /** + * @var array + */ + public $metadata; + + /** + * @var string + */ + public $revocation_date; } diff --git a/lib/Checkout/Issuing/Cards/Create/VirtualCardRequest.php b/lib/Checkout/Issuing/Cards/Create/VirtualCardRequest.php index c764e77e..d7923034 100644 --- a/lib/Checkout/Issuing/Cards/Create/VirtualCardRequest.php +++ b/lib/Checkout/Issuing/Cards/Create/VirtualCardRequest.php @@ -15,4 +15,19 @@ public function __construct() * @var boolean */ public $is_single_use; + + /** + * @var array + */ + public $return_credentials; + + /** + * @var array + */ + public $controls; + + /** + * @var array + */ + public $control_profiles; } diff --git a/lib/Checkout/OnboardingSimulator/OnboardingSimulatorClient.php b/lib/Checkout/OnboardingSimulator/OnboardingSimulatorClient.php new file mode 100644 index 00000000..58393e31 --- /dev/null +++ b/lib/Checkout/OnboardingSimulator/OnboardingSimulatorClient.php @@ -0,0 +1,107 @@ +apiClient->post( + $this->buildPath(self::SIMULATE_PATH, self::ENTITIES_PATH, $entityId, self::REQUIREMENTS_DUE_PATH), + $request, + $this->sdkAuthorization() + ); + } + + /** + * Run a specific scenario for an entity. + * + * @param string $entityId + * @param string $scenarioId + * @return array + * @throws CheckoutApiException + */ + public function runScenario($entityId, $scenarioId) + { + return $this->apiClient->post( + $this->buildPath(self::SIMULATE_PATH, self::ENTITIES_PATH, $entityId, self::SCENARIOS_PATH, $scenarioId), + null, + $this->sdkAuthorization() + ); + } + + /** + * Set the status of an entity. + * + * @param string $entityId + * @param SimulatorSetStatusRequest $request + * @return array + * @throws CheckoutApiException + */ + public function setEntityStatus($entityId, SimulatorSetStatusRequest $request) + { + return $this->apiClient->post( + $this->buildPath(self::SIMULATE_PATH, self::ENTITIES_PATH, $entityId, self::STATUS_PATH), + $request, + $this->sdkAuthorization() + ); + } + + /** + * List all available requirement fields that can be marked as due. + * + * @return array + * @throws CheckoutApiException + */ + public function listAvailableRequirements() + { + return $this->apiClient->get( + $this->buildPath(self::SIMULATE_PATH, self::REQUIREMENTS_DUE_PATH), + $this->sdkAuthorization() + ); + } + + /** + * List all available scenarios that can be run for testing. + * + * @return array + * @throws CheckoutApiException + */ + public function listScenarios() + { + return $this->apiClient->get( + $this->buildPath(self::SIMULATE_PATH, self::SCENARIOS_PATH), + $this->sdkAuthorization() + ); + } +} diff --git a/lib/Checkout/OnboardingSimulator/Requests/SimulatorSetRequirementsDueRequest.php b/lib/Checkout/OnboardingSimulator/Requests/SimulatorSetRequirementsDueRequest.php new file mode 100644 index 00000000..299451f3 --- /dev/null +++ b/lib/Checkout/OnboardingSimulator/Requests/SimulatorSetRequirementsDueRequest.php @@ -0,0 +1,16 @@ +source = new SessionCardSource(); diff --git a/lib/Checkout/Sessions/Source/RequestTokenSource.php b/lib/Checkout/Sessions/Source/RequestTokenSource.php index 84878985..4e679e64 100644 --- a/lib/Checkout/Sessions/Source/RequestTokenSource.php +++ b/lib/Checkout/Sessions/Source/RequestTokenSource.php @@ -15,9 +15,4 @@ public function __construct() * @var string */ public $token; - - /** - * @var bool - */ - public $store_for_future_use; } diff --git a/lib/Checkout/Sessions/Source/SessionCardSource.php b/lib/Checkout/Sessions/Source/SessionCardSource.php index ef8d50eb..95aff7ce 100644 --- a/lib/Checkout/Sessions/Source/SessionCardSource.php +++ b/lib/Checkout/Sessions/Source/SessionCardSource.php @@ -36,9 +36,4 @@ public function __construct() * @var bool */ public $stored = false; - - /** - * @var bool - */ - public $store_for_future_use; } diff --git a/lib/Checkout/Tokens/CardTokenRequest.php b/lib/Checkout/Tokens/CardTokenRequest.php index 7a051dc1..2251c632 100644 --- a/lib/Checkout/Tokens/CardTokenRequest.php +++ b/lib/Checkout/Tokens/CardTokenRequest.php @@ -46,4 +46,13 @@ class CardTokenRequest * @var Phone */ public $phone; + + /** + * The first 2 digits of the card PIN. + * [Optional] + * min 2 characters + * max 2 characters + * @var string|null $pin + */ + public $pin; } diff --git a/lib/Checkout/Tokens/CvvTokenData.php b/lib/Checkout/Tokens/CvvTokenData.php new file mode 100644 index 00000000..e98f4672 --- /dev/null +++ b/lib/Checkout/Tokens/CvvTokenData.php @@ -0,0 +1,15 @@ +apiClient->post(self::TOKENS_PATH, $walletTokenRequest, $this->sdkAuthorization()); } + /** + * Get metadata for a specific token. + * + * @param string $tokenId + * @return array + * @throws CheckoutApiException + */ + public function getTokenMetadata($tokenId) + { + return $this->apiClient->get( + $this->buildPath(self::TOKENS_PATH, $tokenId, "metadata"), + $this->sdkSpecificAuthorization(AuthorizationType::$secretKeyOrOAuth) + ); + } + } diff --git a/test/Checkout/Tests/Accounts/AccountsClientEntityRequirementsTest.php b/test/Checkout/Tests/Accounts/AccountsClientEntityRequirementsTest.php new file mode 100644 index 00000000..6a3c56f4 --- /dev/null +++ b/test/Checkout/Tests/Accounts/AccountsClientEntityRequirementsTest.php @@ -0,0 +1,114 @@ +initMocks(PlatformType::$default); + $filesApiClient = $this->createMock(ApiClient::class); + $this->client = new AccountsClient($this->apiClient, $filesApiClient, $this->configuration); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldGetEntityRequirements() + { + $this->apiClient + ->method("get") + ->willReturn([ + "data" => [ + [ + "id" => "req_123", + "resource" => "ent_456", + "reason" => "periodic_review", + "priority" => "high" + ] + ] + ]); + + $response = $this->client->getEntityRequirements("entity_id"); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldGetEntityRequirementDetails() + { + $this->apiClient + ->method("get") + ->willReturn([ + "id" => "req_123", + "resource" => "ent_456", + "reason" => "periodic_review", + "priority" => "high", + "message" => "Please provide your ID", + "_schema" => [] + ]); + + $response = $this->client->getEntityRequirementDetails("entity_id", "requirement_id"); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldResolveEntityRequirement() + { + $this->apiClient + ->method("put") + ->willReturn([ + "id" => "req_123", + "status" => "processing", + "submitted_at" => "2026-05-05T10:15:30Z" + ]); + + $request = new EntityRequirementUpdateRequest(); + $request->value = ["file_id" => "file_test123"]; + + $response = $this->client->resolveEntityRequirement("entity_id", "requirement_id", $request); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldResolveEntityRequirementWithStringValue() + { + $this->apiClient + ->method("put") + ->willReturn([ + "id" => "req_123", + "status" => "processing", + "submitted_at" => "2026-05-05T10:15:30Z" + ]); + + $request = new EntityRequirementUpdateRequest(); + $request->value = "test_response_value"; + + $response = $this->client->resolveEntityRequirement("entity_id", "requirement_id", $request); + $this->assertNotNull($response); + } +} diff --git a/test/Checkout/Tests/Instruments/InstrumentsClientRevokeTest.php b/test/Checkout/Tests/Instruments/InstrumentsClientRevokeTest.php new file mode 100644 index 00000000..46b2e545 --- /dev/null +++ b/test/Checkout/Tests/Instruments/InstrumentsClientRevokeTest.php @@ -0,0 +1,55 @@ +initMocks(PlatformType::$default); + $this->client = new InstrumentsClient($this->apiClient, $this->configuration); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldRevokeInstrument() + { + $this->apiClient + ->method("patch") + ->willReturn(["success" => true]); + + $response = $this->client->revoke("instrument_id"); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldRevokeInstrumentWithId() + { + $expectedInstrumentId = "inst_test123"; + + $this->apiClient + ->method("patch") + ->willReturn(["success" => true]); + + $response = $this->client->revoke($expectedInstrumentId); + $this->assertNotNull($response); + } +} diff --git a/test/Checkout/Tests/OnboardingSimulator/OnboardingSimulatorClientTest.php b/test/Checkout/Tests/OnboardingSimulator/OnboardingSimulatorClientTest.php new file mode 100644 index 00000000..78b104c2 --- /dev/null +++ b/test/Checkout/Tests/OnboardingSimulator/OnboardingSimulatorClientTest.php @@ -0,0 +1,166 @@ +initMocks(PlatformType::$default); + $this->client = new OnboardingSimulatorClient($this->apiClient, $this->configuration); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldSetRequirementsDue() + { + $this->apiClient + ->method("post") + ->willReturn([ + "entity_id" => "ent_123", + "previous_status" => "Active", + "current_status" => "requirements_due", + "requirements_due" => ["individual.identification.document"] + ]); + + $request = new SimulatorSetRequirementsDueRequest(); + $request->fields = ["individual.identification.document"]; + + $response = $this->client->setRequirementsDue("entity_id", $request); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldRunScenario() + { + $this->apiClient + ->method("post") + ->willReturn([ + "entity_id" => "ent_123", + "scenario_id" => "go_active", + "scenario_name" => "Go Active", + "previous_status" => "RequirementsDue", + "current_status" => "Active", + "requirements_due" => [] + ]); + + $response = $this->client->runScenario("entity_id", "scenario_id"); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldSetEntityStatus() + { + $this->apiClient + ->method("post") + ->willReturn([ + "entity_id" => "ent_123", + "previous_status" => "Pending", + "current_status" => "Active" + ]); + + $request = new SimulatorSetStatusRequest(); + $request->status = "active"; + + $response = $this->client->setEntityStatus("entity_id", $request); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldListAvailableRequirements() + { + $this->apiClient + ->method("get") + ->willReturn([ + "data" => [ + [ + "field" => "individual.identification.document", + "type" => "string" + ] + ] + ]); + + $response = $this->client->listAvailableRequirements(); + $this->assertNotNull($response); + $this->assertTrue(is_array($response)); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldListScenarios() + { + $this->apiClient + ->method("get") + ->willReturn([ + "data" => [ + [ + "id" => "go_active", + "name" => "Go Active", + "description" => "Transitions the entity to active status.", + "action" => "set_status", + "status" => "active" + ] + ] + ]); + + $response = $this->client->listScenarios(); + $this->assertNotNull($response); + $this->assertTrue(is_array($response)); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldSetRequirementsWithMultipleFields() + { + $this->apiClient + ->method("post") + ->willReturn([ + "entity_id" => "ent_123", + "previous_status" => "Active", + "current_status" => "requirements_due", + "requirements_due" => [ + "individual.identification.document", + "company.registration.document" + ] + ]); + + $request = new SimulatorSetRequirementsDueRequest(); + $request->fields = [ + "individual.identification.document", + "company.registration.document" + ]; + + $response = $this->client->setRequirementsDue("entity_id", $request); + $this->assertNotNull($response); + } +} diff --git a/test/Checkout/Tests/Payments/Hosted/HostedPaymentsIntegrationTest.php b/test/Checkout/Tests/Payments/Hosted/HostedPaymentsIntegrationTest.php index c8fbe091..3186a948 100644 --- a/test/Checkout/Tests/Payments/Hosted/HostedPaymentsIntegrationTest.php +++ b/test/Checkout/Tests/Payments/Hosted/HostedPaymentsIntegrationTest.php @@ -19,7 +19,6 @@ use Checkout\Payments\PaymentType; use Checkout\Payments\ProcessingSettings; use Checkout\Payments\Request\PaymentInstruction; -use Checkout\Payments\Request\PaymentRetryRequest; use Checkout\Payments\RiskRequest; use Checkout\Payments\Sender\PaymentInstrumentSender; use Checkout\Payments\ShippingDetails; @@ -129,9 +128,6 @@ private function createHostedPaymentsRequest(): HostedPaymentsSessionRequest $billingDescriptor->city = "London"; $billingDescriptor->name = "Awesome name"; - $customerRetry = new PaymentRetryRequest(); - $customerRetry->max_attempts = 2; - $instruction = new PaymentInstruction(); $instruction->purpose = "fund"; @@ -157,7 +153,6 @@ private function createHostedPaymentsRequest(): HostedPaymentsSessionRequest $hostedPaymentRequest->payment_type = PaymentType::$regular; $hostedPaymentRequest->billing_descriptor = $billingDescriptor; $hostedPaymentRequest->allow_payment_methods = array(PaymentSourceType::$card, PaymentSourceType::$ideal); - $hostedPaymentRequest->customer_retry = $customerRetry; $hostedPaymentRequest->sender = new PaymentInstrumentSender(); $hostedPaymentRequest->instruction = $instruction; diff --git a/test/Checkout/Tests/Payments/Request/Source/Apm/RequestApmSourcesTest.php b/test/Checkout/Tests/Payments/Request/Source/Apm/RequestApmSourcesTest.php index c401186d..ba49fd4d 100644 --- a/test/Checkout/Tests/Payments/Request/Source/Apm/RequestApmSourcesTest.php +++ b/test/Checkout/Tests/Payments/Request/Source/Apm/RequestApmSourcesTest.php @@ -341,7 +341,6 @@ public function testSepaSourceSerializesAllFields() $source = new RequestSepaSource(); $source->country = 'DE'; $source->account_number = 'DE89370400440532013000'; - $source->bank_code = 'COBADEFFXXX'; $source->currency = 'EUR'; $source->mandate_id = 'mandate-123'; $source->date_of_signature = '2026-01-01'; @@ -352,7 +351,6 @@ public function testSepaSourceSerializesAllFields() $this->assertSame('sepa', $decoded['type']); $this->assertSame('DE', $decoded['country']); $this->assertSame('DE89370400440532013000', $decoded['account_number']); - $this->assertSame('COBADEFFXXX', $decoded['bank_code']); $this->assertSame('EUR', $decoded['currency']); $this->assertSame('mandate-123', $decoded['mandate_id']); $this->assertSame('2026-01-01', $decoded['date_of_signature']); diff --git a/test/Checkout/Tests/Tokens/TokensClientMetadataTest.php b/test/Checkout/Tests/Tokens/TokensClientMetadataTest.php new file mode 100644 index 00000000..4ab23c02 --- /dev/null +++ b/test/Checkout/Tests/Tokens/TokensClientMetadataTest.php @@ -0,0 +1,66 @@ +initMocks(PlatformType::$default); + $this->client = new TokensClient($this->apiClient, $this->configuration); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldGetTokenMetadata() + { + $this->apiClient + ->method("get") + ->willReturn([ + "token" => "tok_test123", + "type" => "card", + "scheme" => "Visa", + "last4" => "4242", + "issuer_country" => "US" + ]); + + $response = $this->client->getTokenMetadata("tok_test123"); + $this->assertNotNull($response); + } + + /** + * @test + * @throws CheckoutApiException + */ + public function shouldGetTokenMetadataWithTokenId() + { + $expectedTokenId = "tok_test456"; + + $this->apiClient + ->method("get") + ->willReturn([ + "token" => $expectedTokenId, + "type" => "card", + "bin" => "453255", + "card_type" => "Credit" + ]); + + $response = $this->client->getTokenMetadata($expectedTokenId); + $this->assertNotNull($response); + } +}