Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Use Cart's needs payment check instead of Order's #4955

Merged
merged 4 commits into from
Oct 26, 2021

Conversation

senadir
Copy link
Member

@senadir senadir commented Oct 18, 2021

#4854 Removed a check to see if the order needs payment before setting the payment ID on the order. This caused a regression (#4948) In which free orders didn't pass because there are no payment method.

Reverting #4854 fixes #4948 but regresses #4853 , so this PR reverts it and fixes the original issue in both cases.

What happened?

  • We show the payment setup on Checkout based on $cart->needs_payment(). Plugins (like WC Subscriptions) hooks into needs_payment and sets it to true based on conditions like trial and such. However, when setting a payment method, we check $order->needs_payment.

WooCommerce Subscriptions does hook into $order->needs_payment but that didn't work, because that value is false, causing the subscription recurring payment method to be Manual renewal even if a payment was still captured.

This PR changes that check into $cart->needs_payment instead, why?

I believe, at that moment in the checkout lifecycle, using $cart->needs_payment makes more sense than order given that we still didn't submit the order, and that we use that exact check to decide if we should capture payment or not, so they should be consistent.

Question

There are several other instances in Routes/Checkout.php that uses $order->needs_payment, should I evaluate them to see if they should be $cart->needs_payment or not? In total, there are two other instances left.

Fixes #4948
Reverts #4854
Fixes #4853

Testing

Automated Tests

  • Changes in this PR are covered by Automated Tests.
    • Unit tests
    • E2E tests

Manual Testing

We're testing two issues here, #4948, and #4853

#4948

  1. Create a free order ($0), Add to Cart.
  2. Make sure you have a free shipping method or it's a virtual product.
  3. Visit Checkout, Payment and Express Payment steps shouldn't render.
  4. Place order, you should not see an error.

#4853

  1. Install WC Subscriptions and enable a payment gateway like Stripe or WCPay.
  2. Create a product with a trial.
  3. Visit Checkout, Payment and Express Payment steps should render.
  4. Place order, you should not see an error.
  5. Visit wp-admin->WooCommerce->Subscriptions
  6. See the latest created subscription, it should have Via Credit Card or any payment gateway you did, not vial Manual Renewals.
    image

Changelog

Fix a bug in free orders and trial subscription products.

@senadir senadir added type: bug The issue/PR concerns a confirmed bug. block: checkout Issues related to the checkout block. labels Oct 18, 2021
@senadir senadir self-assigned this Oct 18, 2021
@rubikuserbot rubikuserbot requested review from a team and nielslange and removed request for a team October 18, 2021 11:37
@senadir
Copy link
Member Author

senadir commented Oct 18, 2021

@mikejolley to give this an eye and answer the question above.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2021

Size Change: 0 B

Total Size: 1.1 MB

