Skip to content

Commit

Permalink
Added forceToken support for pricing endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalSalon committed Feb 21, 2022
1 parent adbb569 commit 0a5d9a2
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 37 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,14 @@ 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
- `getBody` method to `BadResponseException`, which returns API response as an array

## 4.1.0 - 2021-12-16

### Added
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -28,17 +28,17 @@ Client consists of one main client and multiple, separate, domain clients.

The main client groups all domain clients under one object, for easier implementation, but every domain client can be initialized and used by itself.

Every client provides an interface that SHOULD be used as parameter types in code, instead of client classes themselves (i.e., use `MpApiClientInterface $client`
Every client provides an interface that SHOULD be used as parameter types in code, instead of client classes themselves (e.g., use `MpApiClientInterface $client`
or `BrandsClientInterface $client` instead of `MpApiClient $client` or `BrandsClient $client`).

When initializing the client, you MUST provide

1. an authenticator implementing [AuthMiddlewareInterface](src/Common/Interfaces/AuthMiddlewareInterface.php)
- currently, only [ClientIdAuthenticator](src/Common/Authenticators/ClientIdAuthenticator.php), which accepts `my-client-id`, is provided
- in the future, new authenticators will be released (i.e., OAuth)
- in the future, new authenticators will be released (e.g., OAuth)
2. name of the app using the API
- it is sent with every request to Mall API for easier request identification and debugging of reported issues
- please provide a simple, yet meaningful name, i.e., `MyAppName CRM` or `MyAppName Order sync` instead of a random string
- please provide a simple, yet meaningful name (e.g., `MyAppName CRM` or `MyAppName Order sync`), instead of a random string

### Examples

Expand Down Expand Up @@ -131,7 +131,7 @@ List of custom Exceptions thrown in this client can be found [here](doc/Exceptio

## ⚠ Warning

- client does not include support for deprecated endpoints that will be changed, replaced or removed in the future (i.e., `/v1/deliveries` or `/v1/gifts`)
- client does not include support for deprecated endpoints that will be changed, replaced or removed in the future (e.g., `/v1/deliveries` or `/v1/gifts`)

## ℹ Known missing or incomplete features

Expand Down
8 changes: 5 additions & 3 deletions doc/Article.md
Expand Up @@ -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 */
Expand Down Expand Up @@ -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()
);
```

Expand Down
23 changes: 18 additions & 5 deletions doc/Exception.md
Expand Up @@ -4,7 +4,7 @@ Client contains custom exceptions, for easier error handling in your application

All custom exceptions extend generic `MpApiException`.

Some methods might throw other native PHP exceptions (i.e., `InvalidArgumentException`), which do not extend `MpApiException`. Such methods always have
Some methods might throw other native PHP exceptions (e.g., `InvalidArgumentException`, `LogicException`), which do not extend `MpApiException`. Such methods always have
appropriate `@throws` tags.

## [MpApiException](../src/Exception/MpApiException.php)
Expand All @@ -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)
- This exception should never be thrown for endpoints with static url (e.g., 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
Expand All @@ -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
<?php declare(strict_types=1);

use MpApiClient\Common\Interfaces\ChecksClientInterface;use MpApiClient\Exception\BadResponseException;
use MpApiClient\Common\Interfaces\ChecksClientInterface;
use MpApiClient\Exception\BadResponseException;
use MpApiClient\Exception\IncorrectDataTypeException;
use MpApiClient\Exception\MpApiException;
use MpApiClient\Exception\TooManyRequestsException;
Expand Down
22 changes: 22 additions & 0 deletions example/Article.php
Expand Up @@ -15,6 +15,7 @@
use MpApiClient\Article\Entity\Common\StatusEnum;
use MpApiClient\Common\Authenticators\ClientIdAuthenticator;
use MpApiClient\Exception\MpApiException;
use MpApiClient\Exception\PriceProtectionException;
use MpApiClient\Filter\Filter;
use MpApiClient\Filter\FilterItem;
use MpApiClient\Filter\FilterOperatorEnum;
Expand Down Expand Up @@ -231,6 +232,16 @@
'my-product-id',
new Pricing(99.9, 112.0, 80.5)
);
} catch (PriceProtectionException $e) {
try {
$client->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();
}
Expand Down Expand Up @@ -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();
}
Expand Down
16 changes: 12 additions & 4 deletions src/Article/ArticleClient.php
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Common/Interfaces/ArticleClientInterface.php
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
56 changes: 39 additions & 17 deletions src/Exception/BadResponseException.php
Expand Up @@ -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<string, mixed>
*/
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<string, mixed> $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<int, array<string, mixed>> $errorCodes
* @return static
* @param string $message
* @param int $code
* @param GuzzleBadResponseException $previous
* @param array<string, mixed> $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<string, mixed>
*/
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;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Exception/ExceptionFactory.php
Expand Up @@ -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);
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/Exception/PriceProtectionException.php
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace MpApiClient\Exception;

use LogicException;

final class PriceProtectionException extends BadResponseException
{

public const ERROR_CODE = 'PRICE_PROTECTION_ERROR';

/**
* @throws LogicException
*/
public function getForceToken(): string
{
if (!isset($this->getBody()['data']['data']['forceToken'])) {
throw new LogicException('forceToken not present in response');
}

return (string) $this->getBody()['data']['data']['forceToken'];
}

}
2 changes: 1 addition & 1 deletion src/MpApiClient.php
Expand Up @@ -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;
Expand Down

0 comments on commit 0a5d9a2

Please sign in to comment.