Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions inc/cleantalk-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
/* The Fluent Form have the direct integration */
$_cleantalk_hooked_actions[] = 'fluentform_submit';

/* WooCommerce Stripe UPE confirms the card on Add payment method. Direct integration. */
$_cleantalk_hooked_actions[] = 'wc_stripe_create_and_confirm_setup_intent';

/* Estimation Forms have the direct integration */
if ( class_exists('LFB_Core') ) {
$_cleantalk_hooked_actions[] = 'send_email';
Expand Down
35 changes: 35 additions & 0 deletions inc/cleantalk-public-validate-skip-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function skip_for_ct_contact_form_validate_postdata()
apbct_is_in_uri('/wc-api') ||
apbct_is_in_uri('wc-api=WC_Gateway_Tpay_Basic') || // Tpay payment Gateway plugin
(isset($_POST['wc_reset_password'], $_POST['_wpnonce'], $_POST['_wp_http_referer'])) || //WooCommerce recovery password form
apbct_is_woocommerce_add_payment_method_form() || // WooCommerce Add payment method, checked as an order
(isset($_POST['woocommerce-login-nonce'], $_POST['login'], $_POST['password'], $_POST['_wp_http_referer'])) || //WooCommerce login form
(isset($_POST['provider'], $_POST['authcode']) && $_POST['provider'] === 'Two_Factor_Totp') || //TwoFactor authorization
(isset($_GET['wc-ajax']) && $_GET['wc-ajax'] === 'sa_wc_buy_now_get_ajax_buy_now_button') || //BuyNow add to cart
Expand All @@ -74,6 +75,38 @@ function skip_for_ct_contact_form_validate_postdata()
return false;
}

/**
* Real WooCommerce "Add payment method" submit.
*
* The dedicated check sends the account email as an order. Skipping it here keeps the
* general checker from marking the request done. A single POST field is not enough:
* the request must hit this endpoint and carry the form nonce.
*
* @return bool
*/
function apbct_is_woocommerce_add_payment_method_form()
{
if (
! apbct_is_plugin_active('woocommerce/woocommerce.php') ||
! apbct_is_in_uri('add-payment-method') ||
Comment thread
svedge marked this conversation as resolved.
! isset($_POST['woocommerce_add_payment_method'], $_POST['payment_method'])
) {
return false;
}

$nonce = '';
if (
isset($_REQUEST['woocommerce-add-payment-method-nonce']) &&
is_string($_REQUEST['woocommerce-add-payment-method-nonce'])
) {
$nonce = $_REQUEST['woocommerce-add-payment-method-nonce'];
} elseif ( isset($_REQUEST['_wpnonce']) && is_string($_REQUEST['_wpnonce']) ) {
$nonce = $_REQUEST['_wpnonce'];
}

return wp_verify_nonce($nonce, 'woocommerce-add-payment-method') !== false;
}