ℹ️ View Unchanged
Filename Size
build/active-filters-frontend.js 8.33 kB
build/active-filters.js 8.01 kB
build/all-products-frontend.js 23.3 kB
build/all-products.js 37.8 kB
build/all-reviews.js 9.57 kB
build/atomic-block-components/add-to-cart--atomic-block-components/button--atomic-block-components/image---a7e2bb9b.js 3.19 kB
build/atomic-block-components/add-to-cart--atomic-block-components/button.js 1.82 kB
build/atomic-block-components/add-to-cart--atomic-block-components/image--atomic-block-components/title.js 332 B
build/atomic-block-components/add-to-cart-frontend.js 8.51 kB
build/atomic-block-components/add-to-cart.js 7.84 kB
build/atomic-block-components/button-frontend.js 1.74 kB
build/atomic-block-components/button.js 874 B
build/atomic-block-components/category-list-frontend.js 464 B
build/atomic-block-components/category-list.js 470 B
build/atomic-block-components/image-frontend.js 1.88 kB
build/atomic-block-components/image.js 1.35 kB
build/atomic-block-components/price-frontend.js 2.14 kB
build/atomic-block-components/price.js 2.11 kB
build/atomic-block-components/rating-frontend.js 561 B
build/atomic-block-components/rating.js 566 B
build/atomic-block-components/sale-badge-frontend.js 859 B
build/atomic-block-components/sale-badge.js 868 B
build/atomic-block-components/sku-frontend.js 392 B
build/atomic-block-components/sku.js 393 B
build/atomic-block-components/stock-indicator-frontend.js 611 B
build/atomic-block-components/stock-indicator.js 611 B
build/atomic-block-components/summary-frontend.js 908 B
build/atomic-block-components/summary.js 911 B
build/atomic-block-components/tag-list-frontend.js 466 B
build/atomic-block-components/tag-list.js 471 B
build/atomic-block-components/title-frontend.js 1.55 kB
build/atomic-block-components/title.js 1.38 kB
build/attribute-filter-frontend.js 18.3 kB
build/attribute-filter.js 12.1 kB
build/blocks-checkout.js 21.1 kB
build/cart-blocks/accepted-payment-methods-frontend.js 1.39 kB
build/cart-blocks/checkout-button-frontend.js 1.24 kB
build/cart-blocks/empty-cart-frontend.js 349 B
build/cart-blocks/express-payment--checkout-blocks/express-payment--checkout-blocks/payment-frontend.js 4.72 kB
build/cart-blocks/express-payment-frontend.js 1.59 kB
build/cart-blocks/filled-cart-frontend.js 808 B
build/cart-blocks/items-frontend.js 303 B
build/cart-blocks/line-items-frontend.js 5.81 kB
build/cart-blocks/order-summary--checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js 3.69 kB
build/cart-blocks/order-summary-frontend.js 7.41 kB
build/cart-blocks/totals-frontend.js 322 B
build/cart-frontend.js 52.8 kB
build/cart.js 50.5 kB
build/checkout-blocks/actions-frontend.js 1.51 kB
build/checkout-blocks/billing-address-frontend.js 2.66 kB
build/checkout-blocks/contact-information-frontend.js 3.9 kB
build/checkout-blocks/express-payment-frontend.js 1.93 kB
build/checkout-blocks/fields-frontend.js 347 B
build/checkout-blocks/order-note-frontend.js 1.56 kB
build/checkout-blocks/order-summary-frontend.js 12.8 kB
build/checkout-blocks/payment-frontend.js 4.58 kB
build/checkout-blocks/shipping-address-frontend.js 3.05 kB
build/checkout-blocks/shipping-methods-frontend.js 5.56 kB
build/checkout-blocks/terms-frontend.js 1.65 kB
build/checkout-blocks/totals-frontend.js 330 B
build/checkout-frontend.js 55.1 kB
build/checkout.js 54 kB
build/featured-category.js 7.74 kB
build/featured-product.js 9.43 kB
build/handpicked-products.js 6.26 kB
build/mini-cart-component-frontend.js 36.9 kB
build/mini-cart-frontend.js 2.32 kB
build/mini-cart.js 5.29 kB
build/price-filter-frontend.js 14.4 kB
build/price-filter.js 9.66 kB
build/price-format.js 1.37 kB
build/product-best-sellers.js 6.63 kB
build/product-categories.js 3.37 kB
build/product-category.js 7.49 kB
build/product-new.js 6.78 kB
build/product-on-sale.js 7.13 kB
build/product-search.js 2.68 kB
build/product-tag.js 6.59 kB
build/product-top-rated.js 6.74 kB
build/products-by-attribute.js 7.71 kB
build/reviews-by-category.js 11.5 kB
build/reviews-by-product.js 13 kB
build/reviews-frontend.js 8.96 kB
build/single-product-frontend.js 26.5 kB
build/single-product.js 9.77 kB
build/stock-filter-frontend.js 8.76 kB
build/stock-filter.js 7.81 kB
build/vendors--atomic-block-components/add-to-cart--cart-blocks/order-summary--checkout-blocks/billing-ad--c5eb4dcd-frontend.js 16.1 kB
build/vendors--atomic-block-components/add-to-cart-frontend.js 4.78 kB
build/vendors--atomic-block-components/price--cart-blocks/line-items--cart-blocks/order-summary--checkout--8a3571de-frontend.js 5.71 kB
build/vendors--cart-blocks/line-items--checkout-blocks/order-summary-frontend.js 3.1 kB
build/vendors--cart-blocks/order-summary--checkout-blocks/billing-address--checkout-blocks/order-summary---eb4d2cec-frontend.js 5.02 kB
build/wc-blocks-data.js 11.3 kB
build/wc-blocks-editor-style-rtl.css 15.6 kB
build/wc-blocks-editor-style.css 15.6 kB
build/wc-blocks-google-analytics.js 1.98 kB
build/wc-blocks-middleware.js 1.47 kB
build/wc-blocks-registry.js 3.71 kB
build/wc-blocks-shared-context.js 1.54 kB
build/wc-blocks-shared-hocs.js 1.75 kB
build/wc-blocks-style-rtl.css 20.6 kB
build/wc-blocks-style.css 20.6 kB
build/wc-blocks-vendors-style-rtl.css 1.37 kB
build/wc-blocks-vendors-style.css 1.37 kB
build/wc-blocks-vendors.js 254 kB
build/wc-blocks.js 3.49 kB
build/wc-payment-method-bacs.js 806 B
build/wc-payment-method-cheque.js 806 B
build/wc-payment-method-cod.js 898 B
build/wc-payment-method-paypal.js 839 B
build/wc-payment-method-stripe.js 12.2 kB
build/wc-settings.js 2.91 kB

