If your application requires an appointment booking functionality to be done through third party, then timekit.io is a very neat and efficient solution. In one of my recent projects - Patients need to book different appointment slots for a doctor in iphone application which is implemented using timekit.
It provides basically following things
- An admin app to create bookings schedules.
- Frontend html for selecting appointment slots through its js widget - booking.js
Implementation logic
- Create an admin account on timekit.
- Create an application(under which all the service providers and their schedules will be created).
- It provides you a slug for your application created.
- Then in account settings - enable the developer mode to get the api token.
- Then configure the app settings in your application and create the resource(say doctor) and their calendar and schedule through timekit apis.
- Provide an api with provides a string of html in response which is created through booking.js
- Then your iphone application uses that provided html string and renders that html in InAppBrowser. From here all the appointment booking functionality works.
Implementation process followed in project
- create a timekit.yml file in config folder
development:
email: timekit.admin.email@domain.com
password: password
appSlug: your_app_slug
apiToken: your_app_api_token
staging:
email: timekit.admin.email@domain.com
password: Welcome@123
appSlug: your_app_slug
apiToken: your_app_api_token
production:
email: timekit.admin.email@domain.com
password: Welcome@123
appSlug: your_app_slug
apiToken: your_app_api_token
- Create an initializer
TIMEKIT = YAML.load_file(File.join(Rails.root, 'config', 'timekit.yml'))[Rails.env]
- Now create a concern in model. On creation of doctor, a resource, its calendar and its projects(appointment scheduling) should get created. And on deletion of doctor, timekit resource should get deleted.
module Timekit
extend ActiveSupport::Concern
included do
before_create :create_resource, :create_calendar, :create_project
after_destroy :delete_resource
end
def create_resource
begin
response = HTTParty.post(
'https://api.timekit.io/v2/resources',
basic_auth: {
username: TIMEKIT['email'],
password: TIMEKIT['apiToken']
},
headers: {
'Content-Type': 'application/json',
'Timekit-App': TIMEKIT['appSlug']
},
body: {
email: self.email,
timezone: 'America/Los_Angeles',
name: self.first_name + (self.last_name.present? ? ' ' + self.last_name : ''),
password: Doctor::PASSWORD
}.to_json
)
if [200, 201].include?(response.code)
self.timekit_resource = response['data'].to_h
self.timekit_project = Doctor::DEFAULT_TIMEKIT_PROJECT_ID
else
raise ActiveRecord::Rollback, response['errors']
end
rescue => e
self.errors.add(:email, 'TimeKit Error -> '+e.message)
throw(:abort)
end
end
def create_calendar
begin
response = HTTParty.post(
'https://api.timekit.io/v2/calendars',
basic_auth: {
username: self.email,
password: self.timekit_resource['api_token']
},
headers: {
'Content-Type': 'application/json',
'Timekit-App': TIMEKIT['appSlug']
},
body: {
name: self.email,
description: "#{self.email} CALENDAR"
}.to_json
)
if [200, 201].include?(response.code)
self.timekit_calendar = response['data'].to_h
else
raise ActiveRecord::Rollback, response['errors']
end
rescue => e
self.errors.add(:email, 'TimeKit Error -> '+e.message)
delete_resource
throw(:abort)
end
end
def create_project
begin
response = HTTParty.post(
'https://api.timekit.io/v2/projects',
basic_auth: {
username: self.email,
password: self.timekit_resource['api_token']
},
headers: {
'Content-Type': 'application/json',
'Timekit-App': TIMEKIT['appSlug']
},
body: {
name: self.email,
slug: self.email.split('@').first.gsub('.', '-').gsub('+', '-'),
config: {
email: self.email,
calendar: self.timekit_calendar['id'],
apiToken: self.timekit_resource['api_token'],
app: TIMEKIT['appSlug'],
timekitFindTime: {
filters: {
or: [{
specific_day_and_time: {
day: "Monday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Tuesday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Wednesday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Thursday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Friday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Saturday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
},
{
specific_day_and_time: {
day: "Sunday",
start: 1,
end: 9,
timezone: "America/Los_Angeles"
}
}]
},
length: "30 minutes"
}
}
}.to_json
)
if [200, 201].include?(response.code)
self.timekit_project = response['data']['id']
else
raise ActiveRecord::Rollback, response['errors']
end
rescue => e
self.errors.add(:email, 'TimeKit Error -> '+e.message)
delete_resource
delete_calendar
throw(:abort)
end
end
def delete_resource
HTTParty.delete(
"https://api.timekit.io/v2/resources/#{self.timekit_resource['id']}",
basic_auth: {
username: TIMEKIT['email'],
password: TIMEKIT['apiToken']
},
headers: {
'Timekit-App': TIMEKIT['appSlug']
}
)
end
def delete_calendar
HTTParty.delete(
"https://api.timekit.io/v2/calendars/#{self.timekit_calendar['id']}",
basic_auth: {
username: TIMEKIT['email'],
password: TIMEKIT['apiToken']
},
headers: {
'Timekit-App': TIMEKIT['appSlug']
}
)
end
def decline_appointment
response = HTTParty.post(
'https://api.timekit.io/v2/bookings/58190fc6-1ec0-4ebb-b627-7ce6aa9fc703/confirm',
basic_auth: {
username: self.email,
password: self.timekit_resource['api_token']
},
headers: {
'Content-Type': 'application/json',
'Timekit-App': TIMEKIT['appSlug']
},
body: {
name: "Doctor's Appointment Calendar",
description: "'Doctor's Appointment Calendar description'"
}.to_json
)
end
def appointment_html_snippet(patient, payment_source_id)
<<EOS
<html>
<head></head>
<body>
<div id='bookingjs'>
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
<script type='text/javascript' src='https://cdn.timekit.io/booking-js/v1/booking.min.js' defer></script>
<script type='text/javascript'>window.timekitBookingConfig = {
app: '#{TIMEKIT['appSlug']}',
email: '#{self.email}',
apiToken: '#{self.timekit_resource['api_token']}',
calendar: '#{self.timekit_calendar['id']}',
name: '#{self.first_name}',
avatar: '#{Rails.application.config.action_mailer.default_url_options[:host]}#{self.avatar.url}',
widgetId: '#{self.timekit_project}',
timekitCreateBooking: {
graph: 'instant',
action: 'confirm',
customer: {
email: '#{patient.email}',
source_id: #{payment_source_id},
patient_id: #{patient.id}
}
},
bookingFields: {
name: {
placeholder: 'Name',
prefilled: '#{patient.first_name || ''}',
locked: false
},
email: {
placeholder: 'Email',
prefilled: '',
locked: false
},
comment: {
enabled: true,
placeholder: 'Comment',
prefilled: false,
required: false,
locked: false
},
phone: {
enabled: true,
placeholder: 'Phone Number',
prefilled: '#{patient.phone_number}',
required: false,
locked: false
}
},
localization: {
strings: {
allocatedResourcePrefix: 'with',
submitText: 'Book Appointment',
successMessageTitle: 'Thanks!',
successMessageBody: 'Appointment confirmed!',
timezoneHelperLoading: 'Loading..',
timezoneHelperDifferent: 'Your timezone is %s hours %s of %s (calendar shown in your local time)',
timezoneHelperSame: 'You are in the same timezone as %s'
}
}
};</script>
</div>
</body>
</html>
EOS
end
end
- Now include this module in your Doctor model.
include Timekit
-
Now on creation of doctor, valid login on timekit gets created with Doctor::PASSWORD. After login in app through doctor, under project section, scheduling of that doctor can be configured.
-
Now provide an api which responds with HTML string with all the settings done.
def new
@doctor = Doctor.find_by_id(params[:id])
if @doctor
render json: {
appointment_html: @doctor.appointment_html_snippet(current_user, @source.id)
}
else
render json: {errors: {doctor: 'not found'}, message: 'Doctor not found.'}, status: :not_found unless @doctor
end
end
- Now iphone should consume this html and show it in InAppBrowser.