/**
* Function for skip in ct_contact_form_validate(). Returns false if no exclusions found, or the key of the exclusion.
* @return false|string
Expand Down Expand Up @@ -304,6 +337,8 @@ function skip_for_ct_contact_form_validate()
(isset($_POST['pass']) && isset($_POST['_pass']))
)
),
// WooCommerce Add payment method. Direct check uses the account email and type "order".
'103' => apbct_is_woocommerce_add_payment_method_form(),
);

foreach ( $exclusions as $exclusion_key => $state ) {
Expand Down
12 changes: 12 additions & 0 deletions inc/cleantalk-public-validate.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ function ct_contact_form_validate()
Get::getString('wc-ajax') === 'wc_stripe_normalize_address' &&
apbct_is_plugin_active('woocommerce-gateway-stripe/woocommerce-gateway-stripe.php') &&
1 == check_ajax_referer('wc-stripe-express-checkout-normalize-address', 'security', false)
) ||
// Add payment method is checked on its own, with the account email and type "order".
(
apbct_is_plugin_active('woocommerce/woocommerce.php') &&
! empty($_POST) &&
apbct_is_plugin_active('woocommerce-gateway-stripe/woocommerce-gateway-stripe.php') &&
apbct_is_in_referer('add-payment-method') &&
Comment thread
svedge marked this conversation as resolved.
(
Get::getString('wc-ajax') === 'wc_stripe_create_setup_intent' ||
Get::getString('wc-ajax') === 'wc_stripe_init_setup_intent' ||
Post::getString('action') === 'wc_stripe_create_and_confirm_setup_intent'
)
)
) {
do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . 'WOOCOMMERCE_SERVICES', $_POST);
Expand Down
2 changes: 1 addition & 1 deletion inc/cleantalk-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ function apbct_settings__set_fields()
'data__protect_logged_in' => array(
'title' => __("Protect logged in Users", 'cleantalk-spam-protect'),
'description' => __(
'Turn this option on to check for spam any submissions (comments, contact forms and etc.) from registered Users.',
'Turn this option on to check for spam any submissions (comments, contact forms and etc.) from registered Users. WooCommerce Add payment method is checked as an order.',
'cleantalk-spam-protect'
),
),
Expand Down
216 changes: 216 additions & 0 deletions lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* 4.1) guest checkout (ajax)
* 4.2) auth checkout (rest)
* 5) send feedback
* 6) add payment method (logged-in customers only, checked as an order)
*/
class Woocommerce extends IntegrationByClassBase
{
Expand All @@ -57,6 +58,12 @@ public function __construct()
*/
private $blocked_order_key = '';

/**
* Text shown when Add payment method is blocked.
* @var string
*/
private $add_payment_method_block_message = '';

/**
* @return void
* @psalm-suppress PossiblyUnusedMethod
Expand Down Expand Up @@ -85,6 +92,9 @@ public function doPublicWork()
$this->addActions();
}

// Add payment method follows "Protect logged in Users", not the checkout option.
$this->addPaymentMethodActions();

// registration
if ( !$apbct->settings['forms__wc_register_from_order'] && (Request::get('wc-ajax') === 'checkout' || Request::get('wc-ajax') === 'complete_order') ) {
remove_filter('woocommerce_registration_errors', 'ct_registration_errors', 1);
Expand Down Expand Up @@ -170,6 +180,212 @@ public function addActions()
add_action('woocommerce_store_api_checkout_update_customer_from_request', [$this, 'storeApiCheckoutUpdateCustomerFromRequest'], 10, 2);
}

/**
* Intercept WooCommerce Add payment method.
*
* The account form has no email field, so the general form checker skips it.
* The customer is already logged in; the check uses that account email and
* is sent with comment_type "order". It runs only when protection of
* logged-in users is enabled.
*
* Stripe confirms the card before the account form is posted. Legacy
* setup intents use wc-ajax. The UPE form uses admin-ajax action
* wc_stripe_create_and_confirm_setup_intent, and that call attaches the
* card at Stripe. Those calls are checked too, otherwise the card is saved
* even when the later account form is blocked.
*
* @return void
*/
public function addPaymentMethodActions()
{
add_filter('woocommerce_add_payment_method_form_is_valid', [$this, 'filterAddPaymentMethodFormIsValid']);
add_action('wc_ajax_wc_stripe_create_setup_intent', [$this, 'blockStripeAddPaymentMethodAjax'], 1);
add_action('wc_ajax_wc_stripe_init_setup_intent', [$this, 'blockStripeAddPaymentMethodAjax'], 1);
// UPE confirms the card here, before the account form is posted.
add_action(
'wp_ajax_wc_stripe_create_and_confirm_setup_intent',
[$this, 'blockStripeCreateAndConfirmSetupIntentAjax'],
1
);
}

/**
* @param bool $is_valid
*
* @return bool
* @psalm-suppress PossiblyUnusedMethod, PossiblyUnusedReturnValue, UndefinedFunction
*/
public function filterAddPaymentMethodFormIsValid($is_valid)
{
if ( ! $is_valid ) {
return false;
}

if ( $this->isAddPaymentMethodAllowed() ) {
return true;
}

if ( function_exists('wc_add_notice') && $this->add_payment_method_block_message !== '' ) {
\wc_add_notice($this->add_payment_method_block_message, 'error');
}

return false;
}

/**
* Stop Stripe setup-intent calls that belong to the Add payment method page.
*
* @return void
* @psalm-suppress PossiblyUnusedMethod, UndefinedFunction
*/
public function blockStripeAddPaymentMethodAjax()
{
if ( ! $this->isAddPaymentMethodPageRequest() ) {
return;
}

if ( $this->isAddPaymentMethodAllowed() ) {
return;
}

$this->sendStripeAddPaymentMethodJsonError();
}

/**
* Stop the UPE call that creates and confirms a setup intent.
*
* That action is used only by the Add payment method form. Checkout does
* not post it. The card is attached at Stripe inside this request, so the
* later account-form block is too late.
*
* @return void
* @psalm-suppress PossiblyUnusedMethod
*/
public function blockStripeCreateAndConfirmSetupIntentAjax()
{
if ( $this->isAddPaymentMethodAllowed() ) {
return;
}

$this->sendStripeAddPaymentMethodJsonError();
}

/**
* @return void
* @psalm-suppress UndefinedFunction
*/
private function sendStripeAddPaymentMethodJsonError()
{
if ( function_exists('wp_send_json_error') ) {
\wp_send_json_error(
array(
'error' => array(
'message' => $this->add_payment_method_block_message,
),
)
);
}
}

/**
* Whether this request may add a payment method.
*
* @return bool
* @psalm-suppress PossiblyUnusedMethod
*/
public function isAddPaymentMethodAllowed()
{
global $apbct;

if ( empty($apbct->settings['data__protect_logged_in']) ) {
do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);

return true;
}

if ( ! is_user_logged_in() ) {
do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);

return true;
}

