@peektravel/app-utilities 0.9.2 → 0.9.4
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 +557 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +287 -5
- package/dist/index.d.ts +287 -5
- package/dist/index.js +557 -70
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +1 -1
- package/llms.txt +26 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -316,6 +316,20 @@ var SEARCH_BY_ACTIVITY_DATE = "activityDate";
|
|
|
316
316
|
function normalizeBookingId(bookingId) {
|
|
317
317
|
return bookingId.toLowerCase().replace(/-/g, "_");
|
|
318
318
|
}
|
|
319
|
+
function buildSalesQuery(nodeBody) {
|
|
320
|
+
return `
|
|
321
|
+
query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
|
|
322
|
+
sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
|
|
323
|
+
pageInfo { endCursor hasNextPage }
|
|
324
|
+
edges {
|
|
325
|
+
node {
|
|
326
|
+
${nodeBody}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
319
333
|
var GUEST_IDENTITY_PII_FIELDS = "name country dateOfBirth email isGdpr";
|
|
320
334
|
var GUEST_CONTACT_PII_FIELDS = `phone
|
|
321
335
|
postalCode
|
|
@@ -409,6 +423,7 @@ function buildBookingQueryFields(fullCustomerAccess) {
|
|
|
409
423
|
availabilityTimeId
|
|
410
424
|
${fullCustomerAccess ? "bookingPortalUrl" : ""}
|
|
411
425
|
operatorNotes
|
|
426
|
+
operatorStatus
|
|
412
427
|
value {
|
|
413
428
|
total {
|
|
414
429
|
formatted
|
|
@@ -491,67 +506,40 @@ function buildBookingsListingQuery(includeGuests, includePriceBreakdown, fullCus
|
|
|
491
506
|
const guestsSection = includeGuests ? buildBookingGuestsFields(fullCustomerAccess) : "";
|
|
492
507
|
const baseFields = buildBookingQueryFields(fullCustomerAccess);
|
|
493
508
|
const fields = includePriceBreakdown ? baseFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : baseFields;
|
|
494
|
-
return `
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
edges {
|
|
499
|
-
node {
|
|
500
|
-
... on Booking {
|
|
501
|
-
${fields}
|
|
502
|
-
${guestsSection}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
509
|
+
return buildSalesQuery(`
|
|
510
|
+
... on Booking {
|
|
511
|
+
${fields}
|
|
512
|
+
${guestsSection}
|
|
507
513
|
}
|
|
508
|
-
|
|
514
|
+
`);
|
|
509
515
|
}
|
|
510
516
|
function buildBookingGuestsQuery(fullCustomerAccess) {
|
|
511
|
-
return `
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
node {
|
|
517
|
-
... on Booking {
|
|
518
|
-
displayId
|
|
519
|
-
id
|
|
520
|
-
${buildBookingGuestsFields(fullCustomerAccess)}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
517
|
+
return buildSalesQuery(`
|
|
518
|
+
... on Booking {
|
|
519
|
+
displayId
|
|
520
|
+
id
|
|
521
|
+
${buildBookingGuestsFields(fullCustomerAccess)}
|
|
524
522
|
}
|
|
525
|
-
|
|
526
|
-
`;
|
|
523
|
+
`);
|
|
527
524
|
}
|
|
528
|
-
var BOOKING_PAYMENTS_ON_FILE_QUERY = `
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
id
|
|
537
|
-
paymentSource { id }
|
|
538
|
-
appliedAt
|
|
539
|
-
currentAmount { amount currency }
|
|
540
|
-
refundableAmount { amount currency }
|
|
541
|
-
}
|
|
542
|
-
id
|
|
543
|
-
displayId
|
|
544
|
-
paymentSources { description type id }
|
|
545
|
-
}
|
|
546
|
-
... on Booking {
|
|
547
|
-
displayId
|
|
548
|
-
id
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
525
|
+
var BOOKING_PAYMENTS_ON_FILE_QUERY = buildSalesQuery(`
|
|
526
|
+
order {
|
|
527
|
+
payments {
|
|
528
|
+
id
|
|
529
|
+
paymentSource { id }
|
|
530
|
+
appliedAt
|
|
531
|
+
currentAmount { amount currency }
|
|
532
|
+
refundableAmount { amount currency }
|
|
552
533
|
}
|
|
534
|
+
id
|
|
535
|
+
displayId
|
|
536
|
+
paymentSources { description type id }
|
|
553
537
|
}
|
|
554
|
-
|
|
538
|
+
... on Booking {
|
|
539
|
+
displayId
|
|
540
|
+
id
|
|
541
|
+
}
|
|
542
|
+
`);
|
|
555
543
|
var UPDATE_OPERATOR_NOTES_MUTATION = `
|
|
556
544
|
mutation Account($input: UpdateOperatorNotesForBookingInput!) {
|
|
557
545
|
updateOperatorNotesForBooking(input: $input) {
|
|
@@ -559,6 +547,13 @@ var UPDATE_OPERATOR_NOTES_MUTATION = `
|
|
|
559
547
|
}
|
|
560
548
|
}
|
|
561
549
|
`;
|
|
550
|
+
var UPDATE_OPERATOR_STATUS_MUTATION = `
|
|
551
|
+
mutation Account($input: UpdateOperatorStatusForBookingInput!) {
|
|
552
|
+
updateOperatorStatusForBooking(input: $input) {
|
|
553
|
+
booking { operatorStatus }
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
`;
|
|
562
557
|
var UPDATE_BOOKING_CHECKIN_MUTATION = `
|
|
563
558
|
mutation Account($input: UpdateBookingCheckInInput!) {
|
|
564
559
|
updateBookingCheckIn(input: $input) {
|
|
@@ -781,6 +776,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
|
|
|
781
776
|
availabilityTimeId: data.availabilityTimeId || null,
|
|
782
777
|
portalUrl: data.bookingPortalUrl || null,
|
|
783
778
|
notes: data.operatorNotes || "",
|
|
779
|
+
customStatus: data.operatorStatus || null,
|
|
784
780
|
valueDisplay: data.value?.total?.formatted || "",
|
|
785
781
|
valueAmount: data.value?.total?.amount || "",
|
|
786
782
|
outstandingBalanceAmount: data.balance?.total?.amount || "",
|
|
@@ -935,6 +931,261 @@ function fromBookingGuestsResponse(response) {
|
|
|
935
931
|
return guests;
|
|
936
932
|
}
|
|
937
933
|
|
|
934
|
+
// src/internal/peek/bookings/metadata-converter.ts
|
|
935
|
+
var UPSERT_SUCCESS_TYPENAME = "UpsertBookingFieldResponsesSuccess";
|
|
936
|
+
function fromBookingMetaDataNode(node) {
|
|
937
|
+
return (node?.fieldResponses ?? []).map(fromFieldResponseNode);
|
|
938
|
+
}
|
|
939
|
+
function fromFieldResponseNode(response) {
|
|
940
|
+
const field = response.fieldLocation?.field;
|
|
941
|
+
const prompt = response.fieldLocation?.prompt;
|
|
942
|
+
const { value, displayValue } = fromValueNode(response.value);
|
|
943
|
+
return {
|
|
944
|
+
id: field?.id || "",
|
|
945
|
+
name: field?.name || "",
|
|
946
|
+
slug: field?.slug || "",
|
|
947
|
+
type: field?.type || "",
|
|
948
|
+
prompt: prompt?.label ?? null,
|
|
949
|
+
promptHint: prompt?.hint ?? null,
|
|
950
|
+
isRequired: Boolean(prompt?.isRequired),
|
|
951
|
+
refid: response.refid || "",
|
|
952
|
+
displayValue,
|
|
953
|
+
value
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function fromValueNode(value) {
|
|
957
|
+
if (!value || !value.__typename) return { value: null, displayValue: "" };
|
|
958
|
+
switch (value.__typename) {
|
|
959
|
+
case "AgeFieldResponseValue": {
|
|
960
|
+
const age = value.age ?? 0;
|
|
961
|
+
return { value: { kind: "age", age }, displayValue: String(age) };
|
|
962
|
+
}
|
|
963
|
+
case "AttachmentFieldResponseValue": {
|
|
964
|
+
const attachmentUrl = value.attachmentUrl ?? "";
|
|
965
|
+
const name = value.name ?? null;
|
|
966
|
+
return {
|
|
967
|
+
value: {
|
|
968
|
+
kind: "attachment",
|
|
969
|
+
attachmentUrl,
|
|
970
|
+
mime: value.mime ?? null,
|
|
971
|
+
name,
|
|
972
|
+
size: value.size ?? null
|
|
973
|
+
},
|
|
974
|
+
displayValue: name || attachmentUrl
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
case "BarcodeFieldResponseValue": {
|
|
978
|
+
const barcodeValue = value.barcodeValue ?? "";
|
|
979
|
+
return {
|
|
980
|
+
value: {
|
|
981
|
+
kind: "barcode",
|
|
982
|
+
barcodeType: value.barcodeType ?? null,
|
|
983
|
+
barcodeValue
|
|
984
|
+
},
|
|
985
|
+
displayValue: barcodeValue
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
case "BooleanFieldResponseValue": {
|
|
989
|
+
const bool = Boolean(value.boolean);
|
|
990
|
+
return { value: { kind: "boolean", boolean: bool }, displayValue: yesNo(bool) };
|
|
991
|
+
}
|
|
992
|
+
case "CheckboxFieldResponseValue": {
|
|
993
|
+
const isChecked = Boolean(value.isChecked);
|
|
994
|
+
return { value: { kind: "checkbox", isChecked }, displayValue: yesNo(isChecked) };
|
|
995
|
+
}
|
|
996
|
+
case "CurrencyFieldResponseValue": {
|
|
997
|
+
const currency = value.currency ?? "";
|
|
998
|
+
return { value: { kind: "currency", currency }, displayValue: currency };
|
|
999
|
+
}
|
|
1000
|
+
case "DateFieldResponseValue": {
|
|
1001
|
+
const date = value.date ?? "";
|
|
1002
|
+
return { value: { kind: "date", date }, displayValue: date };
|
|
1003
|
+
}
|
|
1004
|
+
case "DecimalFieldResponseValue": {
|
|
1005
|
+
const decimal = value.decimal ?? null;
|
|
1006
|
+
return { value: { kind: "decimal", decimal }, displayValue: decimal ?? "" };
|
|
1007
|
+
}
|
|
1008
|
+
case "DurationFieldResponseValue": {
|
|
1009
|
+
const amount = value.duration?.amount ?? 0;
|
|
1010
|
+
const unit = value.duration?.unit ?? null;
|
|
1011
|
+
return { value: { kind: "duration", amount, unit }, displayValue: amountUnit(amount, unit) };
|
|
1012
|
+
}
|
|
1013
|
+
case "EmailFieldResponseValue": {
|
|
1014
|
+
const email = value.email ?? null;
|
|
1015
|
+
return { value: { kind: "email", email }, displayValue: email ?? "" };
|
|
1016
|
+
}
|
|
1017
|
+
case "GuestFieldResponseValue": {
|
|
1018
|
+
const name = value.name ?? null;
|
|
1019
|
+
return {
|
|
1020
|
+
value: {
|
|
1021
|
+
kind: "guest",
|
|
1022
|
+
name,
|
|
1023
|
+
email: value.email ?? null,
|
|
1024
|
+
dateOfBirth: value.dateOfBirth ?? null,
|
|
1025
|
+
notes: value.notes ?? null,
|
|
1026
|
+
waiverSigned: value.waiverSigned ?? null
|
|
1027
|
+
},
|
|
1028
|
+
displayValue: name ?? ""
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
case "HtmlFieldResponseValue": {
|
|
1032
|
+
const html = value.html ?? "";
|
|
1033
|
+
return { value: { kind: "html", html }, displayValue: escapeHtml(html) };
|
|
1034
|
+
}
|
|
1035
|
+
case "ImageUrlFieldResponseValue": {
|
|
1036
|
+
const imageUrl = value.imageUrl ?? "";
|
|
1037
|
+
return { value: { kind: "imageUrl", imageUrl }, displayValue: imageUrl };
|
|
1038
|
+
}
|
|
1039
|
+
case "IntegerFieldResponseValue": {
|
|
1040
|
+
const integer = value.integer ?? 0;
|
|
1041
|
+
return { value: { kind: "integer", integer }, displayValue: String(integer) };
|
|
1042
|
+
}
|
|
1043
|
+
case "LocationFieldResponseValue": {
|
|
1044
|
+
const streetAddress = value.streetAddress ?? null;
|
|
1045
|
+
const locality = value.locality ?? null;
|
|
1046
|
+
const region = value.region ?? null;
|
|
1047
|
+
const postalCode = value.postalCode ?? null;
|
|
1048
|
+
const country = value.country ?? null;
|
|
1049
|
+
return {
|
|
1050
|
+
value: {
|
|
1051
|
+
kind: "location",
|
|
1052
|
+
country,
|
|
1053
|
+
county: value.county ?? null,
|
|
1054
|
+
locality,
|
|
1055
|
+
postalCode,
|
|
1056
|
+
region,
|
|
1057
|
+
streetAddress
|
|
1058
|
+
},
|
|
1059
|
+
displayValue: [streetAddress, locality, region, postalCode, country].filter((part) => part).join(", ")
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
case "LongTextFieldResponseValue": {
|
|
1063
|
+
const longText = value.longText ?? "";
|
|
1064
|
+
return { value: { kind: "longText", longText }, displayValue: longText };
|
|
1065
|
+
}
|
|
1066
|
+
case "MetaFieldResponseValue": {
|
|
1067
|
+
const meta = value.meta ?? null;
|
|
1068
|
+
return { value: { kind: "meta", meta }, displayValue: meta == null ? "" : JSON.stringify(meta) };
|
|
1069
|
+
}
|
|
1070
|
+
case "PercentFieldResponseValue": {
|
|
1071
|
+
const percent = value.percent ?? null;
|
|
1072
|
+
return { value: { kind: "percent", percent }, displayValue: percent == null ? "" : `${percent}%` };
|
|
1073
|
+
}
|
|
1074
|
+
case "PhoneFieldResponseValue": {
|
|
1075
|
+
const phone = value.phone ?? "";
|
|
1076
|
+
return { value: { kind: "phone", phone }, displayValue: phone };
|
|
1077
|
+
}
|
|
1078
|
+
case "ShortTextFieldResponseValue": {
|
|
1079
|
+
const shortText = value.shortText ?? "";
|
|
1080
|
+
return { value: { kind: "shortText", shortText }, displayValue: shortText };
|
|
1081
|
+
}
|
|
1082
|
+
case "TimeFieldResponseValue": {
|
|
1083
|
+
const time = value.time ?? "";
|
|
1084
|
+
return { value: { kind: "time", time }, displayValue: time };
|
|
1085
|
+
}
|
|
1086
|
+
case "UrlFieldResponseValue": {
|
|
1087
|
+
const url = value.url ?? null;
|
|
1088
|
+
return { value: { kind: "url", url }, displayValue: url ?? "" };
|
|
1089
|
+
}
|
|
1090
|
+
case "VolumeFieldResponseValue": {
|
|
1091
|
+
const amount = value.volume?.amount ?? "0";
|
|
1092
|
+
const unit = value.volume?.unit ?? null;
|
|
1093
|
+
return { value: { kind: "volume", amount, unit }, displayValue: amountUnit(amount, unit) };
|
|
1094
|
+
}
|
|
1095
|
+
case "WeightFieldResponseValue": {
|
|
1096
|
+
const amount = value.weight?.amount ?? "0";
|
|
1097
|
+
const unit = value.weight?.unit ?? null;
|
|
1098
|
+
return { value: { kind: "weight", amount, unit }, displayValue: amountUnit(amount, unit) };
|
|
1099
|
+
}
|
|
1100
|
+
default:
|
|
1101
|
+
return { value: null, displayValue: "" };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function yesNo(value) {
|
|
1105
|
+
return value ? "Yes" : "No";
|
|
1106
|
+
}
|
|
1107
|
+
function amountUnit(amount, unit) {
|
|
1108
|
+
return unit ? `${amount} ${unit.toLowerCase()}` : String(amount);
|
|
1109
|
+
}
|
|
1110
|
+
function escapeHtml(value) {
|
|
1111
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1112
|
+
}
|
|
1113
|
+
function toSetMetaDataResult(node, bookingId) {
|
|
1114
|
+
return {
|
|
1115
|
+
success: node?.__typename === UPSERT_SUCCESS_TYPENAME,
|
|
1116
|
+
bookingId: node?.bookingId || bookingId,
|
|
1117
|
+
message: node?.message || ""
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function filterMetaDataByIntegrator(metaData, integrator) {
|
|
1121
|
+
const prefix = `integrators:${integrator}:`;
|
|
1122
|
+
return metaData.filter((entry) => entry.slug.startsWith(prefix)).map((entry) => ({ ...entry, slug: entry.slug.slice(prefix.length) }));
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/internal/peek/bookings/metadata-queries.ts
|
|
1126
|
+
var GUEST_VALUE_PII_FIELDS = "dateOfBirth\n email\n name\n ";
|
|
1127
|
+
function buildGuestValueFields(fullCustomerAccess) {
|
|
1128
|
+
return `... on GuestFieldResponseValue {
|
|
1129
|
+
${fullCustomerAccess ? GUEST_VALUE_PII_FIELDS : ""}notes
|
|
1130
|
+
waiverSigned
|
|
1131
|
+
}`;
|
|
1132
|
+
}
|
|
1133
|
+
function buildMetaDataValueSelection(fullCustomerAccess) {
|
|
1134
|
+
return `value {
|
|
1135
|
+
__typename
|
|
1136
|
+
... on AgeFieldResponseValue { age }
|
|
1137
|
+
... on AttachmentFieldResponseValue { attachmentUrl mime name size }
|
|
1138
|
+
... on BarcodeFieldResponseValue { barcodeType barcodeValue }
|
|
1139
|
+
... on BooleanFieldResponseValue { boolean }
|
|
1140
|
+
... on CheckboxFieldResponseValue { isChecked }
|
|
1141
|
+
... on CurrencyFieldResponseValue { currency }
|
|
1142
|
+
... on DateFieldResponseValue { date }
|
|
1143
|
+
... on DecimalFieldResponseValue { decimal }
|
|
1144
|
+
... on DurationFieldResponseValue { duration { amount unit } }
|
|
1145
|
+
... on EmailFieldResponseValue { email }
|
|
1146
|
+
${buildGuestValueFields(fullCustomerAccess)}
|
|
1147
|
+
... on HtmlFieldResponseValue { html }
|
|
1148
|
+
... on ImageUrlFieldResponseValue { imageUrl }
|
|
1149
|
+
... on IntegerFieldResponseValue { integer }
|
|
1150
|
+
... on LocationFieldResponseValue { country county locality postalCode region streetAddress }
|
|
1151
|
+
... on LongTextFieldResponseValue { longText }
|
|
1152
|
+
... on MetaFieldResponseValue { meta }
|
|
1153
|
+
... on PercentFieldResponseValue { percent }
|
|
1154
|
+
... on PhoneFieldResponseValue { phone }
|
|
1155
|
+
... on ShortTextFieldResponseValue { shortText }
|
|
1156
|
+
... on TimeFieldResponseValue { time }
|
|
1157
|
+
... on UrlFieldResponseValue { url }
|
|
1158
|
+
... on VolumeFieldResponseValue { volume { amount unit } }
|
|
1159
|
+
... on WeightFieldResponseValue { weight { amount unit } }
|
|
1160
|
+
}`;
|
|
1161
|
+
}
|
|
1162
|
+
function buildBookingMetaDataQuery(fullCustomerAccess) {
|
|
1163
|
+
return buildSalesQuery(`
|
|
1164
|
+
... on Booking {
|
|
1165
|
+
displayId
|
|
1166
|
+
id
|
|
1167
|
+
fieldResponses {
|
|
1168
|
+
fieldLocation {
|
|
1169
|
+
field { id name slug type }
|
|
1170
|
+
prompt { label hint isRequired }
|
|
1171
|
+
}
|
|
1172
|
+
refid
|
|
1173
|
+
${buildMetaDataValueSelection(fullCustomerAccess)}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
`);
|
|
1177
|
+
}
|
|
1178
|
+
var UPSERT_BOOKING_FIELD_RESPONSES_MUTATION = `
|
|
1179
|
+
mutation UpsertBookingFieldResponses($input: UpsertBookingFieldResponsesInput!) {
|
|
1180
|
+
upsertBookingFieldResponses(input: $input) {
|
|
1181
|
+
__typename
|
|
1182
|
+
... on UpsertBookingFieldResponsesSuccess { bookingId message }
|
|
1183
|
+
... on BookingNotFoundError { message bookingId }
|
|
1184
|
+
... on GenericError { message }
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
`;
|
|
1188
|
+
|
|
938
1189
|
// src/internal/peek/bookings/payments-on-file-converter.ts
|
|
939
1190
|
function fromPaymentsOnFileResponse(response, bookingId) {
|
|
940
1191
|
const firstEdge = (response?.sales?.edges ?? [])[0];
|
|
@@ -1194,10 +1445,41 @@ function assertRequiredAnswered(resolved, questions) {
|
|
|
1194
1445
|
}
|
|
1195
1446
|
}
|
|
1196
1447
|
|
|
1448
|
+
// src/internal/peek/bookings/list-price.ts
|
|
1449
|
+
var ERROR_LIST_PRICE_INVALID = "listPrice must be a positive number string, e.g. '100', '1,000', or '10.00'";
|
|
1450
|
+
function parseListPriceToCents(listPrice) {
|
|
1451
|
+
if (typeof listPrice !== "string") {
|
|
1452
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1453
|
+
}
|
|
1454
|
+
const cleaned = listPrice.replace(/,/g, "").trim();
|
|
1455
|
+
if (!/^\d+(\.\d+)?$/.test(cleaned)) {
|
|
1456
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1457
|
+
}
|
|
1458
|
+
const cents = Math.round(Number(cleaned) * 100);
|
|
1459
|
+
if (!Number.isFinite(cents) || cents <= 0) {
|
|
1460
|
+
throw new Error(ERROR_LIST_PRICE_INVALID);
|
|
1461
|
+
}
|
|
1462
|
+
return cents;
|
|
1463
|
+
}
|
|
1464
|
+
function centsToAmount(cents) {
|
|
1465
|
+
return (cents / 100).toFixed(2);
|
|
1466
|
+
}
|
|
1467
|
+
function distributeListPrice(listPrice, ticketCount) {
|
|
1468
|
+
const totalCents = parseListPriceToCents(listPrice);
|
|
1469
|
+
const perTicketCents = Math.floor(totalCents / ticketCount);
|
|
1470
|
+
const amounts = [];
|
|
1471
|
+
for (let i = 0; i < ticketCount - 1; i += 1) {
|
|
1472
|
+
amounts.push(centsToAmount(perTicketCents));
|
|
1473
|
+
}
|
|
1474
|
+
amounts.push(centsToAmount(totalCents - perTicketCents * (ticketCount - 1)));
|
|
1475
|
+
return amounts;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1197
1478
|
// src/internal/peek/bookings/booking-service.ts
|
|
1198
1479
|
var DEFAULT_PAGE_SIZE2 = 50;
|
|
1199
1480
|
var DEFAULT_CANCEL_NOTE = "Canceled";
|
|
1200
1481
|
var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
|
|
1482
|
+
var DEFAULT_CURRENCY = "USD";
|
|
1201
1483
|
var BOOKING_DB_ID_REGEX = /^b_[a-z0-9]+$/;
|
|
1202
1484
|
var BOOKING_DISPLAY_ID_REGEX = /^B-[A-Z0-9]+$/;
|
|
1203
1485
|
var ORDER_DB_ID_REGEX = /^o_[a-z0-9]+$/;
|
|
@@ -1210,6 +1492,17 @@ var ERROR_ADDON_OPTION_ID_REQUIRED = "addonOptionId is required";
|
|
|
1210
1492
|
var ERROR_QUANTITY_INVALID = "quantity must be a positive integer string";
|
|
1211
1493
|
var ERROR_INVALID_BOOKING_ID = "bookingId is required and must be a valid booking id, e.g. 'b_abc123' or 'B-ABC123'";
|
|
1212
1494
|
var ERROR_INVALID_ORDER_ID = "orderId is required and must be a valid order id, e.g. 'o_abc123' or 'O-ABC123'";
|
|
1495
|
+
var ERROR_CUSTOM_STATUS_REQUIRED = "customStatus is required and must be a non-empty string";
|
|
1496
|
+
var ERROR_GUESTS_REQUIRED = "at least one guest is required";
|
|
1497
|
+
var ERROR_ATTACHMENTS_REQUIRED = "at least one attachment is required";
|
|
1498
|
+
var META_FIELD_GUEST = "guest";
|
|
1499
|
+
var META_FIELD_ATTACHMENT = "attachment";
|
|
1500
|
+
var META_FIELD_MANIFEST_URL = "manifest_url";
|
|
1501
|
+
var META_FIELD_RESERVATION_ID = "reservation_id";
|
|
1502
|
+
var META_FIELD_BOOKING_STATUS = "booking_status";
|
|
1503
|
+
var META_FIELD_ASSIGNED_PRODUCT = "assigned_product";
|
|
1504
|
+
var META_FIELD_ASSIGNED_EMPLOYEE = "assigned_employee";
|
|
1505
|
+
var META_FIELD_INSURANCE_PURCHASED = "insurance_purchased";
|
|
1213
1506
|
var ERROR_BOOKING_NOT_FOUND = "Booking not found";
|
|
1214
1507
|
var ERROR_MULTIPLE_BOOKINGS_FOUND = "Expected exactly one booking for the provided bookingId";
|
|
1215
1508
|
var ERROR_NO_ADDON_OPTION_TO_REMOVE = "No confirmed add-on option matching addonOptionId was found on the booking";
|
|
@@ -1222,12 +1515,15 @@ var BookingService = class {
|
|
|
1222
1515
|
this.deps = deps;
|
|
1223
1516
|
this.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE2;
|
|
1224
1517
|
this.fullCustomerAccess = resolveAccessOptions(options.accessOptions).fullCustomerAccess;
|
|
1518
|
+
this.integrator = options.integrator ?? "";
|
|
1225
1519
|
}
|
|
1226
1520
|
client;
|
|
1227
1521
|
deps;
|
|
1228
1522
|
pageSize;
|
|
1229
1523
|
/** Whether customer PII is requested and payment operations are allowed. */
|
|
1230
1524
|
fullCustomerAccess;
|
|
1525
|
+
/** Integrator id scoping `getMetaData` results. */
|
|
1526
|
+
integrator;
|
|
1231
1527
|
/**
|
|
1232
1528
|
* Throws a {@link PiiAccessDisabledError} for `operation` when this service was
|
|
1233
1529
|
* created without `fullCustomerAccess` — used to gate payment and booking-modification
|
|
@@ -1315,6 +1611,131 @@ var BookingService = class {
|
|
|
1315
1611
|
);
|
|
1316
1612
|
return fromBookingGuestsResponse(body.data);
|
|
1317
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Returns a booking's custom-field metadata (its `fieldResponses`), scoped to
|
|
1616
|
+
* this service's integrator. Only fields whose slug starts with
|
|
1617
|
+
* `integrators:<integrator>:` are returned, and that prefix is stripped from
|
|
1618
|
+
* each returned `slug`. Returns `[]` when the booking is not found or has no
|
|
1619
|
+
* matching fields.
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```ts
|
|
1623
|
+
* const meta = await peek.getBookingService().getMetaData("b_abc123");
|
|
1624
|
+
* for (const field of meta) {
|
|
1625
|
+
* if (field.value?.kind === "url") console.log(field.slug, field.value.url);
|
|
1626
|
+
* }
|
|
1627
|
+
* ```
|
|
1628
|
+
*/
|
|
1629
|
+
async getMetaData(bookingId) {
|
|
1630
|
+
assertBookingId(bookingId);
|
|
1631
|
+
const body = await this.client.request(
|
|
1632
|
+
SALES_ENDPOINT,
|
|
1633
|
+
buildBookingMetaDataQuery(this.fullCustomerAccess),
|
|
1634
|
+
buildBookingsVariables({
|
|
1635
|
+
pageSize: this.pageSize,
|
|
1636
|
+
after: null,
|
|
1637
|
+
bookingId: normalizeBookingId(bookingId)
|
|
1638
|
+
})
|
|
1639
|
+
);
|
|
1640
|
+
const firstEdge = (body.data?.sales?.edges ?? [])[0];
|
|
1641
|
+
if (!firstEdge) {
|
|
1642
|
+
return [];
|
|
1643
|
+
}
|
|
1644
|
+
return filterMetaDataByIntegrator(fromBookingMetaDataNode(firstEdge.node), this.integrator);
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Sets the booking's `guest` metadata field. Each guest requires a `name` and
|
|
1648
|
+
* `email`; `dateOfBirth`/`waiverSigned`/`notes` are optional.
|
|
1649
|
+
*/
|
|
1650
|
+
async setMetaDataGuest(bookingId, guests) {
|
|
1651
|
+
if (!Array.isArray(guests) || guests.length === 0) {
|
|
1652
|
+
throw new Error(ERROR_GUESTS_REQUIRED);
|
|
1653
|
+
}
|
|
1654
|
+
const built = guests.map((guest) => {
|
|
1655
|
+
assertNonEmptyString(guest?.name, "guest name");
|
|
1656
|
+
assertNonEmptyString(guest?.email, "guest email");
|
|
1657
|
+
const out = { name: guest.name, email: guest.email };
|
|
1658
|
+
if (guest.dateOfBirth !== void 0) out.dateOfBirth = guest.dateOfBirth;
|
|
1659
|
+
if (guest.waiverSigned !== void 0) out.waiverSigned = guest.waiverSigned;
|
|
1660
|
+
if (guest.notes !== void 0) out.notes = guest.notes;
|
|
1661
|
+
return out;
|
|
1662
|
+
});
|
|
1663
|
+
return this.upsertFieldResponse(bookingId, { fieldName: META_FIELD_GUEST, guests: built });
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Sets the booking's `attachment` metadata field. Each attachment requires a
|
|
1667
|
+
* `name` and `attachmentUrl`.
|
|
1668
|
+
*/
|
|
1669
|
+
async setMetaDataAttachment(bookingId, attachments) {
|
|
1670
|
+
if (!Array.isArray(attachments) || attachments.length === 0) {
|
|
1671
|
+
throw new Error(ERROR_ATTACHMENTS_REQUIRED);
|
|
1672
|
+
}
|
|
1673
|
+
const built = attachments.map((attachment) => {
|
|
1674
|
+
assertNonEmptyString(attachment?.name, "attachment name");
|
|
1675
|
+
assertNonEmptyString(attachment?.attachmentUrl, "attachment attachmentUrl");
|
|
1676
|
+
return { name: attachment.name, attachmentUrl: attachment.attachmentUrl };
|
|
1677
|
+
});
|
|
1678
|
+
return this.upsertFieldResponse(bookingId, {
|
|
1679
|
+
fieldName: META_FIELD_ATTACHMENT,
|
|
1680
|
+
attachments: built
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
/** Sets the booking's `manifest_url` metadata field. */
|
|
1684
|
+
async setMetaDataManifestUrl(bookingId, url) {
|
|
1685
|
+
assertNonEmptyString(url, "url");
|
|
1686
|
+
return this.upsertFieldResponse(bookingId, { fieldName: META_FIELD_MANIFEST_URL, url });
|
|
1687
|
+
}
|
|
1688
|
+
/** Sets the booking's `reservation_id` metadata field. */
|
|
1689
|
+
async setMetaDataReservationId(bookingId, reservationId) {
|
|
1690
|
+
return this.setShortTextField(bookingId, META_FIELD_RESERVATION_ID, reservationId, "reservationId");
|
|
1691
|
+
}
|
|
1692
|
+
/** Sets the booking's `booking_status` metadata field. */
|
|
1693
|
+
async setMetaDataBookingStatus(bookingId, status) {
|
|
1694
|
+
return this.setShortTextField(bookingId, META_FIELD_BOOKING_STATUS, status, "status");
|
|
1695
|
+
}
|
|
1696
|
+
/** Sets the booking's `assigned_product` metadata field. */
|
|
1697
|
+
async setMetaDataAssignedProduct(bookingId, productName) {
|
|
1698
|
+
return this.setShortTextField(bookingId, META_FIELD_ASSIGNED_PRODUCT, productName, "productName");
|
|
1699
|
+
}
|
|
1700
|
+
/** Sets the booking's `assigned_employee` metadata field. */
|
|
1701
|
+
async setMetaDataAssignedEmployee(bookingId, employeeName) {
|
|
1702
|
+
return this.setShortTextField(bookingId, META_FIELD_ASSIGNED_EMPLOYEE, employeeName, "employeeName");
|
|
1703
|
+
}
|
|
1704
|
+
/** Sets the booking's `insurance_purchased` metadata field. */
|
|
1705
|
+
async setMetaDataInsurancePurchased(bookingId, insurancePurchased) {
|
|
1706
|
+
if (typeof insurancePurchased !== "boolean") {
|
|
1707
|
+
throw new Error("insurancePurchased is required and must be a boolean");
|
|
1708
|
+
}
|
|
1709
|
+
return this.upsertFieldResponse(bookingId, {
|
|
1710
|
+
fieldName: META_FIELD_INSURANCE_PURCHASED,
|
|
1711
|
+
boolean: insurancePurchased
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
/** Validates a non-empty `shortText` value and upserts it under `fieldName`. */
|
|
1715
|
+
setShortTextField(bookingId, fieldName, value, argName) {
|
|
1716
|
+
assertNonEmptyString(value, argName);
|
|
1717
|
+
return this.upsertFieldResponse(bookingId, { fieldName, shortText: value });
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Validates the booking id and upserts a single field response (scoped to this
|
|
1721
|
+
* service's integrator), mapping the result union into a {@link SetMetaDataResult}.
|
|
1722
|
+
*/
|
|
1723
|
+
async upsertFieldResponse(bookingId, fieldResponse) {
|
|
1724
|
+
assertBookingId(bookingId);
|
|
1725
|
+
const normalized = normalizeBookingId(bookingId);
|
|
1726
|
+
const body = await this.client.request(
|
|
1727
|
+
SALES_ENDPOINT,
|
|
1728
|
+
UPSERT_BOOKING_FIELD_RESPONSES_MUTATION,
|
|
1729
|
+
{
|
|
1730
|
+
input: {
|
|
1731
|
+
bookingId: normalized,
|
|
1732
|
+
integrator: this.integrator,
|
|
1733
|
+
fieldResponses: [fieldResponse]
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
);
|
|
1737
|
+
return toSetMetaDataResult(body.data?.upsertBookingFieldResponses, normalized);
|
|
1738
|
+
}
|
|
1318
1739
|
/** Returns the payments on file for a booking, or null when not found. */
|
|
1319
1740
|
async getPaymentsOnFile(bookingId) {
|
|
1320
1741
|
this.assertPiiEnabled("getPaymentsOnFile");
|
|
@@ -1359,6 +1780,30 @@ ${note}`;
|
|
|
1359
1780
|
});
|
|
1360
1781
|
return this.getById(normalized);
|
|
1361
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Sets a booking's operator-facing custom status. Returns `true` when the
|
|
1785
|
+
* gateway confirms the stored `operatorStatus` equals the requested
|
|
1786
|
+
* `customStatus`, `false` otherwise.
|
|
1787
|
+
*
|
|
1788
|
+
* @example
|
|
1789
|
+
* ```ts
|
|
1790
|
+
* const ok = await peek.getBookingService().updateCustomStatus("b_abc123", "Awaiting deposit");
|
|
1791
|
+
* ```
|
|
1792
|
+
*
|
|
1793
|
+
* @throws {Error} when `bookingId` is not a valid booking id (`b_…`/`B-…`) or
|
|
1794
|
+
* when `customStatus` is not a non-empty string.
|
|
1795
|
+
*/
|
|
1796
|
+
async updateCustomStatus(bookingId, customStatus) {
|
|
1797
|
+
assertBookingId(bookingId);
|
|
1798
|
+
assertCustomStatus(customStatus);
|
|
1799
|
+
const normalized = normalizeBookingId(bookingId);
|
|
1800
|
+
const body = await this.client.request(
|
|
1801
|
+
SALES_ENDPOINT,
|
|
1802
|
+
UPDATE_OPERATOR_STATUS_MUTATION,
|
|
1803
|
+
{ input: { id: normalized, operatorStatus: customStatus } }
|
|
1804
|
+
);
|
|
1805
|
+
return body.data?.updateOperatorStatusForBooking?.booking?.operatorStatus === customStatus;
|
|
1806
|
+
}
|
|
1362
1807
|
/** Cancels a booking and returns its id/displayId/status. */
|
|
1363
1808
|
async cancel(bookingId, notes = DEFAULT_CANCEL_NOTE) {
|
|
1364
1809
|
assertBookingId(bookingId);
|
|
@@ -1714,13 +2159,26 @@ ${note}`;
|
|
|
1714
2159
|
*/
|
|
1715
2160
|
async create(input) {
|
|
1716
2161
|
validateCreateInput(input);
|
|
1717
|
-
const
|
|
2162
|
+
const ticketCount = input.tickets.reduce((count, ticket) => count + ticket.quantity, 0);
|
|
2163
|
+
const listPriceAmounts = input.listPrice === void 0 ? null : distributeListPrice(input.listPrice, ticketCount);
|
|
2164
|
+
const [questionAnswers, currency] = await Promise.all([
|
|
2165
|
+
this.buildQuestionAnswers(input),
|
|
2166
|
+
listPriceAmounts ? this.resolveActivityCurrency(input.activityId) : Promise.resolve("")
|
|
2167
|
+
]);
|
|
2168
|
+
let seatIndex = 0;
|
|
1718
2169
|
const tickets = input.tickets.flatMap(
|
|
1719
|
-
(ticket) => Array.from({ length: ticket.quantity }, () =>
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2170
|
+
(ticket) => Array.from({ length: ticket.quantity }, () => {
|
|
2171
|
+
const seat = {
|
|
2172
|
+
resourceOptionId: ticket.resourceOptionId,
|
|
2173
|
+
reservationStatus: "CONFIRMED",
|
|
2174
|
+
refid: randomUUID()
|
|
2175
|
+
};
|
|
2176
|
+
if (listPriceAmounts) {
|
|
2177
|
+
seat.price = { amount: listPriceAmounts[seatIndex], currency };
|
|
2178
|
+
}
|
|
2179
|
+
seatIndex += 1;
|
|
2180
|
+
return seat;
|
|
2181
|
+
})
|
|
1724
2182
|
);
|
|
1725
2183
|
const bookingQuote = {
|
|
1726
2184
|
tickets,
|
|
@@ -1835,6 +2293,15 @@ ${note}`;
|
|
|
1835
2293
|
}
|
|
1836
2294
|
return result.transactionId;
|
|
1837
2295
|
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Resolves an activity's ISO currency via the product service, falling back to
|
|
2298
|
+
* {@link DEFAULT_CURRENCY} when the activity (or its currency) can't be found.
|
|
2299
|
+
*/
|
|
2300
|
+
async resolveActivityCurrency(activityId) {
|
|
2301
|
+
const products = await this.deps.productService.getAllProducts();
|
|
2302
|
+
const match = products.find((product) => product.productId === activityId);
|
|
2303
|
+
return match?.currency || DEFAULT_CURRENCY;
|
|
2304
|
+
}
|
|
1838
2305
|
/** Finds the parent item id of an add-on by matching its option id. */
|
|
1839
2306
|
async resolveParentItemId(addonOptionId) {
|
|
1840
2307
|
const products = await this.deps.productService.getAllProducts();
|
|
@@ -1909,6 +2376,16 @@ function assertBookingId(bookingId) {
|
|
|
1909
2376
|
throw new Error(ERROR_INVALID_BOOKING_ID);
|
|
1910
2377
|
}
|
|
1911
2378
|
}
|
|
2379
|
+
function assertCustomStatus(customStatus) {
|
|
2380
|
+
if (typeof customStatus !== "string" || customStatus.trim().length === 0) {
|
|
2381
|
+
throw new Error(ERROR_CUSTOM_STATUS_REQUIRED);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
function assertNonEmptyString(value, field) {
|
|
2385
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2386
|
+
throw new Error(`${field} is required and must be a non-empty string`);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
1912
2389
|
function assertOrderId(orderId) {
|
|
1913
2390
|
if (!(ORDER_DB_ID_REGEX.test(orderId) || ORDER_DISPLAY_ID_REGEX.test(orderId))) {
|
|
1914
2391
|
throw new Error(ERROR_INVALID_ORDER_ID);
|
|
@@ -2175,7 +2652,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
2175
2652
|
// src/internal/sdk-headers.ts
|
|
2176
2653
|
var SDK_HEADER_NAME = "x-peek-sdk";
|
|
2177
2654
|
var SDK_PLATFORM = "js";
|
|
2178
|
-
var SDK_VERSION = "0.9.
|
|
2655
|
+
var SDK_VERSION = "0.9.4";
|
|
2179
2656
|
var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
|
|
2180
2657
|
|
|
2181
2658
|
// src/internal/peek/graphql-client.ts
|
|
@@ -3461,7 +3938,7 @@ var CREATE_PROMO_CODE_MUTATION = `
|
|
|
3461
3938
|
|
|
3462
3939
|
// src/internal/peek/promo-codes/promo-code-service.ts
|
|
3463
3940
|
var DEFAULT_PAGE_SIZE3 = 50;
|
|
3464
|
-
var
|
|
3941
|
+
var DEFAULT_CURRENCY2 = "USD";
|
|
3465
3942
|
var CURRENCY_PATTERN = /^[A-Z]{3}$/;
|
|
3466
3943
|
var PromoCodeService = class {
|
|
3467
3944
|
constructor(client, options = {}) {
|
|
@@ -3507,7 +3984,7 @@ var PromoCodeService = class {
|
|
|
3507
3984
|
if (Number.isNaN(parseFloat(input.amount))) {
|
|
3508
3985
|
throw new Error("amount must be a valid number");
|
|
3509
3986
|
}
|
|
3510
|
-
const currency = input.currency ||
|
|
3987
|
+
const currency = input.currency || DEFAULT_CURRENCY2;
|
|
3511
3988
|
if (!CURRENCY_PATTERN.test(currency)) {
|
|
3512
3989
|
throw new Error("currency must be 3 uppercase letters");
|
|
3513
3990
|
}
|
|
@@ -3881,6 +4358,7 @@ var PeekAccessService = class {
|
|
|
3881
4358
|
productServiceOptions;
|
|
3882
4359
|
accessOptions;
|
|
3883
4360
|
jwtSecret;
|
|
4361
|
+
issuer;
|
|
3884
4362
|
productService;
|
|
3885
4363
|
accountUserService;
|
|
3886
4364
|
resourcePoolService;
|
|
@@ -3891,7 +4369,7 @@ var PeekAccessService = class {
|
|
|
3891
4369
|
dailyNoteService;
|
|
3892
4370
|
availabilityService;
|
|
3893
4371
|
membershipService;
|
|
3894
|
-
|
|
4372
|
+
bookingServices = /* @__PURE__ */ new Map();
|
|
3895
4373
|
reviewService;
|
|
3896
4374
|
constructor(config) {
|
|
3897
4375
|
const hasApiUrl = !!config.apiUrl;
|
|
@@ -3902,6 +4380,7 @@ var PeekAccessService = class {
|
|
|
3902
4380
|
if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
|
|
3903
4381
|
if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
|
|
3904
4382
|
this.jwtSecret = config.jwtSecret;
|
|
4383
|
+
this.issuer = config.issuer;
|
|
3905
4384
|
const logger = config.logger ?? noopLogger;
|
|
3906
4385
|
const tokens = createTokenManager(config);
|
|
3907
4386
|
const useRegistrySlug = hasApiUrl || isV2;
|
|
@@ -4062,17 +4541,25 @@ var PeekAccessService = class {
|
|
|
4062
4541
|
}
|
|
4063
4542
|
/**
|
|
4064
4543
|
* Returns the {@link BookingService} for this install, bound to the shared
|
|
4065
|
-
* authenticated transport.
|
|
4544
|
+
* authenticated transport. Instances are created lazily and reused per
|
|
4545
|
+
* `integrator`.
|
|
4546
|
+
*
|
|
4547
|
+
* `integrator` scopes `getMetaData` results to a custom integrator id. **Do
|
|
4548
|
+
* not set it** unless Peek engineering created a custom integrator id for this
|
|
4549
|
+
* integration — omit it and the correct default is used.
|
|
4066
4550
|
*/
|
|
4067
|
-
getBookingService() {
|
|
4068
|
-
|
|
4069
|
-
|
|
4551
|
+
getBookingService(integrator) {
|
|
4552
|
+
const resolved = integrator ?? this.issuer;
|
|
4553
|
+
let service = this.bookingServices.get(resolved);
|
|
4554
|
+
if (!service) {
|
|
4555
|
+
service = new BookingService(
|
|
4070
4556
|
this.client,
|
|
4071
4557
|
{ productService: this.getProductService() },
|
|
4072
|
-
{ accessOptions: this.accessOptions }
|
|
4558
|
+
{ accessOptions: this.accessOptions, integrator: resolved }
|
|
4073
4559
|
);
|
|
4560
|
+
this.bookingServices.set(resolved, service);
|
|
4074
4561
|
}
|
|
4075
|
-
return
|
|
4562
|
+
return service;
|
|
4076
4563
|
}
|
|
4077
4564
|
/**
|
|
4078
4565
|
* Returns the {@link ReviewService} for this install, bound to the shared
|