PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.1.6
LatePoint – Calendar Booking Plugin for Appointments and Events v5.1.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 1 year ago agent_meta_model.php 1 year ago agent_model.php 1 year ago booking_meta_model.php 1 year ago booking_model.php 1 year ago bundle_model.php 1 year ago cart_item_model.php 1 year ago cart_meta_model.php 1 year ago cart_model.php 1 year ago connector_model.php 1 year ago customer_meta_model.php 1 year ago customer_model.php 1 year ago invoice_model.php 1 year ago join_bundles_services_model.php 1 year ago location_category_model.php 1 year ago location_model.php 1 year ago meta_model.php 1 year ago model.php 1 year ago order_intent_meta_model.php 1 year ago order_intent_model.php 1 year ago order_item_model.php 1 year ago order_meta_model.php 1 year ago order_model.php 1 year ago payment_request_model.php 1 year ago process_job_model.php 1 year ago process_model.php 1 year ago service_category_model.php 1 year ago service_meta_model.php 1 year ago service_model.php 1 year ago session_model.php 1 year ago settings_model.php 1 year ago step_settings_model.php 1 year ago transaction_intent_model.php 1 year ago transaction_model.php 1 year ago transaction_refund_model.php 1 year ago work_period_model.php 1 year ago
cart_model.php
578 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 = [ 'apply_coupons' => false, 'apply_taxes' => false ];
357 $options = array_merge( $default_options, $options );
358 $amount = 0;
359 $items = $this->get_items();
360 if ( empty( $items ) ) {
361 return $amount;
362 }
363 foreach ( $items as $item ) {
364 $amount += $item->deposit_amount_to_charge( $options );
365 }
366
367 /**
368 * Filter deposit amount to charge on the cart object
369 *
370 * @param {float} $amount The amount to charge on the cart
371 * @param {OsCartModel} $cart Cart object that deposit amount is calculated on
372 * @param {array} $options Array of options that determine if taxes and coupons should be applied
373 * @returns {float} The filtered amount to charge on the cart
374 *
375 * @since 5.0.0
376 * @hook latepoint_cart_deposit_amount_to_charge
377 *
378 */
379 return apply_filters( 'latepoint_cart_deposit_amount_to_charge', $amount, $this, $options );
380 }
381
382 public function deposit_amount_to_charge_formatted( array $options = [] ) {
383 $amount = $this->deposit_amount_to_charge( $options );
384
385 return OsMoneyHelper::format_price( $amount, true, false );
386 }
387
388 /**
389 * @param array $options
390 *
391 * @return mixed|void
392 *
393 * Returns full amount to charge in database format 1999.0000
394 *
395 */
396 public function full_amount_to_charge( array $options = [] ) {
397 /**
398 * Get full amount to charge
399 *
400 * @param {float} $total Full amount to charge database format 1999.0000
401 * @param {OsCartModel} $cart Cart that total is assessed on
402 * @returns {float} The filtered full amount to charge
403 *
404 * @since 5.0.0
405 * @hook latepoint_cart_full_amount_to_charge
406 *
407 */
408 $amount = apply_filters( 'latepoint_cart_full_amount_to_charge', $this->get_total(), $this, $options );
409
410 return OsMoneyHelper::pad_to_db_format( $amount );
411 }
412
413
414 public function specs_calculate_amount_to_charge() {
415 if ( $this->payment_portion == LATEPOINT_PAYMENT_PORTION_DEPOSIT ) {
416 return $this->specs_calculate_deposit_amount_to_charge();
417 } else {
418 return $this->specs_calculate_full_amount_to_charge();
419 }
420 }
421
422 public function specs_calculate_full_amount_to_charge() {
423 return OsPaymentsHelper::convert_charge_amount_to_requirements( $this->get_total(), $this );
424 }
425
426 public function specs_calculate_deposit_amount_to_charge() {
427 return OsPaymentsHelper::convert_charge_amount_to_requirements( $this->deposit_amount_to_charge(), $this );
428 }
429
430 public function get_total_formatted() {
431 return OsMoneyHelper::format_price( $this->get_total(), true, false );
432 }
433
434 public function set_payment_portion() {
435 if ( ! empty( $this->payment_time ) ) {
436 if ( $this->payment_time == LATEPOINT_PAYMENT_TIME_LATER ) {
437 $this->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
438 } else {
439 $deposit_amount = $this->deposit_amount_to_charge();
440 $this->payment_portion = ( $deposit_amount > 0 ) ? LATEPOINT_PAYMENT_PORTION_DEPOSIT : LATEPOINT_PAYMENT_PORTION_FULL;
441 }
442 }
443 }
444
445 public function set_payment_processor() {
446 if ( empty( $this->payment_processor ) && ! empty( $this->payment_time ) && ! empty( $this->payment_method ) ) {
447 $enabled_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( $this->payment_time, $this->payment_method );
448 if ( count( $enabled_processors ) == 1 ) {
449 $this->payment_processor = array_key_first( $enabled_processors );
450 }
451 }
452 }
453
454 public function set_payment_time() {
455 if ( empty( $this->payment_time ) ) {
456 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
457 if ( count( $enabled_payment_times ) == 1 ) {
458 $this->payment_time = array_key_first( $enabled_payment_times );
459 }
460 }
461 }
462
463 public function set_payment_method() {
464 if ( ! empty( $this->payment_time ) ) {
465 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( $this->payment_time );
466 if ( count( $enabled_payment_methods ) == 1 ) {
467 $this->payment_method = array_key_first( $enabled_payment_methods );
468 }
469 }
470 }
471
472 public function set_singular_payment_attributes() {
473 $this->set_payment_time();
474 $this->set_payment_portion();
475 $this->set_payment_method();
476 $this->set_payment_processor();
477 }
478
479 public function remove_item( OsCartItemModel $item ) {
480 if ( $item->id && $this->id == $item->cart_id ) {
481 $item->delete();
482 $this->items = OsCartsHelper::get_items_for_cart_id( $this->id );
483 }
484 $this->calculate_prices();
485
486 return true;
487 }
488
489 public function add_item( OsCartItemModel $item, bool $permanent = true, bool $calculate_prices = true ) {
490 if ( $permanent ) {
491 // save cart itself if not saved yet, since it's a permanent addition to cart
492 if ( empty( $this->id ) ) {
493 $this->save();
494 }
495 $item->cart_id = $this->id;
496 if ( $item->save() ) {
497 // we are doing this - to modify a copy of $items, to avoid modifying the getter's return value
498 $items = $this->get_items();
499 $items[] = $item;
500 $this->items = $items;
501 }
502 } else {
503 // we are doing this - to modify a copy of $items, to avoid modifying the getter's return value
504 $items = $this->get_items();
505 $items[] = $item;
506 $this->items = $items;
507 }
508 if ( $calculate_prices ) {
509 $this->calculate_prices();
510 }
511
512 return true;
513 }
514
515 public function calculate_prices() {
516
517 // calculate subtotal for all items
518 foreach ( $this->get_items() as $item ) {
519 $item->subtotal = $item->full_amount_to_charge();
520 $item->total = $item->subtotal;
521 }
522
523
524 // do cart subtotal
525 $this->subtotal = 0;
526 foreach ( $this->get_items() as $item ) {
527 $this->subtotal = $this->subtotal += $item->subtotal;
528 }
529 // do cart total
530 $this->total = 0;
531 foreach ( $this->get_items() as $item ) {
532 $this->total = $this->total += $item->total;
533 }
534
535
536 /**
537 * Triggers when cart prices are being calculated
538 *
539 * @param {OsCartModel} $cart Cart model for which prices are being generated
540 *
541 * @since 5.0.0
542 * @hook latepoint_cart_calculate_prices
543 *
544 */
545 do_action( 'latepoint_cart_calculate_prices', $this );
546
547 }
548
549
550 protected function allowed_params( $role = 'admin' ) {
551 $allowed_params = array(
552 'payment_method',
553 'payment_portion',
554 'payment_processor',
555 'payment_time',
556 'coupon_code',
557 'payment_token',
558 'source_id'
559 );
560
561 return $allowed_params;
562 }
563
564
565 protected function params_to_save( $role = 'admin' ) {
566 $params_to_save = array(
567 'id',
568 'uuid',
569 'order_intent_id',
570 'order_id',
571 'coupon_code',
572 'updated_at',
573 'created_at'
574 );
575
576 return $params_to_save;
577 }
578 }