Skip to content

Paywall sheets hard-filter packages by product tier - #92

Open
GianniCarlo wants to merge 1 commit into
developfrom
fix/paywall-product-tier-filter
Open

GianniCarlo wants to merge 1 commit into
developfrom
fix/paywall-product-tier-filter

Conversation

@GianniCarlo

Copy link
Copy Markdown
Contributor

The user report behind this

A user (rprimus@) bought a subscription and "can't stream their books." Debug file + prod data tell the story:

  1. 17:30 UTC — INITIAL_PURCHASE of bookplayer.lite.yearly under an anonymous RC ID, from the Android 1.0.0 Pro-branded paywall: that build listed every package of the monthly/yearly offerings, and the dashboard offerings carry lite packages alongside pro — so the "cheap yearly" they picked was LITE.
  2. 17:31–19:42 — they imported books eight times. LITE = cloud DB data only; the API refuses S3 upload-URL minting (requireS3Upload = PRO), and their 1.0.0 build predates the client-side isPro() upload gate, so uploads queued and stalled forever. Every one of their 12 library rows sits at synced=false — no audio ever reached the cloud, so nothing can stream.

What this PR fixes

resolvePackages trusted offerings to be tier-pure — dashboard config the code doesn't control (and which is mixed today). It now hard-filters by product id keyword after every branch: the Pro sheet can only sell *pro* products, LitePaywallSheet only *lite*, regardless of what the offerings contain. Unit tests pin both directions against a mixed offering, plus the Google product:basePlan id form.

Not in this PR

  • The stranded-upload UX for LITE users on current builds (uploads are silently skipped; the book appears on other devices with no audio and no explanation) — the deferred lite-paywall/upsell follow-up.
  • Support remediation for the affected user (refund/crossgrade — see support notes).

🤖 Generated with Claude Code

https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp

The Pro sheet's package list came straight from the monthly/yearly
RevenueCat offerings (with an unfiltered current-offering fallback),
trusting dashboard config to keep tiers separate. It hasn't: a 1.0.0
user bought bookplayer.lite.yearly from the Pro-branded sheet, then
spent a day re-importing books whose audio a LITE tier can never
upload or stream (server gates S3 to PRO; every library row stuck at
synced=false).

resolvePackages now hard-filters by PRODUCT id keyword ("pro"/"lite")
after every branch — the tier a sheet sells no longer depends on
offering contents. Pinned by unit tests against a mixed offering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNhXd8EkXcrjBVYLHm5ZSp
@github-actions

Copy link
Copy Markdown

✅ Claude PR Review — PASS

Fixes a subscription-tier correctness bug where the Pro paywall could sell a lite package because resolvePackages trusted RevenueCat offerings to be tier-pure. The PR adds a productKeyword hard-filter (filterPackagesByProduct) applied after every offering-lookup branch, so the Pro sheet only shows *pro* products and LitePaywallSheet only *lite*. The change is display/selection-side only and does not touch entitlement-granting logic, and it ships with a co-located unit test covering both tiers against a mixed offering plus the Google product:basePlan id form. Scope is limited to :app paywall UI with no localization, accessibility, or module-boundary impact.

Findings: no findings

Model claude-opus-4-8 · run log · 0 new · 0 carried over · 0 resolved · advisory (a human should still review). Duplicate findings are de-duplicated and stale ones auto-resolved across pushes.

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.

1 participant