From fe1ac2e2f3c16ecc690664fb5f99489d893864cf Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 19 Dec 2023 13:57:42 -0300 Subject: [PATCH 1/4] fix: use Woo's cart fee for covering transaction fees --- .../class-woocommerce-cover-fees.php | 131 ++++++++---------- 1 file changed, 55 insertions(+), 76 deletions(-) diff --git a/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php b/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php index 1743794c9d..d2385b61c8 100644 --- a/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php +++ b/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php @@ -13,18 +13,15 @@ * WooCommerce Order UTM class. */ class WooCommerce_Cover_Fees { - const CUSTOM_FIELD_NAME = 'newspack-wc-pay-fees'; - const PRICE_ELEMENT_ID = 'newspack-wc-price'; - const WC_ORDER_META_NAME = 'newspack_donor_covers_fees'; + const CUSTOM_FIELD_NAME = 'newspack-wc-pay-fees'; /** * Initialize hooks. */ public static function init() { \add_filter( 'woocommerce_checkout_fields', [ __CLASS__, 'add_checkout_fields' ] ); - \add_filter( 'woocommerce_checkout_create_order', [ __CLASS__, 'set_total_with_fees' ], 1, 2 ); \add_action( 'woocommerce_checkout_order_processed', [ __CLASS__, 'add_order_note' ], 1, 3 ); - \add_filter( 'wc_price', [ __CLASS__, 'amend_price_markup' ], 1, 2 ); + \add_action( 'woocommerce_cart_calculate_fees', [ __CLASS__, 'add_transaction_fee' ] ); \add_action( 'wc_stripe_payment_fields_stripe', [ __CLASS__, 'render_stripe_input' ] ); \add_action( 'wp_enqueue_scripts', [ __CLASS__, 'print_checkout_helper_script' ] ); } @@ -49,22 +46,6 @@ public static function add_checkout_fields( $fields ) { return $fields; } - /** - * Set order total, taking the fee into account. - * - * @param \WC_Order $order Order object. - * @param array $data Posted data. - * - * @return \WC_Order - */ - public static function set_total_with_fees( $order, $data ) { - if ( isset( $data[ self::CUSTOM_FIELD_NAME ] ) && 1 === $data[ self::CUSTOM_FIELD_NAME ] ) { - $order->add_meta_data( self::WC_ORDER_META_NAME, 1 ); - $order->set_total( self::get_total_with_fee( $order->get_total() ) ); - } - return $order; - } - /** * Add an order note. * @@ -100,7 +81,7 @@ private static function should_allow_covering_fees() { if ( true !== boolval( get_option( 'newspack_donations_allow_covering_fees' ) ) ) { return false; } - if ( \Newspack_Blocks\Modal_Checkout::is_modal_checkout() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( \Newspack_Blocks\Modal_Checkout::is_modal_checkout() ) { return true; } if ( isset( $_POST['post_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing @@ -126,9 +107,7 @@ public static function render_stripe_input() { id= name= type="checkbox" - value="true" style="margin-right: 8px;" - onchange="newspackHandleCoverFees(this)" checked @@ -166,19 +145,6 @@ private static function get_possessive( $string ) { return $string . '’' . ( 's' !== $string[ strlen( $string ) - 1 ] ? 's' : '' ); } - /** - * Amend the price markup so it's possible to manipulate it via JS. - * - * @param string $html Price HTML. - * @param string $price Price. - */ - public static function amend_price_markup( $html, $price ) { - if ( ! self::should_allow_covering_fees() ) { - return $html; - } - return str_replace( $price, '' . $price . '', $html ); - } - /** * Print the checkout helper JS script. */ @@ -189,36 +155,21 @@ public static function print_checkout_helper_script() { $handler = 'newspack-wc-modal-checkout-helper'; wp_register_script( $handler, '', [], false, [ 'in_footer' => true ] ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion wp_enqueue_script( $handler ); - $original_price = WC()->cart->total; - $price_with_fee = number_format( self::get_total_with_fee( $original_price ), 2 ); - $has_coupons_available = WC()->cart->get_coupon_discount_totals(); - $coupons_handling_script = ''; - if ( \wc_coupons_enabled() ) { - // Handle an edge case where the price was updated in the UI, and then a coupon was applied. - // In this case, the price has to be reverted to the original value, since covering fees - // is not supported with coupons. - $coupons_handling_script = 'setInterval(function(){ - if(document.querySelector(".woocommerce-remove-coupon")){ - document.getElementById( "' . self::PRICE_ELEMENT_ID . '" ).textContent = "' . $original_price . '"; + ob_start(); + ?> + ( function( $ ) { + $(document.body).on('init_checkout', function() { + var inputEl = document.getElementById( "" ); + if ( inputEl ) { + inputEl.addEventListener( 'change', function( e ) { + $( document.body ).trigger( 'update_checkout', { update_shipping_method: false } ); + } ); } - }, 1000);'; - } - wp_add_inline_script( - $handler, - 'const form = document.querySelector(\'form[name="checkout"]\'); - if ( form ) { - form.addEventListener(\'change\', function( e ){ - const inputEl = document.getElementById( "' . self::CUSTOM_FIELD_NAME . '" ); - if( e.target.name === "payment_method" && e.target.value !== "stripe" && inputEl.checked ){ - inputEl.checked = false; - newspackHandleCoverFees(inputEl); - } - }); - } - function newspackHandleCoverFees(inputEl) { - document.getElementById( "' . self::PRICE_ELEMENT_ID . '" ).textContent = inputEl.checked ? "' . $price_with_fee . '" : "' . $original_price . '"; - };' . $coupons_handling_script - ); + }); + } )( jQuery ); + cart->total ); - $total = self::get_total_with_fee( $price ); + $subtotal = WC()->cart->get_subtotal(); + $total = self::get_total_with_fee(); // Just one decimal place, please. - $flat_percentage = (float) number_format( ( ( $total - $price ) * 100 ) / $price, 1 ); + $flat_percentage = (float) number_format( ( ( $total - $subtotal ) * 100 ) / $subtotal, 1 ); return $flat_percentage . '%'; } /** - * Calculate the adjusted total, taking the fee into account. - * - * @param float $total Total amount. + * Add fee. * - * @return float + * @param \WC_Cart $cart Cart object. + */ + public static function add_transaction_fee( $cart ) { + if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { + return; + } + if ( ! self::should_allow_covering_fees() ) { + return; + } + $cart->add_fee( + sprintf( + // Translators: %s is the fee percentage. + __( 'Transaction fees (%s)', 'newspack-plugin' ), + self::get_fee_display_value() + ), + self::get_fee_value() + ); + } + + /** + * Get the fee value. */ - private static function get_total_with_fee( $total ) { + public static function get_fee_value() { $fee_multiplier = self::get_stripe_fee_multiplier_value(); $fee_static = self::get_stripe_fee_static_value(); - $fee = ( ( ( $total + $fee_static ) / ( 100 - $fee_multiplier ) ) * 100 - $total ); - return $total + $fee; + $subtotal = WC()->cart->get_subtotal(); + $fee = ( ( ( $subtotal + $fee_static ) / ( 100 - $fee_multiplier ) ) * 100 - $subtotal ); + return $fee; + } + + /** + * Calculate the adjusted total, taking the fee into account. + * + * @return float + */ + private static function get_total_with_fee() { + return WC()->cart->get_subtotal() + self::get_fee_value(); } } WooCommerce_Cover_Fees::init(); From 07bb3f3477c30c219315a2a74877cf80c58a54bf Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 21 Dec 2023 11:15:36 -0300 Subject: [PATCH 2/4] feat: integrate cart update --- assets/other-scripts/wc-cover-fees/index.js | 29 +++++++++++ .../class-woocommerce-cover-fees.php | 51 +++++++++++-------- 2 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 assets/other-scripts/wc-cover-fees/index.js diff --git a/assets/other-scripts/wc-cover-fees/index.js b/assets/other-scripts/wc-cover-fees/index.js new file mode 100644 index 0000000000..a9844083bc --- /dev/null +++ b/assets/other-scripts/wc-cover-fees/index.js @@ -0,0 +1,29 @@ +/* globals jQuery, newspack_wc_cover_fees */ +( function ( $ ) { + if ( ! $ ) { + return; + } + const $body = $( document.body ); + $body.on( 'init_checkout', function () { + const form = document.querySelector( 'form.checkout' ); + if ( ! form ) { + return; + } + let checked = document.getElementById( newspack_wc_cover_fees.custom_field_name )?.checked; + form.addEventListener( 'change', function () { + // Get element on every change because the DOM is replaced by AJAX. + const input = document.getElementById( newspack_wc_cover_fees.custom_field_name ); + if ( ! input ) { + return; + } + if ( checked !== input.checked ) { + checked = input.checked; + $body.trigger( 'update_checkout', { update_shipping_method: false } ); + } + } ); + // Trigger checkout update on payment method change so it updates the fee. + $( document ).on( 'payment_method_selected', function () { + $body.trigger( 'update_checkout', { update_shipping_method: false } ); + } ); + } ); +} )( jQuery ); diff --git a/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php b/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php index d2385b61c8..5741f8c51d 100644 --- a/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php +++ b/includes/reader-revenue/woocommerce/class-woocommerce-cover-fees.php @@ -104,14 +104,15 @@ public static function render_stripe_input() {

- name= + id="" + name="" type="checkbox" style="margin-right: 8px;" + value="1" checked - > + />