*/
class Payment {
/**
* The ID of this plugin.
*
* @since 3.0.0
* @access private
* @var string $plugin_name The ID of this plugin.
*/
private $plugin_name;
/**
* The version of this plugin.
*
* @since 3.0.0
* @access private
* @var string $version The current version of this plugin.
*/
private $version;
private Loader $loader;
/**
* Initialize the class and set its properties.
*
* @since 3.0.0
* @param string $plugin_name The name of this plugin.
* @param string $version The version of this plugin.
*/
public function __construct( $plugin_name, $version, Loader $loader ) {
$this->plugin_name = $plugin_name;
$this->version = $version;
$this->loader = $loader;
$this->loader->add_action( 'plugins_loaded', $this, 'initialize_payment_gateways' );
$this->loader->add_action( 'woocommerce_before_checkout_form', $this, 'check_payment_status' );
}
/**
* Initialize all payment gateways
*
* @since 3.0.0
*/
public function initialize_payment_gateways() {
//WooCommerce
new Payment\Gateway\WooCommerce( true );
}
/**
* Returns price excluding taxes
*
* @since 3.0.0
*/
public static function get_rate_without_taxes( $method, $rate ) {
if ( empty( $rate[$method] ) ) {
return 0;
}
$rate = $rate[$method];
return $rate->cost;
}
/**
* Returns price including taxes
*
* @since 3.0.0
*/
public static function get_rate_with_taxes( $method, $rate ) {
if ( empty( $rate[$method] ) ) {
return 0;
}
$rate = $rate[$method];
$price = $rate->cost;
if ( empty( $rate->taxes ) ) {
return $price;
}
foreach ( $rate->taxes as $tax ) {
$price += $tax;
}
return $price;
}
/**
* Update banklinks
*
* @since 3.0.0
*/
public static function update_banklinks() {
$obj = new Payment\Gateway\WooCommerce( true );
$obj->mc_banklinks_reload( true );
}
/**
* Add check for unsuccesful payment redirects
* Displays notice if something went wrong
*
* @since 3.0.12
*/
public function check_payment_status()
{
if ( isset ( $_GET['mc_payment_status'] ) ) {
switch( $_GET['mc_payment_status'] ) {
case 'failed':
wc_add_notice( __( 'Payment failed', 'wc_makecommerce_domain' ), 'error' );
break;
case 'cancelled':
wc_add_notice( __( 'Payment transaction cancelled', 'wc_makecommerce_domain' ), 'error' );
break;
case 'expired':
wc_add_notice( __( 'Payment transaction expired', 'wc_makecommerce_domain' ), 'error' );
break;
}
// Avoid multiple error messages
unset($_GET['mc_payment_status']);
}
}
/**
* Check payment and process order if completed, canceled
*
* @since 3.0.0
*/
public static function check_payment( $settings = array() ) {
global $woocommerce;
$api = \MakeCommerce::get_api();
//set the correct language
if ( isset( $_GET["lang1"] ) ) {
\MakeCommerce\i18n::switch_language( $_GET["lang1"] );
}
$returnUrl = home_url();
$request = stripslashes_deep( $_POST );
if ( $api->verifyMac( $request ) ) {
$data = $api->extractRequestData( $request );
$transactionId = false;
$reference = false;
if ( !empty( $data['error'] ) ) {
//add status and send client back to checkout
return add_query_arg( 'mc_payment_status', 'failed', wc_get_checkout_url() );
}
if ( !empty( $data['message_type'] ) ) {
if ( $data['message_type'] === 'payment_return' ) {
$transactionId = $data['transaction'];
$reference = $data['reference'];
$paymentStatus = $data['status'];
$totalAmount = $data['amount'];
}
if ( $data['message_type'] === 'token_return' ) {
if ( !empty( $data['transaction']['reference'] ) ) {
$reference = $data['transaction']['reference'];
}
$paymentStatus = $data['transaction']['status'];
$transactionId = $data['transaction']['id'];
}
}
}
// if we didn't find reference to an Order, we send user to shop landing-page. TODO: could be improved
if ( empty( $transactionId ) ) {
return $returnUrl;
}
//get the post id using transactionid
$orderId = self::get_postid_using_metakey( '_makecommerce_transaction_id', $transactionId );
$order = wc_get_order( $orderId );
// if we didn't find the Order, we send user to shop landing-page. TODO: could be improved
if ( !$order || !$order->get_id() ) {
return $returnUrl;
}
$returnUrl = $order->get_meta( '_makecommerce_order_received_url', true );
if ( !$returnUrl ) {
$returnUrl = $order->get_checkout_order_received_url();
}
// Disregard with subscription method change as amounts do not match (0 vs order total)
// Only check sums if $totalAmount is set
// Trx total and order total did not match
if ( $order->get_status() !== 'active' && isset( $totalAmount ) && $totalAmount != $order->get_total() ) {
if ( $paymentStatus == 'COMPLETED' ) {
$notes[] = sprintf('%s: %s.',
__( 'Order total and transaction amount did not match! Please contact us at', 'wc_makecommerce_domain' ),
__( 'support@maksekeskus.ee', 'wc_makecommerce_domain' )
);
$notes[] = sprintf('%s: %s.',
__( 'Transaction ID that was assigned to this order', 'wc_makecommerce_domain' ),
''.$transactionId.''
);
$notes[] = sprintf('%s: %s€',
__( 'Amount that was paid', 'wc_makecommerce_domain' ),
$totalAmount
);
$order->add_order_note( implode( "\r\n", $notes ) );
// Empty the cart so the customer is not tempted to pay again
$woocommerce->cart->empty_cart();
return $returnUrl;
}
// If the payment was not complete then send the customer back to checkout
return wc_get_checkout_url();
}
if ( $paymentStatus == 'EXPIRED' || $paymentStatus == 'CANCELLED' || $paymentStatus == 'COMPLETED' ) {
//get transaction to update the payment method.
$transaction = $api->getTransaction( $transactionId );
//make sure the payment method is the same for simplecheckout and normal version.
$paymentMethod = "";
if ( isset( $transaction->type ) && isset( $transaction->method ) ) {
$paymentMethod = $transaction->channel;
}
//update _makecommerce_preselected_method
$order->update_meta_data( '_makecommerce_preselected_method', $paymentMethod );
$order->save();
}
if ( $paymentStatus == 'CANCELLED' || ( $paymentStatus == 'EXPIRED' && $order->get_status() == "pending" ) ) {
//send client back to checkout
$returnUrl = wc_get_checkout_url();
}
// Avoid duplicate method changes for subscriptions
$check_status = true;
if ( !empty( $data['token']['id'] ) && !empty( $data['token']['multiuse'] ) ) {
// Order is a subscription and saved token is different from response token
if ( $order->get_meta( '_makecommerce_payment_token', true) !== $data['token']['id'] ) {
$check_status = false;
}
}
// Lock processing for this order+status to avoid race conditions between callback and redirect
$lock_key = '_makecommerce_payment_lock_status_' . $paymentStatus;
$locked = add_post_meta( $orderId, $lock_key, time(), true );
if ( ! $locked ) {
return $returnUrl;
}
//check if we already processed this status in the past.
if ( $check_status && $order->get_meta( '_makecommerce_payment_processed_status', true ) == $paymentStatus ) {
return $returnUrl;
}
if ( $paymentStatus === 'COMPLETED' ) {
$order->update_meta_data( '_makecommerce_payment_processed_status', $paymentStatus );
$order->save();
}
switch( $paymentStatus ) {
case 'CANCELLED':
// Do not update status when disabled in settings
if ( ( $settings['disable_cancelled_payment_update'] ?? '' ) !== 'yes' ) {
// Update automatically
$order->update_status( 'cancelled' );
$order->update_meta_data( '_makecommerce_payment_processed_status', $paymentStatus );
}
$returnUrl = add_query_arg( 'mc_payment_status', 'cancelled', $returnUrl );
break;
case 'EXPIRED':
//only update if order status is pending payment
if ( $order->get_status() == "pending" ) {
// Do not update order status if disabled in settings
if ( ( $settings['disable_expired_payment_update'] ?? '' ) !== 'yes' ) {
// Update automatically
$order->update_status( 'cancelled' );
$order->update_meta_data( '_makecommerce_payment_processed_status', $paymentStatus );
}
$returnUrl = add_query_arg( 'mc_payment_status', 'expired', $returnUrl );
}
break;
case 'COMPLETED':
$orderNote = array();
$transactionIdText = __( 'Transaction ID', 'wc_makecommerce_domain' );
$paymentOptionText = __( 'Payment option', 'wc_makecommerce_domain' );
$transactionIdText = \MakeCommerce\i18n::get_string_from_mo( 'Transaction ID', 'wc_makecommerce_domain', \MakeCommerce\i18n::get_site_default_language() );
$paymentOptionText = \MakeCommerce\i18n::get_string_from_mo( 'Payment option', 'wc_makecommerce_domain', \MakeCommerce\i18n::get_site_default_language() );
$orderNote[] = $transactionIdText . ': '.$transactionId.'';
$orderNote[] = $paymentOptionText . ': ' . $order->get_meta( '_makecommerce_preselected_method', true );
$order->add_order_note( implode( "\r\n", $orderNote ) );
if ( !empty( $data['token'] ) && !empty( $data['token']['multiuse'] ) ) {
$order->update_meta_data( '_makecommerce_payment_token', $data['token']['id'] );
$order->update_meta_data( '_makecommerce_payment_token_valid_until', $data['token']['valid_until'] );
}
if ( isset( $_GET['lang1'] ) ) {
$order->update_meta_data( 'wpml_language', $_GET["lang1"] );
}
$order->payment_complete( $transactionId );
$woocommerce->cart->empty_cart();
break;
}
// Save all changes to order
$order->save();
return $returnUrl;
}
/**
* Returns post_id using transaction id or false if not found
*
* @since 3.0.4
*/
public static function get_postid_using_metakey( $meta_key, $transactionId ) {
global $wpdb;
if ( class_exists( OrderUtil::class )
&& OrderUtil::custom_orders_table_usage_is_enabled()
) {
// HPOS
$results = $wpdb->get_results(
$wpdb->prepare("
SELECT {$wpdb->prefix}wc_orders.id
FROM {$wpdb->prefix}wc_orders
INNER JOIN {$wpdb->prefix}wc_orders_meta AS meta ON meta.order_id = {$wpdb->prefix}wc_orders.id AND meta.meta_key = %s
WHERE meta.meta_value = %s
",
$meta_key,
$transactionId
)
);
if ( isset( $results[0]->id ) ) {
return $results[0]->id;
}
} else {
// Legacy DB
$wpdb->query("SELECT `post_id` FROM $wpdb->postmeta WHERE `meta_key` = '". $meta_key ."' AND `meta_value` = '" . $transactionId . "'");
if ( isset( $wpdb->last_result[0]->post_id ) ) {
return $wpdb->last_result[0]->post_id;
}
}
return false;
}
}