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

Resolve renewal failures for subscription orders with the UPE #6965

Merged
merged 12 commits into from
Aug 15, 2023

Conversation

FangedParakeet
Copy link
Contributor

@FangedParakeet FangedParakeet commented Aug 9, 2023

Fixes #4492
Fixes #6291

Changes proposed in this Pull Request

Fixing dysfunctional subscriptions with split and legacy UPE

This PR simplifies and clarifies the logic used to select an array of payment method type IDs to attach to a payment intent. We first check whether $_POST parameters are present--in which case, the request is from a standard checkout form. If the $_POST parameters are missing, this may be a subscription renewal--in which case, we use the attached payment token to ascertain the payment gateway and ensure we use the currency on the order to filter payment methods. If the scenario fits neither case, we return the payment methods enabled by the current gateway.

Hopefully this should fix...

  • Subscription renewals using UPE where the request context ($_POST[ 'payment_method' ]) is not present.
  • Subscription renewals where store currency differs from order currency.
  • Subscription renewals with Link as a payment method.

Testing instructions

Testing subscription renewals

This is the main feature that needs to be tested in this PR and can generally be performed in two ways.

First ensure your store contains at least one subscription product, add it to cart, and complete a checkout for this product.

Trigger subscription renewal from order page

  1. Find and open the order that you just completed the checkout for.
  2. Scroll down and select the subscription order related to this order.
  3. From the dropdown options in the top-right of the subscription order detail page, select "Process renewal".
  4. Confirm that renewal completes and order status and notes update as expected.

Trigger subscription renewal from scheduled actions

  1. On the order received page, note the order ID of the subscription order related to your checkout order.
  2. Visit the scheduled actions tab (WooCommerce -> Status -> Scheduled Actions).
  3. Search for a 'Pending' hook named 'woocommerce_scheduled_subscription_payment' and find the corresponding entry that matches your subscription order ID.
  4. Select "Run" to trigger the renewal for this subscription.
  5. Confirm that renewal completes and order status and notes on order detail update as expected.

Ideally, we should test subscription renewals with all UPEs disabled, legacy UPE enabled, split UPE enabled, and deferred intent UPE enabled. You can toggle these features using the dev tools.


  • Run npm run changelog to add a changelog file, choose patch to leave it empty if the change is not significant. You can add multiple changelog files in one PR by running this command a few times.
  • Covered with tests (or have a good reason not to test in description ☝️)
  • Tested on mobile (or does not apply)

Post merge

@botwoo
Copy link
Collaborator

botwoo commented Aug 9, 2023

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 6965 or branch name fix/upe-subscriptions-schisms in your-test.site/wp-admin/admin.php?page=jetpack-beta&plugin=woocommerce-payments

Option 2. Jurassic Ninja - available for logged-in A12s

🚀 Launch a JN site with this branch 🚀

ℹ️ Install this Tampermonkey script to get more options.


Build info:

  • Latest commit: 4061bb7
  • Build time: 2023-08-14 18:37:57 UTC

Note: the build is updated when a new commit is pushed to this PR.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 9, 2023

Size Change: 0 B

