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

Fix Bug: Prevent Multi-Currency Conversion During REST API Request #9477

Merged

Conversation

lovo-h
Copy link
Contributor

@lovo-h lovo-h commented Sep 20, 2024

Fixes #9181

Changes proposed in this Pull Request

The changes in this PR ensure that the REST API requests are idempotent. This is accomplished by preventing multi-currency from converting the currency and price based on the user's session. Previously, if a shopper updated their frontend currency with a currency-switcher block, the REST API requests from the frontend would also be unintentionally impacted.

Testing instructions

Replicate bug: Updating the frontend currency impacts the REST API requests

  • Switch to the develop branch.
  • As a merchant, enable multi-currency in WooPayments and add at least one extra currency (e.g. GBP (default) + EUR).
  • Create a new product and note the price in the default currency (e.g. GBP) and the ID.
  • Add a "Currency Switcher" block widget ( Appearance > Widgets).
  • As a shopper, navigate to the merchant shop and use the "Currency Switcher" to switch to the non-default currency (EUR).
  • Make a REST API request to https://merchant.com/wp-json/wc/v2/products/<product_id>?consumer_key=<key>&consumer_secret=<secret> in the browser's search bar.
  • Confirm that the price has changed and that the currency code reads &euro;.

Confirm fix: Updating the frontend currency does not impact the REST API requests

  • Switch to the fix/9181-unintended-mccy-conversion-during-rest-api-request branch.
  • As a merchant, enable multi-currency in WooPayments and add at least one extra currency (e.g. GBP (default) + EUR).
  • Create a new product and note the price in the default currency (e.g. GBP) and the ID.
  • Add a "Currency Switcher" block widget ( Appearance > Widgets).
  • As a shopper, navigate to the merchant shop and use the "Currency Switcher" to switch to the non-default currency (EUR).
  • Make a REST API request to https://merchant.com/wp-json/wc/v2/products/<product_id>?consumer_key=<key>&consumer_secret=<secret> in the browser's search bar.
  • Confirm that the price has not changed and that the currency code reads &pound; (default).
  • Confirm that the correct price and currency appear in the price_html field.

Ensure compatibility: Updating the frontend currency does not impact the REST API requests for compatible plugins

  • Switch to the fix/9181-unintended-mccy-conversion-during-rest-api-request branch.
  • As a merchant, enable multi-currency in WooPayments and add at least one extra currency (e.g. GBP (default) + EUR).
  • Using a compatible plugin from this list:
    • Create a new product (booking, subscription, etc.) and note the price in the default currency (e.g. GBP) and the ID.
  • Add a "Currency Switcher" block widget ( Appearance > Widgets).
  • As a shopper, navigate to the merchant shop and use the "Currency Switcher" to switch to the non-default currency (EUR).
  • Make a REST API request to https://merchant.com/wp-json/wc/v2/products/<product_id>?consumer_key=<key>&consumer_secret=<secret> in the browser's search bar.
  • Confirm that the price has not changed and that the currency code reads &pound; (default).
  • Confirm that the correct price and currency appear in the price_html field.

Ensure unit tests all pass

  • Run the following command npm run test:php.
  • Ensure all tests pass.

  • 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

@lovo-h lovo-h requested a review from a team September 20, 2024 20:39
@lovo-h lovo-h self-assigned this Sep 20, 2024
@lovo-h lovo-h requested review from bborman22 and removed request for a team September 20, 2024 20:39
@botwoo
Copy link
Collaborator

botwoo commented Sep 20, 2024

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 9477 or branch name fix/9181-unintended-mccy-conversion-during-rest-api-request 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: ff76ccf
  • Build time: 2024-09-26 17:10:47 UTC

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

Copy link
Contributor

github-actions bot commented Sep 20, 2024

Size Change: 0 B

