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

Fix Cash App Pay token reuse issues (with subscriptions) #3263

Merged
merged 35 commits into from
Sep 16, 2024

Conversation

wjrosa
Copy link
Contributor

@wjrosa wjrosa commented Jul 10, 2024

Fixes #3171

Changes proposed in this Pull Request:

This PR enables the use of Cash App Pay when paying for subscriptions with free trials and as an option when changing a subscription's payment method.

For that, I have updated the usage of the Cash App Pay confirm method. The wallet hash generation method includes a new parameter (the payment intent type).

Known issue: The subscription extension adds a "success message" before the redirect is completed, which can be confusing for customers.

Testing instructions

  • Checkout to this branch on your local environment (fix/cashapp-token-reuse2)
  • Run npm install, npm run build:webpack and npm run up

(the following testing guide was extracted from the original issue)

Free trial

  1. Change your Stripe Plugin credentials to a US-based account.
  2. Change your store currency to USD.
  3. Enable Cash App Pay from the Stripe payment methods screen.
  4. Enable WooCommerce Subscriptions.
  5. Create a Subscription product with a free trial.
  6. Add the product to your cart.
  7. Go to the checkout
  8. Confirm that you can select Cash App Pay as a payment method
  9. Purchase the subscription using Cash App - this will create a token.
  10. Add the free trial product to your cart again.
  11. Confirm that you can complete the sign-up using the saved payment method.

Changing payment method

  1. Purchase a subscription product using a card.
  2. Go to the My Account → Subscriptions
  3. View your active subscription.
  4. Click the "Change payment" button.
  5. Confirm that the Cash App Pay method is now available.
  6. Confirm that you can change the subscription's payment to a saved cash app token.

  • 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

@wjrosa wjrosa changed the title Fix/cashapp token reuse2 Fix/cashapp token reuse Jul 11, 2024
@wjrosa wjrosa self-assigned this Jul 15, 2024
@wjrosa wjrosa changed the title Fix/cashapp token reuse Fix Cash App Pay token reuse issues Jul 26, 2024
@wjrosa wjrosa changed the title Fix Cash App Pay token reuse issues Fix Cash App Pay token reuse subscription issues Jul 26, 2024
@wjrosa wjrosa changed the title Fix Cash App Pay token reuse subscription issues Fix Cash App Pay token reuse issues (with subscriptions) Jul 26, 2024
@wjrosa wjrosa marked this pull request as ready for review July 26, 2024 21:47
@wjrosa wjrosa requested review from a team and james-allan and removed request for a team July 26, 2024 22:18
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.

Hey @wjrosa. I've been following some general Subscription testing instructions. I've listed them all below. I ran into a couple of issues, I'll follow up and finish testing shortcode checkouts tomorrow or later this week.

BLOCK CHECKOUT

  • Purchase subscription
    • New token
    • Existing token
  • Free trial subscription
    • New token
    • ❗ Existing token
      • Selecting an existing token and attempting to sign up to a free trial just leaves the customer on the checkout page leaves the subscription without a stored payment method. There's more to this as well. I'll leave a comment below. *
  • Mixed checkout (simple + subscriptions)
    • New token
    • Existing token
  • Multiple Subscriptions
    • New token
    • Existing token

SHORTCODE CHECKOUT

  • Purchase subscription
    • New token
    • Existing token
  • Free trial subscription
    • New token
    • Existing token
  • Mixed checkout (simple + subscriptions)
    • New token
    • Existing token
  • Multiple Subscriptions
    • New token
    • Existing token

OTHER FEATURES

  • Renew subscriptions
    • New token
    • Existing token
  • Change subscription payment
    • ❗ I cannot get Cash App to show as an option on the change payment method screen. (screenshot)
    • New token
    • Existing token
  • Add payment method via My Account.
  • Failed/manual renewal - Block Checkout
    • New token
    • Existing token
  • Failed/manual renewal - Shortcode Checkout
    • New token
    • Existing token

* Free trial - existing token issue. Even if I fixed the redirect problem, there's another issue where the subscription's payment method isn't set to the Cash App token. This is because the return happens so early in the process_payment_with_deferred_intent() function that things like saving the payment method information doesn't happen.

@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 6, 2024

@james-allan

  • Free trial - existing token issue. Even if I fixed the redirect problem, there's another issue where the subscription's payment method isn't set to the Cash App token. This is because the return happens so early in the process_payment_with_deferred_intent() function that things like saving the payment method information doesn't happen.

I believe the latest changes fixed this issue.

