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

Set order status to failed for testing failed recurring payments. #40

Merged

Conversation

peterwilsoncc
Copy link
Contributor

This sets the status of a failed recurring payment to failed when testing a failing gateway.

Woo Subscriptions hooks in to the status transition when the payments fail to trigger features such as the automatic retry of failed payments.

Fixes #38.

@jimjasson jimjasson self-requested a review May 9, 2024 10:16
@jimjasson
Copy link
Contributor

Hey @peterwilsoncc,

Thank you for the PR!

I tested your change locally by:

  • setting the Dummy Payment's result to 'Failure' and;
  • trying to purchase a Variable Subscription product,

but the created subscription was set to 'Pending Payment' -- same as before your change.

Moreover, having a look at a Subscription, I noticed that it doesn't have a 'Failed' order status:

Markup 2024-05-09 at 13 18 47

Only Orders have a 'Failed' status. If I manually set an order to 'Failed', then the respective Subscription is put 'On hold'.

Can you please have another look? Moreover, can you please provide some more details about why a 'Pending payment' status is not ideal for failed Subscriptions?

@peterwilsoncc
Copy link
Contributor Author

I tested your change locally by:

  • setting the Dummy Payment's result to 'Failure' and;

  • trying to purchase a Variable Subscription product,

but the created subscription was set to 'Pending Payment' -- same as before your change.

This occurred to me overnight, the process_payment method also needs to use the failed status, I've updated that in 1f04d28

This removes the exceptions from this gateway but I think that's fine as it matches the behavior of gateways I've tested (Stripe, WooPayments, Braintree).

If you wish, I can add fatal error to the options in the settings screen to test throwing an exception. Just let me know.

Moreover, having a look at a Subscription, I noticed that it doesn't have a 'Failed' order status

This is correct.

Subscriptions hooks on to woocommerce_order_status_changed and if the order's status is failed, updates the subscriptions status to 'on-hold'.

Can you please have another look? Moreover, can you please provide some more details about why a 'Pending payment' status is not ideal for failed Subscriptions?

The difference between pending payment and failed payment statuses is a little subtle. In testing various gateways, the difference is:

  • Pending: This is used for Pay on Delivery, Check and similar gateways in which the payment is expected to be made some time after the order is submitted. Merchants using these types of gateways need to manually update the status to processing or completed once they have the cash or check in hand.
  • Failed: This is used by credit card gateways in which the payment is expected to be processed as the order is submitted. If the credit card is declined then merchants don't have an alternative method of receiving payment (or their customer can switch to COD if they do).

If you wish, I can add a pending option to the status options on the settings screen but as Woo has a built in Check and COD payment methods the Order > Pending > Processing flow is already covered in Woo Core.

@jimjasson
Copy link
Contributor

Hey @peterwilsoncc,

Thank you for the additional details!

I tested your changes and now, when using the block-based checkout, a fatal error is triggered:

PHP Fatal error:  Uncaught TypeError: array_merge(): Argument #2 must be of type array, null given in /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Legacy.php:67
Stack trace:
#0 /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Legacy.php(67): array_merge(Array, NULL)
#1 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp-hook.php(324): Automattic\WooCommerce\StoreApi\Legacy->process_legacy_payment(Object(Automattic\WooCommerce\StoreApi\Payments\PaymentContext), Object(Automattic\WooCommerce\StoreApi\Payments\PaymentResult))
#2 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array)
#3 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/plugin.php(565): WP_Hook->do_action(Array)
#4 /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php(82): do_action_ref_array('woocommerce_res...', Array)
#5 /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php(261): Automattic\WooCommerce\StoreApi\Routes\V1\Checkout->process_payment(Object(WP_REST_Request), Object(Automattic\WooCommerce\StoreApi\Payments\PaymentResult))
#6 /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractRoute.php(121): Automattic\WooCommerce\StoreApi\Routes\V1\Checkout->get_route_post_response(Object(WP_REST_Request))
#7 /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php(134): Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute->get_response_by_request_method(Object(WP_REST_Request))
#8 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/rest-api/class-wp-rest-server.php(1230): Automattic\WooCommerce\StoreApi\Routes\V1\Checkout->get_response(Object(WP_REST_Request))
#9 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/rest-api/class-wp-rest-server.php(1063): WP_REST_Server->respond_to_request(Object(WP_REST_Request), '/wc/store/v1/ch...', Array, NULL)
#10 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/rest-api/class-wp-rest-server.php(439): WP_REST_Server->dispatch(Object(WP_REST_Request))
#11 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/rest-api.php(428): WP_REST_Server->serve_request('/wc/store/v1/ch...')
#12 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp-hook.php(324): rest_api_loaded(Object(WP))
#13 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#14 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/plugin.php(565): WP_Hook->do_action(Array)
#15 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp.php(418): do_action_ref_array('parse_request', Array)
#16 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/class-wp.php(813): WP->parse_request('')
#17 /Users/jasonkytros/Local Sites/wp/app/public/wp-includes/functions.php(1336): WP->main('')
#18 /Users/jasonkytros/Local Sites/wp/app/public/wp-blog-header.php(16): wp()
#19 /Users/jasonkytros/Local Sites/wp/app/public/index.php(17): require('/Users/jasonkyt...')
#20 {main}
  thrown in /Users/jasonkytros/Local Sites/wp/app/public/wp-content/plugins/woocommerce-mono/plugins/woocommerce/src/StoreApi/Legacy.php on line 67

As you can see here, the process_payment function is expected to return an array.

Moreover, if you want to add special handling for Subscriptions inside process_payment then it is necessary to check first if an order is a subscription -- if the order is not a Subscription, then a subscription-related message shouldn't show up or added as a note to the order.

@peterwilsoncc
Copy link
Contributor Author

@jimjasson I've pushed the following changes in 554d535

  • restored throwing an exception after marking a the order status failed in process_payment()
  • restored the error message in process_payment() -- that was a copy-paste error sorry.

This removes the fatal error you were seeing last week while testing this PR and allows subs to handle payment retries for recurring payments.

@peterfabian
Copy link

peterfabian commented May 16, 2024

What I tested with the dummy gateway (DG) and block cart & checkout:

master branch

Simple product

  • When DG set to success, order is created and set to Processing.
  • When DG is set to fail, order with Pending Payment status is created, message to the user:

Order payment failed. To make a successful payment using Dummy Payments, please review the gateway settings.

Simple subscription product

  • When DG set to success, order is created and set to Processing, subscription is Active.
  • When DG set to fail,
    • order with Pending Payment status is created,
    • subscription is in Pending state,
    • message to the user:

Order payment failed. To make a successful payment using Dummy Payments, please review the gateway settings.

This branch

Simple product

  • When DG set to success, order is created and set to Processing.
  • When DG is set to fail, order with Failed status is created. Message to the user:

Order payment failed. To make a successful payment using Dummy Payments, please review the gateway settings.

Simple subscription product

  • When DG set to success, order is created and set to Processing, subscription is Active.
  • When DG set to fail,
    • order with Failed status is created,
    • subscription is in On hold state,
    • message to the user:

Order payment failed. To make a successful payment using Dummy Payments, please review the gateway settings.

Subscription renewal:

On an active subscription, I tried to process renewal after switching DG to fail:

  • A new renewal order was created with Failed status.
  • Subscription was put On hold
  • Renewal order has an order note:

Subscription payment failed. To make a successful payment using Dummy Payments, please review the gateway settings. Order status changed from Pending payment to Failed.

In conclusion, it does seem to work as described in the PR.

I also briefly checked the code for process_subscription_payment in Stripe gateway and saw that it catches (its own) exceptions and sets the order status to failed, so the rationale behind the change looks sound, too.

Copy link

@peterfabian peterfabian left a comment

Choose a reason for hiding this comment

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

Based on the testing described in my previous comment, I think the change looks good. Thanks!

@jimjasson jimjasson removed their request for review May 22, 2024 06:37
@PanosSynetos PanosSynetos merged commit 7ebed98 into woocommerce:master May 22, 2024
@peterwilsoncc peterwilsoncc deleted the fix/38-failed-order-status branch October 4, 2024 03:14
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.

Failed renewal orders ought to update the order status to failed.
4 participants