diff --git a/changelog/fix-8423-klarna-cant-undo-manual-refunds-and-process-refunds-again-via-woopayments b/changelog/fix-8423-klarna-cant-undo-manual-refunds-and-process-refunds-again-via-woopayments new file mode 100644 index 00000000000..8cd309f823c --- /dev/null +++ b/changelog/fix-8423-klarna-cant-undo-manual-refunds-and-process-refunds-again-via-woopayments @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Include missing scripts that handle refunds for non credit card payments in the order details page. diff --git a/changelog/fix-9676-multi-currency-autoload b/changelog/fix-9676-multi-currency-autoload new file mode 100644 index 00000000000..04e7fcfbc58 --- /dev/null +++ b/changelog/fix-9676-multi-currency-autoload @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Keep multi-currency module in the same path to avoid autoload errors. + + diff --git a/changelog/update-payment-method-test-mode-label b/changelog/update-payment-method-test-mode-label new file mode 100644 index 00000000000..1c3874ebf5f --- /dev/null +++ b/changelog/update-payment-method-test-mode-label @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +update: payment method "test mode" label at checkout to be displayed only when payment method is selected diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index 924afccde1f..68593bababd 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -110,7 +110,10 @@ Object.entries( enabledPaymentMethodsConfig ) label: ( diff --git a/client/checkout/blocks/payment-method-label.js b/client/checkout/blocks/payment-method-label.js index 417007816aa..b329a165658 100644 --- a/client/checkout/blocks/payment-method-label.js +++ b/client/checkout/blocks/payment-method-label.js @@ -12,9 +12,50 @@ import './style.scss'; import { useEffect, useState } from '@wordpress/element'; import { getAppearance } from 'wcpay/checkout/upe-styles'; -export default ( { api, upeConfig, upeName, upeAppearanceTheme } ) => { +const bnplMethods = [ 'affirm', 'afterpay_clearpay', 'klarna' ]; +const PaymentMethodMessageWrapper = ( { + upeName, + countries, + currentCountry, + amount, + appearance, + children, +} ) => { + if ( ! bnplMethods.includes( upeName ) ) { + return null; + } + + if ( amount <= 0 ) { + return null; + } + + if ( ! currentCountry ) { + return null; + } + + if ( ! appearance ) { + return null; + } + + if ( countries.length !== 0 && ! countries.includes( currentCountry ) ) { + return null; + } + + return ( +
{ children }
+ ); +}; + +export default ( { + api, + title, + countries, + iconLight, + iconDark, + upeName, + upeAppearanceTheme, +} ) => { const cartData = wp.data.select( 'wc/store/cart' ).getCartData(); - const bnplMethods = [ 'affirm', 'afterpay_clearpay', 'klarna' ]; const isTestMode = getUPEConfig( 'testMode' ); const [ appearance, setAppearance ] = useState( getUPEConfig( 'wcBlocksUPEAppearance' ) @@ -35,8 +76,6 @@ export default ( { api, upeConfig, upeName, upeAppearanceTheme } ) => { window.wcBlocksCheckoutData?.storeCountry || 'US'; - const isCreditCard = upeName === 'card'; - useEffect( () => { async function generateUPEAppearance() { // Generate UPE input styles. @@ -56,10 +95,8 @@ export default ( { api, upeConfig, upeName, upeAppearanceTheme } ) => { return ( <>
- - { upeConfig.title } - - { isCreditCard && isTestMode && ( + { title } + { isTestMode && ( { __( 'Test Mode', 'woocommerce-payments' ) } @@ -67,39 +104,35 @@ export default ( { api, upeConfig, upeName, upeAppearanceTheme } ) => { {
- { bnplMethods.includes( upeName ) && - ( upeConfig.countries.length === 0 || - upeConfig.countries.includes( currentCountry ) ) && - amount > 0 && - currentCountry && - appearance && ( -
- - - -
- ) } + + + + + ); }; diff --git a/client/checkout/blocks/style.scss b/client/checkout/blocks/style.scss index b43b7217d9f..a6aa37953b3 100644 --- a/client/checkout/blocks/style.scss +++ b/client/checkout/blocks/style.scss @@ -43,6 +43,21 @@ button.wcpay-stripelink-modal-trigger:hover { } .wc-block-checkout__payment-method { + input:checked ~ div { + .wc-block-components-radio-control__label { + > .payment-method-label { + .test-mode.badge { + // hiding the badge when the payment method is not selected + display: inline-block; + } + + &__pmme-container { + display: none; + } + } + } + } + .wc-block-components-radio-control__label { width: 100%; display: block !important; @@ -77,6 +92,7 @@ button.wcpay-stripelink-modal-trigger:hover { color: #4d3716; justify-self: start; width: max-content; + display: none; } @include breakpoint( '<480px' ) { @@ -88,13 +104,14 @@ button.wcpay-stripelink-modal-trigger:hover { justify-self: end; } } - } - .bnpl-message { - width: 100%; + &__pmme-container { + width: 100%; + pointer-events: none; - @include breakpoint( '<480px' ) { - margin-top: 8px; + @include breakpoint( '<480px' ) { + margin-top: 8px; + } } } } @@ -112,11 +129,6 @@ button.wcpay-stripelink-modal-trigger:hover { } #payment-method { - label.wc-block-components-radio-control__option-checked { - .StripeElement { - display: none; - } - } /* stylelint-disable-next-line selector-id-pattern */ #radio-control-wc-payment-method-options-woocommerce_payments_affirm__label img { diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index 6e4b8fb43fe..714142cae62 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -159,55 +159,43 @@ jQuery( function ( $ ) { async function injectStripePMMEContainers() { const bnplMethods = [ 'affirm', 'afterpay_clearpay', 'klarna' ]; - const labelBase = 'payment_method_woocommerce_payments_'; const paymentMethods = getUPEConfig( 'paymentMethodsConfig' ); const paymentMethodsKeys = Object.keys( paymentMethods ); const cartData = await api.pmmeGetCartData(); for ( const method of paymentMethodsKeys ) { if ( bnplMethods.includes( method ) ) { - const targetLabel = document.querySelector( - `label[for="${ labelBase }${ method }"]` - ); const containerID = `stripe-pmme-container-${ method }`; + const container = document.getElementById( containerID ); - if ( document.getElementById( containerID ) ) { - document.getElementById( containerID ).innerHTML = ''; + if ( ! container ) { + continue; } - if ( targetLabel ) { - let container = document.getElementById( containerID ); - if ( ! container ) { - container = document.createElement( 'span' ); - container.id = containerID; - container.dataset.paymentMethodType = method; - container.classList.add( 'stripe-pmme-container' ); - targetLabel.appendChild( container ); - } - - const currentCountry = - cartData?.billing_address?.country || - getUPEConfig( 'storeCountry' ); - - if ( - paymentMethods[ method ]?.countries.length === 0 || - paymentMethods[ method ]?.countries?.includes( - currentCountry - ) - ) { - await mountStripePaymentMethodMessagingElement( - api, - container, - { - amount: cartData?.totals?.total_price, - currency: cartData?.totals?.currency_code, - decimalPlaces: - cartData?.totals?.currency_minor_unit, - country: currentCountry, - }, - 'shortcode_checkout' - ); - } + container.innerHTML = ''; + container.dataset.paymentMethodType = method; + + const currentCountry = + cartData?.billing_address?.country || + getUPEConfig( 'storeCountry' ); + if ( + paymentMethods[ method ]?.countries.length === 0 || + paymentMethods[ method ]?.countries?.includes( + currentCountry + ) + ) { + await mountStripePaymentMethodMessagingElement( + api, + container, + { + amount: cartData?.totals?.total_price, + currency: cartData?.totals?.currency_code, + decimalPlaces: + cartData?.totals?.currency_minor_unit, + country: currentCountry, + }, + 'shortcode_checkout' + ); } } } diff --git a/client/checkout/classic/style.scss b/client/checkout/classic/style.scss index 5b452accc83..36a3d29bd94 100644 --- a/client/checkout/classic/style.scss +++ b/client/checkout/classic/style.scss @@ -30,6 +30,11 @@ border: none; } +.woopayments-rich-payment-method-label { + // this will be displayed only on specific scenarios. Otherwise, the "legacy" label will be displayed. + display: none; +} + #payment .payment_methods { li[class*='payment_method_woocommerce_payments'] > label > img { float: right; @@ -41,32 +46,68 @@ &.wc_payment_methods, &.woocommerce-PaymentMethods { - li.payment_method_woocommerce_payments { + li[class*='payment_method_woocommerce_payments'] { display: grid; grid-template-columns: 0fr 0fr 1fr; grid-template-rows: max-content; + .woopayments-plain-payment-method-label { + display: none; + } + > input[name='payment_method'] { - align-self: center; + &:checked ~ label { + .payment-method-title { + margin-right: 8px; // 8px gap between .payment-method-title and .test-mode.badge + } + + .test-mode.badge { + display: inline-block; // hiding the badge when the payment method is not selected + } + + .stripe-pmme-container { + display: none; + } + } } + > label { grid-column: 3; + margin-bottom: 0; + } + + > div.payment_box { + grid-area: 2 / 1 / 3 / 4; + } + + > label:has( .woopayments-rich-payment-method-label ) { + display: inline-flex; + align-items: center; + width: 100%; + + > img { + display: none; // we'll display the image inside `.woopayments-rich-payment-method-label`, instead. + } + } + + .woopayments-rich-payment-method-label { display: grid; - grid-template-columns: 0fr auto; - grid-template-rows: max-content; - grid-gap: 0; + grid-template-columns: 1fr auto; + align-items: center; margin-bottom: 0; + flex-grow: 1; - > .label-title-container { - grid-area: 1 / 2 / 2 / 3; + .label-title-container { + display: block; } .payment-method-title { - margin-right: 8px; + white-space: normal; // Allows wrapping if text is too long + vertical-align: middle; } .test-mode.badge { - display: inline-block; + display: none; background-color: #fff2d7; border-radius: 4px; padding: 4px 6px; @@ -75,73 +116,31 @@ line-height: 16px; color: #4d3716; vertical-align: middle; + white-space: nowrap; // Prevents the badge text from wrapping } img { - float: none; - grid-area: 1 / 4 / 2 / 5; - align-self: baseline; + border: 0; + padding: 0; + height: 24px !important; + max-height: 24px !important; justify-self: end; - margin-left: 1em; + margin: 3px 0; // ensuring the images don't appear squished when all the payment methods are rendered next to each others, like in Elementor. + align-self: center; } - } - > div.payment_box { - grid-area: 2 / 1 / 3 / 4; - } - } - } -} - -li.wc_payment_method:has( .input-radio:not( :checked ) - + label - .stripe-pmme-container ) { - display: grid; - grid-template-columns: min-content 1fr; - grid-template-rows: auto auto; - align-items: baseline; - - .input-radio { - grid-row: 1; - grid-column: 1; - } - - label { - grid-column: 2; - grid-row: 1; - } - - img { - grid-row: 1 / span 2; - align-self: center; - } - .stripe-pmme-container { - width: 100%; - grid-column: 1; - grid-row-start: 2; - pointer-events: none; - } - - .payment_box { - flex: 0 0 100%; - grid-row: 2; - grid-column: 1 / span 2; - } -} - -li.wc_payment_method:has( .input-radio:checked - + label - .stripe-pmme-container ) { - display: block; - - .input-radio:checked { - + label { - .stripe-pmme-container { - display: none; - } - - img { - grid-column: 2; + .stripe-pmme-container { + &:empty { + display: none; // hides container if empty, without affecting alignment + } + + margin-left: 0.25em; // WooCommerce Core will add a   on the left of the payment method's label - this spacing ensures that at least it's consistently aligned. + pointer-events: none; + grid-column: 1 / 2; + grid-row: 2 / 3; + align-self: start; + width: 100%; + } } } } diff --git a/client/order/refund-confirm-modal/index.js b/client/order/refund-confirm-modal/index.js index fc589feb3c1..f7d8c66de07 100644 --- a/client/order/refund-confirm-modal/index.js +++ b/client/order/refund-confirm-modal/index.js @@ -115,7 +115,7 @@ const RefundConfirmationModal = ( { { sprintf( /* translators: %s: WooPayments */ __( - "Issue a full refund back to your customer's credit card using %s. " + + "Issue a full refund back to your customer's payment method using %s. " + 'This action can not be undone. To issue a partial refund, click "Cancel", and use ' + 'the "Refund" button in the order details below.', 'woocommerce-payments' diff --git a/composer.json b/composer.json index 497530e36e1..cdb679b4afb 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ }, "autoload": { "psr-4": { - "WCPay\\MultiCurrency\\": "multi-currency/src", + "WCPay\\MultiCurrency\\": "includes/multi-currency", "WCPay\\Vendor\\": "lib/packages", "WCPay\\": "src" }, diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 9c53cb48597..8b2713c4829 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -733,7 +733,7 @@ public function enqueue_payments_scripts() { if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) { $order = wc_get_order(); - if ( $order && WC_Payment_Gateway_WCPay::GATEWAY_ID === $order->get_payment_method() ) { + if ( $order && strpos( $order->get_payment_method(), WC_Payment_Gateway_WCPay::GATEWAY_ID ) !== false ) { $refund_amount = $order->get_remaining_refund_amount(); // Check if the order's test mode meta matches the site's current test mode state. diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 2a50957df4d..f4b87f36354 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -581,16 +581,32 @@ public function get_title() { $title = parent::get_title(); if ( - Payment_Method::CARD === $this->stripe_id && ( is_checkout() || is_add_payment_method_page() ) && ! isset( $_GET['change_payment_method'] ) // phpcs:ignore WordPress.Security.NonceVerification ) { + $test_mode_badge = ''; if ( WC_Payments::mode()->is_test() ) { $test_mode_badge = '' . __( 'Test Mode', 'woocommerce-payments' ) . ''; - } else { - $test_mode_badge = ''; } - return '
 ' . $title . '' . $test_mode_badge . '
'; + + $bnpl_messaging_container = ''; + if ( $this->payment_method->is_bnpl() ) { + $bnpl_messaging_container = ''; + } + + // the "plain" payment method label is displayed on some sections of the app + // - like "pay for order" when a payment method is pre-selected or a payment has previously failed. + $html = '' . $title . ''; + $html .= '
'; + $html .= '
'; + $html .= ' ' . $title . ''; + $html .= $test_mode_badge; + $html .= '
'; + $html .= $this->get_icon(); + $html .= $bnpl_messaging_container; + $html .= '
'; + + return $html; } return $title; @@ -3236,7 +3252,7 @@ public function update_fraud_rules_based_on_general_options() { * @return string */ public function get_icon() { - return '' . esc_attr( $this->payment_method->get_title() ) . ' payment method logo'; + return '' . esc_attr( $this->payment_method->get_title() ) . ' payment method logo'; } /** diff --git a/includes/class-wc-payments-blocks-payment-method.php b/includes/class-wc-payments-blocks-payment-method.php index 607bb3cafb0..877d4bf4270 100644 --- a/includes/class-wc-payments-blocks-payment-method.php +++ b/includes/class-wc-payments-blocks-payment-method.php @@ -58,7 +58,7 @@ public function get_payment_method_script_handles() { 'wc-blocks-checkout-style', plugins_url( 'dist/blocks-checkout.css', WCPAY_PLUGIN_FILE ), [], - '1.0', + WC_Payments::get_file_version( 'dist/checkout.css' ), 'all' ); } diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 214d3178bb8..c4593f54b7e 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -134,9 +134,7 @@ public function register_scripts() { Fraud_Prevention_Service::maybe_append_fraud_prevention_token(); - $script = 'dist/checkout'; - - WC_Payments::register_script_with_dependencies( 'wcpay-upe-checkout', $script, $script_dependencies ); + WC_Payments::register_script_with_dependencies( 'wcpay-upe-checkout', 'dist/checkout', $script_dependencies ); } /** @@ -417,32 +415,29 @@ function () use ( $prepared_customer_data ) { } // Output the form HTML. - ?> - gateway->get_description() ) ) : ?> + if ( ! empty( $this->gateway->get_description() ) ) : ?>

gateway->get_description() ); ?>

- + is_test() ) : ?> + if ( WC_Payments::mode()->is_test() && false !== $this->gateway->get_payment_method()->get_testing_instructions() ) : + ?>

- gateway->get_payment_method()->get_testing_instructions(); - if ( false !== $testing_instructions ) { + '', - 'strong' => '', - 'number' => '