Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Deferred intent] Allow saving cards and using saved ones #2816

Merged
merged 26 commits into from
Jan 19, 2024

Conversation

a-danae
Copy link
Contributor

@a-danae a-danae commented Dec 30, 2023

Fixes #2760
Fixes (partially) #2763

Changes proposed in this Pull Request:

Using deferred intent:

  • Allow saving new cards in the block and shortcode checkout. The card gets attached to the Stripe customer, and a token is created in the store for the user to reuse it.
  • Allow using saved cards in the block and shortcode checkout.
  • Retry processing the payment when the returned error is a retriable one.
  • When prepaid cards are disallowed, bail out gracefully when the used card is prepaid.

Testing instructions

Saving a new card

  1. Ensure "Enable payments via saved cards" is enabled. Under Stripe > Settings > Payments ( siteurl/wp-admin/admin.php?page=wc-settings&tab=checkout&section=stripe&panel=settings) > Payments & transactions
image
  1. Log in as a customer in the store
  2. Add a product to the cart and go to the shortcode checkout
  3. Select the option "Use a new payment method"
  4. Select "Save payment information to my account for future purchases"
  5. Enter a valid card, like 4242424242424242, and place the order
  6. Confirm things work as expected (reference list below)
  7. Add a product to the card and go to the block checkout
  8. Follow steps 5, 6, 7, and 8.

