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,62 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
# Error classes for Channex API responses
|
|
3
|
+
module Errors
|
|
4
|
+
# Base error class for all Channex errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :response, :code
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, response: nil, code: nil)
|
|
9
|
+
@response = response
|
|
10
|
+
@code = code
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Raised when authentication fails (401)
|
|
16
|
+
class AuthenticationError < Error; end
|
|
17
|
+
|
|
18
|
+
# Raised when the request is forbidden (403)
|
|
19
|
+
class ForbiddenError < Error; end
|
|
20
|
+
|
|
21
|
+
# Raised when a resource is not found (404)
|
|
22
|
+
class NotFoundError < Error; end
|
|
23
|
+
|
|
24
|
+
# Raised when validation fails (422)
|
|
25
|
+
class ValidationError < Error
|
|
26
|
+
attr_reader :errors
|
|
27
|
+
|
|
28
|
+
# Channex details the rejected fields by name. Only the names join the message, which reaches error
|
|
29
|
+
# tracking: the details can echo the values sent.
|
|
30
|
+
def initialize(message = nil, errors: {}, **kwargs)
|
|
31
|
+
@errors = errors
|
|
32
|
+
fields = errors.is_a?(Hash) ? errors.keys : []
|
|
33
|
+
super(fields.any? ? "#{message}: #{fields.join(", ")}" : message, **kwargs)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Raised when rate limited (429)
|
|
38
|
+
class RateLimitError < Error
|
|
39
|
+
attr_reader :retry_after
|
|
40
|
+
|
|
41
|
+
def initialize(message = nil, retry_after: nil, **kwargs)
|
|
42
|
+
@retry_after = retry_after
|
|
43
|
+
super(message, **kwargs)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Raised for server errors (5xx)
|
|
48
|
+
class ServerError < Error; end
|
|
49
|
+
|
|
50
|
+
# Raised for connection/network errors
|
|
51
|
+
class ConnectionError < Error; end
|
|
52
|
+
|
|
53
|
+
# Raised when request times out
|
|
54
|
+
class TimeoutError < Error; end
|
|
55
|
+
|
|
56
|
+
# Raised for invalid configuration
|
|
57
|
+
class ConfigurationError < Error; end
|
|
58
|
+
|
|
59
|
+
# Errors that repeating the same request can't fix.
|
|
60
|
+
PERMANENT = [AuthenticationError, ForbiddenError, NotFoundError, ValidationError].freeze
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
# Base class for typed API response objects
|
|
3
|
+
#
|
|
4
|
+
# Provides typed accessor methods while maintaining hash compatibility
|
|
5
|
+
# for backwards compatibility and easy data exploration.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# revision = Channex::Objects::BookingRevision.new(data)
|
|
9
|
+
# revision.id # => "uuid" (typed accessor)
|
|
10
|
+
# revision["id"] # => "uuid" (hash access)
|
|
11
|
+
# revision.dig("customer", "name") # => "John"
|
|
12
|
+
# revision.to_h # => {"id" => "uuid", ...}
|
|
13
|
+
#
|
|
14
|
+
# @example Defining attributes in subclasses
|
|
15
|
+
# class BookingRevision < Channex::Object
|
|
16
|
+
# attribute :id
|
|
17
|
+
# attribute :customer, type: Customer
|
|
18
|
+
# array_attribute :rooms, type: BookingRoom
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
class Object
|
|
22
|
+
include Enumerable
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# Define a typed attribute with optional type coercion
|
|
26
|
+
#
|
|
27
|
+
# @param name [Symbol] Attribute name (becomes the method name)
|
|
28
|
+
# @param type [Class, nil] Optional class for nested object wrapping
|
|
29
|
+
# @param key [String, nil] Override the hash key (defaults to name.to_s)
|
|
30
|
+
#
|
|
31
|
+
# @example Simple attribute
|
|
32
|
+
# attribute :id
|
|
33
|
+
#
|
|
34
|
+
# @example Nested object
|
|
35
|
+
# attribute :customer, type: Customer
|
|
36
|
+
#
|
|
37
|
+
# @example Different key name
|
|
38
|
+
# attribute :email, key: "mail"
|
|
39
|
+
#
|
|
40
|
+
def attribute(name, type: nil, key: nil)
|
|
41
|
+
key ||= name.to_s
|
|
42
|
+
|
|
43
|
+
attributes[name] = {key: key, type: type}
|
|
44
|
+
|
|
45
|
+
define_method(name) do
|
|
46
|
+
value = @data[key]
|
|
47
|
+
return nil if value.nil?
|
|
48
|
+
|
|
49
|
+
if type
|
|
50
|
+
@wrapped_values[name] ||= wrap_value(value, type)
|
|
51
|
+
else
|
|
52
|
+
value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Define an array attribute with typed elements
|
|
58
|
+
#
|
|
59
|
+
# @param name [Symbol] Attribute name
|
|
60
|
+
# @param type [Class] Class for array element wrapping
|
|
61
|
+
# @param key [String, nil] Override the hash key
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# array_attribute :rooms, type: BookingRoom
|
|
65
|
+
#
|
|
66
|
+
def array_attribute(name, type:, key: nil)
|
|
67
|
+
key ||= name.to_s
|
|
68
|
+
|
|
69
|
+
attributes[name] = {key: key, type: type, array: true}
|
|
70
|
+
|
|
71
|
+
define_method(name) do
|
|
72
|
+
@wrapped_values[name] ||= begin
|
|
73
|
+
raw = @data[key] || []
|
|
74
|
+
raw.map { |item| wrap_value(item, type) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Registry of defined attributes for this class
|
|
80
|
+
# @return [Hash]
|
|
81
|
+
def attributes
|
|
82
|
+
@attributes ||= {}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Construct an object from raw API data
|
|
86
|
+
#
|
|
87
|
+
# @param data [Hash, nil] Raw API response data
|
|
88
|
+
# @return [Object, nil] Wrapped object or nil if data is nil
|
|
89
|
+
def construct_from(data)
|
|
90
|
+
return nil if data.nil?
|
|
91
|
+
new(data)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param data [Hash] Raw API response data
|
|
96
|
+
def initialize(data)
|
|
97
|
+
@data = data.is_a?(Hash) ? data : {}
|
|
98
|
+
@wrapped_values = {}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Hash-like access for backwards compatibility
|
|
102
|
+
#
|
|
103
|
+
# @param key [String, Symbol] Hash key
|
|
104
|
+
# @return [Object, nil] Value at key
|
|
105
|
+
def [](key)
|
|
106
|
+
@data[key.to_s]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Deep access for nested data
|
|
110
|
+
#
|
|
111
|
+
# @param keys [Array<String, Symbol>] Path of keys
|
|
112
|
+
# @return [Object, nil] Value at path
|
|
113
|
+
def dig(*keys)
|
|
114
|
+
@data.dig(*keys.map(&:to_s))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Return the underlying hash data
|
|
118
|
+
# @return [Hash]
|
|
119
|
+
def to_h
|
|
120
|
+
@data.dup
|
|
121
|
+
end
|
|
122
|
+
alias_method :to_hash, :to_h
|
|
123
|
+
|
|
124
|
+
# JSON serialization
|
|
125
|
+
# @return [String]
|
|
126
|
+
def to_json(*args)
|
|
127
|
+
@data.to_json(*args)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Rails JSON serialization support
|
|
131
|
+
# @return [Hash]
|
|
132
|
+
def as_json(*args)
|
|
133
|
+
@data.as_json(*args)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Enumerable support - iterate over key/value pairs
|
|
137
|
+
def each(&block)
|
|
138
|
+
@data.each(&block)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [Array<String>] All keys in the data
|
|
142
|
+
delegate :keys, to: :@data
|
|
143
|
+
|
|
144
|
+
# @return [Array] All values in the data
|
|
145
|
+
delegate :values, to: :@data
|
|
146
|
+
|
|
147
|
+
# Check if a key exists
|
|
148
|
+
# @param key [String, Symbol]
|
|
149
|
+
# @return [Boolean]
|
|
150
|
+
def key?(key)
|
|
151
|
+
@data.key?(key.to_s)
|
|
152
|
+
end
|
|
153
|
+
alias_method :has_key?, :key?
|
|
154
|
+
|
|
155
|
+
# Fetch with default value support
|
|
156
|
+
# @param key [String, Symbol]
|
|
157
|
+
# @return [Object]
|
|
158
|
+
def fetch(key, *args, &block)
|
|
159
|
+
@data.fetch(key.to_s, *args, &block)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Debug representation
|
|
163
|
+
# @return [String]
|
|
164
|
+
def inspect
|
|
165
|
+
"#<#{self.class.name} #{@data.inspect}>"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @return [String]
|
|
169
|
+
def to_s
|
|
170
|
+
inspect
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Equality based on underlying data
|
|
174
|
+
# @param other [Object, Hash]
|
|
175
|
+
# @return [Boolean]
|
|
176
|
+
def ==(other)
|
|
177
|
+
case other
|
|
178
|
+
when Channex::Object
|
|
179
|
+
@data == other.to_h
|
|
180
|
+
when Hash
|
|
181
|
+
@data == other
|
|
182
|
+
else
|
|
183
|
+
false
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def wrap_value(value, type)
|
|
190
|
+
return nil if value.nil?
|
|
191
|
+
|
|
192
|
+
if type.respond_to?(:construct_from)
|
|
193
|
+
type.construct_from(value)
|
|
194
|
+
elsif type.is_a?(Class) && type < Channex::Object
|
|
195
|
+
type.new(value)
|
|
196
|
+
else
|
|
197
|
+
value
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Booking revision from Channex API
|
|
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
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# revision.id # => "uuid"
|
|
10
|
+
# revision.status # => "new"
|
|
11
|
+
# revision.new? # => true
|
|
12
|
+
# revision.customer.full_name # => "John Doe"
|
|
13
|
+
# revision.rooms.first.nights # => 3
|
|
14
|
+
#
|
|
15
|
+
# @example Hash access still works
|
|
16
|
+
# revision["booking_id"] # => "uuid"
|
|
17
|
+
# revision.dig("customer", "mail") # => "john@example.com"
|
|
18
|
+
#
|
|
19
|
+
class BookingRevision < Channex::Object
|
|
20
|
+
# Core identifiers
|
|
21
|
+
attribute :id
|
|
22
|
+
attribute :booking_id
|
|
23
|
+
attribute :property_id
|
|
24
|
+
attribute :unique_id
|
|
25
|
+
attribute :system_id
|
|
26
|
+
|
|
27
|
+
# OTA information
|
|
28
|
+
attribute :ota_name
|
|
29
|
+
attribute :ota_reservation_code
|
|
30
|
+
|
|
31
|
+
# Status and timing
|
|
32
|
+
attribute :status
|
|
33
|
+
attribute :inserted_at
|
|
34
|
+
|
|
35
|
+
# Booking dates
|
|
36
|
+
attribute :arrival_date
|
|
37
|
+
attribute :departure_date
|
|
38
|
+
attribute :arrival_hour
|
|
39
|
+
|
|
40
|
+
# Financial
|
|
41
|
+
attribute :amount
|
|
42
|
+
attribute :currency
|
|
43
|
+
attribute :ota_commission
|
|
44
|
+
attribute :payment_collect
|
|
45
|
+
attribute :payment_type
|
|
46
|
+
|
|
47
|
+
# Other
|
|
48
|
+
attribute :notes
|
|
49
|
+
|
|
50
|
+
# Nested objects
|
|
51
|
+
attribute :customer, type: Customer
|
|
52
|
+
attribute :occupancy, type: Occupancy
|
|
53
|
+
attribute :guarantee, type: Guarantee
|
|
54
|
+
|
|
55
|
+
# Arrays
|
|
56
|
+
array_attribute :rooms, type: BookingRoom
|
|
57
|
+
array_attribute :services, type: Service
|
|
58
|
+
|
|
59
|
+
# Status check helpers
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def new?
|
|
62
|
+
status == "new"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
def modified?
|
|
67
|
+
status == "modified"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Boolean]
|
|
71
|
+
def cancelled?
|
|
72
|
+
status == "cancelled"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Arrival date as Date object
|
|
76
|
+
# @return [Date, nil]
|
|
77
|
+
def check_in_date
|
|
78
|
+
Date.parse(arrival_date) if arrival_date
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Departure date as Date object
|
|
82
|
+
# @return [Date, nil]
|
|
83
|
+
def check_out_date
|
|
84
|
+
Date.parse(departure_date) if departure_date
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Timestamp when revision was received
|
|
88
|
+
# @return [Time, nil]
|
|
89
|
+
def received_at
|
|
90
|
+
Time.parse(inserted_at) if inserted_at
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Total booking amount as Money object (if money gem is available)
|
|
94
|
+
# @return [Money, nil]
|
|
95
|
+
def total_amount
|
|
96
|
+
return nil unless amount && currency
|
|
97
|
+
return nil unless defined?(Money)
|
|
98
|
+
|
|
99
|
+
Money.from_amount(BigDecimal(amount.to_s), currency)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# OTA commission as Money object (if money gem is available)
|
|
103
|
+
# @return [Money, nil]
|
|
104
|
+
def commission_amount
|
|
105
|
+
return nil unless ota_commission && currency
|
|
106
|
+
return nil unless defined?(Money)
|
|
107
|
+
|
|
108
|
+
Money.from_amount(BigDecimal(ota_commission.to_s), currency)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Whether payment should be collected by the property
|
|
112
|
+
# @return [Boolean]
|
|
113
|
+
def collect_payment?
|
|
114
|
+
payment_collect == "property" || payment_collect.nil?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Whether payment was already collected by the OTA
|
|
118
|
+
# @return [Boolean]
|
|
119
|
+
def prepaid?
|
|
120
|
+
payment_collect == "ota"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Individual room within a booking revision
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# room.room_type_id # => "uuid"
|
|
7
|
+
# room.check_in_date # => Date
|
|
8
|
+
# room.check_out_date # => Date
|
|
9
|
+
# room.nights # => 3
|
|
10
|
+
# room.occupancy.adults # => 2
|
|
11
|
+
# room.guests.first.name # => "John"
|
|
12
|
+
#
|
|
13
|
+
class BookingRoom < Channex::Object
|
|
14
|
+
attribute :booking_room_id
|
|
15
|
+
attribute :ota_unique_id
|
|
16
|
+
attribute :room_type_id
|
|
17
|
+
attribute :rate_plan_id
|
|
18
|
+
attribute :checkin_date
|
|
19
|
+
attribute :checkout_date
|
|
20
|
+
attribute :amount
|
|
21
|
+
attribute :occupancy, type: Occupancy
|
|
22
|
+
array_attribute :guests, type: Guest
|
|
23
|
+
array_attribute :taxes, type: Tax
|
|
24
|
+
array_attribute :services, type: Service
|
|
25
|
+
|
|
26
|
+
# Check-in date as Date object
|
|
27
|
+
# @return [Date, nil]
|
|
28
|
+
def check_in_date
|
|
29
|
+
Date.parse(checkin_date) if checkin_date
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Check-out date as Date object
|
|
33
|
+
# @return [Date, nil]
|
|
34
|
+
def check_out_date
|
|
35
|
+
Date.parse(checkout_date) if checkout_date
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Number of nights for this room
|
|
39
|
+
# @return [Integer, nil]
|
|
40
|
+
def nights
|
|
41
|
+
return nil unless check_in_date && check_out_date
|
|
42
|
+
(check_out_date - check_in_date).to_i
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def room_reservation_id
|
|
46
|
+
meta["room_reservation_id"] || meta["roomreservation_id"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Daily price breakdown
|
|
50
|
+
# @return [Hash] Date string keys mapped to price strings
|
|
51
|
+
def days
|
|
52
|
+
@data["days"] || {}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Free-form metadata
|
|
56
|
+
# @return [Hash]
|
|
57
|
+
def meta
|
|
58
|
+
@data["meta"] || {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Parent rate plan ID (when using derived rate plans)
|
|
62
|
+
# @return [String, nil]
|
|
63
|
+
def parent_rate_plan_id
|
|
64
|
+
meta["parent_rate_plan_id"]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Company information nested within Customer
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# company.title # => "Acme Corp"
|
|
7
|
+
# company.number # => "12345678"
|
|
8
|
+
# company.number_type # => "vat"
|
|
9
|
+
#
|
|
10
|
+
class Company < Channex::Object
|
|
11
|
+
attribute :title
|
|
12
|
+
attribute :number
|
|
13
|
+
attribute :number_type
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Customer information from a booking
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# customer.name # => "John"
|
|
7
|
+
# customer.surname # => "Doe"
|
|
8
|
+
# customer.full_name # => "John Doe"
|
|
9
|
+
# customer.email # => "john@example.com"
|
|
10
|
+
# customer.company # => Channex::Objects::Company
|
|
11
|
+
#
|
|
12
|
+
class Customer < Channex::Object
|
|
13
|
+
attribute :name
|
|
14
|
+
attribute :surname
|
|
15
|
+
attribute :mail
|
|
16
|
+
attribute :phone
|
|
17
|
+
attribute :country
|
|
18
|
+
attribute :city
|
|
19
|
+
attribute :address
|
|
20
|
+
attribute :zip
|
|
21
|
+
attribute :language
|
|
22
|
+
attribute :company, type: Company
|
|
23
|
+
|
|
24
|
+
# Alias for mail (more intuitive name)
|
|
25
|
+
# @return [String, nil]
|
|
26
|
+
def email
|
|
27
|
+
mail
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Combined first and last name
|
|
31
|
+
# @return [String, nil]
|
|
32
|
+
def full_name
|
|
33
|
+
[name, surname].compact.join(" ").presence
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Free-form metadata
|
|
37
|
+
# @return [Hash]
|
|
38
|
+
def meta
|
|
39
|
+
@data["meta"] || {}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Payment guarantee/card information
|
|
4
|
+
#
|
|
5
|
+
# Note: Contains sensitive payment data. Handle with care.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# guarantee.card_type # => "visa"
|
|
9
|
+
# guarantee.card_number # => "4111111111111111"
|
|
10
|
+
# guarantee.cardholder_name # => "John Doe"
|
|
11
|
+
# guarantee.expiration_date # => "12/25"
|
|
12
|
+
#
|
|
13
|
+
class Guarantee < Channex::Object
|
|
14
|
+
attribute :card_number
|
|
15
|
+
attribute :card_type
|
|
16
|
+
attribute :cardholder_name
|
|
17
|
+
attribute :cvv
|
|
18
|
+
attribute :expiration_date
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Individual guest information within a room
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# guest.name # => "John"
|
|
7
|
+
# guest.surname # => "Doe"
|
|
8
|
+
# guest.full_name # => "John Doe"
|
|
9
|
+
#
|
|
10
|
+
class Guest < Channex::Object
|
|
11
|
+
attribute :name
|
|
12
|
+
attribute :surname
|
|
13
|
+
|
|
14
|
+
# Combined first and last name
|
|
15
|
+
# @return [String, nil]
|
|
16
|
+
def full_name
|
|
17
|
+
[name, surname].compact.join(" ").presence
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# A single message inside a Message Thread.
|
|
4
|
+
#
|
|
5
|
+
# The API returns messages as JSON:API resource nodes — the text lives under
|
|
6
|
+
# `attributes`, the identifier under `id` and the owning thread under
|
|
7
|
+
# `relationships`. {.from_resource} flattens that node so a message is one
|
|
8
|
+
# hash you can read, store and re-wrap.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# message.sender # => "guest"
|
|
12
|
+
# message.from_guest? # => true
|
|
13
|
+
# message.message # => "What time is check-in?"
|
|
14
|
+
# message.thread_id # => "4e160f0b-016a-424a-a30d-a9f0a8b1cbaa"
|
|
15
|
+
#
|
|
16
|
+
class Message < Channex::Object
|
|
17
|
+
# Who put the message on the thread. `system` marks messages Channex
|
|
18
|
+
# generates itself, such as an Airbnb inquiry.
|
|
19
|
+
SENDERS = %w[guest property system].freeze
|
|
20
|
+
|
|
21
|
+
attribute :id
|
|
22
|
+
attribute :message
|
|
23
|
+
attribute :sender
|
|
24
|
+
attribute :inserted_at
|
|
25
|
+
attribute :updated_at
|
|
26
|
+
|
|
27
|
+
# Lifted out of `relationships`
|
|
28
|
+
attribute :thread_id
|
|
29
|
+
attribute :user_id
|
|
30
|
+
|
|
31
|
+
# Flatten a JSON:API resource node into a message
|
|
32
|
+
#
|
|
33
|
+
# @param node [Hash, nil] A `{"id", "attributes", "relationships"}` node
|
|
34
|
+
# @return [Message, nil]
|
|
35
|
+
def self.from_resource(node)
|
|
36
|
+
return nil if node.nil?
|
|
37
|
+
|
|
38
|
+
attributes = node["attributes"] || {}
|
|
39
|
+
relationships = node["relationships"] || {}
|
|
40
|
+
|
|
41
|
+
new(
|
|
42
|
+
attributes.merge(
|
|
43
|
+
"id" => node["id"],
|
|
44
|
+
"thread_id" => relationships.dig("message_thread", "data", "id"),
|
|
45
|
+
"user_id" => relationships.dig("user", "data", "id")
|
|
46
|
+
).compact
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Attachments as returned by the API. Channex sends relative URLs, which
|
|
51
|
+
# have to be prefixed with the API base URL to be fetched.
|
|
52
|
+
#
|
|
53
|
+
# @return [Array]
|
|
54
|
+
def attachments
|
|
55
|
+
@data["attachments"] || []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Attachment URLs, whether the API sent bare strings or objects.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<String>]
|
|
61
|
+
def attachment_urls
|
|
62
|
+
attachments.filter_map do |attachment|
|
|
63
|
+
attachment.is_a?(Hash) ? attachment["url"] : attachment
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def attachments?
|
|
69
|
+
!attachments.empty?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Provider-specific extras, e.g. the booking details of an Airbnb inquiry.
|
|
73
|
+
#
|
|
74
|
+
# @return [Hash]
|
|
75
|
+
def meta
|
|
76
|
+
@data["meta"] || {}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def from_guest?
|
|
81
|
+
sender == "guest"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def from_property?
|
|
86
|
+
sender == "property"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def from_system?
|
|
91
|
+
sender == "system"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|