@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.js CHANGED
@@ -266,7 +266,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
266
266
  timeslotId: data.timeSnapshot?.legacyId || null,
267
267
  totalTickets: ticketQuantity(ticketQuantities),
268
268
  ticketDescription: formatTickets(ticketQuantities),
269
- tickets: ticketsToTicketArray(ticketQuantities),
269
+ tickets: ticketsToTicketArray(ticketQuantities, includePriceBreakdown),
270
270
  isCanceled: data.reservationStatus === "CANCELED",
271
271
  isNoShow: data.fulfillmentStatusOverride?.status === "NO_SHOW",
272
272
  isCheckedIn: data.checkinStatus !== "NONE",
@@ -367,12 +367,14 @@ function resellerNameFromChannelSnapshot(channelSnapshot) {
367
367
  }
368
368
  return out;
369
369
  }
370
- function ticketsToTicketArray(ticketQuantities) {
370
+ function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
371
371
  if (!ticketQuantities || ticketQuantities.length === 0) return [];
372
372
  return ticketQuantities.map((ticket) => ({
373
373
  name: ticket.resourceOptionSnapshot?.name || "Unknown",
374
374
  quantity: ticket.quantity || 0,
375
- ticketId: ticket.resourceOptionSnapshot?.id || "unknown"
375
+ ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
376
+ listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
377
+ totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
376
378
  }));
377
379
  }
