buendon/pwinty-bundle
Symfony bundle providing a service and OO API to interact with the Pwinty photo printing API (via php-pwinty fork). Configure merchant ID, API key, and sandbox/production endpoints, then create and submit orders from your Symfony app.
## Getting Started
### Minimal Setup
1. **Installation**:
- Add the custom repository and dependencies to `composer.json`:
```json
"repositories": [{
"type": "vcs",
"url": "https://github.com/Buendon/php-pwinty"
}],
"require": {
"pwinty/php-pwinty": "dev-api_2.3",
"buendon/pwinty-bundle": "dev-master"
}
```
- Run `composer update buendon/pwinty-bundle pwinty/php-pwinty`.
- Register the bundle in `config/bundles.php`:
```php
return [
// ...
Buendon\PwintyBundle\BuendonPwintyBundle::class => ['all' => true],
];
```
2. **Configuration**:
- Add Pwinty config to `config/packages/buendon_pwinty.yaml`:
```yaml
buendon_pwinty:
apiType: 'sandbox' # or 'production'
merchantId: '%env(PWINTY_MERCHANT_ID)%'
apiKey: '%env(PWINTY_API_KEY)%'
```
- Store credentials in `.env`:
```
PWINTY_MERCHANT_ID=your_merchant_id
PWINTY_API_KEY=your_api_key
```
3. **First Use Case**:
- Inject `PwintyService` into a controller/service and create an order:
```php
use Buendon\PwintyBundle\Service\PwintyService;
use Buendon\PwintyBundle\Order\Order;
public function createOrder(PwintyService $pwintyService) {
$order = new Order();
$order->setRecipientName("John Doe")
->setAddress1("123 Main St")
->setCountryCode("FR")
->setPayment(Order::PAYMENT_INVOICE_ME);
$response = $pwintyService->createOrder($order);
return $response->getId();
}
```
---
## Implementation Patterns
### Core Workflows
1. **Order Management**:
- **Create**: Use `PwintyService::createOrder(Order $order)`.
- **Update**: Modify order properties (e.g., `setRecipientName()`) and call `updateOrder($orderId, $order)`.
- **Submit**: Transition to `Order::UPDATE_STATUS_SUBMITTED` via `updateOrderStatus($orderId, $status)`.
2. **Photo Handling**:
- Add photos to an order with `addPhoto($orderId, Photo $photo)`.
- Configure `Photo` with type (e.g., `'9x12_cm'`), sizing (`SIZING_CROP`), and file path.
3. **Status Checks**:
- Fetch order details with `getOrder($orderId)`.
- Check submission status with `getOrderSubmissionStatus($orderId)`.
4. **Error Handling**:
- Wrap API calls in `try-catch` blocks for `OrderException`.
- Log exceptions for debugging:
```php
catch (OrderException $e) {
$this->logger->error('Pwinty API Error', ['error' => $e->getMessage()]);
}
```
### Integration Tips
- **Dependency Injection**:
- Prefer constructor injection for `PwintyService` in services/controllers.
- Example:
```php
public function __construct(private PwintyService $pwintyService) {}
```
- **Environment Awareness**:
- Use `sandbox` mode for development/testing to avoid real charges.
- Switch to `production` in staging/production via config.
- **Batch Processing**:
- For bulk orders, loop through entities and submit in batches to avoid API rate limits:
```php
foreach ($orders as $order) {
$pwintyService->createOrder($order);
sleep(1); // Respect API rate limits
}
```
- **Event-Driven Workflows**:
- Listen for order submission completion via webhooks (Pwinty API) and trigger Symfony events:
```php
// In a webhook handler
$event = new OrderSubmittedEvent($orderId);
$this->eventDispatcher->dispatch($event, OrderEvents::ORDER_SUBMITTED);
```
---
## Gotchas and Tips
### Pitfalls
1. **API Version Mismatch**:
- The bundle relies on a fork (`php-pwinty`) of the original Pwinty SDK. Ensure the fork is updated to match Pwinty API v2.3.
- **Fix**: Monitor the [php-pwinty repo](https://github.com/Buendon/php-pwinty) for updates or submit PRs to the fork.
2. **File Uploads**:
- Photos must be local files (not URLs). Use temporary files for uploaded content:
```php
$tempFile = tempnam(sys_get_temp_dir(), 'pwinty_');
file_put_contents($tempFile, $photoData);
$photo->setFile($tempFile);
// Clean up after submission
unlink($tempFile);
```
3. **Sandbox vs. Production**:
- Forgetting to switch `apiType` to `production` will cause orders to fail silently or be rejected.
- **Tip**: Use a feature flag or environment variable to toggle modes dynamically.
4. **Rate Limiting**:
- Pwinty API has strict rate limits (e.g., 10 requests/minute). Exceeding limits returns `429 Too Many Requests`.
- **Fix**: Implement exponential backoff in retries:
```php
$attempts = 0;
while ($attempts < 3) {
try {
$response = $pwintyService->createOrder($order);
break;
} catch (OrderException $e) {
if ($e->getCode() === 429) {
sleep(2 ** $attempts); // Exponential backoff
$attempts++;
} else {
throw $e;
}
}
}
```
5. **Order Validation**:
- Pwinty validates orders strictly (e.g., missing `countryCode` or invalid `payment` type).
- **Tip**: Validate locally before submission:
```php
if (!$order->getCountryCode() || !in_array($order->getPayment(), [Order::PAYMENT_INVOICE_ME, Order::PAYMENT_PREPAID])) {
throw new \InvalidArgumentException('Invalid order data');
}
```
### Debugging Tips
- **Enable API Debugging**:
- Set `PWINTY_DEBUG=true` in `.env` to log raw API responses:
```yaml
# config/packages/buendon_pwinty.yaml
buendon_pwinty:
debug: '%env(bool:PWINTY_DEBUG)%'
```
- **Common Errors**:
- **`401 Unauthorized`**: Invalid `merchantId` or `apiKey`. Double-check `.env`.
- **`400 Bad Request`**: Missing required fields (e.g., `recipientName`). Use `var_dump($order)` to verify.
- **`500 Internal Server Error`**: Pwinty API issue. Check [Pwinty Status](http://status.pwinty.com).
### Extension Points
1. **Custom Order Mappers**:
- Extend `Order` or `Photo` to add domain-specific fields:
```php
class CustomOrder extends Order {
public function setCustomField(string $value) {
$this->customData['custom_field'] = $value;
}
}
```
2. **Event Listeners**:
- Subscribe to `OrderEvents` to react to order lifecycle changes:
```php
// src/EventListener/PwintyListener.php
public static function getSubscribedEvents(): array {
return [
OrderEvents::ORDER_SUBMITTED => 'onOrderSubmitted',
];
}
```
3. **API Response Transformers**:
- Override response handling in a custom service:
```php
class CustomPwintyService extends PwintyService {
protected function transformResponse($response) {
// Add custom logic (e.g., currency conversion)
return parent::transformResponse($response);
}
}
```
Register it as a service alias in `services.yaml`:
```yaml
Buendon\PwintyBundle\Service\PwintyService: '@custom_pwinty_service'
```
4. **Testing**:
- Mock `PwintyService` in PHPUnit:
```php
$mockService = $this->createMock(PwintyService::class);
$mockService->method('createOrder')
->willReturn(new OrderResponse('12345'));
$this->container->set(PwintyService::NAME, $mockService);
```
How can I help you explore Laravel packages today?