diff --git a/README.md b/README.md index 479c3ecb..de460306 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,9 @@ HotelOfferSearch offer = amadeus.shopping.hotelOfferSearch("QF3MNOBDQ8").get(); // The body can be a String version of your JSON or a JsonObject HotelBooking[] hotel = amadeus.booking.hotelBookings.post(body); +// Hotel Booking v2 +HotelOrder hotel = amadeus.booking.hotelOrders.post(body); + // Hotel Ratings / Sentiments HotelSentiment[] hotelSentiments = amadeus.ereputation.hotelSentiments.get(Params.with("hotelIds", "ELONMFS,ADNYCCTB")); diff --git a/src/main/java/com/amadeus/Booking.java b/src/main/java/com/amadeus/Booking.java index a6d5c40d..5ea24d66 100644 --- a/src/main/java/com/amadeus/Booking.java +++ b/src/main/java/com/amadeus/Booking.java @@ -3,6 +3,7 @@ import com.amadeus.booking.FlightOrder; import com.amadeus.booking.FlightOrders; import com.amadeus.booking.HotelBookings; +import com.amadeus.booking.HotelOrders; public class Booking { private Amadeus client; @@ -31,6 +32,14 @@ public class Booking { */ public HotelBookings hotelBookings; + /** + *

+ * A namespaced client for the + * /v2/booking/hotelOrders endpoints. + *

