Skip to content

Commit

Permalink
Merge branch 'trunk' into compat-woo-8.8
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffpaul authored May 6, 2024
2 parents 389dd30 + 690ada9 commit fd2e131
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 137 deletions.
7 changes: 7 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
*** PayFast Changelog ***

= 1.6.3 - 2024-05-02 =
* Fix - Enforce amount match check for all payments in the Payfast ITN handler.
* Dev - Bump WooCommerce "tested up to" version 8.7.
* Dev - Bump WooCommerce minimum supported version to 8.5.
* Dev - Bump WordPress "tested up to" version 6.5.
* Dev - Bump WordPress minimum supported version to 6.3.

= 1.6.2 - 2024-03-25 =
* Dev - Bump WooCommerce "tested up to" version 8.6.
* Dev - Bump WooCommerce minimum supported version to 8.4.
Expand Down
205 changes: 74 additions & 131 deletions includes/class-wc-gateway-payfast.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ public function __construct() {
// Supported functionality.
$this->supports = array(
'products',
'pre-orders',
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
Expand Down Expand Up @@ -182,7 +181,6 @@ public function __construct() {
add_action( 'woocommerce_receipt_payfast', array( $this, 'receipt_page' ) );
add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
add_action( 'woocommerce_subscription_status_cancelled', array( $this, 'cancel_subscription_listener' ) );
add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_payments' ) );
add_action( 'admin_notices', array( $this, 'admin_notices' ) );

// Add fees to order.
Expand Down Expand Up @@ -425,6 +423,14 @@ public function generate_payfast_form( $order_id ) {
'source' => 'WooCommerce-Free-Plugin',
);

// Add Change subscription payment method parameters.
if ( isset( $_GET['change_pay_method'] ) ) {
$subscription_id = absint( wp_unslash( $_GET['change_pay_method'] ) );
if ( $this->is_subscription( $subscription_id ) && $order_id === $subscription_id && floatval( 0 ) === floatval( $order->get_total() ) ) {
$this->data_to_send['custom_str4'] = 'change_pay_method';
}
}

/*
* Check If changing payment method.
* We have to generate Tokenization (ad-hoc) token to charge future payments.
Expand Down Expand Up @@ -453,17 +459,6 @@ public function generate_payfast_form( $order_id ) {
}
}

// pre-order: add the subscription type for pre order that require tokenization
// at this point we assume that the order pre order fee and that
// we should only charge that on the order. The rest will be charged later.
if (
$this->order_contains_pre_order( $order_id )
&& $this->order_requires_payment_tokenization( $order_id )
) {
$this->data_to_send['amount'] = $this->get_pre_order_fee( $order_id );
$this->data_to_send['subscription_type'] = '2';
}

/**
* Allow others to modify payment data before that is sent to Payfast.
*
Expand Down Expand Up @@ -569,17 +564,20 @@ class="button cancel"
* }
*/
public function process_payment( $order_id ) {

if ( $this->order_contains_pre_order( $order_id )
&& $this->order_requires_payment_tokenization( $order_id )
&& ! $this->cart_contains_pre_order_fee() ) {
throw new Exception( 'Payfast does not support transactions without any upfront costs or fees. Please select another gateway' );
$order = wc_get_order( $order_id );
$redirect = $order->get_checkout_payment_url( true );

// Check if the payment is for changing payment method.
if ( isset( $_GET['change_payment_method'] ) ) {
$sub_id = absint( wp_unslash( $_GET['change_payment_method'] ) );
if ( $this->is_subscription( $sub_id ) && floatval( 0 ) === floatval( $order->get_total() ) ) {
$redirect = add_query_arg( 'change_pay_method', $sub_id, $redirect );
}
}

$order = wc_get_order( $order_id );
return array(
'result' => 'success',
'redirect' => $order->get_checkout_payment_url( true ),
'redirect' => $redirect,
);
}

Expand Down Expand Up @@ -678,32 +676,71 @@ public function handle_itn_request( $data ) {
}
}

/**
* Handle Changing Payment Method.
* - Save payfast subscription token to handle future payment
* - (for Payfast to Payfast payment method change) Cancel old token, as future payment will be handle with new token
*
* Note: The change payment method is handled before the amount mismatch check, as it doesn't involve an actual payment (0.00) and only token updates are handled here.
*/
if (
! $payfast_error &&
isset( $data['custom_str4'] ) &&
'change_pay_method' === wc_clean( $data['custom_str4'] ) &&
$this->is_subscription( $order_id ) &&
floatval( 0 ) === floatval( $data['amount_gross'] )
) {
if ( self::get_order_prop( $order, 'order_key' ) !== $order_key ) {
$this->log( 'Order key does not match' );
exit;
}

$this->log( '- Change Payment Method' );
$status = strtolower( $data['payment_status'] );
if ( 'complete' === $status && isset( $data['token'] ) ) {
$token = sanitize_text_field( $data['token'] );
$subscription = wcs_get_subscription( $order_id );
if ( ! empty( $subscription ) && ! empty( $token ) ) {
$old_token = $this->_get_subscription_token( $subscription );
// Cancel old subscription token of subscription if we have it.
if ( ! empty( $old_token ) ) {
$this->cancel_subscription_listener( $subscription );
}

// Set new subscription token on subscription.
$this->_set_subscription_token( $token, $subscription );
$this->log( 'Payfast token updated on Subcription: ' . $order_id );
}
}
return;
}

// Check data against internal order.
if ( ! $payfast_error && ! $payfast_done ) {
$this->log( 'Check data against internal order' );

// alter order object to be the renewal order if
// the ITN request comes as a result of a renewal submission request.
$description = json_decode( $data['item_description'] );

if ( ! empty( $description->renewal_order_id ) ) {
$renewal_order = wc_get_order( $description->renewal_order_id );
if ( ! empty( $renewal_order ) && function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $renewal_order ) ) {
$order = $renewal_order;
}
}

// Check order amount.
if ( ! $this->amounts_equal( $data['amount_gross'], self::get_order_prop( $order, 'order_total' ) )
&& ! $this->order_contains_pre_order( $order_id )
&& ! $this->order_contains_subscription( $order_id )
&& ! $this->is_subscription( $order_id ) ) { // if changing payment method.
if ( ! $this->amounts_equal( $data['amount_gross'], self::get_order_prop( $order, 'order_total' ) ) ) {
$payfast_error = true;
$payfast_error_message = PF_ERR_AMOUNT_MISMATCH;
} elseif ( strcasecmp( $data['custom_str1'], self::get_order_prop( $order, 'order_key' ) ) !== 0 ) {
} elseif ( strcasecmp( $data['custom_str1'], self::get_order_prop( $original_order, 'order_key' ) ) !== 0 ) {
// Check session ID.
$payfast_error = true;
$payfast_error_message = PF_ERR_SESSIONID_MISMATCH;
}
}

// alter order object to be the renewal order if
// the ITN request comes as a result of a renewal submission request.
$description = json_decode( $data['item_description'] );

if ( ! empty( $description->renewal_order_id ) ) {
$order = wc_get_order( $description->renewal_order_id );
}

// Get internal order and verify it hasn't already been processed.
if ( ! $payfast_error && ! $payfast_done ) {
$this->log_order_details( $order );
Expand Down Expand Up @@ -779,31 +816,6 @@ public function handle_itn_request( $data ) {

$status = strtolower( $data['payment_status'] );

/**
* Handle Changing Payment Method.
* - Save payfast subscription token to handle future payment
* - (for Payfast to Payfast payment method change) Cancel old token, as future payment will be handle with new token
*/
if ( $this->is_subscription( $order_id ) && floatval( 0 ) === floatval( $data['amount_gross'] ) ) {
$this->log( '- Change Payment Method' );
if ( 'complete' === $status && isset( $data['token'] ) ) {
$token = sanitize_text_field( $data['token'] );
$subscription = wcs_get_subscription( $order_id );
if ( ! empty( $subscription ) && ! empty( $token ) ) {
$old_token = $this->_get_subscription_token( $subscription );
// Cancel old subscription token of subscription if we have it.
if ( ! empty( $old_token ) ) {
$this->cancel_subscription_listener( $subscription );
}

// Set new subscription token on subscription.
$this->_set_subscription_token( $token, $subscription );
$this->log( 'Payfast token updated on Subcription: ' . $order_id );
}
}
return;
}

$subscriptions = array();
if ( function_exists( 'wcs_get_subscriptions_for_renewal_order' ) && function_exists( 'wcs_get_subscriptions_for_order' ) ) {
$subscriptions = array_merge(
Expand Down Expand Up @@ -915,31 +927,8 @@ public function handle_itn_payment_complete( $data, $order, $subscriptions ) {
}
}

// The same mechanism (adhoc token) is used to capture payment later.
if ( $this->order_contains_pre_order( $order_id )
&& $this->order_requires_payment_tokenization( $order_id ) ) {

$token = sanitize_text_field( $data['token'] );
$is_pre_order_fee_paid = $order->get_meta( '_pre_order_fee_paid', true ) === 'yes';

if ( ! $is_pre_order_fee_paid ) {
/* translators: 1: gross amount 2: payment id */
$order->add_order_note( sprintf( esc_html__( 'Payfast pre-order fee paid: R %1$s (%2$s)', 'woocommerce-gateway-payfast' ), $data['amount_gross'], $data['pf_payment_id'] ) );
$this->_set_pre_order_token( $token, $order );
// Set order to pre-ordered.
WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
$order->update_meta_data( '_pre_order_fee_paid', 'yes' );
$order->save_meta_data();
WC()->cart->empty_cart();
} else {
/* translators: 1: gross amount 2: payment id */
$order->add_order_note( sprintf( esc_html__( 'Payfast pre-order product line total paid: R %1$s (%2$s)', 'woocommerce-gateway-payfast' ), $data['amount_gross'], $data['pf_payment_id'] ) );
$order->payment_complete( $data['pf_payment_id'] );
$this->cancel_pre_order_subscription( $token );
}
} else {
$order->payment_complete( $data['pf_payment_id'] );
}
// Mark payment as complete.
$order->payment_complete( $data['pf_payment_id'] );

$debug_email = $this->get_option( 'debug_email', get_option( 'admin_email' ) );
$vendor_name = get_bloginfo( 'name', 'display' );
Expand Down Expand Up @@ -1132,31 +1121,10 @@ protected function _delete_renewal_flag( $subscription ) {
$subscription->save_meta_data();
}

/**
* Store the Payfast pre_order_token token
*
* @param string $token Pre-order token.
* @param WC_Order $order Order object.
*/
protected function _set_pre_order_token( $token, $order ) {
$order->update_meta_data( '_payfast_pre_order_token', $token );
$order->save_meta_data();
}

/**
* Retrieve the Payfast pre-order token for a given order id.
*
* @param WC_Order $order Order object.
* @return mixed
*/
protected function _get_pre_order_token( $order ) {
return $order->get_meta( '_payfast_pre_order_token', true );
}

/**
* Wrapper for WooCommerce subscription function wc_is_subscription.
*
* @param WC_Order $order The order.
* @param WC_Order|int $order The order.
* @return bool
*/
public function is_subscription( $order ) {
Expand Down Expand Up @@ -1458,32 +1426,7 @@ protected function _generate_parameter_string( $api_data, $sort_data_before_merg
* @param WC_Order $order Order object.
*/
public function process_pre_order_payments( $order ) {

// The total amount to charge is the the order's total.
$total = $order->get_total() - $this->get_pre_order_fee( self::get_order_prop( $order, 'id' ) );
$token = $this->_get_pre_order_token( $order );

if ( ! $token ) {
return;
}
// Get the payment token and attempt to charge the transaction.
$item_name = 'pre-order';
$results = $this->submit_ad_hoc_payment( $token, $total, $item_name, '' );

if ( is_wp_error( $results ) ) {
$order->update_status(
'failed',
sprintf(
/* translators: 1: error code 2: error message */
esc_html__( 'Payfast Pre-Order payment transaction failed (%1$s:%2$s)', 'woocommerce-gateway-payfast' ),
$results->get_error_code(),
$results->get_error_message()
)
);
return;
}

// Payment completion will be handled by ITN callback.
wc_deprecated_function( 'process_pre_order_payments', 'x.x.x' );
}

/**
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "woocommerce-gateway-payfast",
"title": "WooCommerce Gateway Payfast",
"version": "1.6.2",
"version": "1.6.3",
"license": "GPL-3.0",
"homepage": "https://wordpress.org/plugins/woocommerce-payfast-gateway/",
"repository": {
Expand Down
9 changes: 8 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: credit card, payfast, payment request, woocommerce, automattic
Requires at least: 6.3
Tested up to: 6.5
Requires PHP: 7.4
Stable tag: 1.6.2
Stable tag: 1.6.3
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html

Expand Down Expand Up @@ -38,6 +38,13 @@ If you get stuck, you can ask for help in the Plugin Forum.

== Changelog ==

= 1.6.3 - 2024-05-02 =
* Fix - Enforce amount match check for all payments in the Payfast ITN handler.
* Dev - Bump WooCommerce "tested up to" version 8.7.
* Dev - Bump WooCommerce minimum supported version to 8.5.
* Dev - Bump WordPress "tested up to" version 6.5.
* Dev - Bump WordPress minimum supported version to 6.3.

= 1.6.2 - 2024-03-25 =
* Dev - Bump WooCommerce "tested up to" version 8.6.
* Dev - Bump WooCommerce minimum supported version to 8.4.
Expand Down
4 changes: 2 additions & 2 deletions woocommerce-gateway-payfast.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Description: Receive payments using the South African Payfast payments provider.
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* Version: 1.6.2
* Version: 1.6.3
* Requires at least: 6.3
* Tested up to: 6.5
* WC requires at least: 8.6
Expand All @@ -19,7 +19,7 @@

defined( 'ABSPATH' ) || exit;

define( 'WC_GATEWAY_PAYFAST_VERSION', '1.6.2' ); // WRCS: DEFINED_VERSION.
define( 'WC_GATEWAY_PAYFAST_VERSION', '1.6.3' ); // WRCS: DEFINED_VERSION.
define( 'WC_GATEWAY_PAYFAST_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
define( 'WC_GATEWAY_PAYFAST_PATH', untrailingslashit( plugin_dir_path( __FILE__ ) ) );

Expand Down

0 comments on commit fd2e131

Please sign in to comment.