refactor: split buspro ApiClient into domain clients

This commit is contained in:
fromme committed 2026-10-02 13:55:30 +02:00
1 parent 0a55544ca8
commit b755a6f43e
52 files changed
+805 -695

No files matched your search

+119
View File
@@ -0,0 +1,119 @@
<?php
namespace App\BusProNet\Client;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\MessageType;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\PersonalDataUpdateResponse;
use App\BusProNet\Model\RegistrationResponse;
use App\BusProNet\Transport\XmlChannel;
use App\Form\Model\RegistrationDto;
/**
* A customer's BusPro account: login, address data, newsletter, CRM selections.
*/
class AccountClient
{
public function __construct(
private readonly XmlChannel $channel,
) {
}
/**
* @throws ApiClientException
*/
public function getPersonalData(string $email, string $password): Notification|PersonalData
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Adressdaten',
'email' => $email,
'passwort' => $password,
]);
}
/**
* @throws ApiClientException
*/
public function register(RegistrationDto $registrationData): Notification|RegistrationResponse
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Adresse_Neu',
'adressdaten' => $registrationData->toPayload(),
]);
}
/**
* @throws ApiClientException
*/
public function resetPassword(string $email): Notification
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Passwort_Anfrage',
'email' => $email,
]);
}
/**
* @throws ApiClientException
*/
public function updatePersonalData(
string $email,
string $password,
PersonalData $personalData,
bool $debug = false,
): Notification|PersonalDataUpdateResponse {
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Adressdaten_Ändern',
'email' => $email,
'passwort' => $password,
'idadresse' => $personalData->addressId,
'adressdaten' => $personalData->toPayload(),
], [], $debug);
}
/**
* @throws ApiClientException
*/
public function createAddress(
PersonalData $personalData,
bool $debug = false,
): RegistrationResponse|Notification {
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Adresse_Neu',
'adressdaten' => $personalData->toPayload(false),
'ohnemailversand' => 'True',
], [], $debug);
}
/**
* @throws ApiClientException
*/
public function updateNewsletterRegistration(string $email, string $password, PersonalData $personalData): Notification|PersonalData
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Newsletter',
'email' => $email,
'passwort' => $password,
'idadresse' => $personalData->addressId,
'newsletter' => [
'email' => $email,
'anmeldung' => $personalData->communication->newsletter ? 'True' : 'False',
],
]);
}
/**
* @throws ApiClientException
*/
public function getCrmAttributes(string $email, string $password): Notification|CrmAttributes
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'SelektionCRM',
'email' => $email,
'passwort' => $password,
]);
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace App\BusProNet\Client;
use App\BusProNet\Constants;
use App\BusProNet\DataProcessor\BookingDataProcessor;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\MessageType;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\BookingResponse;
use App\BusProNet\Model\BookingUpdate;
use App\BusProNet\Model\Notification;
use App\BusProNet\Transport\XmlChannel;
use App\Form\Model\BookingDto;
/**
* A customer's bookings: listing, details and documents, creating and changing them.
*/
class BookingClient
{
public function __construct(
private readonly XmlChannel $channel,
private readonly BookingDataProcessor $bookingDataProcessor,
) {
}
/**
* @throws ApiClientException
*/
public function getBookings(string $email, string $password): Notification|BaseData
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Vorgänge',
'email' => $email,
'passwort' => $password,
]);
}
/**
* @throws ApiClientException
*/
public function getBooking(string $email, string $password, int $id): Notification|Booking
{
return $this->channel->request(MessageType::CustomerData, [
'art' => 'Vorgang_Details',
'email' => $email,
'passwort' => $password,
'idbuchung' => $id,
]);
}
/**
* @throws ApiClientException
*/
public function getDocuments(string $email, string $password, int $bookingId, string $type): mixed
{
return $this->channel->request(MessageType::CustomerData, [
'art' => $type,
'email' => $email,
'passwort' => $password,
'idbuchung' => $bookingId,
]);
}
/**
* @throws ApiClientException
*/
public function updateBooking(BookingDto $formData, bool $debug = false): Notification|BookingUpdate
{
return $this->channel->request(MessageType::BookingUpdate, [
'buchungsart' => Constants::BOOKING_TYPE_BOOKING,
...$this->bookingDataProcessor->createUpdateRequestPayload($formData),
], [], $debug);
}
/**
* First phase of the two-phase booking process: validates the booking and returns the
* prices without creating it.
*
* @throws ApiClientException
*/
public function createBookingInquiry(BookingDto $bookingDto, bool $debug = false): Notification|BookingResponse
{
return $this->sendBooking($bookingDto, Constants::BOOKING_TYPE_INQUIRY, $debug);
}
/**
* Second phase of the two-phase booking process: creates the booking after a successful
* inquiry.
*
* @throws ApiClientException
*/
public function createBooking(BookingDto $bookingDto, bool $debug = false): Notification|BookingResponse
{
return $this->sendBooking($bookingDto, Constants::BOOKING_TYPE_BOOKING, $debug);
}
/**
* @throws ApiClientException
*/
private function sendBooking(BookingDto $bookingDto, string $bookingType, bool $debug): Notification|BookingResponse
{
return $this->channel->request(MessageType::Booking, $this->bookingDataProcessor->createBookingRequestPayload($bookingDto, $bookingType), [], $debug);
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\BusProNet\Client;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\MessageType;
use App\BusProNet\Model\Agency;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Notification;
use App\BusProNet\Transport\XmlChannel;
/**
* Lists that change rarely and that callers cache for long periods.
*/
class ReferenceDataClient
{
public function __construct(
private readonly XmlChannel $channel,
) {
}
/**
* @return Agency[]|Notification
*
* @throws ApiClientException
*/
public function getAgencies(): array|Notification
{
return $this->channel->request(MessageType::Agencies);
}
/**
* @throws ApiClientException
*/
public function getBaseData(MessageType $type): Notification|BaseData
{
return $this->channel->request($type);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\BusProNet\Client;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\MessageType;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\ExtendedServiceAvailabilityResponse;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\ServiceAvailabilityResponse;
use App\BusProNet\Model\Travel;
use App\BusProNet\Transport\XmlChannel;
/**
* Travels from the live API. Most reads belong in TravelDataProvider, which arbitrates
* between this, the XML export and the snapshots.
*/
class TravelClient
{
public function __construct(
private readonly XmlChannel $channel,
) {
}
/**
* @throws ApiClientException
*/
public function getTravelData(int $travelId, ?int $hotelId = null): Notification|Travel
{
return $this->channel->request(MessageType::ProductData, ['idprodukt' => $travelId], ['hotelId' => $hotelId]);
}
/**
* Returns the unparsed response, which has the structure of BusPro's XML export and is
* stored in the export directory as is.
*
* @throws ApiClientException
*/
public function getTravelDataXml(int $travelId): string
{
return $this->channel->requestRaw(MessageType::ProductData, ['idprodukt' => $travelId]);
}
/**
* @throws ApiClientException
*/
public function getProducts(): Notification|BaseData
{
return $this->channel->request(MessageType::Products);
}
/**
* @throws ApiClientException
*/
public function getMutableData(int $dateId): Notification|BaseData
{
return $this->channel->request(MessageType::MutableData, ['idreise' => $dateId]);
}
/**
* @throws ApiClientException
*/
public function getAvailabilities(int $dateId): Notification|ServiceAvailabilityResponse
{
return $this->channel->request(MessageType::Availability, ['idreise' => $dateId]);
}
/**
* @throws ApiClientException
*/
public function getAvailabilitiesExtended(int $dateId): Notification|ExtendedServiceAvailabilityResponse
{
return $this->channel->request(MessageType::AvailabilityExtended, ['idreise' => $dateId]);
}
/**
* @throws ApiClientException
*/
public function getHotelAvailability(int $dateId, int $hotelId, \DateTimeInterface $dateTo): Notification|BaseData
{
return $this->channel->request(MessageType::AvailabilityHotel, [
'idreise' => $dateId,
'idpartner' => $hotelId,
'terminbis' => $dateTo->format('d.m.Y'),
]);
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace App\BusProNet\Client;
use App\BusProNet\Constants;
use App\BusProNet\DataProcessor\VoucherSalePayloadBuilder;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\MessageType;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PromoVoucher;
use App\BusProNet\Model\PurchaseVoucher;
use App\BusProNet\Model\VoucherPaymentMethod;
use App\BusProNet\Model\VoucherPreview;
use App\BusProNet\Model\VoucherSaleResponse;
use App\BusProNet\Model\VoucherTemplates;
use App\BusProNet\Transport\XmlChannel;
use App\Form\Model\VoucherPurchaseDto;
/**
* Redeeming vouchers in a booking, and selling purchase vouchers (Kaufgutscheine).
*/
class VoucherClient
{
public function __construct(
private readonly XmlChannel $channel,
private readonly VoucherSalePayloadBuilder $voucherSalePayloadBuilder,
) {
}
/**
* Looks up a purchase voucher (Gutschein) by its redemption code. Purchase vouchers, regular
* or goodwill (Kulanz), carry a balance that each redemption reduces.
*
* @throws ApiClientException
*/
public function validatePurchaseVoucher(string $redemptionCode): Notification|PurchaseVoucher
{
return $this->channel->request(MessageType::PurchaseVoucher, ['einloesecode' => $redemptionCode]);
}
/**
* Validates a promo voucher (Aktionsgutschein) for a travel and participant price. Promo
* vouchers are absolute discounts, per person ("P") or per booking ("B") as pro_buchung_person
* says.
*
* @throws ApiClientException
*/
public function validatePromoVoucher(string $promoCode, int $travelId, float $participantPrice): Notification|PromoVoucher
{
return $this->channel->request(MessageType::PromoVoucher, [
'aktionsgutschein' => $promoCode,
'idreise' => $travelId,
'preis' => number_format($participantPrice, 2, ',', ''),
]);
}
/**
* Fetches the voucher designs on sale and the amount bounds.
*
* @throws ApiClientException
*/
public function getVoucherTemplates(): Notification|VoucherTemplates
{
return $this->channel->request(MessageType::VoucherTemplates);
}
/**
* Fetches the payment methods offered for voucher purchases.
*
* @return list<VoucherPaymentMethod>|Notification
*
* @throws ApiClientException
*/
public function getVoucherPaymentMethods(): array|Notification
{
return $this->channel->request(MessageType::VoucherPaymentMethods);
}
/**
* Renders the PDF preview of a voucher design with the entered data.
*
* @throws ApiClientException
*/
public function getVoucherPreview(VoucherPurchaseDto $voucherPurchaseDto): Notification|VoucherPreview
{
return $this->channel->request(MessageType::VoucherPreview, $this->voucherSalePayloadBuilder->buildPreviewPayload($voucherPurchaseDto));
}
/**
* Dry run of a voucher purchase: full validation, nothing is issued.
*
* Every HINWEIS is a rejection on this path, including 650 (missing address data), so the
* result must not be judged by Notification::isError().
*
* @throws ApiClientException
*/
public function createVoucherSaleInquiry(VoucherPurchaseDto $voucherPurchaseDto): Notification|VoucherSaleResponse
{
return $this->sendVoucherSale($voucherPurchaseDto, Constants::BOOKING_TYPE_INQUIRY);
}
/**
* Buys the voucher. BusPro mails it to the buyer once the payment has arrived.
*
* Every HINWEIS is a rejection on this path, including 650.
*
* @throws ApiClientException
*/
public function createVoucherSale(VoucherPurchaseDto $voucherPurchaseDto): Notification|VoucherSaleResponse
{
return $this->sendVoucherSale($voucherPurchaseDto, Constants::BOOKING_TYPE_BOOKING);
}
/**
* @throws ApiClientException
*/
private function sendVoucherSale(VoucherPurchaseDto $voucherPurchaseDto, string $saleType): Notification|VoucherSaleResponse
{
return $this->channel->request(MessageType::VoucherSale, $this->voucherSalePayloadBuilder->buildSalePayload($voucherPurchaseDto, $saleType));
}
}