@peektravel/app-utilities 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -288,7 +288,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
288
288
  timeslotId: data.timeSnapshot?.legacyId || null,
289
289
  totalTickets: ticketQuantity(ticketQuantities),
290
290
  ticketDescription: formatTickets(ticketQuantities),
291
- tickets: ticketsToTicketArray(ticketQuantities),
291
+ tickets: ticketsToTicketArray(ticketQuantities, includePriceBreakdown),
292
292
  isCanceled: data.reservationStatus === "CANCELED",
293
293
  isNoShow: data.fulfillmentStatusOverride?.status === "NO_SHOW",
294
294
  isCheckedIn: data.checkinStatus !== "NONE",
@@ -389,12 +389,14 @@ function resellerNameFromChannelSnapshot(channelSnapshot) {
389
389
  }
390
390
  return out;
391
391
  }
392
- function ticketsToTicketArray(ticketQuantities) {
392
+ function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
393
393
  if (!ticketQuantities || ticketQuantities.length === 0) return [];
394
394
  return ticketQuantities.map((ticket) => ({
395
395
  name: ticket.resourceOptionSnapshot?.name || "Unknown",
396
396
  quantity: ticket.quantity || 0,
397
- ticketId: ticket.resourceOptionSnapshot?.id || "unknown"
397
+ ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
398
+ listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
399
+ totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
398
400
  }));
399
401
  }
