From cbeab483aaa1dec835fb46cfc8eff629ae085ac0 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Thu, 19 Dec 2024 15:02:11 -0500 Subject: [PATCH 01/13] Add payment method logos to blocks card label --- .../checkout/blocks/payment-method-label.js | 48 ++++-- .../blocks/payment-methods-logos/index.ts | 1 + .../payment-methods-logos/logo-popover.tsx | 139 +++++++++++++++++ .../payment-methods-logos.tsx | 143 ++++++++++++++++++ .../blocks/payment-methods-logos/style.scss | 47 ++++++ .../blocks/test/payment-method-logos.test.tsx | 115 ++++++++++++++ 6 files changed, 481 insertions(+), 12 deletions(-) create mode 100644 client/checkout/blocks/payment-methods-logos/index.ts create mode 100644 client/checkout/blocks/payment-methods-logos/logo-popover.tsx create mode 100644 client/checkout/blocks/payment-methods-logos/payment-methods-logos.tsx create mode 100644 client/checkout/blocks/payment-methods-logos/style.scss create mode 100644 client/checkout/blocks/test/payment-method-logos.test.tsx diff --git a/client/checkout/blocks/payment-method-label.js b/client/checkout/blocks/payment-method-label.js index 752a9b830db..38a0ccf9d88 100644 --- a/client/checkout/blocks/payment-method-label.js +++ b/client/checkout/blocks/payment-method-label.js @@ -5,6 +5,11 @@ import { Elements, PaymentMethodMessagingElement, } from '@stripe/react-stripe-js'; +import { PaymentMethodsLogos } from './payment-methods-logos'; +import Visa from 'assets/images/payment-method-icons/visa.svg?asset'; +import Mastercard from 'assets/images/payment-method-icons/mastercard.svg?asset'; +import Amex from 'assets/images/payment-method-icons/amex.svg?asset'; +import Discover from 'assets/images/payment-method-icons/discover.svg?asset'; import { normalizeCurrencyToMinorUnit } from '../utils'; import { useStripeForUPE } from 'wcpay/hooks/use-stripe-async'; import { getUPEConfig } from 'wcpay/utils/checkout'; @@ -13,6 +18,32 @@ import './style.scss'; import { useEffect, useState } from '@wordpress/element'; import { getAppearance } from 'wcpay/checkout/upe-styles'; +const paymentMethods = [ + { + name: 'visa', + component: Visa, + }, + { + name: 'mastercard', + component: Mastercard, + }, + { + name: 'amex', + component: Amex, + }, + { + name: 'discover', + component: Discover, + }, + // TODO: Missing Diners Club + // TODO: What other card payment methods should be here? +]; +const breakpointConfigs = [ + { breakpoint: 550, maxElements: 2 }, + { breakpoint: 833, maxElements: 4 }, + { breakpoint: 960, maxElements: 2 }, +]; + const bnplMethods = [ 'affirm', 'afterpay_clearpay', 'klarna' ]; const PaymentMethodMessageWrapper = ( { upeName, @@ -47,16 +78,12 @@ const PaymentMethodMessageWrapper = ( { ); }; -export default ( { api, title, countries, iconLight, iconDark, upeName } ) => { +export default ( { api, title, countries, upeName } ) => { const cartData = wp.data.select( 'wc/store/cart' ).getCartData(); const isTestMode = getUPEConfig( 'testMode' ); const [ appearance, setAppearance ] = useState( getUPEConfig( 'wcBlocksUPEAppearance' ) ); - const [ upeAppearanceTheme, setUpeAppearanceTheme ] = useState( - getUPEConfig( 'wcBlocksUPEAppearanceTheme' ) - ); - // Stripe expects the amount to be sent as the minor unit of 2 digits. const amount = parseInt( normalizeCurrencyToMinorUnit( @@ -81,7 +108,6 @@ export default ( { api, title, countries, iconLight, iconDark, upeName } ) => { 'blocks_checkout' ); setAppearance( upeAppearance ); - setUpeAppearanceTheme( upeAppearance.theme ); } if ( ! appearance ) { @@ -104,12 +130,10 @@ export default ( { api, title, countries, iconLight, iconDark, upeName } ) => { { __( 'Test Mode', 'woocommerce-payments' ) } ) } - { void; + dataTestId?: string; +} + +export const LogoPopover: React.FC< LogoPopoverProps > = ( { + id, + className, + children, + anchor, + open, + onClose, + dataTestId, +} ) => { + const popoverRef = useRef< HTMLDivElement >( null ); + const [ isPositioned, setIsPositioned ] = useState( false ); + + const updatePosition = useCallback( () => { + const popover = popoverRef.current; + if ( ! popover || ! anchor ) { + return; + } + + // Get the most up-to-date anchor rect + const anchorRect = anchor.getBoundingClientRect(); + + // Temporarily make the popover visible to get correct dimensions + popover.style.visibility = 'hidden'; + popover.style.display = 'block'; + const popoverRect = popover.getBoundingClientRect(); + popover.style.display = ''; + popover.style.visibility = ''; + + const offset = 7; + const left = anchorRect.left; + // Position the popover above the anchor + const top = anchorRect.top - popoverRect.height - offset; + + popover.style.position = 'fixed'; + popover.style.width = `${ anchorRect.width }px`; + popover.style.left = `${ left }px`; + popover.style.top = `${ top }px`; + + // Adjust position if popover goes off-screen + if ( top < 0 ) { + // If there's not enough space above, position it below the anchor + popover.style.top = `${ anchorRect.bottom + offset }px`; + } + + setIsPositioned( true ); + }, [ anchor ] ); + + useLayoutEffect( () => { + if ( open && anchor ) { + // Use requestAnimationFrame to ensure the DOM has updated before positioning + requestAnimationFrame( updatePosition ); + } + }, [ open, anchor, updatePosition ] ); + + useEffect( () => { + if ( open && anchor ) { + const observer = new MutationObserver( updatePosition ); + observer.observe( anchor, { + attributes: true, + childList: true, + subtree: true, + } ); + + window.addEventListener( 'resize', updatePosition ); + window.addEventListener( 'scroll', updatePosition ); + + const handleOutsideClick = ( event: MouseEvent ) => { + if ( + popoverRef.current && + ! popoverRef.current.contains( event.target as Node ) && + ! anchor.contains( event.target as Node ) + ) { + onClose?.(); + } + }; + + const handleEscapeKey = ( event: KeyboardEvent ) => { + if ( event.key === 'Escape' ) { + onClose?.(); + } + }; + + document.addEventListener( 'mousedown', handleOutsideClick ); + document.addEventListener( 'keydown', handleEscapeKey ); + + return () => { + observer.disconnect(); + window.removeEventListener( 'resize', updatePosition ); + window.removeEventListener( 'scroll', updatePosition ); + document.removeEventListener( 'mousedown', handleOutsideClick ); + document.removeEventListener( 'keydown', handleEscapeKey ); + }; + } + }, [ open, anchor, updatePosition, onClose ] ); + + if ( ! open ) { + return null; + } + + return ( + + ); +}; diff --git a/client/checkout/blocks/payment-methods-logos/payment-methods-logos.tsx b/client/checkout/blocks/payment-methods-logos/payment-methods-logos.tsx new file mode 100644 index 00000000000..0fb54df3755 --- /dev/null +++ b/client/checkout/blocks/payment-methods-logos/payment-methods-logos.tsx @@ -0,0 +1,143 @@ +/** + * External dependencies + */ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +/** + * Internal dependencies + */ +import { LogoPopover } from './logo-popover'; +import './style.scss'; + +interface BreakpointConfig { + breakpoint: number; + maxElements: number; +} + +interface PaymentMethodsLogosProps { + maxElements: number; + paymentMethods: { name: string; component: string }[]; + breakpointConfigs?: BreakpointConfig[]; +} + +const breakpointConfigsDefault = [ + { breakpoint: 480, maxElements: 5 }, + { breakpoint: 768, maxElements: 7 }, +]; +const paymentMethodsDefault: never[] = []; +export const PaymentMethodsLogos: React.FC< PaymentMethodsLogosProps > = ( { + maxElements = 10, + paymentMethods = paymentMethodsDefault, + breakpointConfigs = breakpointConfigsDefault, +} ) => { + const [ maxShownElements, setMaxShownElements ] = useState( maxElements ); + const [ + popoverAnchor, + setPopoverAnchor, + ] = useState< HTMLDivElement | null >( null ); + const [ popoverOpen, setPopoverOpen ] = useState( false ); + const [ shouldHavePopover, setShouldHavePopover ] = useState( false ); + + const togglePopover = () => setPopoverOpen( ! popoverOpen ); + + const anchorRef = useCallback( ( node: HTMLDivElement | null ) => { + if ( node !== null ) { + setPopoverAnchor( node ); + } + }, [] ); + + const buttonRef = useRef< HTMLDivElement | null >( null ); + + const handlePopoverClose = useCallback( () => { + setPopoverOpen( false ); + buttonRef.current?.focus(); + }, [] ); + + useEffect( () => { + const updateMaxElements = () => { + const sortedConfigs = [ ...breakpointConfigs ].sort( + ( a, b ) => a.breakpoint - b.breakpoint + ); + const config = sortedConfigs.find( + ( cfg ) => window.innerWidth <= cfg.breakpoint + ); + + setMaxShownElements( config ? config.maxElements : maxElements ); + }; + + updateMaxElements(); + window.addEventListener( 'resize', updateMaxElements ); + + return () => window.removeEventListener( 'resize', updateMaxElements ); + }, [ breakpointConfigs, maxElements ] ); + + useEffect( () => { + if ( popoverAnchor ) { + buttonRef.current = popoverAnchor; + } + }, [ popoverAnchor ] ); + + useEffect( () => { + setShouldHavePopover( paymentMethods.length > maxShownElements ); + }, [ maxShownElements, paymentMethods.length ] ); + + return ( + <> +
+
{ + if ( e.key === 'Enter' || e.key === ' ' ) { + e.preventDefault(); + togglePopover(); + } + }, + role: 'button', + tabIndex: 0, + 'aria-expanded': popoverOpen, + 'aria-controls': 'payment-methods-popover', + } ) } + data-testid="payment-methods-logos" + > + { paymentMethods + .slice( 0, maxShownElements ) + .map( ( pm ) => ( + { + ) ) } + { shouldHavePopover && ( +
+ + { paymentMethods.length - maxShownElements } +
+ ) } +
+
+ { shouldHavePopover && popoverOpen && ( + + { paymentMethods.slice( maxShownElements ).map( ( pm ) => ( + { + ) ) } + + ) } + + ); +}; diff --git a/client/checkout/blocks/payment-methods-logos/style.scss b/client/checkout/blocks/payment-methods-logos/style.scss new file mode 100644 index 00000000000..b5f08d2ef8d --- /dev/null +++ b/client/checkout/blocks/payment-methods-logos/style.scss @@ -0,0 +1,47 @@ +.payment-methods--logos { + > div { + display: flex; + align-items: center; + + img { + width: 37px; + height: 24px; + margin-right: 4px; + border: 1px solid $gray-300; + border-radius: 3px; + } + } + + &-count { + width: 38px; + height: 24px; + background-color: rgba( $gray-700, 0.1 ); + color: $gray-900; + text-align: center; + line-height: 24px; + border-radius: 3px; + font-size: 11px; + font-weight: 600; + } +} + +.logo-popover { + background-color: #fff; + border: 1px solid $gray-300; + border-radius: 3px; + padding: 10px; + box-sizing: border-box; + box-shadow: 0 0 10px 0 rgba( 0, 0, 0, 0.1 ); + display: grid; + grid-template-columns: repeat( auto-fit, minmax( 38px, 1fr ) ); + gap: 10px; + justify-items: center; + align-items: center; + cursor: pointer; + + > img { + box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.1 ); + max-width: 100%; + height: auto; + } +} diff --git a/client/checkout/blocks/test/payment-method-logos.test.tsx b/client/checkout/blocks/test/payment-method-logos.test.tsx new file mode 100644 index 00000000000..2e946884312 --- /dev/null +++ b/client/checkout/blocks/test/payment-method-logos.test.tsx @@ -0,0 +1,115 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, test, expect } from '@jest/globals'; +/** + * Internal dependencies + */ +import { PaymentMethodsLogos } from '../payment-methods-logos'; + +const mockPaymentMethods = [ + { name: 'Visa', component: 'visa.png' }, + { name: 'MasterCard', component: 'mastercard.png' }, + { name: 'PayPal', component: 'paypal.png' }, + { name: 'Amex', component: 'amex.png' }, + { name: 'Discover', component: 'discover.png' }, +]; + +describe( 'PaymentMethodsLogos', () => { + test( 'renders without crashing', () => { + render( + + ); + const logoContainer = screen.getByTestId( 'payment-methods-logos' ); + expect( logoContainer ).toBeTruthy(); + } ); + + test( 'displays correct number of logos based on maxElements', () => { + render( + + ); + const logos = screen.queryAllByRole( 'img' ); + expect( logos ).toHaveLength( 3 ); + } ); + + test( 'shows popover indicator when there are more payment methods than maxElements', () => { + render( + + ); + const popoverIndicator = screen.queryByText( + `+ ${ mockPaymentMethods.length - 3 }` + ); + expect( popoverIndicator ).toBeTruthy(); + } ); + + test( 'opens popover on button click', async () => { + render( + + ); + const button = screen.getByTestId( 'payment-methods-logos' ); + + fireEvent.click( button ); + + const popover = await screen.findByTestId( 'payment-methods-popover' ); + expect( popover ).toBeTruthy(); + } ); + + test( 'handles keyboard navigation', async () => { + render( + + ); + const button = screen.getByTestId( 'payment-methods-logos' ); + + fireEvent.keyDown( button, { key: 'Enter' } ); + + const popover = await screen.findByTestId( 'payment-methods-popover' ); + expect( popover ).toBeTruthy(); + } ); + + test( 'does not show popover indicator when there are fewer payment methods than maxElements', () => { + render( + + ); + const popoverIndicator = screen.queryByText( /^\+\s*\d+$/ ); + expect( popoverIndicator ).toBeNull(); + + const logos = screen.getAllByRole( 'img' ); + expect( logos ).toHaveLength( mockPaymentMethods.length ); + } ); + + test( 'does not show popover when there are fewer payment methods than maxElements', async () => { + render( + + ); + + const button = screen.getByTestId( 'payment-methods-logos' ); + + fireEvent.click( button ); + + const popover = screen.queryByTestId( 'payment-methods-popover' ); + expect( popover ).toBeNull(); + } ); +} ); From cf0720177a0e4bf4def705e4a6f53f48f5c4e158 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Fri, 20 Dec 2024 16:30:32 -0500 Subject: [PATCH 02/13] Changelog --- changelog/add-9826-payment-methods-logos-component | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/add-9826-payment-methods-logos-component diff --git a/changelog/add-9826-payment-methods-logos-component b/changelog/add-9826-payment-methods-logos-component new file mode 100644 index 00000000000..51f66c6e070 --- /dev/null +++ b/changelog/add-9826-payment-methods-logos-component @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add payment method logos to checkout block card label. From a71bb0b234d43ab1067f092d126b75d60aca3da4 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Wed, 8 Jan 2025 16:31:45 -0500 Subject: [PATCH 03/13] Re-add non-card logos --- .../checkout/blocks/payment-method-label.js | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/client/checkout/blocks/payment-method-label.js b/client/checkout/blocks/payment-method-label.js index 51df261b50b..6b5918de763 100644 --- a/client/checkout/blocks/payment-method-label.js +++ b/client/checkout/blocks/payment-method-label.js @@ -78,7 +78,7 @@ const PaymentMethodMessageWrapper = ( { ); }; -export default ( { api, title, countries, upeName } ) => { +export default ( { api, title, countries, iconLight, iconDark, upeName } ) => { const cartData = wp.data.select( 'wc/store/cart' ).getCartData(); const isTestMode = getUPEConfig( 'testMode' ); const [ appearance, setAppearance ] = useState( @@ -115,6 +115,7 @@ export default ( { api, title, countries, upeName } ) => { 'blocks_checkout' ); setAppearance( upeAppearance ); + setUpeAppearanceTheme( upeAppearance.theme ); } if ( ! appearance ) { @@ -137,11 +138,23 @@ export default ( { api, title, countries, upeName } ) => { { __( 'Test Mode', 'woocommerce-payments' ) } ) } - + { upeName === 'card' ? ( + + ) : ( + { + ) } Date: Tue, 21 Jan 2025 08:56:01 -0500 Subject: [PATCH 04/13] Add card brand logos to classic checkout --- client/checkout/classic/event-handlers.js | 205 ++++++++++++++++++++++ client/checkout/classic/style.scss | 7 + 2 files changed, 212 insertions(+) diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index 4ed1912250f..c866524f974 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -33,6 +33,10 @@ import { isPreviewing } from 'wcpay/checkout/preview'; import { recordUserEvent } from 'tracks'; import '../utils/copy-test-number'; import { SHORTCODE_BILLING_ADDRESS_FIELDS } from '../constants'; +import Visa from 'assets/images/payment-method-icons/visa.svg?asset'; +import Mastercard from 'assets/images/payment-method-icons/mastercard.svg?asset'; +import Amex from 'assets/images/payment-method-icons/amex.svg?asset'; +import Discover from 'assets/images/payment-method-icons/discover.svg?asset'; jQuery( function ( $ ) { enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); @@ -73,6 +77,7 @@ jQuery( function ( $ ) { $( document.body ).on( 'updated_checkout', () => { maybeMountStripePaymentElement( 'shortcode_checkout' ); injectStripePMMEContainers(); + injectPaymentMethodLogos(); } ); $checkoutForm.on( generateCheckoutEventNames(), function () { @@ -239,6 +244,206 @@ jQuery( function ( $ ) { } } + async function injectPaymentMethodLogos() { + const cardLabel = document.querySelector( + 'label[for="payment_method_woocommerce_payments"]' + ); + if ( ! cardLabel ) return; + + const target = cardLabel.querySelector( 'img' ); + if ( ! target ) return; + + // Create container div + const logosContainer = document.createElement( 'div' ); + logosContainer.className = 'payment-methods--logos'; + + // Create inner div for flex layout + const innerContainer = document.createElement( 'div' ); + innerContainer.setAttribute( 'role', 'button' ); + innerContainer.setAttribute( 'tabindex', '0' ); + innerContainer.setAttribute( 'data-testid', 'payment-methods-logos' ); + + const paymentMethods = [ + { name: 'visa', component: Visa }, + { name: 'mastercard', component: Mastercard }, + { name: 'amex', component: Amex }, + { name: 'discover', component: Discover }, + ]; + + function getMaxElements() { + return window.innerWidth <= 330 ? 2 : 4; + } + + function shouldHavePopover() { + return paymentMethods.length > getMaxElements(); + } + + function createPopover( remainingMethods ) { + const popover = document.createElement( 'div' ); + popover.className = 'logo-popover'; + popover.setAttribute( 'role', 'dialog' ); + popover.setAttribute( + 'aria-label', + 'Supported Credit Card Brands' + ); + + remainingMethods.forEach( ( pm ) => { + const img = document.createElement( 'img' ); + img.src = pm.component; + img.alt = pm.name; + img.width = 38; + img.height = 24; + popover.appendChild( img ); + } ); + + return popover; + } + + function positionPopover( popover, anchor ) { + const label = anchor.closest( 'label' ); + if ( ! label ) return; + + const labelRect = label.getBoundingClientRect(); + const anchorRect = anchor.getBoundingClientRect(); + + if ( ! popover.dataset.labelOffset ) { + popover.style.visibility = 'hidden'; + popover.style.display = 'block'; + const popoverRect = popover.getBoundingClientRect(); + popover.style.display = ''; + popover.style.visibility = ''; + + const offset = 7; + const initialTop = labelRect.top - popoverRect.height - offset; + + // Store the offset from the label + popover.dataset.labelOffset = ( + labelRect.top - initialTop + ).toString(); + } + + const labelOffset = parseFloat( popover.dataset.labelOffset ); + + popover.style.position = 'fixed'; + popover.style.width = `${ anchorRect.width }px`; + popover.style.left = `${ anchorRect.left }px`; // Use anchor's left position + popover.style.top = `${ labelRect.top - labelOffset }px`; + popover.style.zIndex = '1000'; + } + + function updateLogos() { + innerContainer.innerHTML = ''; // Clear existing logos + const maxElements = getMaxElements(); + const visibleMethods = paymentMethods.slice( 0, maxElements ); + const remainingCount = paymentMethods.length - maxElements; + + // Add visible logos + visibleMethods.forEach( ( pm ) => { + const brandImg = document.createElement( 'img' ); + brandImg.src = pm.component; + brandImg.alt = pm.name; + brandImg.width = 38; + brandImg.height = 24; + innerContainer.appendChild( brandImg ); + } ); + + // Add count indicator if we should have a popover + if ( shouldHavePopover() ) { + const countDiv = document.createElement( 'div' ); + countDiv.className = 'payment-methods--logos-count'; + countDiv.textContent = `+ ${ remainingCount }`; + innerContainer.appendChild( countDiv ); + } + + // Remove existing popover if we no longer need it + const existingPopover = cardLabel.querySelector( '.logo-popover' ); + if ( existingPopover && ! shouldHavePopover() ) { + existingPopover.remove(); + } + } + + function setupPopover() { + const popover = createPopover( + paymentMethods.slice( getMaxElements() ) + ); + cardLabel.appendChild( popover ); + positionPopover( popover, innerContainer ); + + const handleResize = () => + positionPopover( popover, innerContainer ); + window.addEventListener( 'resize', handleResize ); + window.addEventListener( 'scroll', handleResize ); + + const handlers = {}; + + const cleanup = () => { + popover.remove(); + window.removeEventListener( 'resize', handleResize ); + window.removeEventListener( 'scroll', handleResize ); + document.removeEventListener( + 'mousedown', + handlers.handleOutsideClick + ); + document.removeEventListener( + 'keydown', + handlers.handleEscapeKey + ); + }; + + handlers.handleOutsideClick = ( e ) => { + if ( + ! popover.contains( e.target ) && + ! innerContainer.contains( e.target ) + ) { + cleanup(); + } + }; + + handlers.handleEscapeKey = ( e ) => { + if ( e.key === 'Escape' ) { + cleanup(); + } + }; + + document.addEventListener( + 'mousedown', + handlers.handleOutsideClick + ); + document.addEventListener( 'keydown', handlers.handleEscapeKey ); + } + + function togglePopover() { + if ( ! shouldHavePopover() ) return; + + const existingPopover = cardLabel.querySelector( '.logo-popover' ); + if ( existingPopover ) { + existingPopover.remove(); + return; + } + + setupPopover(); + } + + // Click handler + innerContainer.addEventListener( 'click', togglePopover ); + + // Keyboard handler + innerContainer.addEventListener( 'keydown', ( e ) => { + if ( e.key === 'Enter' || e.key === ' ' ) { + e.preventDefault(); + togglePopover(); + } + } ); + + // Initial setup + logosContainer.appendChild( innerContainer ); + target.replaceWith( logosContainer ); + updateLogos(); + + // Update on window resize + window.addEventListener( 'resize', updateLogos ); + } + function processPaymentIfNotUsingSavedMethod( $form ) { const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); if ( ! isUsingSavedPaymentMethod( paymentMethodType ) ) { diff --git a/client/checkout/classic/style.scss b/client/checkout/classic/style.scss index e1fd24e3bdc..2b937fb2dae 100644 --- a/client/checkout/classic/style.scss +++ b/client/checkout/classic/style.scss @@ -35,6 +35,13 @@ #payment .payment_methods { li[class*='payment_method_woocommerce_payments'] label { display: inline; + .payment-methods--logos { + float: right; + + img:last-of-type { + margin-right: 0; + } + } img { float: right; border: 0; From 5d980b7162070145ac708c1adcc3c3e6ac0c44e8 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 22 Jan 2025 14:11:44 -0300 Subject: [PATCH 05/13] Update Credit Card / Debit Card label to Cards (#9769) --- changelog/update-cards-label | 4 ++++ client/checkout/blocks/style.scss | 10 ---------- includes/class-wc-payment-gateway-wcpay.php | 2 +- includes/payment-methods/class-cc-payment-method.php | 2 +- .../payment-methods/test-class-upe-payment-gateway.php | 2 +- .../test-class-upe-split-payment-gateway.php | 2 +- tests/unit/test-class-wc-payment-gateway-wcpay.php | 2 +- tests/unit/test-class-wc-payments-checkout.php | 2 +- 8 files changed, 10 insertions(+), 16 deletions(-) create mode 100644 changelog/update-cards-label diff --git a/changelog/update-cards-label b/changelog/update-cards-label new file mode 100644 index 00000000000..ce09cd5fc3a --- /dev/null +++ b/changelog/update-cards-label @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update Credit Card / Debit Card label to Cards diff --git a/client/checkout/blocks/style.scss b/client/checkout/blocks/style.scss index 3e400b6bfff..369a1141d75 100644 --- a/client/checkout/blocks/style.scss +++ b/client/checkout/blocks/style.scss @@ -95,16 +95,6 @@ button.wcpay-stripelink-modal-trigger:hover { display: none; } - @include breakpoint( '<480px' ) { - grid-template-areas: 'label logos' 'badge badge'; - grid-template-columns: 1fr auto; - align-items: start; - - .payment-methods--logos { - justify-self: end; - } - } - &__pmme-container { width: 100%; pointer-events: none; diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index d1be21241b9..45d80db04db 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -1582,7 +1582,7 @@ public function process_payment_for_order( $cart, $payment_information, $schedul do_action( 'woocommerce_payments_changed_subscription_payment_method', $order, $payment_token ); } - $order->set_payment_method_title( __( 'Credit / Debit Card', 'woocommerce-payments' ) ); + $order->set_payment_method_title( __( 'Credit / Debit Cards', 'woocommerce-payments' ) ); $order->save(); return [ diff --git a/includes/payment-methods/class-cc-payment-method.php b/includes/payment-methods/class-cc-payment-method.php index 58d7d733a77..9e7f3ba6860 100644 --- a/includes/payment-methods/class-cc-payment-method.php +++ b/includes/payment-methods/class-cc-payment-method.php @@ -39,7 +39,7 @@ public function __construct( $token_service ) { */ public function get_title( ?string $account_country = null, $payment_details = false ) { if ( ! $payment_details ) { - return __( 'Credit card / debit card', 'woocommerce-payments' ); + return __( 'Cards', 'woocommerce-payments' ); } $details = $payment_details[ $this->stripe_id ]; diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php index 000f5eade08..4df9604fa2e 100644 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -698,7 +698,7 @@ public function test_payment_methods_show_correct_default_outputs() { $afterpay_method = $this->mock_payment_methods['afterpay_clearpay']; $this->assertEquals( 'card', $card_method->get_id() ); - $this->assertEquals( 'Credit card / debit card', $card_method->get_title( 'US' ) ); + $this->assertEquals( 'Cards', $card_method->get_title( 'US' ) ); $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php index a3284a840ce..673aaf4bdd0 100644 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -893,7 +893,7 @@ public function test_payment_methods_show_correct_default_outputs() { $becs_method = $this->mock_payment_methods['au_becs_debit']; $this->assertEquals( 'card', $card_method->get_id() ); - $this->assertEquals( 'Credit card / debit card', $card_method->get_title( 'US' ) ); + $this->assertEquals( 'Cards', $card_method->get_title( 'US' ) ); $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 1827041a1fc..039f229e3a7 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -623,7 +623,7 @@ public function test_payment_methods_show_correct_default_outputs() { $afterpay_method = $this->payment_methods['afterpay_clearpay']; $this->assertEquals( 'card', $card_method->get_id() ); - $this->assertEquals( 'Credit card / debit card', $card_method->get_title() ); + $this->assertEquals( 'Cards', $card_method->get_title() ); $this->assertEquals( 'Visa debit card', $card_method->get_title( 'US', $mock_visa_details ) ); $this->assertEquals( 'Mastercard credit card', $card_method->get_title( 'US', $mock_mastercard_details ) ); $this->assertTrue( $card_method->is_enabled_at_checkout( 'US' ) ); diff --git a/tests/unit/test-class-wc-payments-checkout.php b/tests/unit/test-class-wc-payments-checkout.php index 1fcbe1093ff..0153ecd7c51 100644 --- a/tests/unit/test-class-wc-payments-checkout.php +++ b/tests/unit/test-class-wc-payments-checkout.php @@ -377,7 +377,7 @@ public function test_link_payment_method_provided_when_card_enabled() { [ 'card' => [ 'isReusable' => true, - 'title' => 'Credit card / debit card', + 'title' => 'Cards', 'icon' => $icon_url, 'darkIcon' => $dark_icon_url, 'showSaveOption' => true, From 83dc299b2f8ec56b0894df3ccb52e06f8262747b Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Wed, 22 Jan 2025 12:20:10 -0500 Subject: [PATCH 06/13] Update settings UI text for credit / debit cards --- client/payment-methods-map.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/payment-methods-map.tsx b/client/payment-methods-map.tsx index b22ca1f9ae2..69fa3e0f74a 100644 --- a/client/payment-methods-map.tsx +++ b/client/payment-methods-map.tsx @@ -44,7 +44,7 @@ const PaymentMethodInformationObject: Record< > = { card: { id: 'card', - label: __( 'Credit / Debit card', 'woocommerce-payments' ), + label: __( 'Credit / Debit Cards', 'woocommerce-payments' ), description: __( 'Let your customers pay with major credit and debit cards without leaving your store.', 'woocommerce-payments' From 858b9a281f0913069cff53e0c1d9df523c0e0940 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Wed, 22 Jan 2025 18:51:15 -0500 Subject: [PATCH 07/13] Update tests --- .../payment-methods-checkboxes/test/index.test.tsx | 2 +- .../__tests__/__snapshots__/activation-modal.test.js.snap | 4 ++-- .../__tests__/__snapshots__/delete-modal.test.js.snap | 6 +++--- .../__tests__/payment-methods-section.test.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/components/payment-methods-checkboxes/test/index.test.tsx b/client/components/payment-methods-checkboxes/test/index.test.tsx index 59b231190e0..de5891f5954 100644 --- a/client/components/payment-methods-checkboxes/test/index.test.tsx +++ b/client/components/payment-methods-checkboxes/test/index.test.tsx @@ -195,7 +195,7 @@ describe( 'PaymentMethodsCheckboxes', () => { ); const cardCheckbox = screen.getByRole( 'checkbox', { - name: 'Credit / Debit card', + name: 'Credit / Debit Cards', } ); expect( cardCheckbox ).not.toBeChecked(); userEvent.click( cardCheckbox ); diff --git a/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap b/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap index 6382c0dbaa0..c76c4ca8683 100644 --- a/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap +++ b/client/settings/payment-methods-list/__tests__/__snapshots__/activation-modal.test.js.snap @@ -55,7 +55,7 @@ exports[`Activation Modal matches the snapshot 1`] = ` class="components-modal__header-heading" id="components-modal-header-0" > - One more step to enable Credit / Debit card + One more step to enable Credit / Debit Cards