[Back] <?php
/*
* Copyright (c) 2024 LatePoint LLC. All rights reserved.
*/
class OsTransactionIntentModel extends OsModel {
var $id,
$intent_key,
$order_id,
$customer_id,
$invoice_id,
$transaction_id,
$payment_data = '',
$payment_data_arr,
$other_data,
$charge_amount,
$specs_charge_amount,
$status,
$order_form_page_url,
$updated_at,
$created_at;
function __construct( $id = false ) {
parent::__construct();
$this->table_name = LATEPOINT_TABLE_TRANSACTION_INTENTS;
if ( $id ) {
$this->load_by_id( $id );
}
}
public function calculate_specs_charge_amount() {
/**
* Convert transaction intent charge amount to specs
*
* @param {string} $charge_amount original charge amount of a transaction intent
* @param {OsTransactionIntentModel} $transaction_intent transaction intent model
*
* @returns {string} The filtered to specs charge amount
*
* @since 5.1.3
* @hook latepoint_transaction_intent_specs_charge_amount
*
*/
$this->specs_charge_amount = apply_filters( 'latepoint_transaction_intent_specs_charge_amount', $this->charge_amount, $this );
}
protected function params_to_sanitize() {
return [
'charge_amount' => 'money',
];
}
public function get_payment_data_value( string $key ): string {
if ( ! isset( $this->payment_data_arr ) ) {
$this->payment_data_arr = json_decode( $this->payment_data, true );
}
return $this->payment_data_arr[ $key ] ?? '';
}
public function set_payment_data_value( string $key, string $value, bool $save = true ) {
$this->payment_data_arr = json_decode( $this->payment_data, true );
$this->payment_data_arr[ $key ] = $value;
$this->payment_data = wp_json_encode( $this->payment_data_arr );
if ( $save ) {
$this->update_attributes( [ 'payment_data' => $this->payment_data ] );
}
}
public function is_processing(): bool {
return $this->status == LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING;
}
public function is_failed(): bool {
return $this->status == LATEPOINT_TRANSACTION_INTENT_STATUS_FAILED;
}
public function wait_for_transaction_completion() : OsTransactionIntentModel {
$attempts = 0;
$max_attempts = 6;
$delay_seconds = 2;
while ($attempts < $max_attempts) {
if (!$this->is_processing()) {
return $this;
}
sleep($delay_seconds);
$attempts++;
$this->load_by_id($this->id);
}
if($this->is_processing()){
// if it's still processing after waiting - mark as failed
$this->mark_as_failed();
}
return $this;
}
public function convert_to_transaction() {
if($this->is_processing()){
$this->wait_for_transaction_completion();
if($this->is_failed()){
$this->add_error( 'transaction_intent_error', __('Can not convert to transaction, because transaction intent conversion is being processed', 'latepoint') );
return false;
}
}
if($this->is_converted()){
return $this->transaction_id;
}
$this->mark_as_processing();
try {
// process payment if there is amount due
$transaction = OsPaymentsHelper::process_payment_for_transaction_intent( $this );
if(!$transaction || $transaction->status != LATEPOINT_TRANSACTION_STATUS_SUCCEEDED){
if(!$transaction){
$this->add_error('transaction_intent_error', __('No payment processor available to process this transaction intent', 'latepoint'));
}else{
if ( $transaction->get_error() ) {
$this->add_error('transaction_intent_error', $transaction->get_error_messages());
}
}
$this->mark_as_new();
return false;
}
/**
* Filters transaction right before it's about to be saved when converting from a transaction intent
*
* @param {OsTransactionModel} $transaction Transaction to be filtered
* @returns {OsTransactionModel} The filtered transaction
*
* @since 5.0.0
* @hook latepoint_before_transaction_save_from_transaction_intent
*
*/
$transaction = apply_filters( 'latepoint_before_transaction_save_from_transaction_intent', $transaction );
if ( $transaction->save() ) {
$this->mark_as_converted( $transaction );
/**
* Transaction was created
*
* @param {OsTransactionModel} $transaction instance of transaction model that was created
*
* @since 5.0.0
* @hook latepoint_transaction_created
*
*/
do_action( 'latepoint_transaction_created', $transaction );
if($transaction->invoice_id){
$invoice = new OsInvoiceModel($transaction->invoice_id);
if($invoice && !$invoice->is_new_record()){
$invoice->change_status(LATEPOINT_INVOICE_STATUS_PAID);
OsOrdersHelper::check_if_order_invoices_paid_full_balance($this->order_id);
}
}
return $transaction->id;
} else {
$this->add_error( 'transaction_intent_error', $transaction->get_error_messages() );
$this->mark_as_new();
return false;
}
} catch ( Exception $e ) {
$this->mark_as_new();
// translators: %s is the error description
$this->add_error( 'transaction_intent_error', sprintf(__('Error: %s', 'latepoint'), $e->getMessage() ));
OsDebugHelper::log( 'Error converting transaction intent to a transaction', 'transaction_intent_error', $e->getMessage() );
return false;
}
}
public function get_by_intent_key( $intent_key ) {
return $this->where( [ 'intent_key' => $intent_key ] )->set_limit( 1 )->get_results_as_models();
}
public function mark_as_converted( OsTransactionModel $transaction ) : bool {
if ( empty( $transaction->id ) ) {
return false;
}
$this->transaction_id = $transaction->id;
$this->status = LATEPOINT_TRANSACTION_INTENT_STATUS_CONVERTED;
if($this->save()){
/**
* Transaction intent is converted to transaction
*
* @param {OsTransactionIntentModel} $transaction_intent Instance of transaction intent model that has been converted to transaction
* @param {OsTransactionModel} $transaction Instance of transaction model that transaction intent was converted to
*
* @since 5.0.0
* @hook latepoint_transaction_intent_converted
*
*/
do_action( 'latepoint_transaction_intent_converted', $this, $transaction );
return true;
}else{
return false;
}
}
public function mark_as_failed() {
$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_FAILED ] );
/**
* Transaction intent is marked as failed
*
* @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that has failed
*
* @since 5.2.0
* @hook latepoint_transaction_intent_failed
*
*/
do_action( 'latepoint_transaction_intent_failed', $this );
}
public function mark_as_processing() {
$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING ] );
/**
* Transaction intent is marked as processing
*
* @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that has started processing
*
* @since 5.0.0
* @hook latepoint_transaction_intent_processing
*
*/
do_action( 'latepoint_transaction_intent_processing', $this );
}
public function mark_as_new() {
$this->update_attributes( [ 'status' => LATEPOINT_TRANSACTION_INTENT_STATUS_NEW ] );
/**
* Order intent is marked as new
*
* @param {OsTransactionIntentModel} $transaction_intent Instance of order intent model that is being marked as new
*
* @since 5.0.0
* @hook latepoint_transaction_intent_new
*
*/
do_action( 'latepoint_transaction_intent_new', $this );
}
// Determines if order intent has been converted into a order already
public function is_converted() : bool {
if ( empty( $this->transaction_id ) ) {
return false;
} else {
return true;
}
}
public function generate_data_vars(): array {
$vars = [
'id' => $this->id,
'intent_key' => $this->intent_key,
'payment_data' => !empty($this->payment_data) ? json_decode( $this->payment_data, true ) : [],
'order_id' => $this->order_id,
'transaction_id' => $this->transaction_id,
'order_form_page_url' => $this->order_form_page_url,
'updated_at' => $this->updated_at,
'created_at' => $this->created_at,
];
return $vars;
}
public function get_page_url_with_intent() {
$order_form_page_url = $this->order_form_page_url;
$existing_var_position = strpos( $order_form_page_url, 'latepoint_transaction_intent_key=' );
if ( $existing_var_position === false ) {
// no intent variable in url
$question_position = strpos( $order_form_page_url, '?' );
if ( $question_position === false ) {
// no ?query params
$hash_position = strpos( $order_form_page_url, '#' );
if ( $hash_position === false ) {
// no hashtag in url
$order_form_page_url = $order_form_page_url . '?latepoint_transaction_intent_key=' . $this->intent_key;
} else {
// hashtag in url and no ?query, prepend the hashtag with query
$order_form_page_url = substr_replace( $order_form_page_url, '?latepoint_transaction_intent_key=' . $this->intent_key . '#', $hash_position, 1 );
}
} else {
// ?query string exists, add intent key to it
$order_form_page_url = substr_replace( $order_form_page_url, '?latepoint_transaction_intent_key=' . $this->intent_key . '&', $question_position, 1 );
}
} else {
// intent key variable exist in url
preg_match( '/latepoint_transaction_intent_key=([\d,\w]*)/', $order_form_page_url, $matches );
if ( isset( $matches[1] ) ) {
$order_form_page_url = str_replace( 'latepoint_transaction_intent_key=' . $matches[1], 'latepoint_transaction_intent_key=' . $this->intent_key, $order_form_page_url );
}
}
return $order_form_page_url;
}
public function generate_intent_key() {
$this->intent_key = bin2hex( openssl_random_pseudo_bytes( 10 ) );
}
public function get_customer() : OsCustomerModel {
if ( $this->customer_id ) {
if ( ! isset( $this->customer ) || ( isset( $this->customer ) && ( $this->customer->id != $this->customer_id ) ) ) {
$this->customer = new OsCustomerModel( $this->customer_id );
}
} else {
$this->customer = new OsCustomerModel();
}
return $this->customer;
}
protected function before_create() {
if ( empty( $this->intent_key ) ) {
$this->intent_key = bin2hex( openssl_random_pseudo_bytes( 10 ) );
}
if ( empty( $this->status ) ) {
$this->status = LATEPOINT_TRANSACTION_INTENT_STATUS_NEW;
}
}
protected function allowed_params( $role = 'admin' ) {
$allowed_params = array(
'payment_data',
'intent_key',
'order_id',
'customer_id',
'invoice_id',
'transaction_id',
'order_form_page_url',
'status',
);
return $allowed_params;
}
protected function params_to_save( $role = 'admin' ) {
$params_to_save = array(
'payment_data',
'intent_key',
'charge_amount',
'specs_charge_amount',
'order_id',
'customer_id',
'invoice_id',
'transaction_id',
'status',
);
return $params_to_save;
}
protected function properties_to_validate() {
$validations = array(
'order_id' => array( 'presence' ),
);
return $validations;
}
}