Skip to content

Commit

Permalink
Merge pull request #33 from AlexisPPLIN/fix/servicepoints
Browse files Browse the repository at this point in the history
Fixes of service points integration
  • Loading branch information
villermen authored Feb 10, 2024
2 parents c6eb167 + 1d2c309 commit fd077dc
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 192 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,40 @@ if ($webhookEvent->getType() === WebhookEvent::TYPE_PARCEL_STATUS_CHANGED) {
}
```

### Retieve a list of service points

```php
use JouwWeb\Sendcloud\ServicePointsClient;
use JouwWeb\Sendcloud\Exception\SendcloudRequestException;

$client = new ServicePointsClient('your_public_key', 'your_secret_key');

try {
// Search for service points in the Netherlands.
$servicePoints = $client->searchServicePoints('NL');

var_dump($servicePoints[0]->isActive()); // bool(true)
var_dump($servicePoints[0]->getName()); // string(7) "Primera"
var_dump($servicePoints[0]->getCarrier()); // string(6) "postnl"
var_dump($servicePoints[0]->getDistance()); // NULL ↓

// If we want Sendcloud to calculate the distance between us and each service point, we need to supply latitude and
// longitude.
$servicePointsWithDistance = $client->searchServicePoints(
country: 'NL',
latitude: 51.4350511,
longitude: 5.4746339
);

var_dump($servicePointsWithDistance[0]->getName()); // string(14) "Pakketautomaat"
var_dump($servicePointsWithDistance[0]->getDistance()); // int(553)

// Obtain a specific service point by ID.
$servicePoint = $client->getServicePoint(1);
} catch (SendcloudRequestException $exception) {
echo $exception->getMessage();
}
```

## Installation
`composer require jouwweb/sendcloud`
192 changes: 12 additions & 180 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace JouwWeb\Sendcloud;

use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\Utils;
Expand All @@ -17,7 +16,6 @@
use JouwWeb\Sendcloud\Model\ShippingMethod;
use JouwWeb\Sendcloud\Model\User;
use JouwWeb\Sendcloud\Model\WebhookEvent;
use JouwWeb\Sendcloud\Model\ServicePoint;
use Psr\Http\Message\RequestInterface;

/**
Expand Down Expand Up @@ -64,7 +62,7 @@ public function getUser(): User
try {
return User::fromData(json_decode((string)$this->guzzleClient->get('user')->getBody(), true)['user']);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'An error occurred while fetching the Sendcloud user.');
throw Utility::parseGuzzleException($exception, 'An error occurred while fetching the Sendcloud user.');
}
}

Expand Down Expand Up @@ -129,7 +127,7 @@ public function getShippingMethods(

return $shippingMethods;
} catch (TransferException $exception) {
throw $this->parseGuzzleException(
throw Utility::parseGuzzleException(
$exception,
'An error occurred while fetching shipping methods from the Sendcloud API.'
);
Expand Down Expand Up @@ -196,7 +194,7 @@ public function createParcel(

return Parcel::fromData(json_decode((string)$response->getBody(), true)['parcel']);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not create parcel in Sendcloud.');
throw Utility::parseGuzzleException($exception, 'Could not create parcel in Sendcloud.');
}
}

Expand Down Expand Up @@ -277,7 +275,7 @@ public function createMultiParcel(

return $parcels;
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not create parcel in Sendcloud.');
throw Utility::parseGuzzleException($exception, 'Could not create parcel in Sendcloud.');
}
}

Expand Down Expand Up @@ -312,7 +310,7 @@ public function updateParcel(Parcel|int $parcel, Address $shippingAddress): Parc

return Parcel::fromData(json_decode((string)$response->getBody(), true)['parcel']);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not update parcel in Sendcloud.');
throw Utility::parseGuzzleException($exception, 'Could not update parcel in Sendcloud.');
}
}

Expand Down Expand Up @@ -348,7 +346,7 @@ public function createLabel(Parcel|int $parcel, ShippingMethod|int $shippingMeth

return Parcel::fromData(json_decode((string)$response->getBody(), true)['parcel']);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not create parcel with Sendcloud.');
throw Utility::parseGuzzleException($exception, 'Could not create parcel with Sendcloud.');
}
}

Expand All @@ -373,7 +371,7 @@ public function cancelParcel(Parcel|int $parcel): bool
return false;
}

throw $this->parseGuzzleException($exception, 'An error occurred while cancelling the parcel.');
throw Utility::parseGuzzleException($exception, 'An error occurred while cancelling the parcel.');
}
}

Expand Down Expand Up @@ -405,7 +403,7 @@ public function getLabelPdf(Parcel|int $parcel, int $format): string
try {
return (string)$this->guzzleClient->get($labelUrl)->getBody();
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve label.');
throw Utility::parseGuzzleException($exception, 'Could not retrieve label.');
}
}

Expand Down Expand Up @@ -440,7 +438,7 @@ public function getBulkLabelPdf(array $parcels, int $format): string
],
]);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve label information.');
throw Utility::parseGuzzleException($exception, 'Could not retrieve label information.');
}

$labelData = json_decode((string)$response->getBody(), true);
Expand All @@ -452,7 +450,7 @@ public function getBulkLabelPdf(array $parcels, int $format): string
try {
return (string)$this->guzzleClient->get($labelUrl)->getBody();
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve label PDF data.');
throw Utility::parseGuzzleException($exception, 'Could not retrieve label PDF data.');
}
}

Expand All @@ -472,7 +470,7 @@ public function getSenderAddresses(): array
return SenderAddress::fromData($senderAddressData);
}, $senderAddressesData);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve sender addresses.');
throw Utility::parseGuzzleException($exception, 'Could not retrieve sender addresses.');
}
}

Expand All @@ -487,7 +485,7 @@ public function getParcel(Parcel|int $parcel): Parcel
$response = $this->guzzleClient->get('parcels/' . $this->parseParcelArgument($parcel));
return Parcel::fromData(json_decode((string)$response->getBody(), true)['parcel']);
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve parcel.');
throw Utility::parseGuzzleException($exception, 'Could not retrieve parcel.');
}
}

Expand Down Expand Up @@ -523,135 +521,6 @@ public function getReturnPortalUrl(Parcel|int $parcel): ?string
}
}

/**
* Summary of searchServicePoints
*
* @see https://api.sendcloud.dev/docs/sendcloud-public-api/service-points%2Foperations%2Flist-service-points
* @param string $country A country ISO 2 code (Example : 'NL')
* @param string|null $address Address of the destination address. Can accept postal code instead of the street and the house number. (Example : 'Stadhuisplein 10')
* @param string|null $carrier A comma-separated list of carrier codes (stringified) (Example : 'postnl,dpd')
* @param string|null $city City of the destination address. (Example : 'Eindhoven')
* @param string|null $houseNumber House number of the destination address. (Example : '10')
* @param string|null $latitude Used as a reference point to calculate the distance of the service point to the provided location.
* @param string|null $longitude Used as a reference point to calculate the distance of the service point to the provided location.
* @param string|null $neLatitude Latitude of the northeast corner of the bounding box.
* @param string|null $neLongitude Longitude of the northeast corner of the bounding box.
* @param string|null $postalCode Postal code of the destination address. Using postal_code will return you service points located around that particular postal code. (Example : '5611 EM')
* @param string|null $pudoId DPD-specific query parameter. (<= 7 characters)
* @param int|null $radius Radius (in meter) of a bounding circle. Can be used instead of the ne_latitude, ne_longitude, sw_latitude, and sw_longitude parameters to define a bounding box. By default, it’s 100 meters. Minimum value: 100 meters. Maximum value: 50 000 meters.
* @param string|null $shopType Filters results by their shop type.
* @param string|null $swLatitude Latitude of the southwest corner of the bounding box.
* @param string|null $swLongitude Longitude of the southwest corner of the bounding box.
* @param float|null $weight Weight (in kg.) of the parcel to be shipped to the service points. Certain carriers impose limits for certain service points that cannot accept parcels above a certain weight limit.
* @return ServicePoint[]
*/
public function searchServicePoints(
string $country,
?string $address = null,
?string $carrier = null,
?string $city = null,
?string $houseNumber = null,
?string $latitude = null,
?string $longitude = null,
?string $neLatitude = null,
?string $neLongitude = null,
?string $postalCode = null,
?string $pudoId = null,
?int $radius = null,
?string $shopType = null,
?string $swLatitude = null,
?string $swLongitude = null,
?float $weight = null
): array {
try {
// Construct query array
$query = [];
$query['country_id'] = $country;

if (isset($address)) {
$query['address'] = $address;
}
if (isset($carrier)) {
$query['carrier'] = $carrier;
}
if (isset($city)) {
$query['city'] = $city;
}
if (isset($houseNumber)) {
$query['house_number'] = $houseNumber;
}
if (isset($latitude)) {
$query['latitude'] = $latitude;
}
if (isset($longitude)) {
$query['longitude'] = $longitude;
}
if (isset($neLatitude)) {
$query['ne_latitude'] = $neLatitude;
}
if (isset($neLongitude)) {
$query['ne_longitude'] = $neLongitude;
}
if (isset($postalCode)) {
$query['postal_code'] = $postalCode;
}
if (isset($pudoId)) {
$query['pudo_id'] = $pudoId;
}
if (isset($radius)) {
$query['radius'] = $radius;
}
if (isset($shopType)) {
$query['shop_type'] = $shopType;
}
if (isset($swLatitude)) {
$query['sw_latitude'] = $swLatitude;
}
if (isset($swLongitude)) {
$query['sw_longitude'] = $swLongitude;
}
if (isset($weight)) {
$query['weight'] = $weight;
}

// Send request
$response = $this->guzzleClient->get('service-point', [
'query' => $query,
]);

// Decode and create ServicePoint objects
$json = json_decode((string)$response->getBody(), true);

$servicePoints = [];
foreach ($json as $obj) {
$servicePoints[] = ServicePoint::fromData($obj);
}

return $servicePoints;
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve service point.');
}
}

/**
* Returns service point by ID.
*
* @see https://api.sendcloud.dev/docs/sendcloud-public-api/service-points%2Foperations%2Fget-a-service-point
* @return ServicePoint
* @throws SendcloudRequestException
*/
public function getServicePoint(ServicePoint|int $servicePoint): ServicePoint
{
$servicePointId = $servicePoint instanceof ServicePoint ? $servicePoint->getId() : $servicePoint;

try {
$response = $this->guzzleClient->get('service-point/' . $servicePointId);
return ServicePoint::fromData(json_decode((string)$response->getBody(), true));
} catch (TransferException $exception) {
throw $this->parseGuzzleException($exception, 'Could not retrieve service point.');
}
}

/**
* Returns the given arguments as data in Sendcloud parcel format.
*
Expand Down Expand Up @@ -813,43 +682,6 @@ protected function getParcelData(
return $parcelData;
}

protected function parseGuzzleException(
TransferException $exception,
string $defaultMessage
): SendcloudRequestException {
$message = $defaultMessage;
$code = SendcloudRequestException::CODE_UNKNOWN;

$responseCode = null;
$responseMessage = null;
if ($exception instanceof RequestException && $exception->hasResponse()) {
$responseData = json_decode((string)$exception->getResponse()->getBody(), true);
$responseCode = $responseData['error']['code'] ?? null;
$responseMessage = $responseData['error']['message'] ?? null;
}

if ($exception instanceof ConnectException) {
$message = 'Could not contact Sendcloud API.';
$code = SendcloudRequestException::CODE_CONNECTION_FAILED;
}

// Precondition failed, parse response message to determine code of exception
if ($exception->getCode() === 401) {
$message = 'Invalid public/secret key combination.';
$code = SendcloudRequestException::CODE_UNAUTHORIZED;
} elseif ($exception->getCode() === 412) {
$message = 'Sendcloud account is not fully configured yet.';

if (stripos($responseMessage, 'no address data') !== false) {
$code = SendcloudRequestException::CODE_NO_ADDRESS_DATA;
} elseif (stripos($responseMessage, 'not allowed to announce') !== false) {
$code = SendcloudRequestException::CODE_NOT_ALLOWED_TO_ANNOUNCE;
}
}

return new SendcloudRequestException($message, $code, $exception, $responseCode, $responseMessage);
}

// TODO: Remove parseParcelArgument() now we use native unions.
protected function parseParcelArgument(Parcel|int $parcel): int
{
Expand Down
10 changes: 5 additions & 5 deletions src/Model/ServicePoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public static function fromData(array $data): self
(array)$data['formatted_opening_times'],
(bool)$data['open_tomorrow'],
(bool)$data['open_upcoming_week'],
(int)$data['distance']
isset($data['distance']) ? (int) $data['distance'] : null
);
}

/**
* @param array<string, string> $extraData Can contain carrier specific data
* @param array<int, string[]> $formattedOpeningTimes
* @param int $distance Distance between the reference point and the service point in meters.
* @param ?int $distance Distance in meters OR null if latitude and longitude are not provided in the request
*/
public function __construct(
protected int $id,
Expand All @@ -62,7 +62,7 @@ public function __construct(
protected array $formattedOpeningTimes,
protected bool $openTomorrow,
protected bool $openUpcomingWeek,
protected int $distance,
protected ?int $distance,
) {
}

Expand Down Expand Up @@ -175,9 +175,9 @@ public function isOpenUpcomingWeek(): bool
}

/**
* Distance between the reference point and the service point in meters.
* @return ?int Distance in meters OR null if latitude and longitude are not provided in the request
*/
public function getDistance(): int
public function getDistance(): ?int
{
return $this->distance;
}
Expand Down
Loading

0 comments on commit fd077dc

Please sign in to comment.