[Back] <?php
/*
* Copyright (c) 2024 LatePoint LLC. All rights reserved.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'OsInvoicesController' ) ) :
class OsInvoicesController extends OsController {
function __construct() {
parent::__construct();
$this->action_access['public'] = array_merge( $this->action_access['public'], [ 'view_by_key', 'payment_form', 'summary_before_payment' ] );
$this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'invoices/';
}
public function new_form() {
$order_id = absint( sanitize_text_field( $this->params['order_id'] ) );
if ( ! is_numeric( $order_id ) ) {
echo __( 'Invalid Order ID', 'latepoint' );
return;
}
$order = new OsOrderModel( $order_id );
if ( empty( $order ) || $order->is_new_record() ) {
echo __( 'Invalid Order ID', 'latepoint' );
return;
}
$invoice = new OsInvoiceModel();
$invoice->order_id = $order->id;
$invoice->payment_portion = LATEPOINT_PAYMENT_PORTION_CUSTOM;
$this->vars['invoice'] = $invoice;
$this->format_render( __FUNCTION__ );
}
private function get_invoice_params() {
$invoice_params = $this->params['invoice'];
// 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
$due_at_wp_time = sanitize_text_field( $invoice_params['due_at'] ).' 23:59:59';
$invoice_params['due_at'] = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);
$invoice_params['order_id'] = absint( sanitize_text_field( $this->params['invoice']['order_id'] ) );
$invoice_params['payment_portion'] = sanitize_text_field( $this->params['invoice']['payment_portion'] );
$invoice_params['charge_amount'] = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );
$errors = [];
if ( ! in_array( $invoice_params['payment_portion'], array_keys( OsPaymentsHelper::get_payment_portions_list() ) ) ) {
$errors[] = __( 'Invalid payment portion', 'latepoint' );
}
if ( ! is_numeric( $invoice_params['order_id'] ) ) {
$errors[] = __( 'Invalid Order ID', 'latepoint' );
}
if ( ! empty( $errors ) ) {
return new WP_Error( 'invalid_params', implode( ', ', $errors ) );
}
return $invoice_params;
}
public function process_data_update() {
if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
echo 'Invalid invoice';
return;
}
$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
$old_invoice = clone $invoice;
if ( empty( $invoice ) || $invoice->is_new_record() ) {
echo 'Invalid invoice';
return;
}
$invoice->charge_amount = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );
$due_at_wp_time = sanitize_text_field( $this->params['invoice']['due_at'] ).' 23:59:59';
$due_at_utc_time = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);
$invoice->due_at = $due_at_utc_time;
$invoice->status = sanitize_text_field( $this->params['invoice']['status'] );
if ( $invoice->save() ) {
/**
* Invoice was updated
*
* @param {OsInvoiceModel} $invoice instance of invoice model after it was updated
* @param {OsInvoiceModel} $old_invoice instance of invoice model before it was updated
*
* @since 5.1.0
* @hook latepoint_invoice_updated
*
*/
do_action( 'latepoint_invoice_updated', $invoice, $old_invoice );
$status = LATEPOINT_STATUS_SUCCESS;
ob_start();
OsInvoicesHelper::invoice_document_html( $invoice, true );
$message = ob_get_clean();
} else {
$status = LATEPOINT_STATUS_ERROR;
$message = $invoice->get_error_messages();
}
$this->send_json( [ 'status' => $status, 'message' => $message ] );
}
public function edit_data() {
if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
echo __( 'Invalid Invoice ID', 'latepoint' );
return;
}
$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
if ( empty( $invoice ) || $invoice->is_new_record() ) {
echo __( 'Invoice not found', 'latepoint' );
return;
}
$this->vars['invoice'] = $invoice;
$this->format_render( __FUNCTION__ );
}
public function reload_invoice_tile() {
if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
echo 'Invalid invoice';
return;
}
$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
if ( empty( $invoice ) || $invoice->is_new_record() ) {
echo 'Invalid invoice';
return;
}
$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice ) ] );
}
public function create() {
$invoice_params = $this->get_invoice_params();
if ( is_wp_error( $invoice_params ) ) {
$this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => $invoice_params->get_error_message() ] );
return;
}
$order = new OsOrderModel( $invoice_params['order_id'] );
if ( empty( $order ) || $order->is_new_record() ) {
echo __( 'Invalid Order ID', 'latepoint' );
return;
}
$invoice = new OsInvoiceModel();
$invoice->set_data( $invoice_params );
$invoice->data = json_encode( OsInvoicesHelper::generate_invoice_data_from_order( $order ) );
if ( $invoice->save() ) {
/**
* Invoice was created
*
* @param {OsInvoiceModel} $invoice instance of invoice model that was created
*
* @since 5.1.0
* @hook latepoint_invoice_created
*
*/
do_action( 'latepoint_invoice_created', $invoice );
$response_html = OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice );
$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
} else {
$this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => __( 'Error: ', 'latepoint' ) . $invoice->get_error_messages() ] );
}
}
public function change_status() {
if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
echo 'Invalid Invoice';
return;
}
if ( ! in_array( $this->params['status'], array_keys( OsInvoicesHelper::list_of_statuses_for_select() ) ) ) {
echo 'Invalid Status';
return;
}
$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
$invoice->change_status( $this->params['status'] );
$status = LATEPOINT_STATUS_SUCCESS;
ob_start();
OsInvoicesHelper::invoice_document_html( $invoice, true );
$response_html = ob_get_clean();
if ( $this->get_return_format() == 'json' ) {
$this->send_json( [ 'status' => $status, 'message' => $response_html ] );
}
}
public function email_form() {
if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
echo __( 'Invalid Invoice ID', 'latepoint' );
return;
}
$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
if ( empty( $invoice ) || $invoice->is_new_record() ) {
echo __( 'Invoice not found', 'latepoint' );
return;
}
$errors = [];
$to = __( '{{customer_email}}', 'latepoint' );
$subject = OsInvoicesHelper::get_subject_for_invoice_email();
$content = OsInvoicesHelper::get_content_for_invoice_email();
if ( ! empty( $this->params['invoice_email'] ) ) {
// send email
$to = $this->params['invoice_email[to]'] ?? $to;
$order = new OsOrderModel( $invoice->order_id );
$customer = new OsCustomerModel( $order->customer_id );
$original_to = $to;
$to = OsReplacerHelper::replace_all_vars( $to, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
$subject = OsReplacerHelper::replace_all_vars( $subject, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
$content = OsReplacerHelper::replace_all_vars( $content, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
if ( OsUtilHelper::is_valid_email( $to ) ) {
$mailer = new OsMailer();
wp_mail( $to, $subject, $content, $mailer->get_headers() );
// set back so it appears correctly on the front
$to = $original_to;
$this->vars['success'] = __( 'Invoice email sent', 'latepoint' );
} else {
$errors[] = __( 'Please enter a valid email address.', 'latepoint' );
}
}
$this->vars['errors'] = $errors;
$this->vars['to'] = $to;
$this->vars['subject'] = $subject;
$this->vars['content'] = $content;
$this->vars['invoice'] = $invoice;
$this->format_render( __FUNCTION__ );
}
public function payment_form() {
$invoice_access_key = sanitize_text_field( $this->params['key'] );
if ( empty( $invoice_access_key ) ) {
echo __( 'Invalid Invoice Key', 'latepoint' );
exit;
}
$invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
if ( $invoice->is_new_record() ) {
echo __( 'Invoice not found', 'latepoint' );
exit;
}
$errors = [];
$order = $invoice->get_order();
// find an existing transaction intent for this invoice
$transaction_intent = new OsTransactionIntentModel();
$transaction_intent = $transaction_intent->where( [ 'status' => [
LATEPOINT_TRANSACTION_INTENT_STATUS_NEW,
LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING,
LATEPOINT_TRANSACTION_INTENT_STATUS_CONVERTED
], 'invoice_id' => $invoice->id ] )->set_limit( 1 )->get_results_as_models();
if ( empty( $transaction_intent ) ) {
$transaction_intent = new OsTransactionIntentModel();
}
$transaction_intent->charge_amount = $invoice->charge_amount;
$transaction_intent->invoice_id = $invoice->id;
$transaction_intent->order_id = $order->id;
$transaction_intent->customer_id = $order->customer_id;
$transaction_intent->set_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_NOW, false );
$transaction_intent->set_payment_data_value( 'portion', $invoice->payment_portion, false );
$form_prev_button = esc_html__( 'Back', 'latepoint' );
$form_next_button = esc_html__( 'Next', 'latepoint' );
$invoice_link = '';
$receipt_link = '';
$selected_payment_method = $this->params['payment_method'] ?? '';
$selected_payment_processor = $this->params['payment_processor'] ?? '';
$payment_token = $this->params['payment_token'] ?? '';
$enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( LATEPOINT_PAYMENT_TIME_NOW );
// if only one available, force select it
if ( count( $enabled_payment_methods ) == 1 ) {
$selected_payment_method = array_key_first( $enabled_payment_methods );
}
if ( $selected_payment_method ) {
$enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( LATEPOINT_PAYMENT_TIME_NOW, $selected_payment_method );
if ( count( $enabled_payment_processors ) == 1 ) {
$selected_payment_processor = array_key_first( $enabled_payment_processors );
}
}
if ( ! $selected_payment_method ) {
$current_step = 'methods';
$form_heading = __( 'Payment Methods', 'latepoint' );
$form_prev_button = false;
$form_next_button = false;
} else {
$transaction_intent->set_payment_data_value( 'method', $selected_payment_method, false );
if ( ! $selected_payment_processor ) {
$current_step = 'processors';
$form_heading = __( 'Payment Processors', 'latepoint' );
// hide prev button if we don't need to pick a payment methods
if ( count( $enabled_payment_methods ) <= 1 ) {
$form_prev_button = false;
}
$form_next_button = false;
} else {
$transaction_intent->set_payment_data_value( 'processor', $selected_payment_processor, false );
$form_next_button = sprintf( esc_html__( 'Pay %s', 'latepoint' ), OsMoneyHelper::format_price( $transaction_intent->charge_amount, true, false ) );
$form_heading = __( 'Payment Form', 'latepoint' );
// hide prev button if we don't need to pick a payment method or processor
if ( count( $enabled_payment_methods ) <= 1 && count( $enabled_payment_processors ) <= 1 ) {
$form_prev_button = false;
}
if ( $payment_token ) {
$transaction_intent->set_payment_data_value( 'token', $payment_token, false );
}
if ( ! $payment_token || empty( $this->params['submitting_payment'] ) ) {
$current_step = 'pay';
$transaction_intent->calculate_specs_charge_amount();
$transaction_intent->save();
} else {
$transaction_id = $transaction_intent->convert_to_transaction();
if ( $transaction_id ) {
$transaction = new OsTransactionModel( $transaction_id );
$form_next_button = false;
$form_prev_button = false;
$invoice_link = apply_filters( 'latepoint_transaction_invoice_link', $invoice_link, $invoice );
$receipt_link = apply_filters( 'latepoint_transaction_receipt_link', $receipt_link, $invoice, $transaction );
$current_step = 'confirmation';
$this->vars['transaction'] = $transaction;
$form_heading = __( 'Confirmation', 'latepoint' );
} else {
$current_step = 'pay';
$errors[] = implode( ', ', $transaction_intent->get_error_messages() );
}
}
}
}
$this->vars['invoice'] = $invoice;
$this->vars['invoice_link'] = $invoice_link;
$this->vars['receipt_link'] = $receipt_link;
$this->vars['form_heading'] = $form_heading;
$this->vars['payment_token'] = $payment_token;
$this->vars['errors'] = $errors;
$this->vars['in_lightbox'] = $this->params['in_lightbox'] ?? 'yes';
$this->vars['transaction_intent'] = $transaction_intent;
$this->vars['current_step'] = $current_step;
$this->vars['selected_payment_method'] = $selected_payment_method;
$this->vars['selected_payment_processor'] = $selected_payment_processor;
$this->vars['enabled_payment_methods'] = $enabled_payment_methods;
$this->vars['form_next_button'] = $form_next_button;
$this->vars['form_prev_button'] = $form_prev_button;
$this->vars['invoice_access_key'] = $invoice_access_key;
$this->vars['order'] = $order;
$this->format_render( __FUNCTION__ );
}
public function summary_before_payment() {
$invoice_access_key = sanitize_text_field( $this->params['key'] );
if ( empty( $invoice_access_key ) ) {
echo __( 'Invalid Invoice Key', 'latepoint' );
exit;
}
$invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
if ( $invoice->is_new_record() ) {
echo __( 'Invoice not found', 'latepoint' );
exit;
}
$this->vars['invoice'] = $invoice;
$this->vars['order'] = $invoice->get_order();
if ( $this->get_return_format() == 'json' ) {
$this->vars['in_lightbox'] = true;
$this->set_layout( 'none' );
$response_html = $this->format_render_return( __FUNCTION__ );
$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
} else {
$this->vars['in_lightbox'] = false;
$this->set_layout( 'clean' );
$this->format_render( __FUNCTION__ );
}
}
function view_by_key() {
$invoice_access_key = sanitize_text_field( $this->params['key'] );
$invoice = new OsInvoiceModel();
$invoice = $invoice->where( [ 'access_key' => $invoice_access_key ] )->set_limit( 1 )->get_results_as_models();
$this->vars['invoice'] = $invoice;
$this->set_layout( 'clean' );
$this->format_render( __FUNCTION__ );
}
function view() {
if ( ! filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
return;
}
$invoice = new OsInvoiceModel( $this->params['id'] );
$this->vars['invoice'] = $invoice;
$this->set_layout( 'none' );
$response_html = $this->format_render_return( __FUNCTION__ );
$status = LATEPOINT_STATUS_SUCCESS;
if ( $this->get_return_format() == 'json' ) {
$this->send_json( [ 'status' => $status, 'message' => $response_html ] );
}
}
}
endif;