400
402
  function durationInMin(startsAt, endsAt) {
@@ -759,9 +761,15 @@ var PRICE_BREAKDOWN_FIELDS = `
759
761
  taxes { amount formatted }
760
762
  tips { amount formatted }
761
763
  `;
764
+ var TICKET_VALUE_FIELDS = `
765
+ value {
766
+ price { amount formatted }
767
+ total { amount formatted }
768
+ }
769
+ `;
762
770
  function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
763
771
  const guestsSection = includeGuests ? bookingGuestsFields : "";
764
- const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`) : bookingQueryFields;
772
+ const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
765
773
  return `
766
774
  query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
767
775
  sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
@@ -956,14 +964,18 @@ function buildBookingsVariables(params) {
956
964
  var DEFAULT_PAGE_SIZE2 = 50;
957
965
  var DEFAULT_CANCEL_NOTE = "Canceled";
958
966
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
959
- var BOOKING_ID_PREFIX = "b_";
967
+ var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
968
+ var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
969
+ var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
970
+ var ORDER_DISPLAY_ID_REGEX = /^O-[A-Z0-9]+$/;
960
971
  var PAYMENT_SOURCE_PREFIX = "ps_";
961
972
  var PAYMENT_ID_PREFIX = "pmt_";
962
973
  var ALLOWED_PAYMENT_SOURCE_IDS = ["cash/cash", "custom/other", "custom/voucher"];
963
974
  var CURRENCY_CODE_REGEX = /^[A-Z]{3}$/;
964
975
  var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
965
976
  var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
966
- var ERROR_BOOKING_ID_REQUIRED = "bookingId is required";
977
+ var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
978
+ var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
967
979
  var ERROR_BOOKING_NOT_FOUND = "Booking not found";
968
980
  var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
969
981
  var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
@@ -995,6 +1007,7 @@ var BookingService = class {
995
1007
  * ```
996
1008
  */
997
1009
  async getById(bookingId, options = {}) {
1010
+ assertBookingId(bookingId);
998
1011
  const includeGuests = options.includeGuests ?? false;
999
1012
  const includePriceBreakdown = options.includePriceBreakdown ?? false;
1000
1013
  const body = await this.client.request(
@@ -1043,6 +1056,7 @@ var BookingService = class {
1043
1056
  }
1044
1057
  /** Returns the guests on a booking (primary guest included). */
1045
1058
  async getGuests(bookingId) {
1059
+ assertBookingId(bookingId);
1046
1060
  const body = await this.client.request(
1047
1061
  SALES_ENDPOINT,
1048
1062
  BOOKING_GUESTS_QUERY,
@@ -1056,6 +1070,7 @@ var BookingService = class {
1056
1070
  }
1057
1071
  /** Returns the payments on file for a booking, or null when not found. */
1058
1072
  async getPaymentsOnFile(bookingId) {
1073
+ assertBookingId(bookingId);
1059
1074
  const normalized = normalizeBookingId(bookingId);
1060
1075
  const body = await this.client.request(
1061
1076
  SALES_ENDPOINT,
@@ -1069,6 +1084,7 @@ var BookingService = class {
1069
1084
  * booking, or null when the booking is not found.
1070
1085
  */
1071
1086
  async appendNote(bookingId, note, mode = "append") {
1087
+ assertBookingId(bookingId);
1072
1088
  const normalized = normalizeBookingId(bookingId);
1073
1089
  const booking = await this.getById(normalized);
1074
1090
  if (!booking) {
@@ -1087,6 +1103,7 @@ ${note}`;
1087
1103
  * when not found.
1088
1104
  */
1089
1105
  async setCheckinStatus(bookingId, checkedIn) {
1106
+ assertBookingId(bookingId);
1090
1107
  const normalized = normalizeBookingId(bookingId);
1091
1108
  const checkedInAt = checkedIn ? (/* @__PURE__ */ new Date()).toISOString() : null;
1092
1109
  await this.client.request(SALES_ENDPOINT, UPDATE_BOOKING_CHECKIN_MUTATION, {
@@ -1096,6 +1113,7 @@ ${note}`;
1096
1113
  }
1097
1114
  /** Cancels a booking and returns its id/displayId/status. */
1098
1115
  async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
1116
+ assertBookingId(bookingId);
1099
1117
  const body = await this.client.request(
1100
1118
  SALES_ENDPOINT,
1101
1119
  CANCEL_BOOKING_MUTATION,
@@ -1131,12 +1149,13 @@ ${note}`;
1131
1149
  * @throws {Error} when `paymentSourceId` is missing or not a `ps_…` id / one
1132
1150
  * of `cash/cash`, `custom/other`, `custom/voucher`; when `amount` is not a
1133
1151
  * valid number; when `currency` is not a 3-letter uppercase code; when
1134
- * `idempotencyKey` is empty; when `bookingId` does not resolve to a `b_…` id;
1135
- * when the booking or payment source is not found; or when the charge fails.
1152
+ * `idempotencyKey` is empty; when `bookingId` is not a valid booking id
1153
+ * (`b_…`/`B-…`); when the booking or payment source is not found; or when the
1154
+ * charge fails.
1136
1155
  */
1137
1156
  async makePayment(input) {
1157
+ this.validatePaymentInput(input);
1138
1158
  const normalized = normalizeBookingId(input.bookingId);
1139
- this.validatePaymentInput(input, normalized);
1140
1159
  const onFile = await this.getPaymentsOnFile(normalized);
1141
1160
  if (!onFile) {
1142
1161
  throw new Error("Booking not found");
@@ -1177,8 +1196,8 @@ ${note}`;
1177
1196
  * payments-on-file, then applies the refund.
1178
1197
  */
1179
1198
  async refund(input) {
1199
+ this.validateRefundInput(input);
1180
1200
  const normalized = normalizeBookingId(input.bookingId);
1181
- this.validateRefundInput(input, normalized);
1182
1201
  const onFile = await this.getPaymentsOnFile(normalized);
1183
1202
  if (!onFile) {
1184
1203
  throw new Error("Booking not found");
@@ -1217,9 +1236,7 @@ ${note}`;
1217
1236
  }
1218
1237
  /** Creates an invoice link for a booking's order. */
1219
1238
  async createInvoiceLink(bookingId) {
1220
- if (!bookingId || bookingId.trim().length === 0) {
1221
- throw new Error("bookingId is required");
1222
- }
1239
+ assertBookingId(bookingId);
1223
1240
  const normalized = normalizeBookingId(bookingId);
1224
1241
  const booking = await this.getById(normalized);
1225
1242
  if (!booking || !booking.orderId) {
@@ -1239,6 +1256,7 @@ ${note}`;
1239
1256
  }
1240
1257
  /** Returns the add-ons on a booking, grouped by add-on item (clean model). */
1241
1258
  async listAddons(bookingId) {
1259
+ assertBookingId(bookingId);
1242
1260
  const { items, displayId, orderId, normalizedBookingId } = await this.fetchBookingSale(bookingId);
1243
1261
  const addons = items.map((item) => toBookingAddon(item)).filter((addon) => addon !== null);
1244
1262
  return {
@@ -1273,6 +1291,7 @@ ${note}`;
1273
1291
  * the underlying quote/order mutations fail.
1274
1292
  */
1275
1293
  async addAddon(bookingId, input) {
1294
+ assertBookingId(bookingId);
1276
1295
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1277
1296
  if (!addonOptionId) {
1278
1297
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1325,6 +1344,7 @@ ${note}`;
1325
1344
  * variables. Returns the booking's add-ons after the change.
1326
1345
  */
1327
1346
  async removeAddon(bookingId, input) {
1347
+ assertBookingId(bookingId);
1328
1348
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1329
1349
  if (!addonOptionId) {
1330
1350
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1353,14 +1373,10 @@ ${note}`;
1353
1373
  * booking matched, and parses the node into the internal model.
1354
1374
  */
1355
1375
  async fetchBookingSale(bookingId) {
1356
- const searchString = (bookingId || "").trim();
1357
- if (!searchString) {
1358
- throw new Error(ERROR_BOOKING_ID_REQUIRED);
1359
- }
1360
1376
  const body = await this.client.request(
1361
1377
  SALES_ENDPOINT,
1362
1378
  SALES_ADDONS_QUERY,
1363
- buildSalesAddonsVariables(searchString)
1379
+ buildSalesAddonsVariables(bookingId)
1364
1380
  );
1365
1381
  const edges = body.data?.sales?.edges ?? [];
1366
1382
  if (edges.length === 0) {
@@ -1543,7 +1559,7 @@ ${note}`;
1543
1559
  }
1544
1560
  return matched.productId;
1545
1561
  }
1546
- validatePaymentInput(input, normalizedBookingId) {
1562
+ validatePaymentInput(input) {
1547
1563
  if (!input.paymentSourceId || !input.paymentSourceId.startsWith(PAYMENT_SOURCE_PREFIX) && !ALLOWED_PAYMENT_SOURCE_IDS.includes(input.paymentSourceId)) {
1548
1564
  throw new Error(
1549
1565
  "paymentSourceId is required and must start with 'ps_' or be one of 'cash/cash', 'custom/other', 'custom/voucher'"
@@ -1552,16 +1568,16 @@ ${note}`;
1552
1568
  assertAmount(input.amount);
1553
1569
  assertCurrency(input.currency);
1554
1570
  assertIdempotencyKey(input.idempotencyKey);
1555
- assertBookingId(normalizedBookingId);
1571
+ assertBookingId(input.bookingId);
1556
1572
  }
1557
- validateRefundInput(input, normalizedBookingId) {
1573
+ validateRefundInput(input) {
1558
1574
  if (!input.paymentId || !input.paymentId.startsWith(PAYMENT_ID_PREFIX)) {
1559
1575
  throw new Error("paymentId is required and must start with 'pmt_'");
1560
1576
  }
1561
1577
  assertAmount(input.amount);
1562
1578
  assertCurrency(input.currency);
1563
1579
  assertIdempotencyKey(input.idempotencyKey);
1564
- assertBookingId(normalizedBookingId);
1580
+ assertBookingId(input.bookingId);
1565
1581
  }
1566
1582
  async fetchPaginated(query, baseParams, includeGuests, includePriceBreakdown) {
1567
1583
  const bookings = [];
@@ -1601,9 +1617,14 @@ function assertIdempotencyKey(idempotencyKey) {
1601
1617
  throw new Error("idempotencyKey is required");
1602
1618
  }
1603
1619
  }
1604
- function assertBookingId(normalizedBookingId) {
1605
- if (!normalizedBookingId.startsWith(BOOKING_ID_PREFIX)) {
1606
- throw new Error("bookingId is required and must start with 'b_' or 'B-'");
1620
+ function assertBookingId(bookingId) {
1621
+ if (!(BOOKING_DB_ID_REGEX.test(bookingId) || BOOKING_DISPLAY_ID_REGEX.test(bookingId))) {
1622
+ throw new Error(ERROR_INVALID_BOOKING_ID);
1623
+ }
1624
+ }
1625
+ function assertOrderId(orderId) {
1626
+ if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
1627
+ throw new Error(ERROR_INVALID_ORDER_ID);
1607
1628
  }
1608
1629
  }
1609
1630
  function validateCreateInput(input) {
@@ -1621,6 +1642,9 @@ function validateCreateInput(input) {
1621
1642
  if (input.markAsPaid && !input.idempotencyKey) {
1622
1643
  throw new Error("idempotencyKey is required when markAsPaid is set");
1623
1644
  }
1645
+ if (input.parentOrderId) {
1646
+ assertOrderId(input.parentOrderId);
1647
+ }
1624
1648
  }
1625
1649
  function parseQuantity(value) {
1626
1650
  if (typeof value !== "string" && typeof value !== "number") {