Skip to content

Commit 41e436a

Browse files
author
Fernando González
committed
Adding Stripe link payments
1 parent d8634dc commit 41e436a

File tree

58 files changed

+1014
-80
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1014
-80
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The application is designed to be flexible enough so that it can handle any ente
5353
* Self hosted installation.
5454
* Translated user interface.
5555
* User community support.
56+
* Service payment by [Stripe Payment links](https://stripe.com/en-gb-es/payments/payment-links)
5657

5758
## Setup
5859

application/config/config.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,5 +465,16 @@
465465
$config['rate_limiting'] = TRUE;
466466

467467

468+
/*
469+
|--------------------------------------------------------------------------
470+
| Stripe Payment Configuration
471+
|--------------------------------------------------------------------------
472+
|
473+
| Declare some of the global config values of the Stripe Payments
474+
|
475+
*/
476+
477+
$config['stripe_api_key'] = Config::STRIPE_API_KEY;
478+
468479
/* End of file config.php */
469480
/* Location: ./application/config/config.php */

application/controllers/Booking.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public function index()
9393

9494
foreach ($available_providers as &$available_provider)
9595
{
96-
// Only expose the required provider data.
96+
// Only expose the required provider data.
9797

9898
$this->providers_model->only($available_provider, [
9999
'id',
@@ -170,7 +170,7 @@ public function index()
170170
return;
171171
}
172172

173-
// Make sure the appointment can still be rescheduled.
173+
// Make sure the appointment can still be rescheduled.
174174

175175
$start_datetime = strtotime($results[0]['start_datetime']);
176176

@@ -203,6 +203,7 @@ public function index()
203203
$provider = $this->providers_model->find($appointment['id_users_provider']);
204204
$customer = $this->customers_model->find($appointment['id_users_customer']);
205205
$customer_token = md5(uniqid(mt_rand(), TRUE));
206+
$is_paid = $appointment['is_paid'];
206207

207208
// Cache the token for 10 minutes.
208209
$this->cache->save('customer-token-' . $customer_token, $customer['id'], 600);
@@ -214,6 +215,7 @@ public function index()
214215
$appointment = NULL;
215216
$provider = NULL;
216217
$customer = NULL;
218+
$is_paid = 0;
217219
}
218220

219221
script_vars([
@@ -271,9 +273,11 @@ public function index()
271273
'grouped_timezones' => $grouped_timezones,
272274
'manage_mode' => $manage_mode,
273275
'customer_token' => $customer_token,
276+
'is_paid' => $is_paid == 1,
274277
'appointment_data' => $appointment,
275278
'provider_data' => $provider,
276279
'customer_data' => $customer,
280+
'company_email' => setting('company_email'),
277281
]);
278282

279283
$this->load->view('pages/booking');

application/controllers/Calendar.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ public function save_appointment()
256256
'id_users_provider',
257257
'id_users_customer',
258258
'id_services',
259+
'is_paid',
259260
]);
260261

261262
$appointment['id'] = $this->appointments_model->save($appointment);