378
380
  function durationInMin(startsAt, endsAt) {
@@ -737,9 +739,15 @@ var PRICE_BREAKDOWN_FIELDS = `
737
739
  taxes { amount formatted }
738
740
  tips { amount formatted }
739
741
  `;
742
+ var TICKET_VALUE_FIELDS = `
743
+ value {
744
+ price { amount formatted }
745
+ total { amount formatted }
746
+ }
747
+ `;
740
748
  function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
741
749
  const guestsSection = includeGuests ? bookingGuestsFields : "";
742
- const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`) : bookingQueryFields;
750
+ const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
743
751
  return `
744
752
  query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
745
753
  sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
@@ -934,14 +942,18 @@ function buildBookingsVariables(params) {
934
942
  var DEFAULT_PAGE_SIZE2 = 50;
935
943
  var DEFAULT_CANCEL_NOTE = "Canceled";
936
944
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
937
- var BOOKING_ID_PREFIX = "b_";
945
+ var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
946
+ var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
947
+ var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
948
+ var ORDER_DISPLAY_ID_REGEX = /^O-[A-Z0-9]+$/;
938
949
  var PAYMENT_SOURCE_PREFIX = "ps_";
939
950
  var PAYMENT_ID_PREFIX = "pmt_";
940
951
  var ALLOWED_PAYMENT_SOURCE_IDS = ["cash/cash", "custom/other", "custom/voucher"];
941
952
  var CURRENCY_CODE_REGEX = /^[A-Z]{3}$/;
942
953
  var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
943
954
  var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
944
- var ERROR_BOOKING_ID_REQUIRED = "bookingId is required";
955
+ var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
956
+ var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
945
957
  var ERROR_BOOKING_NOT_FOUND = "Booking not found";
946
958
  var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
947
959
  var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
@@ -973,6 +985,7 @@ var BookingService = class {
973
985
  * ```
974
986
  */
975
987
  async getById(bookingId, options = {}) {
988
+ assertBookingId(bookingId);
976
989
  const includeGuests = options.includeGuests ?? false;
977
990
  const includePriceBreakdown = options.includePriceBreakdown ?? false;
978
991
  const body = await this.client.request(
@@ -1021,6 +1034,7 @@ var BookingService = class {
1021
1034
  }
1022
1035
  /** Returns the guests on a booking (primary guest included). */
1023
1036
  async getGuests(bookingId) {
1037
+ assertBookingId(bookingId);
1024
1038
  const body = await this.client.request(
1025
1039
  SALES_ENDPOINT,
1026
1040
  BOOKING_GUESTS_QUERY,
@@ -1034,6 +1048,7 @@ var BookingService = class {
1034
1048
  }
1035
1049
  /** Returns the payments on file for a booking, or null when not found. */
1036
1050
  async getPaymentsOnFile(bookingId) {
1051
+ assertBookingId(bookingId);
1037
1052
  const normalized = normalizeBookingId(bookingId);
1038
1053
  const body = await this.client.request(
1039
1054
  SALES_ENDPOINT,
@@ -1047,6 +1062,7 @@ var BookingService = class {
1047
1062
  * booking, or null when the booking is not found.
1048
1063
  */
1049
1064
  async appendNote(bookingId, note, mode = "append") {
1065
+ assertBookingId(bookingId);
1050
1066
  const normalized = normalizeBookingId(bookingId);
1051
1067
  const booking = await this.getById(normalized);
1052
1068
  if (!booking) {
@@ -1065,6 +1081,7 @@ ${note}`;
1065
1081
  * when not found.
1066
1082
  */
1067
1083
  async setCheckinStatus(bookingId, checkedIn) {
1084
+ assertBookingId(bookingId);
1068
1085
  const normalized = normalizeBookingId(bookingId);
1069
1086
  const checkedInAt = checkedIn ? (/* @__PURE__ */ new Date()).toISOString() : null;
1070
1087
  await this.client.request(SALES_ENDPOINT, UPDATE_BOOKING_CHECKIN_MUTATION, {
@@ -1074,6 +1091,7 @@ ${note}`;
1074
1091
  }
1075
1092
  /** Cancels a booking and returns its id/displayId/status. */
1076
1093
  async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
1094
+ assertBookingId(bookingId);
1077
1095
  const body = await this.client.request(
1078
1096
  SALES_ENDPOINT,
1079
1097
  CANCEL_BOOKING_MUTATION,
@@ -1109,12 +1127,13 @@ ${note}`;
1109
1127
  * @throws {Error} when `paymentSourceId` is missing or not a `ps_…` id / one
1110
1128
  * of `cash/cash`, `custom/other`, `custom/voucher`; when `amount` is not a
1111
1129
  * valid number; when `currency` is not a 3-letter uppercase code; when
1112
- * `idempotencyKey` is empty; when `bookingId` does not resolve to a `b_…` id;
1113
- * when the booking or payment source is not found; or when the charge fails.
1130
+ * `idempotencyKey` is empty; when `bookingId` is not a valid booking id
1131
+ * (`b_…`/`B-…`); when the booking or payment source is not found; or when the
1132
+ * charge fails.
1114
1133
  */
1115
1134
  async makePayment(input) {
1135
+ this.validatePaymentInput(input);
1116
1136
  const normalized = normalizeBookingId(input.bookingId);
1117
- this.validatePaymentInput(input, normalized);
1118
1137
  const onFile = await this.getPaymentsOnFile(normalized);
1119
1138
  if (!onFile) {
1120
1139
  throw new Error("Booking not found");
@@ -1155,8 +1174,8 @@ ${note}`;
1155
1174
  * payments-on-file, then applies the refund.
1156
1175
  */
1157
1176
  async refund(input) {
1177
+ this.validateRefundInput(input);
1158
1178
  const normalized = normalizeBookingId(input.bookingId);
1159
- this.validateRefundInput(input, normalized);
1160
1179
  const onFile = await this.getPaymentsOnFile(normalized);
1161
1180
  if (!onFile) {
1162
1181
  throw new Error("Booking not found");
@@ -1195,9 +1214,7 @@ ${note}`;
1195
1214
  }
1196
1215
  /** Creates an invoice link for a booking's order. */
1197
1216
  async createInvoiceLink(bookingId) {
1198
- if (!bookingId || bookingId.trim().length === 0) {
1199
- throw new Error("bookingId is required");
1200
- }
1217
+ assertBookingId(bookingId);
1201
1218
  const normalized = normalizeBookingId(bookingId);
1202
1219
  const booking = await this.getById(normalized);
1203
1220
  if (!booking || !booking.orderId) {
@@ -1217,6 +1234,7 @@ ${note}`;
1217
1234
  }
1218
1235
  /** Returns the add-ons on a booking, grouped by add-on item (clean model). */
1219
1236
  async listAddons(bookingId) {
1237
+ assertBookingId(bookingId);
1220
1238
  const { items, displayId, orderId, normalizedBookingId } = await this.fetchBookingSale(bookingId);
1221
1239
  const addons = items.map((item) => toBookingAddon(item)).filter((addon) => addon !== null);
1222
1240
  return {
@@ -1251,6 +1269,7 @@ ${note}`;
1251
1269
  * the underlying quote/order mutations fail.
1252
1270
  */
1253
1271
  async addAddon(bookingId, input) {
1272
+ assertBookingId(bookingId);
1254
1273
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1255
1274
  if (!addonOptionId) {
1256
1275
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1303,6 +1322,7 @@ ${note}`;
1303
1322
  * variables. Returns the booking's add-ons after the change.
1304
1323
  */
1305
1324
  async removeAddon(bookingId, input) {
1325
+ assertBookingId(bookingId);
1306
1326
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1307
1327
  if (!addonOptionId) {
1308
1328
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1331,14 +1351,10 @@ ${note}`;
1331
1351
  * booking matched, and parses the node into the internal model.
1332
1352
  */
1333
1353
  async fetchBookingSale(bookingId) {
1334
- const searchString = (bookingId || "").trim();
1335
- if (!searchString) {
1336
- throw new Error(ERROR_BOOKING_ID_REQUIRED);
1337
- }
1338
1354
  const body = await this.client.request(
1339
1355
  SALES_ENDPOINT,
1340
1356
  SALES_ADDONS_QUERY,
1341
- buildSalesAddonsVariables(searchString)
1357
+ buildSalesAddonsVariables(bookingId)
1342
1358
  );
1343
1359
  const edges = body.data?.sales?.edges ?? [];
1344
1360
  if (edges.length === 0) {
@@ -1521,7 +1537,7 @@ ${note}`;
1521
1537
  }
1522
1538
  return matched.productId;
1523
1539
  }
1524
- validatePaymentInput(input, normalizedBookingId) {
1540
+ validatePaymentInput(input) {
1525
1541
  if (!input.paymentSourceId || !input.paymentSourceId.startsWith(PAYMENT_SOURCE_PREFIX) && !ALLOWED_PAYMENT_SOURCE_IDS.includes(input.paymentSourceId)) {
1526
1542
  throw new Error(
1527
1543
  "paymentSourceId is required and must start with 'ps_' or be one of 'cash/cash', 'custom/other', 'custom/voucher'"
@@ -1530,16 +1546,16 @@ ${note}`;
1530
1546
  assertAmount(input.amount);
1531
1547
  assertCurrency(input.currency);
1532
1548
  assertIdempotencyKey(input.idempotencyKey);
1533
- assertBookingId(normalizedBookingId);
1549
+ assertBookingId(input.bookingId);
1534
1550
  }
1535
- validateRefundInput(input, normalizedBookingId) {
1551
+ validateRefundInput(input) {
1536
1552
  if (!input.paymentId || !input.paymentId.startsWith(PAYMENT_ID_PREFIX)) {
1537
1553
  throw new Error("paymentId is required and must start with 'pmt_'");
1538
1554
  }
1539
1555
  assertAmount(input.amount);
1540
1556
  assertCurrency(input.currency);
1541
1557
  assertIdempotencyKey(input.idempotencyKey);
1542
- assertBookingId(normalizedBookingId);
1558
+ assertBookingId(input.bookingId);
1543
1559
  }
1544
1560
  async fetchPaginated(query, baseParams, includeGuests, includePriceBreakdown) {
1545
1561
  const bookings = [];
@@ -1579,9 +1595,14 @@ function assertIdempotencyKey(idempotencyKey) {
1579
1595
  throw new Error("idempotencyKey is required");
1580
1596
  }
1581
1597
  }
1582
- function assertBookingId(normalizedBookingId) {
1583
- if (!normalizedBookingId.startsWith(BOOKING_ID_PREFIX)) {
1584
- throw new Error("bookingId is required and must start with 'b_' or 'B-'");
1598
+ function assertBookingId(bookingId) {
1599
+ if (!(BOOKING_DB_ID_REGEX.test(bookingId) || BOOKING_DISPLAY_ID_REGEX.test(bookingId))) {
1600
+ throw new Error(ERROR_INVALID_BOOKING_ID);
1601
+ }
1602
+ }
1603
+ function assertOrderId(orderId) {
1604
+ if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
1605
+ throw new Error(ERROR_INVALID_ORDER_ID);
1585
1606
  }
1586
1607
  }
1587
1608
  function validateCreateInput(input) {
@@ -1599,6 +1620,9 @@ function validateCreateInput(input) {
1599
1620
  if (input.markAsPaid && !input.idempotencyKey) {
1600
1621
  throw new Error("idempotencyKey is required when markAsPaid is set");
1601
1622
  }
1623
+ if (input.parentOrderId) {
1624
+ assertOrderId(input.parentOrderId);
1625
+ }
1602
1626
  }
1603
1627
  function parseQuantity(value) {
1604
1628
  if (typeof value !== "string" && typeof value !== "number") {