Skip to content

Commit d82e91a

Browse files
authored
Send bank statement descriptors to payment intents (#2263)
* Revert hide shortened statement descriptor * Add account's own statement descriptor placeholder to settings fields Adding the placeholder in the statement descriptor fields we have in the settings (full and shortened). * Tests: add statement descriptor placeholders test * Add shortened statement descriptor helper * Add shortened descriptor logic to non-UPE payment requests * Add descriptor logic to UPE payment requests * Remove shortened descriptor placeholder It cannot contain a placeholder because we can't replace the prefix. We're using the shortened descriptor and concatenating with the order ID, generating a full descriptor without having to set up a suffix for it. * Tests: Remove shortened descriptor placeholder assertion * Update descriptor preview to include its placeholder in case it's blank * Tests: Add statement descriptor to the request on UPE gateway tests * Update shortened statement descriptor helper Make the order parameter optional to avoid compatibility errors. * Update shortened statement descriptor helper Replace with because they can be different in case they're filtered. * Fix import path * Fix linting * Remove statement descriptor suffix logic * Update shortened statement descriptor helper Truncate the descriptor to 22 characters to avoid edge cases and possible errors. * Fix import path * Rename statement descriptor helper * Add fallback on the dynamic descriptor helper The fallback is being added because currently the field validation has a breach that needs to be worked out and allows the user to save the descriptors blank. * Tweak the dynamic statement descriptor helper * Tweak the dynamic statement descriptor helper
1 parent df82342 commit d82e91a

File tree

6 files changed

+164
-90
lines changed

6 files changed

+164
-90
lines changed

client/settings/payments-and-transactions-section/__tests__/index.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fireEvent, render, screen } from '@testing-library/react';
22
import PaymentsAndTransactionsSection from '..';
3+
import { useAccount } from 'wcstripe/data/account';
34
import {
45
useManualCapture,
56
useSavedCards,
@@ -10,6 +11,10 @@ import {
1011
useGetSavingError,
1112
} from 'wcstripe/data';
1213

14+
jest.mock( 'wcstripe/data/account', () => ( {
15+
useAccount: jest.fn(),
16+
} ) );
17+
1318
jest.mock( 'wcstripe/data', () => ( {
1419
useManualCapture: jest.fn(),
1520
useSavedCards: jest.fn(),
@@ -38,6 +43,7 @@ describe( 'PaymentsAndTransactionsSection', () => {
3843
jest.fn(),
3944
] );
4045
useGetSavingError.mockReturnValue( null );
46+
useAccount.mockReturnValue( { data: {} } );
4147
} );
4248

4349
it( 'displays the length of the bank statement input', () => {
@@ -209,4 +215,25 @@ describe( 'PaymentsAndTransactionsSection', () => {
209215
)
210216
).toBeInTheDocument();
211217
} );
218+
219+
it( "shows the account's statement descriptor placeholder", () => {
220+
const mockValue = 'WOOTESTING, LTD';
221+
222+
useAccount.mockReturnValue( {
223+
data: {
224+
account: {
225+
settings: { payments: { statement_descriptor: mockValue } },
226+
},
227+
},
228+
} );
229+
useIsShortAccountStatementEnabled.mockReturnValue( [
230+
true,
231+
jest.fn(),
232+
] );
233+
render( <PaymentsAndTransactionsSection /> );
234+
235+
expect(
236+
screen.queryByText( 'Full bank statement' ).nextElementSibling
237+
).toHaveAttribute( 'placeholder', mockValue );
238+
} );
212239
} );

