diff --git a/.gitignore b/.gitignore index 8b7ef35..e22ebed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor composer.lock +.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 727cb1b..4138996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## v0.4.0 +* Make api key optional when initializing client. +* Etsy::getUser now supports an optional user ID. +* Add expiry value to access tokens. +* Change Listing update request to use new PATCH method. +* Add new associations to Listing resource. +* Update endpoints for various Listing associated resources. + +### New +* Added Etsy::getMe() method: [User getMe method](https://developers.etsy.com/documentation/reference#operation/getMe) +* Added Etsy::tokenScopes method: [tokenScopes](https://developers.etsy.com/documentation/reference#operation/tokenScopes) +* Added new [BuyerTaxonomy](https://developers.etsy.com/documentation/reference#tag/BuyerTaxonomy) and BuyerTaxonomyProperty resource and supporting methods. +* Added Etsy::findShops() method: [findShops](https://developers.etsy.com/documentation/reference#operation/findShops) +* Add new [ListingVideo](https://developers.etsy.com/documentation/reference#tag/ShopListing-Video) resource and supporting methods. +* Add new [ProductionPartner](https://developers.etsy.com/documentation/reference#tag/Shop-ProductionPartner) resource and supporting methods. +* Add new [ReturnPolicy](https://developers.etsy.com/documentation/reference#tag/Shop-Return-Policy) resource and supporting methods. + +--- + ## v0.3.4 ### Fixed issues * Add pagination support for the Receipt resource. [Issue #15](https://github.com/rhysnhall/etsy-php-sdk/issues/15) diff --git a/composer.json b/composer.json index 2964881..eed1a65 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "rhysnhall/etsy-php-sdk", - "version": "0.3.4", + "version": "0.4.0", "description": "PHP SDK for Etsy API v3.", "type": "library", "keywords": [ @@ -15,6 +15,11 @@ "name": "Rhys Hall", "email": "hello@rhyshall.com", "homepage": "https://github.com/rhysnhall" + }, + { + "name": "Andrew Christensen", + "email": "andrew@critical-code.com", + "homepage": "https://github.com/erankco" } ], "require": { diff --git a/src/Etsy.php b/src/Etsy.php index d97bbbd..b34228d 100644 --- a/src/Etsy.php +++ b/src/Etsy.php @@ -29,7 +29,7 @@ class Etsy { public function __construct( string $client_id, - string $api_key, + string $api_key = null, array $config = [] ) { $this->client_id = $client_id; @@ -118,16 +118,38 @@ public function ping() { } /** - * Only supports getting the user for who the current API KEY is associated with. + * Gets the user id and shop id for the logged in user. + * + * @return array + */ + public function getMe() { + $response = static::$client->get("/application/users/me"); + return $response ?? []; + } + + /** + * Gets the scopes for the current token + * + * @return array + */ + public function tokenScopes() { + $response = static::$client->post("/application/scopes"); + return $response ?? []; + } + + /** + * Get the user for the current tokened user or for a specified user. * + * @params int|null $user_id * @return Etsy\Resources\User */ - public function getUser() { - $user_id = explode(".", $this->api_key)[0]; - $response = static::$client->get("/application/users/{$user_id}"); + public function getUser($user_id = null) { + $user_id = $user_id ?? explode(".", $this->api_key)[0]; + $response = static::$client->get("/application/users/{$user_id}", []); return static::getResource($response, "User"); } + /** * Gets an Etsy shop. If no shop_id is specified the current user will be queried for an associated shop. * @@ -166,6 +188,7 @@ public function getShops($keyword, $params = []) { /** * Retrieves the full hierarchy tree of seller taxonomy nodes. * + * @link https://developers.etsy.com/documentation/reference#tag/getSellerTaxonomyNodes * @return Etsy\Collection[Etsy\Resources\Taxonomy] */ public function getSellerTaxonomy() { @@ -175,6 +198,19 @@ public function getSellerTaxonomy() { return static::getResource($response, "Taxonomy"); } + /** + * Retrieves the full hierarchy tree of buyer taxonomy nodes. + * + * @link https://developers.etsy.com/documentation/reference#operation/getBuyerTaxonomyNodes + * @return Etsy\Collection[Etsy\Resources\BuyerTaxonomy] + */ + public function getBuyerTaxonomy() { + $response = static::$client->get( + "/application/buyer-taxonomy/nodes" + ); + return static::getResource($response, "BuyerTaxonomy"); + } + /** * Retrieves a list of available shipping carriers and the mail classes associated with them for a given country * @@ -253,4 +289,23 @@ public function getListings( return static::getResource($response, "Listing"); } + /** + * Find shops based on a shop name + * + * @param array $params + * @return Etsy\Collection[Etsy\Resources\Shop] + */ + public function findShops( + array $params + ) { + if(!isset($params['shop_name'])) { + throw new ApiException("Etsy findShops operation expects a `shop_name` param."); + } + $response = static::$client->get( + "/application/shops", + $params + ); + return static::getResource($response, "Shop"); + } + } diff --git a/src/OAuth/Client.php b/src/OAuth/Client.php index a6cd261..e213c6b 100644 --- a/src/OAuth/Client.php +++ b/src/OAuth/Client.php @@ -39,6 +39,11 @@ class Client { */ protected $config = []; + /** + * @var array + */ + public $headers = []; + /** * Create a new instance of Client. * @@ -79,11 +84,14 @@ public function setConfig($config) { * @param string $api_key * @return void */ - public function setApiKey($api_key) { - $this->headers = [ - 'x-api-key' => $this->client_id, - 'Authorization' => "Bearer {$api_key}" + public function setApiKey($api_key = null) { + $headers = [ + 'x-api-key' => $this->client_id ]; + if ($api_key) { + $headers['Authorization'] = "Bearer {$api_key}"; + } + $this->headers = $headers; } public function __call($method, $args) { @@ -190,7 +198,8 @@ public function requestAccessToken( $response = json_decode($response->getBody(), false); return [ 'access_token' => $response->access_token, - 'refresh_token' => $response->refresh_token + 'refresh_token' => $response->refresh_token, + 'expires_at' => (time() + $response->expires_in) ]; } catch(\Exception $e) { @@ -219,7 +228,8 @@ public function refreshAccessToken( $response = json_decode($response->getBody(), false); return [ 'access_token' => $response->access_token, - 'refresh_token' => $response->refresh_token + 'refresh_token' => $response->refresh_token, + 'expires_at' => (time() + $response->expires_in) ]; } catch(\Exception $e) { @@ -248,7 +258,8 @@ public function exchangeLegacyToken( $response = json_decode($response->getBody(), false); return [ 'access_token' => $response->access_token, - 'refresh_token' => $response->refresh_token + 'refresh_token' => $response->refresh_token, + 'expires_at' => (time() + $response->expires_in) ]; } catch(\Exception $e) { diff --git a/src/Resources/BuyerTaxonomy.php b/src/Resources/BuyerTaxonomy.php new file mode 100644 index 0000000..8591aff --- /dev/null +++ b/src/Resources/BuyerTaxonomy.php @@ -0,0 +1,28 @@ +request( + 'GET', + "/application/buyer-taxonomy/nodes/{$this->id}/properties", + "BuyerTaxonomyProperty" + ); + } + +} diff --git a/src/Resources/BuyerTaxonomyProperty.php b/src/Resources/BuyerTaxonomyProperty.php new file mode 100644 index 0000000..1b82369 --- /dev/null +++ b/src/Resources/BuyerTaxonomyProperty.php @@ -0,0 +1,13 @@ + "Shop", "User" => "User", - "Images" => "Image" + "Images" => "Image", + "Translations" => "Translation", + "Inventory" => "Inventory", + "Videos" => "Video", + "Shipping" => "Shipping" ]; /** @@ -32,7 +36,8 @@ class Listing extends Resource { public function update(array $data) { return $this->updateRequest( "/application/shops/{$this->shop_id}/listings/{$this->listing_id}", - $data + $data, + 'PATCH' ); } @@ -44,7 +49,7 @@ public function update(array $data) { */ public function delete() { return $this->deleteRequest( - "/application/shops/{$this->shop_id}/listings/{$this->listing_id}" + "/application/listings/{$this->listing_id}" ); } @@ -153,7 +158,7 @@ public function uploadFile(array $data) { public function getImages() { return $this->request( "GET", - "/application/shops/{$this->shop_id}/listings/{$this->listing_id}/images", + "/application/listings/{$this->listing_id}/images", "ListingImage" ) ->append(["shop_id" => $this->shop_id]); @@ -169,7 +174,7 @@ public function getImages() { public function getImage($listing_image_id) { $listing_image = $this->request( "GET", - "/application/shops/{$this->shop_id}/listings/{$this->listing_id}/images/{$listing_image_id}", + "/application/listings/{$this->listing_id}/images/{$listing_image_id}", "ListingImage" ); if($listing_image) { @@ -177,7 +182,7 @@ public function getImage($listing_image_id) { } return $listing_image; } - + /** * Upload a listing image. * @@ -201,6 +206,63 @@ public function uploadImage(array $data) { return $listing_image; } + /** + * Get the Listing Videos for the listing. + * + * @link https://developers.etsy.com/documentation/reference#operation/getListingVideos + * @return Etsy\Collection[Etsy\Resources\ListingVideo] + */ + public function getVideos() { + return $this->request( + "GET", + "/application/listings/{$this->listing_id}/videos", + "ListingVideo" + ) + ->append(["shop_id" => $this->shop_id]); + } + + /** + * Get a specific listing video. + * + * @link https://developers.etsy.com/documentation/reference#operation/getListingVideo + * @param integer|string $listing_video_id + * @return Etsy\Resources\ListingVideo + */ + public function getVideo($listing_video_id) { + $listing_video = $this->request( + "GET", + "/application/listings/{$this->listing_id}/videos/{$listing_video_id}", + "ListingVideo" + ); + if($listing_video) { + $listing_video->shop_id = $this->shop_id; + } + return $listing_video; + } + + /** + * Upload a listing video. + * + * @link https://developers.etsy.com/documentation/reference#operation/uploadListingVideo + * @param array $data + * @return Etsy\Resources\ListingVideo + */ + public function uploadVideo(array $data) { + if(!isset($data['video']) && !isset($data['video_id'])) { + throw new ApiException("Request requires either 'video_id' or 'video' paramater."); + } + $listing_video = $this->request( + "POST", + "/application/shops/{$this->shop_id}/listings/{$this->listing_id}/videos", + "ListingVideo", + $data + ); + if($listing_video) { + $listing_video->shop_id = $this->shop_id; + } + return $listing_video; + } + /** * Get the inventory for the listing. * @@ -247,6 +309,27 @@ public function updateInventory(array $data) { return $inventory; } + /** + * Get an offering for a listing. Use this method to bypass going through the ListingInventory resource. + * + * @link https://developers.etsy.com/documentation/reference#tag/ShopListing-Offering + * @param integer|string $product_id + * @param integer|string $product_offering_id + * @return Etsy\Resources\ListingOffering + */ + public function getOffering($product_id, $product_offering_id) { + $offering = $this->request( + "GET", + "/application/listings/{$this->listing_id}/inventory/products/{$product_id}/offerings/{$product_offering_id}", + "ListingOffering" + ); + if($offering) { + $offering->listing_id = $this->listing_id; + $offering->product_id = $product_id; + } + return $offering; + } + /** * Get a specific product for a listing. Use this method to bypass going through the ListingInventory resource. * @@ -341,4 +424,34 @@ public function updateVariationImages(array $data) { ); } + /** + * Get all reviews for the listing. + * + * @param array $params + * @return Etsy\Collection[Etsy\Resources\Review] + */ + public function getReviews(array $params = []) { + return $this->request( + "GET", + "/application/listings/{$this->listing_id}/reviews", + "Review", + $params + ); + } + + /** + * Get all transactions for the listing. + * + * @link https://developers.etsy.com/documentation/reference#operation/getShopReceiptTransactionsByListing + * @param array $params + * @return Etsy\Collection[Etsy\Resources\Transaction] + */ + public function getTransactions(array $params = []) { + return $this->request( + "GET", + "/application/shops/{$this->shop_id}/listing/{$this->listing_id}/transactions", + "Transaction", + $params + ); + } } diff --git a/src/Resources/ListingVideo.php b/src/Resources/ListingVideo.php new file mode 100644 index 0000000..423c98b --- /dev/null +++ b/src/Resources/ListingVideo.php @@ -0,0 +1,25 @@ +deleteRequest( + "/application/shops/{$this->shop_id}/listings/{$this->listing_id}/videos/{$this->listing_video_id}" + ); + } +} diff --git a/src/Resources/ProductionPartner.php b/src/Resources/ProductionPartner.php new file mode 100644 index 0000000..77be23b --- /dev/null +++ b/src/Resources/ProductionPartner.php @@ -0,0 +1,13 @@ +updateRequest( + "/application/shops/{$this->shop_id}/policies/return/{$this->return_policy_id}", + $data, + 'PUT' + ); + } + + /** + * Delete the return policy. + * + * @link https://developers.etsy.com/documentation/reference#operation/deleteShopReturnPolicy + * @return boolean + */ + public function delete() { + return $this->deleteRequest( + "/application/shops/{$this->shop_id}/policies/return/{$this->return_policy_id}" + ); + } + + + /** + * Get all listings associated with the shop return policy. + * + * @link https://developers.etsy.com/documentation/reference#operation/getListingsByShopReturnPolicy + * @param array $params + * @return Etsy\Collection[Etsy\Resources\Listing] + */ + public function getListings() { + return $this->request( + "GET", + "/application/shops/{$this->shop_id}/policies/return/{$this->return_policy_id}/listings", + "Listing" + ); + } + +} diff --git a/src/Resources/Shop.php b/src/Resources/Shop.php index 3428969..acc018e 100644 --- a/src/Resources/Shop.php +++ b/src/Resources/Shop.php @@ -78,6 +78,21 @@ public function createSection(string $title) { ); } + /** + * Get all production partners for the shop. + * + * @param array $params + * @return \Etsy\Collection + */ + public function getProductionPartners() { + return $this->request( + "GET", + "/application/shops/{$this->shop_id}/production-partners", + "ProductionPartner" + ) + ->append(['shop_id' => $this->shop_id]); + } + /** * Get all reviews for the shop. * @@ -119,7 +134,7 @@ public function getShippingProfiles() { * Gets a single shipping profile for the shop. * * @param integer|string $shipping_profile_id - * @return Etsy\Resources\ShippingProfile + * @return Etsy\Collection[Etsy\Resources\ShippingProfile] */ public function getShippingProfile($shipping_profile_id) { $profile = $this->request( @@ -137,7 +152,7 @@ public function getShippingProfile($shipping_profile_id) { * * @link https://developers.etsy.com/documentation/reference/#operation/createShopShippingProfile * @param array $data - * @return Etsy\Resources\ShippingProfile + * @return Etsy\Collection[Etsy\Resources\ShippingProfile] */ public function createShippingProfile(array $data) { $profile = $this->request( @@ -151,6 +166,70 @@ public function createShippingProfile(array $data) { return $profile; } + /** + * Get all return policies for the shop. + * + * @return Etsy\Collection[Etsy\Resources\ReturnPolicy] + */ + public function getReturnPolicies() { + $policies = $this->request( + "GET", + "/application/shops/{$this->shop_id}/policies/return", + "ReturnPolicy" + )->append(['shop_id' => $this->shop_id]); + return $policies; + } + + /** + * Gets a single shipping profile for the shop. + * + * @param integer $policy_id + * @return Etsy\Resources\ReturnPolicy + */ + public function getReturnPolicy($policy_id) { + $policy = $this->request( + "GET", + "/application/shops/{$this->shop_id}/shipping-profiles/{$policy_id}", + "ReturnPolicy" + ); + return $policy; + } + + /** + * Consolidate two shop return policies. + * + * @param integer $source_policy_id + * @param integer $destination_policy_id + * @return Etsy\Resources\ReturnPolicy + */ + public function consolidateReturnPolicies(int $source_policy_id, int $destination_policy_id) { + $data = [ + 'source_policy_id' => $source_policy_id, + 'destination_policy_id' => $destination_policy_id + ]; + return $this->updateRequest( + "/application/shops/{$this->shop_id}/policies/return/consolidate", + $data, + 'POST' + ); + } + /** + * Creates a new return policy for the shop. + * + * @link https://developers.etsy.com/documentation/reference#operation/createShopReturnPolicy + * @param array $data + * @return Etsy\Resources\ReturnPolicy + */ + public function createReturnPolicy(array $data) { + $policy = $this->request( + "POST", + "/application/shops/{$this->shop_id}/policies/return", + "ReturnPolicy", + $data + ); + return $policy; + } + /** * Assigns the shop ID to a shipping profile. *