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.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 / order_intent_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
order_intent_model.php
538 lines
1 <?php
2 /*
3 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
4 */
5
6 class OsOrderIntentModel extends OsModel {
7 var $id,
8 $intent_key,
9 $customer_id,
10 $booking_form_page_url,
11 $cart_items_data,
12 $restrictions_data,
13 $presets_data,
14 $payment_data = '',
15 $payment_data_arr,
16 $other_data,
17 $charge_amount,
18 $specs_charge_amount,
19 $coupon_code,
20 $coupon_discount,
21 $total,
22 $subtotal,
23 $price_breakdown,
24 $order_id,
25 $tax_total,
26 $status,
27 $updated_at,
28 $created_at;
29
30 function __construct( $id = false ) {
31 parent::__construct();
32 $this->table_name = LATEPOINT_TABLE_ORDER_INTENTS;
33
34 if ( $id ) {
35 $this->load_by_id( $id );
36 }
37 }
38
39
40 protected function params_to_sanitize() {
41 return [
42 'charge_amount' => 'money',
43 'total' => 'money',
44 'subtotal' => 'money',
45 'tax_total' => 'money',
46 ];
47 }
48
49
50 public function delete_meta_by_key( $meta_key ) {
51 if ( $this->is_new_record() ) {
52 return false;
53 }
54
55 $meta = new OsOrderIntentMetaModel();
56
57 return $meta->delete_by_key( $meta_key, $this->id );
58 }
59
60 public function get_meta_by_key( $meta_key, $default = false ) {
61 if ( $this->is_new_record() ) {
62 return $default;
63 }
64
65 $meta = new OsOrderIntentMetaModel();
66
67 return $meta->get_by_key( $meta_key, $this->id, $default );
68 }
69
70 public function save_meta_by_key( $meta_key, $meta_value ) {
71 if ( $this->is_new_record() ) {
72 return false;
73 }
74
75 $meta = new OsOrderIntentMetaModel();
76
77 return $meta->save_by_key( $meta_key, $meta_value, $this->id );
78 }
79
80 public function get_customer() {
81 if ( $this->customer_id ) {
82 if ( ! isset( $this->customer ) || ( isset( $this->customer ) && ( $this->customer->id != $this->customer_id ) ) ) {
83 $this->customer = new OsCustomerModel( $this->customer_id );
84 }
85 } else {
86 $this->customer = new OsCustomerModel();
87 }
88
89 return $this->customer;
90 }
91
92 public function build_cart_object(): OsCartModel {
93 $cart = new OsCartModel();
94 $cart->total = $this->total;
95 $cart->subtotal = $this->subtotal;
96 $cart->coupon_code = $this->coupon_code;
97 $cart->coupon_discount = $this->coupon_discount;
98 $cart->tax_total = $this->tax_total;
99
100 // add items from intent
101 $intent_cart_items = json_decode( $this->cart_items_data, true );
102 foreach ( $intent_cart_items as $cart_item_data ) {
103 $cart->add_item( OsCartsHelper::create_cart_item_from_item_data( $cart_item_data ), false );
104 }
105
106 // restore payment info
107 $payment_data = json_decode( $this->payment_data, true );
108 $cart->payment_method = $payment_data['method'];
109 $cart->payment_time = $payment_data['time'];
110 $cart->payment_portion = $payment_data['portion'];
111 $cart->payment_token = $payment_data['token'];
112 $cart->payment_processor = $payment_data['processor'];
113
114 return $cart;
115 }
116
117 public function get_payment_data_value( string $key ): string {
118 if ( ! isset( $this->payment_data_arr ) ) {
119 $this->payment_data_arr = json_decode( $this->payment_data, true );
120 }
121
122 return $this->payment_data_arr[ $key ] ?? '';
123 }
124
125 public function set_payment_data_value( string $key, string $value, bool $save = true ) {
126 $this->payment_data_arr = json_decode( $this->payment_data, true );
127 $this->payment_data_arr[ $key ] = $value;
128 $this->payment_data = wp_json_encode( $this->payment_data_arr );
129 if ( $save ) {
130 $this->update_attributes( [ 'payment_data' => $this->payment_data ] );
131 }
132 }
133
134 public function is_bookable(): bool {
135 $cart = $this->build_cart_object();
136 // loop items and check if bookings are still available
137 foreach ( $cart->get_items() as $cart_item ) {
138 switch ( $cart_item->variant ) {
139 case LATEPOINT_ITEM_VARIANT_BOOKING:
140 $booking = $cart_item->build_original_object_from_item_data();
141 if ( ! $booking->is_bookable( false ) ) {
142 $this->add_error( 'send_to_step', $booking->get_error_messages(), 'booking__datepicker' );
143
144 return false;
145 }
146 break;
147 case LATEPOINT_ITEM_VARIANT_BUNDLE:
148 break;
149 }
150 }
151
152 return true;
153 }
154
155 public function is_processing(): bool {
156 return $this->status == LATEPOINT_ORDER_INTENT_STATUS_PROCESSING;
157 }
158
159 public function convert_to_order() {
160 if($this->is_converted()){
161 return $this->order_id;
162 }
163
164 if($this->is_processing()){
165 $this->add_error( 'order_error', 'Can not convert to order, because order intent conversion is being processed' );
166 return false;
167 }
168
169 $this->mark_as_processing();
170
171 try {
172
173 // process is cart -> order intent -> order
174 if ( ! $this->is_bookable() ) {
175 $this->mark_as_new();
176 return false;
177 }
178
179 // process payment if there is amount due
180 $transaction = OsPaymentsHelper::process_payment_for_order_intent( $this );
181
182 // payment processing can take a while, make sure to check if the intent wasn't converted already in the meantime
183 $converted_order_id = OsOrderIntentHelper::is_converted( $this->id );
184 if ( $converted_order_id ) {
185 $order = new OsOrderModel($converted_order_id);
186 $this->mark_as_converted($order);
187 return $converted_order_id;
188 }
189
190 if ( $this->get_error() ) {
191 OsDebugHelper::log( 'Error converting intent to an order', 'order_error', $this->get_error_messages() );
192
193 $this->mark_as_new();
194 return false;
195 }
196
197
198 $cart_from_intent = $this->build_cart_object();
199
200 $order = new OsOrderModel();
201 $order->customer_id = $this->customer_id;
202 $order->total = $this->total;
203 $order->subtotal = $this->subtotal;
204 $order->coupon_code = $this->coupon_code;
205 $order->coupon_discount = $this->coupon_discount;
206 $order->tax_total = $this->tax_total;
207 $order->source_url = $this->booking_form_page_url;
208 $order->customer_comment = $this->customer->notes;
209 $order_initial_payment_data_arr = json_decode( $this->payment_data, true );
210 $order_initial_payment_data_arr['charge_amount'] = $this->charge_amount;
211 $order->initial_payment_data = wp_json_encode($order_initial_payment_data_arr);
212 // order's price breakdown should only hold cart items, and never holds total, subtotal, balance variables because those are stored on order model itself and/or generated on the fly
213 $order->price_breakdown = wp_json_encode( $cart_from_intent->generate_price_breakdown_rows(['balance', 'total', 'subtotal']));
214
215 /**
216 * Filters order right before it's about to be saved when converting from an order intent
217 *
218 * @param {OsOrderModel} $order Order to be filtered
219 * @returns {OsOrderModel} The filtered order
220 *
221 * @since 5.0.0
222 * @hook latepoint_before_order_save_from_order_intent
223 *
224 */
225 $order = apply_filters( 'latepoint_before_order_save_from_order_intent', $order );
226
227
228 if ( $order->save() ) {
229 $this->mark_as_converted( $order );
230 OsInvoicesHelper::create_invoices_for_new_order($order);
231
232
233 foreach ( $cart_from_intent->get_items() as $cart_item ) {
234 $order_item = OsOrdersHelper::create_order_item_from_cart_item( $cart_item );
235 $order_item->order_id = $order->id;
236 $order_item->save();
237 }
238
239 if ( $transaction ) {
240 $transaction->order_id = $order->id;
241 $invoice = OsInvoicesHelper::get_matching_invoice_for_transaction($transaction);
242 if(!$invoice->is_new_record()) $transaction->invoice_id = $invoice->id;
243 if ( $transaction->save() ) {
244
245 /**
246 * Transaction was created
247 *
248 * @param {OsTransactionModel} $transaction instance of transaction model that was created
249 *
250 * @since 5.1.0
251 * @hook latepoint_transaction_created
252 *
253 */
254 do_action( 'latepoint_transaction_created', $transaction );
255 if(!$invoice->is_new_record()){
256 $old_invoice = clone $invoice;
257 $invoice->update_attributes(['status' => LATEPOINT_INVOICE_STATUS_PAID]);
258 /**
259 * Invoice was updated
260 *
261 * @param {OsInvoiceModel} $invoice instance of invoice model after it was updated
262 * @param {OsInvoiceModel} $old_invoice instance of invoice model before it was updated
263 *
264 * @since 5.1.0
265 * @hook latepoint_invoice_updated
266 *
267 */
268 do_action( 'latepoint_invoice_updated', $invoice, $old_invoice );
269 // update other invoices with this paid amount, for example if we charge a deposit - then this transaction should also be reflected in draft invoices for the remaining amount that were created earlier
270 $other_invoices = new OsInvoiceModel();
271 $other_invoices = $other_invoices->where(['status' => LATEPOINT_INVOICE_STATUS_DRAFT, 'order_id' => $order->id])->get_results_as_models();
272 if($other_invoices){
273 foreach($other_invoices as $invoice){
274 $data = json_decode($invoice->data, true);
275 $data['totals']['payments'] = $order->get_total_amount_paid_from_transactions(true);
276 $invoice->update_attributes(['data' => json_encode($data)]);
277 }
278 }
279
280 }
281
282 } else {
283 OsDebugHelper::log( 'Error creating transaction', 'transaction_error', $transaction->get_error_messages() );
284 }
285 }
286 $order_bookings = $order->get_bookings_from_order_items(true);
287 if ( $order_bookings ) {
288 foreach ( $order_bookings as $order_item_id => $order_booking ) {
289 $order_booking->order_item_id = $order_item_id;
290 $order_booking->customer_id = $order->customer_id;
291 $order_booking->end_time = $order_booking->calculate_end_time();
292 $order_booking->end_date = $order_booking->calculate_end_date();
293 $order_booking->set_utc_datetimes();
294 $service = new OsServiceModel( $order_booking->service_id );
295 $order_booking->buffer_before = $service->buffer_before;
296 $order_booking->buffer_after = $service->buffer_after;
297 $order_booking->customer_comment = $order->customer->notes;
298 if ( $order_booking->save() ) {
299
300 /**
301 * Booking was created
302 *
303 * @param {OsBookingModel} $booking instance of booking model that was created
304 *
305 * @since 5.0.0
306 * @hook latepoint_booking_created
307 *
308 */
309 do_action( 'latepoint_booking_created', $order_booking );
310 // set booking id to the one that was created for item data property
311 $order_item = new OsOrderItemModel( $order_item_id );
312 $item_data = json_decode( $order_item->item_data, true );
313 $item_data['id'] = $order_booking->id;
314 $order_item->update_attributes( [ 'item_data' => wp_json_encode( $item_data ) ] );
315 } else {
316 OsDebugHelper::log( 'Unable to save booking', 'booking_save_error', $order_booking->get_error_messages() );
317 }
318 }
319 }
320 // update connected cart with created order id
321 $carts = new OsCartModel();
322 $carts = $carts->where( [ 'order_intent_id' => $this->id ] )->get_results_as_models();
323 if ( ! empty( $carts ) ) {
324 foreach ( $carts as $cart ) {
325 $cart->order_id = $order->id;
326 $cart->save();
327 }
328 }
329 $order->determine_payment_status();
330 // update with latest info
331 $order->get_items(true);
332
333 /**
334 * Order was created
335 *
336 * @param {OsOrderModel} $order instance of order model that was created
337 *
338 * @since 5.0.0
339 * @hook latepoint_order_created
340 *
341 */
342 do_action( 'latepoint_order_created', $order );
343
344 return $order->id;
345 } else {
346 $this->add_error( 'order_error', $order->get_error_messages() );
347
348 $this->mark_as_new();
349 return false;
350 }
351 } catch ( Exception $e ) {
352 $this->mark_as_new();
353 // translators: %s is the error description
354 $this->add_error( 'order_error', sprintf(__('Error: %s', 'latepoint'), $e->getMessage() ));
355 OsDebugHelper::log( 'Error converting intent to an order', 'order_error', $e->getMessage() );
356 return false;
357 }
358 }
359
360 public function get_by_intent_key( $intent_key ) {
361 return $this->where( [ 'intent_key' => $intent_key ] )->set_limit( 1 )->get_results_as_models();
362 }
363
364 public function mark_as_converted( OsOrderModel $order ) {
365 if ( empty( $order->id ) ) {
366 return false;
367 }
368
369 $this->update_attributes( [ 'order_id' => $order->id, 'status' => LATEPOINT_ORDER_INTENT_STATUS_CONVERTED ] );
370 /**
371 * Order intent is converted to order
372 *
373 * @param {OsOrderIntentModel} $order_intent Instance of order intent model that has been converted to order
374 * @param {OsOrderModel} $order Instance of order model that order intent was converted to
375 *
376 * @since 5.0.0
377 * @hook latepoint_order_intent_converted
378 *
379 */
380 do_action( 'latepoint_order_intent_converted', $this, $order );
381 }
382
383 public function mark_as_processing() {
384 $this->update_attributes( [ 'status' => LATEPOINT_ORDER_INTENT_STATUS_PROCESSING ] );
385 /**
386 * Order intent is marked as processing
387 *
388 * @param {OsOrderIntentModel} $order_intent Instance of order intent model that has started processing
389 *
390 * @since 5.0.0
391 * @hook latepoint_order_intent_processing
392 *
393 */
394 do_action( 'latepoint_order_intent_processing', $this );
395 }
396
397 public function mark_as_new() {
398 $this->update_attributes( [ 'status' => LATEPOINT_ORDER_INTENT_STATUS_NEW ] );
399 /**
400 * Order intent is marked as new
401 *
402 * @param {OsOrderIntentModel} $order_intent Instance of order intent model that is being marked as new
403 *
404 * @since 5.0.0
405 * @hook latepoint_order_intent_new
406 *
407 */
408 do_action( 'latepoint_order_intent_new', $this );
409 }
410
411 // Determines if order intent has been converted into a order already
412 public function is_converted() : bool {
413 if ( empty( $this->order_id ) ) {
414 return false;
415 } else {
416 return true;
417 }
418 }
419
420 public function generate_data_vars(): array {
421 $vars = [
422 'id' => $this->id,
423 'intent_key' => $this->intent_key,
424 'customer_id' => $this->customer_id,
425 'booking_form_page_url' => $this->booking_form_page_url,
426 'cart_items_data' => !empty($this->cart_items_data) ? json_decode( $this->cart_items_data, true ) : [],
427 'restrictions_data' => !empty($this->restrictions_data) ? json_decode( $this->restrictions_data, true ) : [],
428 'presets_data' => !empty($this->presets_data) ? json_decode( $this->presets_data, true ) : [],
429 'payment_data' => !empty($this->payment_data) ? json_decode( $this->payment_data, true ) : [],
430 'other_data' => !empty($this->other_data) ? json_decode( $this->other_data, true ) : [],
431 'order_id' => $this->order_id,
432 'coupon_code' => $this->coupon_code,
433 'coupon_discount' => $this->coupon_discount,
434 'tax_total' => $this->tax_total,
435 'updated_at' => $this->updated_at,
436 'created_at' => $this->created_at,
437 ];
438
439 return $vars;
440 }
441
442 public function get_page_url_with_intent() {
443 $booking_page_url = $this->booking_form_page_url;
444 $existing_var_position = strpos( $booking_page_url, 'latepoint_order_intent_key=' );
445 if ( $existing_var_position === false ) {
446 // no intent variable in url
447 $question_position = strpos( $booking_page_url, '?' );
448 if ( $question_position === false ) {
449 // no ?query params
450 $hash_position = strpos( $booking_page_url, '#' );
451 if ( $hash_position === false ) {
452 // no hashtag in url
453 $booking_page_url = $booking_page_url . '?latepoint_order_intent_key=' . $this->intent_key;
454 } else {
455 // hashtag in url and no ?query, prepend the hashtag with query
456 $booking_page_url = substr_replace( $booking_page_url, '?latepoint_order_intent_key=' . $this->intent_key . '#', $hash_position, 1 );
457 }
458 } else {
459 // ?query string exists, add intent key to it
460 $booking_page_url = substr_replace( $booking_page_url, '?latepoint_order_intent_key=' . $this->intent_key . '&', $question_position, 1 );
461 }
462 } else {
463 // intent key variable exist in url
464 preg_match( '/latepoint_order_intent_key=([\d,\w]*)/', $booking_page_url, $matches );
465 if ( isset( $matches[1] ) ) {
466 $booking_page_url = str_replace( 'latepoint_order_intent_key=' . $matches[1], 'latepoint_order_intent_key=' . $this->intent_key, $booking_page_url );
467 }
468 }
469
470 return $booking_page_url;
471 }
472
473
474 protected function before_create() {
475 if ( empty( $this->intent_key ) ) {
476 $this->intent_key = bin2hex( openssl_random_pseudo_bytes( 10 ) );
477 }
478 if ( empty( $this->status ) ) {
479 $this->status = LATEPOINT_ORDER_INTENT_STATUS_NEW;
480 }
481 }
482
483 protected function allowed_params( $role = 'admin' ) {
484 $allowed_params = array(
485 'customer_id',
486 'cart_items_data',
487 'restrictions_data',
488 'presets_data',
489 'payment_data',
490 'other_data',
491 'booking_form_page_url',
492 'intent_key',
493 'order_id',
494 'coupon_code',
495 'coupon_discount',
496 'tax_total',
497 'status',
498 );
499
500 return $allowed_params;
501 }
502
503
504 protected function params_to_save( $role = 'admin' ) {
505 $params_to_save = array(
506 'customer_id',
507 'cart_items_data',
508 'restrictions_data',
509 'presets_data',
510 'payment_data',
511 'other_data',
512 'booking_form_page_url',
513 'intent_key',
514 'total',
515 'subtotal',
516 'charge_amount',
517 'specs_charge_amount',
518 'price_breakdown',
519 'order_id',
520 'coupon_code',
521 'coupon_discount',
522 'tax_total',
523 'status',
524 );
525
526 return $params_to_save;
527 }
528
529
530 protected function properties_to_validate() {
531 $validations = array(
532 'customer_id' => array( 'presence' ),
533 );
534
535 return $validations;
536 }
537 }
538