Change subscription payment
❗ I cannot get Cash App to show as an option on the change payment method screen. (screenshot)

I had the same issue and did some debugging. The issue happens on togglePaymentMethodForCountry. For some reason getStripeServerData()?.customerData?.billing_country is not being populated. My country is set to US (on the checkout and on the Stripe side as a customer), but customerData is undefined.

It looks like this property is set by calling useCustomerData. Not sure if we need to call it manually on the change payment method page 🤔 (it is not called there). Still, does not seem directly related to this PR.

@wjrosa wjrosa requested a review from james-allan August 6, 2024 16:34
@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 6, 2024

I actually fixed the above on 64b20d5 @james-allan 👍

@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 8, 2024

hi @james-allan ! I tested the other flows from your checklist myself, and I guess it is all good. One thing that I noticed is that when using an existing token, the subscription is set to "active", while with a new token, it is set to "pending". Is this expected? 🤔 I thought both would require a webhook call to process

@james-allan
Copy link
Contributor

Free trial - existing token issue. Even if I fixed the redirect problem, there's another issue where the subscription's payment method isn't set to the Cash App token. This is because the return happens so early in the process_payment_with_deferred_intent() function that things like saving the payment method information doesn't happen.

I believe the latest changes fixed this issue.

@wjrosa this flow is causing an issue for me. If you add a subscription with a free trial to your cart and then use a saved Cash App token, the resulting subscription will have missing payment method token information.

Screenshot 2024-08-13 at 2 53 17 PM
Empty payment method information on My Account → Subscriptions list.

Screenshot 2024-08-13 at 2 55 20 PM
Empty payment method information on WooCommerce → Subscriptions (edit) screen

To fix this we'd need to update this section of the code. If you purchase a free subscription with a saved token, the payment_complete and early return occurs without storing the token to the order. We essentially need to make sure we still save the token to the order too. re:

$this->maybe_update_source_on_subscription_order(
	$order,
	(object) [
		'payment_method' => $payment_information['payment_method'],
		'customer'       => $payment_information['customer'],
	],
	$this->get_upe_gateway_id_for_order( $upe_payment_method )
);

@james-allan
Copy link
Contributor

One thing that I noticed is that when using an existing token, the subscription is set to "active", while with a new token, it is set to "pending". Is this expected? 🤔 I thought both would require a webhook call to process

Yeah that seems a little off. Are you sure one of them is just going straight to active? My webhooks are so quickly processed that I don't notice any difference. But yes, if you're using a saved payment method or a new token to pay for a new subscription that has an upfront cost I'd expect both of them to involve a webhook process.

In my case when I use a new or existing token by the time I land on the order received page the subscription is active.

Now that I think on it, perhaps what you're seeing is the difference between a setup intent and just payment intent. 🤷 In my testing though both result in a payment_intent.succeeded and a charge.succeeded webhook.

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 @wjrosa. I just left a comment about the subscriptions with a free trial being purchased with a saved payment method.

@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 13, 2024

Free trial - existing token issue. Even if I fixed the redirect problem, there's another issue where the subscription's payment method isn't set to the Cash App token. This is because the return happens so early in the process_payment_with_deferred_intent() function that things like saving the payment method information doesn't happen.

I believe the latest changes fixed this issue.

@wjrosa this flow is causing an issue for me. If you add a subscription with a free trial to your cart and then use a saved Cash App token, the resulting subscription will have missing payment method token information.

Screenshot 2024-08-13 at 2 53 17 PM Empty payment method information on My Account → Subscriptions list.

Screenshot 2024-08-13 at 2 55 20 PM Empty payment method information on WooCommerce → Subscriptions (edit) screen

To fix this we'd need to update this section of the code. If you purchase a free subscription with a saved token, the payment_complete and early return occurs without storing the token to the order. We essentially need to make sure we still save the token to the order too. re:

$this->maybe_update_source_on_subscription_order(
	$order,
	(object) [
		'payment_method' => $payment_information['payment_method'],
		'customer'       => $payment_information['customer'],
	],
	$this->get_upe_gateway_id_for_order( $upe_payment_method )
);

Thanks, @james-allan ! I fixed this on b8bd1af

@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 13, 2024

One thing that I noticed is that when using an existing token, the subscription is set to "active", while with a new token, it is set to "pending". Is this expected? 🤔 I thought both would require a webhook call to process

Yeah that seems a little off. Are you sure one of them is just going straight to active? My webhooks are so quickly processed that I don't notice any difference. But yes, if you're using a saved payment method or a new token to pay for a new subscription that has an upfront cost I'd expect both of them to involve a webhook process.

