Skip to content

fix: only log unfetched products once - #3929

Open
fire-at-will wants to merge 9 commits into
mainfrom
only-log-unfetched-products-once
Open

fix: only log unfetched products once#3929
fire-at-will wants to merge 9 commits into
mainfrom
only-log-unfetched-products-once

Conversation

@fire-at-will

@fire-at-will fire-at-will commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

When fetching products, the SDK queries the stores twice: once for subscription products, and again for one-time purchases. The SDK logs warning messages when stores don't return all expected products. This is a good log to keep, but when querying for a one-time product, the SDK will log the "unfetched product" log when the subscription product query returns nothing, even when the one-time purchase query will be successful.

This creates confusion for humans/agents reading through the SDK logs, as the product isn't truly unfetched yet, it just hasn't been fetched yet.

Description

This PR updates how the SDK generates "product not fetched" logs so that it only logs it once per product request. Since the logs contain store-specific information, they must be generated in each stores' BillingWrapper implementation. So, to accomodate this, we've added a logUnfetchedProducts param to BillingAbstract.queryProductDetailsAsync(), and queryProductDetailsAsync() consumers specify whether or not they'd like the unfetched logs to be generated.

Testing

Added unit tests to confirm whether the logUnfetchedProducts param works as intended, and to check that the queryProductDetailsAsync() consumers are passing in the proper values.


Note

Low Risk
Behavior change is limited to logging timing and slightly more efficient product-type chaining; product fetch semantics and billing APIs stay the same aside from an optional parameter defaulting to true.

Overview
Adds a logUnfetchedProducts flag through BillingAbstract.queryProductDetailsAsync() and each store billing wrapper (Google, Amazon, Galaxy, simulated). Store handlers only emit “missing/unfetched product” warnings when that flag is true, so multi-step fetches do not warn prematurely.

PurchasesOrchestrator.getProductsOfTypes now passes logUnfetchedProducts = false on intermediate product-type queries and true only on the last query in the chain. It also stops querying the next product type when there are no remaining IDs, and only queries INAPP for IDs not already returned from SUBS. Diagnostics notFound tracking uses the remaining requested ID set at completion instead of recomputing from collected products.

OfferingsFactory uses the same pattern: SUBS query with logging disabled, INAPP follow-up with logging enabled when needed.

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

@fire-at-will fire-at-will added the pr:fix A bug fix label Aug 10, 2026
@fire-at-will
fire-at-will requested a review from a team as a code owner August 10, 2026 15:36
@fire-at-will

Copy link
Copy Markdown
Contributor Author

@cursor review

dispatch {
getProductsOfTypes(
productIds,
productIds - storeProducts.map { it.purchasingData.productId }.toSet(),

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.

This is a behavioral change: follow-up queries only query for un-fetched products. This is probably more efficient on the store side, since they'll be fetching fewer products, but it also prevents us from logging "unfetched" log messages for logs that were fetched in a previous query

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.

Indeed! I think this makes sense even if we change what I mentioned in my previous comment. I guess it could be a problem if the ids could be duplicated for different types... But I don't think that's possible?

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.

Yeah, I double checked and it's not possible for the Galaxy, Amazon, or Play Stores, so it shouldn't be a problem today, but could theoretically be possible in the future for other stores? I doubt it though, since by definition, a product identifier should uniquely identify a product

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.26%. Comparing base (feaf92d) to head (01df168).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3929      +/-   ##
==========================================
+ Coverage   81.22%   81.26%   +0.04%     
==========================================
  Files         435      435              
  Lines       17849    17868      +19     
  Branches     2790     2790              
==========================================
+ Hits        14498    14521      +23     
+ Misses       2328     2327       -1     
+ Partials     1023     1020       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fire-at-will
fire-at-will requested a review from a team August 10, 2026 17:22

@cursor cursor Bot left a comment

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.

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 6c0e968. Configure here.

@tonidero tonidero left a comment

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.

I'm just wondering if there is a cleaner way... Wdyt? Happy to chat about it of course! Also, great improvement 🙌

productType = it,
productType = productType,
productIds = productIds,
logUnfetchedProducts = isFinalQuery,

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.

Hmm so I'm wondering if it would be cleaner to just log missing products after all the calls are done, where we then know if there are any missing products after fetching all types... Then, we don't need to be passing the logging logic to each call... Wdyt?

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.

@tonidero Yeah, I like the idea! It doesn't feel great to have params in our functions just for logging logic. It would also allow us to standardize the logging logic across stores, which is a big plus. The reason I didn't go with that approach is that could lose store-specific logging info, like in this log from the Play Store:

Product not found: [redacted product ID]- Product Type: subs, Reason: PRODUCT_NOT_FOUND, Serialized doc ID:

Looking at it though, that reason may not actually be that helpful, and the Galaxy/Amazon stores don't actually include any store-specific info. If you're in favor, I'll refactor this to remove the bool params and centralize the logging outside of the stores' billing wrappers

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.

Yeah... I think that's fine TBH... maybe if there is a reason other than PRODUCT_NOT_FOUND it could be useful? In case there is some useful reason why it doesn't show I guess... But yeah, I feel it's cleaner to move most of the logic outside...

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.

Cool, thanks! I will refactor this to remove the bool params and place the logging outside of the BillingWrappers :)

dispatch {
getProductsOfTypes(
productIds,
productIds - storeProducts.map { it.purchasingData.productId }.toSet(),

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.

Indeed! I think this makes sense even if we change what I mentioned in my previous comment. I guess it could be a problem if the ids could be duplicated for different types... But I don't think that's possible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants