@revolugo/common 7.4.3 → 7.5.0-rc.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revolugo/common",
3
- "version": "7.4.3",
3
+ "version": "7.5.0-rc.0",
4
4
  "private": false,
5
5
  "description": "Revolugo common",
6
6
  "author": "Revolugo",
@@ -23,12 +23,12 @@
23
23
  "./utils": "./src/utils/index.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@asteasolutions/zod-to-openapi": "8.4.3",
26
+ "@asteasolutions/zod-to-openapi": "8.5.0",
27
27
  "change-case": "5.4.4",
28
28
  "dayjs": "1.11.20",
29
29
  "ky": "1.14.3",
30
30
  "slugify": "1.6.8",
31
- "type-fest": "5.4.4",
31
+ "type-fest": "5.5.0",
32
32
  "uuid": "13.0.0",
33
33
  "zod": "4.3.6"
34
34
  },
@@ -9,6 +9,7 @@ import {
9
9
  CHECK_IN_DATE_SCHEMA,
10
10
  CHECK_OUT_DATE_SCHEMA,
11
11
  CHILDREN_SCHEMA,
12
+ IS_CANCELLATION_POLICIES_UPDATED,
12
13
  IS_PRICE_INCREASED,
13
14
  } from './global.ts'
14
15
  import { HOTEL_ROOM_OFFER_SCHEMA } from './hotel-room-offer.ts'
@@ -39,6 +40,7 @@ export const BOOKING_POLICY_SCHEMA = z
39
40
  id: z.string().openapi({
40
41
  description: '**Booking Policy** id',
41
42
  }),
43
+ is_cancellation_policies_updated: IS_CANCELLATION_POLICIES_UPDATED,
42
44
  is_price_increased: IS_PRICE_INCREASED,
43
45
  })
44
46
  .openapi('bookingPoliciesApi', {
@@ -0,0 +1,172 @@
1
+ /* eslint-disable camelcase */
2
+ import { z } from 'zod'
3
+
4
+ import { PayLaterStatusEnum } from '../types/booking.ts'
5
+ import { BookingStatusEnum } from '../types/index.ts'
6
+
7
+ import { CANCELLATION_POLICY_SCHEMA } from './cancellation-policies.ts'
8
+ import { CONTACT_PERSON_SCHEMA } from './contact-person.ts'
9
+ import { CURRENCY_SCHEMA } from './currency.ts'
10
+ import { EVENT_SCHEMA } from './event.ts'
11
+ import {
12
+ ADULT_COUNT_SCHEMA,
13
+ CHECK_IN_DATE_SCHEMA,
14
+ CHECK_OUT_DATE_SCHEMA,
15
+ CHILDREN_SCHEMA,
16
+ SOURCE_MARKET_SCHEMA,
17
+ } from './global.ts'
18
+ import { HOTEL_ROOM_OFFER_SCHEMA } from './hotel-room-offer.ts'
19
+ import { HOTEL_ROOMING_LISTS_SCHEMA } from './hotel-rooming-list.ts'
20
+ import { INVOICES_SCHEMA } from './invoice.ts'
21
+ import { PAYMENT_METHODS_RESPONSE_SCHEMA } from './payment-methods.ts'
22
+ import { TAXES_SCHEMA } from './taxes.ts'
23
+
24
+ export const FILTER_BOOKING_STATUS_SCHEMA = z
25
+ .string()
26
+ .regex(
27
+ /^(?:bkg-af|bkg-cx|bkg-ip|bkg-cf)(,?(?:bkg-af|bkg-cx|bkg-ip|bkg-cf))*/u,
28
+ )
29
+ .transform(values => values.split(','))
30
+ .optional()
31
+ .nullish()
32
+ .openapi({
33
+ description:
34
+ 'Comma separated list of booking status values to filter by. Possible values are: `bkg-af` (Failure), `bkg-cx` (Canceled), `bkg-ip` (In Progress), `bkg-cf` (Confirmed). Example: `bkg-af,bkg-cx`',
35
+ })
36
+
37
+ export const BOOKING_METADATA_SCHEMA = z
38
+ .record(z.string(), z.string())
39
+ .openapi('bookingMetadataApi', {
40
+ description:
41
+ "You can use this parameter to attach key-value data to bookings. Metadata is useful for storing additional, structured information on a booking. As an example, you could store your user's full name and corresponding unique identifier from your system on a booking. Metadata is not used internally by the Booking Engine and won't be seen by your users unless you choose to show it to them.",
42
+ })
43
+ .optional()
44
+ .nullish()
45
+
46
+ export const BOOKING_SCHEMA = z
47
+ .object({
48
+ additionalCheckInInstruction: z
49
+ .string()
50
+ .openapi({
51
+ description: 'Additional instructions on how to check-in.',
52
+ })
53
+ .nullish(),
54
+ additionalPolicies: z
55
+ .string()
56
+ .openapi({
57
+ description:
58
+ 'Additional house policy, house manual, or condominium rules.',
59
+ })
60
+ .nullish(),
61
+ adultCount: ADULT_COUNT_SCHEMA,
62
+ canceledAt: z
63
+ .string()
64
+ .openapi({
65
+ description: 'Date of booking cancellation request, when applicable.',
66
+ })
67
+ .optional()
68
+ .nullish(),
69
+ cancellationPolicies: z.array(CANCELLATION_POLICY_SCHEMA).openapi({
70
+ description:
71
+ 'The list of cancellation policy date range with their corresponding penalty percentage.',
72
+ }),
73
+ cancellationPolicyRemarks: z
74
+ .string()
75
+ .openapi({ description: 'Remarks about the cancellation policy.' })
76
+ .optional()
77
+ .nullish(),
78
+ checkInDate: CHECK_IN_DATE_SCHEMA,
79
+ checkOutDate: CHECK_OUT_DATE_SCHEMA,
80
+ children: CHILDREN_SCHEMA.optional().nullish(),
81
+ confirmedAt: z
82
+ .string()
83
+ .openapi({
84
+ description: 'Date of booking confirmation request, when applicable.',
85
+ })
86
+ .optional()
87
+ .nullish(),
88
+ contactPerson: CONTACT_PERSON_SCHEMA,
89
+ createdAt: z
90
+ .string()
91
+ .openapi({ description: 'Creation date of the **Booking**.' }),
92
+ currency: CURRENCY_SCHEMA,
93
+ customerReference: z
94
+ .string()
95
+ .openapi({
96
+ description:
97
+ 'Customer Reference of the requested Booking (sometimes to present by the customer at hotel check-in).',
98
+ })
99
+ .optional()
100
+ .nullish(),
101
+ event: EVENT_SCHEMA,
102
+ failedAt: z
103
+ .string()
104
+ .openapi({ description: 'Date of booking failure, when applicable.' })
105
+ .optional()
106
+ .nullish(),
107
+ hotelConfirmationId: z
108
+ .string()
109
+ .openapi({
110
+ description:
111
+ 'Code your guest might need to show to the hotel during check-in. This number will only but available a few days prior to check-in and it’s subject to availability.',
112
+ })
113
+ .nullish(),
114
+ hotelId: z
115
+ .string()
116
+ .openapi({ description: 'Hotel Id of the requested Booking.' }),
117
+ hotelRoomingLists: HOTEL_ROOMING_LISTS_SCHEMA.optional(),
118
+ hotelRoomOffer: HOTEL_ROOM_OFFER_SCHEMA,
119
+ id: z.string().openapi({ description: 'Booking Id' }).nullish(),
120
+ invoices: INVOICES_SCHEMA.optional().nullish(),
121
+ lastStatusUpdatedAt: z
122
+ .string()
123
+ .openapi({ description: 'Date of the last booking status update.' }),
124
+ metadata: BOOKING_METADATA_SCHEMA,
125
+ payLater: z
126
+ .enum(PayLaterStatusEnum)
127
+ .optional()
128
+ .nullish()
129
+ .openapi({
130
+ description: 'Booking Pay Later Status',
131
+ enum: Object.values(PayLaterStatusEnum),
132
+ }),
133
+ paymentMethods: PAYMENT_METHODS_RESPONSE_SCHEMA,
134
+ penaltyPercentage: z
135
+ .number()
136
+ .optional()
137
+ .nullish()
138
+ .openapi({
139
+ description: `Penalty percentage of the requested Booking.\n\n When **booking.status = ${BookingStatusEnum.Cx}** this is the percentage of the **booking.tax_included_price** that have been charged on the canceled booking. Otherwise, this field won't be returned.`,
140
+ }),
141
+ reference: z.string().openapi({
142
+ description: 'Booking Reference of the requested Booking.',
143
+ }),
144
+ sourceMarket: SOURCE_MARKET_SCHEMA,
145
+ status: z.enum(BookingStatusEnum).openapi({
146
+ description:
147
+ 'Booking status. Please, refer to [Booking Status](/v1/documentation#tag/Booking-Status) for details.',
148
+ }),
149
+ taxAmount: z.number().openapi({
150
+ description: 'Total tax amount expressed in the booking currency.',
151
+ }),
152
+ taxes: TAXES_SCHEMA.optional().nullish(),
153
+ taxIncludedPrice: z.number().openapi({
154
+ description:
155
+ 'Price of the booking including taxes expressed in the booking currency.',
156
+ }),
157
+ terms: z.string().openapi({
158
+ description:
159
+ 'Link to Revolugo terms and conditions under which the booking is made.',
160
+ }),
161
+ token: z.string().optional().nullish().openapi({
162
+ description:
163
+ 'This is the token to pass as URL params to [Cancel Booking endpoint](/v1/documentation#operation/deleteV1BookingsId) in order to perform a cancel request on the booking. If you want to cancel a booking, you should use this token instead of the booking id.\n\n⚠️ For security reasons, this token is unique and once generated, it is valid during a period of 10 minutes.\n\nIn order to get a fresh valid token you need to call **[Retrieve Booking endpoint](/v1/documentation#operation/getV1BookingsId)**.',
164
+ }),
165
+ })
166
+ .strict()
167
+ .openapi('bookingApi', { description: 'Booking record' })
168
+
169
+ export const BOOKINGS_SCHEMA = z
170
+ .array(BOOKING_SCHEMA)
171
+ .openapi('bookingsApi', { description: 'List of detailed Bookings' })
172
+ /* eslint-enable camelcase */
@@ -0,0 +1,97 @@
1
+ /* eslint-disable camelcase */
2
+ import { z } from 'zod'
3
+
4
+ import { CountryIso2Code } from '../countries/constants.ts'
5
+ import { ContactPersonSalutationEnum } from '../types/index.ts'
6
+
7
+ import { COUNTRY_ISO2_CODE_SCHEMA, LANG_SCHEMA } from './global.ts'
8
+
9
+ export const CONTACT_PERSON_ORGANIZATION_SCHEMA = z
10
+ .object({
11
+ address: z.string().openapi({ description: 'Address of the organization' }),
12
+ city: z.string().openapi({ description: 'City of the organization' }),
13
+ country: COUNTRY_ISO2_CODE_SCHEMA.openapi({
14
+ description: 'Country of the organization',
15
+ }),
16
+ name: z.string().openapi({ description: 'Name of the organization' }),
17
+ state: z
18
+ .string()
19
+ .openapi({ description: 'State of the organization' })
20
+ .optional()
21
+ .nullish(),
22
+ vatNumber: z
23
+ .string()
24
+ .openapi({ description: 'VAT Number of the organization' })
25
+ .optional()
26
+ .nullish(),
27
+ zipCode: z
28
+ .string()
29
+ .openapi({ description: 'ZIP Code of the organization' }),
30
+ })
31
+ .openapi('contactPersonOrganizationApi')
32
+
33
+ export const CONTACT_PERSON_SCHEMA = z
34
+ .object({
35
+ address: z
36
+ .string()
37
+ .openapi({ description: "Contact person's postal code of residence" })
38
+ .optional()
39
+ .nullish(),
40
+ city: z
41
+ .string()
42
+ .openapi({ description: "Contact person's city of residence" })
43
+ .optional()
44
+ .nullish(),
45
+ country: COUNTRY_ISO2_CODE_SCHEMA.openapi({
46
+ description: "Contact person's country of residence",
47
+ enum: Object.values(CountryIso2Code),
48
+ })
49
+ .optional()
50
+ .nullish(),
51
+ email: z.email().openapi({ description: "Contact person's email address" }),
52
+ firstName: z
53
+ .string()
54
+ .openapi({ description: "Contact person's first name" }),
55
+ lang: LANG_SCHEMA.openapi({
56
+ description:
57
+ 'Set the prefered language to use when contacting the contact person for booking related communication(s).',
58
+ }).optional(),
59
+ lastName: z.string().openapi({ description: "Contact person's last name" }),
60
+ nationality: COUNTRY_ISO2_CODE_SCHEMA.openapi({
61
+ description: "Contact person's nationality",
62
+ }),
63
+ organization: CONTACT_PERSON_ORGANIZATION_SCHEMA.openapi({
64
+ description:
65
+ 'Organization details of the contact person for invoicing purposes',
66
+ })
67
+ .optional()
68
+ .nullish(),
69
+ phone: z.string().openapi({ description: "Contact person's phone number" }),
70
+ remarks: z
71
+ .string()
72
+ .openapi({ description: "Contact person's remarks" })
73
+ .optional(),
74
+ salutation: z
75
+ .enum(ContactPersonSalutationEnum)
76
+ .optional()
77
+ .nullish()
78
+ .openapi({
79
+ description: 'Title of the contact person',
80
+ enum: Object.values(ContactPersonSalutationEnum),
81
+ }),
82
+ state: z
83
+ .string()
84
+ .openapi({ description: "Contact person's state of residence" })
85
+ .optional()
86
+ .nullish(),
87
+ zipCode: z
88
+ .string()
89
+ .openapi({ description: "Contact person's postal code of residence" })
90
+ .optional()
91
+ .nullish(),
92
+ })
93
+ .openapi('contactPersonApi', {
94
+ description:
95
+ 'Contact details that will be used to communicate with the contact person for booking related communication (confirmation, modification, etc)',
96
+ })
97
+ /* eslint-enable camelcase */
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+
3
+ export const EVENT_SCHEMA = z
4
+ .object({
5
+ name: z
6
+ .string()
7
+ .nullish()
8
+ .openapi({ description: 'Unique name of the event' }),
9
+ slug: z
10
+ .string()
11
+ .nullish()
12
+ .openapi({ description: 'Unique slug of the event' }),
13
+ })
14
+ .nullish()
15
+ .openapi('eventApi')
@@ -70,6 +70,11 @@ export const IMAGES_SCHEMA = z
70
70
  '🛑 DEPRECATED - Hotel images details.\n\nIn order to retrieve a specific image you need to construct the complete URL from the images parameters: **[images.prefix][highres|lowres|thumb]/[index]/[images.suffix]**. If **images.count = n**, then index is in [0...n-1] range.\n\ne.g.: https://s3.eu-west-3.amazonaws.com/revolugo/hotels/yhKY/images/highres/0.jpg',
71
71
  })
72
72
 
73
+ export const IS_CANCELLATION_POLICIES_UPDATED = z.boolean().openapi({
74
+ description:
75
+ 'Indicates whether the cancellation policies of the **Hotel Room Offer** have changed (become stricter) compared to those returned by [Retrieve Hotel Room Offers endpoint](/v1/documentation#operation/getV1Hotel_room_offers).\n\nIf **is_cancellation_policies_updated** is **true**, the cancellation policies are now less favourable than when the offer was first retrieved. ⚠️ It is strongly advised to clearly inform your customer of any cancellation policy change that may occur.',
76
+ })
77
+
73
78
  export const IS_PRICE_INCREASED = z.boolean().openapi({
74
79
  description:
75
80
  'Indicates whether the price of the **Hotel Room Offer** (without breakfast included) has increased compared to the price returned by [Retrieve Hotel Room Offers endpoint](/v1/documentation#operation/getV1Hotel_room_offers).\n\nIn some case, the returned price may increase for various reasons including: Currency rate change between the POST **Booking Policies** call and the GET **Hotel Room Offers** call, **Hotel Room Offer** price has increased since the GET **Hotel Room Offers** call.\n\nIf **is_price_increased** is **true**, it means that the actual/updated price of the **Hotel Room Offer** is greater than the price previously returned by [Retrieve Hotel Room Offers endpoint](/v1/documentation#operation/getV1Hotel_room_offers). If **is_price_increased** is **false**, the price of the **Hotel Room Offer** is equal to the price returned by [Retrieve Hotel Room Offers endpoint](/v1/documentation#operation/getV1Hotel_room_offers).\n\n⚠️ It is strongly advised to clearly inform your customer of any price increase that may occur.',
@@ -0,0 +1,34 @@
1
+ /* eslint-disable camelcase */
2
+ import { z } from 'zod'
3
+
4
+ import { HOTEL_ROOM_SCHEMA } from './hotel-room.ts'
5
+
6
+ export const HOTEL_ROOMING_LIST_GUEST_SCHEMA = z
7
+ .object({
8
+ fullname: z.string(),
9
+ id: z.string(),
10
+ })
11
+ .openapi({ description: 'Guest of a hotel rooming list' })
12
+ .openapi('hotelRoomingListGuestApi')
13
+
14
+ export const HOTEL_ROOMING_LIST_GUESTS_SCHEMA = z
15
+ .array(HOTEL_ROOMING_LIST_GUEST_SCHEMA)
16
+ .openapi({ description: 'Guests of a hotel rooming list' })
17
+ .openapi('hotelRoomingListGuestsApi')
18
+
19
+ export const HOTEL_ROOMING_LIST_SCHEMA = z
20
+ .object({
21
+ hotelRoom: HOTEL_ROOM_SCHEMA.optional().nullish(),
22
+ hotelRoomId: z.string(),
23
+ hotelRoomingListGuests: HOTEL_ROOMING_LIST_GUESTS_SCHEMA,
24
+ id: z.string(),
25
+ limitDate: z.coerce.date().optional(),
26
+ })
27
+ .openapi({ description: 'Hotel rooming list of a hotel booking' })
28
+ .openapi('hotelRoomingListApi')
29
+
30
+ export const HOTEL_ROOMING_LISTS_SCHEMA = z
31
+ .array(HOTEL_ROOMING_LIST_SCHEMA)
32
+ .openapi({ description: 'Hotel rooming lists of a hotel booking' })
33
+ .openapi('hotelRoomingListsApi')
34
+ /* eslint-enable camelcase */
@@ -1,6 +1,8 @@
1
1
  export * from './booking-policy.ts'
2
+ export * from './booking.ts'
2
3
  export * from './breakfast.ts'
3
4
  export * from './cancellation-policies.ts'
5
+ export * from './contact-person.ts'
4
6
  export * from './currency.ts'
5
7
  export * from './global.ts'
6
8
  export * from './hotel-offer.ts'
@@ -8,5 +10,6 @@ export * from './hotel-room-offer.ts'
8
10
  export * from './hotel-room.ts'
9
11
  export * from './hotel.ts'
10
12
  export * from './list-polling-meta.ts'
13
+ export * from './payment-methods.ts'
11
14
  export * from './tag.ts'
12
15
  export * from './taxes.ts'
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod'
2
+
3
+ import { InvoiceApiTypeEnum } from '../types/index.ts'
4
+
5
+ export const INVOICE_SCHEMA = z
6
+ .object({
7
+ type: z.enum(InvoiceApiTypeEnum),
8
+ url: z.string(),
9
+ })
10
+ .openapi('invoiceApi')
11
+
12
+ export const INVOICES_SCHEMA = z
13
+ .array(INVOICE_SCHEMA)
14
+ .openapi({
15
+ description:
16
+ 'The list of invoices and credit notes (when applicable) direct urls associated to the Booking.',
17
+ })
18
+ .openapi('invoicesApi')
@@ -0,0 +1,135 @@
1
+ /* eslint-disable camelcase */
2
+ import { z } from 'zod'
3
+
4
+ import {
5
+ BookingStatusEnum,
6
+ PayLaterStatusEnum,
7
+ PaymentMethodNameEnum,
8
+ } from '../types/index.ts'
9
+ import { isEqual } from '../utils/is-equal.ts'
10
+
11
+ const allowedPaymentMethodCombinations = [
12
+ [PaymentMethodNameEnum.CreditCard],
13
+ [PaymentMethodNameEnum.DepositAccount],
14
+ [PaymentMethodNameEnum.CreditCard, PaymentMethodNameEnum.Coupon],
15
+ [PaymentMethodNameEnum.Coupon, PaymentMethodNameEnum.CreditCard],
16
+ ]
17
+
18
+ export const PAYMENT_METHOD_REQUEST_SCHEMA = z
19
+ .object({
20
+ name: z.enum([
21
+ PaymentMethodNameEnum.Coupon,
22
+ PaymentMethodNameEnum.CreditCard,
23
+ PaymentMethodNameEnum.DepositAccount,
24
+ ]),
25
+ payload: z
26
+ .object({
27
+ // TODO: Add coupon validation with name
28
+ coupon_id: z.string().nullish(),
29
+ })
30
+ .optional()
31
+ .openapi('paymentMethodRequestPayloadApi'),
32
+ })
33
+ .openapi('paymentMethodRequestApi')
34
+
35
+ export const PAYMENT_METHODS_REQUEST_SCHEMA = z
36
+ .array(PAYMENT_METHOD_REQUEST_SCHEMA)
37
+ .transform((value, ctx) => {
38
+ if (
39
+ !allowedPaymentMethodCombinations.some(allowedPaymentMethodCombination =>
40
+ isEqual(
41
+ allowedPaymentMethodCombination,
42
+ value.map(
43
+ (paymentMethod: { name: PaymentMethodNameEnum }) =>
44
+ paymentMethod.name,
45
+ ),
46
+ ),
47
+ )
48
+ ) {
49
+ ctx.addIssue({
50
+ code: 'custom',
51
+ message: 'Invalid payment method combination',
52
+ path: [],
53
+ })
54
+ return z.never()
55
+ }
56
+
57
+ return value
58
+ })
59
+ .openapi('paymentMethodsRequestApi', {
60
+ description:
61
+ 'List of preferred payment methods to be used along with their respective payload (when applicable) in order to fulfill the booking.',
62
+ })
63
+
64
+ export const PAYMENT_METHOD_RESPONSE_SCHEMA = z
65
+ .discriminatedUnion('name', [
66
+ z.object({
67
+ name: z.literal(PaymentMethodNameEnum.CreditCard),
68
+ payload: z.object({
69
+ amount: z.number(),
70
+ couponId: z.string().nullish(),
71
+ token: z.string(),
72
+ }),
73
+ }),
74
+ z.object({
75
+ name: z.literal(PaymentMethodNameEnum.Coupon),
76
+ payload: z.object({
77
+ amount: z.number(),
78
+ couponId: z.string(),
79
+ token: z.string().nullish(),
80
+ }),
81
+ }),
82
+ z.object({
83
+ name: z.enum([PaymentMethodNameEnum.DepositAccount]),
84
+ payload: z.object({
85
+ amount: z.number(),
86
+ couponId: z.string().nullish(),
87
+ token: z.string().nullish(),
88
+ }),
89
+ }),
90
+ z.object({
91
+ name: z.literal(PaymentMethodNameEnum.PayLater),
92
+ payload: z.object({
93
+ amount: z.number(),
94
+ couponId: z.string().nullish(),
95
+ status: z.enum(PayLaterStatusEnum),
96
+ token: z.string().nullish(),
97
+ }),
98
+ }),
99
+ ])
100
+ .openapi('paymentMethodApi')
101
+
102
+ export const PAYMENT_METHODS_RESPONSE_SCHEMA = z
103
+ .array(PAYMENT_METHOD_RESPONSE_SCHEMA)
104
+ .openapi('paymentMethodsApi', {
105
+ description: `List of preferred payment methods to be used along with their respective payload (when applicable) in order to fulfill the booking.\n\n⚠️ This field is only returned when **booking.status = ${BookingStatusEnum.Created}**`,
106
+ })
107
+ .optional()
108
+
109
+ export const ALLOWED_PAYMENT_METHOD_RESPONSE_SCHEMA = z
110
+ .discriminatedUnion('name', [
111
+ z.object({
112
+ name: z.literal(PaymentMethodNameEnum.CreditCard),
113
+ }),
114
+ z.object({
115
+ name: z.literal(PaymentMethodNameEnum.Coupon),
116
+ }),
117
+ z.object({
118
+ name: z.enum([PaymentMethodNameEnum.DepositAccount]),
119
+ }),
120
+ z.object({
121
+ name: z.literal(PaymentMethodNameEnum.PayLater),
122
+ payload: z.object({
123
+ status: z.enum(PayLaterStatusEnum),
124
+ }),
125
+ }),
126
+ ])
127
+ .openapi('allowedPaymentMethodApi')
128
+
129
+ export const ALLOWED_PAYMENT_METHODS_RESPONSE_SCHEMA = z
130
+ .array(ALLOWED_PAYMENT_METHOD_RESPONSE_SCHEMA)
131
+ .openapi('allowedPaymentMethodsApi', {
132
+ description:
133
+ 'List of allowed payment methods to be used in order to fulfill the booking.',
134
+ })
135
+ /* eslint-enable camelcase */
@@ -78,6 +78,7 @@ export interface BookingPolicies {
78
78
  * @type {boolean}
79
79
  * @memberof BookingPolicies
80
80
  */
81
+ isCancellationPoliciesUpdated: boolean
81
82
  isPriceIncreased: boolean
82
83
  }
83
84
 
@@ -166,5 +166,4 @@ export interface HotelOfferRequestCreate {
166
166
  latitude?: HotelOfferRequest['latitude']
167
167
  longitude?: HotelOfferRequest['longitude']
168
168
  roomCount: HotelOfferRequest['roomCount']
169
- sourceMarket: HotelOfferRequest['sourceMarket']
170
169
  }
@@ -135,7 +135,6 @@ export interface HotelRoomOfferRequestCreate {
135
135
  eventMetadata?: HotelRoomOfferRequest['eventMetadata']
136
136
  hotelId: HotelRoomOfferRequest['hotelId']
137
137
  roomCount: HotelRoomOfferRequest['roomCount']
138
- sourceMarket: HotelRoomOfferRequest['sourceMarket']
139
138
  }
140
139
  export interface HotelRoomOfferRequestsCreatePayload {
141
140
  hotelRoomOfferRequestCreateApi?: HotelRoomOfferRequestCreate
@@ -13,12 +13,12 @@ import timezone from 'dayjs/plugin/timezone.js'
13
13
  import utc from 'dayjs/plugin/utc.js'
14
14
  /* eslint-enable no-restricted-imports, no-restricted-syntax */
15
15
 
16
- /* eslint-disable no-restricted-syntax, import/no-unassigned-import */
16
+ /* eslint-disable no-restricted-syntax, import-x/no-unassigned-import */
17
17
  import 'dayjs/locale/fr.js'
18
18
  import 'dayjs/locale/en.js'
19
- /* eslint-enable no-restricted-syntax, import/no-unassigned-import */
19
+ /* eslint-enable no-restricted-syntax, import-x/no-unassigned-import */
20
20
 
21
- /* eslint-disable import/no-named-as-default-member */
21
+ /* eslint-disable import-x/no-named-as-default-member */
22
22
  dayjs.extend(advancedFormat)
23
23
  dayjs.extend(customParseFormat)
24
24
  dayjs.extend(isBetween)
@@ -30,7 +30,7 @@ dayjs.extend(minMax)
30
30
  dayjs.extend(relativeTime)
31
31
  dayjs.extend(timezone)
32
32
  dayjs.extend(utc)
33
- /* eslint-enable import/no-named-as-default-member */
33
+ /* eslint-enable import-x/no-named-as-default-member */
34
34
 
35
35
  export { dayjs }
36
36
 
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { getUserIpAddress } from './get-user-ip-address.ts'
4
+
5
+ describe('getUserIpAddress', () => {
6
+ test('UT-1a: extracts first IP from x-forwarded-for (comma-separated)', () => {
7
+ const headers = new Headers({
8
+ 'x-forwarded-for': '203.0.113.1, 70.41.3.18, 150.172.238.178',
9
+ })
10
+ expect(getUserIpAddress(headers)).toBe('203.0.113.1')
11
+ })
12
+
13
+ test('UT-1a: extracts single IP from x-forwarded-for', () => {
14
+ const headers = new Headers({ 'x-forwarded-for': '203.0.113.1' })
15
+ expect(getUserIpAddress(headers)).toBe('203.0.113.1')
16
+ })
17
+
18
+ test('UT-1b: extracts IP from cf-connecting-ip', () => {
19
+ const headers = new Headers({ 'cf-connecting-ip': '198.51.100.1' })
20
+ expect(getUserIpAddress(headers)).toBe('198.51.100.1')
21
+ })
22
+
23
+ test('UT-1c: extracts IP from x-real-ip', () => {
24
+ const headers = new Headers({ 'x-real-ip': '198.51.100.2' })
25
+ expect(getUserIpAddress(headers)).toBe('198.51.100.2')
26
+ })
27
+
28
+ test('UT-1c: extracts IP from true-client-ip', () => {
29
+ const headers = new Headers({ 'true-client-ip': '198.51.100.3' })
30
+ expect(getUserIpAddress(headers)).toBe('198.51.100.3')
31
+ })
32
+
33
+ test('UT-1c: extracts IP from x-client-ip', () => {
34
+ const headers = new Headers({ 'x-client-ip': '198.51.100.4' })
35
+ expect(getUserIpAddress(headers)).toBe('198.51.100.4')
36
+ })
37
+
38
+ test('UT-1a: x-forwarded-for takes priority over other headers', () => {
39
+ const headers = new Headers({
40
+ 'cf-connecting-ip': '198.51.100.1',
41
+ 'x-forwarded-for': '203.0.113.1',
42
+ })
43
+ expect(getUserIpAddress(headers)).toBe('203.0.113.1')
44
+ })
45
+
46
+ test('UT-1d: returns null when no IP headers present', () => {
47
+ const headers = new Headers({ 'content-type': 'application/json' })
48
+ expect(getUserIpAddress(headers)).toBeNull()
49
+ })
50
+
51
+ test('UT-1e: extracts IP from Forwarded header (RFC 7239)', () => {
52
+ const headers = new Headers({
53
+ forwarded: 'for=192.0.2.60;proto=http;by=203.0.113.43',
54
+ })
55
+ expect(getUserIpAddress(headers)).toBe('192.0.2.60')
56
+ })
57
+
58
+ test('UT-1e: extracts quoted IP from Forwarded header', () => {
59
+ const headers = new Headers({ forwarded: 'for="192.0.2.60"' })
60
+ expect(getUserIpAddress(headers)).toBe('192.0.2.60')
61
+ })
62
+ })
@@ -0,0 +1,28 @@
1
+ const IP_HEADERS = [
2
+ 'x-forwarded-for',
3
+ 'cf-connecting-ip',
4
+ 'x-real-ip',
5
+ 'true-client-ip',
6
+ 'x-client-ip',
7
+ ] as const
8
+
9
+ export function getUserIpAddress(headers: Headers): string | null {
10
+ for (const header of IP_HEADERS) {
11
+ const value = headers.get(header)
12
+ if (value) {
13
+ return header === 'x-forwarded-for'
14
+ ? (value.split(',')[0]?.trim() ?? null)
15
+ : value
16
+ }
17
+ }
18
+
19
+ const forwarded = headers.get('forwarded')
20
+ if (forwarded) {
21
+ const match = /for=["']?([^;"',\s]+)/u.exec(forwarded)
22
+ if (match?.[1]) {
23
+ return match[1]
24
+ }
25
+ }
26
+
27
+ return null
28
+ }
@@ -26,6 +26,7 @@ export * from './get-random-element-from-array.ts'
26
26
  export * from './get-random-hex-color.ts'
27
27
  export * from './get-random-int.ts'
28
28
  export * from './get-sanitized-room-count.ts'
29
+ export * from './get-user-ip-address.ts'
29
30
  export * from './group-by.ts'
30
31
  export * from './images.ts'
31
32
  export * from './is-empty.ts'
@@ -55,6 +56,7 @@ export * from './sum.ts'
55
56
  export * from './to-boolean.ts'
56
57
  export * from './to-lang.ts'
57
58
  export * from './to-locale.ts'
59
+ export * from './to-number.ts'
58
60
  export * from './transform-schema-keys.ts'
59
61
  export * from './uniq-by.ts'
60
62
  export * from './uniq-with.ts'
@@ -0,0 +1,9 @@
1
+ export const toNumber = (
2
+ value: number | string | undefined,
3
+ ): number | undefined => {
4
+ if (value === undefined || value === '') {
5
+ return undefined
6
+ }
7
+ const n = Number(value)
8
+ return Number.isNaN(n) ? undefined : n
9
+ }