Skip to content

Commit

Permalink
Merge branch 'develop' into dev/multiple-code-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
wjrosa committed Sep 20, 2024
2 parents 925cf38 + a592a76 commit a692d73
Show file tree
Hide file tree
Showing 27 changed files with 2,532 additions and 162 deletions.
14 changes: 10 additions & 4 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
*** Changelog ***

= 8.8.0 - xxxx-xx-xx =
* Tweak - Update the Apple Pay domain registration flow to use the new Stripe API endpoint.
* Fix - Resolve an error for checkout block where 'wc_stripe_upe_params' is undefined due to the script registering the variable not being loaded yet.

= 8.7.0 - xxxx-xx-xx =
* Fix - Fix mandate creation for subscriptions and saved payment methods.
* Fix - Fix Google Pay address fields mapping for UAE addresses.
* Tweak - Render the Klarna payment page in the store locale.
* Tweak - Update the Apple Pay domain registration flow to use the new Stripe API endpoint.
* Fix - Fix empty error message for Express Payments when order creation fails.
* Fix - Fix multiple issues related to the reuse of Cash App Pay tokens (as a saved payment method) when subscribing.

= 8.7.0 - 2024-09-16 =
* Add - Introduces a new promotional surface to encourage merchants with the legacy checkout experience and APMs enabled to use the new checkout experience.
* Fix - Prevent duplicate failed-order emails from being sent.
* Fix - Support custom name and description for Afterpay.
* Fix - Link APM charge IDs in Order Details page to their Stripe dashboard payments page.
Expand Down Expand Up @@ -36,6 +40,8 @@
* Fix - Prevent duplicate order notes and emails being sent when purchasing subscription products with no initial payment.
* Add - Display an admin notice on the WooCommerce > Subscriptions screen for tracking the progress of SEPA subscriptions migrations after the legacy checkout is disabled.
* Add - Introduce a new tool on the WooCommerce > Status > Tools screen to restart the legacy SEPA subscriptions update.
* Fix - Remove the Stripe OAuth Keys when uninstalling the plugin.
* Fix - Resolve an error for checkout block where 'wc_stripe_upe_params' is undefined due to the script registering the variable not being loaded yet.
* Fix - Update Cash App payments to avoid confirming on creation, resolving issues with generic payment failures in live mode.

= 8.6.1 - 2024-08-09 =
Expand Down
6 changes: 3 additions & 3 deletions client/blocks/express-checkout/express-checkout.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global wc_stripe_payment_request_params */
/* global wc_stripe_express_checkout_params */

import React from 'react';
import { Elements, ExpressCheckoutElement } from '@stripe/react-stripe-js';
Expand All @@ -13,8 +13,8 @@ export const ExpressCheckout = ( props ) => {

const buttonOptions = {
buttonType: {
googlePay: wc_stripe_payment_request_params.button.type,
applePay: wc_stripe_payment_request_params.button.type,
googlePay: wc_stripe_express_checkout_params.button.type,
applePay: wc_stripe_express_checkout_params.button.type,
},
};

Expand Down
6 changes: 4 additions & 2 deletions client/classic/upe/deferred-intent.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ jQuery( function ( $ ) {
function maybeConfirmVoucherOrWalletPayment() {
if (
getStripeServerData()?.isOrderPay ||
getStripeServerData()?.isCheckout
getStripeServerData()?.isCheckout ||
getStripeServerData()?.isChangingPayment
) {
if ( window.location.hash.startsWith( '#wc-stripe-voucher-' ) ) {
confirmVoucherPayment(
Expand All @@ -184,7 +185,8 @@ jQuery( function ( $ ) {
) {
confirmWalletPayment(
api,
getStripeServerData()?.isOrderPay
getStripeServerData()?.isOrderPay ||
getStripeServerData()?.isChangingPayment
? $( '#order_review' )
: $( 'form.checkout' )
);
Expand Down
40 changes: 26 additions & 14 deletions client/classic/upe/payment-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export const confirmVoucherPayment = async ( api, jQueryForm ) => {
*
* When processing a payment for a wallet payment method on the checkout or order pay page,
* the process_payment_with_deferred_intent() function redirects the customer to a URL
* formatted with: #wc-stripe-wallet-<order_id>:<payment_method_type>:<client_secret>:<redirect_url>.
* formatted with: #wc-stripe-wallet-<order_id>:<payment_method_type>:<payment_intent_type>:<client_secret>:<redirect_url>.
*
* This function, which is hooked onto the hashchanged event, checks if the URL contains the data we need to process the wallet payment.
*
Expand All @@ -410,7 +410,7 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
}

const partials = window.location.href.match(
/#wc-stripe-wallet-(.+):(.+):(.+):(.+)$/
/#wc-stripe-wallet-(.+):(.+):(.+):(.+):(.+)$/
);

if ( ! partials ) {
Expand All @@ -426,7 +426,7 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
);

const orderId = partials[ 1 ];
const clientSecret = partials[ 3 ];
const clientSecret = partials[ 4 ];

// Verify the request using the data added to the URL.
if (
Expand All @@ -438,7 +438,8 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
}

const paymentMethodType = partials[ 2 ];
const returnURL = decodeURIComponent( partials[ 4 ] );
const intentType = partials[ 3 ];
const returnURL = decodeURIComponent( partials[ 5 ] );

try {
// Confirm the payment to tell Stripe to display the modal to the customer.
Expand All @@ -456,11 +457,19 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
} );
break;
case 'cashapp':
confirmPayment = await api
.getStripe()
.confirmCashappPayment( clientSecret, {
return_url: returnURL,
} );
if ( intentType === 'setup_intent' ) {
confirmPayment = await api
.getStripe()
.confirmCashappSetup( clientSecret, {
return_url: returnURL,
} );
} else {
confirmPayment = await api
.getStripe()
.confirmCashappPayment( clientSecret, {
return_url: returnURL,
} );
}
break;
default:
// eslint-disable-next-line no-console
Expand All @@ -472,15 +481,18 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
throw confirmPayment.error;
}

if ( confirmPayment.paymentIntent.last_payment_error ) {
throw new Error(
confirmPayment.paymentIntent.last_payment_error.message
);
const intentObject =
intentType === 'setup_intent'
? confirmPayment.setupIntent
: confirmPayment.paymentIntent;

if ( intentObject.last_payment_error ) {
throw new Error( intentObject.last_payment_error.message );
}

// Do not redirect to the order received page if the modal is closed without payment.
// Otherwise redirect to the order received page.
if ( confirmPayment.paymentIntent.status !== 'requires_action' ) {
if ( intentObject.status !== 'requires_action' ) {
window.location.href = returnURL;
}
} catch ( error ) {
Expand Down
1 change: 1 addition & 0 deletions client/entrypoints/express-checkout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// express checkout element integration for shortcode goes here.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import React from 'react';
import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import PromotionalBannerSection from '../promotional-banner-section';
import { useEnabledPaymentMethodIds } from 'wcstripe/data';

jest.mock( '@wordpress/data' );

jest.mock( 'wcstripe/data/account', () => ( {
useAccount: jest.fn(),
} ) );

jest.mock( 'wcstripe/data', () => ( {
useEnabledPaymentMethodIds: jest.fn().mockReturnValue( [ [ 'card' ] ] ),
useTestMode: jest.fn().mockReturnValue( [ false ] ),
} ) );

const noticesDispatch = {
createErrorNotice: jest.fn(),
createSuccessNotice: jest.fn(),
Expand Down Expand Up @@ -68,4 +74,19 @@ describe( 'PromotionalBanner', () => {
screen.queryByTestId( 're-connect-account-banner' )
).toBeInTheDocument();
} );

it( 'Display the APM version of the new checkout experience promotional surface when any APM is enabled', () => {
useEnabledPaymentMethodIds.mockReturnValue( [ [ 'card', 'ideal' ] ] );

render(
<PromotionalBannerSection
setShowPromotionalBanner={ setShowPromotionalBanner }
isConnectedViaOAuth={ true }
/>
);

expect(
screen.queryByTestId( 'new-checkout-apms-banner' )
).toBeInTheDocument();
} );
} );
73 changes: 70 additions & 3 deletions client/settings/payment-settings/promotional-banner-section.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data';
import { React } from 'react';
import { Card, Button } from '@wordpress/components';
import { Card, Button, ExternalLink } from '@wordpress/components';
import styled from '@emotion/styled';
import interpolateComponents from 'interpolate-components';
import CardBody from '../card-body';
import bannerIllustration from './banner-illustration.svg';
import bannerIllustrationReConnect from './banner-illustration-re-connect.svg';
import Pill from 'wcstripe/components/pill';
import { recordEvent } from 'wcstripe/tracking';
import { useTestMode } from 'wcstripe/data';
import { useEnabledPaymentMethodIds, useTestMode } from 'wcstripe/data';

const NewPill = styled( Pill )`
border-color: #674399;
Expand Down Expand Up @@ -66,6 +67,9 @@ const PromotionalBannerSection = ( {
'core/notices'
);
const [ isTestModeEnabled ] = useTestMode();
const [ enabledPaymentMethodIds ] = useEnabledPaymentMethodIds();
const hasAPMEnabled =
enabledPaymentMethodIds.filter( ( e ) => e !== 'card' ).length > 0;

const handleButtonClick = () => {
const callback = async () => {
Expand Down Expand Up @@ -159,6 +163,65 @@ const PromotionalBannerSection = ( {
</CardBody>
);

const NewCheckoutExperienceAPMsBanner = () => (
<CardBody data-testid="new-checkout-apms-banner">
<CardInner>
<CardColumn>
<NewPill>
{ __( 'New', 'woocommerce-gateway-stripe' ) }
</NewPill>
<h4>
{ __(
'Enable the new Stripe checkout to continue accepting non-card payments',
'woocommerce-gateway-stripe'
) }
</h4>
<p>
{ interpolateComponents( {
mixedString: __(
'Stripe will end support for non-card payment methods in the {{StripeLegacyLink}}legacy checkout on October 29, 2024{{/StripeLegacyLink}}. To continue accepting non-card payments, you must enable the new checkout experience or remove non-card payment methods from your checkout to avoid payment disruptions.',
'woocommerce-gateway-stripe'
),
components: {
StripeLegacyLink: (
<ExternalLink href="https://support.stripe.com/topics/shutdown-of-the-legacy-sources-api-for-non-card-payment-methods" />
),
},
} ) }
</p>
</CardColumn>
<CardColumn>
<BannerIllustration
src={ bannerIllustration }
alt={ __(
'New Checkout',
'woocommerce-gateway-stripe'
) }
/>
</CardColumn>
</CardInner>
<ButtonsRow>
<MainCTALink
variant="secondary"
data-testid="disable-the-legacy-checkout"
onClick={ handleButtonClick }
>
{ __(
'Enable the new checkout',
'woocommerce-gateway-stripe'
) }
</MainCTALink>
<DismissButton
variant="secondary"
onClick={ handleBannerDismiss }
data-testid="dismiss"
>
{ __( 'Dismiss', 'woocommerce-gateway-stripe' ) }
</DismissButton>
</ButtonsRow>
</CardBody>
);

const NewCheckoutExperienceBanner = () => (
<CardBody>
<CardInner>
Expand Down Expand Up @@ -215,7 +278,11 @@ const PromotionalBannerSection = ( {
if ( isConnectedViaOAuth === false ) {
BannerContent = <ReConnectAccountBanner />;
} else if ( ! isUpeEnabled ) {
BannerContent = <NewCheckoutExperienceBanner />;
if ( hasAPMEnabled ) {
BannerContent = <NewCheckoutExperienceAPMsBanner />;
} else {
BannerContent = <NewCheckoutExperienceBanner />;
}
}

return (
Expand Down
59 changes: 59 additions & 0 deletions includes/class-wc-stripe-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1533,4 +1533,63 @@ public static function get_transaction_url( $is_test_mode = false ) {

return 'https://dashboard.stripe.com/payments/%s';
}

/**
* Returns a supported locale for setting Klarna's "preferred_locale".
* While Stripe allows for localization of Klarna's payments page, it still
* limits the locale to the billing country's set of supported locales. For example,
* we cannot set the locale to "fr-FR" or "fr-US" if the billing country is "US".
*
* We compute our desired locale by combining the language tag from the store locale
* and the billing country. We return that if it is supported.
*
* @param string $store_locale The WooCommerce store locale.
* Expected format: WordPress locale format, e.g. "en" or "en_US".
* @param string $billing_country The billing country code.
* @return string|null The Klarna locale or null if not supported.
*/
public static function get_klarna_preferred_locale( $store_locale, $billing_country ) {
// From https://docs.stripe.com/payments/klarna/accept-a-payment?payments-ui-type=direct-api#supported-locales-and-currencies
$supported_locales = [
'AU' => [ 'en-AU' ],
'AT' => [ 'de-AT', 'en-AT' ],
'BE' => [ 'nl-BE', 'fr-BE', 'en-BE' ],
'CA' => [ 'en-CA', 'fr-CA' ],
'CZ' => [ 'en-CZ', 'cs-CZ' ],
'DK' => [ 'da-DK', 'en-DK' ],
'FI' => [ 'fi-FI', 'sv-FI', 'en-FI' ],
'FR' => [ 'fr-FR', 'en-FR' ],
'DE' => [ 'de-DE', 'en-DE' ],
'GR' => [ 'en-GR', 'el-GR' ],
'IE' => [ 'en-IE' ],
'IT' => [ 'it-IT', 'en-IT' ],
'NL' => [ 'nl-NL', 'en-NL' ],
'NZ' => [ 'en-NZ' ],
'NO' => [ 'nb-NO', 'en-NO' ],
'PL' => [ 'pl-PL', 'en-PL' ],
'PT' => [ 'pt-PT', 'en-PT' ],
'RO' => [ 'ro-RO', 'en-RO' ],
'ES' => [ 'es-ES', 'en-ES' ],
'SE' => [ 'sv-SE', 'en-SE' ],
'CH' => [ 'de-CH', 'fr-CH', 'it-CH', 'en-CH' ],
'GB' => [ 'en-GB' ],
'US' => [ 'en-US', 'es-US' ],
];

$region = strtoupper( $billing_country );
if ( ! isset( $supported_locales[ $region ] ) ) {
return null;
}

// Get the language tag e.g. "en" for "en_US".
$lang = strtolower( explode( '_', $store_locale )[0] );
$target_locale = $lang . '-' . $region;

// Check if the target locale is supported.
if ( ! in_array( $target_locale, $supported_locales[ $region ], true ) ) {
return null;
}

return $target_locale;
}
}
6 changes: 3 additions & 3 deletions includes/class-wc-stripe-intent-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ private function build_base_payment_intent_request_params( $payment_information
$request['return_url'] = $payment_information['return_url'];
}

if ( $payment_information['save_payment_method_to_store'] ) {
if ( $payment_information['save_payment_method_to_store'] || ! empty( $payment_information['has_subscription'] ) ) {
$request['setup_future_usage'] = 'off_session';
}

Expand Down Expand Up @@ -960,7 +960,7 @@ public function is_mandate_data_required( $selected_payment_type, $is_using_save
*
* @throws WC_Stripe_Exception If the create intent call returns with an error.
*
* @return array
* @return stdClass
*/
public function create_and_confirm_setup_intent( $payment_information ) {
$request = [
Expand All @@ -980,7 +980,7 @@ public function create_and_confirm_setup_intent( $payment_information ) {
$request = $this->add_mandate_data( $request );
}

// For voucher payment methods type like Boleto, Oxxo & Multibanco, we shouldn't confirm the intent immediately as this is done on the front-end when displaying the voucher to the customer.
// For voucher payment methods type like Boleto, Oxxo, Multibanco, and Cash App, we shouldn't confirm the intent immediately as this is done on the front-end when displaying the voucher to the customer.
// When the intent is confirmed, Stripe sends a webhook to the store which puts the order on-hold, which we only want to happen after successfully displaying the voucher.
if ( $this->is_delayed_confirmation_required( $request['payment_method_types'] ) ) {
$request['confirm'] = 'false';
Expand Down
Loading

0 comments on commit a692d73

Please sign in to comment.