diff --git a/includes/class-modal-checkout.php b/includes/class-modal-checkout.php index ccf83f69e..70455b3b4 100644 --- a/includes/class-modal-checkout.php +++ b/includes/class-modal-checkout.php @@ -77,6 +77,10 @@ final class Modal_Checkout { 'selectWoo', // Metorik. 'metorik', + // Braintree. + 'braintree', + 'paypal', + 'jetpack', ]; /** @@ -106,6 +110,8 @@ final class Modal_Checkout { 'selectWoo', // Metorik. 'metorik', + // Braintree. + 'braintree', ]; /** @@ -118,6 +124,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 ); @@ -377,29 +385,54 @@ 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; } - if ( ! check_ajax_referer( 'newspack_modal_checkout_nonce' ) ) { wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'newspack-blocks' ) ] ); wp_die(); } - $cart = \WC()->cart; if ( $cart && ! $cart->is_empty() ) { $cart->empty_cart(); } self::reset_checkout_registration_flag(); - wp_send_json_success( [ 'message' => __( 'Cart has been emptied.', 'newspack-blocks' ) ] ); wp_die(); } + /** + * Validate modal checkout. + */ + public static function validate_checkout_request() { + 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(); + } + // We don't want to validate payment methods at this point, so we temporarily set the cart to not need payment. + add_filter( 'woocommerce_cart_needs_payment', '__return_false' ); + $checkout = \WC()->checkout(); + $errors = new WP_Error(); + $posted_data = $checkout->get_posted_data(); + $checkout->update_session( $posted_data ); + $checkout->validate_checkout( $posted_data, $errors ); + remove_filter( 'woocommerce_cart_needs_payment', '__return_false' ); + if ( $errors->get_error_codes() ) { + $error_messages = []; + foreach ( $errors->get_error_messages() as $error_message ) { + $error_messages[] = $error_message; + } + wp_send_json_error( [ 'messages' => $error_messages ] ); + wp_die(); + } else { + wp_send_json_success(); + wp_die(); + } + } + /** * Filter unsupported Woo Payments features. * @@ -786,14 +819,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; + } } } @@ -839,7 +875,7 @@ public static function dequeue_scripts() { } } foreach ( $allowed_gateway_assets as $gateway ) { - if ( false !== strpos( $wp_script->src, $gateway ) ) { + if ( false !== strpos( $handle, $gateway ) || false !== strpos( $wp_script->src, $gateway ) ) { $allowed = true; break; } diff --git a/src/modal-checkout/checkout.scss b/src/modal-checkout/checkout.scss index a9323b6fa..3e5c084d9 100644 --- a/src/modal-checkout/checkout.scss +++ b/src/modal-checkout/checkout.scss @@ -610,6 +610,84 @@ } } + // Braintree styles + li.payment_method_braintree_credit_card, + li.payment_method_braintree_paypal { + .sv-wc-payment-gateway-card-icons { + float: right; + } + + .sv-wc-payment-gateway-payment-form-manage-payment-methods { + display: none; + } + + & > p.form-row { + margin-top: var(--newspack-ui-spacer-3, 16px); + } + + .form-row { + &::before, + &::after { + display: none; + } + + input[type="radio"] + label { + display: inline-block; + } + } + + .payment_method_braintree_credit_card, + .payment_method_braintree_paypal { + .form-row { + display: block; + margin: var(--newspack-ui-spacer-base, 8px) 0; + padding: 0 !important; + + label { + font-size: var(--newspack-ui-font-size-s, 16px); + font-weight: 600; + line-height: var(--newspack-ui-line-height-s, 1.5); + margin: 0 0 var(--newspack-ui-spacer-base, 8px); + } + + .wc-braintree-hosted-field-card-number { + margin-bottom: var(--newspack-ui-spacer-base, 8px); + } + + &:has(label[for="wc-braintree-credit-card-context-hosted"]) { + margin: 0; + } + + // Hiding the test input, since it's distracted and unneeded. + &:has(label[for="wc-braintree-credit-card-test-amount"]), + &:has(label[for="wc-braintree-paypal-test-amount"]) { + display: none; + } + + // This can be done via My Account, removing the link for consistency. + .sv-wc-payment-gateway-payment-form-manage-payment-methods { + display: none; + } + + &:has(#wc-braintree-credit-card-tokenize-payment-method), + &:has(#wc-braintree-paypal-tokenize-payment-method) { + display: grid !important; + gap: 0 var(--newspack-ui-spacer-base, 8px); + grid-template-columns: var(--newspack-ui-spacer-4, 20px) 1fr; + + label { + font-weight: 600; + margin: 0; + } + } + + &:last-child label { + margin: 0; + } + } + } + } + // Order review table. .woocommerce-checkout-review-order-table { th, diff --git a/src/modal-checkout/index.js b/src/modal-checkout/index.js index 4c4527771..0a44b6921 100644 --- a/src/modal-checkout/index.js +++ b/src/modal-checkout/index.js @@ -617,6 +617,8 @@ import { domReady } from './utils'; const removeFromValidation = [ 'save_user_in_woopay', + '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( @@ -624,10 +626,12 @@ import { domReady } from './utils'; ); // Add 'update totals' parameter so it just performs validation. serializedForm.push( { name: 'woocommerce_checkout_update_totals', value: '1' } ); + 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 => {