diff --git a/changelog/fix-tokenized-cart-subscription-signup-fee b/changelog/fix-tokenized-cart-subscription-signup-fee
new file mode 100644
index 00000000000..5abe9f0226b
--- /dev/null
+++ b/changelog/fix-tokenized-cart-subscription-signup-fee
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: fix: tokenized cart subscription signup fee price
+
+
diff --git a/changelog/load-checkout-scripts-on-checkout-if-not-loaded b/changelog/load-checkout-scripts-on-checkout-if-not-loaded
new file mode 100644
index 00000000000..4a684203a2e
--- /dev/null
+++ b/changelog/load-checkout-scripts-on-checkout-if-not-loaded
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Load checkout scripts when they are not previously loaded on checkout 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.'
+ );
+ } );
} );
diff --git a/client/tokenized-express-checkout/transformers/wc-to-stripe.js b/client/tokenized-express-checkout/transformers/wc-to-stripe.js
index 7c4c9c14822..6d9b39035ce 100644
--- a/client/tokenized-express-checkout/transformers/wc-to-stripe.js
+++ b/client/tokenized-express-checkout/transformers/wc-to-stripe.js
@@ -40,8 +40,8 @@ export const transformPrice = ( price, priceObject ) => {
export const transformCartDataForDisplayItems = ( cartData ) => {
const displayItems = cartData.items.map( ( item ) => ( {
amount: transformPrice(
- parseInt( item.prices.price, 10 ),
- item.prices
+ parseInt( item.totals?.line_subtotal || item.prices.price, 10 ),
+ item.totals || item.prices
),
name: [
item.name,
diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php
index ee7a161f3b1..44d92d10b23 100644
--- a/includes/class-wc-payments-checkout.php
+++ b/includes/class-wc-payments-checkout.php
@@ -103,6 +103,7 @@ public function init_hooks() {
add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts_for_zero_order_total' ], 11 );
+ add_action( 'woocommerce_after_checkout_form', [ $this, 'maybe_load_checkout_scripts' ] );
}
/**
@@ -151,11 +152,18 @@ public function register_scripts_for_zero_order_total() {
! has_block( 'woocommerce/checkout' ) &&
! wp_script_is( 'wcpay-upe-checkout', 'enqueued' )
) {
- WC_Payments::get_gateway()->tokenization_script();
- $script_handle = 'wcpay-upe-checkout';
- $js_object = 'wcpay_upe_config';
- wp_localize_script( $script_handle, $js_object, WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config() );
- wp_enqueue_script( $script_handle );
+ $this->load_checkout_scripts();
+ }
+ }
+
+ /**
+ * Sometimes the filters can remove the payment gateway from the checkout page which results in the payment fields not being displayed.
+ * This could prevent loading of the payment fields (checkout) scripts.
+ * This function ensures that these scripts are loaded.
+ */
+ public function maybe_load_checkout_scripts() {
+ if ( is_checkout() && ! wp_script_is( 'wcpay-upe-checkout', 'enqueued' ) ) {
+ $this->load_checkout_scripts();
}
}
@@ -416,7 +424,7 @@ function () use ( $prepared_customer_data ) {
}
?>
-
gateway = $this->gateway->wc_payments_get_payment_gateway_by_id( $payment_method_id );
}
}
+
+ /**
+ * Load the checkout scripts.
+ */
+ private function load_checkout_scripts() {
+ WC_Payments::get_gateway()->tokenization_script();
+ $script_handle = 'wcpay-upe-checkout';
+ $js_object = 'wcpay_upe_config';
+ wp_localize_script( $script_handle, $js_object, WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config() );
+ wp_enqueue_script( $script_handle );
+ }
}