Skip to content

fix: harden store API follow-up - #300

Merged
hyochan merged 7 commits into
mainfrom
fix/store-api-followup
Aug 9, 2026
Merged

hyochan merged 7 commits into
mainfrom
fix/store-api-followup

Conversation

@hyochan

@hyochan hyochan commented Aug 9, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Preserve externally redeemed StoreKit win-back offers through validation failures and reserve each promoted intent for only one purchase attempt.
  • Make Horizon Activity binding lifecycle-aware and owner-scoped while simplifying the Android example to one shared store owner.
  • Synchronize the Advanced Commerce availability contract, document the coordinated patch train, and make KISS/SSOT review rules part of the agent guidance SSOT.

Changes

Store contracts and Apple

  • Clarify AdvancedCommerceInfoIOS.period availability and regenerate all synchronized SDK types.
  • Add an exclusive, generation-safe promoted-offer lease state machine.
  • Release reservations on local validation or StoreKit errors and consume them only after StoreKit returns a purchase result.
  • Update promoted-purchase examples to distinguish subscriptions from one-time products.

Google and Horizon

  • Bind foreground Activities through owner-scoped lifecycle state.
  • Add an Activity-aware ViewModel initialization path for Horizon.
  • Keep one Activity-owned example store and remove per-screen fallback stores and cleanup scopes.
  • Add lifecycle, binding arbitration, and Activity resolution regression tests.

Guidance and documentation

  • Define KISS/SSOT release criteria once in knowledge/internal/03-coding-style.md.
  • Route AGENTS, Claude commands, and Codex skills to that canonical section.
  • Add the expected coordinated patch release entry without rewriting the published base release.

Test plan

  • Apple Swift tests: 134 passed
  • Apple XCFramework build: iOS, iOS Simulator, and Mac Catalyst passed
  • Google Play, Horizon, and Amazon shared tests and example Kotlin compilation passed with caches disabled
  • Android Play, Horizon, and Amazon example APK assemblies passed
  • GraphQL tests, canonical generation, and generated-sync verification passed
  • KMP aggregate build/tests and all three example flavors passed
  • MAUI Apple and Android binding/platform build rows passed
  • Docs typecheck/build, Prettier, audit:docs, audit:parity, and audit:release-state passed
  • Two clean self-review snapshots were recorded 336 seconds apart

Preview

No new interactive UI is introduced. The visible documentation changes are release text and language code samples; the build, typecheck, generated-sync, and audit results above are the applicable proof.

Summary by CodeRabbit

  • New Features

    • Improved Apple promoted-purchase handling so offers remain reserved during validation and are consumed only after a successful purchase.
    • Improved Android Activity lifecycle handling for more reliable billing connections across screens and configuration changes.
    • Updated promoted-product examples to support one-time purchases and subscriptions.
  • Documentation

    • Documented subscription-period availability and supported Apple platform versions across SDKs.
    • Added release guidance for OpenIAP Spec 3.1.0 and related SDK updates.
    • Improved generated C# documentation formatting for multiline descriptions.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a7be446-6305-4098-9bb1-a90a4ca33e8e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds canonical KISS/SSOT review rules, interval-based review-self behavior, Apple promoted-purchase offer leasing, lifecycle-aware Google Activity binding, updated promoted-product examples, release notes, C# documentation generation, and subscription-period availability documentation.

Changes

Canonical review guidance

Layer / File(s) Summary
Canonical rules and review workflow
.claude/commands/*, .claude/skills/*, .codex/skills/*, AGENTS.md, knowledge/*
Review guidance now enforces KISS/SSOT rules, canonical sources, simplification, documented generation, and requested recheck intervals with a five-minute default.
Interval recheck behavior
.codex/skills/review-self/SKILL.md, .claude/skills/review-self/SKILL.md
Rechecks use real wake-up mechanisms, track pending state, require two clean snapshots, and stop after one hour of unchanged external state.

Apple promoted-purchase offer leasing

Layer / File(s) Summary
Lease-based offer state
packages/apple/Sources/Helpers/IapState.swift, packages/apple/Tests/OpenIapTests.swift
Offers now support leasing, release, one-shot consumption, replacement, and stale-lease protection.
Purchase-flow integration
packages/apple/Sources/OpenIapModule.swift
Eligible subscription requests lease offers. Failed preparation or presentation releases leases. Successful StoreKit results consume leases.

Google Activity-aware store lifecycle

Layer / File(s) Summary
Activity binding primitives
packages/google/openiap/src/main/java/dev/hyo/openiap/utils/*, packages/google/openiap/src/test/*
The code resolves Activities from wrapped contexts and manages weak, owner-scoped bindings with lifecycle-state tests.
Store and ViewModel lifecycle
packages/google/openiap/src/main/java/dev/hyo/openiap/{IapContext.kt,OpenIapViewModel.kt}, packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
Compose providers, stores, and ViewModels bind Activities during active lifecycle states and clear bindings during disposal or destruction.
Example-screen integration
packages/google/Example/src/main/java/dev/hyo/martie/**
Example screens use shared Activity-owned stores and remove per-screen Activity assignment and connection cleanup.

Store API metadata and documentation

Layer / File(s) Summary
Subscription-period metadata
libraries/*, packages/apple/Sources/Models/Types.swift, packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt, packages/gql/src/type-ios.graphql
Documentation now describes OpenIAP 3.1.0 availability and minimum Apple platform versions for AdvancedCommerceInfoIOS.period.
Promoted-product examples
packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
Examples refetch all product types and create subscription or in-app purchase requests based on the resolved product.
C# documentation generation
packages/gql/codegen/plugins/csharp.ts, packages/gql/src/codegen-defaults.test.ts
C# generation now emits one XML <summary> element per description and tests multiline escaping.
Release notes
packages/docs/src/pages/docs/updates/releases.tsx
Release entries document Store API follow-up versions, promoted-product handling, Apple lease protection, and Google Activity binding.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PromotedProductListener
  participant ProductAPI
  participant PurchaseRequest
  participant StoreKit
  PromotedProductListener->>ProductAPI: fetch promoted SKU with all query type
  ProductAPI-->>PromotedProductListener: return product or subscription
  PromotedProductListener->>PurchaseRequest: build matching request type
  PurchaseRequest->>StoreKit: submit purchase
Loading
sequenceDiagram
  participant ComposeScreen
  participant IapContext
  participant OpenIapStore
  participant HorizonBilling
  ComposeScreen->>IapContext: resolve current Activity
  IapContext->>OpenIapStore: bind Activity by owner
  OpenIapStore->>HorizonBilling: initialize with current Activity
  HorizonBilling-->>OpenIapStore: return billing state
  OpenIapStore-->>ComposeScreen: provide shared store
Loading

Possibly related PRs

  • hyodotdev/openiap#299: Updates the same Apple promoted-purchase offer handling before this PR adds lease-based reservation and stale-attempt protection.
  • hyodotdev/openiap#35: Modifies the same Google example screens that this PR changes to use shared Activity-owned store lifecycle management.
  • hyodotdev/openiap#234: Introduces the Claude/Codex review workflow infrastructure extended by this PR.

Suggested labels: 🧪 test, ⌚️ regression

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective: hardening the store API follow-up across Apple and Android changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/store-api-followup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hyochan hyochan added cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter-iap godot-iap godot-iap library kmp-iap kmp-iap library maui-iap .NET MAUI SDK react-native-iap react-native-iap library ፦ refactor ❄️ types Typing issues ⬡ protocol 👀 example 📖 documentation Improvements or additions to documentation 📱 iOS Related to iOS 🛠 bugfix All kinds of bug fixes 🤖 android Related to android labels Aug 9, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx (1)

75-99: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle failures inside each asynchronous listener.

fetchProducts and requestPurchase can fail. TypeScript and Dart discard the async listener result. C# uses an async void callback. An uncaught Kotlin exception cancels the collector. Catch failures inside each listener so later promoted-product events still work.

  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx#L75-L99: add a try/catch around the query and purchase flow.
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx#L160-L199: catch failures inside collect.
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx#L208-L239: catch failures inside the stream callback.
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx#L253-L283: catch failures inside the Subscribe callback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx`
around lines 75 - 99, Wrap the asynchronous promoted-product handling in
try/catch blocks so listener failures are contained and later events continue:
in packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
lines 75-99, protect the query and purchase flow around the
promotedProductListenerIOS callback; in lines 160-199, catch failures inside
collect; in lines 208-239, catch failures inside the stream callback; and in
lines 253-283, catch failures inside the Subscribe callback. Handle each caught
failure using the surrounding implementation’s established error-reporting
behavior.
🧹 Nitpick comments (1)
packages/apple/Sources/Helpers/IapState.swift (1)

270-296: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the IOS suffix to the iOS-only lease operations.

record, lease, release, and consume manage Apple PurchaseIntent offer state. Rename them with a final IOS suffix. Update the purchase flow and tests with the new names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/apple/Sources/Helpers/IapState.swift` around lines 270 - 296, Rename
the IapState methods record, lease, release, and consume to recordIOS, leaseIOS,
releaseIOS, and consumeIOS, preserving their behavior. Update every
purchase-flow call site and test reference to use the new IOS-suffixed names.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.codex/skills/review-self/SKILL.md:
- Line 112: Update the prohibition in review-pr.md for the PR fallback to
disallow any recurring loop, not only a five-minute loop, matching the broader
rule in the recurring workflow contract and preventing nested polling or
duplicate reviewer requests.
- Around line 132-137: Update the scheduled re-entry state capsule described in
the review-self skill to include both the user-requested interval and the
currently active interval. Ensure each wake-up reads and validates these
interval values before scheduling or continuing the recheck, preserving
non-default intervals instead of falling back to the five-minute default.

In `@libraries/maui-iap/src/OpenIap.Maui/Types.cs`:
- Around line 2248-2251: Update the generator or GraphQL source mapping for
AdvancedCommerceInfoIOS.Period so all subscription-period documentation is
emitted within one XML <summary> element; then regenerate Types.cs from
packages/gql and do not edit the generated file directly.

In `@packages/docs/src/pages/docs/updates/releases.tsx`:
- Line 28: Defer the August 10, 2026 release content until publication: in
packages/docs/src/pages/docs/updates/releases.tsx lines 28-28, remove or gate
the docs-3.1.0 link; in lines 39-49, defer storeApiFollowupReleases; and in
lines 195-206, remove the release note or rewrite it as planned-release content
without public package links.

In
`@packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt`:
- Line 60: Update currentOpenIapStore usage in PurchaseFlowScreen.kt at line 60
to bind the explicit storeParam to the current Activity before purchase
operations, or require a provider-managed store. Apply the same explicit-store
binding rule in SubscriptionFlowScreen.kt at line 96 so subscription requests
use an Activity-bound billing store.

In `@packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt`:
- Around line 251-252: Add the same OpenIapError.MissingCurrentActivity `@throws`
KDoc entry to the no-argument initConnection() overload, which delegates to
initConnection(null), matching the documentation on the existing overload.

---

Outside diff comments:
In `@packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx`:
- Around line 75-99: Wrap the asynchronous promoted-product handling in
try/catch blocks so listener failures are contained and later events continue:
in packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
lines 75-99, protect the query and purchase flow around the
promotedProductListenerIOS callback; in lines 160-199, catch failures inside
collect; in lines 208-239, catch failures inside the stream callback; and in
lines 253-283, catch failures inside the Subscribe callback. Handle each caught
failure using the surrounding implementation’s established error-reporting
behavior.

---

Nitpick comments:
In `@packages/apple/Sources/Helpers/IapState.swift`:
- Around line 270-296: Rename the IapState methods record, lease, release, and
consume to recordIOS, leaseIOS, releaseIOS, and consumeIOS, preserving their
behavior. Update every purchase-flow call site and test reference to use the new
IOS-suffixed names.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2e690ce-080c-434d-977f-3ba518dc403d

📥 Commits

Reviewing files that changed from the base of the PR and between ff91ba7 and c0b9f70.

⛔ Files ignored due to path filters (6)
  • packages/gql/src/generated/Types.cs is excluded by !**/generated/**
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.gd is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (37)
  • .claude/commands/audit-code.md
  • .claude/commands/review-pr.md
  • .claude/skills/review-self/SKILL.md
  • .codex/skills/openiap-workflows/SKILL.md
  • .codex/skills/review-self/SKILL.md
  • .codex/skills/review-self/agents/openai.yaml
  • AGENTS.md
  • knowledge/_claude-context/context.md
  • knowledge/internal/03-coding-style.md
  • libraries/expo-iap/src/types.ts
  • libraries/flutter_inapp_purchase/lib/types.dart
  • libraries/godot-iap/addons/godot-iap/types.gd
  • libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt
  • libraries/maui-iap/src/OpenIap.Maui/Types.cs
  • libraries/react-native-iap/src/types.ts
  • packages/apple/Sources/Helpers/IapState.swift
  • packages/apple/Sources/Models/Types.swift
  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Tests/OpenIapTests.swift
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
  • packages/docs/src/pages/docs/updates/releases.tsx
  • packages/google/Example/src/main/java/dev/hyo/martie/MainActivity.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AllProductsScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AvailablePurchasesScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/OfferCodeScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/OpenIapStoreContext.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/IapContext.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapViewModel.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/ActivityUtils.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/utils/OwnerScopedValueBinding.kt
  • packages/google/openiap/src/test/java/dev/hyo/openiap/utils/OwnerScopedValueBindingTest.kt
  • packages/google/openiap/src/testHorizon/java/dev/hyo/openiap/utils/ActivityUtilsTest.kt
  • packages/gql/src/type-ios.graphql

Comment thread .codex/skills/review-self/SKILL.md
Comment thread .codex/skills/review-self/SKILL.md Outdated
Comment thread libraries/maui-iap/src/OpenIap.Maui/Types.cs Outdated
Comment thread packages/docs/src/pages/docs/updates/releases.tsx

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/google/Example/src/main/java/dev/hyo/martie/screens/OfferCodeScreen.kt (1)

39-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle initConnection() failures before resume reconciliation.

initConnection() returns a Boolean result from OpenIapStore.initConnection(), and the resume path calls getAvailablePurchases(null). If initialization fails, reconciliation can also fail while both failures are discarded. Store or surface the init error or retry before relying on the resume reconciliation result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/google/Example/src/main/java/dev/hyo/martie/screens/OfferCodeScreen.kt`
around lines 39 - 43, Update the LaunchedEffect around iapStore.initConnection()
to inspect its Boolean result and handle initialization failure before allowing
resume reconciliation to depend on getAvailablePurchases(null). Preserve
successful initialization behavior, but retain or surface the initialization
error and retry or stop reconciliation when initConnection() fails instead of
discarding both failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@packages/google/Example/src/main/java/dev/hyo/martie/screens/OfferCodeScreen.kt`:
- Around line 39-43: Update the LaunchedEffect around iapStore.initConnection()
to inspect its Boolean result and handle initialization failure before allowing
resume reconciliation to depend on getAvailablePurchases(null). Preserve
successful initialization behavior, but retain or surface the initialization
error and retry or stop reconciliation when initConnection() fails instead of
discarding both failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b16064c6-1df0-4693-8f38-7bfb07871860

📥 Commits

Reviewing files that changed from the base of the PR and between c0b9f70 and aa8d083.

⛔ Files ignored due to path filters (1)
  • packages/gql/src/generated/Types.cs is excluded by !**/generated/**
📒 Files selected for processing (13)
  • .claude/commands/review-pr.md
  • .codex/skills/review-self/SKILL.md
  • libraries/maui-iap/src/OpenIap.Maui/Types.cs
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AllProductsScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AvailablePurchasesScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/OfferCodeScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/OpenIapStoreContext.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/SubscriptionFlowScreen.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/gql/codegen/plugins/csharp.ts
  • packages/gql/src/codegen-defaults.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/OpenIapStoreContext.kt
  • .claude/commands/review-pr.md
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AllProductsScreen.kt
  • .codex/skills/review-self/SKILL.md
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Aug 9, 2026
@hyochan
hyochan merged commit 694a55a into main Aug 9, 2026
28 checks passed
@hyochan
hyochan deleted the fix/store-api-followup branch August 9, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android 🛠 bugfix All kinds of bug fixes cross-platform Cross-platform (both Android & iOS) 📖 documentation Improvements or additions to documentation 👀 example expo-iap expo-iap library flutter-iap godot-iap godot-iap library 📱 iOS Related to iOS kmp-iap kmp-iap library maui-iap .NET MAUI SDK ⬡ protocol react-native-iap react-native-iap library ፦ refactor ❄️ types Typing issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant