ti2-ventrata 1.0.42 → 1.0.47

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/index.js CHANGED
@@ -96,6 +96,10 @@ class Plugin {
96
96
  this.errorPathsAxiosAny = () => ([]); // 200's that should be errors
97
97
  }
98
98
 
99
+ static settlementMethodRequiresResellerReference(settlementMethod) {
100
+ return ['DIRECT', 'VOUCHER'].includes(settlementMethod);
101
+ }
102
+
99
103
  static getSettlementMethod(reference, availableSettlementMethods = []) {
100
104
  // Determine settlement method from product's available methods
101
105
  let settlementMethod = 'DEFERRED'; // default fallback
@@ -544,6 +548,11 @@ class Plugin {
544
548
  settlementMethods,
545
549
  );
546
550
 
551
+ assert(
552
+ !Plugin.settlementMethodRequiresResellerReference(settlementMethod) || !isNilOrEmpty(reference),
553
+ `Agent reference is required for ${settlementMethod} booking.`,
554
+ );
555
+
547
556
  let booking;
548
557
  try {
549
558
  booking = R.path(['data'], await axios({
@@ -677,7 +686,7 @@ class Plugin {
677
686
  },
678
687
  payload: {
679
688
  bookingId,
680
- resellerReference,
689
+ reference,
681
690
  supplierBookingId,
682
691
  travelDateStart,
683
692
  travelDateEnd,
@@ -690,7 +699,7 @@ class Plugin {
690
699
  }) {
691
700
  assert(
692
701
  !isNilOrEmpty(bookingId)
693
- || !isNilOrEmpty(resellerReference)
702
+ || !isNilOrEmpty(reference)
694
703
  || !isNilOrEmpty(supplierBookingId)
695
704
  || !(
696
705
  isNilOrEmpty(travelDateStart) && isNilOrEmpty(travelDateEnd) && isNilOrEmpty(dateFormat)
@@ -724,8 +733,8 @@ class Plugin {
724
733
  searchByUrl(`${endpoint || this.endpoint}/bookings?supplierReference=${bookingId}`),
725
734
  ]);
726
735
  }
727
- if (!isNilOrEmpty(resellerReference)) {
728
- url = `${endpoint || this.endpoint}/bookings?resellerReference=${resellerReference}`;
736
+ if (!isNilOrEmpty(reference)) {
737
+ url = `${endpoint || this.endpoint}/bookings?resellerReference=${reference}`;
729
738
  return R.path(['data'], await axios({
730
739
  method: 'get',
731
740
  url,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-ventrata",
3
- "version": "1.0.42",
3
+ "version": "1.0.47",
4
4
  "description": "Ventrata's TI2 Plugin",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -46,6 +46,6 @@
46
46
  "jest-diff": "^27.4.2",
47
47
  "jest-environment-node": "27.4",
48
48
  "jest-util": "27.4",
49
- "ti2": "latest"
49
+ "ti2": "file:../ti2"
50
50
  }
51
51
  }
@@ -180,6 +180,139 @@ describe('ventrata mappings', () => {
180
180
  );
181
181
  });
182
182
 
183
+ const bookingResponse = {
184
+ id: 'booking-id',
185
+ uuid: 'booking-uuid',
186
+ orderId: 'order-id',
187
+ orderReference: 'order-ref',
188
+ supplierReference: 'supplier-ref',
189
+ status: 'CONFIRMED',
190
+ product: { id: 'product-1', internalName: 'Product' },
191
+ option: { id: 'DEFAULT', internalName: 'DEFAULT' },
192
+ availability: { localDateTimeStart: '2026-05-02T08:00:00', localDateTimeEnd: '2026-05-02T10:00:00' },
193
+ contact: { fullName: 'Test User' },
194
+ cancellable: true,
195
+ utcCreatedAt: '2026-05-01T00:00:00Z',
196
+ pricing: { retail: 1000 },
197
+ unitItems: [{ uuid: 'unit-item-1', unitId: 'adult-unit', internalName: 'Adult' }],
198
+ utcConfirmedAt: '2026-05-01T00:00:01Z',
199
+ };
200
+
201
+ it.each(['VOUCHER', 'DIRECT'])(
202
+ 'requires agent reference when the selected settlement method is %s',
203
+ async settlementMethod => {
204
+ const plugin = new Plugin({
205
+ jwtKey: 'test-jwt-key',
206
+ endpoint: 'https://example.test/octo',
207
+ });
208
+
209
+ jest.spyOn(jwt, 'verify').mockReturnValue({
210
+ productId: 'product-1',
211
+ optionId: 'DEFAULT',
212
+ availabilityId: 'avail-1',
213
+ unitItems: [{ unitId: 'adult-unit' }],
214
+ settlementMethods: [settlementMethod],
215
+ });
216
+
217
+ const axios = jest.fn().mockResolvedValue({ data: bookingResponse });
218
+
219
+ await expect(plugin.createBooking({
220
+ axios,
221
+ token: { apiKey: 'api-key' },
222
+ payload: {
223
+ availabilityKey: 'signed-key',
224
+ holder: { name: 'Test', surname: 'User', country: 'US' },
225
+ },
226
+ typeDefsAndQueries: {
227
+ bookingTypeDefs: ti2BookingTypeDefs,
228
+ bookingQuery: ti2BookingQuery,
229
+ },
230
+ })).rejects.toThrow(`Agent reference is required for ${settlementMethod} booking.`);
231
+
232
+ expect(axios).not.toHaveBeenCalled();
233
+ },
234
+ );
235
+
236
+ it.each(['VOUCHER', 'DIRECT'])(
237
+ 'allows %s settlement when agent reference is provided',
238
+ async settlementMethod => {
239
+ const plugin = new Plugin({
240
+ jwtKey: 'test-jwt-key',
241
+ endpoint: 'https://example.test/octo',
242
+ });
243
+
244
+ jest.spyOn(jwt, 'verify').mockReturnValue({
245
+ productId: 'product-1',
246
+ optionId: 'DEFAULT',
247
+ availabilityId: 'avail-1',
248
+ unitItems: [{ unitId: 'adult-unit' }],
249
+ settlementMethods: [settlementMethod, 'DEFERRED'],
250
+ });
251
+
252
+ const axios = jest.fn().mockResolvedValue({ data: bookingResponse });
253
+
254
+ await plugin.createBooking({
255
+ axios,
256
+ token: { apiKey: 'api-key' },
257
+ payload: {
258
+ availabilityKey: 'signed-key',
259
+ holder: { name: 'Test', surname: 'User', country: 'US' },
260
+ reference: 'AGENT-REF-123',
261
+ },
262
+ typeDefsAndQueries: {
263
+ bookingTypeDefs: ti2BookingTypeDefs,
264
+ bookingQuery: ti2BookingQuery,
265
+ },
266
+ });
267
+
268
+ const createBookingCall = axios.mock.calls.find(call => call[0].url === 'https://example.test/octo/bookings');
269
+ expect(createBookingCall).toBeTruthy();
270
+ expect(createBookingCall[0].data).toEqual(
271
+ expect.objectContaining({
272
+ settlementMethod,
273
+ }),
274
+ );
275
+ },
276
+ );
277
+
278
+ it('falls back to DEFERRED without agent reference when DEFERRED is available', async () => {
279
+ const plugin = new Plugin({
280
+ jwtKey: 'test-jwt-key',
281
+ endpoint: 'https://example.test/octo',
282
+ });
283
+
284
+ jest.spyOn(jwt, 'verify').mockReturnValue({
285
+ productId: 'product-1',
286
+ optionId: 'DEFAULT',
287
+ availabilityId: 'avail-1',
288
+ unitItems: [{ unitId: 'adult-unit' }],
289
+ settlementMethods: ['VOUCHER', 'DIRECT', 'DEFERRED'],
290
+ });
291
+
292
+ const axios = jest.fn().mockResolvedValue({ data: bookingResponse });
293
+
294
+ await plugin.createBooking({
295
+ axios,
296
+ token: { apiKey: 'api-key' },
297
+ payload: {
298
+ availabilityKey: 'signed-key',
299
+ holder: { name: 'Test', surname: 'User', country: 'US' },
300
+ },
301
+ typeDefsAndQueries: {
302
+ bookingTypeDefs: ti2BookingTypeDefs,
303
+ bookingQuery: ti2BookingQuery,
304
+ },
305
+ });
306
+
307
+ const createBookingCall = axios.mock.calls.find(call => call[0].url === 'https://example.test/octo/bookings');
308
+ expect(createBookingCall).toBeTruthy();
309
+ expect(createBookingCall[0].data).toEqual(
310
+ expect.objectContaining({
311
+ settlementMethod: 'DEFERRED',
312
+ }),
313
+ );
314
+ });
315
+
183
316
  it('maps participant-scoped custom fields from standard ti2 payload into unitItems questionAnswers', async () => {
184
317
  const plugin = new Plugin({
185
318
  jwtKey: 'test-jwt-key',