In my case when I use a new or existing token by the time I land on the order received page the subscription is active.

Now that I think on it, perhaps what you're seeing is the difference between a setup intent and just payment intent. 🤷 In my testing though both result in a payment_intent.succeeded and a charge.succeeded webhook.

Any ideas where this status change to active is handled? Maybe that's something to do with the early return after calling payment_complete().

@wjrosa
Copy link
Contributor Author

wjrosa commented Aug 14, 2024

Still not sure where the issue is, but I did some debugging and saw that:

  • When purchasing a product using an existing Cash App Pay token, we are calling create_and_confirm_payment_intent
  • So, we are indeed processing the payment intent already
  • Then getting inside this condition:
if ( $payment_needed ) {
				// Use the last charge within the intent to proceed.
				$charge = $this->get_latest_charge_from_intent( $payment_intent );

				// Only process the response if it contains a charge object. Intents with no charge require further action like 3DS and will be processed later.
				if ( $charge ) {
					$this->process_response( $charge, $order );
				}
  • The $charge value will be from the intent we just processed before
  • As it is captured and not pending but succeeded, it will get into this condition:
			if ( 'succeeded' === $response->status ) {
  • Making the order completed, and triggering the hook to change the subscription status along it (to active)

So, what should we do in this case? (when using a saved Cash App Pay token for a paid subscription)

  1. Leave it as is, as the intent is indeed being processed, and the webhook call will probably ignore this order/subscription
  2. Skip this line and also return early:
// Create a payment intent, or update an existing one associated with the order.
$payment_intent = $this->process_payment_intent_for_order($order, $payment_information);

(but it may break something as we will have only the setup intent when the order/subscription is processed by the webhook call)
3. Return early before this check:

			if ( $payment_needed ) {
				// Use the last charge within the intent to proceed.
				$charge = $this->get_latest_charge_from_intent( $payment_intent );

				// Only process the response if it contains a charge object. Intents with no charge require further action like 3DS and will be processed later.
				if ( $charge ) {
					$this->process_response( $charge, $order );
				}
  1. Force it to fall into one of the next conditions if it makes sense: elseif ( $this->is_changing_payment_method_for_subscription() ) or elseif ( in_array( $payment_intent->status, self::SUCCESSFUL_INTENT_STATUS, true ) ) (by looking I would say only the last would make a bit sense)

@wjrosa
Copy link
Contributor Author

wjrosa commented Sep 11, 2024

@james-allan I did some additional analysis today for the status difference I mentioned earlier (after your fix for live payments), and I think the issue might be the $order->payment_complete();

Non-saved token

  • Order is kept as pending
  • Confirmation is made on the frontend (QR code scan)

Saved token

  • Order is set to completed due to the call to $order->payment_complete();
  • Nothing else is done

So:

  • Should we have a QR code scan for saved tokens? From our previous conversations, I think it does not make sense (or did it change due to the live payments fix?)
  • Should we just remove the call to payment_complete here instead?

@wjrosa
Copy link
Contributor Author

wjrosa commented Sep 12, 2024

I just tested this on a JN site, and the webhook call worked. So, maybe we don't need to do anything else here.

@james-allan
Copy link
Contributor

Should we have a QR code scan for saved tokens? From our previous conversations, I think it does not make sense (or did it change due to the live payments fix?)

Nah I don't think we do need to display the QR code because the PM has already been set up and we have a saved token.

I just tested this on a JN site, and the webhook call worked. So, maybe we don't need to do anything else here.

Ok I'll rerun through some tests and confirm this works as expected.

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.

BLOCK CHECKOUT

  • Purchase subscription
    • New token
    • Existing token
  • Free trial subscription
    • New token
    • Existing token
  • Mixed checkout (simple + subscriptions)
    • New token
    • Existing token
  • Multiple Subscriptions
    • New token
    • Existing token

OTHER FEATURES

  • Renew subscriptions
    • New token
    • Existing token
  • Change subscription payment
    • New token
    • Existing token
  • Add payment method via My Account.
  • Failed/manual renewal - Block Checkout
    • New token
    • Existing token

I also ran a basic test using a new token and a saved token on the shortcode checkout just to be sure.

@wjrosa wjrosa merged commit 2788073 into develop Sep 16, 2024
34 of 35 checks passed
@wjrosa wjrosa deleted the fix/cashapp-token-reuse2 branch September 16, 2024 15:10
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.

[CashApp] Support subscription with a free trial and changing a subscription's payment method to Cash App
2 participants