Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(modal-checkout): add custom modal checkout validation #2008

Closed
wants to merge 4 commits into from
Closed
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
65 changes: 36 additions & 29 deletions includes/class-modal-checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public static function init() {
add_action( 'wp', [ __CLASS__, 'process_checkout_request' ] );
add_action( 'wp_ajax_abandon_modal_checkout', [ __CLASS__, 'process_abandon_checkout' ] );
add_action( 'wp_ajax_nopriv_abandon_modal_checkout', [ __CLASS__, 'process_abandon_checkout' ] );
add_action( 'wp_ajax_validate_modal_checkout', [ __CLASS__, 'validate_checkout_request' ] );
add_action( 'wp_ajax_nopriv_validate_modal_checkout', [ __CLASS__, 'validate_checkout_request' ] );

add_filter( 'wp_redirect', [ __CLASS__, 'pass_url_param_on_redirect' ] );
add_filter( 'woocommerce_cart_product_cannot_be_purchased_message', [ __CLASS__, 'woocommerce_cart_product_cannot_be_purchased_message' ], 10, 2 );
Expand Down Expand Up @@ -378,11 +380,7 @@ function ( $item ) {
* Process abandon checkout for modal.
*/
public static function process_abandon_checkout() {
if ( ! defined( 'DOING_AJAX' ) ) {
return;
}

if ( ! self::is_modal_checkout() ) {
if ( ! defined( 'DOING_AJAX' ) || ! self::is_modal_checkout() ) {
return;
}

Expand All @@ -401,6 +399,26 @@ public static function process_abandon_checkout() {
wp_die();
}

/**
* Process modal checkout validation.
*/
public static function validate_checkout_request() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miguelpeixe, I'd love your opinion on this approach for "hijacking" the form validation from Woo. In particular, do you think its safe to rename the woocommerce-process-checkout-nonce on the front end, then reassign in the backend before triggering WC_Checkout::process_checkout?

if ( ! defined( 'DOING_AJAX' ) || ! self::is_modal_checkout() ) {
return;
}
if ( ! check_ajax_referer( 'newspack_modal_checkout_nonce' ) ) {
wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'newspack-blocks' ) ] );
wp_die();
}
// WC process_checkout expects checkout nonce to be in REQUEST and update totals flag to be in POST.
$_REQUEST['woocommerce-process-checkout-nonce'] = filter_input( INPUT_POST, 'newspack-process-checkout-nonce', FILTER_SANITIZE_STRING );
$_POST['woocommerce_checkout_update_totals'] = 1;
// We don't want to validate payment methods at this point, so we temporarily override needs payment flag.
add_filter( 'woocommerce_cart_needs_payment', '__return_false' );
\WC()->checkout()->process_checkout();
remove_filter( 'woocommerce_cart_needs_payment', '__return_false' );
}

