Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Ensure shopper saved card is used as default payment method (default was being overwritten in some circumstances) #3131

Merged
merged 3 commits into from Sep 10, 2020

Conversation

haszari
Copy link
Member

@haszari haszari commented Sep 9, 2020

Fixes #3120

The issue here is that when a user has saved payment method(s) - e.g. a Stripe saved credit card (token), the logic that sets an initial default payment method can overwrite the saved payment method.

This PR fixes this by adding a guard to the default / init effect. If there's a saved payment method, it now exits early; previously was overwriting the saved payment method, causing #3120.

Update: this PR now uses the functional form of setState to apply the default payment method. This ensures that the default is only set if there is no payment method active (selected); previously this decision was made using a stale copy of state. Hat tip @Aljullu for the suggestion 🎉

How to test the changes in this Pull Request:

  • Set up checkout page using block.
  • Enable BACS and Stripe payment methods (only, though may reproduce with other combinations). Note BACS needs to be earlier in the order than Stripe (drag handle in WooCommerce > Settings > Payments).
  • Complete a purchase using a new payment method, Stripe CC, check Save payment information to my account for future purchases.
  • Add something to cart, proceed to checkout.
  • Leave the default selected payment method - i.e. the first saved payment method. Submit checkout.

Should always complete the purchase with the correct payment method - the one visibly selected when user clicks submit, i.e. the saved card.

Please also test a variety of other payment method scenarios and ordering - with / without saved cards (are there any other gateways that support saved payment methods?), with more/less gateways available, in different orders, and with dynamically-available gateways (COD can depend on shipping option). In all cases confirm the following:

  • There is a payment method tab selected by default.
  • Submitting checkout without touching payment section uses the correct payment method (i.e. saved card, payment method tab).
  • Selecting a non-default saved card (from the default) works correctly, uses correct card/token (may need to check network requests to check token).
  • Selecting a non-default payment method uses correct method.
  • Saving a payment method (card) works and is available for use next checkout with that account.
  • Payment tab order should match the configured order.

Changelog

Fix: Ensure that the checkout correctly uses shopper's previously-saved payment method when selected by default (e.g. Stripe saved card). In some circumstances, a different payment method was used (e.g. BACS).

@haszari haszari requested a review from a team as a code owner September 9, 2020 04:41
@haszari haszari requested review from senadir and Aljullu and removed request for a team September 9, 2020 04:41
@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2020

Size Change: +16 B (0%)

Total Size: 1.11 MB

Filename Size Change
build/cart-frontend.js 69.2 kB +4 B (0%)
build/cart.js 37.6 kB +4 B (0%)
build/checkout-frontend.js 84.7 kB +4 B (0%)
build/checkout.js 41.2 kB +4 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/active-filters-frontend.js 8.8 kB 0 B
build/active-filters.js 8.85 kB 0 B
build/all-products-frontend.js 31.2 kB 0 B
build/all-products.js 35.7 kB 0 B
build/all-reviews.js 9.79 kB 0 B
build/atomic-block-components/add-to-cart-frontend.js 8.89 kB 0 B
build/atomic-block-components/add-to-cart.js 7.46 kB 0 B
build/atomic-block-components/add-to-cart~atomic-block-components/button.js 3.16 kB 0 B
build/atomic-block-components/add-to-cart~atomic-block-components/image~atomic-block-components/title.js 334 B 0 B
build/atomic-block-components/button-frontend.js 2.02 kB 0 B
build/atomic-block-components/button.js 839 B 0 B
build/atomic-block-components/category-list-frontend.js 470 B 0 B
build/atomic-block-components/category-list.js 475 B 0 B
build/atomic-block-components/image-frontend.js 1.71 kB 0 B
build/atomic-block-components/image.js 1.15 kB 0 B
build/atomic-block-components/price-frontend.js 2.1 kB 0 B
build/atomic-block-components/price.js 2.13 kB 0 B
build/atomic-block-components/rating-frontend.js 524 B 0 B
build/atomic-block-components/rating.js 526 B 0 B
build/atomic-block-components/sale-badge-frontend.js 862 B 0 B
build/atomic-block-components/sale-badge.js 866 B 0 B
build/atomic-block-components/sku-frontend.js 388 B 0 B
build/atomic-block-components/sku.js 394 B 0 B
build/atomic-block-components/stock-indicator-frontend.js 569 B 0 B
build/atomic-block-components/stock-indicator.js 572 B 0 B
build/atomic-block-components/summary-frontend.js 918 B 0 B
build/atomic-block-components/summary.js 926 B 0 B
build/atomic-block-components/tag-list-frontend.js 467 B 0 B
build/atomic-block-components/tag-list.js 473 B 0 B
build/atomic-block-components/title-frontend.js 1.23 kB 0 B
build/atomic-block-components/title.js 1.06 kB 0 B
build/attribute-filter-frontend.js 18.1 kB 0 B
build/attribute-filter.js 12.4 kB 0 B
build/blocks.js 3.54 kB 0 B
build/editor-rtl.css 13.9 kB 0 B
build/editor.css 13.9 kB 0 B
build/featured-category.js 7.72 kB 0 B
build/featured-product.js 9.97 kB 0 B
build/handpicked-products.js 7.37 kB 0 B
build/price-filter-frontend.js 14.5 kB 0 B
build/price-filter.js 10.3 kB 0 B
build/product-best-sellers.js 7.44 kB 0 B
build/product-categories.js 3.23 kB 0 B
build/product-category.js 8.38 kB 0 B
build/product-new.js 7.6 kB 0 B
build/product-on-sale.js 7.99 kB 0 B
build/product-search.js 3.56 kB 0 B
build/product-tag.js 6.52 kB 0 B
build/product-top-rated.js 7.58 kB 0 B
build/products-by-attribute.js 8.32 kB 0 B
build/reviews-by-category.js 11.8 kB 0 B
build/reviews-by-product.js 13.4 kB 0 B
build/reviews-frontend.js 9.36 kB 0 B
build/single-product-frontend.js 34 kB 0 B
build/single-product.js 10.1 kB 0 B
build/style-rtl.css 17.9 kB 0 B
build/style.css 17.9 kB 0 B
build/vendors-style-rtl.css 1.03 kB 0 B
build/vendors-style.css 1.03 kB 0 B
build/vendors.js 415 kB 0 B
build/vendors~atomic-block-components/price-frontend.js 5.65 kB 0 B
build/wc-blocks-data.js 7.09 kB 0 B
build/wc-blocks-middleware.js 931 B 0 B
build/wc-blocks-registry.js 2.28 kB 0 B
build/wc-payment-method-bacs.js 790 B 0 B
build/wc-payment-method-cheque.js 787 B 0 B
build/wc-payment-method-cod.js 875 B 0 B
build/wc-payment-method-paypal.js 831 B 0 B
build/wc-payment-method-stripe.js 11.9 kB 0 B
build/wc-settings.js 2.33 kB 0 B
build/wc-shared-context.js 1.53 kB 0 B
build/wc-shared-hocs.js 1.66 kB 0 B