Total Size: 1.33 MB

ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.08 kB
release/woocommerce-payments/assets/css/admin.rtl.css 1.08 kB
release/woocommerce-payments/assets/css/success.css 173 B
release/woocommerce-payments/assets/css/success.rtl.css 173 B
release/woocommerce-payments/dist/blocks-checkout-rtl.css 2.64 kB
release/woocommerce-payments/dist/blocks-checkout.css 2.64 kB
release/woocommerce-payments/dist/blocks-checkout.js 66.6 kB
release/woocommerce-payments/dist/cart-block.js 16.2 kB
release/woocommerce-payments/dist/cart.js 5.73 kB
release/woocommerce-payments/dist/checkout-rtl.css 927 B
release/woocommerce-payments/dist/checkout.css 927 B
release/woocommerce-payments/dist/checkout.js 32.4 kB
release/woocommerce-payments/dist/express-checkout-rtl.css 230 B
release/woocommerce-payments/dist/express-checkout.css 230 B
release/woocommerce-payments/dist/express-checkout.js 14.2 kB
release/woocommerce-payments/dist/frontend-tracks.js 858 B
release/woocommerce-payments/dist/index-rtl.css 39.3 kB
release/woocommerce-payments/dist/index.css 39.2 kB
release/woocommerce-payments/dist/index.js 302 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.08 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 3.41 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 60.6 kB
release/woocommerce-payments/dist/multi-currency.css 3.41 kB
release/woocommerce-payments/dist/multi-currency.js 55.6 kB
release/woocommerce-payments/dist/order-rtl.css 730 B
release/woocommerce-payments/dist/order.css 730 B
release/woocommerce-payments/dist/order.js 42.7 kB
release/woocommerce-payments/dist/payment-gateways-rtl.css 1.35 kB
release/woocommerce-payments/dist/payment-gateways.css 1.35 kB
release/woocommerce-payments/dist/payment-gateways.js 39.3 kB
release/woocommerce-payments/dist/payment-request-rtl.css 230 B
release/woocommerce-payments/dist/payment-request.css 230 B
release/woocommerce-payments/dist/payment-request.js 13.6 kB
release/woocommerce-payments/dist/plugins-page-rtl.css 386 B
release/woocommerce-payments/dist/plugins-page.css 386 B
release/woocommerce-payments/dist/plugins-page.js 20.1 kB
release/woocommerce-payments/dist/product-details-rtl.css 433 B
release/woocommerce-payments/dist/product-details.css 436 B
release/woocommerce-payments/dist/product-details.js 11.5 kB
release/woocommerce-payments/dist/settings-rtl.css 11.6 kB
release/woocommerce-payments/dist/settings.css 11.5 kB
release/woocommerce-payments/dist/settings.js 224 kB
release/woocommerce-payments/dist/subscription-edit-page.js 703 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 20.2 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 730 B
release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 19.3 kB
release/woocommerce-payments/dist/tokenized-payment-request-rtl.css 230 B
release/woocommerce-payments/dist/tokenized-payment-request.css 230 B
release/woocommerce-payments/dist/tokenized-payment-request.js 14.4 kB
release/woocommerce-payments/dist/tos-rtl.css 235 B
release/woocommerce-payments/dist/tos.css 235 B
release/woocommerce-payments/dist/tos.js 21.8 kB
release/woocommerce-payments/dist/woopay-direct-checkout.js 6.14 kB
release/woocommerce-payments/dist/woopay-express-button.js 24 kB
release/woocommerce-payments/dist/woopay-rtl.css 4.54 kB
release/woocommerce-payments/dist/woopay.css 4.51 kB
release/woocommerce-payments/dist/woopay.js 71.3 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 625 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 814 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/jetpack-script-data.js 735 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/react-jsx-runtime.js 553 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.02 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/script-data.js 69 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/babel.config.js 163 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.css 2.45 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.js 14.2 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.rtl.css 2.45 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.js 280 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.rtl.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.css 625 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.js 333 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.rtl.css 626 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-users.js 417 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js 584 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.css 215 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css 721 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.js 412 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-users.js 621 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.04 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-empty-state.css 294 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 408 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.59 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 301 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 746 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 574 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 414 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 543 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 9.4 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.78 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.84 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 545 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.52 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.7 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 507 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 358 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 428 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 782 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.09 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.26 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 391 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.04 kB

compressed-size-action

Copy link
Contributor

@bborman22 bborman22 left a comment

Choose a reason for hiding this comment

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

This tested well for the issue being described! As we've discussed, it's a bit challenging to determine what the correct approach is here for the REST API, but I feel this approach is the best balance as it still allows currency conversion if desired, but makes it explicit now.

Also as we discussed this could cause breaking changes if there are uses of the REST API that are depending on the currency conversion today, but at least this approach provides an easy update path to get back to the functionality with the currency parameter.