application/controllers/Payment.php

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<?php defined('BASEPATH') or exit('No direct script access allowed');
2+
3+
/* ----------------------------------------------------------------------------
4+
* Easy!Appointments - Online Appointment Scheduler
5+
*
6+
* @package EasyAppointments
7+
* @author F.González <[email protected]>
8+
* @copyright Copyright (c) Alex Tselegidis
9+
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
10+
* @link https://easyappointments.org
11+
* @since v1.0.0
12+
* ---------------------------------------------------------------------------- */
13+
14+
/**
15+
* Payment confirmation controller.
16+
*
17+
* Handles the confirmation of a payment.
18+
*
19+
*
20+
* @package Controllers
21+
*/
22+
class Payment extends EA_Controller {
23+
/**
24+
* Booking constructor.
25+
*/
26+
public function __construct()
27+
{
28+
parent::__construct();
29+
30+
$this->load->model('appointments_model');
31+
$this->load->model('providers_model');
32+
$this->load->model('admins_model');
33+
$this->load->model('secretaries_model');
34+
$this->load->model('categories_model');
35+
$this->load->model('services_model');
36+
$this->load->model('customers_model');
37+
$this->load->model('settings_model');
38+
$this->load->model('consents_model');
39+
40+
$this->load->library('timezones');
41+
$this->load->library('synchronization');
42+
$this->load->library('notifications');
43+
$this->load->library('availability');
44+
$this->load->library('webhooks_client');
45+
46+
$this->load->driver('cache', ['adapter' => 'file']);
47+
}
48+
49+
/**
50+
* Render the payment confirmation page.
51+
*/
52+
public function index()
53+
{
54+
if ( ! is_app_installed())
55+
{
56+
redirect('installation');
57+
58+
return;
59+
}
60+
61+
$appointment = html_vars('appointment');
62+
63+
if (empty($appointment)) {
64+
abort(404, "Forbidden");
65+
} else {
66+
$manage_mode = TRUE;
67+
$company_name = setting('company_name');
68+
$company_logo = setting('company_logo');
69+
$company_color = setting('company_color');
70+
$google_analytics_code = setting('google_analytics_code');
71+
$matomo_analytics_url = setting('matomo_analytics_url');
72+
$date_format = setting('date_format');
73+
$time_format = setting('time_format');
74+
75+
$display_first_name = setting('display_first_name');
76+
$require_first_name = setting('require_first_name');
77+
$display_last_name = setting('display_last_name');
78+
$require_last_name = setting('require_last_name');
79+
$display_email = setting('display_email');
80+
$require_email = setting('require_email');
81+
$display_phone_number = setting('display_phone_number');
82+
$require_phone_number = setting('require_phone_number');
83+
$display_address = setting('display_address');
84+
$require_address = setting('require_address');
85+
$display_city = setting('display_city');
86+
$require_city = setting('require_city');
87+
$display_zip_code = setting('display_zip_code');
88+
$require_zip_code = setting('require_zip_code');
89+
$display_notes = setting('display_notes');
90+
$require_notes = setting('require_notes');
91+
$display_cookie_notice = setting('display_cookie_notice');
92+
$cookie_notice_content = setting('cookie_notice_content');
93+
$display_terms_and_conditions = setting('display_terms_and_conditions');
94+
$terms_and_conditions_content = setting('terms_and_conditions_content');
95+
$display_privacy_policy = setting('display_privacy_policy');
96+
$privacy_policy_content = setting('privacy_policy_content');
97+
98+
$theme = request('theme', setting('theme', 'default'));
99+
if (empty($theme) || ! file_exists(__DIR__ . '/../../assets/css/themes/' . $theme . '.min.css'))
100+
{
101+
$theme = 'default';
102+
}
103+
104+
$timezones = $this->timezones->to_array();
105+
$grouped_timezones = $this->timezones->to_grouped_array();
106+
$provider = $this->providers_model->find($appointment['id_users_provider']);
107+
$customer = $this->customers_model->find($appointment['id_users_customer']);
108+
109+
script_vars([
110+
'date_format' => $date_format,
111+
'time_format' => $time_format,
112+
'display_cookie_notice' => $display_cookie_notice,
113+
'display_any_provider' => setting('display_any_provider'),
114+
]);
115+
116+
html_vars([
117+
'theme' => $theme,
118+
'company_name' => $company_name,
119+
'company_logo' => $company_logo,
120+
'company_color' => $company_color === '#ffffff' ? '' : $company_color,
121+
'date_format' => $date_format,
122+
'time_format' => $time_format,
123+
'display_first_name' => $display_first_name,
124+
'display_last_name' => $display_last_name,
125+
'display_email' => $display_email,
126+
'display_phone_number' => $display_phone_number,
127+
'display_address' => $display_address,
128+
'display_city' => $display_city,
129+
'display_zip_code' => $display_zip_code,
130+
'display_notes' => $display_notes,
131+
'google_analytics_code' => $google_analytics_code,
132+
'matomo_analytics_url' => $matomo_analytics_url,
133+
'timezones' => $timezones,
134+
'grouped_timezones' => $grouped_timezones,
135+
'appointment' => $appointment,
136+
'provider' => $provider,
137+
'customer' => $customer,
138+
]);
139+
140+
$this->load->view('pages/payment');
141+
}
142+
}
143+
144+
/**
145+
* Validates Stripe payment and render confirmation screen for the appointment.
146+
*
147+
* This method sets a flag as paid for an appointment and call the "index" callback
148+
* to handle the page rendering.
149+
*
150+
* @param string $checkout_session_id Stripe session id.
151+
*/
152+
public function confirm(string $checkout_session_id)
153+
{
154+
try
155+
{
156+
$stripe_api_key = config('stripe_api_key');
157+
158+
$stripe = new \Stripe\StripeClient($stripe_api_key);
159+
160+
$session = $stripe->checkout->sessions->retrieve($checkout_session_id);
161+
162+
$appointment_hash = $session->client_reference_id;
163+
$payment_intent = $session->payment_intent;
164+
165+
$appointment = $this->set_paid($appointment_hash, $payment_intent);
166+
167+
html_vars(['appointment' => $appointment]);
168+
169+
$this->index();
170+
}
171+
catch (Throwable $e)
172+
{
173+
error_log( $e );
174+
abort(500, 'Internal server error');
175+
}
176+
}
177+
178+
/**
179+
* Sets a paid flag and paid intent for an appointment to track paid bookings.
180+
*/
181+
private function set_paid($appointment_hash, $payment_intent)
182+
{
183+
try
184+
{
185+
$manage_mode = TRUE;
186+
187+
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
188+
189+
if (empty($occurrences))
190+
{
191+
abort(404, 'Not Found');
192+
}
193+
194+
$appointment = $occurrences[0];
195+
196+
$provider = $this->providers_model->find($appointment['id_users_provider']);
197+
198+
$customer = $this->customers_model->find($appointment['id_users_customer']);
199+
200+
$service = $this->services_model->find($appointment['id_services']);
201+
202+
203+
$appointment['is_paid'] = 1;
204+
$appointment['payment_intent'] = $payment_intent;
205+
$this->appointments_model->only($appointment, [
206+
'id',
207+
'start_datetime',
208+
'end_datetime',
209+
'location',
210+
'notes',
211+
'color',
212+
'is_unavailability',
213+
'id_users_provider',
214+
'id_users_customer',
215+
'id_services',
216+
'is_paid',
217+
'payment_intent',
218+
]);
219+
$appointment_id = $this->appointments_model->save($appointment);
220+
$appointment = $this->appointments_model->find($appointment_id);
221+
222+
$settings = [
223+
'company_name' => setting('company_name'),
224+
'company_link' => setting('company_link'),
225+
'company_email' => setting('company_email'),
226+
'date_format' => setting('date_format'),
227+
'time_format' => setting('time_format')
228+
];
229+
230+
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);
231+
232+
$this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode);
233+
234+
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);
235+
236+
return $appointment;
237+
}
238+
catch (Throwable $e)
239+
{
240+
error_log( $e );
241+
abort(500, 'Internal server error');
242+
}
243+
}
244+
245+
}