Confirm that:

  1. The WC order is placed as expected in the store (under siteurl/wp-admin/admin.php?page=wc-orders):
  • A WC order was created and has a "processing" status.
  • It has a customer ID, payment intent ID, and charge ID associated with it.
  • Under Item > Above "Refund", confirm it says "{date} via Credit card / debit card".
  1. A payment was processed in Stripe, and it has the expected information (under https://dashboard.stripe.com/test/payments):
  • There's a payment intent associated with the WC order.
  • The payment intent has a "Succeeded" status.
  • It contains all the details of a new non-saved card's payment intent.
  • Under "Payment method", confirm it says "Set up for future use".
image 3. The stripe customer associated with the payment intent has this payment method attached to it.
  • Click on the customer associated with the payment intent to see its details (https://dashboard.stripe.com/test/customers/cus_xyz)
    image
  • Under "Payment methods", ensure the card you just added is listed
    image
  • Ensure the card has the billing details you entered when checking out. They could be different to the details of the Stripe customer.
  • Ensure it has a payment intent ID as the value for "Set up for future use".
  1. The WC customer got this card associated to it:
  • As the logged-in customer in the store, go to the My Account -> Payment methods page (siteurl/my-account/payment-methods/).
  • Ensure the card you just used to check out is listed.
  1. There are no errors in the Stripe dashboard logs, https://dashboard.stripe.com/test/logs

Using a saved card

  1. Check out through the shortcode checkout using the previously saved cards.
  2. Confirm that the payment intent and the WC order are created as expected.
  3. Test again checking out through the block checkout page.

Saving a 3DS card

Test cards

  1. Go to the shortcode checkout page.
  2. Select "Save payment information to my account for future purchases".
  3. Use a test 3DS card that requires authentication for off-session payments, like 4000002760003184.
  4. Place the order but fail the authentication.
  5. Confirm that a user-friendly failure message is displayed.
  6. Place the order again and pass the authentication.
  7. Confirm that the card was attached to the Stripe customer and saved as a token for the WC customer.
  8. Confirm that the payment intent and WC order were created as expected.
  9. Test again on the block checkout page.

Using a saved 3DS card

  1. Go to the shortcode checkout page.
  2. Select the 3DS card saved before and fail the authentication.
  3. Confirm that a user-friendly failure message is displayed.
  4. Place the order again but pass the authentication this time.
  5. Confirm that the payment intent and WC order were created as expected.
  6. Test again on the block checkout page.

Disabling saving cards

  1. Go to the checkout page with the settings for saving cards enabled. That's under "Enable payments via saved cards" is enabled. Under Stripe > Settings > Payments ( siteurl/wp-admin/admin.php?page=wc-settings&tab=checkout&section=stripe&panel=settings) > Payments & transactions
  2. In a separate tab, disable this setting.
  3. Without reloading the checkout page, select "Save payment information to my account for future purchases".
  4. Place the order.
  5. Confirm that the WC order and payment intent are created as expected.
  6. Confirm that the card wasn't attached to the Stripe customer and wasn't added to the WC customer.

  • Covered with tests (or have a good reason not to test in description ☝️)
  • Added changelog entry in both changelog.txt and readme.txt (or does not apply)
  • Tested on mobile (or does not apply)

Post merge

@a-danae a-danae marked this pull request as ready for review January 8, 2024 10:30
@a-danae a-danae requested a review from james-allan January 8, 2024 11:01
Copy link
Contributor

@james-allan james-allan left a comment

Choose a reason for hiding this comment

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

Changes look pretty good @a-danae!

I went through the following testing instructions and ran into some issues using a saved payment method on the classic checkout. Details below.

  • Disabled the save payment method setting (screenshot).
    • On checkout. No checkbox to save. 👍
    • A purchase using a card didn't save the method in Stripe or WC. 👍
    • No setup intent. 👍
  • Saving payment methods enabled (screenshot).
  • 🔲 Block Checkout 🔲
    • Choosing not to save. ie leave the checkbox unchecked on checkout.
      • A purchase using a card didn't save the method in Stripe or WC. 👍
      • No setup intent. 👍
    • Saving a standard card (Mastercard).
      • Saved in Stripe. 👍
      • Saved in WC. 👍
      • Set up intent. 👍 (screenshot)
    • Saving a 3DS card.
      • Saved in Stripe. 👍
      • Saved in WC. 👍
      • Set up intent. 👍
    • Using a saved standard card (Mastercard).
      • PI saved in order meta. 👍
      • Saved PM is used to process the payment. 👍
      • Saved PM saved in order meta. 👍
    • Using a saved 3DS card.
      • Re-authed 👍
      • PI saved in order meta. 👍
      • Saved PM is used to process the payment. 👍
      • Saved PM saved in order meta. 👍
  • 🏛️ Shortcode/Classic Checkout 🏛️
    • Choosing not to save. ie leave the checkbox unchecked on checkout.
      • A purchase using a card didn't save the method in Stripe or WC.
      • No setup intent.
    • Saving a standard card (AMEX).
      • Saved in Stripe. 👍
      • Saved in WC. 👍
      • Set up intent. 👍
    • Saving a 3DS card.
      • Saved in Stripe. 👍
      • Saved in WC. 👍
      • Set up intent. 👍
    • Using a saved standard card (AMEX). 👍
    • Using a saved 3DS card. 👍

@a-danae when I try to use a saved payment method on the classic/shortcode checkout, I get the following error:

Screenshot 2024-01-10 at 3 03 57 pm

Do you get the same error message?

Update: After using the latest version of this branch, this error is now fixed.

@a-danae
Copy link
Contributor Author

a-danae commented Jan 10, 2024

Thanks for the thorough testing, @james-allan !

when I try to use a saved payment method on the classic/shortcode checkout, I get the following error:

Interesting... This validation shouldn't be triggered when using a saved payment method.
I'm not able to replicate this on my end. I tested in both the classic checkout added via shortcode and via blocks.

Mind sharing the ID and name of this input on your end?
image

My thoughts:

  • The "Your card number is incomplete" error is triggered by the Payment Element in the frontend. It's triggered in this line.
  • But the parent processPayment() function that triggers this validation shouldn't be called when using a saved card. It should only be called on the checkout page when not using a saved payment method.
  • To define whether we're using a saved payment method, we check this input by its ID over here. I'm thinking this check is failing. Maybe the ID is different in a scenario I missed.

@james-allan
Copy link
Contributor

Mind sharing the ID and name of this input on your end?

Looks like its the same ID:
Screenshot 2024-01-18 at 10 02 30 am

I can still replicate it today so I'll take a look on my end and see if I can narrow it down.

@james-allan
Copy link
Contributor

So it looks like the flow that leads to this error is starting in the processPaymentIfNotUsingSavedMethod() function. It uses the stripe utils version of the isUsingSavedPaymentMethod() function which has slightly different element IDs.

ie it looks for '#wc-stripe-new-payment-method' rather than #wc-stripe-payment-token-new. Here's a link to that function: https://github.com/woocommerce/woocommerce-gateway-stripe/blob/add/save-cards-deferred-intent/client/stripe-utils/utils.js#L290-L295.

@james-allan
Copy link
Contributor

So it looks like the flow that leads to this error is starting in the processPaymentIfNotUsingSavedMethod() function. It uses the stripe utils version of the isUsingSavedPaymentMethod() function which has slightly different element IDs.

🤦‍♂️🤦‍♂️🤦‍♂️ so sorry @a-danae. I somehow had an old version of the Stipe Utils JS file and just noticed you have updated that function in this PR. After force updating my local branch with the latest version of this branch, it works now. 😅

@mattallan
Copy link
Contributor

mattallan commented Jan 18, 2024

Hey @a-danae 👋 I just merged my Split-PE PR #2827 and resolved the conflicts that were in this PR, but when I tried to test saving/using saved cards in a split PE environment, I got the following results:

  • ✅ Saving a new card.
  • ❌ Using an existing saved card seems to be broken in a Split PE environment.

Here's the error I'm getting:

image

After doing some digging, the issue is caused by this line in the prepare_payment_information_from_request() function:

$selected_payment_type = sanitize_text_field( wp_unslash( $_POST['wc_stripe_selected_upe_payment_type'] ?? '' ) );

Previously with the old Stripe UPE we were hooking onto the StripeElements.change event and setting the selected payment type, but now with a Split PE we're never setting the selected UPE payment type.

To fix this, now that each payment method is separate, I'm wondering if we can get rid of the old wc_stripe_selected_upe_payment_type hidden field implementation and fetch the selected payment method directly from $POST['payment_method'] (i.e. this would be 'stripe' or 'stripe_bancontact;).

I've been testing with this helper function below and it appears to be working but curious to get your thoughts:

private function get_selected_payment_method_type() {
	if ( ! isset( $_POST['payment_method'] ) && substr( $_POST['payment_method'], 0, 6 ) !== 'stripe' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
		return '';
	}

	$payment_method_type = sanitize_text_field( wp_unslash( $_POST['payment_method'] ) );
	return substr( $payment_method_type, 0, 7 ) === 'stripe_' ? substr( $payment_method_type, 7 ) : 'card';
}

With the above function, you then need to update the first line in prepare_payment_information_from_request() to be:

private function prepare_payment_information_from_request( WC_Order $order ) {
	$selected_payment_type = $this->get_selected_payment_method_type();

Since this PR has already been approved I'm happy to move this into a separate PR! Up to you :)

We were getting it from wc_stripe_selected_upe_payment_type before. After implementing Split PE, there's no longer need to use this customized hidden field.
@a-danae
Copy link
Contributor Author

a-danae commented Jan 19, 2024

Big thanks, @james-allan for checking this out again!

And big thanks @mattallan for updating this branch, checking it with the Split PE changes, and proposing a fix! 🙌

I've included your fix and it's testing well 🙂 There are a few tests that need adjustments, but other than that, this should be ready to review. I'll check these tomorrow.

@mattallan
Copy link
Contributor

No worries @a-danae! I have reviewed your latest changes and they look really good!

I also ran through the following checks:

Checkout blocks

  • Not saving a card 👍
  • Saving a new card (4242424242424242) 👍
  • Saving a 3DS card (4000000000003220) 👍
  • Attempting to save a declining card (4000000000009995) 👍
  • Using an existing saved card 👍
  • Using an existing saved 3DS card 👍
  • Using an existing saved card that should decline after attaching (4000000000000341) 👍
  • Confirm saving a card is forced when purchasing a subscription product 👍

Classic checkout

  • Not saving a card 👍
  • Saving a new card 👍
  • Saving a 3DS card 👍
  • Using an existing saved card 👍
  • Using an existing saved 3DS card 👍
  • Using an existing saved card that should decline after attaching 👍
  • Confirm saving a card is forced when purchasing a subscription product 👍

I'm happy to approve this PR. Amazing work on this one!!

@a-danae a-danae merged commit d7abf7b into add/deferred-intent Jan 19, 2024
32 checks passed
@a-danae a-danae deleted the add/save-cards-deferred-intent branch January 19, 2024 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants