@voyant-travel/storefront 0.120.1

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.
Files changed (126) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +231 -0
  3. package/dist/booking-intents.d.ts +42 -0
  4. package/dist/booking-intents.d.ts.map +1 -0
  5. package/dist/booking-intents.js +83 -0
  6. package/dist/customer-portal/index.d.ts +16 -0
  7. package/dist/customer-portal/index.d.ts.map +1 -0
  8. package/dist/customer-portal/index.js +23 -0
  9. package/dist/customer-portal/route-runtime.d.ts +16 -0
  10. package/dist/customer-portal/route-runtime.d.ts.map +1 -0
  11. package/dist/customer-portal/route-runtime.js +27 -0
  12. package/dist/customer-portal/routes-public.d.ts +1936 -0
  13. package/dist/customer-portal/routes-public.d.ts.map +1 -0
  14. package/dist/customer-portal/routes-public.js +165 -0
  15. package/dist/customer-portal/routes.d.ts +43 -0
  16. package/dist/customer-portal/routes.d.ts.map +1 -0
  17. package/dist/customer-portal/routes.js +17 -0
  18. package/dist/customer-portal/service-public-impl.d.ts +138 -0
  19. package/dist/customer-portal/service-public-impl.d.ts.map +1 -0
  20. package/dist/customer-portal/service-public-impl.js +1808 -0
  21. package/dist/customer-portal/service-public.d.ts +2 -0
  22. package/dist/customer-portal/service-public.d.ts.map +1 -0
  23. package/dist/customer-portal/service-public.js +1 -0
  24. package/dist/customer-portal/validation-public/bookings.d.ts +551 -0
  25. package/dist/customer-portal/validation-public/bookings.d.ts.map +1 -0
  26. package/dist/customer-portal/validation-public/bookings.js +132 -0
  27. package/dist/customer-portal/validation-public/common.d.ts +162 -0
  28. package/dist/customer-portal/validation-public/common.d.ts.map +1 -0
  29. package/dist/customer-portal/validation-public/common.js +139 -0
  30. package/dist/customer-portal/validation-public/profile.d.ts +749 -0
  31. package/dist/customer-portal/validation-public/profile.d.ts.map +1 -0
  32. package/dist/customer-portal/validation-public/profile.js +308 -0
  33. package/dist/customer-portal/validation-public.d.ts +3 -0
  34. package/dist/customer-portal/validation-public.d.ts.map +1 -0
  35. package/dist/customer-portal/validation-public.js +2 -0
  36. package/dist/guest-booking-guard.d.ts +24 -0
  37. package/dist/guest-booking-guard.d.ts.map +1 -0
  38. package/dist/guest-booking-guard.js +55 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +41 -0
  42. package/dist/product-extra-ref.d.ts +238 -0
  43. package/dist/product-extra-ref.d.ts.map +1 -0
  44. package/dist/product-extra-ref.js +22 -0
  45. package/dist/routes-admin.d.ts +220 -0
  46. package/dist/routes-admin.d.ts.map +1 -0
  47. package/dist/routes-admin.js +28 -0
  48. package/dist/routes-public.d.ts +1475 -0
  49. package/dist/routes-public.d.ts.map +1 -0
  50. package/dist/routes-public.js +362 -0
  51. package/dist/service-booking-session-bootstrap.d.ts +227 -0
  52. package/dist/service-booking-session-bootstrap.d.ts.map +1 -0
  53. package/dist/service-booking-session-bootstrap.js +287 -0
  54. package/dist/service-boundary-resource-sql.d.ts +18 -0
  55. package/dist/service-boundary-resource-sql.d.ts.map +1 -0
  56. package/dist/service-boundary-resource-sql.js +73 -0
  57. package/dist/service-boundary-sql.d.ts +103 -0
  58. package/dist/service-boundary-sql.d.ts.map +1 -0
  59. package/dist/service-boundary-sql.js +307 -0
  60. package/dist/service-departures-core.d.ts +41 -0
  61. package/dist/service-departures-core.d.ts.map +1 -0
  62. package/dist/service-departures-core.js +92 -0
  63. package/dist/service-departures-extensions.d.ts +46 -0
  64. package/dist/service-departures-extensions.d.ts.map +1 -0
  65. package/dist/service-departures-extensions.js +81 -0
  66. package/dist/service-departures-offers.d.ts +220 -0
  67. package/dist/service-departures-offers.d.ts.map +1 -0
  68. package/dist/service-departures-offers.js +177 -0
  69. package/dist/service-departures-price-preview.d.ts +306 -0
  70. package/dist/service-departures-price-preview.d.ts.map +1 -0
  71. package/dist/service-departures-price-preview.js +383 -0
  72. package/dist/service-departures-pricing-context.d.ts +115 -0
  73. package/dist/service-departures-pricing-context.d.ts.map +1 -0
  74. package/dist/service-departures-pricing-context.js +237 -0
  75. package/dist/service-departures-pricing.d.ts +5 -0
  76. package/dist/service-departures-pricing.d.ts.map +1 -0
  77. package/dist/service-departures-pricing.js +4 -0
  78. package/dist/service-departures.d.ts +192 -0
  79. package/dist/service-departures.d.ts.map +1 -0
  80. package/dist/service-departures.js +213 -0
  81. package/dist/service-intake.d.ts +130 -0
  82. package/dist/service-intake.d.ts.map +1 -0
  83. package/dist/service-intake.js +274 -0
  84. package/dist/service-transport-eligibility.d.ts +10 -0
  85. package/dist/service-transport-eligibility.d.ts.map +1 -0
  86. package/dist/service-transport-eligibility.js +198 -0
  87. package/dist/service.d.ts +1062 -0
  88. package/dist/service.d.ts.map +1 -0
  89. package/dist/service.js +332 -0
  90. package/dist/transport-eligibility.d.ts +4 -0
  91. package/dist/transport-eligibility.d.ts.map +1 -0
  92. package/dist/transport-eligibility.js +2 -0
  93. package/dist/validation/departures.d.ts +1669 -0
  94. package/dist/validation/departures.d.ts.map +1 -0
  95. package/dist/validation/departures.js +397 -0
  96. package/dist/validation/intake.d.ts +147 -0
  97. package/dist/validation/intake.d.ts.map +1 -0
  98. package/dist/validation/intake.js +69 -0
  99. package/dist/validation/offers.d.ts +340 -0
  100. package/dist/validation/offers.d.ts.map +1 -0
  101. package/dist/validation/offers.js +117 -0
  102. package/dist/validation-settings.d.ts +609 -0
  103. package/dist/validation-settings.d.ts.map +1 -0
  104. package/dist/validation-settings.js +235 -0
  105. package/dist/validation-transport-eligibility.d.ts +314 -0
  106. package/dist/validation-transport-eligibility.d.ts.map +1 -0
  107. package/dist/validation-transport-eligibility.js +97 -0
  108. package/dist/validation.d.ts +6 -0
  109. package/dist/validation.d.ts.map +1 -0
  110. package/dist/validation.js +4 -0
  111. package/dist/verification/index.d.ts +12 -0
  112. package/dist/verification/index.d.ts.map +1 -0
  113. package/dist/verification/index.js +18 -0
  114. package/dist/verification/routes-public.d.ts +121 -0
  115. package/dist/verification/routes-public.d.ts.map +1 -0
  116. package/dist/verification/routes-public.js +125 -0
  117. package/dist/verification/schema.d.ts +273 -0
  118. package/dist/verification/schema.d.ts.map +1 -0
  119. package/dist/verification/schema.js +50 -0
  120. package/dist/verification/service.d.ts +114 -0
  121. package/dist/verification/service.d.ts.map +1 -0
  122. package/dist/verification/service.js +283 -0
  123. package/dist/verification/validation.d.ts +98 -0
  124. package/dist/verification/validation.d.ts.map +1 -0
  125. package/dist/verification/validation.js +54 -0
  126. package/package.json +148 -0
