@peektravel/app-utilities 0.9.1 → 0.9.3
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/README.md +5 -1
- package/dist/index.cjs +627 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +350 -5
- package/dist/index.d.ts +350 -5
- package/dist/index.js +627 -80
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +1 -1
- package/llms.txt +36 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -338,6 +338,20 @@ var SEARCH_BY_ACTIVITY_DATE = "activityDate";
|
|
|
338
338
|
function normalizeBookingId(bookingId) {
|
|
339
339
|
return bookingId.toLowerCase().replace(/-/g, "_");
|
|
340
340
|
}
|
|
341
|
+
function buildSalesQuery(nodeBody) {
|
|
342
|
+
return `
|
|
343
|
+
query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
|
|
344
|
+
sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
|
|
345
|
+
pageInfo { endCursor hasNextPage }
|
|
346
|
+
edges {
|
|
347
|
+
node {
|
|
348
|
+
${nodeBody}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
341
355
|
var GUEST_IDENTITY_PII_FIELDS = "name country dateOfBirth email isGdpr";
|
|
342
356
|
var GUEST_CONTACT_PII_FIELDS = `phone
|
|
343
357
|
postalCode
|
|
@@ -431,6 +445,7 @@ function buildBookingQueryFields(fullCustomerAccess) {
|
|
|
431
445
|
availabilityTimeId
|
|
432
446
|
${fullCustomerAccess ? "bookingPortalUrl" : ""}
|
|
433
447
|
operatorNotes
|
|
448
|
+
operatorStatus
|
|
434
449
|
value {
|
|
435
450
|
total {
|
|
436
451
|
formatted
|
|
@@ -513,67 +528,40 @@ function buildBookingsListingQuery(includeGuests, includePriceBreakdown, fullCus
|
|
|
513
528
|
const guestsSection = includeGuests ? buildBookingGuestsFields(fullCustomerAccess) : "";
|
|
514
529
|
const baseFields = buildBookingQueryFields(fullCustomerAccess);
|
|
515
530
|
const fields = includePriceBreakdown ? baseFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : baseFields;
|
|
516
|
-
return `
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
edges {
|
|
521
|
-
node {
|
|
522
|
-
... on Booking {
|
|
523
|
-
${fields}
|
|
524
|
-
${guestsSection}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
531
|
+
return buildSalesQuery(`
|
|
532
|
+
... on Booking {
|
|
533
|
+
${fields}
|
|
534
|
+
${guestsSection}
|
|
529
535
|
}
|
|
530
|
-
|
|
536
|
+
`);
|
|
531
537
|
}
|
|
532
538
|
function buildBookingGuestsQuery(fullCustomerAccess) {
|
|
533
|
-
return `
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
node {
|
|
539
|
-
... on Booking {
|
|
540
|
-
displayId
|
|
541
|
-
id
|
|
542
|
-
${buildBookingGuestsFields(fullCustomerAccess)}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
539
|
+
return buildSalesQuery(`
|
|
540
|
+
... on Booking {
|
|
541
|
+
displayId
|
|
542
|
+
id
|
|
543
|
+
${buildBookingGuestsFields(fullCustomerAccess)}
|
|
546
544
|
}
|
|
547
|
-
|
|
548
|
-
`;
|
|
545
|
+
`);
|
|
549
546
|
}
|
|
550
|
-
var BOOKING_PAYMENTS_ON_FILE_QUERY = `
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
id
|
|
559
|
-
paymentSource { id }
|
|
560
|
-
appliedAt
|
|
561
|
-
currentAmount { amount currency }
|
|
562
|
-
refundableAmount { amount currency }
|
|
563
|
-
}
|
|
564
|
-
id
|
|
565
|
-
displayId
|
|
566
|
-
paymentSources { description type id }
|
|
567
|
-
}
|
|
568
|
-
... on Booking {
|
|
569
|
-
displayId
|
|
570
|
-
id
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
547
|
+
var BOOKING_PAYMENTS_ON_FILE_QUERY = buildSalesQuery(`
|
|
548
|
+
order {
|
|
549
|
+
payments {
|
|
550
|
+
id
|
|
551
|
+
paymentSource { id }
|
|
552
|
+
appliedAt
|
|
553
|
+
currentAmount { amount currency }
|
|
554
|
+
refundableAmount { amount currency }
|
|
574
555
|
}
|
|
556
|
+
id
|
|
557
|
+
displayId
|
|
558
|
+
paymentSources { description type id }
|
|
575
559
|
}
|
|
576
|
-
|
|
560
|
+
... on Booking {
|
|
561
|
+
displayId
|
|
562
|
+
id
|
|
563
|
+
}
|
|
564
|
+
`);
|
|
577
565
|
var UPDATE_OPERATOR_NOTES_MUTATION = `
|
|
578
566
|
mutation Account($input: UpdateOperatorNotesForBookingInput!) {
|
|
579
567
|
updateOperatorNotesForBooking(input: $input) {
|
|
@@ -581,6 +569,13 @@ var UPDATE_OPERATOR_NOTES_MUTATION = `
|
|
|
581
569
|
}
|
|
582
570
|
}
|
|
583
571
|
`;
|
|
572
|
+
var UPDATE_OPERATOR_STATUS_MUTATION = `
|
|
573
|
+
mutation Account($input: UpdateOperatorStatusForBookingInput!) {
|
|
574
|
+
updateOperatorStatusForBooking(input: $input) {
|
|
575
|
+
booking { operatorStatus }
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
`;
|
|
584
579
|
var UPDATE_BOOKING_CHECKIN_MUTATION = `
|
|
585
580
|
mutation Account($input: UpdateBookingCheckInInput!) {
|
|
586
581
|
updateBookingCheckIn(input: $input) {
|
|
@@ -803,6 +798,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
|
|
|
803
798
|
availabilityTimeId: data.availabilityTimeId || null,
|
|
804
799
|
portalUrl: data.bookingPortalUrl || null,
|
|
805
800
|
notes: data.operatorNotes || "",
|
|
801
|
+
customStatus: data.operatorStatus || null,
|
|
806
802
|
valueDisplay: data.value?.total?.formatted || "",
|
|
807
803
|
valueAmount: data.value?.total?.amount || "",
|
|
808
804
|
outstandingBalanceAmount: data.balance?.total?.amount || "",
|
|
@@ -957,6 +953,251 @@ function fromBookingGuestsResponse(response) {
|
|
|
957
953
|
return guests;
|
|
958
954
|
}
|
|
959
955
|
|
|
956
|
+
// src/internal/peek/bookings/metadata-converter.ts
|
|
957
|
+
var UPSERT_SUCCESS_TYPENAME = "UpsertBookingFieldResponsesSuccess";
|
|
958
|
+
function fromBookingMetaDataNode(node) {
|
|
959
|
+
return (node?.fieldResponses ?? []).map(fromFieldResponseNode);
|
|
960
|
+
}
|
|
961
|
+
function fromFieldResponseNode(response) {
|
|
962
|
+
const field = response.fieldLocation?.field;
|
|
963
|
+
const prompt = response.fieldLocation?.prompt;
|
|
964
|
+
return {
|
|
965
|
+
id: field?.id || "",
|
|
966
|
+
name: field?.name || "",
|
|
967
|
+
slug: field?.slug || "",
|
|
968
|
+
type: field?.type || "",
|
|
969
|
+
prompt: prompt?.label ?? null,
|
|
970
|
+
promptHint: prompt?.hint ?? null,
|
|
971
|
+
isRequired: Boolean(prompt?.isRequired),
|
|
972
|
+
refid: response.refid || "",
|
|
973
|
+
value: fromValueNode(response.value)
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function fromValueNode(value) {
|
|
977
|
+
if (!value || !value.__typename) return null;
|
|
978
|
+
switch (value.__typename) {
|
|
979
|
+
case "AgeFieldResponseValue": {
|
|
980
|
+
const age = value.age ?? 0;
|
|
981
|
+
return { kind: "age", age, displayValue: String(age) };
|
|
982
|
+
}
|
|
983
|
+
case "AttachmentFieldResponseValue": {
|
|
984
|
+
const attachmentUrl = value.attachmentUrl ?? "";
|
|
985
|
+
const name = value.name ?? null;
|
|
986
|
+
return {
|
|
987
|
+
kind: "attachment",
|
|
988
|
+
attachmentUrl,
|
|
989
|
+
mime: value.mime ?? null,
|
|
990
|
+
name,
|
|
991
|
+
size: value.size ?? null,
|
|
992
|
+
displayValue: name || attachmentUrl
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
case "BarcodeFieldResponseValue": {
|
|
996
|
+
const barcodeValue = value.barcodeValue ?? "";
|
|
997
|
+
return {
|
|
998
|
+
kind: "barcode",
|
|
999
|
+
barcodeType: value.barcodeType ?? null,
|
|
1000
|
+
barcodeValue,
|
|
1001
|
+
displayValue: barcodeValue
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
case "BooleanFieldResponseValue": {
|
|
1005
|
+
const bool = Boolean(value.boolean);
|
|
1006
|
+
return { kind: "boolean", boolean: bool, displayValue: yesNo(bool) };
|
|
1007
|
+
}
|
|
1008
|
+
case "CheckboxFieldResponseValue": {
|
|
1009
|
+
const isChecked = Boolean(value.isChecked);
|
|
1010
|
+
return { kind: "checkbox", isChecked, displayValue: yesNo(isChecked) };
|
|
1011
|
+
}
|
|
1012
|
+
case "CurrencyFieldResponseValue": {
|
|
1013
|
+
const currency = value.currency ?? "";
|
|
1014
|
+
return { kind: "currency", currency, displayValue: currency };
|
|
1015
|
+
}
|
|
1016
|
+
case "DateFieldResponseValue": {
|
|
1017
|
+
const date = value.date ?? "";
|
|
1018
|
+
return { kind: "date", date, displayValue: date };
|
|
1019
|
+
}
|
|
1020
|
+
case "DecimalFieldResponseValue": {
|
|
1021
|
+
const decimal = value.decimal ?? null;
|
|
1022
|
+
return { kind: "decimal", decimal, displayValue: decimal ?? "" };
|
|
1023
|
+
}
|
|
1024
|
+
case "DurationFieldResponseValue": {
|
|
1025
|
+
const amount = value.duration?.amount ?? 0;
|
|
1026
|
+
const unit = value.duration?.unit ?? null;
|
|
1027
|
+
return { kind: "duration", amount, unit, displayValue: amountUnit(amount, unit) };
|
|
1028
|
+
}
|
|
1029
|
+
case "EmailFieldResponseValue": {
|
|
1030
|
+
const email = value.email ?? null;
|
|
1031
|
+
return { kind: "email", email, displayValue: email ?? "" };
|
|
1032
|
+
}
|
|
1033
|
+
case "GuestFieldResponseValue": {
|
|
1034
|
+
const name = value.name ?? null;
|
|
1035
|
+
return {
|
|
1036
|
+
kind: "guest",
|
|
1037
|
+
name,
|
|
1038
|
+
email: value.email ?? null,
|
|
1039
|
+
dateOfBirth: value.dateOfBirth ?? null,
|
|
1040
|
+
notes: value.notes ?? null,
|
|
1041
|
+
waiverSigned: value.waiverSigned ?? null,
|
|
1042
|
+
displayValue: name ?? ""
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
case "HtmlFieldResponseValue": {
|
|
1046
|
+
const html = value.html ?? "";
|
|
1047
|
+
return { kind: "html", html, displayValue: escapeHtml(html) };
|
|
1048
|
+
}
|
|
1049
|
+
case "ImageUrlFieldResponseValue": {
|
|
1050
|
+
const imageUrl = value.imageUrl ?? "";
|
|
1051
|
+
return { kind: "imageUrl", imageUrl, displayValue: imageUrl };
|
|
1052
|
+
}
|
|
1053
|
+
case "IntegerFieldResponseValue": {
|
|
1054
|
+
const integer = value.integer ?? 0;
|
|
1055
|
+
return { kind: "integer", integer, displayValue: String(integer) };
|
|
1056
|
+
}
|
|
1057
|
+
case "LocationFieldResponseValue": {
|
|
1058
|
+
const streetAddress = value.streetAddress ?? null;
|
|
1059
|
+
const locality = value.locality ?? null;
|
|
1060
|
+
const region = value.region ?? null;
|
|
1061
|
+
const postalCode = value.postalCode ?? null;
|
|
1062
|
+
const country = value.country ?? null;
|
|
1063
|
+
return {
|
|
1064
|
+
kind: "location",
|
|
1065
|
+
country,
|
|
1066
|
+
county: value.county ?? null,
|
|
1067
|
+
locality,
|
|
1068
|
+
postalCode,
|
|
1069
|
+
region,
|
|
1070
|
+
streetAddress,
|
|
1071
|
+
displayValue: [streetAddress, locality, region, postalCode, country].filter((part) => part).join(", ")
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
case "LongTextFieldResponseValue": {
|
|
1075
|
+
const longText = value.longText ?? "";
|
|
1076
|
+
return { kind: "longText", longText, displayValue: longText };
|
|
1077
|
+
}
|
|
1078
|
+
case "MetaFieldResponseValue": {
|
|
1079
|
+
const meta = value.meta ?? null;
|
|
1080
|
+
return { kind: "meta", meta, displayValue: meta == null ? "" : JSON.stringify(meta) };
|
|
1081
|
+
}
|
|
1082
|
+
case "PercentFieldResponseValue": {
|
|
1083
|
+
const percent = value.percent ?? null;
|
|
1084
|
+
return { kind: "percent", percent, displayValue: percent == null ? "" : `${percent}%` };
|
|
1085
|
+
}
|
|
1086
|
+
case "PhoneFieldResponseValue": {
|
|
1087
|
+
const phone = value.phone ?? "";
|
|
1088
|
+
return { kind: "phone", phone, displayValue: phone };
|
|
1089
|
+
}
|
|
1090
|
+
case "ShortTextFieldResponseValue": {
|
|
1091
|
+
const shortText = value.shortText ?? "";
|
|
1092
|
+
return { kind: "shortText", shortText, displayValue: shortText };
|
|
1093
|
+
}
|
|
1094
|
+
case "TimeFieldResponseValue": {
|
|
1095
|
+
const time = value.time ?? "";
|
|
1096
|
+
return { kind: "time", time, displayValue: time };
|
|
1097
|
+
}
|
|
1098
|
+
case "UrlFieldResponseValue": {
|
|
1099
|
+
const url = value.url ?? null;
|
|
1100
|
+
return { kind: "url", url, displayValue: url ?? "" };
|
|
1101
|
+
}
|
|
1102
|
+
case "VolumeFieldResponseValue": {
|
|
1103
|
+
const amount = value.volume?.amount ?? "0";
|
|
1104
|
+
const unit = value.volume?.unit ?? null;
|
|
1105
|
+
return { kind: "volume", amount, unit, displayValue: amountUnit(amount, unit) };
|
|
1106
|
+
}
|
|
1107
|
+
case "WeightFieldResponseValue": {
|
|
1108
|
+
const amount = value.weight?.amount ?? "0";
|
|
1109
|
+
const unit = value.weight?.unit ?? null;
|
|
1110
|
+
return { kind: "weight", amount, unit, displayValue: amountUnit(amount, unit) };
|
|
1111
|
+
}
|
|
1112
|
+
default:
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function yesNo(value) {
|
|
1117
|
+
return value ? "Yes" : "No";
|
|
1118
|
+
}
|
|
1119
|
+
function amountUnit(amount, unit) {
|
|
1120
|
+
return unit ? `${amount} ${unit.toLowerCase()}` : String(amount);
|
|
1121
|
+
}
|
|
1122
|
+
function escapeHtml(value) {
|
|
1123
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1124
|
+
}
|
|
1125
|
+
function toSetMetaDataResult(node, bookingId) {
|
|
1126
|
+
return {
|
|
1127
|
+
success: node?.__typename === UPSERT_SUCCESS_TYPENAME,
|
|
1128
|
+
bookingId: node?.bookingId || bookingId,
|
|
1129
|
+
message: node?.message || ""
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function filterMetaDataByIntegrator(metaData, integrator) {
|
|
1133
|
+
const prefix = `integrator:${integrator}:`;
|
|
1134
|
+
return metaData.filter((entry) => entry.slug.startsWith(prefix)).map((entry) => ({ ...entry, slug: entry.slug.slice(prefix.length) }));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/internal/peek/bookings/metadata-queries.ts
|
|
1138
|
+
var GUEST_VALUE_PII_FIELDS = "dateOfBirth\n email\n name\n ";
|
|
1139
|
+
function buildGuestValueFields(fullCustomerAccess) {
|
|
1140
|
+
return `... on GuestFieldResponseValue {
|
|
1141
|
+
${fullCustomerAccess ? GUEST_VALUE_PII_FIELDS : ""}notes
|
|
1142
|
+
waiverSigned
|
|
1143
|
+
}`;
|
|
1144
|
+
}
|
|
1145
|
+
function buildMetaDataValueSelection(fullCustomerAccess) {
|
|
1146
|
+
return `value {
|
|
1147
|
+
__typename
|
|
1148
|
+
... on AgeFieldResponseValue { age }
|
|
1149
|
+
... on AttachmentFieldResponseValue { attachmentUrl mime name size }
|
|
1150
|
+
... on BarcodeFieldResponseValue { barcodeType barcodeValue }
|
|
1151
|
+
... on BooleanFieldResponseValue { boolean }
|
|
1152
|
+
... on CheckboxFieldResponseValue { isChecked }
|
|
1153
|
+
... on CurrencyFieldResponseValue { currency }
|
|
1154
|
+
... on DateFieldResponseValue { date }
|
|
1155
|
+
... on DecimalFieldResponseValue { decimal }
|
|
1156
|
+
... on DurationFieldResponseValue { duration { amount unit } }
|
|
1157
|
+
... on EmailFieldResponseValue { email }
|
|
1158
|
+
${buildGuestValueFields(fullCustomerAccess)}
|
|
1159
|
+
... on HtmlFieldResponseValue { html }
|
|
1160
|
+
... on ImageUrlFieldResponseValue { imageUrl }
|
|
1161
|
+
... on IntegerFieldResponseValue { integer }
|
|
1162
|
+
... on LocationFieldResponseValue { country county locality postalCode region streetAddress }
|
|
1163
|
+
... on LongTextFieldResponseValue { longText }
|
|
1164
|
+
... on MetaFieldResponseValue { meta }
|
|
1165
|
+
... on PercentFieldResponseValue { percent }
|
|
1166
|
+
... on PhoneFieldResponseValue { phone }
|
|
1167
|
+
... on ShortTextFieldResponseValue { shortText }
|
|
1168
|
+
... on TimeFieldResponseValue { time }
|
|
1169
|
+
... on UrlFieldResponseValue { url }
|
|
1170
|
+
... on VolumeFieldResponseValue { volume { amount unit } }
|
|
1171
|
+
... on WeightFieldResponseValue { weight { amount unit } }
|
|
1172
|
+
}`;
|
|
1173
|
+
}
|
|
1174
|
+
function buildBookingMetaDataQuery(fullCustomerAccess) {
|
|
1175
|
+
return buildSalesQuery(`
|
|
1176
|
+
... on Booking {
|
|
1177
|
+
displayId
|
|
1178
|
+
id
|
|
1179
|
+
fieldResponses {
|
|
1180
|
+
fieldLocation {
|
|
1181
|
+
field { id name slug type }
|
|
1182
|
+
prompt { label hint isRequired }
|
|
1183
|
+
}
|
|
1184
|
+
refid
|
|
1185
|
+
${buildMetaDataValueSelection(fullCustomerAccess)}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
`);
|
|
1189
|
+
}
|
|
1190
|
+
var UPSERT_BOOKING_FIELD_RESPONSES_MUTATION = `
|
|
1191
|
+
mutation UpsertBookingFieldResponses($input: UpsertBookingFieldResponsesInput!) {
|
|
1192
|
+
upsertBookingFieldResponses(input: $input) {
|
|
1193
|
+
__typename
|
|
1194
|
+
... on UpsertBookingFieldResponsesSuccess { bookingId message }
|
|
1195
|
+
... on BookingNotFoundError { message bookingId }
|
|
1196
|
+
... on GenericError { message }
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
`;
|
|
1200
|
+
|
|
960
1201
|
// src/internal/peek/bookings/payments-on-file-converter.ts
|
|
961
1202
|
function fromPaymentsOnFileResponse(response, bookingId) {
|
|
962
1203
|
const firstEdge = (response?.sales?.edges ?? [])[0];
|
|
@@ -1112,6 +1353,9 @@ var QUESTION_TYPE_SELECT_ONE = "SELECT_ONE";
|
|
|
1112
1353
|
var QUESTION_TYPE_LOCATION = "LOCATION";
|
|
1113
1354
|
var QUESTION_ID_REGEX = /^cq_[a-z0-9]+$/;
|
|
1114
1355
|
var OPTION_ID_REGEX = /^cqao_[a-z0-9]+$/;
|
|
1356
|
+
function isCustomQuestionId(idOrText) {
|
|
1357
|
+
return QUESTION_ID_REGEX.test(idOrText);
|
|
1358
|
+
}
|
|
1115
1359
|
var CHECKBOX_TRUE = /* @__PURE__ */ new Set(["yes", "true"]);
|
|
1116
1360
|
var CHECKBOX_VALID = /* @__PURE__ */ new Set(["yes", "no", "true", "false"]);
|
|
1117
1361
|
var CHECKBOX_TEXT_TRUE = "Yes";
|
|
@@ -1129,10 +1373,13 @@ var errMissingRequired = (text, id) => `Custom question "${text}" (${id}) is req
|
|
|
1129
1373
|
function normalize(value) {
|
|
1130
1374
|
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1131
1375
|
}
|
|
1132
|
-
function resolveQuestion(idOrText, questions) {
|
|
1376
|
+
function resolveQuestion(idOrText, questions, acceptAllIds) {
|
|
1133
1377
|
if (QUESTION_ID_REGEX.test(idOrText)) {
|
|
1134
1378
|
const byId = questions.find((question) => question.id === idOrText);
|
|
1135
|
-
if (!byId)
|
|
1379
|
+
if (!byId) {
|
|
1380
|
+
if (acceptAllIds) return null;
|
|
1381
|
+
throw new Error(errUnknownQuestionId(idOrText));
|
|
1382
|
+
}
|
|
1136
1383
|
return byId;
|
|
1137
1384
|
}
|
|
1138
1385
|
const target = normalize(idOrText);
|
|
@@ -1157,8 +1404,11 @@ function resolveOption(value, question) {
|
|
|
1157
1404
|
if (matches.length > 1) throw new Error(errAmbiguousOption(value, question.id));
|
|
1158
1405
|
return matches[0];
|
|
1159
1406
|
}
|
|
1160
|
-
function resolveOne(answer, questions) {
|
|
1161
|
-
const question = resolveQuestion(answer.questionIdOrText, questions);
|
|
1407
|
+
function resolveOne(answer, questions, acceptAllIds) {
|
|
1408
|
+
const question = resolveQuestion(answer.questionIdOrText, questions, acceptAllIds);
|
|
1409
|
+
if (!question) {
|
|
1410
|
+
return { questionId: answer.questionIdOrText, questionAnswerText: answer.value };
|
|
1411
|
+
}
|
|
1162
1412
|
if (question.perGuest) {
|
|
1163
1413
|
throw new Error(errPerGuestUnsupported(question.id));
|
|
1164
1414
|
}
|
|
@@ -1191,7 +1441,8 @@ function resolveOne(answer, questions) {
|
|
|
1191
1441
|
}
|
|
1192
1442
|
}
|
|
1193
1443
|
function resolveCustomQuestionAnswers(answers, questions, options) {
|
|
1194
|
-
const
|
|
1444
|
+
const acceptAllIds = options?.acceptAllIds ?? false;
|
|
1445
|
+
const resolved = answers.map((answer) => resolveOne(answer, questions, acceptAllIds));
|
|
1195
1446
|
if (options?.requireRequired) {
|
|
1196
1447
|
assertRequiredAnswered(resolved, questions);
|
|
1197
1448
|
}
|
|
@@ -1206,10 +1457,41 @@ function assertRequiredAnswered(resolved, questions) {
|
|
|
1206
1457
|
}
|
|
1207
1458
|
}
|
|
1208
1459
|
|
|
1460
|
+
// src/internal/peek/bookings/list-price.ts
|
|
1461
|
+
var ERROR_LIST_PRICE_INVALID = "listPrice must be a positive number string, e.g. '100', '1,000', or '10.00'";
|
|
1462
|
+
function parseListPriceToCents(listPrice) {
|
|
1463
|
+
if (typeof listPrice !== "string") {
|
|
1464
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1465
|
+
}
|
|
1466
|
+
const cleaned = listPrice.replace(/,/g, "").trim();
|
|
1467
|
+
if (!/^\d+(\.\d+)?$/.test(cleaned)) {
|
|
1468
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1469
|
+
}
|
|
1470
|
+
const cents = Math.round(Number(cleaned) * 100);
|
|
1471
|
+
if (!Number.isFinite(cents) || cents <= 0) {
|
|
1472
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1473
|
+
}
|
|
1474
|
+
return cents;
|
|
1475
|
+
}
|
|
1476
|
+
function centsToAmount(cents) {
|
|
1477
|
+
return (cents / 100).toFixed(2);
|
|
1478
|
+
}
|
|
1479
|
+
function distributeListPrice(listPrice, ticketCount) {
|
|
1480
|
+
const totalCents = parseListPriceToCents(listPrice);
|
|
1481
|
+
const perTicketCents = Math.floor(totalCents / ticketCount);
|
|
1482
|
+
const amounts = [];
|
|
1483
|
+
for (let i = 0; i < ticketCount - 1; i += 1) {
|
|
1484
|
+
amounts.push(centsToAmount(perTicketCents));
|
|
1485
|
+
}
|
|
1486
|
+
amounts.push(centsToAmount(totalCents - perTicketCents * (ticketCount - 1)));
|
|
1487
|
+
return amounts;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1209
1490
|
// src/internal/peek/bookings/booking-service.ts
|
|
1210
1491
|
var DEFAULT_PAGE_SIZE2 = 50;
|
|
1211
1492
|
var DEFAULT_CANCEL_NOTE = "Canceled";
|
|
1212
1493
|
var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
|
|
1494
|
+
var DEFAULT_CURRENCY = "USD";
|
|
1213
1495
|
var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
|
|
1214
1496
|
var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
|
|
1215
1497
|
var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
|
|
@@ -1222,6 +1504,17 @@ var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
|
|
|
1222
1504
|
var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
|
|
1223
1505
|
var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
|
|
1224
1506
|
var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
|
|
1507
|
+
var ERROR_CUSTOM_STATUS_REQUIRED = "customStatus is required and must be a non-empty string";
|
|
1508
|
+
var ERROR_GUESTS_REQUIRED = "at least one guest is required";
|
|
1509
|
+
var ERROR_ATTACHMENTS_REQUIRED = "at least one attachment is required";
|
|
1510
|
+
var META_FIELD_GUEST = "guest";
|
|
1511
|
+
var META_FIELD_ATTACHMENT = "attachment";
|
|
1512
|
+
var META_FIELD_MANIFEST_URL = "manifest_url";
|
|
1513
|
+
var META_FIELD_RESERVATION_ID = "reservation_id";
|
|
1514
|
+
var META_FIELD_BOOKING_STATUS = "booking_status";
|
|
1515
|
+
var META_FIELD_ASSIGNED_PRODUCT = "assigned_product";
|
|
1516
|
+
var META_FIELD_ASSIGNED_EMPLOYEE = "assigned_employee";
|
|
1517
|
+
var META_FIELD_INSURANCE_PURCHASED = "insurance_purchased";
|
|
1225
1518
|
var ERROR_BOOKING_NOT_FOUND = "Booking not found";
|
|
1226
1519
|
var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
|
|
1227
1520
|
var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
|
|
@@ -1234,12 +1527,15 @@ var BookingService = class {
|
|
|
1234
1527
|
this.deps = deps;
|
|
1235
1528
|
this.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE2;
|
|
1236
1529
|
this.fullCustomerAccess = resolveAccessOptions(options.accessOptions).fullCustomerAccess;
|
|
1530
|
+
this.integrator = options.integrator ?? "";
|
|
1237
1531
|
}
|
|
1238
1532
|
client;
|
|
1239
1533
|
deps;
|
|
1240
1534
|
pageSize;
|
|
1241
1535
|
/** Whether customer PII is requested and payment operations are allowed. */
|
|
1242
1536
|
fullCustomerAccess;
|
|
1537
|
+
/** Integrator id scoping `getMetaData` results. */
|
|
1538
|
+
integrator;
|
|
1243
1539
|
/**
|
|
1244
1540
|
* Throws a {@link PiiAccessDisabledError} for `operation` when this service was
|
|
1245
1541
|
* created without `fullCustomerAccess` — used to gate payment and booking-modification
|
|
@@ -1327,6 +1623,131 @@ var BookingService = class {
|
|
|
1327
1623
|
);
|
|
1328
1624
|
return fromBookingGuestsResponse(body.data);
|
|
1329
1625
|
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Returns a booking's custom-field metadata (its `fieldResponses`), scoped to
|
|
1628
|
+
* this service's integrator. Only fields whose slug starts with
|
|
1629
|
+
* `integrator:<integrator>:` are returned, and that prefix is stripped from
|
|
1630
|
+
* each returned `slug`. Returns `[]` when the booking is not found or has no
|
|
1631
|
+
* matching fields.
|
|
1632
|
+
*
|
|
1633
|
+
* @example
|
|
1634
|
+
* ```ts
|
|
1635
|
+
* const meta = await peek.getBookingService().getMetaData("b_abc123");
|
|
1636
|
+
* for (const field of meta) {
|
|
1637
|
+
* if (field.value?.kind === "url") console.log(field.slug, field.value.url);
|
|
1638
|
+
* }
|
|
1639
|
+
* ```
|
|
1640
|
+
*/
|
|
1641
|
+
async getMetaData(bookingId) {
|
|
1642
|
+
assertBookingId(bookingId);
|
|
1643
|
+
const body = await this.client.request(
|
|
1644
|
+
SALES_ENDPOINT,
|
|
1645
|
+
buildBookingMetaDataQuery(this.fullCustomerAccess),
|
|
1646
|
+
buildBookingsVariables({
|
|
1647
|
+
pageSize: this.pageSize,
|
|
1648
|
+
after: null,
|
|
1649
|
+
bookingId: normalizeBookingId(bookingId)
|
|
1650
|
+
})
|
|
1651
|
+
);
|
|
1652
|
+
const firstEdge = (body.data?.sales?.edges ?? [])[0];
|
|
1653
|
+
if (!firstEdge) {
|
|
1654
|
+
return [];
|
|
1655
|
+
}
|
|
1656
|
+
return filterMetaDataByIntegrator(fromBookingMetaDataNode(firstEdge.node), this.integrator);
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Sets the booking's `guest` metadata field. Each guest requires a `name` and
|
|
1660
|
+
* `email`; `dateOfBirth`/`waiverSigned`/`notes` are optional.
|
|
1661
|
+
*/
|
|
1662
|
+
async setMetaDataGuest(bookingId, guests) {
|
|
1663
|
+
if (!Array.isArray(guests) || guests.length === 0) {
|
|
1664
|
+
throw new Error(ERROR_GUESTS_REQUIRED);
|
|
1665
|
+
}
|
|
1666
|
+
const built = guests.map((guest) => {
|
|
1667
|
+
assertNonEmptyString(guest?.name, "guest name");
|
|
1668
|
+
assertNonEmptyString(guest?.email, "guest email");
|
|
1669
|
+
const out = { name: guest.name, email: guest.email };
|
|
1670
|
+
if (guest.dateOfBirth !== void 0) out.dateOfBirth = guest.dateOfBirth;
|
|
1671
|
+
if (guest.waiverSigned !== void 0) out.waiverSigned = guest.waiverSigned;
|
|
1672
|
+
if (guest.notes !== void 0) out.notes = guest.notes;
|
|
1673
|
+
return out;
|
|
1674
|
+
});
|
|
1675
|
+
return this.upsertFieldResponse(bookingId, { fieldName: META_FIELD_GUEST, guests: built });
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Sets the booking's `attachment` metadata field. Each attachment requires a
|
|
1679
|
+
* `name` and `attachmentUrl`.
|
|
1680
|
+
*/
|
|
1681
|
+
async setMetaDataAttachment(bookingId, attachments) {
|
|
1682
|
+
if (!Array.isArray(attachments) || attachments.length === 0) {
|
|
1683
|
+
throw new Error(ERROR_ATTACHMENTS_REQUIRED);
|
|
1684
|
+
}
|
|
1685
|
+
const built = attachments.map((attachment) => {
|
|
1686
|
+
assertNonEmptyString(attachment?.name, "attachment name");
|
|
1687
|
+
assertNonEmptyString(attachment?.attachmentUrl, "attachment attachmentUrl");
|
|
1688
|
+
return { name: attachment.name, attachmentUrl: attachment.attachmentUrl };
|
|
1689
|
+
});
|
|
1690
|
+
return this.upsertFieldResponse(bookingId, {
|
|
1691
|
+
fieldName: META_FIELD_ATTACHMENT,
|
|
1692
|
+
attachments: built
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
/** Sets the booking's `manifest_url` metadata field. */
|
|
1696
|
+
async setMetaDataManifestUrl(bookingId, url) {
|
|
1697
|
+
assertNonEmptyString(url, "url");
|
|
1698
|
+
return this.upsertFieldResponse(bookingId, { fieldName: META_FIELD_MANIFEST_URL, url });
|
|
1699
|
+
}
|
|
1700
|
+
/** Sets the booking's `reservation_id` metadata field. */
|
|
1701
|
+
async setMetaDataReservationId(bookingId, reservationId) {
|
|
1702
|
+
return this.setShortTextField(bookingId, META_FIELD_RESERVATION_ID, reservationId, "reservationId");
|
|
1703
|
+
}
|
|
1704
|
+
/** Sets the booking's `booking_status` metadata field. */
|
|
1705
|
+
async setMetaDataBookingStatus(bookingId, status) {
|
|
1706
|
+
return this.setShortTextField(bookingId, META_FIELD_BOOKING_STATUS, status, "status");
|
|
1707
|
+
}
|
|
1708
|
+
/** Sets the booking's `assigned_product` metadata field. */
|
|
1709
|
+
async setMetaDataAssignedProduct(bookingId, productName) {
|
|
1710
|
+
return this.setShortTextField(bookingId, META_FIELD_ASSIGNED_PRODUCT, productName, "productName");
|
|
1711
|
+
}
|
|
1712
|
+
/** Sets the booking's `assigned_employee` metadata field. */
|
|
1713
|
+
async setMetaDataAssignedEmployee(bookingId, employeeName) {
|
|
1714
|
+
return this.setShortTextField(bookingId, META_FIELD_ASSIGNED_EMPLOYEE, employeeName, "employeeName");
|
|
1715
|
+
}
|
|
1716
|
+
/** Sets the booking's `insurance_purchased` metadata field. */
|
|
1717
|
+
async setMetaDataInsurancePurchased(bookingId, insurancePurchased) {
|
|
1718
|
+
if (typeof insurancePurchased !== "boolean") {
|
|
1719
|
+
throw new Error("insurancePurchased is required and must be a boolean");
|
|
1720
|
+
}
|
|
1721
|
+
return this.upsertFieldResponse(bookingId, {
|
|
1722
|
+
fieldName: META_FIELD_INSURANCE_PURCHASED,
|
|
1723
|
+
boolean: insurancePurchased
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
/** Validates a non-empty `shortText` value and upserts it under `fieldName`. */
|
|
1727
|
+
setShortTextField(bookingId, fieldName, value, argName) {
|
|
1728
|
+
assertNonEmptyString(value, argName);
|
|
1729
|
+
return this.upsertFieldResponse(bookingId, { fieldName, shortText: value });
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Validates the booking id and upserts a single field response (scoped to this
|
|
1733
|
+
* service's integrator), mapping the result union into a {@link SetMetaDataResult}.
|
|
1734
|
+
*/
|
|
1735
|
+
async upsertFieldResponse(bookingId, fieldResponse) {
|
|
1736
|
+
assertBookingId(bookingId);
|
|
1737
|
+
const normalized = normalizeBookingId(bookingId);
|
|
1738
|
+
const body = await this.client.request(
|
|
1739
|
+
SALES_ENDPOINT,
|
|
1740
|
+
UPSERT_BOOKING_FIELD_RESPONSES_MUTATION,
|
|
1741
|
+
{
|
|
1742
|
+
input: {
|
|
1743
|
+
bookingId: normalized,
|
|
1744
|
+
integrator: this.integrator,
|
|
1745
|
+
fieldResponses: [fieldResponse]
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
);
|
|
1749
|
+
return toSetMetaDataResult(body.data?.upsertBookingFieldResponses, normalized);
|
|
1750
|
+
}
|
|
1330
1751
|
/** Returns the payments on file for a booking, or null when not found. */
|
|
1331
1752
|
async getPaymentsOnFile(bookingId) {
|
|
1332
1753
|
this.assertPiiEnabled("getPaymentsOnFile");
|
|
@@ -1371,6 +1792,30 @@ ${note}`;
|
|
|
1371
1792
|
});
|
|
1372
1793
|
return this.getById(normalized);
|
|
1373
1794
|
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Sets a booking's operator-facing custom status. Returns `true` when the
|
|
1797
|
+
* gateway confirms the stored `operatorStatus` equals the requested
|
|
1798
|
+
* `customStatus`, `false` otherwise.
|
|
1799
|
+
*
|
|
1800
|
+
* @example
|
|
1801
|
+
* ```ts
|
|
1802
|
+
* const ok = await peek.getBookingService().updateCustomStatus("b_abc123", "Awaiting deposit");
|
|
1803
|
+
* ```
|
|
1804
|
+
*
|
|
1805
|
+
* @throws {Error} when `bookingId` is not a valid booking id (`b_…`/`B-…`) or
|
|
1806
|
+
* when `customStatus` is not a non-empty string.
|
|
1807
|
+
*/
|
|
1808
|
+
async updateCustomStatus(bookingId, customStatus) {
|
|
1809
|
+
assertBookingId(bookingId);
|
|
1810
|
+
assertCustomStatus(customStatus);
|
|
1811
|
+
const normalized = normalizeBookingId(bookingId);
|
|
1812
|
+
const body = await this.client.request(
|
|
1813
|
+
SALES_ENDPOINT,
|
|
1814
|
+
UPDATE_OPERATOR_STATUS_MUTATION,
|
|
1815
|
+
{ input: { id: normalized, operatorStatus: customStatus } }
|
|
1816
|
+
);
|
|
1817
|
+
return body.data?.updateOperatorStatusForBooking?.booking?.operatorStatus === customStatus;
|
|
1818
|
+
}
|
|
1374
1819
|
/** Cancels a booking and returns its id/displayId/status. */
|
|
1375
1820
|
async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
|
|
1376
1821
|
assertBookingId(bookingId);
|
|
@@ -1726,13 +2171,26 @@ ${note}`;
|
|
|
1726
2171
|
*/
|
|
1727
2172
|
async create(input) {
|
|
1728
2173
|
validateCreateInput(input);
|
|
1729
|
-
const
|
|
2174
|
+
const ticketCount = input.tickets.reduce((count, ticket) => count + ticket.quantity, 0);
|
|
2175
|
+
const listPriceAmounts = input.listPrice === void 0 ? null : distributeListPrice(input.listPrice, ticketCount);
|
|
2176
|
+
const [questionAnswers, currency] = await Promise.all([
|
|
2177
|
+
this.buildQuestionAnswers(input),
|
|
2178
|
+
listPriceAmounts ? this.resolveActivityCurrency(input.activityId) : Promise.resolve("")
|
|
2179
|
+
]);
|
|
2180
|
+
let seatIndex = 0;
|
|
1730
2181
|
const tickets = input.tickets.flatMap(
|
|
1731
|
-
(ticket) => Array.from({ length: ticket.quantity }, () =>
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2182
|
+
(ticket) => Array.from({ length: ticket.quantity }, () => {
|
|
2183
|
+
const seat = {
|
|
2184
|
+
resourceOptionId: ticket.resourceOptionId,
|
|
2185
|
+
reservationStatus: "CONFIRMED",
|
|
2186
|
+
refid: crypto.randomUUID()
|
|
2187
|
+
};
|
|
2188
|
+
if (listPriceAmounts) {
|
|
2189
|
+
seat.price = { amount: listPriceAmounts[seatIndex], currency };
|
|
2190
|
+
}
|
|
2191
|
+
seatIndex += 1;
|
|
2192
|
+
return seat;
|
|
2193
|
+
})
|
|
1736
2194
|
);
|
|
1737
2195
|
const bookingQuote = {
|
|
1738
2196
|
tickets,
|
|
@@ -1799,19 +2257,22 @@ ${note}`;
|
|
|
1799
2257
|
* Resolves the input's custom-question answers into the quote's
|
|
1800
2258
|
* `questionAnswers` payload (each tagged with a fresh `refid`), or `null` when
|
|
1801
2259
|
* none were supplied. Fetches the activity's custom questions to validate
|
|
1802
|
-
* against; throws on the first unmatched/ambiguous/invalid answer.
|
|
2260
|
+
* against; throws on the first unmatched/ambiguous/invalid answer. When
|
|
2261
|
+
* `acceptAllCustomQuestionIds` is set the fetch is skipped if every answer is
|
|
2262
|
+
* given by id (a by-name answer still needs the list to resolve).
|
|
1803
2263
|
*/
|
|
1804
2264
|
async buildQuestionAnswers(input) {
|
|
1805
2265
|
const answers = input.customQuestionAnswers ?? [];
|
|
1806
2266
|
const requireRequired = input.requireRequiredQuestions ?? false;
|
|
2267
|
+
const acceptAllIds = input.acceptAllCustomQuestionIds ?? false;
|
|
1807
2268
|
if (answers.length === 0 && !requireRequired) {
|
|
1808
2269
|
return null;
|
|
1809
2270
|
}
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
);
|
|
2271
|
+
const needsQuestions = !acceptAllIds || requireRequired || answers.some((answer) => !isCustomQuestionId(answer.questionIdOrText));
|
|
2272
|
+
const questions = needsQuestions ? await this.deps.productService.getCustomQuestions(input.activityId) : [];
|
|
1813
2273
|
const resolved = resolveCustomQuestionAnswers(answers, questions, {
|
|
1814
|
-
requireRequired
|
|
2274
|
+
requireRequired,
|
|
2275
|
+
acceptAllIds
|
|
1815
2276
|
});
|
|
1816
2277
|
if (resolved.length === 0) {
|
|
1817
2278
|
return null;
|
|
@@ -1844,6 +2305,15 @@ ${note}`;
|
|
|
1844
2305
|
}
|
|
1845
2306
|
return result.transactionId;
|
|
1846
2307
|
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Resolves an activity's ISO currency via the product service, falling back to
|
|
2310
|
+
* {@link DEFAULT_CURRENCY} when the activity (or its currency) can't be found.
|
|
2311
|
+
*/
|
|
2312
|
+
async resolveActivityCurrency(activityId) {
|
|
2313
|
+
const products = await this.deps.productService.getAllProducts();
|
|
2314
|
+
const match = products.find((product) => product.productId === activityId);
|
|
2315
|
+
return match?.currency || DEFAULT_CURRENCY;
|
|
2316
|
+
}
|
|
1847
2317
|
/** Finds the parent item id of an add-on by matching its option id. */
|
|
1848
2318
|
async resolveParentItemId(addonOptionId) {
|
|
1849
2319
|
const products = await this.deps.productService.getAllProducts();
|
|
@@ -1918,6 +2388,16 @@ function assertBookingId(bookingId) {
|
|
|
1918
2388
|
throw new Error(ERROR_INVALID_BOOKING_ID);
|
|
1919
2389
|
}
|
|
1920
2390
|
}
|
|
2391
|
+
function assertCustomStatus(customStatus) {
|
|
2392
|
+
if (typeof customStatus !== "string" || customStatus.trim().length === 0) {
|
|
2393
|
+
throw new Error(ERROR_CUSTOM_STATUS_REQUIRED);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
function assertNonEmptyString(value, field) {
|
|
2397
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2398
|
+
throw new Error(`${field} is required and must be a non-empty string`);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
1921
2401
|
function assertOrderId(orderId) {
|
|
1922
2402
|
if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
|
|
1923
2403
|
throw new Error(ERROR_INVALID_ORDER_ID);
|
|
@@ -2184,7 +2664,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
2184
2664
|
// src/internal/sdk-headers.ts
|
|
2185
2665
|
var SDK_HEADER_NAME = "x-peek-sdk";
|
|
2186
2666
|
var SDK_PLATFORM = "js";
|
|
2187
|
-
var SDK_VERSION = "0.9.
|
|
2667
|
+
var SDK_VERSION = "0.9.3";
|
|
2188
2668
|
var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
|
|
2189
2669
|
|
|
2190
2670
|
// src/internal/peek/graphql-client.ts
|
|
@@ -2609,6 +3089,23 @@ function fromReviewNode(node) {
|
|
|
2609
3089
|
comment: node.comment ?? null
|
|
2610
3090
|
};
|
|
2611
3091
|
}
|
|
3092
|
+
function fromActivityReviewSummaryNode(node) {
|
|
3093
|
+
const meta = node.reviewMeta;
|
|
3094
|
+
return {
|
|
3095
|
+
productId: node.id,
|
|
3096
|
+
productName: node.name,
|
|
3097
|
+
avgRating: meta?.avgRating ?? null,
|
|
3098
|
+
countTotal: meta?.count ?? 0,
|
|
3099
|
+
countOneStar: meta?.oneStar ?? 0,
|
|
3100
|
+
countTwoStar: meta?.twoStar ?? 0,
|
|
3101
|
+
countThreeStar: meta?.threeStar ?? 0,
|
|
3102
|
+
countFourStar: meta?.fourStar ?? 0,
|
|
3103
|
+
countFiveStar: meta?.fiveStar ?? 0
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
function fromActivityReviewSummaryNodes(nodes) {
|
|
3107
|
+
return nodes.map(fromActivityReviewSummaryNode);
|
|
3108
|
+
}
|
|
2612
3109
|
|
|
2613
3110
|
// src/internal/peek/reviews/review-cursor.ts
|
|
2614
3111
|
function encodeCursor(offset, pageSize) {
|
|
@@ -2644,6 +3141,23 @@ function buildReviewsQuery(fullCustomerAccess) {
|
|
|
2644
3141
|
}
|
|
2645
3142
|
`;
|
|
2646
3143
|
}
|
|
3144
|
+
var REVIEW_SUMMARIES_QUERY = `
|
|
3145
|
+
query ReviewSummaries {
|
|
3146
|
+
activities {
|
|
3147
|
+
id
|
|
3148
|
+
name
|
|
3149
|
+
reviewMeta {
|
|
3150
|
+
avgRating
|
|
3151
|
+
count
|
|
3152
|
+
fiveStar
|
|
3153
|
+
fourStar
|
|
3154
|
+
threeStar
|
|
3155
|
+
twoStar
|
|
3156
|
+
oneStar
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
`;
|
|
2647
3161
|
function buildReviewsVariables(params) {
|
|
2648
3162
|
return {
|
|
2649
3163
|
first: params.first,
|
|
@@ -2703,6 +3217,29 @@ var ReviewService = class {
|
|
|
2703
3217
|
const edges = body.data?.reviews?.edges ?? [];
|
|
2704
3218
|
return edges.map((edge) => fromReviewNode(edge.node));
|
|
2705
3219
|
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Returns an aggregate review summary for **every** activity — its average
|
|
3222
|
+
* rating and the count of reviews at each star level. This reads the same
|
|
3223
|
+
* activity list as `getAllProducts`; it carries no per-review detail or PII, so
|
|
3224
|
+
* it is unaffected by `fullCustomerAccess`. An activity with no reviews comes
|
|
3225
|
+
* back with a `null` `avgRating` and zero counts.
|
|
3226
|
+
*
|
|
3227
|
+
* @example
|
|
3228
|
+
* ```ts
|
|
3229
|
+
* const summaries = await peek.getReviewService().getAllReviewSummaries();
|
|
3230
|
+
* for (const s of summaries) {
|
|
3231
|
+
* console.log(s.productName, s.avgRating, s.countTotal);
|
|
3232
|
+
* }
|
|
3233
|
+
* ```
|
|
3234
|
+
*/
|
|
3235
|
+
async getAllReviewSummaries() {
|
|
3236
|
+
const body = await this.client.request(
|
|
3237
|
+
SALES_ENDPOINT,
|
|
3238
|
+
REVIEW_SUMMARIES_QUERY,
|
|
3239
|
+
{}
|
|
3240
|
+
);
|
|
3241
|
+
return fromActivityReviewSummaryNodes(body.data?.activities ?? []);
|
|
3242
|
+
}
|
|
2706
3243
|
validate(productId, reviewCount, reviewOffset) {
|
|
2707
3244
|
if (!productId) {
|
|
2708
3245
|
throw new Error(ERROR_PRODUCT_ID_REQUIRED);
|
|
@@ -3413,7 +3950,7 @@ var CREATE_PROMO_CODE_MUTATION = `
|
|
|
3413
3950
|
|
|
3414
3951
|
// src/internal/peek/promo-codes/promo-code-service.ts
|
|
3415
3952
|
var DEFAULT_PAGE_SIZE3 = 50;
|
|
3416
|
-
var
|
|
3953
|
+
var DEFAULT_CURRENCY2 = "USD";
|
|
3417
3954
|
var CURRENCY_PATTERN = /^[A-Z]{3}$/;
|
|
3418
3955
|
var PromoCodeService = class {
|
|
3419
3956
|
constructor(client, options = {}) {
|
|
@@ -3459,7 +3996,7 @@ var PromoCodeService = class {
|
|
|
3459
3996
|
if (Number.isNaN(parseFloat(input.amount))) {
|
|
3460
3997
|
throw new Error("amount must be a valid number");
|
|
3461
3998
|
}
|
|
3462
|
-
const currency = input.currency ||
|
|
3999
|
+
const currency = input.currency || DEFAULT_CURRENCY2;
|
|
3463
4000
|
if (!CURRENCY_PATTERN.test(currency)) {
|
|
3464
4001
|
throw new Error("currency must be 3 uppercase letters");
|
|
3465
4002
|
}
|
|
@@ -3833,6 +4370,7 @@ var PeekAccessService = class {
|
|
|
3833
4370
|
productServiceOptions;
|
|
3834
4371
|
accessOptions;
|
|
3835
4372
|
jwtSecret;
|
|
4373
|
+
issuer;
|
|
3836
4374
|
productService;
|
|
3837
4375
|
accountUserService;
|
|
3838
4376
|
resourcePoolService;
|
|
@@ -3843,7 +4381,7 @@ var PeekAccessService = class {
|
|
|
3843
4381
|
dailyNoteService;
|
|
3844
4382
|
availabilityService;
|
|
3845
4383
|
membershipService;
|
|
3846
|
-
|
|
4384
|
+
bookingServices = /* @__PURE__ */ new Map();
|
|
3847
4385
|
reviewService;
|
|
3848
4386
|
constructor(config) {
|
|
3849
4387
|
const hasApiUrl = !!config.apiUrl;
|
|
@@ -3854,6 +4392,7 @@ var PeekAccessService = class {
|
|
|
3854
4392
|
if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
|
|
3855
4393
|
if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
|
|
3856
4394
|
this.jwtSecret = config.jwtSecret;
|
|
4395
|
+
this.issuer = config.issuer;
|
|
3857
4396
|
const logger = config.logger ?? noopLogger;
|
|
3858
4397
|
const tokens = createTokenManager(config);
|
|
3859
4398
|
const useRegistrySlug = hasApiUrl || isV2;
|
|
@@ -4014,17 +4553,25 @@ var PeekAccessService = class {
|
|
|
4014
4553
|
}
|
|
4015
4554
|
/**
|
|
4016
4555
|
* Returns the {@link BookingService} for this install, bound to the shared
|
|
4017
|
-
* authenticated transport.
|
|
4556
|
+
* authenticated transport. Instances are created lazily and reused per
|
|
4557
|
+
* `integrator`.
|
|
4558
|
+
*
|
|
4559
|
+
* `integrator` scopes `getMetaData` results to a custom integrator id. **Do
|
|
4560
|
+
* not set it** unless Peek engineering created a custom integrator id for this
|
|
4561
|
+
* integration — omit it and the correct default is used.
|
|
4018
4562
|
*/
|
|
4019
|
-
getBookingService() {
|
|
4020
|
-
|
|
4021
|
-
|
|
4563
|
+
getBookingService(integrator) {
|
|
4564
|
+
const resolved = integrator ?? this.issuer;
|
|
4565
|
+
let service = this.bookingServices.get(resolved);
|
|
4566
|
+
if (!service) {
|
|
4567
|
+
service = new BookingService(
|
|
4022
4568
|
this.client,
|
|
4023
4569
|
{ productService: this.getProductService() },
|
|
4024
|
-
{ accessOptions: this.accessOptions }
|
|
4570
|
+
{ accessOptions: this.accessOptions, integrator: resolved }
|
|
4025
4571
|
);
|
|
4572
|
+
this.bookingServices.set(resolved, service);
|
|
4026
4573
|
}
|
|
4027
|
-
return
|
|
4574
|
+
return service;
|
|
4028
4575
|
}
|
|
4029
4576
|
/**
|
|
4030
4577
|
* Returns the {@link ReviewService} for this install, bound to the shared
|