It was already highlighted in the Slack thread by @frosso that this still has a side effect of changing the "shopper's" currency in their meta. The concern raised there was that if we're changing it on a request, we should also honor it on a request. This presents a challenge just given how the functionality is designed today. One possible alternative is to make use of the override_selected_currency filter when the currency parameter is provided as well so that it doesn't pull from the user meta (and thus doesn't have to set it during a REST API request).

Let me know what you think of an approach like that, and @frosso feel free to weigh in here as well!

@lovo-h lovo-h requested a review from bborman22 September 25, 2024 19:12
@lovo-h
Copy link
Contributor Author

lovo-h commented Sep 26, 2024

It was already highlighted in the Slack thread by @frosso that this still has a side effect of changing the "shopper's" currency in their meta. The concern raised there was that if we're changing it on a request, we should also honor it on a request. This presents a challenge just given how the functionality is designed today. One possible alternative is to make use of the override_selected_currency filter when the currency parameter is provided as well so that it doesn't pull from the user meta (and thus doesn't have to set it during a REST API request).

Let me know what you think of an approach like that, and @frosso feel free to weigh in here as well!

Good call. Fixed in 398610c.

Copy link
Contributor

@bborman22 bborman22 left a comment

Choose a reason for hiding this comment

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

Thanks for making those changes @lovo-h! This tests well for me now with no side effects. I confirmed that I can pass in the currency parameter and get the correct price from the REST API and it doesn't update the authenticated user's currency meta. I also confirmed that no currency parameter always returns in the default currency as expected.

I wanted to do some quick regression testing with the Store API side of it and found one issue that is pre-existing, but causes a breaking issue with this PR. In short /batch requests on the Store API will return false on \WC_Payments_Utils::is_store_api_request(). This is because the batch path isn't included in STORE_API_ROUTE_PATTERNS that are compared. This can be observed in the following test:

  • Add an item to the cart
  • Visit the cart block
  • Change currency to non-default currency (I had default of GBP and selected JPY)
  • Note that the cart prices are in JPY
  • Now update the quantity on an item and this should trigger a batch request
  • Note that the prices now go back to GBP

I'm unsure the best approach here (add batch to the list or handle this check separately?) my only concern with adding batch to the list is if that has implications outside of this flow that would trigger a WooPay flow. I don't believe that would be a problem as all of our WooPay checks are combined with a is_request_from_woopay check. I also see the is_store_api_request is used in multiple places and surprised this hasn't come up in other areas so wanted to get a double check on my thoughts here?

@frosso
Copy link
Contributor

frosso commented Sep 26, 2024

@bborman22 that /batch endpoint is a great callout!
I believe the original is_store_api_request() utility was initially added for WooPay's specific case.
But I also believe that WooPay calls aren't coming in batches on the merchant's site.

I don't know if there's an easy fix to that detection mechanism 😬 it could be an enhancement 🤷

@lovo-h
Copy link
Contributor Author

lovo-h commented Sep 26, 2024

I wanted to do some quick regression testing with the Store API side of it and found one issue that is pre-existing, but causes a breaking issue with this PR. In short /batch requests on the Store API will return false on \WC_Payments_Utils::is_store_api_request(). This is because the batch path isn't included in STORE_API_ROUTE_PATTERNS that are compared. This can be observed in the following test:

  • Add an item to the cart
  • Visit the cart block
  • Change currency to non-default currency (I had default of GBP and selected JPY)
  • Note that the cart prices are in JPY
  • Now update the quantity on an item and this should trigger a batch request
  • Note that the prices now go back to GBP

Good catch. I was able to reproduce the issue.

I'm unsure the best approach here (add batch to the list or handle this check separately?) my only concern with adding batch to the list is if that has implications outside of this flow that would trigger a WooPay flow.

Agreed. I don't think we should add the /batch request to the list of STORE_API_ROUTE_PATTERNS because it could cause unintended side effects.

In order to keep things narrow, we could make an exception for when we apply the patch. That is, when it's not a /batch request. In other words, we could do something like the following here:

if ( ! \WC_Payments_Utils::is_store_batch_request() && ! \WC_Payments_Utils::is_store_api_request() && WC()->is_rest_api_request() ) {
  // Apply patch.
}

This would maintain the same functionality as before and apply the patch on the REST API requests, as needed.

What are your thoughts on this @bborman22 @frosso?

Copy link
Contributor

@bborman22 bborman22 left a comment

Choose a reason for hiding this comment

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

I was able to test this again with the above scenarios and confirmed that Store API (batch really) requests now keep the correct currency and the logic is being applied correctly there. I also finished the last test case by testing the REST API with a subscription product and a bookings product and all the pricing behaved as expected by returning in default currency unless the currency parameter was provided.

Thanks for all your effort on getting this here @lovo-h!

LGTM!

@lovo-h lovo-h added this pull request to the merge queue Sep 27, 2024
Merged via the queue into develop with commit 69d979c Sep 27, 2024
25 checks passed
@lovo-h lovo-h deleted the fix/9181-unintended-mccy-conversion-during-rest-api-request branch September 27, 2024 14:40
ricardo added a commit that referenced this pull request Oct 15, 2024
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.

Possible bug with Store API requests and MultiCurrency
4 participants