Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use statement_descriptor_suffix for card payments and fallback to Stripe account statement descriptions for all payments #2833

Merged
merged 11 commits into from
Jan 24, 2024
Merged
4 changes: 4 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
*** Changelog ***

= 7.9.2 - 2024-xx-xx =
* Fix - Resolved an issue that could cause card payments to fail when providing a Bank statement description with the `statement_descriptor` parameter.
* Tweak - The Bank statement description settings in the Stripe plugin settings are no longer editable. The description is now automatically pulled from the Stripe account settings.

= 7.9.1 - 2024-01-16 =
* Fix - PHP fatal error when updating a user with saved tokens from the WP Dashboard.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import PaymentsAndTransactionsSection from '..';
import { useAccount } from 'wcstripe/data/account';
import {
Expand Down Expand Up @@ -34,110 +34,84 @@ describe( 'PaymentsAndTransactionsSection', () => {
jest.fn(),
] );
useSeparateCardForm.mockReturnValue( [ true, jest.fn() ] );
useAccountStatementDescriptor.mockReturnValue( [
'WOOTESTING, LTD',
jest.fn(),
] );
useShortAccountStatementDescriptor.mockReturnValue( [
'WOOTESTING',
jest.fn(),
] );
useAccount.mockReturnValue( {
data: {
account: {
settings: {
payments: { statement_descriptor: 'WOOTESTING, LTD' },
card_payments: {
statement_descriptor_prefix: 'WOOTEST',
},
},
},
},
} );

useGetSavingError.mockReturnValue( null );
useAccount.mockReturnValue( { data: {} } );
} );

it( 'displays the length of the bank statement input', () => {
const updateAccountStatementDescriptor = jest.fn();
useAccountStatementDescriptor.mockReturnValue( [
'WOOTESTING, LTD',
updateAccountStatementDescriptor,
] );
render( <PaymentsAndTransactionsSection /> );

// The default bank statement ("WOOTESTING, LTD") is 15 characters long.
expect( screen.getByText( '15 / 22' ) ).toBeInTheDocument();

fireEvent.change( screen.getByLabelText( 'Full bank statement' ), {
target: { value: 'New Statement Name' },
} );

expect( updateAccountStatementDescriptor ).toHaveBeenCalledWith(
'New Statement Name'
);
} );

it( 'shows the shortened bank statement input', () => {
useIsShortAccountStatementEnabled.mockReturnValue( [
true,
jest.fn(),
] );
const updateShortAccountStatementDescriptor = jest.fn();
useShortAccountStatementDescriptor.mockReturnValue( [
'WOOTEST',
updateShortAccountStatementDescriptor,
] );
render( <PaymentsAndTransactionsSection /> );

expect( screen.getByText( '7 / 10' ) ).toBeInTheDocument();
useAccount.mockReturnValue( {
data: {
account: {
settings: {
card_payments: {
statement_descriptor_prefix: 'WOOTEST',
},
},
},
},
} );

fireEvent.change(
screen.getByLabelText( 'Shortened customer bank statement' ),
{
target: { value: 'WOOTESTING' },
}
);
render( <PaymentsAndTransactionsSection /> );

expect( updateShortAccountStatementDescriptor ).toHaveBeenCalledWith(
'WOOTESTING'
);
// The default short bank statement ("WOOTEST") is 7 characters long.
expect( screen.getByText( '7 / 10' ) ).toBeInTheDocument();
} );

it( 'shows the full bank statement preview', () => {
const updateAccountStatementDescriptor = jest.fn();
const mockValue = 'WOOTESTING, LTD';
useAccountStatementDescriptor.mockReturnValue( [
mockValue,
updateAccountStatementDescriptor,
] );
render( <PaymentsAndTransactionsSection /> );

expect(
document.querySelector(
'.full-bank-statement .transaction-detail.description'
)
).toHaveTextContent( mockValue );
).toHaveTextContent( 'WOOTESTING, LTD' );
} );

it( 'shows the shortened customer bank statement preview when useIsShortAccountStatementEnabled is true', () => {
useIsShortAccountStatementEnabled.mockReturnValue( [
true,
jest.fn(),
] );
const updateShortAccountStatementDescriptor = jest.fn();
const mockValue = 'WOOTEST';
useShortAccountStatementDescriptor.mockReturnValue( [
mockValue,
updateShortAccountStatementDescriptor,
] );

render( <PaymentsAndTransactionsSection /> );

expect(
document.querySelector(
'.shortened-bank-statement .transaction-detail.description'
)
).toHaveTextContent( `${ mockValue }* #123456` );
).toHaveTextContent( 'WOOTEST* W #123456' );
} );

