From 2b8046b41692afec7958213182da59d512a7bdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 10 Dec 2025 09:29:19 +0100 Subject: [PATCH] feat: re-implement contact form submissions api endpoint for website --- api.http | 16 ++++++ http-client.env.json | 12 ++-- src/BusProNet/ApiClient.php | 20 +++++++ src/BusProNet/Model/ContactFormResponse.php | 15 +++++ src/BusProNet/Model/ContactFormSubmission.php | 17 ++++++ src/BusProNet/Model/PersonalData.php | 19 +++++++ src/BusProNet/XmlLoader/HotelLoader.php | 3 + src/BusProNet/XmlParser/ApiResponseParser.php | 2 + .../XmlParser/ContactFormResponseParser.php | 24 ++++++++ src/Controller/Api/ContactFormController.php | 55 +++++++++++++++++++ src/Controller/Api/HotelController.php | 9 +-- 11 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 src/BusProNet/Model/ContactFormResponse.php create mode 100644 src/BusProNet/Model/ContactFormSubmission.php create mode 100644 src/BusProNet/XmlParser/ContactFormResponseParser.php create mode 100644 src/Controller/Api/ContactFormController.php diff --git a/api.http b/api.http index bbd5b2e..0fa072a 100644 --- a/api.http +++ b/api.http @@ -68,3 +68,19 @@ GET {{base_url}}/api/products Accept: application/json Authorization: Bearer {{$auth.token("oauth2_api")}} +### API contact form +POST {{base_url}}/api/contactform +Accept: application/json +Content-Type: application/json +Authorization: Bearer {{$auth.token("oauth2_api")}} + +{ + "lastName": "Duschen", + "firstName": "Isolde", + "gender": "f", + "street": "Fooroad 1", + "zipCode": "12345", + "city": "Bartown", + "email": "foo@example.com", + "phone": "12345" +} diff --git a/http-client.env.json b/http-client.env.json index 051e049..e38eec1 100644 --- a/http-client.env.json +++ b/http-client.env.json @@ -7,8 +7,8 @@ "Grant Type": "Authorization Code", "Client ID": "{{oauth2_client_id}}", "Client Secret": "{{oauth2_client_secret}}", - "Auth URL": "https://myep-next.ddev.site/authorize", - "Token URL": "https://myep-next.ddev.site/token", + "Auth URL": "https://myep-next-booking.ddev.site/authorize", + "Token URL": "https://myep-next-booking.ddev.site/token", "Redirect URL": "https://myep-team.ddev.site/auth/check", "Scope": "email" }, @@ -17,8 +17,8 @@ "Grant Type": "Authorization Code", "Client ID": "{{oauth2_client_id}}", "Client Secret": "{{oauth2_client_secret}}", - "Auth URL": "https://myep-next.ddev.site/authorize", - "Token URL": "https://myep-next.ddev.site/token", + "Auth URL": "https://myep-next-booking.ddev.site/authorize", + "Token URL": "https://myep-next-booking.ddev.site/token", "Redirect URL": "https://myep-team.ddev.site/auth/check", "Scope": "email profile" }, @@ -27,8 +27,8 @@ "Grant Type": "Client Credentials", "Client ID": "{{oauth2_api_client_id}}", "Client Secret": "{{oauth2_api_client_secret}}", - "Auth URL": "https://myep-next.ddev.site/authorize", - "Token URL": "https://myep-next.ddev.site/token", + "Auth URL": "https://myep-next-booking.ddev.site/authorize", + "Token URL": "https://myep-next-booking.ddev.site/token", "Scope": "api" } } diff --git a/src/BusProNet/ApiClient.php b/src/BusProNet/ApiClient.php index 53839de..756d008 100644 --- a/src/BusProNet/ApiClient.php +++ b/src/BusProNet/ApiClient.php @@ -9,6 +9,7 @@ use App\BusProNet\Model\BaseData; use App\BusProNet\Model\Booking; use App\BusProNet\Model\BookingResponse; use App\BusProNet\Model\BookingUpdate; +use App\BusProNet\Model\ContactFormResponse; use App\BusProNet\Model\CrmAttributes; use App\BusProNet\Model\Notification; use App\BusProNet\Model\PersonalData; @@ -137,6 +138,25 @@ class ApiClient return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data, [], $debug); } + /** + * @throws ApiClientException + */ + public function createAddress( + PersonalData $personalData, + bool $debug = false, + ): ContactFormResponse|Notification { + $data = [ + 'user' => $this->config['bpn_username'], + 'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA), + 'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA], + 'art' => 'Adresse_Neu', + 'adressdaten' => $personalData->toPayload(), + 'ohnemailversand' => 'True', + ]; + + return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data, [], $debug); + } + /** * @throws ApiClientException */ diff --git a/src/BusProNet/Model/ContactFormResponse.php b/src/BusProNet/Model/ContactFormResponse.php new file mode 100644 index 0000000..b62e158 --- /dev/null +++ b/src/BusProNet/Model/ContactFormResponse.php @@ -0,0 +1,15 @@ +gender ? strtoupper($dto->gender) : null; + if ('F' === $gender) { + $gender = 'W'; + } + $instance = new self(); + $instance->name = $dto->lastName; + $instance->firstName = $dto->firstName; + $instance->gender = $gender; + $instance->address->street = $dto->street; + $instance->address->postCode = $dto->zipCode; + $instance->address->city = $dto->city; + $instance->communication->email = $dto->email; + $instance->communication->phone = $dto->phone; + + return $instance; + } + /** * Extracts user claims for authentication and profile information. * diff --git a/src/BusProNet/XmlLoader/HotelLoader.php b/src/BusProNet/XmlLoader/HotelLoader.php index 3ad1932..dcae452 100644 --- a/src/BusProNet/XmlLoader/HotelLoader.php +++ b/src/BusProNet/XmlLoader/HotelLoader.php @@ -48,6 +48,9 @@ class HotelLoader extends AbstractLoader return null; } + /** + * @throws HotelNotFoundException + */ public function loadById(int $id, ?string $filename = 'hotel.xml'): Hotel { $hotels = $this->loadAll($filename); diff --git a/src/BusProNet/XmlParser/ApiResponseParser.php b/src/BusProNet/XmlParser/ApiResponseParser.php index 220bb76..6bd5869 100644 --- a/src/BusProNet/XmlParser/ApiResponseParser.php +++ b/src/BusProNet/XmlParser/ApiResponseParser.php @@ -36,6 +36,8 @@ class ApiResponseParser extends AbstractParser case 'Adressdaten_Ändern': case 'Newsletter': return (new PersonalDataParser())->parse($resultNode); + case 'Adresse_Neu': + return (new ContactFormResponseParser())->parse($resultNode); case 'SelektionCRM': case 'SelektionCRM_Ändern': return (new CrmAttributesResponseParser())->parse($resultNode); diff --git a/src/BusProNet/XmlParser/ContactFormResponseParser.php b/src/BusProNet/XmlParser/ContactFormResponseParser.php new file mode 100644 index 0000000..e67bacd --- /dev/null +++ b/src/BusProNet/XmlParser/ContactFormResponseParser.php @@ -0,0 +1,24 @@ +filterXPath('//idadresse')->text(); + $personId = (int) $node->filterXPath('//idperson')->text(); + $isNewRecord = $this->getBoolValue($node->filterXPath('//neuanlage')); + + return new ContactFormResponse( + addressId: $addressId, + personId: $personId, + isNewRecord: $isNewRecord, + ); + } +} diff --git a/src/Controller/Api/ContactFormController.php b/src/Controller/Api/ContactFormController.php new file mode 100644 index 0000000..566c07c --- /dev/null +++ b/src/Controller/Api/ContactFormController.php @@ -0,0 +1,55 @@ +apiClient->createAddress($personalData); + + if ($result instanceof Notification) { + return new JsonResponse([ + 'success' => false, + 'message' => $result->message, + ], Response::HTTP_CONFLICT); + } + + return $this->json([ + 'success' => true, + 'addressId' => $result->addressId, + 'personId' => $result->personId, + 'isNewRecord' => $result->isNewRecord, + ], Response::HTTP_CREATED); + } catch (ApiClientException $e) { + return new JsonResponse([ + 'success' => false, + 'message' => $e->getMessage(), + ], Response::HTTP_BAD_REQUEST); + } + } +} diff --git a/src/Controller/Api/HotelController.php b/src/Controller/Api/HotelController.php index 4907c10..1e73e58 100644 --- a/src/Controller/Api/HotelController.php +++ b/src/Controller/Api/HotelController.php @@ -3,6 +3,7 @@ namespace App\Controller\Api; use App\BusProNet\XmlLoader\HotelLoader; +use App\Exception\HotelNotFoundException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -28,12 +29,12 @@ class HotelController extends AbstractController #[Route('/hotels/{id}', name: 'api_hotels_single')] public function single(int $id): JsonResponse { - $hotel = $this->xmlLoader->loadById($id); + try { + $hotel = $this->xmlLoader->loadById($id); - if (null === $hotel) { + return $this->json($hotel, Response::HTTP_OK, [], ['groups' => 'api:single']); + } catch (HotelNotFoundException $e) { return new JsonResponse(['message' => 'Not found'], Response::HTTP_NOT_FOUND); } - - return $this->json($hotel, Response::HTTP_OK, [], ['groups' => 'api:single']); } }