PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.2.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.2.10
5.6.9 5.6.8 5.6.7 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 / controllers / invoices_controller.php
latepoint / lib / controllers Last commit date
activities_controller.php 4 months ago auth_controller.php 4 months ago booking_form_settings_controller.php 5 months ago bookings_controller.php 1 year ago calendars_controller.php 9 months ago carts_controller.php 1 year ago controller.php 9 months ago customer_cabinet_controller.php 4 months ago customers_controller.php 4 months ago dashboard_controller.php 9 months ago default_agent_controller.php 1 year ago events_controller.php 1 year ago form_fields_controller.php 9 months ago integrations_controller.php 9 months ago invoices_controller.php 4 months ago manage_booking_by_key_controller.php 4 months ago manage_order_by_key_controller.php 1 year ago notifications_controller.php 1 year ago orders_controller.php 4 months ago pro_controller.php 1 year ago process_jobs_controller.php 4 months ago processes_controller.php 4 months ago search_controller.php 1 year ago services_controller.php 9 months ago settings_controller.php 4 months ago steps_controller.php 9 months ago stripe_connect_controller.php 9 months ago support_topics_controller.php 1 year ago todos_controller.php 1 year ago transactions_controller.php 4 months ago wizard_controller.php 4 months ago
invoices_controller.php
478 lines
1 <?php
2 /*
3 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10
11 if ( ! class_exists( 'OsInvoicesController' ) ) :
12
13
14 class OsInvoicesController extends OsController {
15
16 function __construct() {
17 parent::__construct();
18
19 $this->action_access['public'] = array_merge( $this->action_access['public'], [ 'view_by_key', 'payment_form', 'summary_before_payment' ] );
20
21 $this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'invoices/';
22 }
23
24
25 public function new_form() {
26 $order_id = absint( sanitize_text_field( $this->params['order_id'] ) );
27
28 if ( ! is_numeric( $order_id ) ) {
29 echo __( 'Invalid Order ID', 'latepoint' );
30
31 return;
32 }
33
34 $order = new OsOrderModel( $order_id );
35 if ( empty( $order ) || $order->is_new_record() ) {
36 echo __( 'Invalid Order ID', 'latepoint' );
37
38 return;
39 }
40
41 $invoice = new OsInvoiceModel();
42 $invoice->order_id = $order->id;
43 $invoice->payment_portion = LATEPOINT_PAYMENT_PORTION_CUSTOM;
44
45 $this->vars['invoice'] = $invoice;
46
47 $this->format_render( __FUNCTION__ );
48 }
49
50 private function get_invoice_params() {
51 $invoice_params = $this->params['invoice'];
52
53 // input date is in WP format (or in viewer's format), we need to make it "end of the day" and also convert to UTC timezone
54 $due_at_wp_time = sanitize_text_field( $invoice_params['due_at'] ).' 23:59:59';
55 $invoice_params['due_at'] = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);
56
57 $invoice_params['order_id'] = absint( sanitize_text_field( $this->params['invoice']['order_id'] ) );
58 $invoice_params['payment_portion'] = sanitize_text_field( $this->params['invoice']['payment_portion'] );
59 $invoice_params['charge_amount'] = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );
60
61 $errors = [];
62 if ( ! in_array( $invoice_params['payment_portion'], array_keys( OsPaymentsHelper::get_payment_portions_list() ) ) ) {
63 $errors[] = __( 'Invalid payment portion', 'latepoint' );
64 }
65 if ( ! is_numeric( $invoice_params['order_id'] ) ) {
66 $errors[] = __( 'Invalid Order ID', 'latepoint' );
67 }
68 if ( ! empty( $errors ) ) {
69 return new WP_Error( 'invalid_params', implode( ', ', $errors ) );
70 }
71
72 return $invoice_params;
73 }
74
75 public function process_data_update() {
76
77 if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
78 echo 'Invalid invoice';
79
80 return;
81 }
82 $invoice = new OsInvoiceModel( $this->params['invoice_id'] );
83 $old_invoice = clone $invoice;
84
85 if ( empty( $invoice ) || $invoice->is_new_record() ) {
86 echo 'Invalid invoice';
87
88 return;
89 }
90
91 // Verify nonce.
92 $this->check_nonce( 'update_invoice_' . $this->params['invoice_id'] );
93
94 $invoice->charge_amount = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );
95 $due_at_wp_time = sanitize_text_field( $this->params['invoice']['due_at'] ).' 23:59:59';
96 $due_at_utc_time = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);
97 $invoice->due_at = $due_at_utc_time;
98 $invoice->status = sanitize_text_field( $this->params['invoice']['status'] );
99
100 if ( $invoice->save() ) {
101
102 /**
103 * Invoice was updated
104 *
105 * @param {OsInvoiceModel} $invoice instance of invoice model after it was updated
106 * @param {OsInvoiceModel} $old_invoice instance of invoice model before it was updated
107 *
108 * @since 5.1.0
109 * @hook latepoint_invoice_updated
110 *
111 */
112 do_action( 'latepoint_invoice_updated', $invoice, $old_invoice );
113 $status = LATEPOINT_STATUS_SUCCESS;
114 ob_start();
115 OsInvoicesHelper::invoice_document_html( $invoice, true );
116 $message = ob_get_clean();
117
118 } else {
119 $status = LATEPOINT_STATUS_ERROR;
120 $message = $invoice->get_error_messages();
121 }
122
123 $this->send_json( [ 'status' => $status, 'message' => $message ] );
124 }
125
126 public function edit_data() {
127 if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
128 echo __( 'Invalid Invoice ID', 'latepoint' );
129
130 return;
131 }
132 $invoice = new OsInvoiceModel( $this->params['invoice_id'] );
133 if ( empty( $invoice ) || $invoice->is_new_record() ) {
134 echo __( 'Invoice not found', 'latepoint' );
135
136 return;
137 }
138
139 $this->vars['invoice'] = $invoice;
140
141 $this->format_render( __FUNCTION__ );
142 }
143
144 public function reload_invoice_tile() {
145 if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
146 echo 'Invalid invoice';
147
148 return;
149 }
150 $invoice = new OsInvoiceModel( $this->params['invoice_id'] );
151 if ( empty( $invoice ) || $invoice->is_new_record() ) {
152 echo 'Invalid invoice';
153
154 return;
155 }
156
157 $this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice ) ] );
158 }
159
160 public function create() {
161 // Verify nonce.
162 $this->check_nonce( 'create_invoice' );
163
164 $invoice_params = $this->get_invoice_params();
165
166 if ( is_wp_error( $invoice_params ) ) {
167 $this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => $invoice_params->get_error_message() ] );
168
169 return;
170 }
171
172 $order = new OsOrderModel( $invoice_params['order_id'] );
173 if ( empty( $order ) || $order->is_new_record() ) {
174 echo __( 'Invalid Order ID', 'latepoint' );
175
176 return;
177 }
178
179 $invoice = new OsInvoiceModel();
180 $invoice->set_data( $invoice_params );
181
182 $invoice->data = json_encode( OsInvoicesHelper::generate_invoice_data_from_order( $order ) );
183 if ( $invoice->save() ) {
184 /**
185 * Invoice was created
186 *
187 * @param {OsInvoiceModel} $invoice instance of invoice model that was created
188 *
189 * @since 5.1.0
190 * @hook latepoint_invoice_created
191 *
192 */
193 do_action( 'latepoint_invoice_created', $invoice );
194 $response_html = OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice );
195 $this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
196 } else {
197 $this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => __( 'Error: ', 'latepoint' ) . $invoice->get_error_messages() ] );
198 }
199
200 }
201
202 public function change_status() {
203 if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
204 echo 'Invalid Invoice';
205
206 return;
207 }
208
209 // Condition for pro compatibility. Remove later.
210 if( isset( $this->params['_wpnonce'] ) ) {
211 // Verify nonce.
212 $this->check_nonce( 'change_invoice_status_' . $this->params['invoice_id'] );
213 }
214
215 if ( ! in_array( $this->params['status'], array_keys( OsInvoicesHelper::list_of_statuses_for_select() ) ) ) {
216 echo 'Invalid Status';
217
218 return;
219 }
220
221 $invoice = new OsInvoiceModel( $this->params['invoice_id'] );
222 $invoice->change_status( $this->params['status'] );
223
224 $status = LATEPOINT_STATUS_SUCCESS;
225
226 ob_start();
227 OsInvoicesHelper::invoice_document_html( $invoice, true );
228 $response_html = ob_get_clean();
229
230 if ( $this->get_return_format() == 'json' ) {
231
232 $this->send_json( [ 'status' => $status, 'message' => $response_html ] );
233 }
234 }
235
236 public function email_form() {
237 if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
238 echo __( 'Invalid Invoice ID', 'latepoint' );
239
240 return;
241 }
242 $invoice = new OsInvoiceModel( $this->params['invoice_id'] );
243 if ( empty( $invoice ) || $invoice->is_new_record() ) {
244 echo __( 'Invoice not found', 'latepoint' );
245
246 return;
247 }
248 $errors = [];
249
250 $to = __( '{{customer_email}}', 'latepoint' );
251 $subject = OsInvoicesHelper::get_subject_for_invoice_email();
252 $content = OsInvoicesHelper::get_content_for_invoice_email();
253
254 if ( ! empty( $this->params['invoice_email'] ) ) {
255 // send email
256 $to = $this->params['invoice_email[to]'] ?? $to;
257 $order = new OsOrderModel( $invoice->order_id );
258 $customer = new OsCustomerModel( $order->customer_id );
259
260 $original_to = $to;
261 $to = OsReplacerHelper::replace_all_vars( $to, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
262 $subject = OsReplacerHelper::replace_all_vars( $subject, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
263 $content = OsReplacerHelper::replace_all_vars( $content, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
264 if ( OsUtilHelper::is_valid_email( $to ) ) {
265 $mailer = new OsMailer();
266 wp_mail( $to, $subject, $content, $mailer->get_headers() );
267 // set back so it appears correctly on the front
268 $to = $original_to;
269 $this->vars['success'] = __( 'Invoice email sent', 'latepoint' );
270 } else {
271 $errors[] = __( 'Please enter a valid email address.', 'latepoint' );
272 }
273
274 }
275
276 $this->vars['errors'] = $errors;
277 $this->vars['to'] = $to;
278 $this->vars['subject'] = $subject;
279 $this->vars['content'] = $content;
280 $this->vars['invoice'] = $invoice;
281
282 $this->format_render( __FUNCTION__ );
283 }
284
285
286 public function payment_form() {
287 $invoice_access_key = sanitize_text_field( $this->params['key'] );
288 if ( empty( $invoice_access_key ) ) {
289 echo __( 'Invalid Invoice Key', 'latepoint' );
290 exit;
291 }
292
293 $invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
294 if ( $invoice->is_new_record() ) {
295 echo __( 'Invoice not found', 'latepoint' );
296 exit;
297 }
298
299 $errors = [];
300 $order = $invoice->get_order();
301
302 // find an existing transaction intent for this invoice
303
304 $transaction_intent = new OsTransactionIntentModel();
305 $transaction_intent = $transaction_intent->where( [ 'status' => [
306 LATEPOINT_TRANSACTION_INTENT_STATUS_NEW,
307 LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING,
308 LATEPOINT_TRANSACTION_INTENT_STATUS_CONVERTED
309 ], 'invoice_id' => $invoice->id ] )->set_limit( 1 )->get_results_as_models();
310 if ( empty( $transaction_intent ) ) {
311 $transaction_intent = new OsTransactionIntentModel();
312 }
313
314 $transaction_intent->charge_amount = $invoice->charge_amount;
315 $transaction_intent->invoice_id = $invoice->id;
316 $transaction_intent->order_id = $order->id;
317 $transaction_intent->customer_id = $order->customer_id;
318 $transaction_intent->set_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_NOW, false );
319 $transaction_intent->set_payment_data_value( 'portion', $invoice->payment_portion, false );
320
321 $form_prev_button = esc_html__( 'Back', 'latepoint' );
322 $form_next_button = esc_html__( 'Next', 'latepoint' );
323 $invoice_link = '';
324 $receipt_link = '';
325
326 $selected_payment_method = $this->params['payment_method'] ?? '';
327 $selected_payment_processor = $this->params['payment_processor'] ?? '';
328 $payment_token = $this->params['payment_token'] ?? '';
329
330 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( LATEPOINT_PAYMENT_TIME_NOW );
331 // if only one available, force select it
332 if ( count( $enabled_payment_methods ) == 1 ) {
333 $selected_payment_method = array_key_first( $enabled_payment_methods );
334 }
335
336 if ( $selected_payment_method ) {
337 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( LATEPOINT_PAYMENT_TIME_NOW, $selected_payment_method );
338 if ( count( $enabled_payment_processors ) == 1 ) {
339 $selected_payment_processor = array_key_first( $enabled_payment_processors );
340 }
341 }
342
343 if ( ! $selected_payment_method ) {
344 $current_step = 'methods';
345 $form_heading = __( 'Payment Methods', 'latepoint' );
346 $form_prev_button = false;
347 $form_next_button = false;
348 } else {
349 $transaction_intent->set_payment_data_value( 'method', $selected_payment_method, false );
350 if ( ! $selected_payment_processor ) {
351 $current_step = 'processors';
352 $form_heading = __( 'Payment Processors', 'latepoint' );
353
354 // hide prev button if we don't need to pick a payment methods
355 if ( count( $enabled_payment_methods ) <= 1 ) {
356 $form_prev_button = false;
357 }
358 $form_next_button = false;
359 } else {
360 $transaction_intent->set_payment_data_value( 'processor', $selected_payment_processor, false );
361 $form_next_button = sprintf( esc_html__( 'Pay %s', 'latepoint' ), OsMoneyHelper::format_price( $transaction_intent->charge_amount, true, false ) );
362 $form_heading = __( 'Payment Form', 'latepoint' );
363 // hide prev button if we don't need to pick a payment method or processor
364 if ( count( $enabled_payment_methods ) <= 1 && count( $enabled_payment_processors ) <= 1 ) {
365 $form_prev_button = false;
366 }
367 if ( $payment_token ) {
368 $transaction_intent->set_payment_data_value( 'token', $payment_token, false );
369 }
370 if ( ! $payment_token || empty( $this->params['submitting_payment'] ) ) {
371 $current_step = 'pay';
372 $transaction_intent->calculate_specs_charge_amount();
373 $transaction_intent->save();
374 } else {
375 $transaction_id = $transaction_intent->convert_to_transaction();
376 if ( $transaction_id ) {
377 $transaction = new OsTransactionModel( $transaction_id );
378 $form_next_button = false;
379 $form_prev_button = false;
380 $invoice_link = apply_filters( 'latepoint_transaction_invoice_link', $invoice_link, $invoice );
381 $receipt_link = apply_filters( 'latepoint_transaction_receipt_link', $receipt_link, $invoice, $transaction );
382 $current_step = 'confirmation';
383 $this->vars['transaction'] = $transaction;
384 $form_heading = __( 'Confirmation', 'latepoint' );
385 } else {
386 $current_step = 'pay';
387 $errors[] = implode( ', ', $transaction_intent->get_error_messages() );
388 }
389 }
390 }
391 }
392
393
394 $this->vars['invoice'] = $invoice;
395 $this->vars['invoice_link'] = $invoice_link;
396 $this->vars['receipt_link'] = $receipt_link;
397 $this->vars['form_heading'] = $form_heading;
398 $this->vars['payment_token'] = $payment_token;
399 $this->vars['errors'] = $errors;
400 $this->vars['in_lightbox'] = $this->params['in_lightbox'] ?? 'yes';
401 $this->vars['transaction_intent'] = $transaction_intent;
402 $this->vars['current_step'] = $current_step;
403 $this->vars['selected_payment_method'] = $selected_payment_method;
404 $this->vars['selected_payment_processor'] = $selected_payment_processor;
405 $this->vars['enabled_payment_methods'] = $enabled_payment_methods;
406
407 $this->vars['form_next_button'] = $form_next_button;
408 $this->vars['form_prev_button'] = $form_prev_button;
409 $this->vars['invoice_access_key'] = $invoice_access_key;
410
411
412 $this->vars['order'] = $order;
413
414 $this->format_render( __FUNCTION__ );
415 }
416
417 public function summary_before_payment() {
418 $invoice_access_key = sanitize_text_field( $this->params['key'] );
419 if ( empty( $invoice_access_key ) ) {
420 echo __( 'Invalid Invoice Key', 'latepoint' );
421 exit;
422 }
423
424 $invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
425 if ( $invoice->is_new_record() ) {
426 echo __( 'Invoice not found', 'latepoint' );
427 exit;
428 }
429
430 $this->vars['invoice'] = $invoice;
431 $this->vars['order'] = $invoice->get_order();
432
433 if ( $this->get_return_format() == 'json' ) {
434 $this->vars['in_lightbox'] = true;
435 $this->set_layout( 'none' );
436 $response_html = $this->format_render_return( __FUNCTION__ );
437 $this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
438 } else {
439 $this->vars['in_lightbox'] = false;
440 $this->set_layout( 'clean' );
441 $this->format_render( __FUNCTION__ );
442 }
443 }
444
445
446 function view_by_key() {
447 $invoice_access_key = sanitize_text_field( $this->params['key'] );
448 $invoice = new OsInvoiceModel();
449 $invoice = $invoice->where( [ 'access_key' => $invoice_access_key ] )->set_limit( 1 )->get_results_as_models();
450 $this->vars['invoice'] = $invoice;
451
452 $this->set_layout( 'clean' );
453 $this->format_render( __FUNCTION__ );
454 }
455
456 function view() {
457 if ( ! filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
458 return;
459 }
460
461 $invoice = new OsInvoiceModel( $this->params['id'] );
462
463 $this->vars['invoice'] = $invoice;
464
465 $this->set_layout( 'none' );
466 $response_html = $this->format_render_return( __FUNCTION__ );
467
468 $status = LATEPOINT_STATUS_SUCCESS;
469
470 if ( $this->get_return_format() == 'json' ) {
471
472 $this->send_json( [ 'status' => $status, 'message' => $response_html ] );
473 }
474 }
475 }
476
477 endif;
478