PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.4.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.4.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
fluent-booking / app / Services / Integrations / FluentCart / Bootstrap.php

Bootstrap.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.4.0, at app/Services/Integrations/FluentCart/Bootstrap.php

486 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services\Integrations\FluentCart;
4
5 use FluentBooking\Framework\Foundation\Application;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Services\Renderer\Receipt\ThankYouRender;
9 use FluentBooking\App\Models\Booking;
10 use FluentBooking\App\Models\CalendarSlot;
11 use FluentBooking\App\Services\DateTimeHelper;
12 use FluentBooking\App\Services\Helper;
13 use FluentBooking\Framework\Support\Arr;
14
15 class Bootstrap
16 {
17 public function __construct(Application $app)
18 {
19 $app->router->group(function ($router) {
20 require_once __DIR__ . '/Http/cart_api.php';
21 });
22
23 $this->registerHooks();
24 }
25
26 public function registerHooks()
27 {
28 add_filter('fluent_booking/public_event_vars', [$this, 'maybePushPaymentVars'], 11, 2);
29 add_filter('fluent_booking/booking_data', [$this, 'maybePushPaymentData'], 10, 2);
30
31 add_action('fluent_cart/receipt/thank_you/after_order_items', [$this, 'maybeShowBookingDetailsInReceipt'], 10, 1);
32 add_action('fluent_booking/booking_meta_info_main_meta_cart', [$this, 'pushOrderDataToBookingView'], 10, 2);
33
34 // new API
35 add_action('fluent_cart/cart/line_item/line_meta', [$this, 'maybeRenderBookingInfoOnCartItem'], 10, 1);
36 // We are adding booking id to the order config after draft is created
37 add_action('fluent_booking/cart/booking_order_created', function ($eventData) {
38 $cart = Arr::get($eventData, 'cart');
39 if (empty($cart->checkout_data['fluent_booking_data'])) {
40 return;
41 }
42
43 $order = Arr::get($eventData, 'order');
44 $config = $order->config;
45 $bookingId = Arr::get($cart->checkout_data, 'fluent_booking_data.booking_id');
46 if ($bookingId) {
47 $config['fcal_booking_id'] = $bookingId;
48 $order->config = $config;
49 $order->save();
50 }
51 });
52
53 // after order confirmation
54 add_action('fluent_booking/cart/booking_order_completed', [$this, 'maybeScheduleBooking'], 10, 1);
55
56 // Offline and async payments settle after their cart is closed, so the
57 // action above never reaches us. This one fires on the payment itself.
58 add_action('fluent_cart/order_paid_done', [$this, 'reconcileBookingFromOrder'], 10, 1);
59
60 add_filter('fluent_cart/checkout_page_name_fields_schema', [$this, 'maybeFillSplitNameFields'], 10, 2);
61 }
62
63 public function maybeFillSplitNameFields($nameFields, $context)
64 {
65 $cart = Arr::get($context, 'cart');
66 if (!$cart || empty($cart->checkout_data['fluent_booking_data'])) {
67 return $nameFields;
68 }
69
70 if (isset($nameFields['billing_first_name']) && empty($nameFields['billing_first_name']['value'])) {
71 $nameFields['billing_first_name']['value'] = $cart->first_name;
72 }
73
74 if (isset($nameFields['billing_last_name']) && empty($nameFields['billing_last_name']['value'])) {
75 $nameFields['billing_last_name']['value'] = $cart->last_name;
76 }
77
78 return $nameFields;
79 }
80
81
82 public function maybePushPaymentVars($eventVars, CalendarSlot $calendarEvent)
83 {
84 if (!CartHelper::isEnabled($calendarEvent)) {
85 return $eventVars;
86 }
87
88 $paymentSettings = $calendarEvent->getPaymentSettings();
89 $defaultDuration = $calendarEvent->getDefaultDuration();
90
91 $eventVars['slot']['total_payment'] = CartHelper::getCartProductPrice($paymentSettings, $defaultDuration);
92
93 $isMultiEnabled = Arr::get($paymentSettings, 'multi_payment_enabled') === 'yes';
94 if ($calendarEvent->isMultiDurationEnabled() && $isMultiEnabled) {
95 $productIds = Arr::get($paymentSettings, 'multi_payment_cart_ids', []);
96 $eventVars['multi_payment_cart_ids'] = CartHelper::getCartProductPriceByDuration($productIds);
97 }
98
99 return $eventVars;
100 }
101
102 public function maybePushPaymentData($bookingData, $calendarEvent)
103 {
104 if (Arr::get($bookingData, 'source') != 'web') {
105 return $bookingData;
106 }
107
108 if (!CartHelper::isEnabled($calendarEvent)) {
109 return $bookingData;
110 }
111
112 $duration = Arr::get($bookingData, 'slot_minutes');
113
114 $variationId = CartHelper::getEventProductId($calendarEvent, $duration);
115 if (!$variationId) {
116 return $bookingData;
117 }
118
119 $product = CartHelper::getProduct($variationId);
120 if (!$product) {
121 return $bookingData;
122 }
123
124 $bookingData['source'] = 'cart';
125 $bookingData['payment_method'] = 'fluent_cart';
126 $bookingData['payment_status'] = 'pending';
127 $bookingData['status'] = 'pending';
128
129 add_filter('fluent_booking/booking_confirmation_response', function ($response, $booking) use ($product) {
130 if ($booking->status != 'pending' || $booking->source != 'cart') {
131 return $response;
132 }
133
134 $quantity = $booking->getMeta('quantity', 1);
135 $newItem = $product->toArray();
136
137 $instantCart = \FluentCart\App\Helpers\CartHelper::generateCartFromCustomVariation($newItem, $quantity);
138
139 $cartData = $instantCart->cart_data;
140 $cartData[0]['fcal_booking_id'] = $booking->id;
141 $instantCart->cart_data = $cartData;
142
143 $instantCart->cart_group = 'instant';
144 $instantCart->first_name = $booking->first_name;
145 $instantCart->last_name = $booking->last_name;
146 $instantCart->email = $booking->email;
147 $instantCart->user_id = $booking->person_user_id;
148 $instantCart->cart_hash = md5('booking_cart_' . wp_generate_uuid4() . time());
149 $instantCart->checkout_data = [
150 'is_locked' => 'yes',
151 'fluent_booking_data' => [
152 'booking_id' => $booking->id,
153 'target_variant_id' => $product->id
154 ],
155 '__on_success_actions__' => [
156 'fluent_booking/cart/booking_order_completed'
157 ],
158 '__after_draft_created_actions__' => [
159 'fluent_booking/cart/booking_order_created'
160 ],
161 '__cart_notices' => [
162
163 ]
164 ];
165
166 $instantCart->save();
167
168 $cartHash = $instantCart->cart_hash;
169 $checkoutUrl = add_query_arg(
170 [
171 'fct_cart_hash' => $cartHash
172 ],
173 (new StoreSettings())->getCheckoutPage()
174 );
175
176 $response['data']['redirect_to'] = $checkoutUrl;
177 $response['data']['redirect_message'] = __('You are redirecting to checkout page to complete the appointment.', 'fluent-booking');
178
179 do_action('fluent_booking/log_booking_activity', [
180 'booking_id' => $booking->id,
181 'status' => 'closed',
182 'type' => 'info',
183 'title' => __('Redirect to FluentCart checkout page', 'fluent-booking'),
184 'description' => __('User redirected to FluentCart checkout page to comeplete the order.', 'fluent-booking')
185 ]);
186
187 return $response;
188 }, 10, 2);
189
190 return $bookingData;
191 }
192
193 public function maybeRenderBookingInfoOnCartItem($eventInfo)
194 {
195 $item = Arr::get($eventInfo, 'item', []);
196
197 if (empty($item['fcal_booking_id'])) {
198 return;
199 }
200
201 $bookingId = $item['fcal_booking_id'];
202 $booking = Booking::find($bookingId);
203 if (!$booking || !$booking->calendar_event || $booking->status !== 'pending') {
204 return;
205 }
206
207 $bookingTime = $booking->getFullBookingDateTimeText($booking->person_time_zone, true) . ' (' . $booking->person_time_zone . ')';
208
209 if ($booking->calendar_event->allowMultiBooking()) {
210 $bookingTime = array_merge((array)$bookingTime, $booking->getOtherBookingTimes());
211 }
212 ?>
213 <div class="fct_item_title booking_meta">
214 <div class="fcal_meta_label"
215 style="font-weight: 600;"><?php echo esc_html__('Appointment:', 'fluent-booking'); ?></div>
216 <div class="fcal_meta_value"
217 style="font-weight: 400;"><?php echo esc_html(implode(', ', (array)$bookingTime)); ?></div>
218 </div>
219 <?php
220 }
221
222 public function maybeScheduleBooking($eventData)
223 {
224 $order = Arr::get($eventData, 'order');
225
226 if (empty($order)) {
227 return;
228 }
229
230 $cart = Arr::get($eventData, 'cart');
231
232 if (!$cart) {
233 return;
234 }
235
236 $targetProductId = Arr::get($cart->checkout_data, 'fluent_booking_data.target_variant_id', 0);
237
238 if (!$targetProductId) {
239 return;
240 }
241
242 $checkoutItem = array_filter($cart->cart_data, function ($item) use ($targetProductId) {
243 return Arr::get($item, 'object_id', 0) == $targetProductId;
244 });
245
246 if (!$checkoutItem) {
247 return;
248 }
249
250 $this->scheduleBookingForOrder($order);
251 }
252
253 /**
254 * The same work, reached without a cart.
255 *
256 * __on_success_actions__ only run while the cart is open, and offline and
257 * async orders close theirs before the payment settles - leaving the
258 * booking pending for good. The order keeps the booking id either way.
259 *
260 * @param array $eventData
261 */
262 public function reconcileBookingFromOrder($eventData)
263 {
264 $order = Arr::get($eventData, 'order');
265
266 if (empty($order)) {
267 return;
268 }
269
270 $this->scheduleBookingForOrder($order);
271 }
272
273 /**
274 * @param \FluentCart\App\Models\Order $order
275 */
276 protected function scheduleBookingForOrder($order)
277 {
278 $bookingId = Arr::get($order->config, 'fcal_booking_id', '');
279
280 if (empty($bookingId)) {
281 return;
282 }
283
284 $booking = Booking::find($bookingId);
285 if (!$booking || $booking->source_id) {
286 return;
287 }
288
289 $calendarEvent = $booking->calendar_event;
290 if (!CartHelper::isEnabled($calendarEvent)) {
291 return;
292 }
293
294 if ($booking->status != 'pending') {
295 do_action('fluent_booking/log_booking_activity', [
296 'booking_id' => $booking->id,
297 'status' => 'closed',
298 'type' => 'success',
299 'title' => __('Cart: Booking status could not be changed', 'fluent-booking'),
300 'description' => sprintf(
301 /* translators: Notification message when the booking status could not be changed due to its current status. %1$s is the status, %2$s and %3$s are the HTML link tags for "View Order" */
302 __('Booking status could not be changed as it is in %1$s status. %2$sView Order%3$s', 'fluent-booking'),
303 $booking->status,
304 '<a target="_blank" href="' . $order->getViewUrl('admin') . '">',
305 '</a>')
306 ]);
307 return;
308 }
309
310 $isRequireConfirmation = $calendarEvent->isConfirmationRequired($booking->start_time, $booking->created_at);
311
312 if (!$isRequireConfirmation) {
313 $booking->status = 'scheduled';
314 }
315
316 $booking->payment_status = 'paid';
317 $booking->source_id = $order->id;
318 $booking->save();
319
320 $this->maybeUpdateChildBookings($booking, $calendarEvent, $order);
321
322 do_action('fluent_booking/log_booking_activity', CartHelper::getSuccessLog($booking->id, $order));
323
324 $bookingData = [
325 'name' => $booking->first_name . ' ' . $booking->last_name,
326 'email' => $booking->email,
327 'phone' => $booking->phone
328 ];
329
330 $order->addLog(
331 __('Booking Confirmation', 'fluent-booking'),
332 sprintf(
333 /* translators: Order log message for the change of booking status to scheduled. %1$s is the booking ID, %2$s is the booking status, %3$s is the date/time+timezone, %4$s is a link open tag, %5$s is the link close tag */
334 __('Booking #%1$s status changed to %2$s at %3$s. %4$sView Booking%5$s', 'fluent-booking'),
335 $booking->id,
336 $booking->status,
337 $booking->getFullBookingDateTimeText($booking->calendar->author_timezone, true) . ' (' . $booking->calendar->author_timezone . ')',
338 '<a target="_blank" href="' . esc_url(Helper::getAppBaseUrl('scheduled-events?period=upcoming&booking_id=' . $booking->id)) . '">',
339 '</a>'
340 ),
341 'info',
342 'FluentBooking'
343 );
344
345 // this pre hook is for early actions that require for remote calendars and locations
346 do_action('fluent_booking/pre_after_booking_' . $booking->status, $booking, $booking->calendar_event, $bookingData);
347
348 do_action('fluent_booking/after_booking_' . $booking->status, $booking, $booking->calendar_event, $bookingData);
349 }
350
351 public function maybeShowBookingDetailsInReceipt($event)
352 {
353 $order = Arr::get($event, 'order');
354 $bookingId = Arr::get($order->config, 'fcal_booking_id', '');
355
356 if (empty($order) || empty($bookingId)) {
357 return;
358 }
359
360 $booking = Booking::find($bookingId);
361 if (!$booking) {
362 return;
363 }
364
365 $redirectUrl = $booking->getRedirectUrlWithQuery();
366
367 if ($redirectUrl && in_array($order->payment_status, Status::getOrderPaymentSuccessStatuses()) && Arr::get($event, 'is_first_time', false)) {
368 add_action('wp_footer', function () use ($redirectUrl) {
369 ?>
370 <script type="text/javascript">
371 document.addEventListener('DOMContentLoaded', function () {
372 window.location.href = "<?php echo esc_url($redirectUrl); ?>";
373 });
374 </script>
375 <?php
376 });
377 }
378 ?>
379 <style>
380 .fcal_receipt_booking_details h5 {
381 margin: 0 0 10px;
382 border-bottom: 1px solid #dee2e6;
383 padding-bottom: 5px;
384 font-size: 15px;
385 font-weight: 700;
386 color: #495057;
387 }
388
389 .fcal_receipt_booking_info p {
390 margin: 0 0 4px;
391 color: #111111;
392 font-size: 14px;
393 }
394 </style>
395 <div class="fcal_receipt_booking_details">
396 <h5><?php esc_html_e('Booking Details', 'fluent-booking'); ?></h5>
397 <div class="fcal_receipt_booking_info">
398 <p>
399 <b><?php esc_html_e('Meeting Info:', 'fluent-booking'); ?></b> <?php echo esc_html($booking->getBookingTitle()); ?>
400 </p>
401 <p>
402 <b><?php esc_html_e('Date & Time:', 'fluent-booking'); ?></b> <?php echo esc_html(implode(', ', $booking->getAllBookingShortTimes($booking->person_time_zone))); ?>
403 (<?php echo esc_html($booking->person_time_zone); ?>)
404 </p>
405 <p>
406 <b><?php esc_html_e('Status:', 'fluent-booking'); ?></b> <?php echo esc_html(ucfirst($booking->status)); ?>
407 </p>
408 <p>
409 <a href="<?php echo esc_url($booking->getConfirmationUrl()); ?>"><?php esc_html_e('View Full Meeting Details', 'fluent-booking'); ?></a>
410 </p>
411 </div>
412 </div>
413 <?php
414 }
415
416 public function pushOrderDataToBookingView($meta, $booking)
417 {
418 $orderId = $booking->source_id;
419 if (!$orderId) {
420 return $meta;
421 }
422
423 $order = CartHelper::getOrder($orderId);
424 if (!$order) {
425 return $meta;
426 }
427
428 // Get FluentCart Order Summary as html
429 ob_start();
430 printf(
431 /* translators: 1: order number 2: order date 3: order status */
432 esc_html__('Order %1$s was placed on %2$s and is currently %3$s.', 'fluent-booking'),
433 '<span class="order-number"><a href="' . esc_url($order->getViewUrl('admin')) . '">' . '#' . esc_html($order->id) . '</a></span>',
434 '<span class="order-date">' . esc_html(DateTimeHelper::formatToLocale($order->created_at, 'date_time')) . '</span>',
435 '<span class="order-status">' . esc_html($order->status) . '</span>'
436 );
437
438 (new ThankYouRender(['order' => $order]))->renderOrderItems();
439
440 $orderSummary = ob_get_clean();
441
442 $meta[] = [
443 'id' => 'cart-order-summary',
444 'title' => __('Order Summary', 'fluent-booking'),
445 'content' => $orderSummary
446 ];
447
448 return $meta;
449 }
450
451 private function maybeUpdateChildBookings($booking, $calendarEvent, $order)
452 {
453 $childBookingIds = Booking::where('parent_id', $booking->id)
454 ->where('status', 'pending')
455 ->pluck('id')
456 ->toArray();
457
458 if (!$childBookingIds) {
459 return;
460 }
461
462 foreach ($childBookingIds as $childBookingId) {
463 $childBooking = Booking::find($childBookingId);
464
465 if (!$childBooking) {
466 continue;
467 }
468
469 $childBooking->update([
470 'status' => $booking->status,
471 'payment_status' => $booking->payment_status,
472 ]);
473
474 if ($booking->status == 'scheduled') {
475 do_action('fluent_booking/pre_after_booking_scheduled', $childBooking, $calendarEvent, $childBooking);
476
477 $childBooking = Booking::with(['calendar_event', 'calendar'])->find($childBooking->id);
478
479 do_action('fluent_booking/after_booking_scheduled', $childBooking, $calendarEvent, $childBooking);
480 }
481
482 do_action('fluent_booking/log_booking_activity', CartHelper::getSuccessLog($childBookingId, $order));
483 }
484 }
485 }
486