client/settings/payments-and-transactions-section/index.js

Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import TextLengthHelpInputWrapper from './text-length-help-input-wrapper';
77
import StatementPreviewsWrapper from './statement-previews-wrapper';
88
import StatementPreview from './statement-preview';
99
import ManualCaptureControl from './manual-capture-control';
10+
import { useAccount } from 'wcstripe/data/account';
1011
import Tooltip from 'wcstripe/components/tooltip';
1112
import {
1213
useSavedCards,
@@ -62,6 +63,10 @@ const PaymentsAndTransactionsSection = () => {
6263
? __( 'All Other Payment Methods', 'woocommerce-gateway-stripe' )
6364
: __( 'All Payment Methods', 'woocommerce-gateway-stripe' );
6465

66+
const { data } = useAccount();
67+
const statementDescriptorPlaceholder =
68+
data?.account?.settings?.payments?.statement_descriptor || '';
69+
6570
return (
6671
<Card className="transactions-and-payouts">
6772
<CardBody>
@@ -132,81 +137,78 @@ const PaymentsAndTransactionsSection = () => {
132137
) }
133138
value={ accountStatementDescriptor }
134139
onChange={ setAccountStatementDescriptor }
140+
placeholder={ statementDescriptorPlaceholder }
135141
maxLength={ 22 }
136142
/>
137143
</TextLengthHelpInputWrapper>
138-
{ /* TODO: Hiding the Short Account Statement fields until it's included in the POST to Stripe */ }
139-
<div style={ { display: 'none' } }>
140-
<CheckboxControl
141-
checked={ isShortAccountStatementEnabled }
142-
onChange={ setIsShortAccountStatementEnabled }
143-
label={ __(
144-
'Add customer order number to the bank statement',
145-
'woocommerce-gateway-stripe'
146-
) }
147-
help={ __(
148-
"When enabled, we'll include the order number for card and express checkout transactions.",
149-
'woocommerce-gateway-stripe'
150-
) }
151-
/>
152-
{ isShortAccountStatementEnabled && (
153-
<>
154-
{ shortStatementDescriptorErrorMessage && (
155-
<InlineNotice
156-
status="error"
157-
isDismissible={ false }
158-
>
159-
<span
160-
dangerouslySetInnerHTML={ {
161-
__html: shortStatementDescriptorErrorMessage,
162-
} }
163-
/>
164-
</InlineNotice>
165-
) }
166-
<TextLengthHelpInputWrapper
167-
textLength={
168-
shortAccountStatementDescriptor.length
169-
}
170-
maxLength={ 10 }
144+
145+
<CheckboxControl
146+
checked={ isShortAccountStatementEnabled }
147+
onChange={ setIsShortAccountStatementEnabled }
148+
label={ __(
149+
'Add customer order number to the bank statement',
150+
'woocommerce-gateway-stripe'
151+
) }
152+
help={ __(
153+
"When enabled, we'll include the order number for card and express checkout transactions.",
154+
'woocommerce-gateway-stripe'
155+
) }
156+
/>
157+
{ isShortAccountStatementEnabled && (
158+
<>
159+
{ shortStatementDescriptorErrorMessage && (
160+
<InlineNotice
161+
status="error"
162+
isDismissible={ false }
171163
>
172-
<TextControl
173-
help={ __(
174-
"We'll use the short version in combination with the customer order number.",
175-
'woocommerce-gateway-stripe'
176-
) }
177-
label={ __(
178-
'Shortened customer bank statement',
179-
'woocommerce-gateway-stripe'
180-
) }
181-
value={ shortAccountStatementDescriptor }
182-
onChange={
183-
setShortAccountStatementDescriptor
184-
}
185-
maxLength={ 10 }
164+
<span
165+
dangerouslySetInnerHTML={ {
166+
__html: shortStatementDescriptorErrorMessage,
167+
} }
186168
/>
187-
</TextLengthHelpInputWrapper>
188-
</>
189-
) }
190-
</div>
191-
<StatementPreviewsWrapper>
192-
{ /* TODO: Hiding the Short Account Statement fields until it's included in the POST to Stripe */ }
193-
<div style={ { display: 'none' } }>
194-
{ isShortAccountStatementEnabled && (
195-
<StatementPreview
196-
icon="creditCard"
197-
title={ __(
198-
'Cards & Express Checkouts',
169+
</InlineNotice>
170+
) }
171+
<TextLengthHelpInputWrapper
172+
textLength={
173+
shortAccountStatementDescriptor.length
174+
}
175+
maxLength={ 10 }
176+
>
177+
<TextControl
178+
help={ __(
179+
"We'll use the short version in combination with the customer order number.",
180+
'woocommerce-gateway-stripe'
181+
) }
182+
label={ __(
183+
'Shortened customer bank statement',
199184
'woocommerce-gateway-stripe'
200185
) }
201-
text={ `${ shortAccountStatementDescriptor }* #123456` }
202-
className="shortened-bank-statement"
186+
value={ shortAccountStatementDescriptor }
187+
onChange={ setShortAccountStatementDescriptor }
188+
maxLength={ 10 }
203189
/>
204-
) }
205-
</div>
190+
</TextLengthHelpInputWrapper>
191+
</>
192+
) }
193+
<StatementPreviewsWrapper>
194+
{ isShortAccountStatementEnabled && (
195+
<StatementPreview
196+
icon="creditCard"
197+
title={ __(
198+
'Cards & Express Checkouts',
199+
'woocommerce-gateway-stripe'
200+
) }
201+
text={ `${ shortAccountStatementDescriptor }* #123456` }
202+
className="shortened-bank-statement"
203+
/>
204+
) }
206205
<StatementPreview
207206
icon="bank"
208207
title={ translatedFullBankPreviewTitle }
209-
text={ accountStatementDescriptor }
208+
text={
209+
accountStatementDescriptor ||
210+
statementDescriptorPlaceholder
211+
}
210212
className="full-bank-statement"
211213
/>
212214
</StatementPreviewsWrapper>

includes/abstracts/abstract-wc-stripe-payment-gateway.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,14 @@ public function get_stripe_return_url( $order = null, $id = null ) {
409409
* @return array()
410410
*/
411411
public function generate_payment_request( $order, $prepared_payment_method ) {
412-
$settings = get_option( 'woocommerce_stripe_settings', [] );
413-
$statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
414-
$capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
415-
$post_data = [];
416-
$post_data['currency'] = strtolower( $order->get_currency() );
417-
$post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
412+
$settings = get_option( 'woocommerce_stripe_settings', [] );
413+
$statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
414+
$short_statement_descriptor = ! empty( $settings['short_statement_descriptor'] ) ? str_replace( "'", '', $settings['short_statement_descriptor'] ) : '';
415+
$is_short_statement_descriptor_enabled = ! empty( $settings['is_short_statement_descriptor_enabled'] ) && 'yes' === $settings['is_short_statement_descriptor_enabled'];
416+
$capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
417+
$post_data = [];
418+
$post_data['currency'] = strtolower( $order->get_currency() );
419+
$post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
418420
/* translators: 1) blog name 2) order number */
419421
$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
420422
$billing_email = $order->get_billing_email();
@@ -427,7 +429,9 @@ public function generate_payment_request( $order, $prepared_payment_method ) {
427429

428430
switch ( $order->get_payment_method() ) {
429431
case 'stripe':
430-
if ( ! empty( $statement_descriptor ) ) {
432+
if ( $is_short_statement_descriptor_enabled && ! ( empty( $short_statement_descriptor ) && empty( $statement_descriptor ) ) ) {
433+
$post_data['statement_descriptor'] = WC_Stripe_Helper::get_dynamic_statement_descriptor( $short_statement_descriptor, $order, $statement_descriptor );
434+
} elseif ( ! empty( $statement_descriptor ) ) {
431435
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
432436
}
433437

@@ -437,7 +441,7 @@ public function generate_payment_request( $order, $prepared_payment_method ) {
437441
if ( ! empty( $statement_descriptor ) ) {
438442
$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
439443
}
440-
break;
444+
// other payment methods error if we try to add a statement descriptor in the request
441445
}
442446

443447
if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {

includes/class-wc-stripe-helper.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,14 +443,41 @@ public static function get_order_by_setup_intent_id( $intent_id ) {
443443
return false;
444444
}
445445

446+
/**
447+
* Sanitize and retrieve the shortened statement descriptor concatenated with the order number.
448+
*
449+
* @param string $statement_descriptor Shortened statement descriptor.
450+
* @param WC_Order $order Order.
451+
* @param string $fallback_descriptor (optional) Fallback of the shortened statement descriptor in case it's blank.
452+
* @return string $statement_descriptor Final shortened statement descriptor.
453+
*/
454+
public static function get_dynamic_statement_descriptor( $statement_descriptor = '', $order = null, $fallback_descriptor = '' ) {
455+
$actual_descriptor = ! empty( $statement_descriptor ) ? $statement_descriptor : $fallback_descriptor;
456+
$prefix = self::clean_statement_descriptor( $actual_descriptor );
457+
$suffix = '';
458+
459+
if ( empty( $prefix ) ) {
460+
return '';
461+
}
462+
463+
if ( method_exists( $order, 'get_order_number' ) && ! empty( $order->get_order_number() ) ) {
464+
$suffix = '* #' . $order->get_order_number();
465+
}
466+
467+
// Make sure it is limited at 22 characters.
468+
$statement_descriptor = substr( $prefix . $suffix, 0, 22 );
469+
470+
return $statement_descriptor;
471+
}
472+
446473
/**
447474
* Sanitize statement descriptor text.
448475
*
449476
* Stripe requires max of 22 characters and no special characters.
450477
*
451478
* @since 4.0.0
452-
* @param string $statement_descriptor
453-
* @return string $statement_descriptor Sanitized statement descriptor
479+
* @param string $statement_descriptor Statement descriptor.
480+
* @return string $statement_descriptor Sanitized statement descriptor.
454481
*/
455482
public static function clean_statement_descriptor( $statement_descriptor = '' ) {
456483
$disallowed_characters = [ '<', '>', '\\', '*', '"', "'", '/', '(', ')', '{', '}' ];

includes/payment-methods/class-wc-stripe-upe-payment-gateway.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,17 +495,29 @@ public function process_payment( $order_id, $retry = true, $force_save_source =
495495
$save_payment_method = $this->has_subscription( $order_id ) || ! empty( $_POST[ 'wc-' . self::ID . '-new-payment-method' ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
496496
$selected_upe_payment_type = ! empty( $_POST['wc_stripe_selected_upe_payment_type'] ) ? wc_clean( wp_unslash( $_POST['wc_stripe_selected_upe_payment_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
497497

498+
$statement_descriptor = ! empty( $this->get_option( 'statement_descriptor' ) ) ? str_replace( "'", '', $this->get_option( 'statement_descriptor' ) ) : '';
499+
$short_statement_descriptor = ! empty( $this->get_option( 'short_statement_descriptor' ) ) ? str_replace( "'", '', $this->get_option( 'short_statement_descriptor' ) ) : '';
500+
$is_short_statement_descriptor_enabled = ! empty( $this->get_option( 'is_short_statement_descriptor_enabled' ) ) && 'yes' === $this->get_option( 'is_short_statement_descriptor_enabled' );
501+
$descriptor = null;
502+
if ( 'card' === $selected_upe_payment_type && $is_short_statement_descriptor_enabled && ! ( empty( $short_statement_descriptor ) && empty( $statement_descriptor ) ) ) {
503+
// Use the shortened statement descriptor for card transactions only
504+
$descriptor = WC_Stripe_Helper::get_dynamic_statement_descriptor( $short_statement_descriptor, $order, $statement_descriptor );
505+
} elseif ( ! empty( $statement_descriptor ) ) {
506+
$descriptor = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
507+
}
508+
498509
if ( $payment_intent_id ) {
499510
if ( $payment_needed ) {
500511
$amount = $order->get_total();
501512
$currency = $order->get_currency();
502513
$converted_amount = WC_Stripe_Helper::get_stripe_amount( $amount, $currency );
503514

504515
$request = [
505-
'amount' => $converted_amount,
506-
'currency' => $currency,
516+
'amount' => $converted_amount,
517+
'currency' => $currency,
518+
'statement_descriptor' => $descriptor,
507519
/* translators: 1) blog name 2) order number */
508-
'description' => sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() ),
520+
'description' => sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() ),
509521
];
510522

511523
// Get user/customer for order.

tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private function get_order_details( $order ) {
145145
$order_key = $order->get_order_key();
146146
$amount = WC_Stripe_Helper::get_stripe_amount( $total, $currency );
147147
$description = "Test Blog - Order $order_number";
148-
$metadata = [
148+
$metadata = [
149149
'customer_name' => 'Jeroen Sormani',
150150
'customer_email' => '[email protected]',
151151
'site_url' => 'http://example.org',
@@ -185,11 +185,12 @@ public function test_process_payment_returns_valid_response() {
185185
list( $amount, $description, $metadata ) = $this->get_order_details( $order );
186186

187187
$expected_request = [
188-
'amount' => $amount,
189-
'currency' => $currency,
190-
'description' => $description,
191-
'customer' => $customer_id,
192-
'metadata' => $metadata,
188+
'amount' => $amount,
189+
'currency' => $currency,
190+
'description' => $description,
191+
'customer' => $customer_id,
192+
'metadata' => $metadata,
193+
'statement_descriptor' => null,
193194
];
194195

195196
$_POST = [ 'wc_payment_intent_id' => $payment_intent_id ];
@@ -312,7 +313,7 @@ public function test_process_redirect_payment_only_runs_once() {
312313

313314
$success_order = wc_get_order( $order_id );
314315

315-
$note = wc_get_order_notes(
316+
$note = wc_get_order_notes(
316317
[
317318
'order_id' => $order_id,
318319
'limit' => 2,
@@ -983,12 +984,13 @@ public function test_if_order_has_subscription_payment_method_will_be_saved() {
983984
list( $amount, $description, $metadata ) = $this->get_order_details( $order );
984985

985986
$expected_request = [
986-
'amount' => $amount,
987-
'currency' => $currency,
988-
'description' => $description,
989-
'customer' => $customer_id,
990-
'metadata' => $metadata,
991-
'setup_future_usage' => 'off_session',
987+
'amount' => $amount,
988+
'currency' => $currency,
989+
'description' => $description,
990+
'customer' => $customer_id,
991+
'metadata' => $metadata,
992+
'setup_future_usage' => 'off_session',
993+
'statement_descriptor' => null,
992994
];
993995

994996
$_POST = [ 'wc_payment_intent_id' => $payment_intent_id ];

0 commit comments

Comments
 (0)