it( 'should not show the shortened customer bank statement preview when useIsShortAccountStatementEnabled is false', () => {
useIsShortAccountStatementEnabled.mockReturnValue( [
false,
jest.fn(),
] );
const updateShortAccountStatementDescriptor = jest.fn();
const mockValue = 'WOOTEST';
useShortAccountStatementDescriptor.mockReturnValue( [
mockValue,
updateShortAccountStatementDescriptor,
] );

render( <PaymentsAndTransactionsSection /> );

expect(
Expand Down Expand Up @@ -215,25 +189,4 @@ describe( 'PaymentsAndTransactionsSection', () => {
)
).toBeInTheDocument();
} );

it( "shows the account's statement descriptor placeholder", () => {
const mockValue = 'WOOTESTING, LTD';

useAccount.mockReturnValue( {
data: {
account: {
settings: { payments: { statement_descriptor: mockValue } },
},
},
} );
useIsShortAccountStatementEnabled.mockReturnValue( [
true,
jest.fn(),
] );
render( <PaymentsAndTransactionsSection /> );

expect(
screen.queryByText( 'Full bank statement' ).nextElementSibling
).toHaveAttribute( 'placeholder', mockValue );
} );
} );
79 changes: 49 additions & 30 deletions client/settings/payments-and-transactions-section/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { __ } from '@wordpress/i18n';
import React, { useContext } from 'react';
import { Card, CheckboxControl, TextControl } from '@wordpress/components';
import {
Card,
CheckboxControl,
TextControl,
ExternalLink,
} from '@wordpress/components';
import { Icon, help } from '@wordpress/icons';
import interpolateComponents from 'interpolate-components';
import CardBody from '../card-body';
import TextLengthHelpInputWrapper from './text-length-help-input-wrapper';
import StatementPreviewsWrapper from './statement-previews-wrapper';
Expand All @@ -12,9 +18,7 @@ import Tooltip from 'wcstripe/components/tooltip';
import {
useSavedCards,
useSeparateCardForm,
useAccountStatementDescriptor,
useIsShortAccountStatementEnabled,
useShortAccountStatementDescriptor,
useGetSavingError,
} from 'wcstripe/data';
import InlineNotice from 'wcstripe/components/inline-notice';
Expand All @@ -39,18 +43,10 @@ const PaymentsAndTransactionsSection = () => {
isSeparateCardFormEnabled,
setIsSeparateCardFormEnabled,
] = useSeparateCardForm();
const [
accountStatementDescriptor,
setAccountStatementDescriptor,
] = useAccountStatementDescriptor();
const [
isShortAccountStatementEnabled,
setIsShortAccountStatementEnabled,
] = useIsShortAccountStatementEnabled();
const [
shortAccountStatementDescriptor,
setShortAccountStatementDescriptor,
] = useShortAccountStatementDescriptor();

const { isUpeEnabled } = useContext( UpeToggleContext );

