PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.5.2
LatePoint – Calendar Booking Plugin for Appointments and Events v5.5.2
5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / models / cart_model.php
latepoint / lib / models Last commit date
activity_model.php 3 months ago agent_meta_model.php 3 months ago agent_model.php 3 months ago booking_meta_model.php 3 months ago booking_model.php 3 months ago bundle_meta_model.php 3 months ago bundle_model.php 3 months ago cart_item_model.php 3 months ago cart_meta_model.php 3 months ago cart_model.php 3 months ago connector_model.php 3 months ago customer_meta_model.php 3 months ago customer_model.php 1 month ago invoice_model.php 3 months ago join_bundles_services_model.php 3 months ago location_category_model.php 3 months ago location_model.php 3 months ago meta_model.php 3 months ago model.php 3 months ago off_period_model.php 3 months ago order_intent_meta_model.php 3 months ago order_intent_model.php 3 months ago order_item_model.php 3 months ago order_meta_model.php 3 months ago order_model.php 3 months ago otp_model.php 3 months ago payment_request_model.php 3 months ago process_job_model.php 3 months ago process_model.php 3 months ago recurrence_model.php 3 months ago service_category_model.php 3 months ago service_meta_model.php 3 months ago service_model.php 3 months ago session_model.php 3 months ago settings_model.php 3 months ago step_settings_model.php 3 months ago transaction_intent_model.php 3 months ago transaction_model.php 3 months ago transaction_refund_model.php 3 months ago work_period_model.php 3 months ago
cart_model.php
590 lines
1 <?php
2 /*
3 * Copyright (c) 2023 LatePoint LLC. All rights reserved.
4 */
5
6 class OsCartModel extends OsModel {
7 public $items; // should NOT be set by default, it means they are not loaded, to avoid queries to DB
8
9 public $id,
10 $uuid,
11 $order_id,
12 $coupon_code = '',
13 $order_intent_id,
14 $payment_method,
15 $payment_portion,
16 $payment_time,
17 $payment_token,
18 $payment_processor,
19 $source_id = '',
20 $order_forced_customer_id = false, // only used for when you creating a cart from an order
21 $subtotal = 0,
22 $total = 0,
23 $coupon_discount = 0,
24 $tax_total = 0,
25 $updated_at,
26 $created_at;
27
28 function __construct( $id = false ) {
29 parent::__construct();
30 $this->table_name = LATEPOINT_TABLE_CARTS;
31
32 if ( $id ) {
33 $this->load_by_id( $id );
34 }
35 }
36
37
38 public function get_total() {
39
40 /**
41 * Get total of a cart
42 *
43 * @param {float} $total Total amount in database format 1999.0000
44 * @param {OsCartModel} $cart Cart that total is assessed on
45 * @returns {float} The filtered "total" amount
46 *
47 * @since 5.0.0
48 * @hook latepoint_cart_get_total
49 *
50 */
51 $amount = apply_filters( 'latepoint_cart_get_total', $this->total, $this );
52
53 return OsMoneyHelper::pad_to_db_format( $amount );
54 }
55
56 public function get_order_intent(): OsOrderIntentModel {
57 return new OsOrderIntentModel( $this->order_intent_id );
58 }
59
60
61 public function get_subtotal() {
62
63 /**
64 * Get subtotal of a cart
65 *
66 * @param {float} $subtotal Subtotal amount in database format 1999.0000
67 * @param {OsCartModel} $cart Cart that subtotal is assessed on
68 * @returns {float} The filtered "subtotal" amount
69 *
70 * @since 5.0.0
71 * @hook latepoint_cart_get_subtotal
72 *
73 */
74 $amount = apply_filters( 'latepoint_cart_get_subtotal', $this->subtotal, $this );
75
76 return OsMoneyHelper::pad_to_db_format( $amount );
77 }
78
79 public function get_coupon_discount() {
80
81 /**
82 * Get coupon discount of a cart
83 *
84 * @param {float} $discount_amount Coupon discount amount in database format 1999.0000
85 * @param {OsCartModel} $cart Cart that coupon discount is assessed on
86 * @returns {float} The filtered "coupon discount" amount
87 *
88 * @since 5.0.0
89 * @hook latepoint_cart_get_coupon_discount
90 *
91 */
92 $amount = apply_filters( 'latepoint_cart_get_coupon_discount', $this->coupon_discount, $this );
93
94 return OsMoneyHelper::pad_to_db_format( $amount );
95 }
96
97
98 public function get_tax_total() {
99
100 /**
101 * Get Total Tax amount of a cart
102 *
103 * @param {float} $tax_total Total amount of tax for a cart in database format 1999.0000
104 * @param {OsCartModel} $cart Cart that tax total is requested for
105 * @returns {float} The filtered "tax_total" amount
106 *
107 * @since 5.0.0
108 * @hook latepoint_cart_get_tax_total
109 *
110 */
111 $amount = apply_filters( 'latepoint_cart_get_tax_total', $this->tax_total, $this );
112
113 return OsMoneyHelper::pad_to_db_format( $amount );
114 }
115
116 public function get_coupon_code() {
117 /**
118 * Get coupon code of a cart
119 *
120 * @param {string} $coupon_code Coupon code
121 * @param {OsCartItemModel} $cart Cart Item that coupon code is requested for
122 * @returns {string} The filtered "coupon code" value
123 *
124 * @since 5.0.0
125 * @hook latepoint_cart_get_coupon_code
126 *
127 */
128 return apply_filters( 'latepoint_cart_get_coupon_code', $this->coupon_code, $this );
129 }
130
131
132 public function set_coupon_code( string $coupon_code ) {
133 $this->coupon_code = $coupon_code;
134 if ( ! $this->is_new_record() ) {
135 $this->update_attributes( [ 'coupon_code' => $coupon_code ] );
136 }
137 }
138
139
140 public function clear_coupon_code() {
141 $this->coupon_code = '';
142 if ( ! $this->is_new_record() ) {
143 $this->update_attributes( [ 'coupon_code' => '' ] );
144 }
145 }
146
147 /**
148 * @return OsBookingModel[]
149 */
150 public function get_bookings_from_cart_items(): array {
151 $cart_bookings = [];
152 foreach ( $this->get_items() as $cart_item ) {
153 if ( $cart_item->is_booking() ) {
154 $cart_bookings[ $cart_item->id ] = $cart_item->build_original_object_from_item_data();
155 }
156 }
157
158 return $cart_bookings;
159 }
160
161
162 /**
163 * @return OsBundleModel[]
164 */
165 public function get_bundles_from_cart_items(): array {
166 $cart_bundles = [];
167 foreach ( $this->get_items() as $cart_item ) {
168 if ( $cart_item->is_bundle() ) {
169 $cart_bundles[ $cart_item->id ] = $cart_item->build_original_object_from_item_data();
170 }
171 }
172
173 return $cart_bundles;
174 }
175
176 public function is_empty(): bool {
177 return ! $this->get_items();
178 }
179
180
181 public function delete_meta_by_key( $meta_key ) {
182 if ( $this->is_new_record() ) {
183 return false;
184 }
185
186 $meta = new OsCartMetaModel();
187
188 return $meta->delete_by_key( $meta_key, $this->id );
189 }
190
191 public function get_meta_by_key( $meta_key, $default = false ) {
192 if ( $this->is_new_record() ) {
193 return $default;
194 }
195
196 $meta = new OsCartMetaModel();
197
198 return $meta->get_by_key( $meta_key, $this->id, $default );
199 }
200
201 public function save_meta_by_key( $meta_key, $meta_value ) {
202 if ( $this->is_new_record() ) {
203 return false;
204 }
205
206 $meta = new OsCartMetaModel();
207
208 return $meta->save_by_key( $meta_key, $meta_value, $this->id );
209 }
210
211
212 public function clear(): void {
213 // remove current cart items
214 foreach ( $this->get_items() as $cart_item ) {
215 $cart_item->delete();
216 }
217 unset( $this->items ); // important to unset, to avoid db queries
218 }
219
220 /** ?
221 *
222 * @return OsCartItemModel[]
223 */
224 public function get_items(): array {
225 // only call DB when needed
226 if ( ! isset( $this->items ) && ! empty( $this->id ) ) {
227 $this->items = OsCartsHelper::get_items_for_cart_id( $this->id );
228 }
229
230 if ( empty( $this->items ) ) {
231 $this->items = [];
232 }
233
234 return $this->items;
235 }
236
237 /**
238 * @param array $rows_to_hide
239 *
240 * @return array[]
241 */
242 public function generate_price_breakdown_rows( array $rows_to_hide = [] ): array {
243 $rows = [
244 'before_subtotal' => [],
245 'subtotal' => [],
246 'after_subtotal' => [],
247 'total' => [],
248 'balance' => [],
249 ];
250
251 $items = $this->get_items();
252
253
254 // payments and balance have to always be recalculated, even if requested for existing booking
255 if ( ! in_array( 'balance', $rows_to_hide ) ) {
256 $balance_due_amount = $this->total;
257 $rows['balance'] = [
258 'label' => __( 'Balance Due', 'latepoint' ),
259 'raw_value' => OsMoneyHelper::pad_to_db_format( $balance_due_amount ),
260 'value' => OsMoneyHelper::format_price( $balance_due_amount, true, false ),
261 'style' => 'total',
262 ];
263 }
264
265 foreach ( $items as $item ) {
266 switch ( $item->variant ) {
267 case LATEPOINT_ITEM_VARIANT_BOOKING:
268 $booking = $item->build_original_object_from_item_data();
269
270 // recalculations are below this point
271 $service_row = [
272 'heading' => __( 'Service', 'latepoint' ),
273 'items' => [],
274 ];
275 $item_subtotal = OsBookingHelper::calculate_full_amount_for_service( $booking );
276 $service_row_item = [
277 'label' => $booking->service->name,
278 'raw_value' => OsMoneyHelper::pad_to_db_format( $item_subtotal ),
279 'value' => OsMoneyHelper::format_price( $item_subtotal, true, false ),
280 ];
281 $service_row['items'][] = $service_row_item;
282 $service_row = apply_filters( 'latepoint_price_breakdown_service_row_for_booking', $service_row, $booking );
283 $rows['before_subtotal'][] = $service_row;
284 break;
285 case LATEPOINT_ITEM_VARIANT_BUNDLE:
286 // TODO Merge somehow this case with the booking case as they are reusing a lot of code
287 // recalculations are below this point
288 $bundle = $item->build_original_object_from_item_data();
289 $service_row = [
290 'heading' => __( 'Bundle', 'latepoint' ),
291 'items' => [],
292 ];
293 $item_subtotal = OsBundlesHelper::calculate_full_amount_for_bundle( $bundle );
294 $service_row_item = [
295 'label' => $bundle->name,
296 'raw_value' => OsMoneyHelper::pad_to_db_format( $item_subtotal ),
297 'value' => OsMoneyHelper::format_price( $item_subtotal, true, false ),
298 ];
299 $service_row['items'][] = $service_row_item;
300 $service_row = apply_filters( 'latepoint_price_breakdown_service_row_for_bundle', $service_row, $bundle );
301 $rows['before_subtotal'][] = $service_row;
302 break;
303 }
304 }
305
306
307 if ( ! in_array( 'subtotal', $rows_to_hide ) ) {
308 $subtotal_amount = $this->subtotal;
309 $rows['subtotal'] = [
310 'label' => __( 'Sub Total', 'latepoint' ),
311 'style' => 'strong',
312 'raw_value' => OsMoneyHelper::pad_to_db_format( $subtotal_amount ),
313 'value' => OsMoneyHelper::format_price( $subtotal_amount, true, false ),
314 ];
315 }
316
317 if ( ! in_array( 'total', $rows_to_hide ) ) {
318 $total_amount = $this->total;
319 $rows['total'] = [
320 'label' => __( 'Total Price', 'latepoint' ),
321 'style' => in_array( 'balance', $rows_to_hide ) ? 'total' : 'strong',
322 'raw_value' => OsMoneyHelper::pad_to_db_format( $total_amount ),
323 'value' => OsMoneyHelper::format_price( $total_amount, true, false ),
324 ];
325 }
326
327 // filter only applies when recalculating rows, do not apply it to the existing data, since it has already ran
328 return apply_filters( 'latepoint_cart_price_breakdown_rows', $rows, $this, $rows_to_hide );
329 }
330
331
332 /**
333 * @param array $options
334 *
335 * @return mixed|void
336 *
337 * Returns amount to charge depending on a portion set in database format 1999.0000
338 *
339 */
340 public function amount_to_charge( array $options = [] ) {
341 $amount = ( $this->payment_portion == LATEPOINT_PAYMENT_PORTION_DEPOSIT ) ? $this->deposit_amount_to_charge( $options ) : $this->full_amount_to_charge( $options );
342
343 return apply_filters( 'latepoint_cart_amount_to_charge', $amount, $this, $options );
344 }
345
346
347 /**
348 * @param array $options
349 *
350 * @return mixed|void
351 *
352 * Returns deposit amount to charge in database format 1999.0000
353 *
354 */
355 public function deposit_amount_to_charge( array $options = [] ) {
356 $default_options = [
357 'apply_coupons' => false,
358 'apply_taxes' => false,
359 ];
360 $options = array_merge( $default_options, $options );
361 $amount = 0;
362 $items = $this->get_items();
363 if ( empty( $items ) ) {
364 return $amount;
365 }
366 foreach ( $items as $item ) {
367 $amount += $item->deposit_amount_to_charge( $options );
368 }
369
370 /**
371 * Filter deposit amount to charge on the cart object
372 *
373 * @param {float} $amount The amount to charge on the cart
374 * @param {OsCartModel} $cart Cart object that deposit amount is calculated on
375 * @param {array} $options Array of options that determine if taxes and coupons should be applied
376 * @returns {float} The filtered amount to charge on the cart
377 *
378 * @since 5.0.0
379 * @hook latepoint_cart_deposit_amount_to_charge
380 *
381 */
382 return apply_filters( 'latepoint_cart_deposit_amount_to_charge', $amount, $this, $options );
383 }
384
385 public function deposit_amount_to_charge_formatted( array $options = [] ) {
386 $amount = $this->deposit_amount_to_charge( $options );
387
388 return OsMoneyHelper::format_price( $amount, true, false );
389 }
390
391 /**
392 * @param array $options
393 *
394 * @return mixed|void
395 *
396 * Returns full amount to charge in database format 1999.0000
397 *
398 */
399 public function full_amount_to_charge( array $options = [] ) {
400 /**
401 * Get full amount to charge
402 *
403 * @param {float} $total Full amount to charge database format 1999.0000
404 * @param {OsCartModel} $cart Cart that total is assessed on
405 * @returns {float} The filtered full amount to charge
406 *
407 * @since 5.0.0
408 * @hook latepoint_cart_full_amount_to_charge
409 *
410 */
411 $amount = apply_filters( 'latepoint_cart_full_amount_to_charge', $this->get_total(), $this, $options );
412
413 return OsMoneyHelper::pad_to_db_format( $amount );
414 }
415
416
417 public function specs_calculate_amount_to_charge() {
418 if ( $this->payment_portion == LATEPOINT_PAYMENT_PORTION_DEPOSIT ) {
419 return $this->specs_calculate_deposit_amount_to_charge();
420 } else {
421 return $this->specs_calculate_full_amount_to_charge();
422 }
423 }
424
425 public function specs_calculate_full_amount_to_charge() {
426 return OsPaymentsHelper::convert_charge_amount_to_requirements( $this->get_total(), $this );
427 }
428
429 public function specs_calculate_deposit_amount_to_charge() {
430 return OsPaymentsHelper::convert_charge_amount_to_requirements( $this->deposit_amount_to_charge(), $this );
431 }
432
433 public function get_total_formatted() {
434 return OsMoneyHelper::format_price( $this->get_total(), true, false );
435 }
436
437 public function set_payment_portion() {
438 if ( ! empty( $this->payment_time ) ) {
439 if ( $this->payment_time == LATEPOINT_PAYMENT_TIME_LATER ) {
440 $this->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
441 } else {
442 $deposit_amount = $this->deposit_amount_to_charge();
443 $this->payment_portion = ( $deposit_amount > 0 ) ? LATEPOINT_PAYMENT_PORTION_DEPOSIT : LATEPOINT_PAYMENT_PORTION_FULL;
444 }
445 }
446 }
447
448 public function set_payment_processor() {
449 if ( empty( $this->payment_processor ) && ! empty( $this->payment_time ) && ! empty( $this->payment_method ) ) {
450 $enabled_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( $this->payment_time, $this->payment_method );
451 if ( count( $enabled_processors ) == 1 ) {
452 $this->payment_processor = array_key_first( $enabled_processors );
453 }
454 }
455 }
456
457 public function set_payment_time() {
458 if ( empty( $this->payment_time ) ) {
459 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
460 if ( count( $enabled_payment_times ) == 1 ) {
461 $this->payment_time = array_key_first( $enabled_payment_times );
462 }
463 }
464 }
465
466 public function set_payment_method() {
467 if ( ! empty( $this->payment_time ) ) {
468 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( $this->payment_time );
469 if ( count( $enabled_payment_methods ) == 1 ) {
470 $this->payment_method = array_key_first( $enabled_payment_methods );
471 }
472 }
473 }
474
475 public function set_singular_payment_attributes() {
476 $this->set_payment_time();
477 $this->set_payment_portion();
478 $this->set_payment_method();
479 $this->set_payment_processor();
480 }
481
482 public function remove_item( OsCartItemModel $item, bool $remove_connected_items = true ) {
483 if ( $item->id && $this->id == $item->cart_id ) {
484 if ( $remove_connected_items ) {
485 if ( ! empty( $item->connected_cart_item_id ) ) {
486 $cart_items = new OsCartItemModel();
487 $cart_items->delete_where( [ 'id' => $item->connected_cart_item_id ] );
488 }
489 // search for connected cart items
490 $cart_items = new OsCartItemModel();
491 $cart_items->delete_where( [ 'connected_cart_item_id' => $item->id ] );
492 }
493 $item->delete();
494 $this->items = OsCartsHelper::get_items_for_cart_id( $this->id );
495 }
496 $this->calculate_prices();
497
498 return true;
499 }
500
501 public function add_item( OsCartItemModel $item, bool $permanent = true, bool $calculate_prices = true ) {
502 if ( $permanent ) {
503 // save cart itself if not saved yet, since it's a permanent addition to cart
504 if ( empty( $this->id ) ) {
505 $this->save();
506 }
507 $item->cart_id = $this->id;
508 if ( $item->save() ) {
509 // we are doing this - to modify a copy of $items, to avoid modifying the getter's return value
510 $items = $this->get_items();
511 $items[] = $item;
512 $this->items = $items;
513 }
514 } else {
515 // we are doing this - to modify a copy of $items, to avoid modifying the getter's return value
516 $items = $this->get_items();
517 $items[] = $item;
518 $this->items = $items;
519 }
520 if ( $calculate_prices ) {
521 $this->calculate_prices();
522 }
523
524 return true;
525 }
526
527 public function calculate_prices() {
528
529 // calculate subtotal for all items
530 foreach ( $this->get_items() as $item ) {
531 $item->subtotal = $item->full_amount_to_charge();
532 $item->total = $item->subtotal;
533 }
534
535
536 // do cart subtotal
537 $this->subtotal = 0;
538 foreach ( $this->get_items() as $item ) {
539 $this->subtotal = $this->subtotal += $item->subtotal;
540 }
541 // do cart total
542 $this->total = 0;
543 foreach ( $this->get_items() as $item ) {
544 $this->total = $this->total += $item->total;
545 }
546
547
548 /**
549 * Triggers when cart prices are being calculated
550 *
551 * @param {OsCartModel} $cart Cart model for which prices are being generated
552 *
553 * @since 5.0.0
554 * @hook latepoint_cart_calculate_prices
555 *
556 */
557 do_action( 'latepoint_cart_calculate_prices', $this );
558 }
559
560
561 protected function allowed_params( $role = 'admin' ) {
562 $allowed_params = array(
563 'payment_method',
564 'payment_portion',
565 'payment_processor',
566 'payment_time',
567 'coupon_code',
568 'payment_token',
569 'source_id',
570 );
571
572 return $allowed_params;
573 }
574
575
576 protected function params_to_save( $role = 'admin' ) {
577 $params_to_save = array(
578 'id',
579 'uuid',
580 'order_intent_id',
581 'order_id',
582 'coupon_code',
583 'updated_at',
584 'created_at',
585 );
586
587 return $params_to_save;
588 }
589 }
590