From 93810b1c877afa65a478ee5731f8a1be022dba3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Salo=C5=88?= Date: Fri, 11 Feb 2022 11:52:59 +0100 Subject: [PATCH] Added forceToken support for pricing endpoints --- CHANGELOG.md | 7 +++ doc/Article.md | 8 ++- doc/Exception.md | 19 ++++++- example/Article.php | 22 ++++++++ src/Article/ArticleClient.php | 16 ++++-- .../Interfaces/ArticleClientInterface.php | 4 +- src/Exception/BadResponseException.php | 56 +++++++++++++------ src/Exception/ExceptionFactory.php | 2 +- src/Exception/PriceProtectionException.php | 24 ++++++++ src/MpApiClient.php | 2 +- 10 files changed, 129 insertions(+), 31 deletions(-) create mode 100644 src/Exception/PriceProtectionException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca4428..2501b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 4.2.0 - 2022-02-10 + +### Added + +- support for `forceToken` parameter for `updateProductPricing` and `updateVariantPricing` methods of `Article` client +- new `PriceProtectionException` thrown when API returns error with `PRICE_PROTECTION_ERROR` code, used to obtain force token using `getForceToken` method + ## 4.1.0 - 2021-12-16 ### Added diff --git a/doc/Article.md b/doc/Article.md index bf85a18..3247cec 100644 --- a/doc/Article.md +++ b/doc/Article.md @@ -317,7 +317,7 @@ Example above prints out ## Update product pricing -Method expects `product` ID and [Pricing](../src/Article/Entity/Common/Pricing.php) entity and does not return anything. +Method expects `product` ID, [Pricing](../src/Article/Entity/Common/Pricing.php) entity and an optional `forceToken` and does not return anything. ```php /** @var MpApiClient\Common\Interfaces\ArticleClientInterface $articleClient */ @@ -638,14 +638,16 @@ Example above prints out ## Update variant pricing -Method expects `product` and `variant` IDs and [Pricing](../src/Article/Entity/Common/Pricing.php) entity and does not return anything. +Method expects `product` and `variant` IDs, [Pricing](../src/Article/Entity/Common/Pricing.php) entity and an optional `forceToken` and does not return anything. ```php /** @var MpApiClient\Common\Interfaces\ArticleClientInterface $articleClient */ +/** @var MpApiClient\Exception\PriceProtectionException $e */ $articleClient->updateVariantPricing( 'my-product-id', 'my-variant-id', - new Pricing(99.9, 112.0, 80.5) + new Pricing(99.9, 112.0, 80.5), + $e->getForceToken() ); ``` diff --git a/doc/Exception.md b/doc/Exception.md index 1f134a2..d9bdd6c 100644 --- a/doc/Exception.md +++ b/doc/Exception.md @@ -13,25 +13,36 @@ appropriate `@throws` tags. ## [IncorrectDataTypeException](../src/Exception/IncorrectDataTypeException.php) +- Extends [MpApiException](#mpapiexception) - Thrown when method called expects data of a specific type, but received a different one ## [BadResponseException](../src/Exception/BadResponseException.php) +- Extends [MpApiException](#mpapiexception) - Thrown when API responded with `4xx` or `5xx` status code that could not be translated to more specific exceptions -- Usually indicates an unknown/unexpected error occurred +- Usually indicates, that an unknown/unexpected error occurred + +## [PriceProtectionException](../src/Exception/PriceProtectionException.php) + +- Extends [BadResponseException](#badresponseexception) +- Thrown when price protection mechanism is tripped during an article price update +- Contains `getForceToken` method, that may be used to force the price change using a provided token ## [ForbiddenException](../src/Exception/ForbiddenException.php) +- Extends [BadResponseException](#badresponseexception) - Thrown when API returns `403` status code - Usually returned on endpoints your account does not have access to ## [NotFoundException](../src/Exception/NotFoundException.php) +- Extends [BadResponseException](#badresponseexception) - Thrown when API returns `404` status code - This exception should never be thrown for endpoints with static url (i.e., enums/lists) ## [UnauthorizedException](../src/Exception/UnauthorizedException.php) +- Extends [BadResponseException](#badresponseexception) - Thrown when API returns `401` status code - Reasons might include - you forgot to provide authenticator middleware @@ -40,16 +51,18 @@ appropriate `@throws` tags. ## [TooManyRequestsException](../src/Exception/TooManyRequestsException.php) +- Extends [BadResponseException](#badresponseexception) - Thrown when API returns `429` status code - In that case, you have been rate limited by the API and should slow down your request rate -- API returns rate limit information as part of response headers, which you can easily check +- API returns rate limit information as part of response headers, which can be easily checked ### Example of handling some errors ```php article()->updateProductPricing( + 'my-product-id', + new Pricing(99.9, 112.0, 80.5), + $e->getForceToken() + ); + } catch (MpApiException $e) { + echo 'Unexpected error occurred during product pricing update with force token: ' . $e->getMessage(); + } } catch (MpApiException $e) { echo 'Unexpected error occurred during product pricing update: ' . $e->getMessage(); } @@ -429,6 +440,17 @@ 'my-variant-id', new Pricing(99.9, 112.0, 80.5) ); +} catch (PriceProtectionException $e) { + try { + $client->article()->updateVariantPricing( + 'my-product-id', + 'my-variant-id', + new Pricing(99.9, 112.0, 80.5), + $e->getForceToken() + ); + } catch (MpApiException $e) { + echo 'Unexpected error occurred during variant pricing update with force token: ' . $e->getMessage(); + } } catch (MpApiException $e) { echo 'Unexpected error occurred during variant pricing update: ' . $e->getMessage(); } diff --git a/src/Article/ArticleClient.php b/src/Article/ArticleClient.php index fa9a985..c964fe8 100644 --- a/src/Article/ArticleClient.php +++ b/src/Article/ArticleClient.php @@ -87,9 +87,13 @@ public function getProductPricing(string $productId): Pricing ); } - public function updateProductPricing(string $productId, Pricing $pricing): void + public function updateProductPricing(string $productId, Pricing $pricing, ?string $forceToken = null): void { - $this->sendJson('PUT', sprintf(self::PRODUCT_PRICING, $productId), $pricing->getArrayForApi()); + $url = sprintf(self::PRODUCT_PRICING, $productId); + if ($forceToken !== null) { + $url = sprintf('%s?force_token=%s', $url, $forceToken); + } + $this->sendJson('PUT', $url, $pricing->getArrayForApi()); } public function listProductVariants(string $productId, ?Filter $filter): BasicVariantList @@ -144,9 +148,13 @@ public function getVariantPricing(string $productId, string $variantId): Pricing ); } - public function updateVariantPricing(string $productId, string $variantId, Pricing $pricing): void + public function updateVariantPricing(string $productId, string $variantId, Pricing $pricing, ?string $forceToken = null): void { - $this->sendJson('PUT', sprintf(self::VARIANT_PRICING, $productId, $variantId), $pricing->getArrayForApi()); + $url = sprintf(self::VARIANT_PRICING, $productId, $variantId); + if ($forceToken !== null) { + $url = sprintf('%s?force_token=%s', $url, $forceToken); + } + $this->sendJson('PUT', $url, $pricing->getArrayForApi()); } public function updateBatchAvailability(BatchAvailabilityIterator $availability): void diff --git a/src/Common/Interfaces/ArticleClientInterface.php b/src/Common/Interfaces/ArticleClientInterface.php index 74201e5..e567e99 100644 --- a/src/Common/Interfaces/ArticleClientInterface.php +++ b/src/Common/Interfaces/ArticleClientInterface.php @@ -59,7 +59,7 @@ public function getProductPricing(string $productId): Pricing; * @throws MpApiException * @deprecated Will be replaced with batch endpoint, similar to BatchAvailability */ - public function updateProductPricing(string $productId, Pricing $pricing): void; + public function updateProductPricing(string $productId, Pricing $pricing, ?string $forceToken = null): void; /** * @throws MpApiException @@ -102,7 +102,7 @@ public function getVariantPricing(string $productId, string $variantId): Pricing * @throws MpApiException * @deprecated Will be replaced with batch endpoint, similar to BatchAvailability */ - public function updateVariantPricing(string $productId, string $variantId, Pricing $pricing): void; + public function updateVariantPricing(string $productId, string $variantId, Pricing $pricing, ?string $forceToken = null): void; /** * @throws MpApiException diff --git a/src/Exception/BadResponseException.php b/src/Exception/BadResponseException.php index ad4939f..69c9448 100644 --- a/src/Exception/BadResponseException.php +++ b/src/Exception/BadResponseException.php @@ -16,32 +16,54 @@ class BadResponseException extends MpApiException private array $errorCodes; /** - * @param string $message - * @param int $code - * @param Throwable|null $previous - * @param ErrorCode[] $errorCodes + * @var array */ - final private function __construct(string $message = '', int $code = 0, Throwable $previous = null, array $errorCodes = []) + private array $body; + + /** + * @param string $message + * @param int $code + * @param Throwable|null $previous + * @param array $body + * @param ErrorCode[] $errorCodes + */ + final private function __construct(string $message = '', int $code = 0, Throwable $previous = null, array $body = [], array $errorCodes = []) { parent::__construct($message, $code, $previous); + $this->body = $body; $this->errorCodes = $errorCodes; } /** - * @param string $message - * @param int $code - * @param GuzzleBadResponseException $previous - * @param array> $errorCodes - * @return static + * @param string $message + * @param int $code + * @param GuzzleBadResponseException $previous + * @param array $body + * @return BadResponseException + * + * @internal + */ + public static function createFromGuzzle(string $message, int $code, GuzzleBadResponseException $previous, array $body = []): BadResponseException + { + $errorCodes = array_map(fn(array $item): ErrorCode => ErrorCode::createFromApi($item), $body['errorCodes'] ?? []); + if ($errorCodes === []) { + return new static($message, $code, $previous, $body); + } + + switch ($errorCodes[0]->getCode()) { + case PriceProtectionException::ERROR_CODE: + return new PriceProtectionException($message, $code, $previous, $body, $errorCodes); + default: + return new static($message, $code, $previous, $body, $errorCodes); + } + } + + /** + * @return array */ - public static function createFromGuzzle(string $message, int $code, GuzzleBadResponseException $previous, array $errorCodes = []): BadResponseException + public function getBody(): array { - return new static( - $message, - $code, - $previous, - array_map(fn(array $item): ErrorCode => ErrorCode::createFromApi($item), $errorCodes), - ); + return $this->body; } /** diff --git a/src/Exception/ExceptionFactory.php b/src/Exception/ExceptionFactory.php index f40ee8a..380f751 100644 --- a/src/Exception/ExceptionFactory.php +++ b/src/Exception/ExceptionFactory.php @@ -39,7 +39,7 @@ public static function fromGuzzleBadResponse(GuzzleBadResponseException $e): MpA return TooManyRequestsException::createFromGuzzle($message, $code, $e); default: /** @psalm-suppress PossiblyInvalidArgument - https://github.com/vimeo/psalm/issues/4295 */ - return BadResponseException::createFromGuzzle($message, $code, $e, $body['errorCodes'] ?? []); + return BadResponseException::createFromGuzzle($message, $code, $e, $body); } } diff --git a/src/Exception/PriceProtectionException.php b/src/Exception/PriceProtectionException.php new file mode 100644 index 0000000..52ef024 --- /dev/null +++ b/src/Exception/PriceProtectionException.php @@ -0,0 +1,24 @@ +getBody()['data']['data']['forceToken'])) { + throw new LogicException('forceToken not present in response'); + } + + return (string) $this->getBody()['data']['data']['forceToken']; + } + +} diff --git a/src/MpApiClient.php b/src/MpApiClient.php index 2eaa9da..07dfeb6 100644 --- a/src/MpApiClient.php +++ b/src/MpApiClient.php @@ -29,7 +29,7 @@ final class MpApiClient implements ClientInterface, MpApiClientInterface { const APP_NAME = 'mp-api-client'; - const APP_VERSION = '4.1.0'; + const APP_VERSION = '4.2.0'; private BrandClientInterface $brandClient; private CategoryClientInterface $categoryClient;