Skip to content

feat(checkout): CHECKOUT-10026 Connect new Google Places API behind feature flag#3142

Open
bc-maxy wants to merge 7 commits into
masterfrom
checkout-10026-wiring
Open

feat(checkout): CHECKOUT-10026 Connect new Google Places API behind feature flag#3142
bc-maxy wants to merge 7 commits into
masterfrom
checkout-10026-wiring

Conversation

@bc-maxy

@bc-maxy bc-maxy commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What/Why?

This is part II of this change: #3135, where I actually wire up this new Places API service to production, but hide new functionality behind a feature flag.

When the CHECKOUT-10026.new_google_places_api experiment is on, address autocomplete uses Google's new Places API (session-token based, AutocompleteSuggestion/Place classes) instead of the legacy API service. If the new API is denied due to permissions error (gRPC PERMISSION_DENIED), we assume that the service is not enabled for the store and silently fall back to the legacy API for the rest of the session. No visible errors for users and no visible change between old and new service - this is an infrastructure swap behind a feature flag.

NOTE: when the flag is off, there will be no change for end users, However, the code won't be byte-to-byte identical: we initialize newGooglePlacesApiServiceRef.current with new NewGooglePlacesApiService(apiKey) unconditionally on mount, even when the flag is off. It has no side effects though (no network call, no script load - the script loader inside of it is lazy), so this is just a trivial extra allocation without any functional change for end users.

CHECKOUT-10026.new_google_places_api is currently set to true in integration and staging and false for everyone in production.

Rollout/Rollback

Set CHECKOUT-10026.new_google_places_api experiment to false if the store where we rolled out the feature is affected. For issues with other stores revert the PR.

Testing

New CI tests + manual testing.

If you want to test yourself you can use these API keys in https://console.cloud.google.com/apis/credentials?project=bc-hackathon-env:

  1. Google Maps API Key test - our old API key that still has access to old Places API and has no access to new Places API. Represents old users.
  2. Maxs Places API Key Test - our new API key that has access to new Places API and no access to old Places API. Represents new users.

My tests are below:

With experiment set to false (same as current production)

Existing customers with old API keys:

Screen.Recording.2026-06-30.at.3.16.03.PM.mov

New customers with new API keys (doesn't work):

Screen.Recording.2026-06-30.at.3.18.15.PM.mov

With experiment set to true

Existing customers with old API keys (Initial 403, then fallback to the old service. Billing step will remember that we fallback and skips new api):

Screen.Recording.2026-06-30.at.3.30.18.PM.mov

New customers with new API keys (just works):

Screen.Recording.2026-06-30.at.3.24.12.PM.mov

Note

Medium Risk
Touches checkout address entry and external Google APIs with session-wide fallback behavior; mitigated by default-off production flag and extensive tests, but wrong fallback could break autocomplete for some API keys.

Overview
Checkout address line 1 autocomplete can use Google's new Places API when LaunchDarkly flag CHECKOUT-10026.new_google_places_api is on. AddressForm reads the experiment and passes isNewPlacesApiEnabled through GoogleAutocompleteFormField into GoogleAutocomplete.

Autocomplete logic moves into useGoogleAutocomplete, which prefers the new API for suggestions and place details and keeps the legacy GoogleAutocompleteService as fallback. On gRPC permission denied, a session-scoped latch marks the new API unavailable and subsequent requests use legacy only; other errors clear suggestions (suggestions) or skip onSelect (details) without latching. With the flag off, only legacy runs. Legacy shares the new API's script loader when the flag is on so Maps JS is not loaded twice.

GoogleAutocomplete is thinned to UI wiring; legacy prediction mapping lives in utils.toAutocompleteItems. GoogleAutocomplete.test.tsx covers happy path, permission fallback, transient retries, and flag-off behavior.

Reviewed by Cursor Bugbot for commit 52cbcd8. Bugbot is set up for automated code reviews on this repo. Configure here.

@bc-maxy

bc-maxy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

cursor review

Comment thread packages/core/src/app/address/googleAutocomplete/GoogleAutocomplete.tsx Outdated
Comment thread packages/core/src/app/address/googleAutocomplete/GoogleAutocomplete.tsx Outdated
@bc-maxy

bc-maxy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

cursor review

Comment thread packages/core/src/app/address/googleAutocomplete/GoogleAutocomplete.tsx Outdated
@bc-maxy

bc-maxy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c05b09f. Configure here.

const onSelectHandler = (item: AutocompleteItem) => {
const isUsingLegacyApi = () => !isNewPlacesApiEnabled || newGooglePlacesApiState.isUnavailable;

const finalizeSelection = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like many lines are added to this file.
Is it possible to move them out as separate file(s) to keep this file small?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The rest of the PR looks good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking it @bc-peng 🙏 I made changes here: 33ca5c2. Is this what you had in mind?

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 33ca5c2. Configure here.

Comment thread packages/core/src/app/address/googleAutocomplete/useGoogleAutocomplete.ts Outdated
Comment on lines +81 to +97
const fetchSuggestionsWithLegacyApi = (input: string): void => {
const service = googleAutocompleteServiceRef.current;

if (!service) return;

service.getAutocompleteService().then((autocompleteService) => {
autocompleteService.getPlacePredictions(
{ input, types: types || ['geocode'], componentRestrictions },
(results) => {
setItems(toAutocompleteItems(results ?? undefined));
},
);
});
};

const fetchSuggestionsWithNewApi = (input: string): void => {
const service = newGooglePlacesApiServiceRef.current;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not blocking, just a thought. Does it makes sense to have 2 classes or similar for old and new aPI and then based on the isUsingLegacyApi we use the correct class in the hook to avoid naming such as fetchSuggestionsWithNewApi/fetchSuggestionsWithLegacyApi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking @richapatel1510 🙏 I like this idea. However, I tried it as two classes and initialization and updates ended up more verbose (constructors, and passing render-scoped values like types/fields in on every call) without a real readability gain. Instead, I grouped the existing functions into two objects (legacyApi/newApi), which in my mind gets us the similar naming clarity you were after, without the class overhead. What do you think about that?

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.

3 participants