application/language/arabic/translations_lang.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,11 @@
408408
$lang['status'] = 'Status';
409409
$lang['appointment_status_options'] = 'Appointment Status Options';
410410
$lang['appointment_status_options_info'] = 'Define a list of available appointment status options that can be used in the the calendar page (the first one will automatically become the default value).';
411+
$lang['service_payment_link'] = 'Payment link';
412+
$lang['service_payment_link_description'] = 'You can include the following variables: {$appointment_hash}, {$customer_email}. In order to confirm the payment use https://<YOUR_SERVER>/index.php/payment/confirm/{CHECKOUT_SESSION_ID} url as confirmation page in Stripe';
413+
$lang['appointment_payment_title'] = 'Payment details';
414+
$lang['appointment_payment_text'] = 'Click on the following link to proceed with payment';
415+
$lang['appointment_paymentPaid_text'] = 'Payment has been successfully completed';
416+
$lang['payment_intent'] = 'Stripe Payment Intent';
417+
$lang['service_paid_warning'] = 'It is not possible to change an already paid service. Please contact us at {$mail_link} for more information.';
411418
// End

application/language/bulgarian/translations_lang.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,11 @@
408408
$lang['status'] = 'Status';
409409
$lang['appointment_status_options'] = 'Appointment Status Options';
410410
$lang['appointment_status_options_info'] = 'Define a list of available appointment status options that can be used in the the calendar page (the first one will automatically become the default value).';
411+
$lang['service_payment_link'] = 'Payment link';
412+
$lang['service_payment_link_description'] = 'You can include the following variables: {$appointment_hash}, {$customer_email}. In order to confirm the payment use https://<YOUR_SERVER>/index.php/payment/confirm/{CHECKOUT_SESSION_ID} url as confirmation page in Stripe';
413+
$lang['appointment_payment_title'] = 'Payment details';
414+
$lang['appointment_payment_text'] = 'Click on the following link to proceed with payment';
415+
$lang['appointment_paymentPaid_text'] = 'Payment has been successfully completed';
416+
$lang['payment_intent'] = 'Stripe Payment Intent';
417+
$lang['service_paid_warning'] = 'It is not possible to change an already paid service. Please contact us at {$mail_link} for more information.';
411418
// End

