channex 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE +21 -0
- data/README.md +281 -0
- data/Rakefile +12 -0
- data/lib/channex/client.rb +283 -0
- data/lib/channex/configuration.rb +52 -0
- data/lib/channex/errors.rb +62 -0
- data/lib/channex/object.rb +201 -0
- data/lib/channex/objects/booking_revision.rb +124 -0
- data/lib/channex/objects/booking_room.rb +68 -0
- data/lib/channex/objects/company.rb +16 -0
- data/lib/channex/objects/customer.rb +43 -0
- data/lib/channex/objects/guarantee.rb +21 -0
- data/lib/channex/objects/guest.rb +21 -0
- data/lib/channex/objects/message.rb +95 -0
- data/lib/channex/objects/message_thread.rb +76 -0
- data/lib/channex/objects/occupancy.rb +29 -0
- data/lib/channex/objects/review.rb +87 -0
- data/lib/channex/objects/service.rb +44 -0
- data/lib/channex/objects/tax.rb +36 -0
- data/lib/channex/objects.rb +22 -0
- data/lib/channex/resource.rb +130 -0
- data/lib/channex/resources/attachments.rb +36 -0
- data/lib/channex/resources/availability.rb +87 -0
- data/lib/channex/resources/availability_rules.rb +52 -0
- data/lib/channex/resources/booking_revisions.rb +112 -0
- data/lib/channex/resources/bookings.rb +72 -0
- data/lib/channex/resources/channels.rb +96 -0
- data/lib/channex/resources/facilities.rb +29 -0
- data/lib/channex/resources/group_users.rb +76 -0
- data/lib/channex/resources/groups.rb +26 -0
- data/lib/channex/resources/hotel_policies.rb +54 -0
- data/lib/channex/resources/message_threads.rb +89 -0
- data/lib/channex/resources/messages.rb +64 -0
- data/lib/channex/resources/photos.rb +117 -0
- data/lib/channex/resources/properties.rb +42 -0
- data/lib/channex/resources/property_users.rb +32 -0
- data/lib/channex/resources/rate_plans.rb +34 -0
- data/lib/channex/resources/restrictions.rb +98 -0
- data/lib/channex/resources/reviews.rb +41 -0
- data/lib/channex/resources/room_types.rb +34 -0
- data/lib/channex/resources/tax_sets.rb +34 -0
- data/lib/channex/resources/taxes.rb +60 -0
- data/lib/channex/resources/webhooks.rb +87 -0
- data/lib/channex/resources.rb +5 -0
- data/lib/channex/response.rb +102 -0
- data/lib/channex/version.rb +3 -0
- data/lib/channex.rb +52 -0
- data/sig/channex.rbs +4 -0
- metadata +124 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Booking Revisions API resource
|
|
4
|
+
#
|
|
5
|
+
# Booking Revisions are snapshots of bookings at a given time.
|
|
6
|
+
# Each time an OTA provides an update, a new revision is created.
|
|
7
|
+
# The feed endpoint is the primary way to receive new bookings from Channex.
|
|
8
|
+
#
|
|
9
|
+
# IMPORTANT: You must acknowledge bookings after processing them.
|
|
10
|
+
# Not acknowledging bookings will result in them being repeatedly
|
|
11
|
+
# returned for 30 minutes, followed by email warnings.
|
|
12
|
+
#
|
|
13
|
+
# @example Get unacknowledged booking revisions (typed objects)
|
|
14
|
+
# response = client.booking_revisions.feed
|
|
15
|
+
# response.objects.each do |revision|
|
|
16
|
+
# puts revision.customer.full_name
|
|
17
|
+
# revision.rooms.each { |room| puts room.nights }
|
|
18
|
+
# client.booking_revisions.acknowledge(revision.id)
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Hash access still works for backwards compatibility
|
|
22
|
+
# response = client.booking_revisions.feed
|
|
23
|
+
# response.data.each do |revision|
|
|
24
|
+
# puts revision["attributes"]["customer"]["name"]
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @see https://docs.channex.io/api-v.1-documentation/bookings-collection
|
|
28
|
+
#
|
|
29
|
+
class BookingRevisions < Resource
|
|
30
|
+
# Get unacknowledged booking revisions from the feed
|
|
31
|
+
#
|
|
32
|
+
# Returns a response with both raw data and typed objects.
|
|
33
|
+
# Use response.objects for typed access, response.data for raw hashes.
|
|
34
|
+
#
|
|
35
|
+
# @param order [Hash] Ordering options (e.g., { inserted_at: :asc })
|
|
36
|
+
# @return [Response] Response with #objects method for typed access
|
|
37
|
+
def feed(order: {inserted_at: :asc})
|
|
38
|
+
response = client.get("#{resource_path}/feed", params: {order: order})
|
|
39
|
+
add_objects_wrapper(response)
|
|
40
|
+
response
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Acknowledge a booking revision
|
|
44
|
+
#
|
|
45
|
+
# Once acknowledged, the revision will not appear in the feed again.
|
|
46
|
+
#
|
|
47
|
+
# @param id [String] Booking revision UUID
|
|
48
|
+
# @return [Response]
|
|
49
|
+
def acknowledge(id)
|
|
50
|
+
client.post("#{resource_path}/#{id}/ack", body: {})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Acknowledge multiple booking revisions at once
|
|
54
|
+
#
|
|
55
|
+
# @param ids [Array<String>] Array of booking revision UUIDs
|
|
56
|
+
# @return [Array<Response>]
|
|
57
|
+
def acknowledge_all(ids)
|
|
58
|
+
ids.map { |id| acknowledge(id) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Retrieve a single booking revision
|
|
62
|
+
#
|
|
63
|
+
# Returns a response with both raw data and a typed object.
|
|
64
|
+
# Use response.object for typed access, response.data for raw hash.
|
|
65
|
+
#
|
|
66
|
+
# @param id [String] Booking revision UUID
|
|
67
|
+
# @return [Response] Response with #object method for typed access
|
|
68
|
+
def retrieve(id)
|
|
69
|
+
response = super
|
|
70
|
+
add_object_wrapper(response)
|
|
71
|
+
response
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# List booking revisions, filtered by booking_id
|
|
75
|
+
#
|
|
76
|
+
# @return [Response] Response with #objects method for typed access
|
|
77
|
+
def list(**)
|
|
78
|
+
response = super
|
|
79
|
+
add_objects_wrapper(response)
|
|
80
|
+
response
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
protected
|
|
84
|
+
|
|
85
|
+
def resource_path
|
|
86
|
+
"/booking_revisions"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def filter_keys
|
|
90
|
+
%i[booking_id]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Add #object method to response for single revision
|
|
96
|
+
def add_object_wrapper(response)
|
|
97
|
+
response.define_singleton_method(:object) do
|
|
98
|
+
@_object ||= Channex::Objects::BookingRevision.construct_from(data&.dig("attributes"))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Add #objects method to response for multiple revisions
|
|
103
|
+
def add_objects_wrapper(response)
|
|
104
|
+
response.define_singleton_method(:objects) do
|
|
105
|
+
@_objects ||= (data || []).map do |item|
|
|
106
|
+
Channex::Objects::BookingRevision.construct_from(item["attributes"])
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Bookings API resource
|
|
4
|
+
#
|
|
5
|
+
# Bookings represent reservations made through channels connected to Channex.
|
|
6
|
+
# For receiving new bookings, use the BookingRevisions resource with the feed endpoint.
|
|
7
|
+
#
|
|
8
|
+
# @example List bookings
|
|
9
|
+
# client.bookings.list(property_id: "property-uuid")
|
|
10
|
+
#
|
|
11
|
+
# @example Retrieve a booking
|
|
12
|
+
# client.bookings.retrieve("booking-uuid")
|
|
13
|
+
#
|
|
14
|
+
# @example Create a booking (requires Booking CRS App)
|
|
15
|
+
# client.bookings.create(
|
|
16
|
+
# property_id: "property-uuid",
|
|
17
|
+
# rooms: [{
|
|
18
|
+
# room_type_id: "room-type-uuid",
|
|
19
|
+
# rate_plan_id: "rate-plan-uuid",
|
|
20
|
+
# checkin_date: "2024-02-01",
|
|
21
|
+
# checkout_date: "2024-02-05",
|
|
22
|
+
# guests: [{ name: "John Doe" }]
|
|
23
|
+
# }]
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# @see https://docs.channex.io/api-v.1-documentation/bookings-collection
|
|
27
|
+
# @see https://docs.channex.io/api-v.1-documentation/booking-crs-api
|
|
28
|
+
#
|
|
29
|
+
class Bookings < Resource
|
|
30
|
+
# List bookings; date_from and date_to bound the arrival date
|
|
31
|
+
#
|
|
32
|
+
# @return [Response]
|
|
33
|
+
def list(date_from: nil, date_to: nil, filter: {}, **options)
|
|
34
|
+
arrival_date = {gte: date_from, lte: date_to}.compact
|
|
35
|
+
filter = filter.merge(arrival_date:) if arrival_date.any?
|
|
36
|
+
super(filter:, **options)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Revise (modify) an existing booking
|
|
40
|
+
#
|
|
41
|
+
# @param reservation_id [String] The reservation ID (part after "-" in unique_id)
|
|
42
|
+
# @param attributes [Hash] Updated booking attributes with status: "modified"
|
|
43
|
+
# @return [Response]
|
|
44
|
+
def revise(reservation_id, attributes)
|
|
45
|
+
client.post(
|
|
46
|
+
"#{resource_path}/revise",
|
|
47
|
+
body: {
|
|
48
|
+
booking: attributes.merge(reservation_id: reservation_id, status: "modified")
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Cancel a booking, including one that came from an OTA (requires Booking CRS App)
|
|
54
|
+
#
|
|
55
|
+
# @param booking_id [String] The booking UUID
|
|
56
|
+
# @return [Response]
|
|
57
|
+
def cancel(booking_id)
|
|
58
|
+
update(booking_id, status: "cancelled")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
def resource_path
|
|
64
|
+
"/bookings"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def filter_keys
|
|
68
|
+
%i[property_id channel_id status]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Channels API resource
|
|
4
|
+
#
|
|
5
|
+
# Channels represent connections to OTAs (Online Travel Agencies) like
|
|
6
|
+
# Booking.com, Airbnb, Expedia, etc.
|
|
7
|
+
#
|
|
8
|
+
# @example List channels for a property
|
|
9
|
+
# client.channels.list(property_id: "property-uuid")
|
|
10
|
+
#
|
|
11
|
+
# @example Retrieve a channel
|
|
12
|
+
# client.channels.retrieve("channel-uuid")
|
|
13
|
+
#
|
|
14
|
+
# @see https://docs.channex.io/api-v.1-documentation/channel-api
|
|
15
|
+
#
|
|
16
|
+
class Channels < Resource
|
|
17
|
+
# Get list of available channel codes/types
|
|
18
|
+
#
|
|
19
|
+
# @return [Response]
|
|
20
|
+
def codes
|
|
21
|
+
client.get("/channel_codes")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Test OTA credentials before connecting. Returns the OTA's acknowledgement
|
|
25
|
+
# that the hotel_id + channel are reachable. The first call also reveals
|
|
26
|
+
# whether the account has Channel API access.
|
|
27
|
+
#
|
|
28
|
+
# @param channel [String] OTA code (e.g. "BookingCom")
|
|
29
|
+
# @param settings [Hash] OTA settings, at least { hotel_id: "..." }
|
|
30
|
+
# @return [Response]
|
|
31
|
+
def test_connection(channel:, settings:)
|
|
32
|
+
client.post("#{resource_path}/test_connection", body: {channel:, settings:})
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Fetch the OTA's rooms and rates to map against local rate plans. For
|
|
36
|
+
# Booking.com this returns rooms[].rates[], each keyed by an integer code.
|
|
37
|
+
#
|
|
38
|
+
# @param channel [String] OTA code (e.g. "BookingCom")
|
|
39
|
+
# @param settings [Hash] OTA settings, at least { hotel_id: "..." }
|
|
40
|
+
# @return [Response]
|
|
41
|
+
def mapping_details(channel:, settings:)
|
|
42
|
+
client.post("#{resource_path}/mapping_details", body: {channel:, settings:})
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Connect a new channel to a property
|
|
46
|
+
#
|
|
47
|
+
# @param attributes [Hash] Channel attributes including property_id, channel code, credentials
|
|
48
|
+
# @return [Response]
|
|
49
|
+
def connect(attributes)
|
|
50
|
+
create(attributes)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Channels are created inactive; activate to go live.
|
|
54
|
+
#
|
|
55
|
+
# @param id [String] Channel UUID
|
|
56
|
+
# @return [Response]
|
|
57
|
+
def activate(id)
|
|
58
|
+
client.post("#{resource_path}/#{id}/activate", body: {})
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Pause a live channel without deleting it.
|
|
62
|
+
#
|
|
63
|
+
# @param id [String] Channel UUID
|
|
64
|
+
# @return [Response]
|
|
65
|
+
def deactivate(id)
|
|
66
|
+
client.post("#{resource_path}/#{id}/deactivate", body: {})
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Disconnect a channel (must be inactive first)
|
|
70
|
+
#
|
|
71
|
+
# @param id [String] Channel UUID
|
|
72
|
+
# @return [Response]
|
|
73
|
+
def disconnect(id)
|
|
74
|
+
delete(id)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Full sync a channel (refresh all data from OTA)
|
|
78
|
+
#
|
|
79
|
+
# @param id [String] Channel UUID
|
|
80
|
+
# @return [Response]
|
|
81
|
+
def full_sync(id)
|
|
82
|
+
client.post("#{resource_path}/#{id}/full_sync", body: {})
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
protected
|
|
86
|
+
|
|
87
|
+
def resource_path
|
|
88
|
+
"/channels"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def filter_keys
|
|
92
|
+
%i[property_id]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Facilities API resource
|
|
4
|
+
#
|
|
5
|
+
# Facilities represent amenities available at properties or room types.
|
|
6
|
+
# This is a read-only resource providing the list of available facility IDs.
|
|
7
|
+
#
|
|
8
|
+
# @example List available facilities
|
|
9
|
+
# client.facilities.list
|
|
10
|
+
#
|
|
11
|
+
# @see https://docs.channex.io/api-v.1-documentation/facilities-collection
|
|
12
|
+
#
|
|
13
|
+
class Facilities < Resource
|
|
14
|
+
# List available facilities
|
|
15
|
+
#
|
|
16
|
+
# @param options [Hash] Additional options
|
|
17
|
+
# @return [Response]
|
|
18
|
+
def list(**options)
|
|
19
|
+
client.get(resource_path, params: options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def resource_path
|
|
25
|
+
"/facilities"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Group Users API resource
|
|
4
|
+
#
|
|
5
|
+
# Group Users represent the association between users and groups,
|
|
6
|
+
# defining access permissions. By inviting a User into a Group, you
|
|
7
|
+
# automatically grant access for this user to all properties in the Group.
|
|
8
|
+
#
|
|
9
|
+
# @example List users in a group
|
|
10
|
+
# client.group_users.list(group_id: "group-uuid")
|
|
11
|
+
#
|
|
12
|
+
# @example Invite a user to a group
|
|
13
|
+
# client.group_users.invite(
|
|
14
|
+
# group_id: "group-uuid",
|
|
15
|
+
# user_email: "user@example.com",
|
|
16
|
+
# role: "user"
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# @see https://docs.channex.io/api-v.1-documentation/group-users-collection
|
|
20
|
+
#
|
|
21
|
+
class GroupUsers < Resource
|
|
22
|
+
ROLES = %w[owner user].freeze
|
|
23
|
+
|
|
24
|
+
# Invite a user to a group
|
|
25
|
+
#
|
|
26
|
+
# If the user is not registered, Channex will create their account
|
|
27
|
+
# automatically and send an email with instructions to onboard.
|
|
28
|
+
#
|
|
29
|
+
# @param group_id [String] Group UUID (required)
|
|
30
|
+
# @param user_email [String] Email address of the user to invite (required)
|
|
31
|
+
# @param role [String] Role: "owner" or "user" (required)
|
|
32
|
+
# @param overrides [Hash] Access policies overrides (optional)
|
|
33
|
+
# @return [Response]
|
|
34
|
+
#
|
|
35
|
+
# @example Invite a user with default permissions
|
|
36
|
+
# client.group_users.invite(
|
|
37
|
+
# group_id: "52397a6e-c330-44f4-a293-47042d3a3607",
|
|
38
|
+
# user_email: "other_user@channex.io",
|
|
39
|
+
# role: "user"
|
|
40
|
+
# )
|
|
41
|
+
def invite(group_id:, user_email:, role:, overrides: {})
|
|
42
|
+
client.post(resource_path, body: {
|
|
43
|
+
invite: {
|
|
44
|
+
group_id: group_id,
|
|
45
|
+
user_email: user_email,
|
|
46
|
+
role: role,
|
|
47
|
+
overrides: overrides
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @see #invite
|
|
53
|
+
def create(attributes)
|
|
54
|
+
invite(**attributes)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Revoke a user's access to a group
|
|
58
|
+
#
|
|
59
|
+
# @param id [String] Group User UUID
|
|
60
|
+
# @return [Response]
|
|
61
|
+
def revoke(id)
|
|
62
|
+
delete(id)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
protected
|
|
66
|
+
|
|
67
|
+
def resource_path
|
|
68
|
+
"/group_users"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def filter_keys
|
|
72
|
+
%i[group_id]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Groups API resource
|
|
4
|
+
#
|
|
5
|
+
# Groups are used to organize properties and control access.
|
|
6
|
+
# Each property can have a Base Group assigned.
|
|
7
|
+
#
|
|
8
|
+
# @example List groups
|
|
9
|
+
# client.groups.list
|
|
10
|
+
#
|
|
11
|
+
# @example Create a group
|
|
12
|
+
# client.groups.create(
|
|
13
|
+
# title: "Europe Hotels"
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @see https://docs.channex.io/api-v.1-documentation/groups-collection
|
|
17
|
+
#
|
|
18
|
+
class Groups < Resource
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def resource_path
|
|
22
|
+
"/groups"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Hotel Policies API resource
|
|
4
|
+
#
|
|
5
|
+
# Hotel Policies contain property-level settings like check-in/check-out times,
|
|
6
|
+
# internet access, parking, pets policy, and smoking rules.
|
|
7
|
+
#
|
|
8
|
+
# @example List hotel policies for a property
|
|
9
|
+
# client.hotel_policies.list(property_id: "property-uuid")
|
|
10
|
+
#
|
|
11
|
+
# @example Create a hotel policy
|
|
12
|
+
# client.hotel_policies.create(
|
|
13
|
+
# property_id: "property-uuid",
|
|
14
|
+
# title: "Standard Policy",
|
|
15
|
+
# currency: "USD",
|
|
16
|
+
# checkin_time: "15:00",
|
|
17
|
+
# checkout_time: "11:00",
|
|
18
|
+
# pets_policy: "not_allowed",
|
|
19
|
+
# smoking_policy: "no_smoking"
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @see https://docs.channex.io/api-v.1-documentation/hotel-policy-collection
|
|
23
|
+
#
|
|
24
|
+
class HotelPolicies < Resource
|
|
25
|
+
# Pets policy options
|
|
26
|
+
PETS_POLICIES = %w[allowed not_allowed by_arrangements pets_not_allowed_on_beds].freeze
|
|
27
|
+
|
|
28
|
+
# Smoking policy options
|
|
29
|
+
SMOKING_POLICIES = %w[no_smoking smoking_allowed designated_areas].freeze
|
|
30
|
+
|
|
31
|
+
# Internet access type options
|
|
32
|
+
INTERNET_ACCESS_TYPES = %w[wifi wired none].freeze
|
|
33
|
+
|
|
34
|
+
# Internet access coverage options
|
|
35
|
+
INTERNET_ACCESS_COVERAGES = %w[entire_property public_areas some_rooms all_rooms].freeze
|
|
36
|
+
|
|
37
|
+
# Parking type options
|
|
38
|
+
PARKING_TYPES = %w[on_site off_site none].freeze
|
|
39
|
+
|
|
40
|
+
# Parking reservation options
|
|
41
|
+
PARKING_RESERVATIONS = %w[needed not_needed not_available].freeze
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def resource_path
|
|
46
|
+
"/hotel_policies"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def filter_keys
|
|
50
|
+
%i[property_id]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Message Threads API resource
|
|
4
|
+
#
|
|
5
|
+
# A message thread is one guest conversation, normalised by Channex across
|
|
6
|
+
# Booking.com, Airbnb and Expedia. Threads are the channel-agnostic entry
|
|
7
|
+
# point: they exist even when there is no booking to hang them off (an
|
|
8
|
+
# Airbnb inquiry), which booking-scoped messages cannot represent.
|
|
9
|
+
#
|
|
10
|
+
# Requires the Messages application to be installed on the property —
|
|
11
|
+
# without it every call in here answers 403 Forbidden.
|
|
12
|
+
#
|
|
13
|
+
# @example List the open threads for a property
|
|
14
|
+
# client.message_threads.list(property_id: "property-uuid")
|
|
15
|
+
#
|
|
16
|
+
# @example Read a conversation
|
|
17
|
+
# client.message_threads.messages("thread-uuid", order: {inserted_at: :asc})
|
|
18
|
+
#
|
|
19
|
+
# @example Reply to the guest
|
|
20
|
+
# client.message_threads.send_message("thread-uuid", message: "Check-in is from 15:00.")
|
|
21
|
+
#
|
|
22
|
+
# @see https://docs.channex.io/api-v.1-documentation/messages-collection
|
|
23
|
+
#
|
|
24
|
+
class MessageThreads < Resource
|
|
25
|
+
# List the messages on a thread, newest first by default
|
|
26
|
+
#
|
|
27
|
+
# @param id [String] Thread UUID
|
|
28
|
+
# @param options [Hash] Additional options (page, limit, filter, order)
|
|
29
|
+
# @return [Response]
|
|
30
|
+
def messages(id, **options)
|
|
31
|
+
client.get("#{resource_path}/#{id}/messages", params: query_params(**options))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Send a message to the guest on a thread
|
|
35
|
+
#
|
|
36
|
+
# Pass `attachment_id` to send a file previously uploaded through
|
|
37
|
+
# {Attachments#create}; `message` and `attachment_id` can be combined to
|
|
38
|
+
# send a file with a caption.
|
|
39
|
+
#
|
|
40
|
+
# @param id [String] Thread UUID
|
|
41
|
+
# @param message [String] Message text
|
|
42
|
+
# @param attachment_id [String] Attachment UUID from {Attachments#create}
|
|
43
|
+
# @param attributes [Hash] Additional message attributes
|
|
44
|
+
# @return [Response]
|
|
45
|
+
def send_message(id, message: nil, attachment_id: nil, **attributes)
|
|
46
|
+
client.post(
|
|
47
|
+
"#{resource_path}/#{id}/messages",
|
|
48
|
+
body: {message: message_attributes(message:, attachment_id:, **attributes)}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Close a thread
|
|
53
|
+
#
|
|
54
|
+
# @param id [String] Thread UUID
|
|
55
|
+
# @return [Response]
|
|
56
|
+
def close(id)
|
|
57
|
+
client.post("#{resource_path}/#{id}/close", body: {})
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Mark a thread as needing no reply (Booking.com only)
|
|
61
|
+
#
|
|
62
|
+
# Booking.com scores properties on how fast they answer, but some guest
|
|
63
|
+
# messages do not call for an answer at all. Marking the thread keeps the
|
|
64
|
+
# response-time score honest instead of leaving the clock running.
|
|
65
|
+
#
|
|
66
|
+
# @param id [String] Thread UUID
|
|
67
|
+
# @return [Response]
|
|
68
|
+
def no_reply_needed(id)
|
|
69
|
+
client.post("#{resource_path}/#{id}/no_reply_needed", body: {})
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
protected
|
|
73
|
+
|
|
74
|
+
def resource_path
|
|
75
|
+
"/message_threads"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def filter_keys
|
|
79
|
+
%i[property_id booking_id]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def message_attributes(message: nil, attachment_id: nil, **attributes)
|
|
83
|
+
attributes[:message] = message if message
|
|
84
|
+
attributes[:attachment_id] = attachment_id if attachment_id
|
|
85
|
+
attributes
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Booking Messages API resource
|
|
4
|
+
#
|
|
5
|
+
# The booking-scoped view of the same conversations {MessageThreads}
|
|
6
|
+
# exposes: reach a guest by the booking you already hold, without first
|
|
7
|
+
# resolving which thread it belongs to. Channex answers 422 `not_supported`
|
|
8
|
+
# for bookings from an OTA that has no messaging API.
|
|
9
|
+
#
|
|
10
|
+
# Requires the Messages application to be installed on the property.
|
|
11
|
+
#
|
|
12
|
+
# @example Read the conversation on a booking
|
|
13
|
+
# client.messages.list(booking_id: "booking-uuid")
|
|
14
|
+
#
|
|
15
|
+
# @example Answer the guest
|
|
16
|
+
# client.messages.send_message(
|
|
17
|
+
# booking_id: "booking-uuid",
|
|
18
|
+
# message: "Thank you for your reservation!"
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @see https://docs.channex.io/api-v.1-documentation/messages-collection
|
|
22
|
+
#
|
|
23
|
+
class Messages < Resource
|
|
24
|
+
# List the messages on a booking, newest first by default
|
|
25
|
+
#
|
|
26
|
+
# @param booking_id [String] Booking UUID
|
|
27
|
+
# @param options [Hash] Additional options (page, limit, filter, order)
|
|
28
|
+
# @return [Response]
|
|
29
|
+
def list(booking_id:, **options)
|
|
30
|
+
client.get(booking_path(booking_id), params: query_params(**options))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Send a message to the guest on a booking
|
|
34
|
+
#
|
|
35
|
+
# Pass `attachment_id` to send a file previously uploaded through
|
|
36
|
+
# {Attachments#create}; `message` and `attachment_id` can be combined to
|
|
37
|
+
# send a file with a caption.
|
|
38
|
+
#
|
|
39
|
+
# @param booking_id [String] Booking UUID
|
|
40
|
+
# @param message [String] Message text
|
|
41
|
+
# @param attachment_id [String] Attachment UUID from {Attachments#create}
|
|
42
|
+
# @param attributes [Hash] Additional message attributes
|
|
43
|
+
# @return [Response]
|
|
44
|
+
def send_message(booking_id:, message: nil, attachment_id: nil, **attributes)
|
|
45
|
+
attributes[:message] = message if message
|
|
46
|
+
attributes[:attachment_id] = attachment_id if attachment_id
|
|
47
|
+
|
|
48
|
+
client.post(booking_path(booking_id), body: {message: attributes})
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
protected
|
|
52
|
+
|
|
53
|
+
# Messages only exist under a booking, so there is no collection of their
|
|
54
|
+
# own to retrieve, update or delete.
|
|
55
|
+
def resource_path
|
|
56
|
+
raise NotImplementedError, "Messages are scoped to a booking — use #list or #send_message"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def booking_path(booking_id)
|
|
60
|
+
"/bookings/#{booking_id}/messages"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|