Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The application is designed to be flexible enough so that it can handle any ente
* Self hosted installation.
* Translated user interface.
* User community support.
* Service payment by [Stripe Payment links](https://stripe.com/en-gb-es/payments/payment-links)

## Setup

Expand Down
12 changes: 12 additions & 0 deletions application/config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,5 +457,17 @@
*/
$config['rate_limiting'] = true;

/*
|--------------------------------------------------------------------------
| Stripe Payment Configuration
|--------------------------------------------------------------------------
|
| Declare some of the global config values of the Stripe Payments
|
*/

$config['stripe_payment_feature'] = Config::STRIPE_PAYMENT_FEATURE;
$config['stripe_api_key'] = Config::STRIPE_API_KEY;

/* End of file config.php */
/* Location: ./application/config/config.php */
4 changes: 4 additions & 0 deletions application/controllers/Booking.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ public function index(): void
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);
$customer_token = md5(uniqid(mt_rand(), true));
$is_paid = $appointment['is_paid'];

// Cache the token for 10 minutes.
$this->cache->save('customer-token-' . $customer_token, $customer['id'], 600);
Expand All @@ -218,6 +219,7 @@ public function index(): void
$appointment = null;
$provider = null;
$customer = null;
$is_paid = 0;
}

script_vars([
Expand Down Expand Up @@ -276,9 +278,11 @@ public function index(): void
'grouped_timezones' => $grouped_timezones,
'manage_mode' => $manage_mode,
'customer_token' => $customer_token,
'is_paid' => $is_paid,
'appointment_data' => $appointment,
'provider_data' => $provider,
'customer_data' => $customer,
'company_email' => setting('company_email'),
]);

$this->load->view('pages/booking');
Expand Down
20 changes: 20 additions & 0 deletions application/controllers/Booking_confirmation.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,32 @@ public function of(): void

$add_to_google_url = $this->google_sync->get_add_to_google_url($appointment['id']);

$service = $this->services_model->find($appointment['id_services']);

$customer = $this->customers_model->find($appointment['id_users_customer']);

$payment_link = null;
if( isset($service['payment_link']) && !empty(trim($service['payment_link']))){
$payment_link_vars = array(
'{$appointment_hash}' => $appointment['hash'],
'{$customer_email}' => $customer['email'],
);
$payment_link_template = $service['payment_link']
. (str_contains($service['payment_link'], '?')
? '' : '?')
. 'client_reference_id={$appointment_hash}&prefilled_email={$customer_email}';

$payment_link = strtr($payment_link_template, $payment_link_vars);
}

html_vars([
'page_title' => lang('success'),
'company_color' => setting('company_color'),
'google_analytics_code' => setting('google_analytics_code'),
'matomo_analytics_url' => setting('matomo_analytics_url'),
'add_to_google_url' => $add_to_google_url,
'is_paid' => $appointment['is_paid'],
'payment_link' => $payment_link,
]);

$this->load->view('pages/booking_confirmation');
Expand Down
14 changes: 8 additions & 6 deletions application/controllers/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public function index(string $appointment_hash = ''): void
'secretary_providers' => $secretary_providers,
'edit_appointment' => $edit_appointment,
'customers' => $this->customers_model->get(null, 50, null, 'update_datetime DESC'),
'stripe_payment_feature' => config('stripe_payment_feature'),
]);

html_vars([
Expand Down Expand Up @@ -255,6 +256,7 @@ public function save_appointment(): void
'id_users_provider',
'id_users_customer',
'id_services',
'is_paid',
]);