compressed-size-action

Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating this issue and creating a fix. If I understand what's going on is that in SavedPaymentMethodOptions we are setting the saved payment method as the active payment option, but that was overridden in the PaymentMethodDataProvider useEffect. With this PR, we bail out soon if there are saved customer payment methods so the issue is fixed, is that correct?

Below I suggested an alternative approach that I think would work as well and is more generic because it doesn't handle the customer payment methods case specially. But I might have missed some special cases, so feel free to ignore if you don't think it will work.

Please also test a variety of other payment method scenarios and ordering - with / without saved cards (are there any other gateways that support saved payment methods?), with more/less gateways available, in different orders, and with dynamically-available gateways (COD can depend on shipping option).

I did a variety of tests and everything seemed to work alright. Seeing there are so many different scenarios is a good proof that it would be nice adding e2e tests at some point! 🙂

if ( customerPaymentMethods ) {
return;
}

// If there's no active payment method, or the active payment method has
// been removed (e.g. COD vs shipping methods), set one as active.
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an alternative approach that doesn't require the customerPaymentMethods dependency. It would involve using useState functional updates. Something like:

setActive( ( currentActivePaymentMethod ) => {
	// If there's no active payment method, or the active payment method has
	// been removed (e.g. COD vs shipping methods), set one as active.
	if (
		! currentActivePaymentMethod ||
		! paymentMethodKeys.includes( currentActivePaymentMethod )
	) {
		return Object.keys( paymentData.paymentMethods )[ 0 ];
	}
	return currentActivePaymentMethod;
} );

This would ensure currentActivePaymentMethod is evaluated right at the moment when the state is updated, so the payment method activated in SavedPaymentMethodOptions would already be active.

I didn't do a lot of testing of this approach, so feel free to ignore, but wanted to propose it in case you think it might be a good alternative.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smart! I like this, and of course since it's more general it avoids the issue with my truthy test below. Thanks for picking up on this, much better fix 🚀

The crux of the issue is that we were previously using a stale copy of currentActivePaymentMethod; so it makes sense that we'd fix this by using the functional form of setState. The principle to keep in mind here (just reminding myself!) - if a state update depends on the previous value, use functional form.

I've made the change and I'll do some more testing of a few scenarios. Note the other change from the sketch above is the reset of checkout status to PRISTINE when applying the default.

Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think I found an issue. If I open the Checkout page in a private window (incognito), the first tab is not selected. That happens because customerPaymentMethods is an (empty) object and evaluates to true so the effect returns too early.

imatge

Can you reproduce that too?

- use functional form of setState; previous state (current payment
  method) is not potentially stale value
- so the default is only set if there really is no active payment method
@haszari haszari changed the title bail out of setting a default payment method if shopper has saved card Ensure shopper saved card is used as default payment method (default was being overwritten in some circumstances) Sep 9, 2020
@haszari haszari added this to the 3.4.0 milestone Sep 9, 2020
@haszari
Copy link
Member Author

haszari commented Sep 9, 2020

I've tested the new approach (functional setState to use fresh version of active payment method state) and didn't find any issues. Tested checkouts with and without saved cards, saving new cards, reordering payment methods in admin, and submitting checkout with default-selected method or manually selecting.

@Aljullu ready for another review!

Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for applying the feedback. I did another round of testing and everything looked good. I just added a small comment about an effect dependency that I guess can be removed, but pre-approving. 👍

Travis tests are failing, but I don't think it's related to this PR. I will spend some time today investigating what's going on with them.

paymentMethodsInitialized,
paymentData.paymentMethods,
setActivePaymentMethod,
setActive,
customerPaymentMethods,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this dependency needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, and it looks like it hasn't been needed for a while! Removed it and retested (just in case) 👍

@haszari
Copy link
Member Author

haszari commented Sep 10, 2020

Merging this (with tests failing) as I believe the test failure is unrelated - see #3139

@haszari haszari merged commit 110cf74 into main Sep 10, 2020
@haszari haszari deleted the fix/3120-default-saved-card-not-used branch September 10, 2020 20:48
@nerrad nerrad added the type: bug The issue/PR concerns a confirmed bug. label Sep 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: bug The issue/PR concerns a confirmed bug.
Projects
None yet
4 participants