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

Create pending renewal order (improve safety/error handling) #768

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from

Conversation

barryhughes
Copy link
Member

For most active subscriptions, it's possible for the merchant to create a pending renewal order. This is generally done via the Order Actions dropdown:

However, as described in the linked issue, it is possible for an uncaught exception to be raised (this might happen if it is not possible to change the subscription status), which breaks the process and leads the user to a blank screen or error message:

[17-Oct-2024 02:22:56 UTC] PHP Fatal error:  Uncaught Exception: Unable to change subscription status to "on-hold". in /.../woocommerce-subscriptions/vendor/woocommerce/subscriptions-core/includes/class-wc-subscription.php:450

Stack trace:
#0 /.../woocommerce-subscriptions/vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-meta-boxes.php(255): WC_Subscription->update_status('on-hold')
#1 /.../wp-includes/class-wp-hook.php(324): WCS_Admin_Meta_Boxes::create_pending_renewal_action_request(Object(WC_Subscription))
#2 /.../wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array)

Though the original report cites a problem when the subscription status cannot be changed, on further examination it looked to me like there are 3 points of failure within WCS_Admin_Meta_Boxes::create_pending_renewal_action_request(), so I've tried to address all three. However, if we prefer to stay tightly focused only on the potential for an uncaught exception (when the subscription status is changed), I'm more than happy to revise and simplify the PR.

The three areas I felt could benefit from additional safety:

  • Status update → an exception can be raised, and is not caught (notice also that we add an order note before this happens).
  • Creation of the renewal order → we assume an order was successfully created, but it is possible a WP_Error will have been raised (if so, we'd hit problems when we subsequently try to call order methods).
  • Setting the payment method → An exception is again possible here and, similar to the first issue, is not caught.

Therefore, this change mostly involves adding try/catch blocks or conditionals so that we handle things gracefully should the unexpected happen.

Additionally (happy to unwind this if it's not desirable), I decided to consolidate the creation of admin notices and order notes, as it felt like both were useful (the admin notice catches the user's eye, and the order note leaves a permanent record/paper trail in case the notice is missed).

Lastly, the linked issue proposes we remove the create pending renewal order item from the Order Actions dropdown if it looks like creating it will lead to an error. As described above, there are lots of conditions in which this might happen, not just one, so I'm not too sure about the merits of this (and, it seems a fix has already been made to the extension with which this was being experienced). However, more than happy to follow-up and put this in place.

Updates https://github.com/woocommerce/woocommerce-subscriptions/issues/4735

How to test this PR

We're can contrive error conditions for each of the three points of failure by applying the following snippets, one at a time (you could, for instance, place these lines in a mu-plugin file as needed):

# Trigger the originally reported problem, in which the subscription status
# cannot be updated to 'on-hold' (leads to an exception being raised).
add_filter( 'woocommerce_can_subscription_be_updated_to_on-hold', '__return_false' );
# Triggers the second possible failure, in which the new order is not created
# and a WP_Error is raised.
add_filter( 'wcs_new_order_created', fn () => new WP_Error( 'simulated-order-creation-error ' ) );
# Triggers the third possible failure, in which setting the order's payment 
# method leads to an exception being raised.
add_filter( 'wcs_renewal_order_created', function () {
	class Problematic_Renewal_Order {
		public function get_edit_order_url() {
			return '';
		}

		public function set_payment_method() {
			throw new WC_Data_Exception( 'problem', 'simulated' );
		}
	}

	return new Problematic_Renewal_Order;
}, 10000 );

Let's do some testing! Create or find a suitable existing subscription (one that is active, has a valid payment method already, etc):

  1. Start by testing the 'happy path', and try creating a pending renewal order. This should succeed (note: you should not have any test snippets in place just yet). Once the editor reloads, you should see both an admin notice and an order note, reading, "A pending renewal order was successfully created!"
  2. Since the last test succeeded, the subscription will now be on hold. We need to change this before we continue with our tests: set it back to active.
  3. Add the first test snippet and create another pending renewal order. This time, an order will not be created, and you should see an admin notice (and order note) reading, "Pending renewal order was not created, as it was not possible to update the subscription status."
  4. Remove the test snippet, and replace with the second snippet, then repeat the test. Again, the order will not be created and we should see, "Creation of the pending renewal order failed."
  5. Finally, replace the test snippet with the last snippet, then repeat once more. This time the renewal order should have been created, but the payment method will not have been set. Accordingly, we should see, "A pending renewal order was successfully created, but there was a problem setting the payment method. Please review the order."
  6. You can of course repeat the tests without this fix branch, in which case you will encounter the naked errors.

Some open questions

  • As above, are we happy to tackle all three potential failures from ::create_pending_renewal_action_request() in the same PR?
  • Do we feel there is a need to follow-up, and try to proactive remove create pending renewal order from the order actions list (where possible)? This was one of the bug report author's recommendations, and it seems like this may already have happened from within WooCommerce Pre-Orders.
  • It seems like essentially this same set of problems is also true of the create pending parent action: should we follow-up with similar fixes for that, too?

Product impact

  • Added changelog entry (or does not apply)
  • Will this PR affect WooCommerce Subscriptions? yes (#4735)
  • Will this PR affect WooCommerce Payments? no
  • Added deprecated functions, hooks or classes to the spreadsheet none

@barryhughes barryhughes requested a review from mattallan January 17, 2025 23:39
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.

1 participant