# Pickup Pricing - Implementation Documentation
## Current BusPro Behavior (January 2026)
BusPro currently only charges pickup price when the **outbound** transportation is BUS, regardless of the inbound selection.
### Pricing Matrix (Current)
| Outbound | Inbound | BusPro Charges | Notes |
|----------|---------|----------------|-------|
| BUS | BUS | Pickup price once | Full price from outbound |
| BUS | PKW | Pickup price once | Full price from outbound |
| PKW | BUS | **€0** | Known loophole in BusPro |
| PKW | PKW | €0 | No pickup available |
The "loophole" (PKW outbound + BUS inbound = no charge) exists on BusPro's side. Our portal must match this behavior to avoid price validation errors when submitting bookings.
## XML Data Structure
Both travel sections contain pickup data with prices:
```xml
```
Currently, prices are duplicated in both sections. In the future, when BusPro supports split pricing, travels may be configured with different prices per direction (e.g., 2.95 each way instead of 5.90 outbound only).
## Portal Implementation
### Forward-Compatible Data Layer
The portal parses and stores both direction prices for future use:
1. **Pickup Model** (`src/BusProNet/Model/Pickup.php`):
- `price`: The outbound price (used for pricing calculations)
- `priceOutbound`: Explicit outbound price (populated but not used yet)
- `priceInbound`: Explicit inbound price (populated but not used yet)
- `calculateEffectivePrice()`: Ready for future split pricing activation
2. **TravelParser** (`src/BusProNet/XmlParser/TravelParser.php`):
- Parses `zustiege` section and sets `priceOutbound`
- Parses `zustiege_rueck` section and sets `priceInbound`
- Merges inbound prices into outbound pickup objects by ID
3. **BookingDataProcessor** (`src/BusProNet/DataProcessor/BookingDataProcessor.php`):
- Enriches participant pickups with all price properties from travel data
### Current Pricing Logic
The pricing calculators use simple outbound-only logic to match BusPro:
```php
// Only charge if outbound is BUS - matches current BusPro behavior
$hasOutboundBus = null !== $participant->transportationOutbound
&& 'BUS' === $participant->transportationOutbound->subType;
if ($hasOutboundBus && null !== $participant->pickup && null !== $participant->pickup->price) {
$serviceTotal += $participant->pickup->price;
}
```
This ensures portal prices always match BusPro responses, avoiding validation errors.
## API Constraints
- Pickups can only be submitted via the `zustiege` (outbound) XML section
- There is no `zustiege_rueck` (inbound) equivalent for submission
- Booking responses return pickups in the outbound section only
- A future API update will support separate pickup/drop-off locations
## Future Activation (When BusPro Supports Split Pricing)
When BusPro is updated to charge based on actual transportation selections, update the pricing calculators to use `calculateEffectivePrice()`:
```php
$hasOutboundBus = null !== $participant->transportationOutbound
&& 'BUS' === $participant->transportationOutbound->subType;
$hasInboundBus = null !== $participant->transportationInbound
&& 'BUS' === $participant->transportationInbound->subType;
if (null !== $participant->pickup) {
$pickupPrice = $participant->pickup->calculateEffectivePrice($hasOutboundBus, $hasInboundBus);
if (null !== $pickupPrice) {
$serviceTotal += $pickupPrice;
}
}
```
### Important Caveat for Split Pricing Configuration
If travels are configured with split pricing (e.g., outbound=2.95, inbound=2.95) before BusPro supports it:
| Scenario | Portal Charges | BusPro Charges | Intended |
|----------|----------------|----------------|----------|
| BUS+BUS | 2.95 | 2.95 | 5.90 |
| BUS+PKW | 2.95 | 2.95 | 2.95 |
| PKW+BUS | 0.00 | 0.00 | 2.95 |
**Recommendation**: Keep full price on outbound (`preis="5,90"`) until BusPro supports split pricing, then reconfigure to true split values.
## Related Code Locations
- `src/BusProNet/Model/Pickup.php` - Pickup model with split pricing properties and calculation methods
- `src/BusProNet/XmlParser/TravelParser.php` - Parses and merges pickup prices from XML
- `src/BusProNet/DataProcessor/BookingDataProcessor.php` - Enriches pickups with travel data
- `src/BusProNet/DataProcessor/BookingPayloadBuilder.php` - Pickup submission
- `src/BusProNet/Model/Booking.php` - `getPickupForParticipant()` method
- `src/Form/Service/EditFieldStateProvider.php` - Pickup field visibility
- `src/Form/Service/CreateFieldStateProvider.php` - Pickup field visibility
- `src/Service/ParticipantPricingCalculator.php` - Pickup pricing logic (individual)
- `src/Service/ServicePricingCalculator.php` - Pickup pricing aggregation (summary)
## Test Scenarios
1. **BUS + BUS**: Charges `pickup->price` (outbound)
2. **BUS + PKW**: Charges `pickup->price` (outbound)
3. **PKW + BUS**: Charges €0 (matches BusPro loophole)
4. **PKW + PKW**: No pickup available
---
*Last updated: 2026-01-21*