compressed-size-action

@mikejolley
Copy link
Member

@senadir Pardon me if missing the point here, but the error in #4948 indicates that we're attempting to take a payment when no payment is required? Setting payment method blank (changed in this PR) is ok. Why do we need an extra check there?

I see our usage as follows:

$cart->needs_payment() used by the cart endpoint. This would be useful before checkout, for example, to determine if express payment methods are shown.

$order->needs_payment() used by the checkout endpoint. This would be preferred once the draft order has been created, and this will be true or false based on cost.

I don't think we should mix these up between the 2 endpoints.

WooCommerce Subscriptions does hook into $order->needs_payment but that didn't work, because that value is false, causing the subscription recurring payment method to be Manual renewal even if a payment was still captured.

https://github.com/woocommerce/woocommerce-subscriptions/blob/d0f64e5e23b8fcc5bdb2fd422a3e7da6a9d76a78/includes/class-wc-subscriptions-order.php#L658-L704

Is there a bug in this function? It would only bail if true. Why would it return false in this scenario?

@senadir
Copy link
Member Author

senadir commented Oct 18, 2021

$cart->needs_payment() used by the cart endpoint. This would be useful before checkout, for example, to determine if express payment methods are shown.

$order->needs_payment() used by the checkout endpoint. This would be preferred once the draft order has been created, and this will be true or false based on cost.

My point around using the cart's method was because we rely on to decide if we want to capture a payment on Checkout (Payment block, Express Payment block). So having a parity between if we decide to capture payment and if we should set a payment method.

Do you think all plugins who accept zeored orders would hook into both woocommerce_cart_needs_payment an woocommerce_order_needs_payment? The issue above is one instance of why those conditions might be different. Up to Checkout block, orders weren't created untill you hit submit, meaning woocommerce_order_needs_payment was in a different flow than woocommerce_cart_needs_payment.

@mikejolley
Copy link
Member

My point around using the cart's method was because we rely on to decide if we want to capture a payment on Checkout (Payment block, Express Payment block). So having a parity between if we decide to capture payment and if we should set a payment method.

The checkout one could probably switch if we have an order by that point.

Do you think all plugins who accept zeored orders would hook into both woocommerce_cart_needs_payment an woocommerce_order_needs_payment?

I don't have an answer for this but I believe you'd need both..there are flows in core when you'd want both, e.g. paying for an order from My Account.

@senadir
Copy link
Member Author

senadir commented Oct 18, 2021

The checkout one could probably switch if we have an order by that point.

I tried doing that, we can do one of two:

  • Add $order->needs_payment to /store/checkout, however, this won't work as /store/checkout won't refresh if you update shipping method, add coupons, or any other amount changing variables, would cause a degradation of quality if we always refresh checkout an cart, batching can solve this somehow.
  • Make the needs_payment value in /store/cart conditional so it uses either $order->needs_payment or $cart->needs_payment depending on context, seems to be the hardest option.

I don't have an answer for this but I believe you'd need both..there are flows in core when you'd want both, e.g. paying for an order from My Account.

Should they be assumed to be similar is the issue here, meaning would it always be $cart->needs_payment() === $order->needs_payment() or else we need to switch our Checkout conditional rendering to use $order->needs_payment()

@mikejolley
Copy link
Member

Ok, I'm starting to understand this a bit more after seeing the diff in #4854

