diff --git a/CHANGELOG.md b/CHANGELOG.md index ec28fb06..96c9a74b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## v8.5.0 (2026-02-03) + +- Adds the following functions usable by child and referral customer users (closes #375): + - `apiKey.create` + - `apiKey.delete` + - `apiKey.enable` + - `apiKey.disable` + ## v8.4.0 (2025-11-24) - Adds the following functions: diff --git a/composer.json b/composer.json index a4a9f8e9..20aef0a8 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "easypost/easypost-php", "description": "EasyPost Shipping API Client Library for PHP", - "version": "8.4.0", + "version": "8.5.0", "keywords": [ "shipping", "api", diff --git a/lib/EasyPost/Constant/Constants.php b/lib/EasyPost/Constant/Constants.php index 123a72f8..750ab962 100644 --- a/lib/EasyPost/Constant/Constants.php +++ b/lib/EasyPost/Constant/Constants.php @@ -11,7 +11,7 @@ abstract class Constants const BETA_API_VERSION = 'beta'; // Library constants - const LIBRARY_VERSION = '8.4.0'; + const LIBRARY_VERSION = '8.5.0'; const SUPPORT_EMAIL = 'support@easypost.com'; // Validation diff --git a/lib/EasyPost/Service/ApiKeyService.php b/lib/EasyPost/Service/ApiKeyService.php index 4b6c0075..c755e305 100644 --- a/lib/EasyPost/Service/ApiKeyService.php +++ b/lib/EasyPost/Service/ApiKeyService.php @@ -12,18 +12,6 @@ */ class ApiKeyService extends BaseService { - /** - * Retrieve a list of all API keys. - * - * @return mixed - */ - public function all(): mixed - { - $response = Requestor::request($this->client, 'get', '/api_keys'); - - return InternalUtil::convertToEasyPostObject($this->client, $response); - } - /** * Retrieve a list of API keys (works for the authenticated user or a child user). * @@ -40,7 +28,6 @@ public function retrieveApiKeysForUser(string $id): mixed return $apiKeys->keys; } - // This function was called on a child user, authenticated as parent, only return this child user's details foreach ($apiKeys->children as $childrenKeys) { if ($childrenKeys->id == $id) { @@ -50,4 +37,66 @@ public function retrieveApiKeysForUser(string $id): mixed throw new FilteringException(Constants::NO_USER_FOUND_ERROR); } + + /** + * Retrieve a list of all API keys. + * + * @return mixed + */ + public function all(): mixed + { + $response = Requestor::request($this->client, 'get', '/api_keys'); + + return InternalUtil::convertToEasyPostObject($this->client, $response); + } + + /** + * Create an API key for a child or referral customer user. + * + * @param string $mode + * @return mixed + */ + public function create(string $mode): mixed + { + $params = ['mode' => $mode]; + + return self::createResource(self::serviceModelClassName(self::class), $params); + } + + /** + * Delete an API key for a child or referral customer user. + * + * @param string $id + * @return void + */ + public function delete(string $id): void + { + $this->deleteResource(self::serviceModelClassName(self::class), $id); + } + + /** + * Enable a child or referral customer API key. + * + * @param string $id + * @return mixed + */ + public function enable(string $id): mixed + { + $response = Requestor::request($this->client, 'post', "/api_keys/{$id}/enable"); + + return InternalUtil::convertToEasyPostObject($this->client, $response); + } + + /** + * Disable a child or referral customer API key. + * + * @param string $id + * @return mixed + */ + public function disable(string $id): mixed + { + $response = Requestor::request($this->client, 'post', "/api_keys/{$id}/disable"); + + return InternalUtil::convertToEasyPostObject($this->client, $response); + } } diff --git a/test/EasyPost/ApiKeyTest.php b/test/EasyPost/ApiKeyTest.php index 27efddaf..11d754e9 100644 --- a/test/EasyPost/ApiKeyTest.php +++ b/test/EasyPost/ApiKeyTest.php @@ -27,21 +27,6 @@ public static function tearDownAfterClass(): void TestUtil::teardownVcrTests(); } - /** - * Test retrieving all API keys. - */ - public function testAllApiKeys(): void - { - TestUtil::setupCassette('apiKeys/allApiKeys.yml'); - - $apiKeys = self::$client->apiKeys->all(); - - $this->assertContainsOnlyInstancesOf(ApiKey::class, $apiKeys['keys']); - foreach ($apiKeys['children'] as $child) { - $this->assertContainsOnlyInstancesOf(ApiKey::class, $child['keys']); - } - } - /** * Test retrieving the authenticated user's API keys. */ @@ -59,9 +44,9 @@ public function testAuthenticatedUserApiKeys(): void /** * Test retrieving the authenticated user's API keys. */ - public function testChildUserApiKeys(): void + public function testRetrieveChildUserApiKeys(): void { - TestUtil::setupCassette('apiKeys/childUserApiKeys.yml'); + TestUtil::setupCassette('apiKeys/retrieveChildUserApiKeys.yml'); $user = self::$client->user->create([ 'name' => 'Test User', @@ -75,4 +60,45 @@ public function testChildUserApiKeys(): void // Delete the user once done so we don't pollute with hundreds of child users self::$client->user->delete($childUser->id); } + + /** + * Test retrieving all API keys. + */ + public function testAllApiKeys(): void + { + TestUtil::setupCassette('apiKeys/allApiKeys.yml'); + + $apiKeys = self::$client->apiKeys->all(); + + $this->assertContainsOnlyInstancesOf(ApiKey::class, $apiKeys['keys']); + foreach ($apiKeys['children'] as $child) { + $this->assertContainsOnlyInstancesOf(ApiKey::class, $child['keys']); + } + } + + /** + * Test creating an API key for a child user. + */ + public function testApiKeyLifecycle(): void + { + TestUtil::setupCassette('apiKeys/lifecycle.yml'); + + // Create an API key + $referralClient = new EasyPostClient((string)getenv('REFERRAL_CUSTOMER_PROD_API_KEY')); + $apiKey = $referralClient->apiKeys->create('production'); + $this->assertInstanceOf(ApiKey::class, $apiKey); + $this->assertStringMatchesFormat('ak_%s', $apiKey->id); + $this->assertEquals('production', $apiKey->mode); + + // Disable the API key + $apiKey = $referralClient->apiKeys->disable($apiKey->id); + $this->assertFalse($apiKey->active); + + // Enable the API key + $apiKey = $referralClient->apiKeys->enable($apiKey->id); + $this->assertTrue($apiKey->active); + + // Delete the API key + $referralClient->apiKeys->delete($apiKey->id); + } } diff --git a/test/cassettes/apiKeys/lifecycle.yml b/test/cassettes/apiKeys/lifecycle.yml new file mode 100644 index 00000000..9a8751d2 --- /dev/null +++ b/test/cassettes/apiKeys/lifecycle.yml @@ -0,0 +1,336 @@ + +- + request: + method: POST + url: 'https://api.easypost.com/v2/api_keys' + headers: + Host: api.easypost.com + Expect: '' + Accept-Encoding: '' + Accept: application/json + Authorization: '' + Content-Type: application/json + User-Agent: '' + body: '{"mode":"production"}' + response: + status: + code: 201 + message: Created + headers: + x-frame-options: SAMEORIGIN + x-xss-protection: '1; mode=block' + x-content-type-options: nosniff + x-download-options: noopen + x-permitted-cross-domain-policies: none + referrer-policy: strict-origin-when-cross-origin + x-ep-request-uuid: cfd5bd42698109e6e2b988370270e6e6 + cache-control: 'private, no-cache, no-store' + pragma: no-cache + expires: '0' + content-type: 'application/json; charset=utf-8' + content-length: '199' + x-runtime: '0.119875' + x-node: bigweb66nuq + x-version-label: easypost-202602021944-8c5966c6f9-master + x-backend: easypost + x-proxied: ['intlb5nuq d9379ca146', 'extlb1nuq cbbd141214'] + strict-transport-security: 'max-age=31536000; includeSubDomains; preload' + body: '{"object":"ApiKey","key":"","mode":"production","created_at":"2026-02-02T20:32:38Z","active":true,"id":"ak_2df46e55f8434c3882b40f7c99c8b64f"}' + curl_info: + url: 'https://api.easypost.com/v2/api_keys' + content_type: 'application/json; charset=utf-8' + http_code: 201 + header_size: 693 + request_size: 326 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.295414 + namelookup_time: 0.077613 + connect_time: 0.108231 + pretransfer_time: 0.14122 + size_upload: 21.0 + size_download: 199.0 + speed_download: 673.0 + speed_upload: 71.0 + download_content_length: 199.0 + upload_content_length: 21.0 + starttransfer_time: 0.295391 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 169.62.110.131 + certinfo: { } + primary_port: 443 + local_ip: 192.168.1.60 + local_port: 53672 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: https + appconnect_time_us: 141166 + queue_time_us: 16 + connect_time_us: 108231 + namelookup_time_us: 77613 + pretransfer_time_us: 141220 + redirect_time_us: 0 + starttransfer_time_us: 295391 + posttransfer_time_us: 141219 + total_time_us: 295414 + effective_method: POST + capath: '' + cainfo: '' + used_proxy: 0 + httpauth_used: 0 + proxyauth_used: 0 + conn_id: 0 + index: 0 +- + request: + method: POST + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f/disable' + headers: + Host: api.easypost.com + Expect: '' + Accept-Encoding: '' + Accept: application/json + Authorization: '' + Content-Type: application/json + User-Agent: '' + body: '[]' + response: + status: + code: 200 + message: OK + headers: + x-frame-options: SAMEORIGIN + x-xss-protection: '1; mode=block' + x-content-type-options: nosniff + x-download-options: noopen + x-permitted-cross-domain-policies: none + referrer-policy: strict-origin-when-cross-origin + x-ep-request-uuid: cfd5bd41698109e6e2b988380270e748 + cache-control: 'private, no-cache, no-store' + pragma: no-cache + expires: '0' + content-type: 'application/json; charset=utf-8' + content-length: '200' + x-runtime: '0.143682' + x-node: bigweb33nuq + x-version-label: easypost-202602021944-8c5966c6f9-master + x-backend: easypost + x-proxied: ['intlb4nuq d9379ca146', 'extlb1nuq cbbd141214'] + strict-transport-security: 'max-age=31536000; includeSubDomains; preload' + body: '{"object":"ApiKey","key":"","mode":"production","created_at":"2026-02-02T20:32:38Z","active":false,"id":"ak_2df46e55f8434c3882b40f7c99c8b64f"}' + curl_info: + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f/disable' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 688 + request_size: 350 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.258389 + namelookup_time: 0.002056 + connect_time: 0.037743 + pretransfer_time: 0.075941 + size_upload: 2.0 + size_download: 200.0 + speed_download: 774.0 + speed_upload: 7.0 + download_content_length: 200.0 + upload_content_length: 2.0 + starttransfer_time: 0.258373 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 169.62.110.131 + certinfo: { } + primary_port: 443 + local_ip: 192.168.1.60 + local_port: 53673 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: https + appconnect_time_us: 75894 + queue_time_us: 22 + connect_time_us: 37743 + namelookup_time_us: 2056 + pretransfer_time_us: 75941 + redirect_time_us: 0 + starttransfer_time_us: 258373 + posttransfer_time_us: 75941 + total_time_us: 258389 + effective_method: POST + capath: '' + cainfo: '' + used_proxy: 0 + httpauth_used: 0 + proxyauth_used: 0 + conn_id: 0 + index: 0 +- + request: + method: POST + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f/enable' + headers: + Host: api.easypost.com + Expect: '' + Accept-Encoding: '' + Accept: application/json + Authorization: '' + Content-Type: application/json + User-Agent: '' + body: '[]' + response: + status: + code: 200 + message: OK + headers: + x-frame-options: SAMEORIGIN + x-xss-protection: '1; mode=block' + x-content-type-options: nosniff + x-download-options: noopen + x-permitted-cross-domain-policies: none + referrer-policy: strict-origin-when-cross-origin + x-ep-request-uuid: cfd5bd41698109e6e2b988390270e7ae + cache-control: 'private, no-cache, no-store' + pragma: no-cache + expires: '0' + content-type: 'application/json; charset=utf-8' + content-length: '199' + x-runtime: '0.116097' + x-node: bigweb42nuq + x-version-label: easypost-202602021944-8c5966c6f9-master + x-backend: easypost + x-proxied: ['intlb5nuq d9379ca146', 'extlb1nuq cbbd141214'] + strict-transport-security: 'max-age=31536000; includeSubDomains; preload' + body: '{"object":"ApiKey","key":"","mode":"production","created_at":"2026-02-02T20:32:38Z","active":true,"id":"ak_2df46e55f8434c3882b40f7c99c8b64f"}' + curl_info: + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f/enable' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 688 + request_size: 349 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.226663 + namelookup_time: 0.003653 + connect_time: 0.037558 + pretransfer_time: 0.073309 + size_upload: 2.0 + size_download: 199.0 + speed_download: 877.0 + speed_upload: 8.0 + download_content_length: 199.0 + upload_content_length: 2.0 + starttransfer_time: 0.226637 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 169.62.110.131 + certinfo: { } + primary_port: 443 + local_ip: 192.168.1.60 + local_port: 53674 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: https + appconnect_time_us: 73265 + queue_time_us: 14 + connect_time_us: 37558 + namelookup_time_us: 3653 + pretransfer_time_us: 73309 + redirect_time_us: 0 + starttransfer_time_us: 226637 + posttransfer_time_us: 73309 + total_time_us: 226663 + effective_method: POST + capath: '' + cainfo: '' + used_proxy: 0 + httpauth_used: 0 + proxyauth_used: 0 + conn_id: 0 + index: 0 +- + request: + method: DELETE + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f' + headers: + Host: api.easypost.com + Accept-Encoding: '' + Accept: application/json + Authorization: '' + Content-Type: application/json + User-Agent: '' + response: + status: + code: 204 + message: 'No Content' + headers: + x-frame-options: SAMEORIGIN + x-xss-protection: '1; mode=block' + x-content-type-options: nosniff + x-download-options: noopen + x-permitted-cross-domain-policies: none + referrer-policy: strict-origin-when-cross-origin + x-ep-request-uuid: cfd5bd44698109e6e2b9883a0270e816 + cache-control: 'private, no-cache, no-store' + pragma: no-cache + expires: '0' + x-runtime: '0.259319' + x-node: bigweb39nuq + x-version-label: easypost-202602021944-8c5966c6f9-master + x-backend: easypost + x-proxied: ['intlb3nuq d9379ca146', 'extlb1nuq cbbd141214'] + strict-transport-security: 'max-age=31536000; includeSubDomains; preload' + curl_info: + url: 'https://api.easypost.com/v2/api_keys/ak_2df46e55f8434c3882b40f7c99c8b64f' + content_type: null + http_code: 204 + header_size: 628 + request_size: 323 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.359269 + namelookup_time: 0.002726 + connect_time: 0.032882 + pretransfer_time: 0.066007 + size_upload: 0.0 + size_download: 0.0 + speed_download: 0.0 + speed_upload: 0.0 + download_content_length: 0.0 + upload_content_length: 0.0 + starttransfer_time: 0.359067 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 169.62.110.131 + certinfo: { } + primary_port: 443 + local_ip: 192.168.1.60 + local_port: 53675 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: https + appconnect_time_us: 65971 + queue_time_us: 17 + connect_time_us: 32882 + namelookup_time_us: 2726 + pretransfer_time_us: 66007 + redirect_time_us: 0 + starttransfer_time_us: 359067 + posttransfer_time_us: 66006 + total_time_us: 359269 + effective_method: DELETE + capath: '' + cainfo: '' + used_proxy: 0 + httpauth_used: 0 + proxyauth_used: 0 + conn_id: 0 + index: 0 diff --git a/test/cassettes/apiKeys/childUserApiKeys.yml b/test/cassettes/apiKeys/retrieveChildUserApiKeys.yml similarity index 100% rename from test/cassettes/apiKeys/childUserApiKeys.yml rename to test/cassettes/apiKeys/retrieveChildUserApiKeys.yml