Expand All @@ -64,9 +60,19 @@ const PaymentsAndTransactionsSection = () => {
: __( 'All Payment Methods', 'woocommerce-gateway-stripe' );

const { data } = useAccount();
const statementDescriptorPlaceholder =
const stripeAccountStatementDescriptor =
data?.account?.settings?.payments?.statement_descriptor || '';

const stripeAccountShortStatementDescriptor =
data?.account?.settings?.card_payments?.statement_descriptor_prefix ||
'';

// Stripe requires the short statement descriptor suffix to have at least 1 latin character.
// To meet this requirement, we use the first character of the full statement descriptor.
const shortStatementDescriptorSuffix = stripeAccountShortStatementDescriptor.charAt(
0
);

return (
<Card className="transactions-and-payouts">
<CardBody>
Expand Down Expand Up @@ -122,23 +128,29 @@ const PaymentsAndTransactionsSection = () => {
</InlineNotice>
) }
<TextLengthHelpInputWrapper
textLength={ accountStatementDescriptor.length }
textLength={ stripeAccountStatementDescriptor.length }
maxLength={ 22 }
iconSlot={ <TooltipBankStatementHelp /> }
>
<TextControl
help={ __(
'Enter the name your customers will see on their transactions. Use a recognizable name – e.g. the legal entity name or website address – to avoid potential disputes and chargebacks.',
'woocommerce-gateway-stripe'
) }
help={ interpolateComponents( {
mixedString: __(
'You can change the description your customers will see on their bank statement in your {{settingsLink}}Stripe account settings{{/settingsLink}}. Set this to a recognizable name – e.g. the legal entity name or website address – to avoid potential disputes and chargebacks.',
'woocommerce-gateway-stripe'
),
components: {
settingsLink: (
<ExternalLink href="https://dashboard.stripe.com/settings/public" />
),
},
} ) }
label={ __(
'Full bank statement',
'woocommerce-gateway-stripe'
) }
value={ accountStatementDescriptor }
onChange={ setAccountStatementDescriptor }
placeholder={ statementDescriptorPlaceholder }
value={ stripeAccountStatementDescriptor }
maxLength={ 22 }
disabled={ true } // This field is read only. It is set in the Stripe account.
/>
</TextLengthHelpInputWrapper>

Expand Down Expand Up @@ -170,22 +182,29 @@ const PaymentsAndTransactionsSection = () => {
) }
<TextLengthHelpInputWrapper
textLength={
shortAccountStatementDescriptor.length
stripeAccountShortStatementDescriptor.length
}
maxLength={ 10 }
>
<TextControl
help={ __(
"We'll use the short version in combination with the customer order number.",
'woocommerce-gateway-stripe'
) }
help={ interpolateComponents( {
mixedString: __(
"We'll use the shortened descriptor in combination with the customer order number. You can change the shortened description in your {{settingsLink}}Stripe account settings{{/settingsLink}}.",
'woocommerce-gateway-stripe'
),
components: {
settingsLink: (
<ExternalLink href="https://dashboard.stripe.com/settings/public" />
),
},
} ) }
label={ __(
'Shortened customer bank statement',
'woocommerce-gateway-stripe'
) }
value={ shortAccountStatementDescriptor }
onChange={ setShortAccountStatementDescriptor }
value={ stripeAccountShortStatementDescriptor }
maxLength={ 10 }
disabled={ true } // This field is read only. It is set in the Stripe account.
/>
</TextLengthHelpInputWrapper>
</>
Expand All @@ -198,16 +217,16 @@ const PaymentsAndTransactionsSection = () => {
'Cards & Express Checkouts',
'woocommerce-gateway-stripe'
) }
text={ `${ shortAccountStatementDescriptor }* #123456` }
text={ `${ stripeAccountShortStatementDescriptor }* ${ shortStatementDescriptorSuffix } #123456` }
className="shortened-bank-statement"
/>
) }
<StatementPreview
icon="bank"
title={ translatedFullBankPreviewTitle }
text={
accountStatementDescriptor ||
statementDescriptorPlaceholder
stripeAccountStatementDescriptor ||
stripeAccountShortStatementDescriptor
}
className="full-bank-statement"
/>
Expand Down
27 changes: 8 additions & 19 deletions includes/abstracts/abstract-wc-stripe-payment-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,12 @@ public function get_stripe_return_url( $order = null, $id = null ) {
*/
public function generate_payment_request( $order, $prepared_payment_method ) {
$settings = get_option( 'woocommerce_stripe_settings', [] );
$statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
$short_statement_descriptor = ! empty( $settings['short_statement_descriptor'] ) ? str_replace( "'", '', $settings['short_statement_descriptor'] ) : '';
$is_short_statement_descriptor_enabled = ! empty( $settings['is_short_statement_descriptor_enabled'] ) && 'yes' === $settings['is_short_statement_descriptor_enabled'];
$capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
$post_data = [];
$post_data['currency'] = strtolower( $order->get_currency() );
$post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );

/* translators: 1) blog name 2) order number */
$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
$billing_email = $order->get_billing_email();
Expand All @@ -446,21 +445,11 @@ public function generate_payment_request( $order, $prepared_payment_method ) {
$post_data['receipt_email'] = $billing_email;
}

switch ( $order->get_payment_method() ) {
case 'stripe':
if ( $is_short_statement_descriptor_enabled && ! ( empty( $short_statement_descriptor ) && empty( $statement_descriptor ) ) ) {
$post_data['statement_descriptor'] = WC_Stripe_Helper::get_dynamic_statement_descriptor( $short_statement_descriptor, $order, $statement_descriptor );
} elseif ( ! empty( $statement_descriptor ) ) {
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
}

$post_data['capture'] = $capture ? 'true' : 'false';
break;
case 'stripe_sepa':
if ( ! empty( $statement_descriptor ) ) {
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
}
// other payment methods error if we try to add a statement descriptor in the request
if ( 'stripe' === $order->get_payment_method() ) {
$post_data['capture'] = $capture ? 'true' : 'false';
if ( $is_short_statement_descriptor_enabled ) {
$post_data['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order );
}
}

if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
Expand Down Expand Up @@ -1324,8 +1313,8 @@ public function generate_create_intent_request( $order, $prepared_source ) {
$request['customer'] = $prepared_source->customer;
}

if ( isset( $full_request['statement_descriptor'] ) ) {
$request['statement_descriptor'] = $full_request['statement_descriptor'];
if ( isset( $full_request['statement_descriptor_suffix'] ) ) {
$request['statement_descriptor_suffix'] = $full_request['statement_descriptor_suffix'];
}

if ( isset( $full_request['shipping'] ) ) {
Expand Down