If we leave the cart/order checks alone and assume that cart data is synced to the order before these checks run and just focus on the change in #4854, that PR is problematic because it assumes a payment method is sent with all requests when I believe it's optional if no payment is required.

To solve the original "Fix free trial subscriptions orders missing payment method meta" issue we can handle it in 2 ways:

  1. Ensure subs filters on order needs payment as well as cart needs payment so this code still runs even if an order has 0 total
  2. In subs, use one of the other filters to set the payment_id on the order if total is 0

Do you agree with that @senadir?

This minimises the changes we're making our side. We should revert #4854 because get_request_payment_method_id runs validation.

@senadir
Copy link
Member Author

senadir commented Oct 18, 2021

2 doesn't seem like a possible solution, right? I don't think subs has access to the payment id if it's not saved on the order.

Ensure subs filters on order needs payment as well as cart needs payment so this code still runs even if an order has 0 total

This could be possible but it might be a bit risky of a change, but we this is one possible solution, however, it only fixes it for Subs, the issue if cart->needs_payment !== order->needs_payment would still persist.

@mikejolley
Copy link
Member

@senadir only in the client though? Whenever the checkout endpoint is hit (so when you make payment) it syncs with cart: https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/StoreApi/Routes/Checkout.php#L370-L382

When else would they differ?

@senadir
Copy link
Member Author

senadir commented Oct 19, 2021

Whenever the checkout endpoint is hit (so when you make payment) it syncs with cart

It only syncs the content, so the core logic of cart->needs_payment and order->needs_payment would be the same, but this doesn't account for filters and if plugins treats those two differently, which is the issue happening with WooCommerce Subscriptions for example. Going with this assumption would require us to maybe document this change (that cart->needs_payment and order->needs_payment are used interchangeably in Checkout block and therefore should have the same logic)

@mikejolley
Copy link
Member

2 doesn't seem like a possible solution, right? I don't think subs has access to the payment id if it's not saved on the order.

__experimental_woocommerce_blocks_checkout_update_order_from_request has the request data and the order so could be used...

If we are leaving the code as is with the change that saved payment method for all requests, then we need to remove the validation inside update_order_from_request, so perhaps:

$this->order->set_payment_method( $this->get_request_payment_method( $request ) );

to:

$this->order->set_payment_method( $request['payment_method'] ?? '' );

OR remove the validation inside get_request_payment_method_id if the payment method is empty.

Then we just need to ensure validation runs before the actual payment is attempted. This should satisfy both use case?

Copy link
Member

@nielslange nielslange left a comment

Choose a reason for hiding this comment

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

I checked both manual test cases against the current trunk and against your PR and your fix works as expected.

As for the points @mikejolley mentioned, I cannot say too much about this yet, so I'd leave that conversation up to you, @senadir. In case this PR remains as it is, feel free to merge it.

@senadir
Copy link
Member Author

senadir commented Oct 21, 2021

@mikejolley using

$this->order->set_payment_method( $request['payment_method'] ?? '' );

Worked like a charm!

@senadir senadir requested a review from mikejolley October 21, 2021 13:58
@senadir senadir added the status: blocker Used on issues or pulls that block work from being released. label Oct 25, 2021
src/StoreApi/Routes/Checkout.php Outdated Show resolved Hide resolved
@senadir senadir force-pushed the fix/broken-free-orders branch from 4141e57 to 31c9da8 Compare October 26, 2021 09:27
@senadir senadir merged commit ed517bb into trunk Oct 26, 2021
@senadir senadir deleted the fix/broken-free-orders branch October 26, 2021 09:42
jonny-bull pushed a commit to jonny-bull/woocommerce-gutenberg-products-block that referenced this pull request Dec 14, 2021
* Revert "Fix free orders missing payment method (woocommerce#4854)"

This reverts commit 90e513b.

* use Cart needs payment instead of Order needs payment

* switch to nullish coalescing

* remove extra line
jonny-bull pushed a commit to jonny-bull/woocommerce-gutenberg-products-block that referenced this pull request Dec 16, 2021
* Revert "Fix free orders missing payment method (woocommerce#4854)"

This reverts commit 90e513b.

* use Cart needs payment instead of Order needs payment

* switch to nullish coalescing

* remove extra line
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
block: checkout Issues related to the checkout block. status: blocker Used on issues or pulls that block work from being released. type: bug The issue/PR concerns a confirmed bug.
Projects
None yet
3 participants