From 5eaa046fa289b83943f5b595074cd1fc0da417a6 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Wed, 18 Dec 2024 16:31:39 +0100 Subject: [PATCH] Add failure reason to failed timeline events (#9980) --- ...lude-more-info-on-transaction-details-page | 4 ++ client/payment-details/timeline/map-events.js | 19 +++++-- client/payment-details/timeline/mappings.ts | 43 +++++++++++++++ .../timeline/test/__snapshots__/index.js.snap | 2 +- .../test/__snapshots__/map-events.js.snap | 2 +- .../timeline/test/map-events.js | 55 +++++++++++++++++++ 6 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page diff --git a/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page b/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page new file mode 100644 index 00000000000..daf90a1cd39 --- /dev/null +++ b/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add failure reason to failed payments in the timeline. diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js index da4e275105c..b76f659ac63 100644 --- a/client/payment-details/timeline/map-events.js +++ b/client/payment-details/timeline/map-events.js @@ -30,7 +30,7 @@ import { import { formatFee } from 'utils/fees'; import { getAdminUrl } from 'wcpay/utils'; import { ShieldIcon } from 'wcpay/icons'; -import { fraudOutcomeRulesetMapping } from './mappings'; +import { fraudOutcomeRulesetMapping, paymentFailureMapping } from './mappings'; /** * Creates a timeline item about a payment status change @@ -772,6 +772,10 @@ const mapEventToTimelineItems = ( event ) => { ), ]; case 'failed': + const paymentFailureMessage = + paymentFailureMapping[ event.reason ] || + paymentFailureMapping.default; + return [ getStatusChangeTimelineItem( event, @@ -779,11 +783,14 @@ const mapEventToTimelineItems = ( event ) => { ), getMainTimelineItem( event, - stringWithAmount( - /* translators: %s is a monetary amount */ - __( 'A payment of %s failed.', 'woocommerce-payments' ), - event.amount, - true + sprintf( + /* translators: %1$s is the payment amount, %2$s is the failure reason message */ + __( + 'A payment of %1$s failed: %2$s.', + 'woocommerce-payments' + ), + formatExplicitCurrency( event.amount, event.currency ), + paymentFailureMessage ), ), diff --git a/client/payment-details/timeline/mappings.ts b/client/payment-details/timeline/mappings.ts index 80d2aceaa98..affcb62063a 100644 --- a/client/payment-details/timeline/mappings.ts +++ b/client/payment-details/timeline/mappings.ts @@ -65,3 +65,46 @@ export const fraudOutcomeRulesetMapping = { ), }, }; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const paymentFailureMapping = { + card_declined: __( + 'The card was declined by the bank', + 'woocommerce-payments' + ), + expired_card: __( 'The card has expired', 'woocommerce-payments' ), + incorrect_cvc: __( + 'The security code is incorrect', + 'woocommerce-payments' + ), + incorrect_number: __( + 'The card number is incorrect', + 'woocommerce-payments' + ), + incorrect_zip: __( 'The postal code is incorrect', 'woocommerce-payments' ), + invalid_cvc: __( 'The security code is invalid', 'woocommerce-payments' ), + invalid_expiry_month: __( + 'The expiration month is invalid', + 'woocommerce-payments' + ), + invalid_expiry_year: __( + 'The expiration year is invalid', + 'woocommerce-payments' + ), + invalid_number: __( 'The card number is invalid', 'woocommerce-payments' ), + processing_error: __( + 'An error occurred while processing the card', + 'woocommerce-payments' + ), + authentication_required: __( + 'The payment requires authentication', + 'woocommerce-payments' + ), + insufficient_funds: __( + 'The card has insufficient funds to complete the purchase', + 'woocommerce-payments' + ), + + // Default fallback + default: __( 'The payment was declined', 'woocommerce-payments' ), +}; diff --git a/client/payment-details/timeline/test/__snapshots__/index.js.snap b/client/payment-details/timeline/test/__snapshots__/index.js.snap index 95f1e8a5c9f..c7915e6aee9 100644 --- a/client/payment-details/timeline/test/__snapshots__/index.js.snap +++ b/client/payment-details/timeline/test/__snapshots__/index.js.snap @@ -933,7 +933,7 @@ exports[`PaymentDetailsTimeline renders correctly (with a mocked Timeline compon - A payment of $77.00 failed. + A payment of $77.00 failed: The card was declined by the bank. , diff --git a/client/payment-details/timeline/test/map-events.js b/client/payment-details/timeline/test/map-events.js index c3e42ceae8b..f1c0588d659 100644 --- a/client/payment-details/timeline/test/map-events.js +++ b/client/payment-details/timeline/test/map-events.js @@ -662,4 +662,59 @@ describe( 'mapTimelineEvents', () => { ).toMatchSnapshot(); } ); } ); + + test( 'formats payment failure events with different error codes', () => { + const testCases = [ + { + reason: 'insufficient_funds', + expectedMessage: + 'A payment of $77.00 USD failed: The card has insufficient funds to complete the purchase.', + }, + { + reason: 'expired_card', + expectedMessage: + 'A payment of $77.00 USD failed: The card has expired.', + }, + { + reason: 'invalid_cvc', + expectedMessage: + 'A payment of $77.00 USD failed: The security code is invalid.', + }, + { + reason: 'unknown_reason', + expectedMessage: + 'A payment of $77.00 USD failed: The payment was declined.', + }, + ]; + + testCases.forEach( ( { reason, expectedMessage } ) => { + const events = mapTimelineEvents( [ + { + amount: 7700, + currency: 'USD', + datetime: 1585712113, + reason, + type: 'failed', + }, + ] ); + + expect( events[ 1 ].headline ).toBe( expectedMessage ); + } ); + } ); + + test( 'formats payment failure events with different currencies', () => { + const events = mapTimelineEvents( [ + { + amount: 7700, + currency: 'EUR', + datetime: 1585712113, + reason: 'card_declined', + type: 'failed', + }, + ] ); + + expect( events[ 1 ].headline ).toBe( + 'A payment of €77.00 EUR failed: The card was declined by the bank.' + ); + } ); } );