Automattic / woocommerce-subscriptions-core

Subscriptions core package for WooCommerce
Other
81 stars 29 forks source link

Fix manual renewal order payments via the checkout block not associating with the subscription #498

Closed james-allan closed 9 months ago

james-allan commented 10 months ago

Fixes #456

Description

When you pay for a failed or manual renewal order Subscriptions does a couple of things to make sure you resume payment of the original order, not create a new one.

Those two are:

  1. Set the 'order_awaiting_payment' and 'store_api_draft_order' session keys to set the order being paid.
  2. Ensure that the cart hash is updated in a way that when WC verifies whether the order being paid for is compatible with the cart changes, it returns true. We achieve that by updating the cart has stored on the order right before WC runs that check, effectively bypassing that "feature" completely.

The issue with the block checkout centres on point 2 from above. In the Store API (used by checkout blocks), there are very few hooks that trigger before the cart hash condition that we can use to bypass it.

So, in order to fix renewal orders being paid via a block checkout, I used the $order->has_status( 'checkout-draft' ) check here.

When the block checkout validates the order being paid for, it checks if it has a checkout-draft status. The order in our case will be pending-payment or failed and so it fails that condition, falling through to the cart hash logic. On trunk it fails that one too because of slight changes (eg shipping method) made during the checkout process.

In order to ensure that the renewal order is valid for payment, this PR uses the woocommerce_order_has_status filter to make sure that when a renewal order is checked if it has a checkout-draft status, it will update the cart hash ensuring it's valid for payment resumption via a block checkout.

How to test this PR

  1. Create a page with a block checkout.
  2. Make sure you have at least 2 shipping methods.
  3. Purchase a subscription.
  4. Go to the edit admin screen for the subscription.
  5. From the actions dropdown select "Create pending renewal order"
  6. Save
  7. Attempt to pay for the newly created pending renewal order.
  8. Make sure to use the checkout block page.
  9. Change the chosen shipping method
    • On trunk a new order will be created that isn't linked to the original subscription. And the pending renewal order will be left pending.
    • On this branch, the original renewal order will be paid without creating a new order.

Product impact

james-allan commented 9 months ago

@mattallan do you mind giving this another look? I had to make a couple of changes since your last review.

I've updated set_cart_hash() to be able to receive an order object - not just an ID. With the changes in https://github.com/Automattic/woocommerce-subscriptions-core/pull/498/commits/e2011014e3ca9bbb8b5abe0b89e07b309433aae1 the logic in DraftOrderTrait::is_valid_draft_order() was still failing because the cart hash was being updated on a different instance of the order.

By updating the cart hash on the same exact same order object passed to the woocommerce_order_has_status filter we can make sure that logic passes.