Implementing 2FA: Step-by-Step
If you are unfamiliar with the background of the various initiatives that are requiring 2 Factor Authentication (2FA) in Expedia API booking transactions, please refer to the Overview article first, and also the "Implementing 2FA: The Process" article, which introduces the components that are required to complete the 2FA process.
This article provides partner software developers with the code samples required to implement Two Factor Authentication (2FA) that is compliant with both PSD2 and 3DSv2 requirements.
API Booking Without 3DS Enabled
If 3DS is not required to complete the booking transaction, then the booking will proceed as it does today.
With this in mind, partners who do not anticipate a large number of their transactions to require 2FA may choose not to implement 3DS and continue booking with the XAP Booking APIs. If this describes your situation you must be aware that — in the event that 3DS is required to complete a particular transaction — that transaction will fail and there will be no way for you to complete the transaction via the API.
When a transaction requires 3DS and you do not include the BrowserData Node in your XAP Booking API request (more info below), then you will receive the following error message:
Error Example – Invalid Browser Data (due to no 3DS BrowserData Node included)
{"Errors":[{"Code":"INVALID_BROWSER_DATA","Description":"Invalid Input. Please enter the full BrowserData"}],"TransactionId":"50114033-f9db-4d2f-82ab-4db7e6935828"}
If you receive the error message above in response to an XAP Booking API request, there are two options to proceed:
- Set up a 3DS session and then use the returned BrowserNode data to re-run the booking process.
- Return a message to the user to let them know that you will not be able to complete the transaction using this credit card.
The rest of this article outlines the process of performing a 3DS booking with the 3DS Session initialized at the start of the process, which is the recommended approach for partners to implement 2FA.
Step 1: Add iFrame to Checkout page
The first step to enable 3DS bookings is to create the 3DS iFrame that operates in conjunction with your website checkout page. The 3DS iFrame should be wrapped in a container that is created on page load, but initially hidden from the user's view. Should a 3DS Challenge be required, the iFrame will then be made visible to the user.
The code below shows a sample implementation using a bootstrap modal, and is meant for guidance only. Your method of implementing the iFrame container will depend on the protocols that you have used to implement your checkout page.
iFrame Container for 2FA - Bootstrap Modal Example
<div id="threeDsIframeModal" class="modal" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body iFrame-container">
<div class="embed-responsive embed-responsive-16by9">
<iFrame id="threeDsIframe"src="<<3DS iFrame URL>>"></iFrame>
</div>
</div>
</div>
</div>
</div>
The source of the iFrame must be set to one of two values listed below, depending on whether the implementation is for testing or production use:
URL Type | URL | Notes |
---|---|---|
Test/Int | https://static.test.pay.expedia.com/3ds/threeDsIframe.html | Testing URL |
Prod | https://static.pay.expedia.com/3ds/threeDsIframe.html | Production URL |
To restrict the contents of the iFrame during testing, iFrame can be designated as "sandbox" however it must have the following permissions enabled to allow it to work:
sandbox="allow-scripts allow-forms allow-same-origin"
iFrame for 2FA - Testing Example
<iFrame id="threedsiFrame" class="embed-responsive-item" src="https://static.test.pay.expedia.com/3ds/threeDsIframe.html" sandbox="allow-scripts allow-forms allow-same-origin" frameborder="0"></iFrame>
Step 2: Add 3DS Connector JavaScript to Checkout Page
The 3DS Connector Library will communicates directly with the 3DS iFrame and send the collected data to the issuing bank (who will provide the iFrame content).
The sample below shows how to add 3DS Connector Library to the checkout page.
Import 3DS Connector Library - Testing Example
<head>
<script src="https://static.test.pay.expedia.com/3ds/1.3.65/pay-3ds-js-libs-connector.min.js" integrity=""></script>
</head>
Note that the two examples above are using the testing values - you will need to substitute the production values when you are ready to deploy your tested code:
You can use any method which is available for you to generate integrity
For example, online tool :https://www.srihash.org
Library Version | Environment | Attribute | Value |
---|---|---|---|
1.3.65 | Test/Int | src integrity | https://static.test.pay.expedia.com/3ds/1.3.65/pay-3ds-js-libs-connector.min.js value should be blank during testing in the integration environment |
1.3.65 | Prod | src integrity | https://static.pay.expedia.com/3ds/1.3.65/pay-3ds-js-libs-connector.min.js eg:sha384-gYopPw6xE5DZwnZXGavkwnvs3NkDOobnHqjroUnSHpGXvs/J9xjHX/8aGzKtSgWI |
Step 3: Set Up the 3DS Session
You must set up a 3DS session prior to submitting a 3DS booking request. This is done by calling one of the functions contained in the JavaScript file.
Note: There is no penalty for setting up a 3DS Session that is never used. This is the recommended approach, because it will prepare you to process either a 3DS transaction or a non-3DS transaction.
Setup Call Function:
new ThreeDSConnector(threeDsIFrameId, threeDsIFrameOrigin)
Setup Call Parameters:
Name | Type | Description |
---|---|---|
threeDsIFrameId | string | the DOM id of the 3DS iframe. |
threeDsIFrameOrigin | string | the origin of the 3DS iframe. Used to target outgoing window messagesand filter incoming messages when communicating with the 3DS iframe. |
Setup Call Example:
var c = new PayThreeDSConnector.ThreeDSConnector("threedsiFrame", "https://static.test.pay.expedia.com"); // change to match 3DS iFrame ID and origin
var id;
c.setup({referenceId: "1000"}).then(function (setupResponse) {
console.log("Setup Output: ", setupResponse);
$("#strBrowserMetadata").html(setupResponse.encodedBrowserMetadata);
$("#client-id").val(returnCitySN["cip"]);
return book();
}
Setup Response Example
{
"encodedBrowserMetadata": "eyJicm93c2VySmF2YUVuYWJsZWQiOmZhbHNlLCJicm93c2VyTGFuZ3VhZ2UiOiJlbi1VUyIsImJyb3dzZXJDb2xvckRlcHRoIjoyNCwiYnJvd3NlclNjcmVlbkhlaWdodCI6MTA4MCwiYnJvd3NlclNjcmVlbldpZHRoIjoxOTIwLCJicm93c2VyVFoiOjQyMCwiYnJvd3NlclVzZXJBZ2VudCI6Ik1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDYuMzsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzc1LjAuMzc3MC4xNDIgU2FmYXJpLzUzNy4zNiJ9",
"version": "1.3.65"
}
3DS Session Setup Response - BrowserData Node
Once your 3DS Session is successfully set up, you will receive EncodedBrowserMetadata element in response. You will need to pass IpAddressV4 BrowserAcceptHeader and MerchantUrl along with EncodedBrowserMetadata in BrowserData node
This data node will contain the following elements:
- IpAddressV4
- BrowserAcceptHeader
- EncodedBrowserMetadata
- MerchantUrl
Step 4: Submit Booking Request
The BrowserData Node elements returned from the JavaScript Library must now be included in the XAP Booking request (see example below):
Code Example – 3DS Booking Request
Note: The BrowserData Node has been included as an element in the XAP Booking API JSON:
{
"Title": "Hotel Booking Automation",
"FraudCheckId": "2CAAADE1R-654F-43B3-B932-EEDF8764CAA1",
"BillingDetails": {
"CreditCard": {
"CardNumber": "4000000000001091",
"CardType": "Visa",
"CardSecurityCode": "112",
"ExpirationMonth": "12",
"ExpirationYear": "2025"
},
"Name": {
"FirstName": "John",
"MiddleName": "Edward",
"LastName": "Doe"
},
"Address": {
"Address1": "3333 108th Ave",
"Address2": "Expedia HQ",
"City": "Bellevue",
"Province": "WA",
"Country": "USA",
"PostalCode": "98004"
},
"Phone": {
"CountryCode": "1",
"AreaCode": "425",
"Number": "1234567"
}
},
"AdditionalBookingDetails": {
"MarketingCode": "111abc",
"PartnerAgentId": "123456",
"PartnerConfirmationNumber": "hello123",
"HotelLoyalty": {
"Type": "AB",
"Number": "556677"
}
},
"BrowserData": {
"IpAddressV4": "216.251.118.195",
"BrowserAcceptHeader": "text/html",
"EncodedBrowserMetadata": "eyJicm93c2VySmF2YUVuYWJsZWQiOmZhbHNlLCJicm93c2VyTGFuZ3VhZ2UiOiJ6aC1DTiIsImJyb3dzZXJDb2xvckRlcHRoIjoyNCwiYnJvd3NlclNjcmVlbkhlaWdodCI6MTA1MCwiYnJvd3NlclNjcmVlbldpZHRoIjoxNjgwLCJicm93c2VyVFoiOi00ODAsImJyb3dzZXJVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xM182KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvNzUuMC4zNzcwLjEwMCBTYWZhcmkvNTM3LjM2In0=",
"MerchantUrl": "https://www.expedia.com/hotel"
},
"RoomDetails": [
{
"Traveler": {
"Name": {
"FirstName": "fish",
"MiddleName": "yanping",
"LastName": "guo"
},
"Phone": {
"CountryCode": "1",
"AreaCode": "206",
"Number": "9876543"
},
"Email": "18944905523@163.com",
"Primary": true
},
"RoomPreferences": [
{
"Type": "SmokingPreference",
"Value": "SmokingOrNonSmoking"
},
{
"Type": "Bed",
"Value": "13"
}
],
"SpecialRequests": "check-in 2 hours late,Crib Required"
}
]
}
Code Example – "3DS in Progress" Response
{
"TransactionId": "b593ad05-e75d-4a87-bc29-14ca89bcb371",
"PaymentSessionId": "ern:pay:pa:r1::93c9aa11-810f-0b02-2e33-30f98980831d",
"encodedInitConfig": "W3sicHJvdmlkZXJJZCI6IjEiLCJwYXltZW50SW5zdHJ1bWVudFJlZklkIjoiMTQ4NDUiLCJ0aW1lb3V0TXMiOiIxMDAwMCIsIm5vdGlmaWNhdGlvblVybCI6Imh0dHBzOi8vcmVkaXJlY3Rwcm94eS5pbnQtbWF1aS5rYXJtYWxhYi5uZXQvcmVkaXJlY3QtcHJveHkvbm90aWZ5L2V5SnJhV1FpT2lJeE5URTJNak01TWpNaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SnVhV1FpT2lKeU1Ub3hPRFl4TlNJc0ltVjRjQ0k2TVRVMk1qWTJOell4TUgwLkI3R29pNEJObzlKMTFURk01VFBmRHVUMURTZTdkaEdibWF5ZzRqN3VUNEUiLCJjYXJkaW5hbEp3dCI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpQY21kVmJtbDBTV1FpT2lJMVlXVmlObUUwTURabVpUTmtNVEUzTnpSbU1EWTJNalVpTENKU1pXWmxjbVZ1WTJWSlpDSTZJbVUyTmpabU56QmlMVE16WlRrdE5HSXdaaTFpTVRBNExUZGxOakkyWWpVMk5HVmxOaUlzSW1semN5STZJalZoWldNNE5qZ3hNR1UwTWpOa01qWTRPREF5TWpSak1DSXNJbEJoZVd4dllXUWlPbnNpVDNKa1pYSkVaWFJoYVd4eklqcDdJazl5WkdWeVRuVnRZbVZ5SWpvaU0yTXpOekUyTjJJdFpEUXdOUzAwWkRRMExXSTNObVl0T1dZME9UVmlNbVkyWWpsaklpd2lRV05qYjNWdWRFNTFiV0psY2lJNklqUXdNREF3TUNKOWZTd2laWGh3SWpveE5UWXlOamd4T1RrNUxDSnBZWFFpT2pFMU5qSTJOamMxT1Rrc0ltcDBhU0k2SWpabU1HRTVOakU0TFdZM1lXUXRORGd5WVMwNFl6QTVMVEpsT1dZelkyVmpaamhoTVNKOS5Zc2xROWQ1MnZnS252c25hVFh0cHMzbTBBdHZHU0VPcEpqSDh5WFpfcGFZIiwiZW52aXJvbm1lbnQiOiJTdGFnaW5nIiwibG9nZ2luZ0xldmVsIjoib24ifV0=",
"Links": {
"ApiBook": {
"Accept": "application/vnd.exp-hotel.v3+json",
"Method": "POST",
"Href": "https://apim.expedia.com/hotels/bookings/CgcyNTcwMjc5EjIKCTIwMDczNzA1NhIJMjAzNjYxMjM5GgIyNCoKMjAxOS0wOC0xNzIKMjAxOS0wOC0yMCoDCgEy?price=483.68¤cy=USD"
}
},
"Status": "3DSINPROGRESS"
}
In the event that a booking transaction will require 3DS to complete and the BrowserData Node was included in the XAP Booking API request, the XAP Booking API will return a response with a status of 3DSINPROGRESS, along with two additional parameters that will be used to initialize the 3DS Payment Session:
- PaymentSessionId
- EncodedInitConfig
Step 5: Initializing the 3DS Payment Session
If the XAP Booking API has returned a status of 3DSINPROGRESS, then the partner must initialize the 3DS Session. This is done by calling the InitSessionRequest function contained in the JavaScript file, and passing in the two parameters returned by the XAP Booking API response as arguments:
Initialize Call Function:
new InitSessionRequest(paymentSessionId,encodedInitConfig)
Initialize Call Parameters:
Name | Type | Description |
---|---|---|
paymentSessionId | string | A unique ID returned by 3DS service as part of the createPaymentSession backend call. |
encodedInitConfig | string | An encoded list of config objects containing data required for initialization,returned by 3DS service as part of the createPaymentSession backend call. Each item in the array would correspond to data for one payment instrument.The client should treat this as opaque data to be passed without parsing.When decoded (Base64), the structure would be a list of InitConfig. |
Initialize Call Example:
//var c = new PayThreeDSConnector.ThreeDSConnector("threedsiFrame", "https://static.test.pay.expedia.com");
var paymentSessionId;
var encodedInitConfig;
var initResponse = c.initSession({paymentSessionId: paymentSessionId, encodedInitConfig: encodedInitConfig});
Initialize Response Example:
{
"message": "",
"statusCode": "SUCCESS"
}
Step 6: Re-Submit Booking with paymentSessionId
Now that you have initialized the 3DS Payment Session, you must re-submit the booking request to the XAP Booking API, making sure that you include one more additional node in the request to tie the XAP Booking request to the associated 3DS Payment Session:
Code Example – TwoStepPaymentDetails Node
"TwoStepPaymentDetails": {
"PaymentType": "3DS",
"PaymentSessionId": "[ insert your paymentSessionID here ]"
}
You must build this node exactly as shown above, inserting the paymentSessionID that you received in response to the previous XAP Booking call into the JSON structure above, then include the new TwoStepPaymentDetails node into your next XAP Booking API call.
Code Example – Booking Request including TwoStepPaymentDetails
{
"Title": "Hotel Booking Automation",
"FraudCheckId": "2CAAADE1R-654F-43B3-B932-EEDF8764CAA1",
"BillingDetails": {
"CreditCard": {
"CardNumber": "4000000000001091",
"CardType": "Visa",
"CardSecurityCode": "112",
"ExpirationMonth": "12",
"ExpirationYear": "2025"
},
"TwoStepPaymentDetails": {
"PaymentType": "3DS",
"PaymentSessionId": "ern:pay:pa:r1::93c9aa11-810f-0b02-2e33-30f98980831d"
},
"Name": {
"FirstName": "John",
"MiddleName": "Edward",
"LastName": "Doe"
},
"Address": {
"Address1": "3333 108th Ave",
"Address2": "Expedia HQ",
"City": "Bellevue",
"Province": "WA",
"Country": "USA",
"PostalCode": "98004"
},
"Phone": {
"CountryCode": "1",
"AreaCode": "425",
"Number": "1234567"
}
},
"AdditionalBookingDetails": {
"MarketingCode": "111abc",
"PartnerAgentId": "123456",
"PartnerConfirmationNumber": "hello123",
"HotelLoyalty": {
"Type": "AB",
"Number": "556677"
}
},
"BrowserData": {
"IpAddressV4": "216.251.118.195",
"BrowserAcceptHeader": "text/html",
"EncodedBrowserMetadata": "eyJicm93c2VySmF2YUVuYWJsZWQiOmZhbHNlLCJicm93c2VyTGFuZ3VhZ2UiOiJ6aC1DTiIsImJyb3dzZXJDb2xvckRlcHRoIjoyNCwiYnJvd3NlclNjcmVlbkhlaWdodCI6MTA1MCwiYnJvd3NlclNjcmVlbldpZHRoIjoxNjgwLCJicm93c2VyVFoiOi00ODAsImJyb3dzZXJVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xM182KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvNzUuMC4zNzcwLjEwMCBTYWZhcmkvNTM3LjM2In0=",
"MerchantUrl": "https://www.expedia.com/hotel"
},
"RoomDetails": [
{
"Traveler": {
"Name": {
"FirstName": "fish",
"MiddleName": "yanping",
"LastName": "guo"
},
"Phone": {
"CountryCode": "1",
"AreaCode": "206",
"Number": "9876543"
},
"Email": "18944905523@163.com",
"Primary": true
},
"RoomPreferences": [
{
"Type": "SmokingPreference",
"Value": "SmokingOrNonSmoking"
},
{
"Type": "Bed",
"Value": "13"
},
{
"Type": "Bed",
"Value": "13"
}
],
"SpecialRequests": "check-in 2 hours late,Crib Required"
}
]
}
The XAP Booking API will now change the status of the transaction from 'in progress' to 'pending', and will return the encodedChallengeConfig element that you will use to populate the 3DS Challenge into the 3DS iFrame.
Code Example – Booking Response including encodedChallengeConfig
{
"TransactionId": "cad9c2bc-c5b0-481d-abd5-a4cc24b7c14f",
"ItineraryNumber": 791394437833,
"encodedChallengeConfig": "W3sicHJvdmlkZXJJZCI6IjEiLCJwYXltZW50SW5zdHJ1bWVudFJlZklkIjoiMTQ4NDUiLCJub3RpZmljYXRpb25VcmwiOiJodHRwczovL3JlZGlyZWN0cHJveHkuaW50LW1hdWkua2FybWFsYWIubmV0L3JlZGlyZWN0LXByb3h5L25vdGlmeS9leUpyYVdRaU9pSXhOVEUyTWpNNU1qTWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp1YVdRaU9pSnlNVG94T0RZeE5pSXNJbVY0Y0NJNk1UVTJNalkyT1RjNU5uMC5feUN2RnVHODdlZGdzTWVZdkNqS212ZDRBcnprb0hxd2ltVnJ2RUtMQi1RIiwidGltZW91dE1zIjoiMTIzMDAwMCIsImFjc1VybCI6Imh0dHBzOi8vMG1lcmNoYW50YWNzc3RhZy5jYXJkaW5hbGNvbW1lcmNlLmNvbS9NZXJjaGFudEFDU1dlYi9jcmVxLmpzcCIsInBheWxvYWQiOiJleUp0WlhOellXZGxWSGx3WlNJNklrTlNaWEVpTENKdFpYTnpZV2RsVm1WeWMybHZiaUk2SWpJdU1TNHdJaXdpZEdoeVpXVkVVMU5sY25abGNsUnlZVzV6U1VRaU9pSm1aREptT1dSa1lpMHpPV1kzTFRRM1lqa3RPRFpsTVMxbU9USTBNek0zTmpFeU5URWlMQ0poWTNOVWNtRnVjMGxFSWpvaU16ZGhNREl3TkdRdE1qa3dZeTAwWVRnMkxUazBNemd0TkRBd01EZzBZelUyTXpFNElpd2lZMmhoYkd4bGJtZGxWMmx1Wkc5M1UybDZaU0k2SWpBMUluMCIsInRyYW5zYWN0aW9uSWQiOiJqVGZ5N0JBMHlIUlB4SXBOS2xJMSIsImNoYWxsZW5nZVdpbmRvd1NpemUiOiIwNSJ9XQ==",
"Links": {
"ApiResume": {
"Accept": "application/vnd.exp-hotel.v3+json",
"Method": "PUT",
"Href": "https://apim.expedia.com/hotels/bookings/791394437833/resume"
}
},
"Status": "PENDING"
}
Step 7: Load 3DS Challenge into 3DS iFrame
The encodedChallengeConfig element from the previous XAP Booking API response contains the data required to populate the 3DS iFrame. It is encoded using Base64 Encoding, but should be passed without any decoding or parsing exactly as it was provided to the partner. The iFrame is expecting to receive the data as a single object encoded in Base64 and decode the message prior to loading.
Loading the 3DS Challenge into the 3DS iFrame
When you submit the ChallengeRequest, the JavaScript Library will load the 3DS iFrame, make it visible, display the challenge content to the user, record the user's inputs, and evaluate whether the user has passed or failed the challenge.
Challenge Call Function:
new ChallengeRequest()
Challenge Call Properties:
Name | Type | Description |
---|---|---|
paymentSessionId | string | A unique ID returned by 3DS service as part of the createPaymentSession backend call. |
encodedInitConfig | string | An encoded list of config objects containing data required for presenting 3DS challenge,returned by 3DS service as part of the authentication backend call.For each payment instrument, external scripts would be invoked from inside the same sandboxed iframe used for initialization. The challenges would be shown in sequence of their order in this list. The client should treat this as opaque data to be passed without parsing. When decoded (Base64). |
Challenge Call Example:
//var c = new PayThreeDSConnector.ThreeDSConnector("threedsiFrame", "https://static.test.pay.expedia.com");
var paymentSessionId;
var encodedChallengeConfig;
var challengeResponse = c.challenge({paymentSessionId: paymentSessionId, encodedChallengeConfig: encodedChallengeConfig});
User Interaction with the 3DS Challenge
Below is an example of how the 3DS iFrame might appear to the user. The actual contents of the iFrame are provided by the bank, so the contents will most likely be different that what is displayed below.

Success or Failure of the 3DS Challenge
When the user clicks the Submit Button on the page shown to the user in the 3DS iFrame, a message will be passed back to you with a 'success' or a detailed failure message, indicating the outcome of the 3DS Challenge.
Challenge Response Example:
{
"message": "Success",
"statusCode": "SUCCESS"
}
Retrying the Challenge
Should the user enter an incorrect password, the 3DS iFrame will remain in view for the user to retry the authentication.
Should there be a deeper issue with the 3DS Challenge, the ChallengeRequest can be called again or, in the event of the 3DS Session expiring, the partner may need to re-initialize the 3DS Session to start the charging process over again.
Step 8: Resume After Challenge Success
Once the 3DS Challenge is successfully completed, the final step in the process is to complete the booking transaction using the XAP Resume Booking API. This API request only includes the Itinerary Number associated to the booking and is intended to let Expedia know that the 3DS process has completed, and all necessary data has been submitted.
Code Example – Resume Booking Request
https://apim.expedia.com/hotels/bookings/791394437833/resume
Code Example – Resume Booking Response
{
"TransactionId": "dce81a0f-1afd-433d-853c-072c1cee10f2",
"ItineraryNumber": 791400669859,
"BookingDateTime": "2021-07-11T19:43:20-07:00",
"PartnerAgentId": "123456",
"PartnerConfirmationNumber": "hello123",
"Links": {
"ApiCancel": {
"Accept": "application/vnd.exp-hotel.v3+json",
"Method": "PUT",
"Href": "https://apim.expedia.com/hotels/bookings/791400669859"
},
"ApiPreCancel": {
"Accept": "application/vnd.exp-hotel.v3+json",
"Method": "GET",
"Href": "https://apim.expedia.com/hotels/bookings/791400669859/canceldetails"
},
"WebItinRetrieve": {
"Href": "https://wwwexpediacom.integration.sb.karmalab.net/trips/791400669859?userToken=ZqA-rDWzalveAvidFZtTIAN3WtFp_Cl8U1uY-MwPpiyCLGR5UWToP9DheRZkrP2M3KM_yPrxQopdJA8IzYEhxCYZicni7GrcIpAiUA"
},
"ApiItinRetrieve": {
"Accept": "application/vnd.exp-hotel.v3+json",
"Method": "GET",
"Href": "https://apim.expedia.com/hotels/bookings/791400669859"
}
},
"HotelConfirmation": "8088974250123",
"NumberOfRooms": 1,
"LengthOfStay": 3,
"HotelDetails": {
"Id": "2570279",
"LocalCurrencyCode": "GBP",
"ChainAndBrandInfo": {
"ChainId": 2936,
"ChainName": "Bespoke",
"BrandId": 2936,
"BrandName": "Bespoke"
},
"Rooms": [
{
"Traveler": {
"Name": {
"FirstName": "fish",
"MiddleName": "yanping",
"LastName": "guo"
},
"Phone": {
"CountryCode": "1",
"AreaCode": "206",
"Number": "9876543"
},
"Email": "18944905523@163.com"
},
"Status": "BOOKED",
"RoomPreferences": [
{
"Type": "SmokingPreference",
"Value": "NonSmoking"
},
{
"Type": "Bed",
"Value": "13"
}
],
"SpecialRequests": "check-in 2 hours late,Crib Required",
"Occupants": [
{
"Adults": 2
}
],
"StayDates": [
{
"CheckInDate": "2021-08-17",
"CheckOutDate": "2021-08-20"
}
],
"RatePlans": [
{
"RoomTypeId": "200737056",
"RatePlanId": "203661239",
"RateRuleId": "342351812",
"StayDates": {
"CheckInDate": "2021-08-17",
"CheckOutDate": "2021-08-20"
},
"Price": {
"BaseRate": {
"Value": "403.37",
"Currency": "USD"
},
"TaxesAndFees": {
"Value": "80.67",
"Currency": "USD"
},
"TotalPrice": {
"Value": "484.04",
"Currency": "USD"
},
"AvgNightlyRate": {
"Value": "134.46",
"Currency": "USD"
},
"RoomRates": [
{
"RoomIndex": 1,
"NightlyRates": [
{
"StayDate": "2021-08-17",
"BaseRate": {
"Value": "151.37",
"Currency": "USD"
},
"TaxesAndFees": {
"Value": "30.27",
"Currency": "USD"
},
"TotalPrice": {
"Value": "181.64",
"Currency": "USD"
}
},
{
"StayDate": "2021-08-18",
"BaseRate": {
"Value": "109.09",
"Currency": "USD"
},
"TaxesAndFees": {
"Value": "21.81",
"Currency": "USD"
},
"TotalPrice": {
"Value": "130.90",
"Currency": "USD"
}
},
{
"StayDate": "2021-08-19",
"BaseRate": {
"Value": "142.91",
"Currency": "USD"
},
"TaxesAndFees": {
"Value": "28.59",
"Currency": "USD"
},
"TotalPrice": {
"Value": "171.50",
"Currency": "USD"
}
}
]
}
]
},
"PaymentMethod": "Online",
"Status": "BOOKED",
"CancellationPolicy": {
"Refundable": false,
"FreeCancellation": false,
"CancellationPenaltyRules": [
{
"PenaltyPercentOfStay": "100",
"PenaltyStartDateTime": "2021-07-12T02:43:00+00:00",
"PenaltyEndDateTime": "2021-08-17T14:00:00+00:00"
}
],
"CancelPolicyDescription": "This reservation is non-refundable and cannot be cancelled or changed.",
"NonRefundableDateRanges": [
{
"StartDate": "2021-08-17",
"EndDate": "2021-08-19"
}
]
},
"ConfirmationNumber": "4795b8bd-dcd5-49ca-9a92-2155e009adfc"
}
],
"Price": {
"BaseRate": {
"Value": "403.37",
"Currency": "USD"
},
"TaxesAndFees": {
"Value": "80.67",
"Currency": "USD"
},
"TotalPrice": {
"Value": "484.04",
"Currency": "USD"
},
"AvgNightlyRate": {
"Value": "134.46",
"Currency": "USD"
}
},
"SmokingOption": "NonSmoking"
}
]
},
"TotalPrice": {
"Value": "484.04",
"Currency": "USD"
}
}