{
  "info": {
    "_postman_id": "84fd78d6-a762-4cee-8a8e-3324f1332782",
    "name": "SwOTA/Flat files",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_exporter_id": "52910816"
  },
  "item": [
    {
      "name": "SwOTA REST API",
      "item": [
        {
          "name": "OTA_PingRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "protocolProfileBehavior": {
            "disabledSystemHeaders": {
              "accept-encoding": true
            }
          },
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml; charset=utf-8"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_PingRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" Version=\"1.0\" TimeStamp=\"2025-01-01T12:00:00Z\">\n    <EchoData>REST API test connection</EchoData>\n</OTA_PingRQ>",
              "options": {
                "raw": {
                  "language": "xml"
                }
              }
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_PingRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_PingRQ"
              ]
            }
          },
          "response": []
        },
        {
          "name": "OTA_CruiseSailAvailRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Extract VoyageID to collection variable', function () {",
                  "  const xml = pm.response.text();",
                  "",
                  "  let voyageId;",
                  "  try {",
                  "    const json = xml2Json(xml);",
                  "",
                  "    const findVoyageId = (value) => {",
                  "      if (!value) return null;",
                  "      if (Array.isArray(value)) {",
                  "        for (const v of value) {",
                  "          const found = findVoyageId(v);",
                  "          if (found) return found;",
                  "        }",
                  "        return null;",
                  "      }",
                  "      if (typeof value === 'object') {",
                  "        if (value['@_VoyageID']) return value['@_VoyageID'];",
                  "        if (value['@VoyageID']) return value['@VoyageID'];",
                  "        if (value['@_VoyageId']) return value['@_VoyageId'];",
                  "        if (value['@VoyageId']) return value['@VoyageId'];",
                  "",
                  "        for (const k of Object.keys(value)) {",
                  "          const found = findVoyageId(value[k]);",
                  "          if (found) return found;",
                  "        }",
                  "      }",
                  "      return null;",
                  "    };",
                  "",
                  "    voyageId = findVoyageId(json);",
                  "  } catch (e) {",
                  "    // ignore parse errors and try regex fallback",
                  "  }",
                  "",
                  "  if (!voyageId) {",
                  "    const m = xml.match(/\\bVoyageID\\s*=\\s*\"([^\"]+)\"/i);",
                  "    if (m) voyageId = m[1];",
                  "  }",
                  "",
                  "  pm.expect(voyageId, 'VoyageID should be present in response').to.exist;",
                  "  pm.collectionVariables.set('VOYAGE_ID', String(voyageId));",
                  "});",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            },
            {
              "listen": "prerequest",
              "script": {
                "exec": [
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_CruiseSailAvailRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" Version=\"1.0\">\n{{POS_XML}}\n    <SailingDateRange>\n        <StartDateWindow EarliestDate=\"2026-06-01\"/>\n        <EndDateWindow LatestDate=\"2026-06-30\"/>\n    </SailingDateRange>\n</OTA_CruiseSailAvailRQ>",
              "options": {
                "raw": {
                  "language": "xml"
                }
              }
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_CruiseSailAvailRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_CruiseSailAvailRQ"
              ]
            }
          },
          "response": []
        },
        {
          "name": "OTA_CruiseCategoryAvailRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Extract PricedCategoryCode from BESTPRICE Fare block and save to collection variable', function () {",
                  "  const xml = pm.response.text();",
                  "",
                  "  const patterns = [",
                  "    /<[^>]*SelectedFare\\b[^>]*FareCode\\s*=\\s*\"BESTPRICE\"[^>]*>[\\s\\S]*?<\\/[^>]*SelectedFare\\s*>/i,",
                  "    /<[^>]*FareOption\\b[^>]*FareCode\\s*=\\s*\"BESTPRICE\"[^>]*>[\\s\\S]*?<\\/[^>]*FareOption\\s*>/i",
                  "  ];",
                  "",
                  "  let bestPriceBlock = null;",
                  "  for (const re of patterns) {",
                  "    const match = xml.match(re);",
                  "    if (match && match[0]) {",
                  "      bestPriceBlock = match[0];",
                  "      break;",
                  "    }",
                  "  }",
                  "",
                  "  pm.expect(bestPriceBlock, 'Could not find a BESTPRICE block. Expected a <SelectedFare ... FareCode=\"BESTPRICE\">...</SelectedFare> or <FareOption ... FareCode=\"BESTPRICE\">...</FareOption> section in the response XML.').to.not.be.null;",
                  "",
                  "  const codeMatch = bestPriceBlock.match(/PricedCategoryCode\\s*=\\s*\"([^\"]+)\"/i);",
                  "  pm.expect(codeMatch, 'BESTPRICE block was found but no PricedCategoryCode=\"...\" attribute was found inside it.').to.not.be.null;",
                  "",
                  "  const code = codeMatch[1];",
                  "  pm.expect(code, 'Extracted PricedCategoryCode was empty.').to.be.a('string').and.not.equal('');",
                  "",
                  "  pm.collectionVariables.set('PRICED_CATEGORY_CODE', code);",
                  "});",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_CruiseCategoryAvailRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" Version=\"1.0\">\n{{POS_XML}}\n<Guest/><Guest/>\n<GuestCounts><GuestCount Code=\"10\" Quantity=\"2\"/></GuestCounts>\n<SailingInfo><SelectedSailing VoyageID=\"{{VOYAGE_ID}}\"><CruiseLine/></SelectedSailing></SailingInfo>\n<SelectedFare FareCode=\"BESTPRICE\"/>\n</OTA_CruiseCategoryAvailRQ>",
              "options": {
                "raw": {
                  "language": "xml"
                }
              }
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_CruiseCategoryAvailRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_CruiseCategoryAvailRQ"
              ]
            }
          },
          "response": []
        },
        {
          "name": "OTA_CruiseCabinAvailRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "",
                  "// Extract CabinNumber and Status from the first <CabinOption ...> element in the response XML",
                  "const xml = pm.response.text();",
                  "",
                  "const cabinOptionRegex = /<[^>]*CabinOption\\b[^>]*>/i;",
                  "const firstCabinOptionTag = (xml.match(cabinOptionRegex) || [null])[0];",
                  "",
                  "pm.test('Found first CabinOption element', function () {",
                  "  pm.expect(firstCabinOptionTag, 'Expected at least one <CabinOption ...> element').to.not.be.null;",
                  "});",
                  "",
                  "if (firstCabinOptionTag) {",
                  "  const cabinNumberMatch = firstCabinOptionTag.match(/\\bCabinNumber\\s*=\\s*\"([^\"]+)\"/i);",
                  "  const statusMatch = firstCabinOptionTag.match(/\\bStatus\\s*=\\s*\"([^\"]+)\"/i);",
                  "",
                  "  pm.test('Extracted CabinNumber and Status attributes', function () {",
                  "    pm.expect(cabinNumberMatch, 'CabinNumber attribute not found on first CabinOption').to.not.be.null;",
                  "    pm.expect(statusMatch, 'Status attribute not found on first CabinOption').to.not.be.null;",
                  "  });",
                  "",
                  "  if (cabinNumberMatch && statusMatch) {",
                  "    const cabinNumber = cabinNumberMatch[1];",
                  "    const cabinStatus = statusMatch[1];",
                  "",
                  "    pm.collectionVariables.set('CABIN_NUMBER', cabinNumber);",
                  "    pm.collectionVariables.set('CABIN_STATUS', cabinStatus);",
                  "",
                  "    pm.test('Saved CabinNumber and CabinStatus to collection variables', function () {",
                  "      pm.expect(pm.collectionVariables.get('CABIN_NUMBER')).to.eql(cabinNumber);",
                  "      pm.expect(pm.collectionVariables.get('CABIN_STATUS')).to.eql(cabinStatus);",
                  "    });",
                  "  }",
                  "}",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_CruiseCabinAvailRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" Version=\"1.0\">\n{{POS_XML}}\n\n    <GuestCounts>\n        <GuestCount Quantity=\"2\"/>\n    </GuestCounts>\n    <SailingInfo>\n        <SelectedSailing VoyageID=\"{{VOYAGE_ID}}\">\n            <CruiseLine/>\n        </SelectedSailing>\n        <SelectedCategory PricedCategoryCode=\"{{PRICED_CATEGORY_CODE}}\"/>\n    </SailingInfo>\n    <SelectedFare FareCode=\"BESTPRICE\"/>\n</OTA_CruiseCabinAvailRQ>",
              "options": {
                "raw": {
                  "language": "xml"
                }
              }
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_CruiseCabinAvailRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_CruiseCabinAvailRQ"
              ]
            }
          },
          "response": []
        },
        {
          "name": "OTA_CruisePriceBookingRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_CruisePriceBookingRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" TimeStamp=\"2025-01-01T12:00:00Z\" Version=\"1.0\">\n{{POS_XML}}\n\n    <GuestCounts>\n        <GuestCount Code=\"10\" Quantity=\"1\"/>\n    </GuestCounts>\n    <SailingInfo>\n        <SelectedSailing VoyageID=\"{{VOYAGE_ID}}\">\n            <CruiseLine/>\n        </SelectedSailing>\n        <SelectedCategory PricedCategoryCode=\"{{PRICED_CATEGORY_CODE}}\">\n            <SelectedCabin CabinNumber=\"{{CABIN_NUMBER}}\" Status=\"{{CABIN_STATUS}}\"/>\n        </SelectedCategory>\n    </SailingInfo>\n    <ReservationInfo>\n        <GuestDetails>\n            <GuestDetail>\n                <ContactInfo>\n                    <PersonName>\n                        <GivenName>John</GivenName>\n                        <Surname>Doe</Surname>\n                    </PersonName>\n                </ContactInfo>\n            </GuestDetail>\n        </GuestDetails>\n    </ReservationInfo>\n</OTA_CruisePriceBookingRQ>",
              "options": {
                "raw": {
                  "language": "xml"
                }
              }
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_CruisePriceBookingRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_CruisePriceBookingRQ"
              ]
            }
          },
          "response": []
        },
        {
          "name": "OTA_CruiseBookRQ",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});"
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Content-Type",
                "value": "application/xml"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "<OTA_CruiseBookRQ xmlns=\"http://www.opentravel.org/OTA/2003/05\" TimeStamp=\"2025-01-01T12:00:00Z\" Version=\"1.0\">\n{{POS_XML}}    \n    \n    <SailingInfo>\n        <SelectedSailing VoyageID=\"{{VOYAGE_ID}}\">\n            <CruiseLine/>\n        </SelectedSailing>\n        <SelectedCategory PricedCategoryCode=\"{{PRICED_CATEGORY_CODE}}\">\n            <SelectedCabin CabinNumber=\"{{CABIN_NUMBER}}\" GuestRefNumbers=\"1\" Status=\"{{CABIN_STATUS}}\"/>\n        </SelectedCategory>\n    </SailingInfo>\n    <ReservationInfo>\n        <GuestDetails>\n            <!-- Guest 1 -->\n            <GuestDetail>\n                <ContactInfo>\n                    <PersonName>\n                        <GivenName>Alice</GivenName>\n                        <Surname>Williams</Surname>\n                    </PersonName>\n                </ContactInfo>\n            </GuestDetail>\n        </GuestDetails>\n    </ReservationInfo>\n</OTA_CruiseBookRQ>"
            },
            "url": {
              "raw": "{{REST_BASE_URL}}OTA_CruiseBookRQ",
              "host": [
                "{{REST_BASE_URL}}OTA_CruiseBookRQ"
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Flat Files",
      "item": [
        {
          "name": "Download Flatfiles (Save raw response to .zip) Copy",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "exec": [
                  "// Cache-bust param for FlatFile download",
                  "pm.environment.set('CACHE_BUST', Date.now().toString());",
                  ""
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            },
            {
              "listen": "test",
              "script": {
                "exec": [
                  "/// Save response as .zip"
                ],
                "type": "text/javascript",
                "packages": {},
                "requests": {}
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{BEARER_TOKEN}}"
              },
              {
                "key": "Cache-Control",
                "value": "no-cache"
              }
            ],
            "url": {
              "raw": "{{API_BASE}}/FlatFile/{{AGENCY_ID}}?user_key={{USER_KEY}}&includeSwota=true&_t={{CACHE_BUST}}",
              "host": [
                "{{API_BASE}}"
              ],
              "path": [
                "FlatFile",
                "{{AGENCY_ID}}"
              ],
              "query": [
                {
                  "key": "user_key",
                  "value": "{{USER_KEY}}"
                },
                {
                  "key": "includeSwota",
                  "value": "true"
                },
                {
                  "key": "_t",
                  "value": "{{CACHE_BUST}}"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Get OAuth Token (client_credentials) Copy",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('Token request succeeded', function () {",
              "  pm.expect(pm.response.code).to.be.oneOf([200, 201]);",
              "});",
              "",
              "let json;",
              "try {",
              "  json = pm.response.json();",
              "} catch (e) {",
              "  json = {};",
              "}",
              "",
              "if (json && json.access_token) {",
              "  pm.collectionVariables.set('BEARER_TOKEN', json.access_token);",
              "  if (json.expires_in) {",
              "    pm.collectionVariables.set('TOKEN_EXPIRES_AT', (Date.now() + (json.expires_in * 1000)).toString());",
              "  }",
              "}",
              ""
            ],
            "type": "text/javascript",
            "packages": {},
            "requests": {}
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\"client_id\":\"{{AUTH0_CLIENT_ID}}\",\"client_secret\":\"{{AUTH0_CLIENT_SECRET}}\",\"audience\":\"{{AUTH0_AUDIENCE}}\",\"grant_type\":\"client_credentials\"}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{AUTH0_TOKEN_URL}}",
          "host": [
            "{{AUTH0_TOKEN_URL}}"
          ]
        },
        "description": "Auth0 client_credentials token. Saves access_token to {{BEARER_TOKEN}}."
      },
      "response": []
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "packages": {},
        "requests": {},
        "exec": [
          ""
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "packages": {},
        "requests": {},
        "exec": [
          ""
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "API_BASE",
      "value": "https://partner.travelhx.com/api/v1"
    },
    {
      "key": "AGENCY_ID",
      "value": "YOUR_AGENCY_ID"
    },
    {
      "key": "USER_KEY",
      "value": "YOUR_USER_KEY"
    },
    {
      "key": "AUTH0_TOKEN_URL",
      "value": "https://partner-travelhx.eu.auth0.com/oauth/token"
    },
    {
      "key": "AUTH0_CLIENT_ID",
      "value": "YOUR_CLIENT_ID"
    },
    {
      "key": "AUTH0_CLIENT_SECRET",
      "value": "YOUR_CLIENT_SECRET"
    },
    {
      "key": "AUTH0_AUDIENCE",
      "value": "https://partner.travelhx.com/api"
    },
    {
      "key": "BEARER_TOKEN",
      "value": ""
    },
    {
      "key": "TOKEN_EXPIRES_AT",
      "value": ""
    },
    {
      "key": "CACHE_BUST",
      "value": ""
    },
    {
      "key": "REST_BASE_URL",
      "value": "https://bookings.sw.travelhx.com/ota/rest/"
    },
    {
      "key": "POS_XML",
      "value": "  <POS>\n    <Source>\n      <RequestorID Type=\"5\" ID_Context=\"SEAWARE\" ID=\"YOUR_AGENCY_ID\" />\n      <BookingChannel Type=\"1\">\n        <CompanyName>DIRECT-AGENT</CompanyName>\n      </BookingChannel>\n    </Source>\n  </POS>"
    },
    {
      "key": "VOYAGE_ID",
      "value": ""
    },
    {
      "key": "PRICED_CATEGORY_CODE",
      "value": ""
    },
    {
      "key": "CABIN_NUMBER",
      "value": ""
    },
    {
      "key": "CABIN_STATUS",
      "value": ""
    }
  ]
}