-
Notifications
You must be signed in to change notification settings - Fork 0
JTB
JTB is multi unit supplier which provides properties with instant confirmation over Asia, most of them in Japan. You can try to quote and book JTB properties here: https://www.jtb.com.sg/jp/hotels/tokyo-accommodation/shiba-park-hotel/
JTB provides two ways to interact with him:
- SOAP interface. It provides realtime inventory information and booking/cancellation operation.
- Master Synchronization. It provides master information as TSV file (tab-delimited format) using SFTP protocol. Some of files are really huge.
Full documentation list
- Developers Guide - common information about interaction with JTB. If you know nothing about JTB start from here.
- API References Guide - SOAP API documentation, request/response fields, etc.
- Data references - Master Synchronization documentation, TSV columns' descriptions, etc.
- Column Relations - scheme of relations between SOAP API and Master Synchronization files.
Authentication happens with the same credentials described in the Authentication section below.
All calls to JTB's API must be authenticated using an id
, user
, password
and company
combination. These are sent as xml block for every method call.
<POS>
<Source>
<RequestorID ID="AGTUSER01" UserName="USERNAME" MessagePassword="Agtuser_01">
<CompanyName Code="AGT01"/>
<BasicInfo Version="2013" Language="EN"/>
</RequestorID>
</Source>
</POS>
WSDL URLs can be found in Developers Guide documentation They are different for different services, for example for GA_HotelAvail service they currently are:
Sandbox: https://trial-www.jtbgenesis.com/genesis2-demo/services/GA_HotelAvail_v2013?wsdl
Production: https://www.jtbgenesis.com/genesis2/services/GA_HotelAvail_v2013?wsdl
For SFTP connection you need: user_id
, password
, port
and host
Port and host can be found in Developers Guide documentation and currently they are:
port: 10036
production host: "ftp.jtbgenesis.com"
sandbox host: "trial-ftp.jtbgenesis.com"
Each JTB unit has unique room_code
, but their API methods (quote, booking) don't allow to work with room_code
directly. Instead of this each of them works with rate_plans
(every unit can have more then one rate_plan).
Concierge uses combination of room_type_code
and room_code
as Roomorama unit_id
and stores rate_plan_id
and room_code
relations in jtb_rate_plans
table. Using this table c can find exactly unit's rate_plans from quotation response and work with exactly unit further.
JTB::UnitId
class responsible for converting JTB ids to Roomorama id and vice versa. For more details see the class documentation.
NOTE: Quote Prices depends on state of jtb_rate_plan
DB table. This table is actualized by sync workers. So it is important to run sync regularly.
Checking if a property is available and getting the price for a given stay is covered by the https://www.jtbgenesis.com/genesis2/services/GA_HotelAvail_v2013
endpoint with called :gby010
operation.
Specific rules
- maximum stay length 14 days API References Guide page 77
Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://service.api.genesis2.jtbgmt.com/">
<soapenv:Header/>
<soapenv:Body>
<GA_HotelAvailRQ>
<POS>
<Source>
<RequestorID ID="AGTUSER01" UserName="USERNAME" MessagePassword="Agtuser_01">
<CompanyName Code="AGT01"/>
<BasicInfo Version="2013" Language="EN"/>
</RequestorID>
</Source>
</POS>
<AvailRequestSegments>
<AvailRequestSegment>
<HotelSearchCriteria>
<Criterion SortType="PRICE" AvailStatus="ALL">
<HotelCode Code="4016001"/>
<RoomStayCandidates SearchCondition="OR">
<RoomStayCandidate RoomTypeCode="TWN" Quantity="1"/>
</RoomStayCandidates>
<StayDateRange Start="2013-04-01" End="2013-04-02"/>
</Criterion>
</HotelSearchCriteria>
</AvailRequestSegment>
</AvailRequestSegments>
</GA_HotelAvailRQ>
</soapenv:Body>
</soapenv:Envelope>
-
HotelCode
is the property ID in their system. -
RoomTypeCode
is the unit ID in their system. -
StayDateRange
has two attributesStart
andEnd
which meanscheck_in
andcheck_out
dates.
Response
{
ga_hotel_avail_rs: {
room_stays: {
room_stay: [* array of availabilities *]
},
... some_useless info
}
}
The response returned by JTB is huge hash where deepest keys has @
at the beginning.
Example: { room_rates: { room_rate: { total: { :@amount_after_tax => '10000' }, ... } }
!!! IMPORTANT !!!
JTB returns list of availabilities with rate plans
for each day of requested period. It means that for the quoted room price they will provide different prices. The request's params don't provide an ability to get quote for exactly room by its id, so to consider only rate plans of requested unit Concierge uses jtb_rate_plans
table.
# After small modification of response data looks like:
{ date: '2016-03-04', price: 20000, rate_plan: 'QWERQW222', available: 'OK' },
{ date: '2016-03-04', price: 22000, rate_plan: 'ASDRQW222', available: 'UC' },
{ date: '2016-03-05', price: 20000, rate_plan: 'QWERQW222', available: 'OK' },
....
{ date: '2016-03-06', price: 20000, rate_plan: 'QWERQW222', available: 'OK' },
{ date: '2016-03-06', price: 24000, rate_plan: 'ASDRQW222', available: 'OK' }
-
date
is each day of the requested period -
price
is final price after tax -
rate_plan
is plan with some conditions of room (not related to Roomorama businesses logic) -
available
has two varies:OK
- available;UC
- unavailable
All prices are quoted in JPY.
NOTE: Booking depends on state of jtb_rate_plan
DB table. This table is actualized by sync workers. So it is important to run sync regularly.
The request to create booking for a property is covered by the https://www.jtbgenesis.com/genesis2/services/GA_HotelRes_v2013
endpoint with called :gby011
operation.
The JTB API doesn't have room id as param, but has rate plan id arg. So before booking Concierge call quote request to find the best rate plan for given room.
Request
<HotelReservations>
<HotelReservation PassiveIndicator="false">
<ResGlobalInfo>
<RatePlans>
<RatePlan RatePlanID="TYOHKPT00STD1DBL"/>
</RatePlans>
<TimeSpan StartDate="2016-04-01" EndDate="2016-04-02"/>
</ResGlobalInfo>
<ResGuests>
<ResGuest ResGuestRPH="1" PrimaryIndicator="true" AgeQualifyingCode="ADL">
<Profiles>
<ProfileInfo>
<Profile>
<PrefCollections>
<PrefCollection>
<CommonPref SmokingAllowed="true"/>
</PrefCollection>
</PrefCollections>
<Customer>
<PersonName>
<GivenName>GivenName1</GivenName>
<Surname>Surname1</Surname>
<NamePrefix>Mr</NamePrefix>
</PersonName>
</Customer>
</Profile>
</ProfileInfo>
</Profiles>
</ResGuest>
<ResGuest ResGuestRPH="2" PrimaryIndicator="false" AgeQualifyingCode="ADL">
...
</ResGuest>
</ResGuests>
<RoomStays>
<RoomStay>
<ResGuestRPHs>
<ResGuestRPH RPH="1"/> # related to each guest number
<ResGuestRPH RPH="2"/>
</ResGuestRPHs>
<RoomTypes>
<RoomType RoomTypeCode="DBL"/> # RoomTypeCode is `Unit#hotel_room_type` on Roomorama project
</RoomTypes>
</RoomStay>
</RoomStays>
</HotelReservation>
</HotelReservations>
Important fields and attributes:
-
PassiveIndicator
- allows to simulate request. If "true", returns unique_id="XXXXXXXXXX" -
RatePlanID
- main JTB price related object -
TimeSpan
- check in/out data -
ResGuest
- guest info. JTB requires info for each guest and also don't allow to book double room for one guest. I'll complete this part after getting JTB's answer about guests count fordouble
,triple
... room types
Response
{
"ga_hotel_res_rs": {
"hotel_reservations": {
"hotel_reservation": {
....
"unique_id": {
"@id": "XXXXXXXXXX"
},
"@create_date_time": "2016-03-14",
"@pnr": "XXXXXX",
"@passive_indicator": "true",
"@res_status": "OK"
}
},
"success": null,
"@xmlns": "http://service.api.genesis2.jtbgmt.com/"
}
}
~~~
as success result response consist `unique_id` which is booking identifier. If `PassiveIndicator` is true returns "XXXXXXXXXX"
Because JTB cancel API method requires `rate_plan_id` as arg Concierge store `reference_number` as combination of `unique_id` and `rate_plan_id`. `JTB::ReferenceNumber` class responsible for converting JTB ids to Roomorama reference_number and vice versa. For more details see the class documentation.
### Cancel reservation
The request to cancel reservation is covered by the `https://www.jtbgenesis.com/genesis2/services/GA_Cancel_v2013` endpoint with called `:gby012` operation.
The method requires `rate_plan_id` param as well as `reservation_id`.
**Request**
~~~xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jtb="http://service.api.genesis2.jtbgmt.com/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<jtb:GA_CancelRQ PassiveIndicator="false">
<jtb:POS>
<jtb:Source>
<jtb:RequestorID ID="id" UserName="name" MessagePassword="pass">
<jtb:CompanyName Code="code"/>
<jtb:BasicInfo Version="2013" Language="EN"/>
</jtb:RequestorID>
</jtb:Source>
</jtb:POS>
<jtb:UniqueID ID="0044UT0001"/>
<jtb:Verification>
<jtb:RatePlans>
<jtb:RatePlan RatePlanID="CUBHC2101STD1DBL"/>
</jtb:RatePlans>
<jtb:ReservationTimeSpan Start="2016-11-02"/>
</jtb:Verification>
</jtb:GA_CancelRQ>
</soapenv:Body>
</soapenv:Envelope>
~~~
**Response**
~~~xml
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GA_CancelRS
xmlns="http://service.api.genesis2.jtbgmt.com/" PassiveIndicator="false" Status="XL">
<CancelInfoRS>
<CancelRules>
<CancelRule Amount="0.00" AmountG="0.00">
<RoomStays>
<RoomStay>
<CancelPenalties>
<CancelPenalty End="2016-10-31" Start="2014-02-07" duration="1">
<AmountPercent Percent="0.00" />
</CancelPenalty>
<CancelPenalty End="2016-11-01" Start="2016-11-01" duration="1">
<AmountPercent Percent="100.00" />
</CancelPenalty>
<CancelPenalty End="2016-11-02" Start="2016-11-02" duration="1">
<AmountPercent Percent="100.00" />
</CancelPenalty>
<CancelPenalty End="2017-02-09" Start="2016-11-03" duration="1">
<AmountPercent Percent="100.00" />
</CancelPenalty>
</CancelPenalties>
<RoomRates>
<RoomRate RoomID="1" TotalForCancellation="0.00">
<GuestRate Amount="0.00" PNRPAXID="001" />
<GuestRate Amount="0.00" PNRPAXID="002" />
</RoomRate>
</RoomRates>
<TimeSpan End="2016-11-02" Start="2016-11-02" />
</RoomStay>
</RoomStays>
</CancelRule>
</CancelRules>
</CancelInfoRS>
<Success></Success>
<UniqueID ID="0044UT0001" />
<Verification>
<RatePlans>
<RatePlan RatePlanID="CUBHC2101STD1DBL" />
</RatePlans>
<ReservationTimeSpan Start="2016-11-02" />
</Verification>
</GA_CancelRS>
</soap:Body>
</soap:Envelope>
~~~
### Quote/Booking/Cancel testing
JTB provides two ways to use their API in test mode. The first one is set `PassiveIndicator` attribute in request to `true` (to manipulate this parameter Concierge uses `test` field from JTB credentials). This is useful for onetime testing. The problem is that booking with this parameter always return `XXXXXXXX` as reservation id and developer cannot test the cancellation of the reservation. To solve the problem he can use JTB sandbox (with `PassiveIndicator == false`). The sandbox URLs:
* API: https://trial-www.jtbgenesis.com/genesis2-demo/services
* SFTP: trial-ftp.jtbgenesis.com
### Synchronisation
JTB sync is implemented with usage of Master Synchronisation (TSV files). Some files are really huge (~4Gb) that is why before the first sync Concierge imports files to DB and before further syncs it actualizes the DB with Diff files if it's possible.
There are two kinds of files:
* ALL - file contains all the data from JTB (big files)
* Diff - file contains only diff information (small files)
Concierge required next JTB files for sync:
* _GenericMaster_ - references for JTB enums (location codes, room types, room grades, amenities, etc.). JTB doesn't provide Diff files for this information.
* _HotelInfo_ - all JTB hotels with basic information about them (title, description, etc.). Concierge imports only hotels with type `R` - ryokans.
* _PictureMaster_ - contains images for hotels and rooms
* _RoomType_ - contains information about hotels' rooms
* _RoomPlan_ - each room can have several rate plans, for example they can be different by meal type (breakfast only, etc.), each plan has its price
* _RoomPrice_ - price information for each rate plan for each date
* _RoomStock_ - information about availability for each rate plan for each date. If `sale_status = '0'` and `number_of_units > 0` then rate plan is available for given date. **Note:** JTB documentation contains errors in description of columns for this file, actually `language` column is `city_code` and `option_plan_id` is `rate_plan_id`.
JTB provides information in several languages. Currently Concierge uses only english.
Concierge has separate DB table for each file. `JTB::Sync::Actualizer` class responsible for fetching information from JTB and actualization Concierge's JTB tables. There is also special table `jtb_state` which stores name of last synced file name for each category:
concierge_development=> select * from jtb_state ; prefix | file_name ---------------+--------------------------------------- GenericMaster | GenericMaster_ALL_20161012.zip RoomStock | RoomStock_Diff_20161013104213.zip HotelInfo | HotelInfo_Diff_20161013070036.zip PictureMaster | PictureMaster_Diff_20161013104008.zip RoomPlan | RoomPlan_Diff_20161013081243.zip RoomType | RoomType_Diff_20161013074153.zip RoomPrice | RoomPrice_Diff_20161013083228.zip (7 rows)
TTL of each file on JTB server is one week. During sync the actualizer checks if the last sync was not so long ago (actually it checks if last synced file is still exists on SFTP server) and downloads only Diff files if possible.
As well as sync workers require actualization of JTB DB tables at the beginning and to avoid simultaneous running of multiple actualization processes Concierge uses one worker to implement metadata and availabilities sync: `Workers::Suppliers::JTB::Metadata`