$user = wp_get_current_user();
$sender_email = isset($user->user_email) ? $user->user_email : '';
$sender_nickname = isset($user->display_name) ? $user->display_name : '';

$message = array();
$payment_method = Post::getString('payment_method');
if ( $payment_method === '' ) {
$payment_method = Post::getString('payment_method_type');
}
if ( $payment_method !== '' ) {
$message['payment_method'] = $payment_method;
}

IMetricService::seek(
$this,
__FUNCTION__
);

$base_call_result = apbct_base_call(
array(
'message' => $message,
'sender_email' => $sender_email,
'sender_nickname' => $sender_nickname,
'post_info' => array(
'comment_type' => 'order',
'post_url' => Server::get('HTTP_REFERER'),
),
'sender_info' => array(
'sender_url' => null,
IMetricDTO::$SENDER_INFO_KEY => IMetricService::finalizeDTO($this),
),
)
);

if ( ! isset($base_call_result['ct_result']) ) {
return true;
}

$ct_result = $base_call_result['ct_result'];
ct_hash($ct_result->id);

// An empty response means the call was skipped. Only an explicit denial blocks.
if ( ! isset($ct_result->allow) || (int) $ct_result->allow !== 0 ) {
return true;
}

$this->add_payment_method_block_message = $this->getAddPaymentMethodBlockMessage($ct_result);

return false;
}

/**
* The Stripe wc-ajax call is not the account form itself. The page that opened it is.
*
* @return bool
*/
private function isAddPaymentMethodPageRequest()
{
return apbct_is_in_referer('add-payment-method') || apbct_is_in_uri('add-payment-method');
Comment thread
svedge marked this conversation as resolved.
}

/**
* @param object $ct_result
*
* @return string
*/
private function getAddPaymentMethodBlockMessage($ct_result)
{
global $apbct;

if ( ! empty($apbct->settings['forms__wc_show_rejection_message']) && ! empty($ct_result->comment) ) {
return (string) $ct_result->comment;
}

return __('Unable to add payment method to your account.', 'cleantalk-spam-protect');
}

public function addHoneypotField($fields)
{
if (apbct_exclusions_check__url()) {
Expand Down
Loading
Loading