$appointment['id'] = $this->appointments_model->save($appointment);
Expand Down Expand Up @@ -654,17 +656,17 @@ public function get_calendar_appointments(): void
$start_date .
' AND start_datetime < ' .
$end_date .
')
')
or (end_datetime > ' .
$start_date .
' AND end_datetime < ' .
$end_date .
')
')
or (start_datetime <= ' .
$start_date .
' AND end_datetime >= ' .
$end_date .
'))
'))
AND is_unavailability = 0
';

Expand All @@ -691,17 +693,17 @@ public function get_calendar_appointments(): void
$start_date .
' AND start_datetime < ' .
$end_date .
')
')
or (end_datetime > ' .
$start_date .
' AND end_datetime < ' .
$end_date .
')
')
or (start_datetime <= ' .
$start_date .
' AND end_datetime >= ' .
$end_date .
'))
'))
AND is_unavailability = 1
';

Expand Down
245 changes: 245 additions & 0 deletions application/controllers/Payment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');

/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author F.González <fernando@tuta.io>
* @copyright Copyright (c) Alex Tselegidis
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
* @link https://easyappointments.org
* @since v1.0.0
* ---------------------------------------------------------------------------- */

/**
* Payment confirmation controller.
*
* Handles the confirmation of a payment.
*
*
* @package Controllers
*/
class Payment extends EA_Controller {
/**
* Booking constructor.
*/
public function __construct()
{
parent::__construct();

$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('admins_model');
$this->load->model('secretaries_model');
$this->load->model('categories_model');
$this->load->model('services_model');
$this->load->model('customers_model');
$this->load->model('settings_model');
$this->load->model('consents_model');

$this->load->library('timezones');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->load->library('availability');
$this->load->library('webhooks_client');

$this->load->driver('cache', ['adapter' => 'file']);
}

/**
* Render the payment confirmation page.
*/
public function index()
{
if ( ! is_app_installed())
{
redirect('installation');

return;
}

$appointment = html_vars('appointment');

if (empty($appointment)) {
abort(404, "Forbidden");
} else {
$manage_mode = TRUE;
$company_name = setting('company_name');
$company_logo = setting('company_logo');
$company_color = setting('company_color');
$google_analytics_code = setting('google_analytics_code');
$matomo_analytics_url = setting('matomo_analytics_url');
$date_format = setting('date_format');
$time_format = setting('time_format');

$display_first_name = setting('display_first_name');
$require_first_name = setting('require_first_name');
$display_last_name = setting('display_last_name');
$require_last_name = setting('require_last_name');
$display_email = setting('display_email');
$require_email = setting('require_email');
$display_phone_number = setting('display_phone_number');
$require_phone_number = setting('require_phone_number');
$display_address = setting('display_address');
$require_address = setting('require_address');
$display_city = setting('display_city');
$require_city = setting('require_city');
$display_zip_code = setting('display_zip_code');
$require_zip_code = setting('require_zip_code');
$display_notes = setting('display_notes');
$require_notes = setting('require_notes');
$display_cookie_notice = setting('display_cookie_notice');
$cookie_notice_content = setting('cookie_notice_content');
$display_terms_and_conditions = setting('display_terms_and_conditions');
$terms_and_conditions_content = setting('terms_and_conditions_content');
$display_privacy_policy = setting('display_privacy_policy');
$privacy_policy_content = setting('privacy_policy_content');

$theme = request('theme', setting('theme', 'default'));
if (empty($theme) || ! file_exists(__DIR__ . '/../../assets/css/themes/' . $theme . '.min.css'))
{
$theme = 'default';
}

$timezones = $this->timezones->to_array();
$grouped_timezones = $this->timezones->to_grouped_array();
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);

script_vars([
'date_format' => $date_format,
'time_format' => $time_format,
'display_cookie_notice' => $display_cookie_notice,
'display_any_provider' => setting('display_any_provider'),
]);

html_vars([
'theme' => $theme,
'company_name' => $company_name,
'company_logo' => $company_logo,
'company_color' => $company_color === '#ffffff' ? '' : $company_color,
'date_format' => $date_format,
'time_format' => $time_format,
'display_first_name' => $display_first_name,
'display_last_name' => $display_last_name,
'display_email' => $display_email,
'display_phone_number' => $display_phone_number,
'display_address' => $display_address,
'display_city' => $display_city,
'display_zip_code' => $display_zip_code,
'display_notes' => $display_notes,
'google_analytics_code' => $google_analytics_code,
'matomo_analytics_url' => $matomo_analytics_url,
'timezones' => $timezones,
'grouped_timezones' => $grouped_timezones,
'appointment' => $appointment,
'provider' => $provider,
'customer' => $customer,
]);

$this->load->view('pages/payment');
}
}

/**
* Validates Stripe payment and render confirmation screen for the appointment.
*
* This method sets a flag as paid for an appointment and call the "index" callback
* to handle the page rendering.
*
* @param string $checkout_session_id Stripe session id.
*/
public function confirm(string $checkout_session_id)
{
try
{
$stripe_api_key = config('stripe_api_key');

$stripe = new \Stripe\StripeClient($stripe_api_key);

$session = $stripe->checkout->sessions->retrieve($checkout_session_id);

$appointment_hash = $session->client_reference_id;
$payment_intent = $session->payment_intent;

$appointment = $this->set_paid($appointment_hash, $payment_intent);

html_vars(['appointment' => $appointment]);

$this->index();
}
catch (Throwable $e)
{
error_log( $e );
abort(500, 'Internal server error');
}
}

/**
* Sets a paid flag and paid intent for an appointment to track paid bookings.
*/
private function set_paid($appointment_hash, $payment_intent)
{
try
{
$manage_mode = TRUE;

$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);

if (empty($occurrences))
{
abort(404, 'Not Found');
}

$appointment = $occurrences[0];

$provider = $this->providers_model->find($appointment['id_users_provider']);

$customer = $this->customers_model->find($appointment['id_users_customer']);

$service = $this->services_model->find($appointment['id_services']);


$appointment['is_paid'] = 1;
$appointment['payment_intent'] = $payment_intent;
$this->appointments_model->only($appointment, [
'id',
'start_datetime',
'end_datetime',
'location',
'notes',
'color',
'is_unavailability',
'id_users_provider',
'id_users_customer',
'id_services',
'is_paid',
'payment_intent',
]);
$appointment_id = $this->appointments_model->save($appointment);
$appointment = $this->appointments_model->find($appointment_id);

$settings = [
'company_name' => setting('company_name'),
'company_link' => setting('company_link'),
'company_email' => setting('company_email'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format')
];

$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);

$this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);

$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);

return $appointment;
}
catch (Throwable $e)
{
error_log( $e );
abort(500, 'Internal server error');
}
}

}
Loading