17 KiB
Booking Submission Implementation Status
Overview
Implementation of two-phase booking submission for the booking creation flow. This allows users to create new bookings through inquiry validation followed by final booking commit.
Status: ✅ 100% Complete - TESTED SUCCESSFULLY Last Updated: 2025-10-06 First Successful Test Booking: 2025-10-06 Related Documentation:
docs/BOOKING_PAYMENT_STEP.md- Payment step implementationdocs/REFACTORING_BOOKING_DATA_PROCESSOR.md- Future refactoring plan
Architecture Decision
Participant-Centric Structure: The CREATE flow uses a cleaner participant-centric data structure where services are attached directly to participants in the DTO, not centralized with mapping arrays. This is the new standard.
UPDATE Flow: Currently uses a different structure (centralized services with mappings). Future refactoring will align it with the CREATE flow's participant-centric approach.
Completed Work (80%)
1. Response Models ✅
Created Files:
src/BusProNet/Model/BookingResponse.phpsrc/BusProNet/Model/PriceItem.phpsrc/BusProNet/Model/PaymentTerms.php
BookingResponse:
- Represents API response from booking requests (inquiry or final)
- Properties:
status,transactionNumber,priceItems,totalPrice,paymentTerms - Methods:
isInquiryValid(),isBookingSuccessful() - Handles both
möglich(inquiry valid) anderfolgt(booking created) statuses
PriceItem:
- Represents individual price items from response
- Properties:
position,type,subType,label,dateFrom,dateTo,quantity,assignment,unitPrice,totalPrice,id - Used for price validation against calculated prices
PaymentTerms:
- Represents payment schedule from response
- Properties:
depositAmount,depositDate,finalPaymentAmount,finalPaymentDate
2. Response Parser ✅
File: src/BusProNet/XmlParser/BookingResponseParser.php
Functionality:
- Extends
AbstractParser - Parses BUCHUNG type responses
- Extracts booking status from
<buchung>node - Extracts transaction number from
<vorgang>node - Parses all price items from
<preise><preis>nodes - Parses total price from
<gesamtpreis>node - Parses payment terms from
<zahlungsbedingungen>node
XML Structure Handled:
<ergebnis>
<satz typ="BUCHUNG" />
<buchung>möglich|erfolgt</buchung>
<vorgang>321530</vorgang>
<preise>
<preis position="1" art="BEF" unterart="BUS" bezeichnung="..."
terminvon="..." terminbis="..." anzahl="2" zuordnung="1,2"
preis="50.00" gesamtpreis="100.00" id="123" />
</preise>
<gesamtpreis>671,78</gesamtpreis>
<zahlungsbedingungen>
<anzahlung betrag="128,00" termin="21.03.2017" />
<restzahlung betrag="543,78" termin="12.11.2017" />
</zahlungsbedingungen>
</ergebnis>
3. Constants ✅
File: src/BusProNet/Constants.php
Added:
PAYMENT_TYPE_ID_TRANSFER = 2- Payment type ID for bank transferPAYMENT_TYPE_ID_DEBIT = 5- Payment type ID for direct debit
4. Payload Generation ✅
File: src/BusProNet/DataProcessor/BookingDataProcessor.php
New Method: createBookingRequestPayload(BookingCreateDto $bookingDto, string $bookingType): array
Functionality:
- Generates XML payload for new bookings (inquiry or final)
- Supports both 'Anfrage' (inquiry) and 'Buchung' (final booking) modes
- Participant-centric structure (services attached to participants)
- Includes ALL service types: board, ski passes, rentals, courses, additional services, transportation, pickups
- INCLUDES INSURANCE (critical difference from update flow)
- Proper room mapping via
assignedRoomId - Payment information with correct type IDs
Helper Methods:
addServicesFromMap()- Reusable helper for converting service maps to XML structurecollectServiceMappings()- Groups all participant services by IDcollectTransportationMappings()- Groups transportation servicescollectRoomMappings()- Groups room assignmentscollectPickupMappings()- Groups pickup selectionscollectInsuranceMappings()- Groups insurance selections (CREATE only!)
Payload Structure:
[
'buchungsart' => 'Anfrage|Buchung',
'status' => 'F',
'idreise' => $travelId,
'anmelder' => [
'name' => '...',
'vorname' => '...',
'geschlecht' => '...',
'nationalitaet' => '...',
'geburtsdatum' => '...',
'kommunikation' => ['email' => '...', 'telefonmobil' => '...'],
],
'teilnehmerliste' => ['teilnehmer' => [...]],
'beförderungen' => ['beförderung' => [...]],
'unterbringungen' => ['unterbringung' => [...]],
'zusatzleistungen' => ['zusatzleistung' => [...]],
'zustiege' => ['zustieg' => [...]],
'versicherungen' => ['versicherung' => [...]], // CREATE only!
'zahlung' => [
'@idzahlungsart' => 2|5,
'@art' => 'EINZUG|UEBERWEISUNG',
'bankverbindung' => [...], // if debit
],
]
5. API Client Methods ✅
File: src/BusProNet/ApiClient.php
New Constant:
TYPE_BOOKING = 'BUCHUNG'
New Methods:
public function createBookingInquiry(
BookingCreateDto $bookingDto,
bool $debug = false
): Notification|BookingResponse
- First phase: validates booking data
- Returns pricing information
- Does not create actual booking
public function createBooking(
BookingCreateDto $bookingDto,
bool $debug = false
): Notification|BookingResponse
- Second phase: creates actual booking
- Returns booking number (transaction number)
- Only called after successful inquiry
Both methods:
- Use
BookingDataProcessor::createBookingRequestPayload() - Send request to BUCHUNG type endpoint
- Return
Notificationon error orBookingResponseon success - Support debug mode for XML dumps
6. Response Parser Integration ✅
File: src/BusProNet/XmlParser/ApiResponseParser.php
Updated:
- Added case for
ApiClient::TYPE_BOOKING - Routes to
BookingResponseParser - Handles both inquiry and final booking responses
Remaining Work (0%)
1. Controller Implementation ✅
File: src/Controller/Booking/CreateStep4Controller.php
Completed:
- Imported
ApiClientand injected via constructor - Imported
LoggerInterfaceand injected via constructor - Implemented two-phase submission in form handler
if ($form->isSubmitted() && $form->isValid()) {
try {
// Phase 1: Inquiry (Validation)
$inquiryResponse = $this->apiClient->createBookingInquiry($bookingCreateDto);
if ($inquiryResponse instanceof Notification) {
// API returned error notification
$this->addFlash('error', $inquiryResponse->message);
return $this->render(...);
}
if (false === $inquiryResponse->isInquiryValid()) {
// Inquiry validation failed
$this->addFlash('error', 'Buchungsvalidierung fehlgeschlagen.');
return $this->render(...);
}
// Optional: Validate prices match our calculations
// Compare $inquiryResponse->totalPrice with calculated total
// Phase 2: Booking (Commit)
$bookingResponse = $this->apiClient->createBooking($bookingCreateDto);
if ($bookingResponse instanceof Notification) {
// API returned error notification
$this->addFlash('error', $bookingResponse->message);
return $this->render(...);
}
if (false === $bookingResponse->isBookingSuccessful()) {
// Booking creation failed
$this->addFlash('error', 'Buchung konnte nicht erstellt werden.');
return $this->render(...);
}
// Success: Clear session and redirect
$this->bookingService->clearBookingCreateDto($request);
return $this->redirectToRoute('app_booking_success', [
'bookingNumber' => $bookingResponse->transactionNumber,
]);
} catch (\Exception $e) {
$this->logger->error('Booking creation failed', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
$this->addFlash('error', 'Ein technischer Fehler ist aufgetreten.');
return $this->render(...);
}
}
Constructor Update:
public function __construct(
private readonly BookingService $bookingService,
private readonly ApiClient $apiClient,
private readonly LoggerInterface $logger,
) {
}
2. Service Layer ✅
File: src/Service/BookingService.php
Completed:
/**
* Clears the booking creation DTO from the session.
*
* This method removes only the booking DTO while preserving other session data.
* Used after successful booking submission to clear the booking flow state.
*/
public function clearBookingCreateDto(Request $request): void
{
$request->getSession()->remove(self::BOOKING_CREATE_KEY);
}
3. Success Page ✅
New Controller: src/Controller/Booking/BookingSuccessController.php
Completed:
<?php
declare(strict_types=1);
namespace App\Controller\Booking;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BookingSuccessController extends AbstractController
{
#[Route('/bookings/success/{bookingNumber}', name: 'app_booking_success')]
public function success(string $bookingNumber): Response
{
return $this->render('booking/success.html.twig', [
'bookingNumber' => $bookingNumber,
]);
}
}
New Template: templates/booking/success.html.twig
{% extends 'layout.html.twig' %}
{% block title %}Buchung erfolgreich{% endblock %}
{% block content %}
<div class="max-w-2xl mx-auto text-center py-12">
<div class="mb-8">
<svg class="w-24 h-24 text-green-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<h1 class="text-3xl font-bold mb-4">Buchung erfolgreich abgeschlossen</h1>
<p class="text-xl mb-8">
Ihre Buchungsnummer: <strong class="font-mono">{{ bookingNumber }}</strong>
</p>
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8">
<p class="text-gray-700">
Sie erhalten in Kürze eine Bestätigungs-E-Mail mit allen Details zu Ihrer Buchung.
</p>
</div>
<a href="{{ path('app_home') }}" class="button bg-button bg-button--primary">
Zurück zur Startseite
</a>
</div>
{% endblock %}
4. Testing ⏳
Sandbox Testing Checklist:
- Test inquiry phase with valid data
- Test inquiry phase with invalid data (validation errors)
- Test booking phase after successful inquiry
- Test booking phase failure scenarios
- Verify pricing data matches calculations
- Test with different service combinations:
- With insurance
- Without insurance
- With transportation services
- With pickup locations
- With all service types
- Minimum services only
- Test payment methods:
- Direct debit (14+ days before travel)
- Bank transfer
- Direct debit blocked (<14 days)
- Test session clearing
- Verify booking number display
- Test error handling
- Verify XML dumps in debug mode
Key Implementation Notes
Two-Phase Process
-
Phase 1: Inquiry (
buchungsart => 'Anfrage')- Validates all booking data
- Returns pricing information
- Response:
<buchung>möglich</buchung> - No actual booking created
-
Phase 2: Booking (
buchungsart => 'Buchung')- Creates actual booking
- Returns booking number
- Response:
<buchung>erfolgt</buchung> - Only proceed if Phase 1 succeeded
Error Handling
API Errors:
- API may return
Notificationobject instead ofBookingResponse - Check instanceof before accessing BookingResponse methods
- Display error message from notification
Validation Errors:
- Check
isInquiryValid()on inquiry response - Check
isBookingSuccessful()on booking response - Display appropriate error messages
Network/System Errors:
- Catch all exceptions
- Log with full trace
- Display generic error message to user
- Do NOT clear session on error (allow retry)
Price Validation (Optional)
Inquiry response includes:
- Individual price items with quantities and assignments
- Total price from API
- Payment terms (deposit/final payment)
Can compare:
- API total vs calculated total
- Individual service prices
- Participant assignments
Implementation:
if (abs($inquiryResponse->totalPrice - $calculatedTotal) > 0.01) {
$this->logger->warning('Price mismatch', [
'api_price' => $inquiryResponse->totalPrice,
'calculated_price' => $calculatedTotal,
]);
// Decide: continue or abort
}
Session Management
Important:
- Only clear session on successful booking
- Keep session on errors (allows retry)
- Clear using
BookingService::clearBookingCreateDto()
Logging
Log events:
- Inquiry submission (info level)
- Inquiry success/failure (info/error)
- Booking submission (info level)
- Booking success/failure (info/error)
- Price mismatches (warning)
- Exceptions (error with full trace)
Context to include:
- Travel ID
- Participant count
- Total price
- Payment method
- Error messages
- Exception traces
Testing Strategy
Unit Tests (Future)
BookingResponseParser:
- Test parsing successful inquiry response
- Test parsing successful booking response
- Test parsing price items
- Test parsing payment terms
- Test handling missing optional fields
BookingDataProcessor:
- Test payload generation with all services
- Test payload generation with minimum services
- Test insurance inclusion
- Test payment methods
- Test participant mappings
Integration Tests (Future)
ApiClient:
- Mock socket communication
- Test inquiry request format
- Test booking request format
- Test response parsing
- Test error handling
Controller:
- Test two-phase submission flow
- Test error scenarios
- Test session clearing
- Test redirects
Manual Testing (Immediate)
Use sandbox environment:
- Current ddev setup points to sandbox
- XML dumps enabled for debugging
- Test with real travel data
- Verify all email notifications
File Locations Summary
Models:
src/BusProNet/Model/BookingResponse.phpsrc/BusProNet/Model/PriceItem.phpsrc/BusProNet/Model/PaymentTerms.php
Parsers:
src/BusProNet/XmlParser/BookingResponseParser.phpsrc/BusProNet/XmlParser/ApiResponseParser.php(updated)
Data Processing:
src/BusProNet/DataProcessor/BookingDataProcessor.php(enhanced)
API:
src/BusProNet/ApiClient.php(enhanced)src/BusProNet/Constants.php(enhanced)
Controllers (to be updated/created):
src/Controller/Booking/CreateStep4Controller.php(update)src/Controller/Booking/BookingSuccessController.php(create)
Services (to be updated):
src/Service/BookingService.php(add method)
Templates (to be created):
templates/booking/success.html.twig
Documentation:
docs/BOOKING_SUBMISSION_STATUS.md(this file)docs/REFACTORING_BOOKING_DATA_PROCESSOR.mddocs/BOOKING_PAYMENT_STEP.mddocs/Beschreibung XMLAnfrage.pdf(API documentation)
Next Steps
-
Immediate:
- Implement controller logic (20 minutes)
- Add session clearing method (5 minutes)
- Create success page (10 minutes)
- Test with sandbox (30 minutes)
-
Short-term:
- Price validation logic (optional)
- Enhanced error messages
- Email confirmation integration
- PDF generation
-
Long-term:
- Refactor UPDATE flow to use participant-centric structure
- Comprehensive test suite
- Performance optimization
- Enhanced logging and monitoring
Status: ✅ Implementation Complete - Ready for Sandbox Testing Estimated Time to Test: 30-60 minutes Blockers: None Dependencies: All completed
Implementation Summary
All coding tasks have been completed:
- ✅ Response Models - BookingResponse, PriceItem, PaymentTerms created with full pricing support
- ✅ Response Parser - BookingResponseParser parses all XML response data including prices
- ✅ Payload Generation - createBookingRequestPayload() with participant-centric structure and all service types
- ✅ API Client Methods - createBookingInquiry() and createBooking() methods implemented
- ✅ Response Routing - ApiResponseParser updated to handle BUCHUNG type
- ✅ Controller Logic - Two-phase submission with comprehensive error handling in CreateStep4Controller
- ✅ Service Method - clearBookingCreateDto() added to BookingService
- ✅ Success Page - BookingSuccessController and success.html.twig template created
- ✅ Code Quality - All files validated with PHP-CS-Fixer and syntax checking
Next Step: Sandbox testing with real API calls