diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 52534ece6..85f67cbd8 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -5,7 +5,7 @@ on: jobs: labeler: - if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'trunk' + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'trunk' && github.event.pull_request.user.login != 'dependabot[bot]' permissions: contents: read pull-requests: write @@ -14,7 +14,7 @@ jobs: - uses: actions/labeler@v5 comment_pr: - if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'trunk' + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'trunk' && github.event.pull_request.user.login != 'dependabot[bot]' permissions: contents: read pull-requests: write @@ -25,7 +25,7 @@ jobs: uses: thollander/actions-comment-pull-request@v3 with: message: | - Hey @${{ github.event.pull_request.assignee.login }}, good job getting this PR merged! :tada: + Hey @${{ github.event.pull_request.user.login }}, good job getting this PR merged! :tada: Now, the `needs-changelog` label has been added to it. diff --git a/composer.lock b/composer.lock index 6f02d2c3d..9e20281a0 100644 --- a/composer.lock +++ b/composer.lock @@ -2469,16 +2469,16 @@ }, { "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.19", + "version": "v2.11.21", "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1" + "reference": "eb2b351927098c24860daa7484e290d3eed693be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", - "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/eb2b351927098c24860daa7484e290d3eed693be", + "reference": "eb2b351927098c24860daa7484e290d3eed693be", "shasum": "" }, "require": { @@ -2489,9 +2489,9 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", "phpcsstandards/phpcsdevcs": "^1.1", "phpstan/phpstan": "^1.7", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", "sirbrillig/phpcs-import-detection": "^1.1", - "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta" + "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2523,20 +2523,20 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2024-06-26T20:08:34+00:00" + "time": "2024-12-02T16:37:49+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.1", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", "shasum": "" }, "require": { @@ -2603,7 +2603,7 @@ "type": "open_collective" } ], - "time": "2024-05-22T21:24:41+00:00" + "time": "2024-11-16T12:02:36+00:00" }, { "name": "symfony/console", diff --git a/includes/class-modal-checkout.php b/includes/class-modal-checkout.php index e0cc0e126..ccf83f69e 100644 --- a/includes/class-modal-checkout.php +++ b/includes/class-modal-checkout.php @@ -13,6 +13,20 @@ * Modal Checkout Class. */ final class Modal_Checkout { + /** + * Checkout registration flag. + * + * @var string + */ + const CHECKOUT_REGISTRATION_FLAG = '_newspack_checkout_registration'; + + /** + * Checkout registration order meta key. + * + * @var string + */ + const CHECKOUT_REGISTRATION_ORDER_META_KEY = '_newspack_checkout_registration_meta'; + /** * Whether the modal checkout has been enqueued. * @@ -27,11 +41,84 @@ final class Modal_Checkout { */ private static $products = []; + /** + * Labels for the modal checkout UI. + * + * @var mixed[] + */ + private static $modal_checkout_labels = []; + + /** + * Allowed assets for modal checkout. + * + * @var string[] + */ + private static $allowed_scripts = [ + 'jquery', + 'google_gtagjs', + // Newspack. + 'newspack-newsletters-', + 'newspack-blocks-modal', + 'newspack-blocks-modal-checkout', + 'newspack-wc', + 'newspack-ui', + 'newspack-style', + 'newspack-recaptcha', + 'newspack-woocommerce-style', + // Woo. + 'woocommerce', + 'WCPAY', + 'Woo', + 'wc-', + 'wc_', + 'wcs-', + 'stripe', + 'select2', + 'selectWoo', + // Metorik. + 'metorik', + ]; + + /** + * Allowed styles for modal checkout. + * + * @var string[] + */ + private static $allowed_styles = [ + // Newspack. + 'newspack-newsletters-', + 'newspack-blocks-modal', + 'newspack-blocks-modal-checkout', + 'newspack-wc', + 'newspack-ui', + 'newspack-style', + 'newspack-recaptcha', + 'newspack-woocommerce-style', + // Woo. + 'woocommerce', + 'WCPAY', + 'Woo', + 'wc-', + 'wc_', + 'wcs-', + 'stripe', + 'select2', + 'selectWoo', + // Metorik. + 'metorik', + ]; + /** * Initialize hooks. */ public static function init() { - add_action( 'wp_loaded', [ __CLASS__, 'process_checkout_request' ], 5 ); + // Process checkout request. + add_action( 'wp_ajax_modal_checkout_request', [ __CLASS__, 'process_checkout_request' ] ); + add_action( 'wp_ajax_nopriv_modal_checkout_request', [ __CLASS__, 'process_checkout_request' ] ); + 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_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 ); add_filter( 'woocommerce_add_error', [ __CLASS__, 'hide_expiry_message_shop_link' ] ); @@ -43,26 +130,37 @@ public static function init() { add_filter( 'woocommerce_get_return_url', [ __CLASS__, 'woocommerce_get_return_url' ], 10, 2 ); add_filter( 'woocommerce_get_checkout_order_received_url', [ __CLASS__, 'woocommerce_get_return_url' ], 10, 2 ); add_filter( 'wc_get_template', [ __CLASS__, 'wc_get_template' ], 10, 2 ); - add_filter( 'woocommerce_checkout_get_value', [ __CLASS__, 'woocommerce_checkout_get_value' ], 10, 2 ); add_filter( 'woocommerce_checkout_fields', [ __CLASS__, 'woocommerce_checkout_fields' ] ); add_filter( 'woocommerce_update_order_review_fragments', [ __CLASS__, 'order_review_fragments' ] ); - add_filter( 'woocommerce_payment_successful_result', [ __CLASS__, 'woocommerce_payment_successful_result' ] ); - add_action( 'woocommerce_checkout_create_order_line_item', [ __CLASS__, 'woocommerce_checkout_create_order_line_item' ], 10, 4 ); - add_filter( 'newspack_donations_cart_item_data', [ __CLASS__, 'amend_cart_item_data' ] ); add_filter( 'newspack_recaptcha_verify_captcha', [ __CLASS__, 'recaptcha_verify_captcha' ], 10, 2 ); add_filter( 'woocommerce_enqueue_styles', [ __CLASS__, 'dequeue_woocommerce_styles' ] ); - add_filter( 'wcs_place_subscription_order_text', [ __CLASS__, 'order_button_text' ], 1 ); - add_filter( 'woocommerce_order_button_text', [ __CLASS__, 'order_button_text' ] ); - add_filter( 'option_woocommerce_subscriptions_order_button_text', [ __CLASS__, 'order_button_text' ] ); + add_filter( 'wcs_place_subscription_order_text', [ __CLASS__, 'order_button_text' ], 5 ); + add_filter( 'woocommerce_order_button_text', [ __CLASS__, 'order_button_text' ], 5 ); + add_filter( 'option_woocommerce_subscriptions_order_button_text', [ __CLASS__, 'order_button_text' ], 5 ); + add_action( 'woocommerce_before_checkout_form', [ __CLASS__, 'render_before_checkout_form' ] ); + add_action( 'woocommerce_before_checkout_form', [ __CLASS__, 'render_name_your_price_form' ], 11 ); + add_action( 'woocommerce_checkout_before_customer_details', [ __CLASS__, 'render_before_customer_details' ] ); + add_filter( 'woocommerce_enable_order_notes_field', [ __CLASS__, 'enable_order_notes_field' ] ); + add_action( 'woocommerce_checkout_process', [ __CLASS__, 'wcsg_apply_gift_subscription' ] ); + add_filter( 'woocommerce_order_received_verify_known_shoppers', '__return_false' ); + add_filter( 'woocommerce_order_button_html', [ __CLASS__, 'order_button_html' ], 10, 1 ); add_action( 'option_woocommerce_default_customer_address', [ __CLASS__, 'ensure_base_default_customer_address' ] ); add_action( 'default_option_woocommerce_default_customer_address', [ __CLASS__, 'ensure_base_default_customer_address' ] ); + add_action( 'wp_ajax_process_name_your_price_request', [ __CLASS__, 'process_name_your_price_request' ] ); + add_filter( 'option_woocommerce_woocommerce_payments_settings', [ __CLASS__, 'filter_woocommerce_payments_settings' ] ); + add_action( 'init', [ __CLASS__, 'unhook_woocommerce_payments_update_billing_fields' ] ); + add_action( 'wp_enqueue_scripts', [ __CLASS__, 'update_password_strength_message' ], 9999 ); + /** Custom handling for registered users. */ add_filter( 'woocommerce_checkout_customer_id', [ __CLASS__, 'associate_existing_user' ] ); + add_action( 'woocommerce_after_checkout_validation', [ __CLASS__, 'maybe_reset_checkout_registration_flag' ], 10, 2 ); add_filter( 'woocommerce_checkout_posted_data', [ __CLASS__, 'skip_account_creation' ], 11 ); + add_action( 'woocommerce_checkout_create_order', [ __CLASS__, 'maybe_add_checkout_registration_order_meta' ], 10, 1 ); + add_action( 'newpack_blocks_modal_checkout_thankyou', [ __CLASS__, 'reset_checkout_registration_flag' ] ); // Remove some stuff from the modal checkout page. It's displayed in an iframe, so it should not be treated as a separate page. - add_action( 'wp_enqueue_scripts', [ __CLASS__, 'dequeue_scripts' ], 11 ); + add_action( 'wp_enqueue_scripts', [ __CLASS__, 'dequeue_scripts' ], PHP_INT_MAX ); add_filter( 'newspack_reader_activation_should_render_auth', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); add_filter( 'newspack_enqueue_reader_activation_block', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); add_filter( 'newspack_enqueue_memberships_block_patterns', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); @@ -70,11 +168,13 @@ public static function init() { add_filter( 'newspack_theme_enqueue_js', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); add_filter( 'newspack_theme_enqueue_print_styles', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); add_filter( 'cmplz_site_needs_cookiewarning', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); - add_filter( 'googlesitekit_analytics_tag_blocked', [ __CLASS__, 'is_modal_checkout' ] ); - add_filter( 'googlesitekit_analytics-4_tag_blocked', [ __CLASS__, 'is_modal_checkout' ] ); add_filter( 'googlesitekit_adsense_tag_blocked', [ __CLASS__, 'is_modal_checkout' ] ); - add_filter( 'googlesitekit_tagmanager_tag_blocked', [ __CLASS__, 'is_modal_checkout' ] ); add_filter( 'jetpack_active_modules', [ __CLASS__, 'jetpack_active_modules' ] ); + add_filter( 'woocommerce_checkout_update_order_review_expired', [ __CLASS__, 'is_not_modal_checkout_filter' ] ); + + // Make the current cart price available to the JavaScript. + add_action( 'wp_ajax_get_cart_total', [ __CLASS__, 'get_cart_total_js' ] ); + add_action( 'wp_ajax_nopriv_get_cart_total', [ __CLASS__, 'get_cart_total_js' ] ); /** * Ensure that options to limit the number of subscriptions per product are respected. @@ -91,6 +191,10 @@ public static function init() { add_filter( 'woocommerce_valid_order_statuses_for_order_again', [ 'WCS_Limiter', 'filter_order_again_statuses_for_limited_subscriptions' ] ); } add_filter( 'woocommerce_subscriptions_product_limited_for_user', [ __CLASS__, 'subscriptions_product_limited_for_user' ], 10, 3 ); + add_filter( 'woocommerce_get_privacy_policy_text', [ __CLASS__, 'woocommerce_get_privacy_policy_text' ], 10, 2 ); + + // Remove any hooks that aren't supported by the modal checkout. + add_action( 'plugins_loaded', [ __CLASS__, 'remove_hooks' ] ); } /** @@ -105,60 +209,13 @@ public static function amend_cart_item_data( $cart_item_data ) { return $cart_item_data; } - /** - * Add information about modal checkout to order item meta. - * - * @param \WC_Order_Item_Product $item The cart item. - * @param string $cart_item_key The cart item key. - * @param array $values The cart item values. - * @param \WC_Order $order The order. - * @return void - */ - public static function woocommerce_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) { - if ( ! empty( $values['newspack_modal_checkout_url'] ) ) { - $order->add_meta_data( '_newspack_modal_checkout_url', $values['newspack_modal_checkout_url'] ); - } - } - - /** - * Change the post-transaction return URL. - * This is specifically for non-redirect-based flows, such as Apple Pay. - * - * @param array $result The return payload for a successful transaction. - */ - public static function woocommerce_payment_successful_result( $result ) { - $order_id = $result['order_id']; - $order = \wc_get_order( $order_id ); - $modal_checkout_url = $order->get_meta( '_newspack_modal_checkout_url' ); - if ( empty( $modal_checkout_url ) ) { - return $result; - } - - $modal_checkout_url_query = \wp_parse_url( $modal_checkout_url, PHP_URL_QUERY ); - \wp_parse_str( $modal_checkout_url_query, $modal_checkout_url_query_params ); - $passed_params_names = [ 'modal_checkout', 'after_success_behavior', 'after_success_url', 'after_success_button_label' ]; - // Pick passed params from the query params. - $passed_params = array_intersect_key( $modal_checkout_url_query_params, array_flip( $passed_params_names ) ); - - $result['redirect'] = \add_query_arg( - array_merge( - $passed_params, - [ - 'order_id' => $order_id, - ] - ), - $result['redirect'] - ); - return $result; - } - /** * Dequeue WC styles if on modal checkout. * * @param array $enqueue_styles Array of styles to enqueue. */ public static function dequeue_woocommerce_styles( $enqueue_styles ) { - if ( ! isset( $_REQUEST['modal_checkout'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! self::is_modal_checkout() ) { return $enqueue_styles; } unset( $enqueue_styles['woocommerce-general'] ); @@ -170,26 +227,30 @@ public static function dequeue_woocommerce_styles( $enqueue_styles ) { * Process checkout request for modal. */ public static function process_checkout_request() { - if ( is_admin() ) { + if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { + return; + } + + $is_newspack_checkout = filter_input( INPUT_GET, 'newspack_checkout', FILTER_SANITIZE_NUMBER_INT ); + + if ( ! $is_newspack_checkout ) { return; } - $is_newspack_checkout = filter_input( INPUT_GET, 'newspack_checkout', FILTER_SANITIZE_NUMBER_INT ); $product_id = filter_input( INPUT_GET, 'product_id', FILTER_SANITIZE_NUMBER_INT ); $variation_id = filter_input( INPUT_GET, 'variation_id', FILTER_SANITIZE_NUMBER_INT ); $after_success_behavior = filter_input( INPUT_GET, 'after_success_behavior', FILTER_SANITIZE_SPECIAL_CHARS ); - $after_success_url = filter_input( INPUT_GET, 'after_success_url', FILTER_SANITIZE_SPECIAL_CHARS ); + $after_success_url = filter_input( INPUT_GET, 'after_success_url', FILTER_SANITIZE_URL ); $after_success_button_label = filter_input( INPUT_GET, 'after_success_button_label', FILTER_SANITIZE_SPECIAL_CHARS ); - if ( ! $is_newspack_checkout || ! $product_id ) { + if ( ! $product_id ) { return; } + if ( $variation_id ) { $product_id = $variation_id; } - \WC()->cart->empty_cart(); - $referer = wp_get_referer(); $params = []; $parsed_url = wp_parse_url( $referer ); @@ -251,16 +312,21 @@ function ( $item ) { } /** - * Filters the cart item data for the modal checkout. - * - * @param array $cart_item_data Cart item data. - */ + * Filters the cart item data for the modal checkout. + * + * @param array $cart_item_data Cart item data. + */ $cart_item_data = apply_filters( 'newspack_blocks_modal_checkout_cart_item_data', $cart_item_data ); + \WC()->cart->empty_cart(); \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data ); - $query_args = []; + // Set checkout registration flag if user is logged not logged in. + if ( ! is_user_logged_in() ) { + self::set_checkout_registration_flag(); + } + $query_args = []; if ( ! empty( $referer_tags ) ) { $query_args['referer_tags'] = implode( ',', $referer_tags ); } @@ -282,9 +348,187 @@ function ( $item ) { \wc_get_page_permalink( 'checkout' ) ); - // Redirect to checkout. - \wp_safe_redirect( apply_filters( 'newspack_blocks_checkout_url', $checkout_url ) ); - exit; + // Get data to send for this purchase. + $checkout_button_metadata = [ + 'currency' => function_exists( 'get_woocommerce_currency' ) ? \get_woocommerce_currency() : 'USD', + 'amount' => $price, + 'product_id' => $product_id, + 'referrer' => $referer, + ]; + + /** + * Action to fire for checkout button block modal. + */ + \do_action( 'newspack_blocks_checkout_button_modal', $checkout_button_metadata ); + + $checkout_url = apply_filters( 'newspack_blocks_checkout_url', $checkout_url ); + + if ( defined( 'DOING_AJAX' ) ) { + echo wp_json_encode( [ 'url' => $checkout_url ] ); + exit; + } else { + // Redirect to checkout. + \wp_safe_redirect( $checkout_url ); + exit; + } + } + + /** + * Process abandon checkout for modal. + */ + public static function process_abandon_checkout() { + if ( ! defined( 'DOING_AJAX' ) ) { + return; + } + + if ( ! 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(); + } + + /** + * Filter unsupported Woo Payments features. + * + * @param array $settings WooCommerce Payments settings. + * + * @return array Filtered WooCommerce Payments settings. + */ + public static function filter_woocommerce_payments_settings( $settings ) { + if ( ! self::is_modal_checkout() ) { + return $settings; + } + if ( isset( $settings['platform_checkout'] ) ) { + $settings['platform_checkout'] = 'no'; + } + return $settings; + } + + /** + * Unhook WooCommerce Payments billing fields update. + * + * The WC_Payment_Gateway_WCPay->checkout_update_email_field_priority() hook + * changes the position of the email address field in the checkout form and + * appends a broken Stripelink button to the email input. + */ + public static function unhook_woocommerce_payments_update_billing_fields() { + if ( ! self::is_modal_checkout() ) { + return; + } + if ( ! class_exists( 'WC_Payments' ) ) { + return; + } + $gateway = \WC_Payments::get_gateway(); + if ( ! $gateway ) { + return; + } + $filters = $GLOBALS['wp_filter']['woocommerce_billing_fields']; + foreach ( $filters as $index => $filter ) { + $keys = array_keys( $filter ); + foreach ( $keys as $key ) { + if ( strpos( $key, 'checkout_update_email_field_priority' ) !== false ) { + remove_filter( 'woocommerce_billing_fields', $key, $index ); + } + } + } + } + + + /** + * Process name your price request for modal. + */ + public static function process_name_your_price_request() { + if ( ! defined( 'DOING_AJAX' ) ) { + return; + } + + if ( ! function_exists( 'WC' ) || ! \Newspack_Blocks::can_use_name_your_price() || ! method_exists( '\WC_Name_Your_Price_Helpers', 'is_nyp' ) ) { + return; + } + + check_ajax_referer( 'newspack_checkout_name_your_price' ); + + $is_newspack_checkout_nyp = filter_input( INPUT_POST, 'newspack_checkout_name_your_price', FILTER_SANITIZE_NUMBER_INT ); + $product_id = filter_input( INPUT_POST, 'product_id', FILTER_SANITIZE_NUMBER_INT ); + + if ( ! $is_newspack_checkout_nyp || ! $product_id ) { + return; + } + + $price = \WC_Name_Your_Price_Helpers::standardize_number( filter_input( INPUT_POST, 'price', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ) ); + $max_price = \WC_Name_Your_Price_Helpers::get_maximum_price( $product_id ); + + if ( ! empty( $max_price ) && $price > $max_price ) { + wp_send_json_error( + [ + 'message' => sprintf( + // Translators: %s is the maximum price. + __( 'Adjusted price must be less than the maximum of %s.', 'newspack-blocks' ), + \wc_price( $max_price ) + ), + ] + ); + + wp_die(); + } + + $cart_item_data = self::amend_cart_item_data( [ 'referer' => wp_get_referer() ] ); + + foreach ( \WC()->cart->get_cart() as $cart_item_key => $cart_item ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( $cart_item['product_id'] !== (int) $product_id && $cart_item['variation_id'] !== (int) $product_id ) { + continue; + } + + $cart_item_data['nyp'] = $price; + $cart_item_data['base_price'] = isset( $cart_item['base_price'] ) ? $cart_item['base_price'] : $cart_item['nyp']; + + if ( $price < $cart_item_data['base_price'] ) { + wp_send_json_error( + [ + 'message' => sprintf( + // Translators: %s is the name-your-price custom price. + __( 'Adjusted price must be greater than base price of %s.', 'newspack-blocks' ), + \wc_price( $cart_item_data['base_price'] ) + ), + ] + ); + + wp_die(); + } + } + + $coupons = \WC()->cart->get_applied_coupons(); + + \WC()->cart->empty_cart(); + \WC()->cart->add_to_cart( $product_id, 1, 0, [], $cart_item_data ); + + if ( ! empty( $coupons ) ) { + foreach ( $coupons as $coupon ) { + \WC()->cart->apply_coupon( $coupon ); + } + } + + wp_send_json_success( + [ + 'message' => self::get_modal_checkout_labels( 'checkout_nyp_thankyou' ), + 'price' => \wc_price( $price ), + ] + ); + + wp_die(); } /** @@ -294,23 +538,66 @@ public static function render_modal_markup() { if ( ! self::$has_modal ) { return; } + /** + * Filters the header title for the modal checkout. + * + * @param string $title The title. + */ + $title = self::get_modal_checkout_labels( 'checkout_modal_title' ); + $class_prefix = self::get_class_prefix(); ?> -