+ */ + public HotelOrders hotelOrders; + /** * Constructor. * @hide @@ -39,6 +48,7 @@ public Booking(Amadeus client) { this.client = client; this.flightOrders = new FlightOrders(client); this.hotelBookings = new HotelBookings(client); + this.hotelOrders = new HotelOrders(client); } public FlightOrder flightOrder(String flightOrderId) { diff --git a/src/main/java/com/amadeus/booking/HotelOrders.java b/src/main/java/com/amadeus/booking/HotelOrders.java new file mode 100644 index 00000000..00b47060 --- /dev/null +++ b/src/main/java/com/amadeus/booking/HotelOrders.java @@ -0,0 +1,78 @@ +package com.amadeus.booking; + +import com.amadeus.Amadeus; +import com.amadeus.Response; +import com.amadeus.exceptions.ResponseException; +import com.amadeus.resources.HotelOrder; +import com.amadeus.resources.Resource; +import com.google.gson.JsonObject; + +/** + *

+ * A namespaced client for the + * /v2/booking/hotel-orders endpoints. + *

+ * + *

+ * Access via the Amadeus client object. + *

+ * + *
+ * Amadeus amadeus = Amadeus.builder(API_KEY, API_SECRET).build();
+ * amadeus.booking.HotelOrders;
+ */ +public class HotelOrders { + private Amadeus client; + + /** + * Constructor. + * + * @hide + */ + public HotelOrders(Amadeus client) { + this.client = client; + } + + /** + *

+ * The Hotel Booking API allows you to perform hotel booking. + *

+ * + *
+   * amadeus.booking.hotelOrders.post(body);
+ * + * @param body the parameters to send to the API as a JSonObject + * @return an API resource + * @throws ResponseException when an exception occurs + */ + public HotelOrder post(JsonObject body) throws ResponseException { + Response response = client.post("/v2/booking/hotel-orders", body); + return (HotelOrder) Resource.fromObject(response, HotelOrder.class); + } + + /** + *

+ * The Hotel Booking API allows you to perform hotel booking. + *

+ * + *
+   * amadeus.booking.hotelOrders.post(body);
+ * + * @param body the parameters to send to the API as a String + * @return an API resource + * @throws ResponseException when an exception occurs + */ + public HotelOrder post(String body) throws ResponseException { + Response response = client.post("/v2/booking/hotel-orders", body); + return (HotelOrder) Resource.fromObject(response, HotelOrder.class); + } + + /** + * Convenience method for calling post without any parameters. + * + * @see HotelBookings#post() + */ + public HotelOrder post() throws ResponseException { + return post((String) null); + } +} diff --git a/src/main/java/com/amadeus/resources/FlightOrder.java b/src/main/java/com/amadeus/resources/FlightOrder.java index ffff30d6..1caa6507 100644 --- a/src/main/java/com/amadeus/resources/FlightOrder.java +++ b/src/main/java/com/amadeus/resources/FlightOrder.java @@ -48,6 +48,7 @@ public static class Traveler { @AllArgsConstructor @NoArgsConstructor + @ToString public static class Name { diff --git a/src/main/java/com/amadeus/resources/Hotel.java b/src/main/java/com/amadeus/resources/Hotel.java index b46d2a99..b9e0fbe8 100644 --- a/src/main/java/com/amadeus/resources/Hotel.java +++ b/src/main/java/com/amadeus/resources/Hotel.java @@ -1,6 +1,5 @@ package com.amadeus.resources; -import com.google.gson.annotations.SerializedName; import lombok.Getter; import lombok.ToString; diff --git a/src/main/java/com/amadeus/resources/HotelOrder.java b/src/main/java/com/amadeus/resources/HotelOrder.java new file mode 100644 index 00000000..e5649e9f --- /dev/null +++ b/src/main/java/com/amadeus/resources/HotelOrder.java @@ -0,0 +1,91 @@ +package com.amadeus.resources; + +import com.amadeus.resources.HotelOfferSearch.Offer; +import lombok.Getter; +import lombok.ToString; + +/** + * An HotelOrder object as returned by the Hotel Booking v2 API. + * @see com.amadeus.booking.HotelOrders#get() + */ +@ToString +public class HotelOrder extends Resource { + private @Getter String self; + private @Getter String type; + private @Getter String id; + private @Getter HotelBooking[] hotelBookings; + private @Getter AssociatedRecord[] associatedRecords; + private @Getter Guest[] guests; + + protected HotelOrder() {} + + @ToString + public static class HotelBooking { + private @Getter String type; + private @Getter String id; + private @Getter String bookingStatus; + private @Getter RoomAssociation[] roomAssociations; + private @Getter Offer hotelOffer; + private @Getter Hotel hotel; + + protected HotelBooking() {} + } + + @ToString + public static class RoomAssociation { + private @Getter String hotelOfferId; + private GuestReference[] guestReferences; + private @Getter String specialRequest; + + protected RoomAssociation() {} + } + + @ToString + public static class GuestReference { + private @Getter String guestReference; + private @Getter String hotelLoyaltyId; + + protected GuestReference() {} + } + + @ToString + public static class Guest { + private @Getter int tid; + private @Getter int id; + private @Getter FrequentTraveler[] frequentTraveler; + private @Getter String phone; + private @Getter String email; + private @Getter String title; + private @Getter String firstName; + private @Getter String lastName; + private @Getter Integer childAge; + + protected Guest() {} + } + + @ToString + public static class FrequentTraveler { + private @Getter String airlineCode; + private @Getter String frequentTravelerId; + + protected FrequentTraveler() {} + } + + @ToString + public static class AssociatedRecord { + private @Getter String reference; + private @Getter String originSystemCode; + + protected AssociatedRecord() {} + } + + @ToString + public static class Hotel { + private @Getter String hotelId; + private @Getter String chainCode; + private @Getter String name; + private @Getter String self; + + protected Hotel() {} + } +} diff --git a/src/test/java/com/amadeus/NamespaceTest.java b/src/test/java/com/amadeus/NamespaceTest.java index be526366..fa01fb8d 100644 --- a/src/test/java/com/amadeus/NamespaceTest.java +++ b/src/test/java/com/amadeus/NamespaceTest.java @@ -10,6 +10,7 @@ import com.amadeus.booking.FlightOrder; import com.amadeus.booking.FlightOrders; import com.amadeus.booking.HotelBookings; +import com.amadeus.booking.HotelOrders; import com.amadeus.ereputation.HotelSentiments; import com.amadeus.exceptions.ResponseException; import com.amadeus.location.analytics.CategoryRatedAreas; @@ -27,6 +28,7 @@ import com.amadeus.referencedata.locations.hotels.ByGeocode; import com.amadeus.referencedata.locations.hotels.ByHotels; import com.amadeus.referencedata.urls.CheckinLinks; +import com.amadeus.resources.HotelOrder; import com.amadeus.resources.TransferCancellation; import com.amadeus.schedule.Flights; import com.amadeus.shopping.Activities; @@ -95,6 +97,7 @@ public class NamespaceTest { assertNotNull(client.airport.predictions.onTime); assertNotNull(client.booking.flightOrder("XXX")); assertNotNull(client.booking.hotelBookings); + assertNotNull(client.booking.hotelOrders); assertNotNull(client.schedule.flights); assertNotNull(client.travel.tripParser); assertNotNull(client.airport.directDestinations); @@ -674,6 +677,20 @@ public void testHotelBookings() throws ResponseException { assertNotNull(hotel.post(body)); } + @Test + public void testHotelOrders() throws ResponseException { + // Test Trip Parser + Mockito.when(client.post("/v2/booking/hotel-orders", (String) null)) + .thenReturn(singleResponse); + Mockito.when(client.post("/v2/booking/hotel-orders", body)) + .thenReturn(singleResponse); + Mockito.when(client.post("/v2/booking/hotel-orders", jsonObject)) + .thenReturn(singleResponse); + HotelOrders hotelOrder = new HotelOrders(client); + assertNotNull(hotelOrder.post()); + assertNotNull(hotelOrder.post(body)); + } + @Test public void testTripParser() throws ResponseException { // Test Trip Parser diff --git a/src/test/java/com/amadeus/booking/HotelOrdersIT.java b/src/test/java/com/amadeus/booking/HotelOrdersIT.java new file mode 100644 index 00000000..25c6aa3f --- /dev/null +++ b/src/test/java/com/amadeus/booking/HotelOrdersIT.java @@ -0,0 +1,99 @@ +package com.amadeus.booking; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.amadeus.Amadeus; +import com.amadeus.exceptions.ResponseException; +import com.amadeus.resources.HotelOrder; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +// https://developers.amadeus.com/self-service/category/hotels/api-doc/hotel-booking +public class HotelOrdersIT { + + WireMockServer wireMockServer; + + private Amadeus amadeus; + + /** + * In every tests, we will authenticate. + */ + @BeforeEach + public void setup() { + wireMockServer = new WireMockServer(8080); + wireMockServer.start(); + + // API at https://developers.amadeus.com/self-service/apis-docs/guides/authorization-262 + String address = "/v1/security/oauth2/token" + + "?grant_type=client_credentials&client_secret=DEMO&client_id=DEMO"; + wireMockServer.stubFor(post(urlEqualTo(address)) + .willReturn(aResponse().withHeader("Content-Type", "application/json") + .withStatus(200) + .withBodyFile("auth_ok.json"))); + + amadeus = Amadeus + .builder("DEMO", "DEMO") + .setHost("localhost") + .setPort(8080) + .setSsl(false) + .setLogLevel("debug") + .build(); + } + + @AfterEach + public void teardown() { + wireMockServer.stop(); + } + + @Test + public void given_client_when_call_hotel_orders_with_params_then_ok() + throws ResponseException, IOException { + + // Given + String address = "/v2/booking/hotel-orders"; + + wireMockServer.stubFor(post(urlEqualTo(address)) + .willReturn(aResponse().withHeader("Content-Type", "application/json") + .withStatus(200) + .withBodyFile("hotel_orders_response_ok.json"))); + + + JsonObject request = getRequestFromResources("hotel_orders_request_ok.json"); + + // When + HotelOrder result = amadeus.booking.hotelOrders.post(request); + + // Then + assertNotNull(result); + } + + private JsonObject getRequestFromResources(String jsonFile) throws IOException { + + final String folder = "__files/"; + + ClassLoader classLoader = getClass().getClassLoader(); + File file = new File(classLoader.getResource(folder + jsonFile).getFile()); + String jsonString = new String(Files.readAllBytes(file.toPath())); + + return new JsonParser().parse(jsonString).getAsJsonObject(); + } + + private File getFileRequestFromResources(String jsonFile) throws IOException { + + final String folder = "__files/"; + + ClassLoader classLoader = getClass().getClassLoader(); + return new File(classLoader.getResource(folder + jsonFile).getFile()); + } + +} diff --git a/src/test/resources/__files/hotel_orders_request_ok.json b/src/test/resources/__files/hotel_orders_request_ok.json new file mode 100644 index 00000000..3879e0dd --- /dev/null +++ b/src/test/resources/__files/hotel_orders_request_ok.json @@ -0,0 +1,41 @@ +{ + "data": { + "type": "hotel-order", + "guests": [ + { + "tid": 1, + "title": "MR", + "firstName": "BOB", + "lastName": "SMITH", + "phone": "+33679278416", + "email": "bob.smith@email.com" + } + ], + "travelAgent": { + "contact": { + "email": "bob.smith@email.com" + } + }, + "roomAssociations": [ + { + "guestReferences": [ + { + "guestReference": "1" + } + ], + "hotelOfferId": "4L8PRJPEN7" + } + ], + "payment": { + "method": "CREDIT_CARD", + "paymentCard": { + "paymentCardInfo": { + "vendorCode": "VI", + "cardNumber": "4151289722471370", + "expiryDate": "2026-08", + "holderName": "BOB SMITH" + } + } + } + } + } \ No newline at end of file diff --git a/src/test/resources/__files/hotel_orders_response_ok.json b/src/test/resources/__files/hotel_orders_response_ok.json new file mode 100644 index 00000000..99a2f84d --- /dev/null +++ b/src/test/resources/__files/hotel_orders_response_ok.json @@ -0,0 +1,119 @@ +{ + "data": { + "type": "hotel-order", + "id": "V0g2VFJaLzIwMjQtMDYtMDc=", + "hotelBookings": [ + { + "type": "hotel-booking", + "id": "MS84OTkyMjcxMC85MDIyNDU0OQ==", + "bookingStatus": "CONFIRMED", + "hotelProviderInformation": [ + { + "hotelProviderCode": "AR", + "confirmationNumber": "89922710" + } + ], + "roomAssociations": [ + { + "hotelOfferId": "******", + "guestReferences": [ + { + "guestReference": "1" + } + ] + } + ], + "hotelOffer": { + "id": "******", + "type": "hotel-offer", + "category": "TYPE_CONDITIONAL", + "checkInDate": "2024-06-07", + "checkOutDate": "2024-06-08", + "guests": { + "adults": 1 + }, + "policies": { + "cancellations": [ + { + "amount": "215.05", + "deadline": "2024-06-06T23:59:00+02:00" + } + ], + "paymentType": "GUARANTEE" + }, + "price": { + "base": "195.50", + "currency": "EUR", + "sellingTotal": "215.05", + "taxes": [ + { + "amount": "19.55", + "code": "VALUE_ADDED_TAX", + "currency": "EUR", + "included": false, + "pricingFrequency": "PER_STAY", + "pricingMode": "PER_PRODUCT" + } + ], + "total": "215.05", + "variations": { + "changes": [ + { + "endDate": "2024-06-08", + "startDate": "2024-06-07", + "base": "195.50", + "currency": "EUR" + } + ] + } + }, + "rateCode": "S9R", + "room": { + "description": { + "lang": "EN", + "text": "Marriott Senior Discount, includes" + }, + "type": "XMI" + }, + "roomQuantity": 1 + }, + "hotel": { + "hotelId": "ARMADAIT", + "chainCode": "AR", + "name": "AC BY MARRIOTT HOTEL AITANA", + "self": "https://test.travel.api.amadeus.com/v1/reference-data/locations/by-hotel/ARMADAIT" + }, + "payment": { + "method": "CREDIT_CARD", + "paymentCard": { + "paymentCardInfo": { + "vendorCode": "VI", + "cardNumber": "415128XXXXXX1370", + "expiryDate": "0826", + "holderName": "BOB SMITH" + } + } + }, + "travelAgentId": "00000000" + } + ], + "guests": [ + { + "tid": 1, + "id": 1, + "title": "MR", + "firstName": "BOB", + "lastName": "SMITH", + "phone": "+33679278416", + "email": "bob.smith@email.com" + } + ], + "associatedRecords": [ + { + "reference": "WH6TRZ", + "originSystemCode": "GDS" + } + ], + "self": "http://test.api.amadeus.com/v2/booking/hotel-orders/V0g2VFJaLzIwMjQtMDYtMDc=" + } + } \ No newline at end of file