woocommerce/payment-gateway/Handlers/Abstract_Payment_Handler.php
<?php
/**
* WooCommerce Payment Gateway Framework
*
* This source file is subject to the GNU General Public License v3.0
* that is bundled with this package in the file license.txt.
* It is also available through the world-wide-web at this URL:
* http://www.gnu.org/licenses/gpl-3.0.html
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@skyverge.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade the plugin to newer
* versions in the future. If you wish to customize the plugin for your
* needs please refer to http://www.skyverge.com
*
* @package SkyVerge/WooCommerce/Payment-Gateway/Admin
* @author SkyVerge
* @copyright Copyright (c) 2013-2024, SkyVerge, Inc.
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
*/
namespace SkyVerge\WooCommerce\PluginFramework\v5_12_3\Payment_Gateway\Handlers;
use SkyVerge\WooCommerce\PluginFramework\v5_12_3 as FrameworkBase;
if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_12_3\\Payment_Gateway\\Handlers\\Abstract_Payment_Handler' ) ) :
/**
* The base payment handler class.
*
* This acts as an abstracted handler for processing payments, regardless of their front-end or API implementation.
* Both direct and hosted gateways' transactions end up as the same response object, which this class handles for order
* updating.
*
* @see Abstract_Hosted_Payment_Handler
*
* @since 5.4.0
*/
#[\AllowDynamicProperties]
abstract class Abstract_Payment_Handler {
/** the success result code */
const RESULT_CODE_SUCCESS = 'success';
/** the failure result code */
const RESULT_CODE_FAILURE = 'failure';
/** @var FrameworkBase\SV_WC_Payment_Gateway gateway instance */
protected $gateway;
/**
* Constructs the class.
*
* @since 5.4.0
*
* @param FrameworkBase\SV_WC_Payment_Gateway $gateway
*/
public function __construct( FrameworkBase\SV_WC_Payment_Gateway $gateway ) {
$this->gateway = $gateway;
$this->add_hooks();
}
/**
* Adds any action and filter hooks required by the handler.
*
* @since 5.4.0
*/
protected function add_hooks() {
// filter order received text for held orders
add_filter( 'woocommerce_thankyou_order_received_text', array( $this, 'maybe_render_held_order_received_text' ), 10, 2 );
}
/**
* Renders a custom held order message if available.
*
* @since 5.4.0
*
* @param string $text default text
* @param \WC_Order $order order object
*
* @return mixed
*/
public function maybe_render_held_order_received_text( $text, $order ) {
if ( $order && isset( WC()->session->held_order_received_text ) ) {
$text = WC()->session->held_order_received_text;
unset( WC()->session->held_order_received_text );
}
return $text;
}
/**
* Processes payment for an order.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @return array
* @throws FrameworkBase\SV_WC_Plugin_Exception
*/
abstract public function process_order_payment( \WC_Order $order );
/**
* Processes a gateway API payment response and handles the order accordingly.
*
* @since 5.4.0
*
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response $response
* @param \WC_Order $order
* @throws FrameworkBase\SV_WC_Payment_Gateway_Exception for payment failures
* @throws FrameworkBase\SV_WC_Plugin_Exception for other validation errors
*/
protected function process_transaction_response( FrameworkBase\SV_WC_Payment_Gateway_API_Response $response, \WC_Order $order ) {
// validate the response data such as order ID and payment status
$this->validate_transaction_response( $order, $response );
try {
if ( $response->transaction_approved() || $response->transaction_held() ) {
if ( $response->transaction_held() || ( $this->get_gateway()->supports_credit_card_authorization() && $this->get_gateway()->perform_credit_card_authorization( $order ) ) ) {
$this->process_order_transaction_held( $order, $response );
} elseif ( $response->transaction_approved() ) {
$this->process_order_transaction_approved( $order, $response );
}
$this->mark_order_as_paid( $order, $response );
} else {
$message = '';
// build the order note with what data we have
if ( $response->get_status_code() && $response->get_status_message() ) {
/* translators: Placeholders: %1$s - status code, %2$s - status message */
$message = sprintf( esc_html__( 'Status code %1$s: %2$s', 'woocommerce-plugin-framework' ), $response->get_status_code(), $response->get_status_message() );
} elseif ( $response->get_status_code() ) {
/* translators: Placeholders: %s - status code */
$message = sprintf( esc_html__( 'Status code: %s', 'woocommerce-plugin-framework' ), $response->get_status_code() );
} elseif ( $response->get_status_message() ) {
/* translators: Placeholder: %s - Status message */
$message = sprintf( esc_html__( 'Status message: %s', 'woocommerce-plugin-framework' ), $response->get_status_message() );
}
// add transaction id if there is one
if ( $response->get_transaction_id() ) {
/* translators: Placeholder: %s - Payment transaction ID */
$message .= ' ' . sprintf( esc_html__( 'Transaction ID %s', 'woocommerce-plugin-framework' ), $response->get_transaction_id() );
}
if ( $response->get_user_message() && $this->get_gateway()->is_detailed_customer_decline_messages_enabled() ) {
$user_exception = new FrameworkBase\SV_WC_Payment_Gateway_Exception( $response->get_user_message() );
} else {
$user_exception = null;
}
throw new FrameworkBase\SV_WC_Payment_Gateway_Exception( $message, null, $user_exception );
}
// add an order note for all exceptions and rethrow
} catch ( FrameworkBase\SV_WC_Payment_Gateway_Exception $exception ) {
$this->process_order_transaction_failed( $order, $exception->getMessage(), $response );
// one can not simply throw $exception or the previous (user-friendly) exception message won't make it through
throw new FrameworkBase\SV_WC_Payment_Gateway_Exception( $exception->getMessage(), $exception->getCode(), $exception->getPrevious() );
}
}
/**
* Validates a transaction response & its order.
*
* This ensures duplicate or fraudulent responses aren't processed. Implementations can add exceptions to this for
* things like invalid hashes, etc...
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response $response API response object
* @throws FrameworkBase\SV_WC_API_Exception
*/
protected function validate_transaction_response( \WC_Order $order, FrameworkBase\SV_WC_Payment_Gateway_API_Response $response ) {
// if the order has already been completed, bail
if ( ! $order->needs_payment() ) {
/* translators: Placeholders: %s - payment gateway title (such as Authorize.net, Braintree, etc) */
$order->add_order_note( sprintf( esc_html__( '%s duplicate transaction received', 'woocommerce-plugin-framework' ), $this->get_gateway()->get_method_title() ) );
throw new FrameworkBase\SV_WC_API_Exception( sprintf(
__( 'Order %s is already paid for.', 'woocommerce-plugin-framework' ),
$order->get_order_number()
) );
}
}
/**
* Handles actions after an approved transaction.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response $response API response object
*/
protected function process_order_transaction_approved( \WC_Order $order, FrameworkBase\SV_WC_Payment_Gateway_API_Response $response ) {
try {
$message = '';
if ( FrameworkBase\SV_WC_Payment_Gateway::PAYMENT_TYPE_CREDIT_CARD === $response->get_payment_type() ) {
$message = $this->get_gateway()->get_credit_card_transaction_approved_message( $order, $response );
} elseif ( FrameworkBase\SV_WC_Payment_Gateway::PAYMENT_TYPE_ECHECK === $response->get_payment_type() ) {
$message = $this->get_gateway()->get_echeck_transaction_approved_message( $order, $response );
} else {
$message_method = 'get_' . $response->get_payment_type() . '_transaction_approved_message';
if ( is_callable( array( $this->get_gateway(), $message_method ) ) ) {
$message = $this->get_gateway()->$message_method( $order, $response );
}
}
$this->mark_order_as_approved( $order, $message, $response );
} catch ( \Exception $exception ) {
// TODO
}
}
/**
* Handles actions after a held transaction.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response $response API response object
*/
protected function process_order_transaction_held( \WC_Order $order, FrameworkBase\SV_WC_Payment_Gateway_API_Response $response ) {
$user_message = '';
if ( $this->get_gateway()->is_detailed_customer_decline_messages_enabled() ) {
$user_message = $response->get_user_message();
}
if ( ! $user_message || ( $this->get_gateway()->supports_credit_card_authorization() && $this->get_gateway()->perform_credit_card_authorization( $order ) ) ) {
$user_message = __( 'Your order has been received and is being reviewed. Thank you for your business.', 'woocommerce-plugin-framework' );
}
if ( null !== WC()->session ) {
WC()->session->held_order_received_text = $user_message;
}
$note_message = $this->get_gateway()->supports_credit_card_authorization() && $this->get_gateway()->perform_credit_card_authorization( $order ) ? __( 'Authorization only transaction', 'woocommerce-plugin-framework' ) : $response->get_status_message();
$this->mark_order_as_held( $order, $note_message, $response );
}
/**
* Handles actions after a failed transaction.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param string $message failure message
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response response object
*/
protected function process_order_transaction_failed( \WC_Order $order, $message = '', FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
$this->mark_order_as_failed( $order, $message, $response );
}
/** Order marking methods *****************************************************************************************/
/**
* Marks an order as paid.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Customer_Response|FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response API response object
*/
public function mark_order_as_paid( \WC_Order $order, FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
$this->get_gateway()->add_transaction_data( $order, $response );
// let gateways easily add their own data
$this->get_gateway()->add_payment_gateway_transaction_data( $order, $response );
if ( $order->has_status( $this->get_held_order_status( $order, $response ) ) ) {
// reduce stock for held orders, but don't complete payment (pass order ID so WooCommerce fetches fresh order object with reduced_stock meta set on order status change)
wc_reduce_stock_levels( $order->get_id() );
} else {
// mark order as having received payment
$order->payment_complete();
}
/**
* Payment Gateway Payment Processed Action.
*
* Fired when a payment is processed for an order.
*
* @since 4.1.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_Direct $this instance
*/
do_action( 'wc_payment_gateway_' . $this->get_gateway()->get_id() . '_payment_processed', $order, $this->get_gateway() );
}
/**
* Marks an order as approved.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param string $message message for the order note
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response API response object
*/
public function mark_order_as_approved( \WC_Order $order, $message = '', FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
$order->add_order_note( $message );
}
/**
* Marks an order as held for review.
*
* Adds an order note and transitions to a held status.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param string $message order note message
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response
*/
public function mark_order_as_held( \WC_Order $order, $message = '', FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
/* translators: Example: "Authorize.Net Transaction Held for Review". Placeholder: %s - Payment gateway title */
$order_note = sprintf( __( '%s Transaction Held for Review', 'woocommerce-plugin-framework' ), $this->get_gateway()->get_method_title() );
if ( $message ) {
$order_note .= " ({$message})";
}
$order_status = $this->get_held_order_status( $order, $response );
// mark order as held
if ( ! $order->has_status( $order_status ) ) {
$order->update_status( $order_status, $order_note );
} else {
$order->add_order_note( $order_note );
}
}
/**
* Gets the order status used for held orders.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response API response object
*
* @return string
*/
public function get_held_order_status( \WC_Order $order, $response = null ) {
/**
* Held Order Status Filter.
*
* This filter is deprecated. Use wc_<gateway_id>_held_order_status instead.
*
* @since 4.0.1
* @deprecated 5.3.0
*
* @param string $order_status 'on-hold' by default
* @param \WC_Order $order WC order
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response API response object, if any
* @param FrameworkBase\SV_WC_Payment_Gateway $gateway gateway instance
*/
$status = apply_filters( 'wc_payment_gateway_' . $this->get_gateway()->get_id() . '_held_order_status', 'on-hold', $order, $response, $this->get_gateway() );
/**
* Filters the order status that's considered to be "held".
*
* @since 5.3.0
*
* @param string $status held order status
* @param \WC_Order $order order object
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response API response object, if any
*/
$status = apply_filters( 'wc_' . $this->get_gateway()->get_id() . '_held_order_status', $status, $order, $response );
return (string) $status;
}
/**
* Marks an order as failed.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param string $message order note message
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response
*/
public function mark_order_as_failed( \WC_Order $order, $message = '', FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
/* translators: Placeholders: %s - payment gateway title */
$order_note = sprintf( esc_html__( '%s Payment Failed', 'woocommerce-plugin-framework' ), $this->get_gateway()->get_method_title() );
if ( $message ) {
$order_note .= " ({$message})";
}
// Mark order as failed if not already set, otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
if ( ! $order->has_status( 'failed' ) ) {
$order->update_status( 'failed', $order_note );
} else {
$order->add_order_note( $order_note );
}
}
/**
* Marks an order as cancelled.
*
* @since 5.4.0
*
* @param \WC_Order $order order object
* @param string $message order note message
* @param FrameworkBase\SV_WC_Payment_Gateway_API_Response|null $response
*/
public function mark_order_as_cancelled( \WC_Order $order, $message, FrameworkBase\SV_WC_Payment_Gateway_API_Response $response = null ) {
/* translators: Placeholders: %s - payment gateway title */
$order_note = sprintf( __( '%s Transaction Cancelled', 'woocommerce-plugin-framework' ), $this->get_gateway()->get_method_title() );
if ( $message ) {
$order_note .= " ({$message})";
}
// Mark order as cancelled if not already set
if ( ! $order->has_status( 'cancelled' ) ) {
$order->update_status( 'cancelled', $order_note );
} else {
$order->add_order_note( $order_note );
}
}
/** Conditional methods *******************************************************************************************/
/** Getter methods ************************************************************************************************/
/**
* Gets the gateway object.
*
* @since 5.4.0
*
* @return FrameworkBase\SV_WC_Payment_Gateway
*/
public function get_gateway() {
return $this->gateway;
}
/** Setter methods ************************************************************************************************/
}
endif;