@@ -0,0 +1,198 @@
1
+ import { storefrontTransportEligibilityRuleSchema } from "./validation-transport-eligibility.js";
2
+ function parseDate(value) {
3
+ if (!value)
4
+ return null;
5
+ const date = new Date(`${value}T00:00:00.000Z`);
6
+ return Number.isNaN(date.getTime()) ? null : date;
7
+ }
8
+ function addDays(value, days) {
9
+ const date = parseDate(value);
10
+ if (!date)
11
+ return value;
12
+ date.setUTCDate(date.getUTCDate() + days);
13
+ return date.toISOString().slice(0, 10);
14
+ }
15
+ function getAgeOn(dateOfBirth, onDate) {
16
+ const birth = parseDate(dateOfBirth);
17
+ const reference = parseDate(onDate);
18
+ if (!birth || !reference)
19
+ return null;
20
+ let age = reference.getUTCFullYear() - birth.getUTCFullYear();
21
+ const birthdayThisYear = Date.UTC(reference.getUTCFullYear(), birth.getUTCMonth(), birth.getUTCDate());
22
+ if (reference.getTime() < birthdayThisYear) {
23
+ age -= 1;
24
+ }
25
+ return age;
26
+ }
27
+ function isBeforeDate(left, right) {
28
+ const leftDate = parseDate(left);
29
+ const rightDate = parseDate(right);
30
+ if (!leftDate || !rightDate)
31
+ return false;
32
+ return leftDate.getTime() < rightDate.getTime();
33
+ }
34
+ function findDocuments(traveler, rule) {
35
+ if (rule.requiredDocumentType === "none")
36
+ return [];
37
+ if (rule.requiredDocumentType === "passport_or_id_card") {
38
+ return traveler.documents.filter((document) => document.type === "passport" || document.type === "id_card");
39
+ }
40
+ return traveler.documents.filter((document) => document.type === rule.requiredDocumentType);
41
+ }
42
+ function documentTypeLabel(rule) {
43
+ switch (rule.requiredDocumentType) {
44
+ case "passport_or_id_card":
45
+ return "passport or ID card";
46
+ case "id_card":
47
+ return "ID card";
48
+ case "passport":
49
+ return "passport";
50
+ case "none":
51
+ return "travel document";
52
+ }
53
+ }
54
+ function buildIssue(code, traveler, rule, message) {
55
+ return {
56
+ code,
57
+ severity: rule.severity,
58
+ message: rule.message ?? message,
59
+ travelerRef: traveler.travelerRef,
60
+ ruleId: rule.id,
61
+ destinationCountries: rule.destinationCountries,
62
+ requiredDocumentType: rule.requiredDocumentType,
63
+ };
64
+ }
65
+ function pushIssue(target, issue) {
66
+ if (issue.severity === "warning") {
67
+ target.warnings.push(issue);
68
+ }
69
+ else {
70
+ target.blockingIssues.push(issue);
71
+ }
72
+ }
73
+ function appliesToTarget(rule, target) {
74
+ if (rule.productId && rule.productId !== target.productId)
75
+ return false;
76
+ if (rule.departureId && rule.departureId !== target.departureId)
77
+ return false;
78
+ return true;
79
+ }
80
+ function appliesToTraveler(rule, traveler, travelStartsOn) {
81
+ if (rule.nationalityCountries.length > 0) {
82
+ if (!traveler.nationalityCountry)
83
+ return "missing_nationality";
84
+ if (!rule.nationalityCountries.includes(traveler.nationalityCountry))
85
+ return false;
86
+ }
87
+ if (rule.minAge == null && rule.maxAge == null)
88
+ return true;
89
+ if (!traveler.dateOfBirth || !travelStartsOn)
90
+ return "missing_age_basis";
91
+ const age = getAgeOn(traveler.dateOfBirth, travelStartsOn);
92
+ if (age == null)
93
+ return "missing_age_basis";
94
+ if (rule.minAge != null && age < rule.minAge)
95
+ return false;
96
+ if (rule.maxAge != null && age > rule.maxAge)
97
+ return false;
98
+ return true;
99
+ }
100
+ function evaluateTravelerRule(traveler, rule, target) {
101
+ const blockingIssues = [];
102
+ const warnings = [];
103
+ const resultTarget = { blockingIssues, warnings };
104
+ const travelerApplicability = appliesToTraveler(rule, traveler, target.travelStartsOn);
105
+ if (travelerApplicability === false) {
106
+ return null;
107
+ }
108
+ if (travelerApplicability === "missing_nationality") {
109
+ pushIssue(resultTarget, buildIssue("nationality_required", traveler, rule, "Traveler nationality is required to evaluate destination document rules."));
110
+ }
111
+ if (travelerApplicability === "missing_age_basis") {
112
+ pushIssue(resultTarget, buildIssue("date_of_birth_required", traveler, rule, "Traveler date of birth and travel start date are required to evaluate age rules."));
113
+ }
114
+ const documents = findDocuments(traveler, rule);
115
+ if (rule.requiredDocumentType !== "none" && documents.length === 0) {
116
+ pushIssue(resultTarget, buildIssue("document_required", traveler, rule, `Traveler needs a ${documentTypeLabel(rule)} for this destination.`));
117
+ }
118
+ if (rule.minValidityDaysAfterReturn > 0) {
119
+ if (!target.travelEndsOn) {
120
+ pushIssue(resultTarget, buildIssue("travel_dates_required", traveler, rule, "Travel end date is required to evaluate document validity after return."));
121
+ }
122
+ else if (documents.length === 0) {
123
+ // Missing documents are already reported through document_required.
124
+ }
125
+ else {
126
+ const requiredValidUntil = addDays(target.travelEndsOn, rule.minValidityDaysAfterReturn);
127
+ const documentsWithExpiry = documents.filter((document) => typeof document.expiresOn === "string");
128
+ const hasValidDocument = documentsWithExpiry.some((document) => !isBeforeDate(document.expiresOn, requiredValidUntil));
129
+ if (hasValidDocument) {
130
+ // Any acceptable document can satisfy a passport-or-ID-card validity rule.
131
+ }
132
+ else if (documentsWithExpiry.length > 0) {
133
+ pushIssue(resultTarget, buildIssue("document_validity", traveler, rule, `Traveler ${documentTypeLabel(rule)} must be valid until at least ${requiredValidUntil}.`));
134
+ }
135
+ else {
136
+ pushIssue(resultTarget, buildIssue("document_expiry_required", traveler, rule, `Traveler ${documentTypeLabel(rule)} expiry date is required.`));
137
+ }
138
+ }
139
+ }
140
+ if (rule.visaRequired) {
141
+ const hasVisa = traveler.hasVisa || traveler.documents.some((document) => document.type === "visa");
142
+ if (!hasVisa) {
143
+ pushIssue(resultTarget, buildIssue("visa_required", traveler, rule, "Traveler needs a visa for this destination."));
144
+ }
145
+ }
146
+ if (rule.minorConsentRequired) {
147
+ const age = traveler.dateOfBirth && target.travelStartsOn
148
+ ? getAgeOn(traveler.dateOfBirth, target.travelStartsOn)
149
+ : null;
150
+ const isMinor = age == null ? true : age < 18;
151
+ const hasConsent = traveler.hasMinorConsent ||
152
+ traveler.travelingWithGuardian ||
153
+ traveler.documents.some((document) => document.type === "minor_consent");
154
+ if (isMinor && !hasConsent) {
155
+ pushIssue(resultTarget, buildIssue("minor_consent_required", traveler, rule, "Minor traveler needs guardian travel consent for this departure."));
156
+ }
157
+ }
158
+ return { blockingIssues, warnings };
159
+ }
160
+ export function evaluateStorefrontTransportEligibility(input) {
161
+ const travelStartsOn = input.travelStartsOn ?? null;
162
+ const travelEndsOn = input.travelEndsOn ?? null;
163
+ const rules = input.rules
164
+ .map((rule) => storefrontTransportEligibilityRuleSchema.parse(rule))
165
+ .filter((rule) => appliesToTarget(rule, input));
166
+ const travelerResults = input.travelers.map((traveler) => {
167
+ const blockingIssues = [];
168
+ const warnings = [];
169
+ const matchedRuleIds = [];
170
+ for (const rule of rules) {
171
+ const result = evaluateTravelerRule(traveler, rule, { travelStartsOn, travelEndsOn });
172
+ if (!result)
173
+ continue;
174
+ matchedRuleIds.push(rule.id);
175
+ blockingIssues.push(...result.blockingIssues);
176
+ warnings.push(...result.warnings);
177
+ }
178
+ return {
179
+ travelerRef: traveler.travelerRef,
180
+ eligible: blockingIssues.length === 0,
181
+ matchedRuleIds,
182
+ blockingIssues,
183
+ warnings,
184
+ };
185
+ });
186
+ const blockingIssues = travelerResults.flatMap((traveler) => traveler.blockingIssues);
187
+ const warnings = travelerResults.flatMap((traveler) => traveler.warnings);
188
+ return {
189
+ departureId: input.departureId,
190
+ productId: input.productId ?? null,
191
+ travelStartsOn,
192
+ travelEndsOn,
193
+ eligible: blockingIssues.length === 0,
194
+ blockingIssues,
195
+ warnings,
196
+ travelers: travelerResults,
197
+ };
198
+ }