@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.cjs CHANGED
@@ -256,6 +256,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
256
256
  const data = node ?? {};
257
257
  const ticketQuantities = data.ticketQuantities ?? [];
258
258
  const app = data.order?.initialQuote?.source?.actor?.app ?? null;
259
+ const sourceDetails = data.order?.initialQuote?.source?.actor?.name ?? null;
259
260
  const customQuestionAnswers = (data.questionAnswers ?? []).map(
260
261
  (answer) => {
261
262
  const base = {
@@ -279,6 +280,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
279
280
  source: sourceFromApp(app),
280
281
  sourceApp: app || UNKNOWN,
281
282
  sourceDescription: sourceDescriptionFromApp(app),
283
+ sourceDetails,
282
284
  customerName: data.primaryGuest?.name || "",
283
285
  customerEmail: data.primaryGuest?.email || null,
284
286
  customerPhone: data.primaryGuest?.phone || null,
@@ -288,7 +290,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
288
290
  timeslotId: data.timeSnapshot?.legacyId || null,
289
291
  totalTickets: ticketQuantity(ticketQuantities),
290
292
  ticketDescription: formatTickets(ticketQuantities),
291
- tickets: ticketsToTicketArray(ticketQuantities),
293
+ tickets: ticketsToTicketArray(ticketQuantities, includePriceBreakdown),
292
294
  isCanceled: data.reservationStatus === "CANCELED",
293
295
  isNoShow: data.fulfillmentStatusOverride?.status === "NO_SHOW",
294
296
  isCheckedIn: data.checkinStatus !== "NONE",
@@ -389,12 +391,14 @@ function resellerNameFromChannelSnapshot(channelSnapshot) {
389
391
  }
390
392
  return out;
391
393
  }
392
- function ticketsToTicketArray(ticketQuantities) {
394
+ function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
393
395
  if (!ticketQuantities || ticketQuantities.length === 0) return [];
394
396
  return ticketQuantities.map((ticket) => ({
395
397
  name: ticket.resourceOptionSnapshot?.name || "Unknown",
396
398
  quantity: ticket.quantity || 0,
397
- ticketId: ticket.resourceOptionSnapshot?.id || "unknown"
399
+ ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
400
+ listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
401
+ totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
398
402
  }));
399
403
  }
400
404
  function durationInMin(startsAt, endsAt) {
@@ -712,6 +716,7 @@ var bookingQueryFields = `
712
716
  source {
713
717
  actor {
714
718
  app
719
+ name
715
720
  }
716
721
  }
717
722
  }
@@ -759,9 +764,15 @@ var PRICE_BREAKDOWN_FIELDS = `
759
764
  taxes { amount formatted }
760
765
  tips { amount formatted }
761
766
  `;
767
+ var TICKET_VALUE_FIELDS = `
768
+ value {
769
+ price { amount formatted }
770
+ total { amount formatted }
771
+ }
772
+ `;
762
773
  function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
763
774
  const guestsSection = includeGuests ? bookingGuestsFields : "";
764
- const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`) : bookingQueryFields;
775
+ const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
765
776
  return `
766
777
  query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
767
778
  sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
@@ -956,14 +967,18 @@ function buildBookingsVariables(params) {
956
967
  var DEFAULT_PAGE_SIZE2 = 50;
957
968
  var DEFAULT_CANCEL_NOTE = "Canceled";
958
969
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
959
- var BOOKING_ID_PREFIX = "b_";
970
+ var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
971
+ var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
972
+ var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
973
+ var ORDER_DISPLAY_ID_REGEX = /^O-[A-Z0-9]+$/;
960
974
  var PAYMENT_SOURCE_PREFIX = "ps_";
961
975
  var PAYMENT_ID_PREFIX = "pmt_";
962
976
  var ALLOWED_PAYMENT_SOURCE_IDS = ["cash/cash", "custom/other", "custom/voucher"];
963
977
  var CURRENCY_CODE_REGEX = /^[A-Z]{3}$/;
964
978
  var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
965
979
  var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
966
- var ERROR_BOOKING_ID_REQUIRED = "bookingId is required";
980
+ var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
981
+ var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
967
982
  var ERROR_BOOKING_NOT_FOUND = "Booking not found";
968
983
  var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
969
984
  var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
@@ -995,6 +1010,7 @@ var BookingService = class {
995
1010
  * ```
996
1011
  */
997
1012
  async getById(bookingId, options = {}) {
1013
+ assertBookingId(bookingId);
998
1014
  const includeGuests = options.includeGuests ?? false;
999
1015
  const includePriceBreakdown = options.includePriceBreakdown ?? false;
1000
1016
  const body = await this.client.request(
@@ -1043,6 +1059,7 @@ var BookingService = class {
1043
1059
  }
1044
1060
  /** Returns the guests on a booking (primary guest included). */
1045
1061
  async getGuests(bookingId) {
1062
+ assertBookingId(bookingId);
1046
1063
  const body = await this.client.request(
1047
1064
  SALES_ENDPOINT,
1048
1065
  BOOKING_GUESTS_QUERY,
@@ -1056,6 +1073,7 @@ var BookingService = class {
1056
1073
  }
1057
1074
  /** Returns the payments on file for a booking, or null when not found. */
1058
1075
  async getPaymentsOnFile(bookingId) {
1076
+ assertBookingId(bookingId);
1059
1077
  const normalized = normalizeBookingId(bookingId);
1060
1078
  const body = await this.client.request(
1061
1079
  SALES_ENDPOINT,
@@ -1069,6 +1087,7 @@ var BookingService = class {
1069
1087
  * booking, or null when the booking is not found.
1070
1088
  */
1071
1089
  async appendNote(bookingId, note, mode = "append") {
1090
+ assertBookingId(bookingId);
1072
1091
  const normalized = normalizeBookingId(bookingId);
1073
1092
  const booking = await this.getById(normalized);
1074
1093
  if (!booking) {
@@ -1087,6 +1106,7 @@ ${note}`;
1087
1106
  * when not found.
1088
1107
  */
1089
1108
  async setCheckinStatus(bookingId, checkedIn) {
1109
+ assertBookingId(bookingId);
1090
1110
  const normalized = normalizeBookingId(bookingId);
1091
1111
  const checkedInAt = checkedIn ? (/* @__PURE__ */ new Date()).toISOString() : null;
1092
1112
  await this.client.request(SALES_ENDPOINT, UPDATE_BOOKING_CHECKIN_MUTATION, {
@@ -1096,6 +1116,7 @@ ${note}`;
1096
1116
  }
1097
1117
  /** Cancels a booking and returns its id/displayId/status. */
1098
1118
  async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
1119
+ assertBookingId(bookingId);
1099
1120
  const body = await this.client.request(
1100
1121
  SALES_ENDPOINT,
1101
1122
  CANCEL_BOOKING_MUTATION,
@@ -1131,12 +1152,13 @@ ${note}`;
1131
1152
  * @throws {Error} when `paymentSourceId` is missing or not a `ps_…` id / one
1132
1153
  * of `cash/cash`, `custom/other`, `custom/voucher`; when `amount` is not a
1133
1154
  * 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.
1155
+ * `idempotencyKey` is empty; when `bookingId` is not a valid booking id
1156
+ * (`b_…`/`B-…`); when the booking or payment source is not found; or when the
1157
+ * charge fails.
1136
1158
  */
1137
1159
  async makePayment(input) {
1160
+ this.validatePaymentInput(input);
1138
1161
  const normalized = normalizeBookingId(input.bookingId);
1139
- this.validatePaymentInput(input, normalized);
1140
1162
  const onFile = await this.getPaymentsOnFile(normalized);
1141
1163
  if (!onFile) {
1142
1164
  throw new Error("Booking not found");
@@ -1177,8 +1199,8 @@ ${note}`;
1177
1199
  * payments-on-file, then applies the refund.
1178
1200
  */
1179
1201
  async refund(input) {
1202
+ this.validateRefundInput(input);
1180
1203
  const normalized = normalizeBookingId(input.bookingId);
1181
- this.validateRefundInput(input, normalized);
1182
1204
  const onFile = await this.getPaymentsOnFile(normalized);
1183
1205
  if (!onFile) {
1184
1206
  throw new Error("Booking not found");
@@ -1217,9 +1239,7 @@ ${note}`;
1217
1239
  }
1218
1240
  /** Creates an invoice link for a booking's order. */
1219
1241
  async createInvoiceLink(bookingId) {
1220
- if (!bookingId || bookingId.trim().length === 0) {
1221
- throw new Error("bookingId is required");
1222
- }
1242
+ assertBookingId(bookingId);
1223
1243
  const normalized = normalizeBookingId(bookingId);
1224
1244
  const booking = await this.getById(normalized);
1225
1245
  if (!booking || !booking.orderId) {
@@ -1239,6 +1259,7 @@ ${note}`;
1239
1259
  }
1240
1260
  /** Returns the add-ons on a booking, grouped by add-on item (clean model). */
1241
1261
  async listAddons(bookingId) {
1262
+ assertBookingId(bookingId);
1242
1263
  const { items, displayId, orderId, normalizedBookingId } = await this.fetchBookingSale(bookingId);
1243
1264
  const addons = items.map((item) => toBookingAddon(item)).filter((addon) => addon !== null);
1244
1265
  return {
@@ -1273,6 +1294,7 @@ ${note}`;
1273
1294
  * the underlying quote/order mutations fail.
1274
1295
  */
1275
1296
  async addAddon(bookingId, input) {
1297
+ assertBookingId(bookingId);
1276
1298
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1277
1299
  if (!addonOptionId) {
1278
1300
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1325,6 +1347,7 @@ ${note}`;
1325
1347
  * variables. Returns the booking's add-ons after the change.
1326
1348
  */
1327
1349
  async removeAddon(bookingId, input) {
1350
+ assertBookingId(bookingId);
1328
1351
  const addonOptionId = (input?.addonOptionId || input?.addonId || "").trim();
1329
1352
  if (!addonOptionId) {
1330
1353
  throw new Error(ERROR_ADDON_OPTION_ID_REQUIRED);
@@ -1353,14 +1376,10 @@ ${note}`;
1353
1376
  * booking matched, and parses the node into the internal model.
1354
1377
  */
1355
1378
  async fetchBookingSale(bookingId) {
1356
- const searchString = (bookingId || "").trim();
1357
- if (!searchString) {
1358
- throw new Error(ERROR_BOOKING_ID_REQUIRED);
1359
- }
1360
1379
  const body = await this.client.request(
1361
1380
  SALES_ENDPOINT,
1362
1381
  SALES_ADDONS_QUERY,
1363
- buildSalesAddonsVariables(searchString)
1382
+ buildSalesAddonsVariables(bookingId)
1364
1383
  );
1365
1384
  const edges = body.data?.sales?.edges ?? [];
1366
1385
  if (edges.length === 0) {
@@ -1543,7 +1562,7 @@ ${note}`;
1543
1562
  }
1544
1563
  return matched.productId;
1545
1564
  }
1546
- validatePaymentInput(input, normalizedBookingId) {
1565
+ validatePaymentInput(input) {
1547
1566
  if (!input.paymentSourceId || !input.paymentSourceId.startsWith(PAYMENT_SOURCE_PREFIX) && !ALLOWED_PAYMENT_SOURCE_IDS.includes(input.paymentSourceId)) {
1548
1567
  throw new Error(
1549
1568
  "paymentSourceId is required and must start with 'ps_' or be one of 'cash/cash', 'custom/other', 'custom/voucher'"
@@ -1552,16 +1571,16 @@ ${note}`;
1552
1571
  assertAmount(input.amount);
1553
1572
  assertCurrency(input.currency);
1554
1573
  assertIdempotencyKey(input.idempotencyKey);
1555
- assertBookingId(normalizedBookingId);
1574
+ assertBookingId(input.bookingId);
1556
1575
  }
1557
- validateRefundInput(input, normalizedBookingId) {
1576
+ validateRefundInput(input) {
1558
1577
  if (!input.paymentId || !input.paymentId.startsWith(PAYMENT_ID_PREFIX)) {
1559
1578
  throw new Error("paymentId is required and must start with 'pmt_'");
1560
1579
  }
1561
1580
  assertAmount(input.amount);
1562
1581
  assertCurrency(input.currency);
1563
1582
  assertIdempotencyKey(input.idempotencyKey);
1564
- assertBookingId(normalizedBookingId);
1583
+ assertBookingId(input.bookingId);
1565
1584
  }
1566
1585
  async fetchPaginated(query, baseParams, includeGuests, includePriceBreakdown) {
1567
1586
  const bookings = [];
@@ -1601,9 +1620,14 @@ function assertIdempotencyKey(idempotencyKey) {
1601
1620
  throw new Error("idempotencyKey is required");
1602
1621
  }
1603
1622
  }
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-'");
1623
+ function assertBookingId(bookingId) {
1624
+ if (!(BOOKING_DB_ID_REGEX.test(bookingId) || BOOKING_DISPLAY_ID_REGEX.test(bookingId))) {
1625
+ throw new Error(ERROR_INVALID_BOOKING_ID);
1626
+ }
1627
+ }
1628
+ function assertOrderId(orderId) {
1629
+ if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
1630
+ throw new Error(ERROR_INVALID_ORDER_ID);
1607
1631
  }
1608
1632
  }
1609
1633
  function validateCreateInput(input) {
@@ -1621,6 +1645,9 @@ function validateCreateInput(input) {
1621
1645
  if (input.markAsPaid && !input.idempotencyKey) {
1622
1646
  throw new Error("idempotencyKey is required when markAsPaid is set");
1623
1647
  }
1648
+ if (input.parentOrderId) {
1649
+ assertOrderId(input.parentOrderId);
1650
+ }
1624
1651
  }
1625
1652
  function parseQuantity(value) {
1626
1653
  if (typeof value !== "string" && typeof value !== "number") {
@@ -2325,7 +2352,7 @@ function fromTimeslotNode(node, productId) {
2325
2352
  durationMin: node.minuteLength ?? 0,
2326
2353
  date: node.date || "",
2327
2354
  startTime: node.start ?? null,
2328
- assignedResources: mapAssignedResources(node.resourceAllocations)
2355
+ assignedResources: mapAssignedResources(node.inheritedResourceAllocations)
2329
2356
  };
2330
2357
  }
2331
2358
  function mapAssignedResources(allocations) {
@@ -2335,11 +2362,11 @@ function mapAssignedResources(allocations) {
2335
2362
  return allocations.map((allocation) => {
2336
2363
  const pool = allocation.resourcePool;
2337
2364
  return {
2338
- id: pool?.id || "",
2339
2365
  name: pool?.name || "",
2340
2366
  capacity: pool?.capacity ?? 0,
2341
2367
  category: pool?.category || "",
2342
2368
  quantity: allocation.quantity ?? 0,
2369
+ status: allocation.status || "",
2343
2370
  accountUserId: pool?.accountUser?.id ?? null
2344
2371
  };
2345
2372
  });
@@ -2414,10 +2441,10 @@ var TIMESLOTS_QUERY = `
2414
2441
  minuteLength
2415
2442
  status
2416
2443
  date
2417
- resourceAllocations {
2444
+ inheritedResourceAllocations {
2418
2445
  quantity
2446
+ status
2419
2447
  resourcePool {
2420
- id
2421
2448
  name
2422
2449
  category
2423
2450
  capacity
@@ -2452,10 +2479,10 @@ var TIMESLOT_BY_ID_QUERY = `
2452
2479
  minuteLength
2453
2480
  status
2454
2481
  date
2455
- resourceAllocations {
2482
+ inheritedResourceAllocations {
2456
2483
  quantity
2484
+ status
2457
2485
  resourcePool {
2458
- id
2459
2486
  name
2460
2487
  category
2461
2488
  capacity