Total Size: 1.41 MB

ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.03 kB
release/woocommerce-payments/assets/css/success.css 158 B
release/woocommerce-payments/dist/blocks-checkout-rtl.css 1.5 kB
release/woocommerce-payments/dist/blocks-checkout.css 1.5 kB
release/woocommerce-payments/dist/blocks-checkout.js 73.4 kB
release/woocommerce-payments/dist/checkout-rtl.css 440 B
release/woocommerce-payments/dist/checkout.css 441 B
release/woocommerce-payments/dist/checkout.js 28.4 kB
release/woocommerce-payments/dist/index-rtl.css 35.6 kB
release/woocommerce-payments/dist/index.css 35.6 kB
release/woocommerce-payments/dist/index.js 283 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.05 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 2.87 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 60.5 kB
release/woocommerce-payments/dist/multi-currency.css 2.87 kB
release/woocommerce-payments/dist/multi-currency.js 55.6 kB
release/woocommerce-payments/dist/order-rtl.css 716 B
release/woocommerce-payments/dist/order.css 719 B
release/woocommerce-payments/dist/order.js 40.4 kB
release/woocommerce-payments/dist/payment-gateways-rtl.css 686 B
release/woocommerce-payments/dist/payment-gateways.css 688 B
release/woocommerce-payments/dist/payment-gateways.js 38.9 kB
release/woocommerce-payments/dist/payment-request.js 11.6 kB
release/woocommerce-payments/dist/product-details.js 789 B
release/woocommerce-payments/dist/settings-rtl.css 8.18 kB
release/woocommerce-payments/dist/settings.css 8.18 kB
release/woocommerce-payments/dist/settings.js 232 kB
release/woocommerce-payments/dist/subscription-edit-page.js 669 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 519 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 519 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 20.2 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 693 B
release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css 295 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 295 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 19.4 kB
release/woocommerce-payments/dist/tos-rtl.css 230 B
release/woocommerce-payments/dist/tos.css 231 B
release/woocommerce-payments/dist/tos.js 21.7 kB
release/woocommerce-payments/dist/upe_checkout-rtl.css 440 B
release/woocommerce-payments/dist/upe_checkout.css 441 B
release/woocommerce-payments/dist/upe_checkout.js 34 kB
release/woocommerce-payments/dist/upe_split_checkout-rtl.css 440 B
release/woocommerce-payments/dist/upe_split_checkout.css 441 B
release/woocommerce-payments/dist/upe_split_checkout.js 34.6 kB
release/woocommerce-payments/dist/upe_with_deferred_intent_creation_checkout.js 36.3 kB
release/woocommerce-payments/dist/upe-blocks-checkout-rtl.css 1.5 kB
release/woocommerce-payments/dist/upe-blocks-checkout.css 1.5 kB
release/woocommerce-payments/dist/upe-blocks-checkout.js 39.5 kB
release/woocommerce-payments/dist/upe-split-blocks-checkout-rtl.css 1.5 kB
release/woocommerce-payments/dist/upe-split-blocks-checkout.css 1.5 kB
release/woocommerce-payments/dist/upe-split-blocks-checkout.js 41.1 kB
release/woocommerce-payments/dist/woopay-express-button.js 50.4 kB
release/woocommerce-payments/dist/woopay-rtl.css 3.85 kB
release/woocommerce-payments/dist/woopay.css 3.85 kB
release/woocommerce-payments/dist/woopay.js 70.5 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 633 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 720 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.43 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.01 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js 522 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js 581 B
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/babel.config.js 160 B
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.css 2.32 kB
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.js 13.8 kB
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.rtl.css 2.32 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 403 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.56 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 299 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 742 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 572 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 411 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 544 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 9.55 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.8 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.83 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 544 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.38 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.1 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.6 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/payment-method-restrictions.js 1.29 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/wcs-meta-boxes-order.js 502 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 355 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 429 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/view-subscription.js 1.38 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/wcs-cart.js 781 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.1 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.27 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 392 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.06 kB

compressed-size-action

@csmcneill
Copy link
Contributor

csmcneill commented Aug 9, 2023

Once this is reviewed/tested/merged, how would we feel about sharing a "beta" build with 6430073-zen? They are greatly affected by #4492:

We are losing lots of revenue (we are talking about thousands of euros per month) just because people cannot use their local currency.

