Installation:
composer require answear/boxnow-bundle
The bundle auto-registers in config/bundles.php.
Configure API credentials in config/packages/answear_boxnow.yaml:
answear_box_now:
client_id: your_client_id
client_secret: your_client_secret
api_url: https://locationapi-stage.boxnow.gr # Optional (defaults to stage)
First Use Case: Fetch pickup points for a region (e.g., Cyprus) without authentication:
use Answear\BoxNowBundle\Service\PickupPointService;
use Answear\BoxNowBundle\Enum\RegionEnum;
$pickupPoints = $this->container->get(PickupPointService::class);
$cyprusPoints = $pickupPoints->getAllByRegion(RegionEnum::Cyprus);
Authentication Flow:
AuthorizationService for OAuth2 token retrieval:
$auth = $this->authorizationService->authorize();
$token = $auth->getAccessToken(); // Cache this token (e.g., in Redis)
expires_in - 300 seconds.Pickup Point Integration:
$pickupPoints = $this->pickupPointService->getAllByRegion(RegionEnum::Greece);
$pickupPoints = $this->pickupPointService->getAll(token: $cachedToken);
PickupPointDTO properties (e.g., isActive, region) to filter results in your application logic.Event-Driven Updates:
pickup_points):
$points = $pickupPoints->getAllByRegion(RegionEnum::Croatia);
foreach ($points as $point) {
PickupPoint::updateOrCreate(
['boxnow_id' => $point->getId()],
$point->toArray()
);
}
PickupPointDTO into a table with fields like:
// Example migration
Schema::create('pickup_points', function (Blueprint $table) {
$table->id();
$table->string('boxnow_id')->unique();
$table->string('name');
$table->string('address');
$table->decimal('latitude', 10, 8);
$table->decimal('longitude', 11, 8);
$table->enum('region', ['Greece', 'Cyprus', 'Croatia', 'Bulgaria']);
$table->boolean('is_active');
$table->timestamps();
});
PickupPointService to throttle requests (e.g., 1 request/minute per region).$cacheKey = 'boxnow_pickup_points_' . RegionEnum::Cyprus->value;
$points = $this->cache->get($cacheKey, function () use ($pickupPoints) {
return $pickupPoints->getAllByRegion(RegionEnum::Cyprus);
});
Authentication Scope:
getAllByRegion method does not require authentication (since it uses the public locationapi endpoint). Avoid passing tokens unnecessarily.getAllByRegion may trigger API errors. Validate the response structure:
if ($pickupPoints->getAllByRegion(RegionEnum::Cyprus)->isEmpty()) {
throw new \RuntimeException("Invalid API response for Cyprus");
}
Region Coverage:
Greece, Cyprus, Croatia, Bulgaria). Attempting to use unsupported regions (e.g., RegionEnum::Spain) will return an empty array.Token Expiry:
expires_in seconds (typically 3600). Implement a refresh mechanism:
if ($auth->isExpired()) {
$auth = $this->authorizationService->authorize(); // Auto-refresh
}
Guzzle Timeouts:
answear_box_now:
guzzle_timeout: 10 # seconds
Enable Logging: Inject a custom logger to debug API responses:
answear_box_now:
logger: app.logger.boxnow
// In a controller/service
$this->logger->debug('BoxNow API Response', [
'data' => $pickupPoints->getAllByRegion(RegionEnum::Cyprus)
]);
HTTP Client Errors: Wrap API calls in a try-catch to handle Guzzle exceptions:
try {
$points = $pickupPoints->getAll(token: $token);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$response = $e->getResponse();
$this->logger->error('BoxNow API Error', [
'status' => $response->getStatusCode(),
'body' => $response->getBody()->getContents()
]);
throw new \RuntimeException("BoxNow API failed: " . $e->getMessage());
}
Custom DTO Mapping:
Extend PickupPointDTO to add business logic:
class ExtendedPickupPointDTO extends PickupPointDTO {
public function isOpenNow(): bool {
$hours = $this->getOpeningHours();
$now = new \DateTime();
return $hours->isOpenAt($now);
}
}
Service Decorator:
Decorate PickupPointService to add pre/post-processing:
class CachedPickupPointService implements PickupPointServiceInterface {
public function __construct(
private PickupPointService $decorated,
private CacheInterface $cache
) {}
public function getAllByRegion(RegionEnum $region): array {
$key = "boxnow_{$region->value}";
return $this->cache->get($key, fn() => $this->decorated->getAllByRegion($region));
}
}
Webhook Integration:
Use the AuthorizationResponse to validate webhook signatures (if BoxNow supports them). Example:
$auth = $this->authorizationService->authorize();
$signature = $auth->getTokenType() . $auth->getAccessToken();
if (!hash_equals($expectedSignature, $signature)) {
throw new \RuntimeException("Invalid BoxNow webhook signature");
}
https://locationapi-stage.boxnow.gr. For production, override it:
answear_box_now:
api_url: https://locationapi.boxnow.gr # Production endpoint
logger is not set, the bundle uses Symfony’s default logger. For silent failures, set:
answear_box_now:
logger: null
How can I help you explore Laravel packages today?