@peektravel/app-utilities 0.2.5 → 0.2.7

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
@@ -234,6 +234,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
234
234
  const data = node ?? {};
235
235
  const ticketQuantities = data.ticketQuantities ?? [];
236
236
  const app = data.order?.initialQuote?.source?.actor?.app ?? null;
237
+ const sourceDetails = data.order?.initialQuote?.source?.actor?.name ?? null;
237
238
  const customQuestionAnswers = (data.questionAnswers ?? []).map(
238
239
  (answer) => {
239
240
  const base = {
@@ -257,6 +258,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
257
258
  source: sourceFromApp(app),
258
259
  sourceApp: app || UNKNOWN,
259
260
  sourceDescription: sourceDescriptionFromApp(app),
261
+ sourceDetails,
260
262
  customerName: data.primaryGuest?.name || "",
261
263
  customerEmail: data.primaryGuest?.email || null,
262
264
  customerPhone: data.primaryGuest?.phone || null,
@@ -266,7 +268,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
266
268
  timeslotId: data.timeSnapshot?.legacyId || null,
267
269
  totalTickets: ticketQuantity(ticketQuantities),
268
270
  ticketDescription: formatTickets(ticketQuantities),
269
- tickets: ticketsToTicketArray(ticketQuantities),
271
+ tickets: ticketsToTicketArray(ticketQuantities, includePriceBreakdown),
270
272
  isCanceled: data.reservationStatus === "CANCELED",
271
273
  isNoShow: data.fulfillmentStatusOverride?.status === "NO_SHOW",
272
274
  isCheckedIn: data.checkinStatus !== "NONE",
@@ -367,12 +369,14 @@ function resellerNameFromChannelSnapshot(channelSnapshot) {
367
369
  }
368
370
  return out;
369
371
  }
370
- function ticketsToTicketArray(ticketQuantities) {
372
+ function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
371
373
  if (!ticketQuantities || ticketQuantities.length === 0) return [];
372
374
  return ticketQuantities.map((ticket) => ({
373
375
  name: ticket.resourceOptionSnapshot?.name || "Unknown",
374
376
  quantity: ticket.quantity || 0,
375
- ticketId: ticket.resourceOptionSnapshot?.id || "unknown"
377
+ ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
378
+ listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
379
+ totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
376
380
  }));
377
381
  }
378
382
  function durationInMin(startsAt, endsAt) {
@@ -690,6 +694,7 @@ var bookingQueryFields = `
690
694
  source {
691
695
  actor {
692
696
  app
697
+ name
693
698
  }
694
699
  }
695
700
  }
@@ -737,9 +742,15 @@ var PRICE_BREAKDOWN_FIELDS = `
737
742
  taxes { amount formatted }
738
743
  tips { amount formatted }
739
744
  `;
745
+ var TICKET_VALUE_FIELDS = `
746
+ value {
747
+ price { amount formatted }
748
+ total { amount formatted }
749
+ }
750
+ `;
740
751
  function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
741
752
  const guestsSection = includeGuests ? bookingGuestsFields : "";
742
- const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`) : bookingQueryFields;
753
+ const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
743
754
  return `
744
755
  query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
745
756
  sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
@@ -934,14 +945,18 @@ function buildBookingsVariables(params) {
934
945
  var DEFAULT_PAGE_SIZE2 = 50;
935
946
  var DEFAULT_CANCEL_NOTE = "Canceled";
936
947
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
937
- var BOOKING_ID_PREFIX = "b_";
948
+ var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
949
+ var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
950
+ var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
951
+ var ORDER_DISPLAY_ID_REGEX = /^O-[A-Z0-9]+$/;
938
952
  var PAYMENT_SOURCE_PREFIX = "ps_";
939
953
  var PAYMENT_ID_PREFIX = "pmt_";
940
954
  var ALLOWED_PAYMENT_SOURCE_IDS = ["cash/cash", "custom/other", "custom/voucher"];
941
955
  var CURRENCY_CODE_REGEX = /^[A-Z]{3}$/;
942
956
  var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
943
957
  var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
944
- var ERROR_BOOKING_ID_REQUIRED = "bookingId is required";
958
+ var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
959
+ var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
945
960
  var ERROR_BOOKING_NOT_FOUND = "Booking not found";
946
961
  var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
947
962
  var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
@@ -973,6 +988,7 @@ var BookingService = class {
973
988
  * ```
974
989
  */
975
990
  async getById(bookingId, options = {}) {
991
+ assertBookingId(bookingId);
976
992
  const includeGuests = options.includeGuests ?? false;
977
993
  const includePriceBreakdown = options.includePriceBreakdown ?? false;
978
994
  const body = await this.client.request(
@@ -1021,6 +1037,7 @@ var BookingService = class {
1021
1037
  }
1022
1038
  /** Returns the guests on a booking (primary guest included). */
1023
1039
  async getGuests(bookingId) {
1040
+ assertBookingId(bookingId);
1024
1041
  const body = await this.client.request(
1025
1042
  SALES_ENDPOINT,
1026
1043
  BOOKING_GUESTS_QUERY,
@@ -1034,6 +1051,7 @@ var BookingService = class {
1034
1051
  }
1035
1052
  /** Returns the payments on file for a booking, or null when not found. */
1036
1053
  async getPaymentsOnFile(bookingId) {
1054
+ assertBookingId(bookingId);
1037
1055
  const normalized = normalizeBookingId(bookingId);
1038
1056
  const body = await this.client.request(
1039
1057
  SALES_ENDPOINT,
@@ -1047,6 +1065,7 @@ var BookingService = class {
1047
1065
  * booking, or null when the booking is not found.
1048
1066
  */
1049
1067
  async appendNote(bookingId, note, mode = "append") {
1068
+ assertBookingId(bookingId);
1050
1069
  const normalized = normalizeBookingId(bookingId);
1051
1070
  const booking = await this.getById(normalized);
1052
1071
  if (!booking) {
@@ -1065,6 +1084,7 @@ ${note}`;
1065
1084
  * when not found.
1066
1085
  */
1067
1086
  async setCheckinStatus(bookingId, checkedIn) {
1087
+ assertBookingId(bookingId);
1068
1088
  const normalized = normalizeBookingId(bookingId);
1069
1089
  const checkedInAt = checkedIn ? (/* @__PURE__ */ new Date()).toISOString() : null;
1070
1090
  await this.client.request(SALES_ENDPOINT, UPDATE_BOOKING_CHECKIN_MUTATION, {
@@ -1074,6 +1094,7 @@ ${note}`;
1074
1094
  }
1075
1095
  /** Cancels a booking and returns its id/displayId/status. */
1076
1096
  async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
1097
+ assertBookingId(bookingId);
1077
1098
  const body = await this.client.request(
1078
1099
  SALES_ENDPOINT,
1079
1100
  CANCEL_BOOKING_MUTATION,
@@ -1109,12 +1130,13 @@ ${note}`;
1109
1130
  * @throws {Error} when `paymentSourceId` is missing or not a `ps_…` id / one
1110
1131
  * of `cash/cash`, `custom/other`, `custom/voucher`; when `amount` is not a
1111
1132
  * 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.
1133
+ * `idempotencyKey` is empty; when `bookingId` is not a valid booking id
1134
+ * (`b_…`/`B-…`); when the booking or payment source is not found; or when the
1135
+ * charge fails.
1114
1136
  */
1115
1137
  async makePayment(input) {
1138
+ this.validatePaymentInput(input);
1116
1139
  const normalized = normalizeBookingId(input.bookingId);
1117
- this.validatePaymentInput(input, normalized);
1118
1140
  const onFile = await this.getPaymentsOnFile(normalized);
1119
1141
  if (!onFile) {
1120
1142
  throw new Error("Booking not found");
@@ -1155,8 +1177,8 @@ ${note}`;
1155
1177
  * payments-on-file, then applies the refund.
1156
1178
  */
1157
1179
  async refund(input) {
1180
+ this.validateRefundInput(input);
1158
1181
  const normalized = normalizeBookingId(input.bookingId);
1159
- this.validateRefundInput(input, normalized);
1160
1182
  const onFile = await this.getPaymentsOnFile(normalized);
1161
1183
  if (!onFile) {
1162
1184
  throw new Error("Booking not found");
@@ -1195,9 +1217,7 @@ ${note}`;
1195
1217
  }
1196
1218
  /** Creates an invoice link for a booking's order. */
1197
1219
  async createInvoiceLink(bookingId) {
1198
- if (!bookingId || bookingId.trim().length === 0) {
1199
- throw new Error("bookingId is required");
1200
- }
1220
+ assertBookingId(bookingId);
1201
1221
  const normalized = normalizeBookingId(bookingId);
1202
1222
  const booking = await this.getById(normalized);
1203
1223
  if (!booking || !booking.orderId) {
@@ -1217,6 +1237,7 @@ ${note}`;
1217
1237
  }
1218
1238
  /** Returns the add-ons on a booking, grouped by add-on item (clean model). */
1219
1239
  async listAddons(bookingId) {
1240
+ assertBookingId(bookingId);
1220
1241
  const { items, displayId, orderId, normalizedBookingId } = await this.fetchBookingSale(bookingId);
1221
1242
  const addons = items.map((item) => toBookingAddon(item)).filter((addon) => addon !== null);
1222
1243
  return {
@@ -1251,6 +1272,7 @@ ${note}`;
1251
1272
  * the underlying quote/order mutations fail.
1252
1273
  */
1253
1274
  async addAddon(bookingId, input) {
1275
+ assertBookingId(bookingId);
1254
1276
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1255
1277
  if (!addonOptionId) {
1256
1278
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1303,6 +1325,7 @@ ${note}`;
1303
1325
  * variables. Returns the booking's add-ons after the change.
1304
1326
  */
1305
1327
  async removeAddon(bookingId, input) {
1328
+ assertBookingId(bookingId);
1306
1329
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1307
1330
  if (!addonOptionId) {
1308
1331
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1331,14 +1354,10 @@ ${note}`;
1331
1354
  * booking matched, and parses the node into the internal model.
1332
1355
  */
1333
1356
  async fetchBookingSale(bookingId) {
1334
- const searchString = (bookingId || "").trim();
1335
- if (!searchString) {
1336
- throw new Error(ERROR_BOOKING_ID_REQUIRED);
1337
- }
1338
1357
  const body = await this.client.request(
1339
1358
  SALES_ENDPOINT,
1340
1359
  SALES_ADDONS_QUERY,
1341
- buildSalesAddonsVariables(searchString)
1360
+ buildSalesAddonsVariables(bookingId)
1342
1361
  );
1343
1362
  const edges = body.data?.sales?.edges ?? [];
1344
1363
  if (edges.length === 0) {
@@ -1521,7 +1540,7 @@ ${note}`;
1521
1540
  }
1522
1541
  return matched.productId;
1523
1542
  }
1524
- validatePaymentInput(input, normalizedBookingId) {
1543
+ validatePaymentInput(input) {
1525
1544
  if (!input.paymentSourceId || !input.paymentSourceId.startsWith(PAYMENT_SOURCE_PREFIX) && !ALLOWED_PAYMENT_SOURCE_IDS.includes(input.paymentSourceId)) {
1526
1545
  throw new Error(
1527
1546
  "paymentSourceId is required and must start with 'ps_' or be one of 'cash/cash', 'custom/other', 'custom/voucher'"
@@ -1530,16 +1549,16 @@ ${note}`;
1530
1549
  assertAmount(input.amount);
1531
1550
  assertCurrency(input.currency);
1532
1551
  assertIdempotencyKey(input.idempotencyKey);
1533
- assertBookingId(normalizedBookingId);
1552
+ assertBookingId(input.bookingId);
1534
1553
  }
1535
- validateRefundInput(input, normalizedBookingId) {
1554
+ validateRefundInput(input) {
1536
1555
  if (!input.paymentId || !input.paymentId.startsWith(PAYMENT_ID_PREFIX)) {
1537
1556
  throw new Error("paymentId is required and must start with 'pmt_'");
1538
1557
  }
1539
1558
  assertAmount(input.amount);
1540
1559
  assertCurrency(input.currency);
1541
1560
  assertIdempotencyKey(input.idempotencyKey);
1542
- assertBookingId(normalizedBookingId);
1561
+ assertBookingId(input.bookingId);
1543
1562
  }
1544
1563
  async fetchPaginated(query, baseParams, includeGuests, includePriceBreakdown) {
1545
1564
  const bookings = [];
@@ -1579,9 +1598,14 @@ function assertIdempotencyKey(idempotencyKey) {
1579
1598
  throw new Error("idempotencyKey is required");
1580
1599
  }
1581
1600
  }
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-'");
1601
+ function assertBookingId(bookingId) {
1602
+ if (!(BOOKING_DB_ID_REGEX.test(bookingId) || BOOKING_DISPLAY_ID_REGEX.test(bookingId))) {
1603
+ throw new Error(ERROR_INVALID_BOOKING_ID);
1604
+ }
1605
+ }
1606
+ function assertOrderId(orderId) {
1607
+ if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
1608
+ throw new Error(ERROR_INVALID_ORDER_ID);
1585
1609
  }
1586
1610
  }
1587
1611
  function validateCreateInput(input) {
@@ -1599,6 +1623,9 @@ function validateCreateInput(input) {
1599
1623
  if (input.markAsPaid && !input.idempotencyKey) {
1600
1624
  throw new Error("idempotencyKey is required when markAsPaid is set");
1601
1625
  }
1626
+ if (input.parentOrderId) {
1627
+ assertOrderId(input.parentOrderId);
1628
+ }
1602
1629
  }
1603
1630
  function parseQuantity(value) {
1604
1631
  if (typeof value !== "string" && typeof value !== "number") {
@@ -2303,7 +2330,7 @@ function fromTimeslotNode(node, productId) {
2303
2330
  durationMin: node.minuteLength ?? 0,
2304
2331
  date: node.date || "",
2305
2332
  startTime: node.start ?? null,
2306
- assignedResources: mapAssignedResources(node.resourceAllocations)
2333
+ assignedResources: mapAssignedResources(node.inheritedResourceAllocations)
2307
2334
  };
2308
2335
  }
2309
2336
  function mapAssignedResources(allocations) {
@@ -2313,11 +2340,11 @@ function mapAssignedResources(allocations) {
2313
2340
  return allocations.map((allocation) => {
2314
2341
  const pool = allocation.resourcePool;
2315
2342
  return {
2316
- id: pool?.id || "",
2317
2343
  name: pool?.name || "",
2318
2344
  capacity: pool?.capacity ?? 0,
2319
2345
  category: pool?.category || "",
2320
2346
  quantity: allocation.quantity ?? 0,
2347
+ status: allocation.status || "",
2321
2348
  accountUserId: pool?.accountUser?.id ?? null
2322
2349
  };
2323
2350
  });
@@ -2392,10 +2419,10 @@ var TIMESLOTS_QUERY = `
2392
2419
  minuteLength
2393
2420
  status
2394
2421
  date
2395
- resourceAllocations {
2422
+ inheritedResourceAllocations {
2396
2423
  quantity
2424
+ status
2397
2425
  resourcePool {
2398
- id
2399
2426
  name
2400
2427
  category
2401
2428
  capacity
@@ -2430,10 +2457,10 @@ var TIMESLOT_BY_ID_QUERY = `
2430
2457
  minuteLength
2431
2458
  status
2432
2459
  date
2433
- resourceAllocations {
2460
+ inheritedResourceAllocations {
2434
2461
  quantity
2462
+ status
2435
2463
  resourcePool {
2436
- id
2437
2464
  name
2438
2465
  category
2439
2466
  capacity