* @return array List of payment methods.
*/
private function get_payment_methods_from_request() {
$upe_payment_method = sanitize_text_field( wp_unslash( $_POST['payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
public function get_payment_method_types( $payment_information ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess for the supported PHP versions we can harden this method a bit: public function get_payment_method_types( Payment_Information $payment_information ): array

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing--added in 178d076.

private function get_payment_methods_from_request() {
$upe_payment_method = sanitize_text_field( wp_unslash( $_POST['payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
public function get_payment_method_types( $payment_information ) {
$request_payment_method = sanitize_text_field( wp_unslash( $_POST['payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
Copy link
Contributor

Choose a reason for hiding this comment

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

Naming proposal: $requested_payment_method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense--added in 178d076.

$request_payment_method = sanitize_text_field( wp_unslash( $_POST['payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
$token = $payment_information->get_payment_token();

if ( ! empty( $request_payment_method ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we prioritize posted PM over token? Can we add a comment explaining the order in which are trying to pick PMs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm basically prioritising checkout transactions over subscription renewals, since I believe the former will be more common: explained over here.

$payment_methods = $this->get_payment_methods_from_gateway_id( $request_payment_method );
} elseif ( ! is_null( $token ) ) {
$order = $payment_information->get_order();
$order_id = is_a( $order, 'WC_Order' ) ? $order->get_id() : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use $order instanceof WC_Order - a better practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call--updated in 178d076.

* @return array List of payment methods.
*/
public function get_payment_methods_from_gateway_id( $gateway_id, $order_id = null ) {
if ( 'woocommerce_payments' !== $gateway_id ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we should check here is the id start with woocommerce_payments_ as well. WDYT?

Copy link
Contributor

@kalessil kalessil Aug 11, 2023

Choose a reason for hiding this comment

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

Also, I lack a comment here explaining which gateways have this sort of IDs. Can you add it, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At this point, we know that the ID will start with woocommerce_payments; if the ID is anything other than woocommerce_payments, we presume that it will begin with woocommerce_payments_ as this should be the only remaining available option.

I simplified this (with a few explanatory comments) in 178d076 by simply checking whether the ID begins with woocommerce_payments_ first instead. However, I don't really feel a need to also confirm that the ID begins with woocommerce_payments, as I don't believe that it's possible for neither of those cases to be true.

*/
public function get_payment_methods_from_gateway_id( $gateway_id, $order_id = null ) {
if ( 'woocommerce_payments' !== $gateway_id ) {
$payment_methods = [ str_replace( 'woocommerce_payments_', '', $gateway_id ) ];
} elseif ( WC_Payments_Features::is_upe_split_enabled() || WC_Payments_Features::is_upe_deferred_intent_enabled() ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

That's difficult to understand can we introduce separate ifs for WC_Payments_Features::is_upe_split_enabled() and WC_Payments_Features::is_upe_deferred_intent_enabled() )?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Separate the clauses in 178d076.

*/
public function get_payment_methods_from_gateway_id( $gateway_id, $order_id = null ) {
if ( 'woocommerce_payments' !== $gateway_id ) {
$payment_methods = [ str_replace( 'woocommerce_payments_', '', $gateway_id ) ];
} elseif ( WC_Payments_Features::is_upe_split_enabled() || WC_Payments_Features::is_upe_deferred_intent_enabled() ) {
$payment_methods = [ 'card' ];
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 a constant for card PM.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good shout--amended in 178d076.

} elseif ( WC_Payments_Features::is_upe_split_enabled() || WC_Payments_Features::is_upe_deferred_intent_enabled() ) {
$payment_methods = [ 'card' ];
if ( WC_Payments_Features::is_upe_deferred_intent_enabled() &&
in_array( Payment_Method::LINK, $this->get_upe_enabled_payment_method_ids(), true ) ) {
$payment_methods[] = Payment_Method::LINK;
}
} else {
$payment_methods = WC_Payments::get_gateway()->get_payment_method_ids_enabled_at_checkout( null, true );
$payment_methods = WC_Payments::get_gateway()->get_payment_method_ids_enabled_at_checkout( $order_id, true );
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a comment explaining when we get into this branch please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a comment explaining the logic a bit better in 178d076 and 08e0daa. We will get here if the $gateway_id is woocommerce_payments and neither split nor deferred intent UPE is enabled--so this is either the legacy UPE or legacy card gateway.

@FangedParakeet FangedParakeet marked this pull request as ready for review August 12, 2023 00:59
@FangedParakeet FangedParakeet requested review from a team, timur27 and gpressutto5 and removed request for a team and timur27 August 12, 2023 00:59
@FangedParakeet
Copy link
Contributor Author

FangedParakeet commented Aug 14, 2023

@kalessil, just seen your comment over here. I tested this locally and I think this does enable us to process renewals for subscriptions using SEPA as a payment method...but this wasn't particularly intended by me when I authored this PR. 😅

I was actually under the impression that we would continue proceeding to keep SEPA subscriptions disabled--consequently we have closed #5517 and #5531. Can you confirm whether this is still the case or whether we intend to give SEPA subscriptions another chance? In the latter case, I would have to reassess whether SEPA subscriptions integration should be included in our upcoming deferred intent UPE feature release.

Copy link
Contributor

@gpressutto5 gpressutto5 left a comment

Choose a reason for hiding this comment

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

It's working great!

@frosso frosso added this pull request to the merge queue Aug 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants