diff --git a/src/Client.php b/src/Client.php index 5a8f786..530d063 100644 --- a/src/Client.php +++ b/src/Client.php @@ -17,6 +17,7 @@ use JouwWeb\Sendcloud\Model\SenderAddress; use JouwWeb\Sendcloud\Model\ShippingMethod; use JouwWeb\Sendcloud\Model\ShippingProduct; +use JouwWeb\Sendcloud\Model\Tracking; use JouwWeb\Sendcloud\Model\User; use JouwWeb\Sendcloud\Model\WebhookEvent; use Psr\Http\Message\RequestInterface; @@ -824,4 +825,28 @@ protected function createParcelData( return $parcelData; } + + /** + * Returns the tracking history of a Parcel. + * + * @param string $trackingNumber The tracking number of the Parcel. + * @return Tracking + * @throws SendcloudClientException + * @see https://api.sendcloud.dev/docs/sendcloud-public-api/branches/v2/tracking/operations/get-a-tracking + */ + public function getTracking( + string $trackingNumber, + ): Tracking { + try { + $response = $this->guzzleClient->get('tracking/'.$trackingNumber); + $trackingData = json_decode((string)$response->getBody(), true); + + return Tracking::fromData($trackingData); + } catch (TransferException $exception) { + throw Utility::parseGuzzleException( + $exception, + 'An error occurred while getting tracking from the Sendcloud API.' + ); + } + } } diff --git a/src/Model/Tracking.php b/src/Model/Tracking.php new file mode 100644 index 0000000..902aa70 --- /dev/null +++ b/src/Model/Tracking.php @@ -0,0 +1,47 @@ +carrierCode` is not empty. + * @param string $carrierMessage Status description specified by the carrier, more detailed and more "human-friendly" than `parentStatus`. + */ + public function __construct( + public readonly \DateTimeImmutable $carrierUpdateTimestamp, + public readonly string $parcelStatusHistoryId, + public readonly string $parentStatus, + public readonly string $carrierCode, + public readonly string $carrierMessage, + ) { + } + + public static function fromData(array $data): self + { + return new self( + carrierUpdateTimestamp: new \DateTimeImmutable($data['carrier_update_timestamp']), + parcelStatusHistoryId: (string)$data['parcel_status_history_id'], + parentStatus: (string)$data['parent_status'], + carrierCode: (string)$data['carrier_code'], + carrierMessage: (string)$data['carrier_message'], + ); + } +} \ No newline at end of file diff --git a/test/ClientTest.php b/test/ClientTest.php index 21531ba..366786c 100644 --- a/test/ClientTest.php +++ b/test/ClientTest.php @@ -815,4 +815,67 @@ public function testGetParcelDocumentReturnsTheRequestedContent(): void $this->assertEquals('The ZPL content', $this->client->getParcelDocument(1, Parcel::DOCUMENT_TYPE_LABEL, Parcel::DOCUMENT_CONTENT_TYPE_ZPL, Parcel::DOCUMENT_DPI_203)); } + + public function testGetTracking(): void + { + $this->guzzleClientMock->expects($this->once())->method('request')->willReturn(new Response( + 200, + [], + '{ + "parcel_id": "123456789", + "carrier_code": "colissimo", + "created_at": "2026-01-14 09:58:57.726684+00:00", + "carrier_tracking_url": "https://tracking.eu-central-1-0.sendcloud.sc/forward?carrier=colissimo&code=fake_url", + "sendcloud_tracking_url": null, + "is_return": false, + "is_to_service_point": false, + "is_mail_box": false, + "expected_delivery_date": "2026-01-15", + "statuses": [{ + "carrier_update_timestamp": "2026-01-14 09:58:00+00:00", + "parcel_status_history_id": "7654321000", + "parent_status": "announced-uncollected", + "carrier_code": "colissimo", + "carrier_message": "Your parcel will soon be handed over to us! It is being prepared by the sender." + }, + { + "carrier_update_timestamp": "2026-01-14 09:58:57.726684+00:00", + "parcel_status_history_id": "7654321408", + "parent_status": "no-label", + "carrier_code": "", + "carrier_message": "No label" + }, + { + "carrier_update_timestamp": "2026-01-17 10:30:00+00:00", + "parcel_status_history_id": "7654321253", + "parent_status": "delivered", + "carrier_code": "colissimo", + "carrier_message": "Your parcel has been delivered in your letter box." + }]}' + )); + + $tracking = $this->client->getTracking('ABCDEF'); + + // Assert all the data is get in Tracking + $this->assertEquals('123456789', $tracking->parcelId); + $this->assertEquals('colissimo', $tracking->carrierCode); + $this->assertEquals(new \DateTimeImmutable('2026-01-14 09:58:57.726684+00:00'), $tracking->createdAt); + $this->assertEquals('https://tracking.eu-central-1-0.sendcloud.sc/forward?carrier=colissimo&code=fake_url', $tracking->carrierTrackingUrl); + $this->assertEquals('', $tracking->sendcloudTrackingUrl); + $this->assertFalse($tracking->isReturn); + $this->assertFalse( $tracking->isToServicePoint); + $this->assertFalse($tracking->isMailBox); + $this->assertEquals(new \DateTimeImmutable('2026-01-15'), $tracking->expectedDeliveryDate); + $this->assertCount(3, $tracking->statuses); + + // Assert all the data is get in TrackingStatus + $this->assertEquals(new \DateTimeImmutable('2026-01-17 10:30:00+00:00'), $tracking->statuses[2]->carrierUpdateTimestamp); + $this->assertEquals('7654321253', $tracking->statuses[2]->parcelStatusHistoryId); + $this->assertEquals('delivered', $tracking->statuses[2]->parentStatus); + $this->assertEquals('colissimo', $tracking->statuses[2]->carrierCode); + $this->assertEquals('Your parcel has been delivered in your letter box.', $tracking->statuses[2]->carrierMessage); + + // Assertion for empty string + $this->assertEquals('', $tracking->statuses[1]->carrierCode); + } }