/**
* Filter unsupported Woo Payments features.
*
Expand Down Expand Up @@ -787,14 +805,17 @@ public static function dequeue_scripts() {
global $wp_scripts, $wp_styles;

$payment_gateways = \WC()->payment_gateways->get_available_payment_gateways();
$allowed_gateway_assets = [];
$allowed_gateway_assets = array_keys( $payment_gateways );
if ( ! empty( $payment_gateways ) ) {
// Payment gateway id doesn't always match the plugin slug, so account for these cases.
foreach ( array_keys( $payment_gateways ) as $gateway ) {
$class = get_class( $payment_gateways[ $gateway ] );
$plugin_file = ( new \ReflectionClass( $class ) )->getFileName();
$plugin_base = \plugin_basename( $plugin_file );
$plugin_slug = explode( '/', $plugin_base )[0];
$allowed_gateway_assets[] = $plugin_slug;
$class = get_class( $payment_gateways[ $gateway ] );
$plugin_file = ( new \ReflectionClass( $class ) )->getFileName();
$plugin_base = \plugin_basename( $plugin_file );
$plugin_slug = explode( '/', $plugin_base )[0];
if ( ! in_array( $plugin_slug, $allowed_gateway_assets, true ) ) {
$allowed_gateway_assets[] = $plugin_slug;
}
}
}

Expand All @@ -806,20 +827,12 @@ public static function dequeue_scripts() {
$allowed_styles = apply_filters( 'newspack_blocks_modal_checkout_allowed_styles', self::$allowed_styles );
foreach ( $wp_styles->registered as $handle => $wp_style ) {
$allowed = false;
foreach ( $allowed_styles as $allowed_style ) {
if ( 0 === strpos( $handle, $allowed_style ) ) {
foreach ( array_merge( $allowed_styles, $allowed_gateway_assets ) as $allowed_style ) {
if ( 0 === strpos( $handle, $allowed_style ) || false !== strpos( $wp_style->src, $allowed_style ) ) {
$allowed = true;
break;
}
}
if ( ! empty( $payment_gateways ) ) {
foreach ( $allowed_gateway_assets as $gateway ) {
if ( false !== strpos( $wp_style->src, $gateway ) ) {
$allowed = true;
break;
}
}
}
if ( ! $allowed ) {
wp_dequeue_style( $handle );
}
Expand All @@ -833,14 +846,8 @@ public static function dequeue_scripts() {
$allowed_scripts = apply_filters( 'newspack_blocks_modal_checkout_allowed_scripts', self::$allowed_scripts );
foreach ( $wp_scripts->registered as $handle => $wp_script ) {
$allowed = false;
foreach ( $allowed_scripts as $allowed_script ) {
if ( 0 === strpos( $handle, $allowed_script ) ) {
$allowed = true;
break;
}
}
foreach ( $allowed_gateway_assets as $gateway ) {
if ( false !== strpos( $wp_script->src, $gateway ) ) {
foreach ( array_merge( $allowed_scripts, $allowed_gateway_assets ) as $allowed_script ) {
if ( 0 === strpos( $handle, $allowed_script ) || false !== strpos( $wp_script->src, $allowed_script ) ) {
$allowed = true;
break;
}
Expand Down
39 changes: 26 additions & 13 deletions src/modal-checkout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,32 +615,45 @@ import { domReady } from './utils';
$genericErrors.remove();
}

const checkoutNonce = $form.find( 'input[name="woocommerce-process-checkout-nonce"]' );
const removeFromValidation = [
'save_user_in_woopay',
// Remove these fields to avoid triggering Woo's validation.
'woocommerce_checkout_place_order',
'woocommerce-process-checkout-nonce',
];
// Serialize form and remove fields that shouldn't be included for validation.
const serializedForm = $form.serializeArray().filter(
item => ! removeFromValidation.includes( item.name )
);
// Add 'update totals' parameter so it just performs validation.
serializedForm.push( { name: 'woocommerce_checkout_update_totals', value: '1' } );
if ( checkoutNonce.length ) {
serializedForm.push( {
name: 'newspack-process-checkout-nonce',
value: checkoutNonce.val(),
} );
}
serializedForm.push( { name: 'action', value: 'validate_modal_checkout' } );
serializedForm.push( { name: '_wpnonce', value: newspackBlocksModalCheckout.checkout_nonce } );
// Ajax request.
$.ajax( {
type: 'POST',
url: wc_checkout_params.checkout_url,
url: newspackBlocksModalCheckout.ajax_url,
data: serializedForm,
dataType: 'html',
success: response => {
let result;
try {
result = JSON.parse( response );
} catch ( e ) {
result = {
messages:
'<div class="woocommerce-error">' +
wc_checkout_params.i18n_checkout_error +
'</div>',
};
if ( typeof response === 'object' ) {
result = response;
} else {
try {
result = JSON.parse( response );
} catch ( e ) {
result = {
messages:
'<div class="woocommerce-error">' +
wc_checkout_params.i18n_checkout_error +
'</div>',
};
}
}

// Reload page
Expand Down
2 changes: 1 addition & 1 deletion src/modal-checkout/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,8 @@ domReady( () => {
if ( ! entries || ! entries.length ) {
return;
}
iframe.scrollIntoView( { behavior: 'smooth', block: 'start' } );
if ( ! iframe.contentDocument ) {
iframe.scrollIntoView( { behavior: 'smooth', block: 'start' } );
return;
}
const contentRect = entries[ 0 ].contentRect;
Expand Down
Loading