application/language/catalan/translations_lang.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,11 @@
408408
$lang['status'] = 'Status';
409409
$lang['appointment_status_options'] = 'Appointment Status Options';
410410
$lang['appointment_status_options_info'] = 'Define a list of available appointment status options that can be used in the the calendar page (the first one will automatically become the default value).';
411+
$lang['service_payment_link'] = 'Payment link';
412+
$lang['service_payment_link_description'] = 'You can include the following variables: {$appointment_hash}, {$customer_email}. In order to confirm the payment use https://<YOUR_SERVER>/index.php/payment/confirm/{CHECKOUT_SESSION_ID} url as confirmation page in Stripe';
413+
$lang['appointment_payment_title'] = 'Payment details';
414+
$lang['appointment_payment_text'] = 'Click on the following link to proceed with payment';
415+
$lang['appointment_paymentPaid_text'] = 'Payment has been successfully completed';
416+
$lang['payment_intent'] = 'Stripe Payment Intent';
417+
$lang['service_paid_warning'] = 'It is not possible to change an already paid service. Please contact us at {$mail_link} for more information.';
411418
// End

application/language/chinese/translations_lang.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,11 @@
408408
$lang['status'] = 'Status';
409409
$lang['appointment_status_options'] = 'Appointment Status Options';
410410
$lang['appointment_status_options_info'] = 'Define a list of available appointment status options that can be used in the the calendar page (the first one will automatically become the default value).';
411+
$lang['service_payment_link'] = 'Payment link';
412+
$lang['service_payment_link_description'] = 'You can include the following variables: {$appointment_hash}, {$customer_email}. In order to confirm the payment use https://<YOUR_SERVER>/index.php/payment/confirm/{CHECKOUT_SESSION_ID} url as confirmation page in Stripe';
413+
$lang['appointment_payment_title'] = 'Payment details';
414+
$lang['appointment_payment_text'] = 'Click on the following link to proceed with payment';
415+
$lang['appointment_paymentPaid_text'] = 'Payment has been successfully completed';
416+
$lang['payment_intent'] = 'Stripe Payment Intent';
417+
$lang['service_paid_warning'] = 'It is not possible to change an already paid service. Please contact us at {$mail_link} for more information.';
411418
// End

application/language/czech/translations_lang.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,11 @@
408408
$lang['status'] = 'Status';
409409
$lang['appointment_status_options'] = 'Appointment Status Options';
410410
$lang['appointment_status_options_info'] = 'Define a list of available appointment status options that can be used in the the calendar page (the first one will automatically become the default value).';
411+
$lang['service_payment_link'] = 'Payment link';
412+
$lang['service_payment_link_description'] = 'You can include the following variables: {$appointment_hash}, {$customer_email}. In order to confirm the payment use https://<YOUR_SERVER>/index.php/payment/confirm/{CHECKOUT_SESSION_ID} url as confirmation page in Stripe';
413+
$lang['appointment_payment_title'] = 'Payment details';
414+
$lang['appointment_payment_text'] = 'Click on the following link to proceed with payment';
415+
$lang['appointment_paymentPaid_text'] = 'Payment has been successfully completed';
416+
$lang['payment_intent'] = 'Stripe Payment Intent';
417+
$lang['service_paid_warning'] = 'It is not possible to change an already paid service. Please contact us at {$mail_link} for more information.';
411418
// End

0 commit comments

Comments
 (0)