Skip to content

feat(openiap): apply integration and api updates#545

Merged
hyochan merged 22 commits intomainfrom
feat/openiap-migration
Sep 9, 2025
Merged

feat(openiap): apply integration and api updates#545
hyochan merged 22 commits intomainfrom
feat/openiap-migration

Conversation

@hyochan
Copy link
Copy Markdown
Owner

@hyochan hyochan commented Sep 8, 2025

iOS Migration to OpenIAP Module

iOS Integration

  • Integrate OpenIAP module
  • Implement fetchProducts, *IOS methods
  • Add receipt validation

Events

  • Use @mainactor for concurrency safety
  • Apply type coercion for Dart compatibility

Example

  • getAvailablePurchases
    • Support Active purchases
    • Support History purchases

Cleanup

  • Remove v6.4.0 deprecated paths
    • Dart
    • Android

Summary by CodeRabbit

  • New Features

    • iOS migrated to OpenIAP: storefront, pending-transactions, promoted-product events, code-redemption, manage-subscriptions, active-subscriptions and related APIs; improved purchase events and error messages.
  • Refactor

    • Public API renames/signature updates; removed deprecated proration/subscription fields; standardized product/price typing and JSON normalization.
  • Documentation

    • Added iOS notes, developer setup (git hooks), sponsors and license, and refreshed error-code docs.
  • Tests

    • Added iOS-specific method tests; updated tests for fetchProducts/requestPurchase changes.
  • Chores

    • Added recommended pre-commit hook, CocoaPods/OpenIAP pinning, LLDB helper, .gitignore and iOS deployment update.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 8, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates iOS native integration to OpenIAP with many new iOS-specific APIs/events, removes deprecated Android/Dart fields/aliases, adds a pre-commit hook and README dev docs, introduces ephemeral iOS LLDB helpers and Podfile/podspec updates, and updates tests and examples to match renamed methods.

Changes

Cohort / File(s) Summary
Git hooks & docs
.githooks/pre-commit, README.md
Adds pre-commit script to auto-format staged Dart files, run analyzer/tests with env knobs (ENFORCE_ANALYZE, SKIP_PRECOMMIT_TESTS, PRECOMMIT_TEST_CONCURRENCY); updates README with iOS notes and developer workflow.
iOS plugin & native deps
ios/Classes/FlutterInappPurchasePlugin.swift, ios/flutter_inapp_purchase.podspec, example/ios/Podfile, example/ios/Flutter/Flutter.podspec, example/ios/Runner.xcodeproj/project.pbxproj
Replaces StoreKit plumbing with OpenIAP, adds OpenIAP dependency, introduces many iOS-specific channel methods/events (fetchProducts mapping, getStorefrontIOS, getPendingTransactionsIOS, promoted-product event, finish/validate via OpenIAP), and embeds OpenIAP framework in example project.
iOS ephemeral/debug tooling
example/ios/Flutter/ephemeral/flutter_lldb_helper.py, example/ios/Flutter/ephemeral/flutter_lldbinit
Adds LLDB Python helper and init file to write a diagnostic prefix on a debugger notification.
Android plugin
android/src/main/kotlin/.../AndroidInappPurchasePlugin.kt
Removes deprecated subscriptionOfferDetails, uses subscriptionOfferDetailsAndroid only; consolidates replacementMode handling to Android-specific parameter name.
Dart types & API surface
lib/types.dart, lib/enums.dart, lib/flutter_inapp_purchase.dart, lib/modules/ios.dart, lib/errors.dart, lib/*
Removes deprecated aliases/fields (subscriptionOfferDetails, AndroidProrationMode, prorationMode), makes PurchaseOptions const, introduces fetchProducts/fetchProducts, adds iOS-specific public APIs (getStorefrontIOS, getPromotedProductIOS, presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS, getPendingTransactionsIOS, clearTransactionIOS), adds ActiveSubscription APIs, improves error normalization and user-facing messages, and normalizes JSON string fields.
Examples & UI calls
example/lib/...screens/*.dart, example/lib/storekit2_demo.dart
Updates example screens and demo to call iOS-specific variants (presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS).
Tests
test/flutter_inapp_purchase_test.dart, test/ios_methods_test.dart, test/subscription_flow_test.dart, test/purchase_flow_test.dart
Adds iOS-methods tests and updates existing tests to reflect renamed methods (getItems→fetchProducts, buyProduct→requestPurchase, iOS-specific method names and behavior).
Docs & misc
docs/docs/api/error-codes.md, .gitignore
Formatting/whitespace cleanup in error codes doc; adds example/ios/Flutter/ephemeral/ to .gitignore.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Dev as Developer
    participant App as Flutter app (Dart)
    participant Lib as flutter_inapp_purchase (Dart)
    participant Channel as Method/Event Channels
    participant iOS as iOS native (OpenIAP)

    Dev->>App: user action (fetch/purchase/restore)
    App->>Lib: call public API (fetchProducts / requestSubscription / restorePurchases / getStorefrontIOS / ...)
    Lib->>Channel: invoke method (fetchProducts / requestPurchase / getStorefrontIOS / ...)
    Channel->>iOS: native method invoked (handled by OpenIAP)
    iOS-->>Channel: returns result or emits event (purchase-updated, iap-promoted-product)
    Channel-->>Lib: result or event
    alt event == purchase-updated
        Lib->>App: dispatch purchase-updated payload
    else event == iap-promoted-product
        Lib->>App: dispatch promoted product payload
    end
    Note right of iOS: OpenIAP manages StoreKit details, listeners, and receipt/transaction operations
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

"I hopped through code to tidy the lair,
Formatted Dart leaves with vigilant care.
OpenIAP sings on iOS now bright,
Android trims old shells out of sight.
Tests and hooks in a carrot-sweet flight! 🐇"


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79bf5c0 and 1c107b9.

📒 Files selected for processing (2)
  • lib/flutter_inapp_purchase.dart (18 hunks)
  • test/purchase_flow_test.dart (3 hunks)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/openiap-migration

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @hyochan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a substantial refactor of the iOS in-app purchase implementation by integrating the OpenIAP module, aiming to modernize the codebase and standardize the API surface across platforms. It streamlines the interaction with native purchase mechanisms, cleans up outdated parameters and fields, and refines the handling of purchase events and historical data within the example application.

Highlights

  • iOS Migration to OpenIAP: The core iOS in-app purchase logic has been migrated from direct StoreKit 2 usage to the OpenIAP module. This involves significant changes to how products are fetched, purchases are made, and transactions are handled, leveraging OpenIAP's abstractions.
  • API Alignment and Deprecation Cleanup: Several iOS-specific API methods have been renamed (e.g., getItems to fetchProducts, getStorefront to getStorefrontIOS) and aligned with the new OpenIAP integration. Deprecated fields and parameters related to Android subscription offers and proration modes have been removed from the codebase.
  • Enhanced Purchase History Handling: The example application and underlying iOS logic for getAvailablePurchases have been updated to explicitly differentiate between active (non-consumed) and historical (including expired) purchases, providing more granular control and clarity.
  • Improved Event Handling: The iOS plugin now sets up OpenIAP listeners for purchase updates, errors, and promoted products earlier in the lifecycle, ensuring more robust and timely event propagation to the Dart side, with improved type coercion for compatibility.
  • Build System Updates: The iOS project's Podfile.lock and Runner.xcodeproj/project.pbxproj have been updated to include the openiap CocoaPods dependency, and the minimum iOS deployment target in example/ios/Flutter/Flutter.podspec has been adjusted.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully migrates the iOS implementation to use the OpenIAP native module, aligning with the project's goal of standardization. The changes also include valuable cleanup of deprecated APIs on both Android and Dart sides, improving code health. The refactoring on the Swift side is extensive and well-executed. My review includes a couple of minor suggestions to fix an inconsistency in the example app's configuration and to clean up some log messages in the native iOS code.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

Codex Review: Here are some suggestions.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
lib/types.dart (1)

3-5: Fix export directive; add missing semicolon and avoid mis-export.

Compilation will fail due to the missing semicolon. Also, exporting getCurrentPlatform from errors.dart is likely wrong (it should come from enums.dart, which you already export). Simplify to export only what errors.dart owns.

Apply:

-export 'enums.dart';
-export 'errors.dart'
-    show PurchaseError, PurchaseResult, ConnectionResult, getCurrentPlatform;
+export 'enums.dart';
+export 'errors.dart'
+    show PurchaseError, PurchaseResult, ConnectionResult;
♻️ Duplicate comments (4)
example/ios/Flutter/Flutter.podspec (1)

14-14: Align iOS deployment target to 15.0 (matches plugin + README).

Set to 15.0 to avoid mismatches and SK2/OpenIAP runtime issues in the example app.

-  s.ios.deployment_target = '13.0'
+  s.ios.deployment_target = '15.0'
ios/Classes/FlutterInappPurchasePlugin.swift (2)

193-193: Remove unnecessary backslashes from string interpolation in log statements.

The backslashes before the string interpolation are unnecessary and will be printed literally in the logs, making them harder to read.

Apply this diff to fix the log statements:

-                print("\\(FlutterInappPurchasePlugin.TAG) ✅ purchaseUpdatedListener fired")
+                print("\(FlutterInappPurchasePlugin.TAG) ✅ purchaseUpdatedListener fired")
-                    print("\\(FlutterInappPurchasePlugin.TAG) Emitting purchase-updated to Flutter")
+                    print("\(FlutterInappPurchasePlugin.TAG) Emitting purchase-updated to Flutter")
-                print("\\(FlutterInappPurchasePlugin.TAG) ❌ purchaseErrorListener fired")
+                print("\(FlutterInappPurchasePlugin.TAG) ❌ purchaseErrorListener fired")
-                    print("\\(FlutterInappPurchasePlugin.TAG) Emitting purchase-error to Flutter")
+                    print("\(FlutterInappPurchasePlugin.TAG) Emitting purchase-error to Flutter")
-                print("\\(FlutterInappPurchasePlugin.TAG) 📱 promotedProductListenerIOS fired for: \(productId)")
+                print("\(FlutterInappPurchasePlugin.TAG) 📱 promotedProductListenerIOS fired for: \(productId)")

Also applies to: 203-203, 212-212, 221-221, 230-230


497-521: Verify the return type of getPromotedProductIOS matches the Dart API expectations.

The iOS implementation now returns a structured map with product details (productIdentifier, title, description, price, etc.), but the Dart wrapper still declares getPromotedProduct() as Future<String?> and simply forwards the native call. This type mismatch will cause runtime errors when existing code tries to use the result as a String.

#!/bin/bash
# Check the Dart API declaration for getPromotedProduct
echo "Checking Dart API declaration for getPromotedProduct:"
rg -n "Future<.*?>\s+getPromotedProduct\(" lib/
lib/flutter_inapp_purchase.dart (1)

2020-2026: Critical: getPromotedProduct return type mismatch will cause runtime errors.

The Dart method still returns Future<String?> but the iOS native implementation now returns a structured map containing product details. This type mismatch will cause runtime TypeError when callers try to use the result as a String.

Either:

  1. Update the Dart method signature to return Future<Map<String, dynamic>?> and document the breaking change, or
  2. Modify the iOS implementation to extract just the productIdentifier string from the map

Option 1 (Update Dart API - breaking change):

-  Future<String?> getPromotedProduct() async {
+  Future<Map<String, dynamic>?> getPromotedProduct() async {
     if (_platform.isIOS) {
-      return await _channel.invokeMethod('getPromotedProductIOS');
+      final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('getPromotedProductIOS');
+      return result != null ? Map<String, dynamic>.from(result) : null;
     }
     return null;
   }

Option 2 (Keep String API - non-breaking):

   Future<String?> getPromotedProduct() async {
     if (_platform.isIOS) {
-      return await _channel.invokeMethod('getPromotedProductIOS');
+      final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('getPromotedProductIOS');
+      return result?['productIdentifier'] as String?;
     }
     return null;
   }
🧹 Nitpick comments (5)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)

7-33: Generated file triggers ARG001; exclude ephemeral from lint.

Prefer repo-level linter ignore (e.g., .ruffignore) for example/ios/Flutter/ephemeral/** rather than editing generated code.

lib/types.dart (4)

511-532: Unreachable branch: this is Subscription inside Product.toJson.

Subscription does not extend Product; this branch is dead code and confusing. Remove it to reduce maintenance burden.

-      // Add OpenIAP compliant iOS fields for Subscription
-      // Note: In Product class, this check is needed; in Subscription class, it's redundant
-      else if (this is Subscription) {
-        // Remove unnecessary cast since we know the type
-        if ((this as dynamic).isFamilyShareableIOS != null) {
-          json['isFamilyShareableIOS'] = (this as dynamic).isFamilyShareableIOS;
-        }
-        if ((this as dynamic).jsonRepresentationIOS != null) {
-          json['jsonRepresentationIOS'] =
-              (this as dynamic).jsonRepresentationIOS;
-        }
-      }
+      // (removed unreachable Subscription branch)

1200-1219: Constructor forwards subscriptionOfferDetailsAndroid but fromJson never sets it. Verify intent.

ProductAndroid accepts and forwards super.subscriptionOfferDetailsAndroid, yet ProductAndroid.fromJson omits it (a comment later says it exists only on Product). If consumers construct ProductAndroid via fromJson, the field will always be null, diverging from Product.fromJson.

If you want parity with Product.fromJson, wire it here too:

   factory ProductAndroid.fromJson(Map<String, dynamic> json) {
     return ProductAndroid(
       productId: json['productId'] as String? ?? '',
       price: json['price'] as String? ?? '0',
       currency: json['currency'] as String?,
       localizedPrice: json['localizedPrice'] as String?,
       title: json['title'] as String?,
       description: json['description'] as String?,
       type: json['type'] as String?,
       originalPrice: json['originalPrice'] as String?,
+      subscriptionOfferDetailsAndroid: json['subscriptionOfferDetailsAndroid'] != null
+          ? (json['subscriptionOfferDetailsAndroid'] as List)
+              .map((item) {
+                final map = _safeJsonMap(item);
+                return map != null ? OfferDetail.fromJson(map) : null;
+              })
+              .whereType<OfferDetail>()
+              .toList()
+          : null,
       subscriptionPeriod: json['subscriptionPeriod'] as String?,
       introductoryPriceCycles: json['introductoryPriceCycles'] as String?,
       introductoryPricePeriod: json['introductoryPricePeriod'] as String?,
       freeTrialPeriod: json['freeTrialPeriod'] as String?,
       signature: json['signature'] as String?,
       subscriptionOffers: json['subscriptionOffers'] != null

1244-1247: Comment contradicts constructor API.

The note says subscriptionOfferDetailsAndroid is only in Product, but the constructor explicitly forwards it. Either parse it in fromJson (preferred) or drop the forwarded param to avoid confusion.


1811-1853: Deduplicate and fix key casing in Purchase.toString (iOS block).

  • productTypeIOS and subscriptionGroupIdIOS are printed twice.
  • Mixed casing for storefront: storefrontCountryCodeIOS vs storeFrontCountryCodeIOS.
-      if (subscriptionGroupIdIOS != null) {
-        buffer.writeln('  subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",');
-      }
-      if (productTypeIOS != null) {
-        buffer.writeln('  productTypeIOS: "$productTypeIOS",');
-      }
+      if (subscriptionGroupIdIOS != null) {
+        buffer.writeln('  storeFrontCountryCodeIOS: "$storeFrontCountryCodeIOS",');
+      }
       if (transactionReasonIOS != null) {
         buffer.writeln('  transactionReasonIOS: "$transactionReasonIOS",');
       }
       if (currencyCodeIOS != null) {
         buffer.writeln('  currencyCodeIOS: "$currencyCodeIOS",');
       }
       if (storeFrontCountryCodeIOS != null) {
-        buffer.writeln(
-          '  storefrontCountryCodeIOS: "$storeFrontCountryCodeIOS",',
-        );
+        buffer.writeln('  storeFrontCountryCodeIOS: "$storeFrontCountryCodeIOS",');
       }
 ...
-      if (productTypeIOS != null) {
-        buffer.writeln('  productTypeIOS: "$productTypeIOS",');
-      }
-      if (subscriptionGroupIdIOS != null) {
-        buffer.writeln('  subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",');
-      }
-      if (storeFrontCountryCodeIOS != null) {
-        buffer.writeln(
-          '  storeFrontCountryCodeIOS: $storeFrontCountryCodeIOS,',
-        );
-      }
+      // (removed duplicate prints for productTypeIOS, subscriptionGroupIdIOS, storeFrontCountryCodeIOS)

Also keep only one occurrence of each field and use the correct storeFrontCountryCodeIOS casing consistently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be76ca0 and 698e5c7.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • README.md (1 hunks)
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (2 hunks)
  • example/ios/Flutter/Flutter.podspec (1 hunks)
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1 hunks)
  • example/ios/Flutter/ephemeral/flutter_lldbinit (1 hunks)
  • example/ios/Runner.xcodeproj/project.pbxproj (1 hunks)
  • example/lib/src/screens/available_purchases_screen.dart (1 hunks)
  • ios/Classes/FlutterInappPurchasePlugin.swift (11 hunks)
  • ios/flutter_inapp_purchase.podspec (1 hunks)
  • lib/enums.dart (0 hunks)
  • lib/flutter_inapp_purchase.dart (11 hunks)
  • lib/modules/ios.dart (2 hunks)
  • lib/types.dart (5 hunks)
💤 Files with no reviewable changes (1)
  • lib/enums.dart
🧰 Additional context used
📓 Path-based instructions (8)
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using emojis in documentation, especially in headings

Files:

  • README.md
{README.md,docs/**}

📄 CodeRabbit inference engine (GEMINI.md)

{README.md,docs/**}: Update relevant documentation when API changes are made
Document new features in README or API docs

Files:

  • README.md
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • lib/modules/ios.dart
  • example/lib/src/screens/available_purchases_screen.dart
  • lib/flutter_inapp_purchase.dart
  • lib/types.dart
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
  • lib/types.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • lib/modules/ios.dart
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
  • lib/types.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • lib/modules/ios.dart
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • example/lib/src/screens/available_purchases_screen.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
  • lib/types.dart
example/**

📄 CodeRabbit inference engine (GEMINI.md)

Update the example app when needed for new features

Files:

  • example/ios/Runner.xcodeproj/project.pbxproj
  • example/ios/Flutter/ephemeral/flutter_lldbinit
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py
  • example/lib/src/screens/available_purchases_screen.dart
  • example/ios/Flutter/Flutter.podspec
{android/**,ios/**}

📄 CodeRabbit inference engine (GEMINI.md)

For new features, implement for both iOS and Android when applicable

Files:

  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • ios/flutter_inapp_purchase.podspec
  • ios/Classes/FlutterInappPurchasePlugin.swift
🧠 Learnings (13)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Document new features in README or API docs

Applied to files:

  • README.md
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Update relevant documentation when API changes are made

Applied to files:

  • README.md
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)

Applied to files:

  • lib/modules/ios.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • lib/flutter_inapp_purchase.dart
  • lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)

Applied to files:

  • lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use RequestProductsParams; it has been removed

Applied to files:

  • lib/types.dart
🪛 Ruff (0.12.2)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py

7-7: Unused function argument: bp_loc

(ARG001)


7-7: Unused function argument: extra_args

(ARG001)


7-7: Unused function argument: intern_dict

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (17)
ios/flutter_inapp_purchase.podspec (1)

17-19: OpenIAP dependency addition looks good.

Please confirm openiap ~> 1.1.6 resolves via CocoaPods and works on CI/macOS runners.

lib/modules/ios.dart (2)

102-112: Use of IOS-suffixed channel method is correct; verify native handler name.

Ensure the Swift side exports presentCodeRedemptionSheetIOS (exact string) on the MethodChannel.


114-124: Consistent IOS-suffixed API; verify native handler name.

Ensure iOS plugin exposes showManageSubscriptionsIOS on the MethodChannel.

android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (1)

838-839: EOF newline added.

No concerns.

README.md (1)

54-59: iOS notes are clear and consistent with the migration.

Please also align example’s Flutter.podspec to iOS 15.0 (it’s currently 13.0) to match this doc and the plugin podspec.

example/ios/Runner.xcodeproj/project.pbxproj (1)

287-295: OpenIAP.framework embed entries look fine.

Confirm these entries are generated by CocoaPods (not hand-edited) by re-running pod install and committing Pod-related diffs.

example/ios/Flutter/ephemeral/flutter_lldbinit (1)

5-5: LLDB init inclusion is fine.

No action needed.

ios/Classes/FlutterInappPurchasePlugin.swift (1)

451-464: LGTM!

The OpenIAP integration for presentCodeRedemptionSheetIOS is correctly implemented with proper iOS version checking and async/await error handling.

example/lib/src/screens/available_purchases_screen.dart (2)

112-115: LGTM! Correctly implements platform-aware purchase filtering for iOS.

The implementation properly uses PurchaseOptions with onlyIncludeActiveItemsIOS: true to filter only active items on iOS, aligning with the OpenIAP specification changes.


122-132: Good platform-specific history handling.

The implementation correctly differentiates between iOS and Android for fetching purchase history. iOS uses getAvailablePurchases with onlyIncludeActiveItemsIOS: false to include expired items, while Android continues using the dedicated getPurchaseHistories API.

lib/flutter_inapp_purchase.dart (4)

208-212: LGTM! Correct OpenIAP migration for iOS fetchProducts.

The iOS implementation now properly uses fetchProducts with explicit type parameter, aligning with the OpenIAP module changes.


821-824: Good defensive programming for iOS ProductIOS parsing.

The code properly handles both productId and id field names with fallback to empty string, and ensures price and currency are coerced to strings for consistent typing.


1348-1348: LGTM! Correct removal of deprecated prorationModeAndroid parameter.

The code properly uses replacementModeAndroid instead of the deprecated prorationModeAndroid, aligning with the Android billing library update.


1981-1983: Verify iOS restore purchase flow
lib/flutter_inapp_purchase.dart:1981-1983
Ensure that using

await _channel.invokeMethod('endConnection');
await _channel.invokeMethod('initConnection');

actually invokes the App Store’s restoreCompletedTransactions under the hood via OpenIAP and does not unregister or lose any active listeners. Confirm in the OpenIAP iOS source or documentation that initConnection covers restoration and review potential side-effects on listener registration.

lib/types.dart (3)

48-51: Nice: make PurchaseOptions const.

Const constructor here is appropriate for an options value object.


340-350: LGTM: tolerant parsing for subscriptionOfferDetailsAndroid.

The _safeJsonMap + whereType pattern is safe and aligns with mixed shapes returned from Android.


820-830: LGTM: same tolerant parsing in Subscription.fromJson.

Consistent with Product.fromJson.

@hyochan hyochan marked this pull request as draft September 8, 2025 18:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
ios/Classes/FlutterInappPurchasePlugin.swift (1)

72-87: Preserve iOS purchase options (appAccountToken, quantity) and remove silent drops.

Dart may pass 'forUser' (appAccountToken) and 'quantity' when invoking buyProduct. Native currently ignores both. Parse them and forward to OpenIAP props.

@@
-        case "buyProduct":
-            // Support both old and new API
-            var productId: String?
-            
-            if let args = call.arguments as? [String: Any] {
-                productId = args["productId"] as? String ?? args["sku"] as? String
-            } else if let id = call.arguments as? String {
-                productId = id
-            }
-            
-            guard let id = productId else {
+        case "buyProduct":
+            // Support both old and new API
+            var productId: String?
+            var appAccountToken: String?
+            var quantity: Int?
+
+            if let args = call.arguments as? [String: Any] {
+                productId = args["productId"] as? String ?? args["sku"] as? String
+                if let q = args["quantity"] as? Int { quantity = q }
+                else if let qs = args["quantity"] as? String { quantity = Int(qs) }
+                appAccountToken = (args["forUser"] as? String) ?? (args["appAccountToken"] as? String)
+            } else if let id = call.arguments as? String {
+                productId = id
+            }
+
+            guard let id = productId else {
                 result(FlutterError(code: "INVALID_ARGUMENTS", message: "productId required", details: nil))
                 return
             }
-            buyProduct(productId: id, result: result)
+            buyProduct(productId: id, appAccountToken: appAccountToken, quantity: quantity, result: result)
@@
-    private func buyProduct(productId: String, result: @escaping FlutterResult) {
+    private func buyProduct(productId: String, appAccountToken: String?, quantity: Int?, result: @escaping FlutterResult) {
         print("\(FlutterInappPurchasePlugin.TAG) buyProduct called with productId: \(productId)")
         Task { @MainActor in
             do {
                 let props = OpenIapRequestPurchaseProps(
                     sku: productId,
                     andDangerouslyFinishTransactionAutomatically: false,
-                    appAccountToken: nil,
-                    quantity: nil,
+                    appAccountToken: appAccountToken,
+                    quantity: quantity,
                     withOffer: nil
                 )
                 _ = try await OpenIapModule.shared.requestPurchase(props)
                 result(nil)

Also applies to: 351-377

lib/flutter_inapp_purchase.dart (1)

292-316: Avoid calling unimplemented iOS methods (requestProductWithOfferIOS/requestProductWithQuantityIOS).

These channel methods don’t exist in the new Swift plugin and will throw. Route all iOS purchases through 'buyProduct' and pass optional fields; native now consumes appAccountToken/quantity.

-        if (iosRequest.withOffer != null) {
-          await _channel
-              .invokeMethod('requestProductWithOfferIOS', <String, dynamic>{
-            'sku': iosRequest.sku,
-            'forUser': iosRequest.appAccountToken ?? '',
-            'withOffer': iosRequest.withOffer!.toJson(),
-          });
-        } else if (iosRequest.quantity != null && iosRequest.quantity! > 1) {
-          await _channel.invokeMethod(
-            'requestProductWithQuantityIOS',
-            <String, dynamic>{
-              'sku': iosRequest.sku,
-              'quantity': iosRequest.quantity!.toString(),
-            },
-          );
-        } else {
-          if (type == iap_types.ProductType.subs) {
-            await requestSubscription(iosRequest.sku);
-          } else {
-            await _channel.invokeMethod('buyProduct', <String, dynamic>{
-              'sku': iosRequest.sku,
-              'forUser': iosRequest.appAccountToken,
-            });
-          }
-        }
+        await _channel.invokeMethod('buyProduct', <String, dynamic>{
+          'sku': iosRequest.sku,
+          if (iosRequest.appAccountToken != null)
+            'forUser': iosRequest.appAccountToken,
+          if (iosRequest.quantity != null) 'quantity': iosRequest.quantity,
+          // TODO: wire up withOffer once native supports it
+        });
♻️ Duplicate comments (2)
example/ios/Flutter/Flutter.podspec (1)

14-14: Good: iOS min target aligned to 15.0 (OpenIAP/StoreKit 2 requirement).

This resolves prior inconsistency with README and podspec. No further action.

lib/flutter_inapp_purchase.dart (1)

2021-2030: API now returns a Map for getPromotedProduct — matches native.

Resolves prior P1 mismatch (String → Map). Back-compat wrapper for String is a nice touch.

🧹 Nitpick comments (3)
ios/Classes/FlutterInappPurchasePlugin.swift (2)

38-41: Optional: gate canMakePayments behind initialization.

Returning true unconditionally may mislead callers before initConnection completes. Consider returning _initialized state via OpenIAP if available, or defer to isReady() on Dart.


129-134: Optional: expose getAppTransaction on iOS or document as best-effort.

Dart calls 'getAppTransaction' but iOS plugin has no case. It’s caught and returns null, which is fine. If desired, add a stub handler returning nil to avoid exceptions in logs.

@@
         case "showManageSubscriptionsIOS":
             showManageSubscriptionsIOS(result: result)
+        case "getAppTransaction":
+            result(nil) // optional stub until implemented

Also applies to: 523-536

lib/flutter_inapp_purchase.dart (1)

631-643: Keep in sync with native getStorefrontIOS return shape.

After changing native to return {'countryCode': ''}, this read is correct. If you prefer native to return a String, adjust here to invokeMethod and return it directly.

-      final result = await channel.invokeMethod<Map<dynamic, dynamic>>(
-        'getStorefrontIOS',
-      );
-      if (result != null && result['countryCode'] != null) {
-        return result['countryCode'] as String;
-      }
+      // If native returns a raw String instead of a map, use this:
+      // final result = await channel.invokeMethod<String>('getStorefrontIOS');
+      // if (result != null) return result;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 698e5c7 and 1250c76.

📒 Files selected for processing (4)
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (2 hunks)
  • example/ios/Flutter/Flutter.podspec (1 hunks)
  • ios/Classes/FlutterInappPurchasePlugin.swift (11 hunks)
  • lib/flutter_inapp_purchase.dart (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
🧰 Additional context used
📓 Path-based instructions (6)
example/**

📄 CodeRabbit inference engine (GEMINI.md)

Update the example app when needed for new features

Files:

  • example/ios/Flutter/Flutter.podspec
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
{android/**,ios/**}

📄 CodeRabbit inference engine (GEMINI.md)

For new features, implement for both iOS and Android when applicable

Files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • lib/flutter_inapp_purchase.dart
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/flutter_inapp_purchase.dart
🧠 Learnings (7)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Implementations must follow the OpenIAP specification (APIs, Types, Events, Errors)
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:01:17.634Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • lib/flutter_inapp_purchase.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
ios/Classes/FlutterInappPurchasePlugin.swift (1)

209-223: Align purchase-error payload with Dart PurchaseResult.fromJSON. Swift is emitting only code, message and productId, but PurchaseResult.fromJSON expects keys
responseCode (int?),
debugMessage (String?),
code (String?),
message (String?),
purchaseTokenAndroid (String?).

Either augment the JSON in ios/Classes/FlutterInappPurchasePlugin.swift to include responseCode and debugMessage (and drop or rename productId if it’s unused), or update the Dart model to consume productId if intentional. [ios/Classes/FlutterInappPurchasePlugin.swift:209–223]

⛔ Skipped due to learnings
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
lib/flutter_inapp_purchase.dart (4)

208-213: Good: iOS now uses fetchProducts with direct parameters (skus/type).

Matches OpenIAP naming (“skus”) and guideline to avoid params objects.


688-707: LGTM: iOS wrappers (presentCodeRedemptionSheetIOS/showManageSubscriptionsIOS) and platform-gated calls.

Bindings and error mapping look consistent with native.

Also applies to: 671-681, 1992-2006, 686-709


1387-1391: OK: getPendingTransactionsIOS channel name matches native.

Decoding path (JSON-encoding list) is consistent with other iOS flows.


1978-1987: Restore on iOS via reconnection – confirm UX.

The end/init approach is acceptable with OpenIAP; just be aware it toggles connection state. No change requested.

Would you like to log connection-updated events around this sequence for easier debugging?

@codecov
Copy link
Copy Markdown

codecov bot commented Sep 8, 2025

Codecov Report

❌ Patch coverage is 33.33333% with 100 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.25%. Comparing base (efceba6) to head (1c107b9).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/flutter_inapp_purchase.dart 44.44% 60 Missing ⚠️
lib/errors.dart 7.14% 26 Missing ⚠️
lib/modules/ios.dart 0.00% 13 Missing ⚠️
lib/types.dart 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #545      +/-   ##
==========================================
+ Coverage   35.71%   37.25%   +1.54%     
==========================================
  Files          10       10              
  Lines        2212     2158      -54     
==========================================
+ Hits          790      804      +14     
+ Misses       1422     1354      -68     

☔ View full report in Codecov by Sentry.
📢 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.

@hyochan hyochan force-pushed the feat/openiap-migration branch from 3af2e05 to 48afb4f Compare September 8, 2025 18:43
Migrate iOS to OpenIAP module, add fetchProducts, align iOS method names (*IOS), coerce payload types, and update example to use getAvailablePurchases. Remove deprecated TODO v6.4.0 items across Dart/Android. Includes validateReceiptIOS via OpenIAP with purchaseToken and platform in payload.
Reduce Swift-side mapping; rely on Dart parser. Update Dart to coerce iOS product price to string via toString() and fallback productId from id. This aligns closer to Expo module semantics.
- Set example iOS deployment target to 15.0\n- Remove stray backslashes in Swift logs\n- Android: read replacementModeAndroid (fallback to replacementMode)\n- Dart: getPromotedProduct returns structured map for iOS
Adapt tests to new iOS fetchProducts API and expected arguments; keep behavior identical otherwise.
Add tests for presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS, getStorefrontIOS, getPromotedProduct (map), and getPendingTransactionsIOS to raise patch coverage.
Versioned git hook under .githooks runs flutter pub get, dart format check, and flutter test. To enable locally: git config core.hooksPath .githooks
… storefront code

- clearTransactionCache returns success to avoid Dart-side exceptions\n- promoted product event now 'iap-promoted-product' with String payload\n- getStorefrontIOS returns { countryCode } to match Dart expectations
@hyochan hyochan force-pushed the feat/openiap-migration branch from 78ab532 to 7e6ab9c Compare September 9, 2025 04:18
Replace local error enum with OpenIapError E_* constants from the OpenIAP module and normalize a hardcoded service error. Also remove duplicate definitions of getPromotedProductIOS and showManageSubscriptionsIOS that caused invalid redeclaration build failures. This keeps error codes single‑sourced and restores a clean iOS build.
@hyochan hyochan marked this pull request as ready for review September 9, 2025 05:25
Make .githooks/pre-commit executable and enhance it to auto-format staged Dart files, re-stage them, and enforce a repo-wide format check before running tests. Prevents CI format failures by catching them locally.
Update pre-commit hook to auto-format staged Dart files, re-stage changes, and verify formatting across the repo. Make the hook executable and document enabling hooks in README (). Prevents CI format failures by catching them before push.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (6)
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (4)

396-401: Support skus per OpenIAP; keep productIds as fallback.

Aligns with current spec and Dart API.

-        val productIds : ArrayList<String> = call.argument<ArrayList<String>>("productIds")!!
+        val productIds: ArrayList<String> =
+            call.argument<ArrayList<String>>("skus")
+                ?: call.argument<ArrayList<String>>("productIds")
+                ?: arrayListOf()
+        if (productIds.isEmpty()) {
+            safeChannel.error(call.method, "E_INVALID_ARGS", "Missing 'skus' (or legacy 'productIds').")
+            return
+        }

75-77: Possible NPE: channel!! before it's guaranteed set.

Guard channel null and return a proper error.

-        safeResult = MethodResultWrapper(result, channel!!)
-        val safeChannel = MethodResultWrapper(result, channel!!)
+        val ch = channel ?: run {
+            result.error("E_CHANNEL_NULL", "MethodChannel not attached yet.", null)
+            return
+        }
+        safeResult = MethodResultWrapper(result, ch)
+        val safeChannel = MethodResultWrapper(result, ch)

191-196: Possible NPE: activity!! in URL open flow.

Gracefully handle when activity isn’t attached.

-        try{
-            activity!!.startActivity(Intent(Intent.ACTION_VIEW).apply { data = uri })
+        val act = activity ?: return false
+        try{
+            act.startActivity(Intent(Intent.ACTION_VIEW).apply { data = uri })

242-243: Compile-breaker: Purchase.developerPayload no longer exists (Billing v8).

Remove usage or replace with available identifiers (e.g., obfuscated ids). Leaving as-is will fail to build.

-                    item.put("developerPayloadAndroid", purchase.developerPayload)
+                    // developerPayload removed in V5+. Omit field to avoid build errors.
-                    item.put("developerPayloadAndroid", purchase.developerPayload)
+                    // developerPayload removed in V5+. Omit field to avoid build errors.
-                    item.put("developerPayloadAndroid", purchase.developerPayload)
+                    // developerPayload removed in V5+. Omit field to avoid build errors.

Also applies to: 373-374, 785-785

lib/types.dart (1)

1191-1247: Wire up subscriptionOfferDetailsAndroid in ProductAndroid.fromJson (or drop param)

The ctor forwards super.subscriptionOfferDetailsAndroid, but fromJson never sets it. Either parse and pass it, or remove the super-param to avoid confusion.

   factory ProductAndroid.fromJson(Map<String, dynamic> json) {
     return ProductAndroid(
       productId: json['productId'] as String? ?? '',
       price: json['price'] as String? ?? '0',
@@
       originalPriceAmount: (json['originalPriceAmount'] as num?)?.toDouble(),
       iconUrl: json['iconUrl'] as String?,
-      // Note: subscriptionOfferDetailsAndroid is only in Product class
+      // Forward Android-only details to the base Product
+      subscriptionOfferDetailsAndroid: json['subscriptionOfferDetailsAndroid'] != null
+          ? (json['subscriptionOfferDetailsAndroid'] as List)
+              .map((item) {
+                final map = _safeJsonMap(item);
+                return map != null ? OfferDetail.fromJson(map) : null;
+              })
+              .whereType<OfferDetail>()
+              .toList()
+          : null,
     );
   }
example/lib/src/screens/available_purchases_screen.dart (1)

135-151: Guard setState after awaits to prevent setState() called after dispose

risk: async tasks may complete after widget is disposed. Wrap post-await setState calls with mounted checks.

Apply this diff in both _initConnection and _loadPurchases (repeat pattern wherever setState follows awaits):

   try {
     await _iap.initConnection();
-    setState(() {
-      _connected = true;
-    });
+    if (!mounted) return;
+    setState(() {
+      _connected = true;
+    });
     await _loadPurchases();
   } catch (e) {
-    setState(() {
+    if (!mounted) return;
+    setState(() {
       _error = e.toString();
     });
   } finally {
-    setState(() {
+    if (!mounted) return;
+    setState(() {
       _loading = false;
     });
   }

Also applies to: 79-101

♻️ Duplicate comments (2)
example/ios/Flutter/Flutter.podspec (1)

14-14: Align iOS deployment target to 15.0 (StoreKit 2).

Example app should match plugin/docs minimum to avoid build mismatches.

-  s.ios.deployment_target = '13.0'
+  s.ios.deployment_target = '15.0'
lib/flutter_inapp_purchase.dart (1)

2034-2041: Breaking change: getPromotedProduct return type changed.

The return type changed from Future<String?> to Future<Map<String, dynamic>?>. While backward compatibility is attempted with the productIdentifier wrapper, this is still a breaking change for existing code that expects a string.

🧹 Nitpick comments (9)
README.md (1)

54-59: iOS notes look good—add pod repo update tip and confirm version sync.

Add a troubleshooting note and double-check that OpenIAP 1.1.7 matches Podspec/Podfile.

 ### iOS Notes
 - This plugin uses the OpenIAP Apple native module via CocoaPods (`openiap ~> 1.1.7`).
 - After upgrading, run `pod install` in your iOS project (e.g., `example/ios`).
 - Minimum iOS deployment target is `15.0` for StoreKit 2 support.
+- If CocoaPods can't find `openiap (1.1.7)`, run `pod repo update` then `pod install`.
docs/docs/api/error-codes.md (2)

92-107: Formatting-only churn.

Extra blank lines increase diff noise. Consider reverting or enforcing a docs formatter to keep minimal changes.


599-603: Update snippet to OpenIAP requestProducts + skus.

Docs should reflect current API/terminology.

-      final products = await FlutterInappPurchase.instance.getProducts([productId]);
+      final products = await FlutterInappPurchase.instance.requestProducts(
+        skus: [productId],
+        type: PurchaseType.inapp,
+      );
example/ios/Podfile (1)

35-36: Pinned OpenIAP via git tag—verify duplication with plugin Podspec.

This enforces 1.1.7 for the example, but the plugin also depends on openiap ~> 1.1.7. Confirm only one Pod is installed and no duplicate embed occurs; remove the direct Pod once trunk reliably has 1.1.7.

lib/types.dart (2)

1758-1869: Deduplicate iOS fields in Purchase.toString (several keys printed twice)

productTypeIOS, subscriptionGroupIdIOS, currencyCodeIOS, storeFrontCountryCodeIOS, and expirationDateIOS are emitted twice (and once with a lowercase “storefront”). Trim the duplicates to keep logs compact and consistent.

-      if (productTypeIOS != null) {
-        buffer.writeln('  productTypeIOS: "$productTypeIOS",');
-      }
@@
-      if (subscriptionGroupIdIOS != null) {
-        buffer.writeln('  subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",');
-      }
@@
-      if (currencyCodeIOS != null) {
-        buffer.writeln('  currencyCodeIOS: "$currencyCodeIOS",');
-      }
@@
-      if (storeFrontCountryCodeIOS != null) {
-        buffer.writeln(
-          '  storefrontCountryCodeIOS: "$storeFrontCountryCodeIOS",',
-        );
-      }
@@
-      if (expirationDateIOS != null) {
-        buffer.writeln('  expirationDateIOS: $expirationDateIOS,');
-      }

239-274: Defaulting Product.platformEnum to iOS can misclassify platform

If callers omit platformEnum, Android products may serialize as iOS. Consider defaulting based on an explicit platform string or requiring the enum.

test/ios_methods_test.dart (1)

85-90: Naming consistency: use IOS suffix for iOS-only Dart APIs

Test calls getPromotedProduct(), but other iOS-only APIs use IOS suffix (e.g., getStorefrontIOS, getPendingTransactionsIOS). Consider using getPromotedProductIOS() for consistency, if available, and assert the same channel method.

#!/bin/bash
# Find promoted product APIs to confirm available names
rg -nP --type=dart -C2 '\bgetPromotedProduct(IOS)?\s*\('
lib/errors.dart (1)

103-137: Add a few more user-friendly branches (optional)

Consider explicit messages for eIapNotAvailable and eUnknown to improve UX fallbacks.

   switch (code) {
+    case ErrorCode.eIapNotAvailable:
+      return 'In‑app purchases are not available on this device';
     ...
     default:
       // Try to surface message from PurchaseError if available
       if (error is PurchaseError && error.message.isNotEmpty) {
         return error.message;
       }
       if (error is Map && error['message'] is String) {
         return error['message'] as String;
       }
-      return 'An unexpected error occurred';
+      return 'An unexpected error occurred. Please try again.';
   }
ios/Classes/FlutterInappPurchasePlugin.swift (1)

267-275: Add input validation for SKU format.

The method handles both dictionary and array formats but doesn't validate the SKU format or content. Consider adding validation to prevent invalid SKUs from being sent to the native layer.

// Add SKU validation
for sku in skus {
    guard !sku.isEmpty && !sku.contains(" ") else {
        result(FlutterError(code: OpenIapError.E_DEVELOPER_ERROR, 
                          message: "Invalid SKU format: '\(sku)'", 
                          details: nil))
        return
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efceba6 and d202b29.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .githooks/pre-commit (1 hunks)
  • README.md (1 hunks)
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (2 hunks)
  • docs/docs/api/error-codes.md (24 hunks)
  • example/ios/Flutter/Flutter.podspec (1 hunks)
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1 hunks)
  • example/ios/Flutter/ephemeral/flutter_lldbinit (1 hunks)
  • example/ios/Podfile (2 hunks)
  • example/ios/Runner.xcodeproj/project.pbxproj (1 hunks)
  • example/lib/src/screens/available_purchases_screen.dart (1 hunks)
  • ios/Classes/FlutterInappPurchasePlugin.swift (10 hunks)
  • ios/flutter_inapp_purchase.podspec (1 hunks)
  • lib/enums.dart (0 hunks)
  • lib/errors.dart (2 hunks)
  • lib/flutter_inapp_purchase.dart (18 hunks)
  • lib/modules/ios.dart (2 hunks)
  • lib/types.dart (5 hunks)
  • test/flutter_inapp_purchase_test.dart (1 hunks)
  • test/ios_methods_test.dart (1 hunks)
  • test/subscription_flow_test.dart (1 hunks)
💤 Files with no reviewable changes (1)
  • lib/enums.dart
🧰 Additional context used
📓 Path-based instructions (9)
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • test/subscription_flow_test.dart
  • example/lib/src/screens/available_purchases_screen.dart
  • test/ios_methods_test.dart
  • test/flutter_inapp_purchase_test.dart
  • lib/modules/ios.dart
  • lib/errors.dart
  • lib/types.dart
  • lib/flutter_inapp_purchase.dart
test/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

test/**/*.dart: Add tests for new functionality or bug fixes
Ensure new code is covered by tests
For new features, add comprehensive tests
For bug fixes, write a failing test that demonstrates the bug

Files:

  • test/subscription_flow_test.dart
  • test/ios_methods_test.dart
  • test/flutter_inapp_purchase_test.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • test/subscription_flow_test.dart
  • example/lib/src/screens/available_purchases_screen.dart
  • test/ios_methods_test.dart
  • test/flutter_inapp_purchase_test.dart
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • lib/modules/ios.dart
  • lib/errors.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/types.dart
  • lib/flutter_inapp_purchase.dart
example/**

📄 CodeRabbit inference engine (GEMINI.md)

Update the example app when needed for new features

Files:

  • example/lib/src/screens/available_purchases_screen.dart
  • example/ios/Flutter/Flutter.podspec
  • example/ios/Runner.xcodeproj/project.pbxproj
  • example/ios/Flutter/ephemeral/flutter_lldbinit
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py
  • example/ios/Podfile
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using emojis in documentation, especially in headings

Files:

  • README.md
  • docs/docs/api/error-codes.md
{README.md,docs/**}

📄 CodeRabbit inference engine (GEMINI.md)

{README.md,docs/**}: Update relevant documentation when API changes are made
Document new features in README or API docs

Files:

  • README.md
  • docs/docs/api/error-codes.md
{android/**,ios/**}

📄 CodeRabbit inference engine (GEMINI.md)

For new features, implement for both iOS and Android when applicable

Files:

  • ios/flutter_inapp_purchase.podspec
  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • ios/Classes/FlutterInappPurchasePlugin.swift
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
  • lib/modules/ios.dart
  • lib/errors.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/types.dart
  • lib/flutter_inapp_purchase.dart
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/modules/ios.dart
  • lib/errors.dart
  • lib/types.dart
  • lib/flutter_inapp_purchase.dart
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: When adding a new feature, verify it aligns with the OpenIAP specification
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • test/subscription_flow_test.dart
  • test/flutter_inapp_purchase_test.dart
  • lib/types.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • test/ios_methods_test.dart
  • lib/modules/ios.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to test/**/*.dart : Add tests for new functionality or bug fixes

Applied to files:

  • test/ios_methods_test.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: All changes must pass `flutter test`

Applied to files:

  • test/ios_methods_test.dart
  • test/flutter_inapp_purchase_test.dart
  • .githooks/pre-commit
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • test/flutter_inapp_purchase_test.dart
  • docs/docs/api/error-codes.md
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • test/flutter_inapp_purchase_test.dart
  • docs/docs/api/error-codes.md
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use RequestProductsParams; it has been removed

Applied to files:

  • test/flutter_inapp_purchase_test.dart
  • lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)

Applied to files:

  • test/flutter_inapp_purchase_test.dart
  • docs/docs/api/error-codes.md
  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Use --set-exit-if-changed with dart format to match CI behavior

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Document new features in README or API docs

Applied to files:

  • README.md
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Id consistently across identifiers (e.g., productId, transactionId, offerId)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)

Applied to files:

  • lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Applied to files:

  • lib/flutter_inapp_purchase.dart
🪛 GitHub Actions: Flutter CI
test/ios_methods_test.dart

[error] 1-1: Dart format changed formatting in this file. Run 'dart format' to apply formatting.

lib/flutter_inapp_purchase.dart

[error] 1-1: Dart format changed formatting in this file. Run 'dart format' to apply formatting.

🔇 Additional comments (32)
example/ios/Runner.xcodeproj/project.pbxproj (1)

289-290: Verify if manual OpenIAP.framework entries are needed.

The Pods frameworks script usually handles embedding. Duplicating inputs/outputs can cause stale/duplicate-embed issues. Please confirm with a clean build or remove if redundant.

Also applies to: 293-295

ios/flutter_inapp_purchase.podspec (1)

17-19: OpenIAP dependency addition looks correct.

Ensure CocoaPods resolves 1.1.7 from trunk; example Podfile pins via git.

example/ios/Podfile (1)

1-2: Add trunk CDN—LGTM.

android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (1)

557-559: Correct key read for replacement mode—LGTM.

Fallback to legacy key preserves backward compatibility.

example/ios/Flutter/ephemeral/flutter_lldbinit (1)

5-5: LGTM: imports the helper relative to the init file

This keeps the helper load stable across build paths.

lib/types.dart (3)

48-51: Const ctor for PurchaseOptions is good

Reduces allocations at callsites.


340-349: OK: Android-only field usage

Parsing only subscriptionOfferDetailsAndroid matches the migration away from legacy fields.

If any tests still send subscriptionOfferDetails, update fixtures to subscriptionOfferDetailsAndroid.


820-829: OK: Same alignment in Subscription.fromJson

Consistent with Product.fromJson.

test/flutter_inapp_purchase_test.dart (1)

181-185: iOS requestProducts uses fetchProducts with skus/type — LGTM

Matches OpenIAP: passing { skus, type } where type is ProductType.subs. Good.

test/subscription_flow_test.dart (1)

83-98: Updated iOS mock to fetchProducts — LGTM

Switching to 'fetchProducts' with { skus, type } aligns with the iOS path in this PR.

lib/modules/ios.dart (1)

111-124: Channel method renames to IOS-suffixed endpoints — LGTM

presentCodeRedemptionSheetIOS/showManageSubscriptionsIOS correctly target iOS-specific entrypoints and enforce platform checks.

example/lib/src/screens/available_purchases_screen.dart (2)

112-116: Correct: filter active items on iOS via PurchaseOptions — LGTM

Using onlyIncludeActiveItemsIOS for available purchases matches the new iOS behavior.


123-132: History source selection per platform — LGTM

iOS via getAvailablePurchases(...false) and Android via getPurchaseHistories() is consistent with the migration notes.

test/ios_methods_test.dart (1)

1-101: Run dart format to fix pipeline failure

CI reports formatting changes. Please run: dart format .

#!/bin/bash
# Format Dart files and show diff
dart format .
git --no-pager diff --exit-code || true
lib/errors.dart (2)

85-101: Robust error normalization — LGTM

_normalizeToErrorCode sensibly handles PurchaseError, String/Map codes, and legacy ints via ErrorCodeUtils. Good cross-platform coverage.


235-257: String/legacy code mapping in fromPlatformCode — LGTM

Accepts OpenIAP string codes across platforms and falls back to iOS numeric mapping; safe default to eUnknown.

lib/flutter_inapp_purchase.dart (8)

10-10: LGTM! Good practice using import alias.

Using the alias iap_err for the errors import helps avoid naming conflicts and makes the code more readable.


208-212: Good alignment with OpenIAP specification.

The iOS implementation now correctly uses fetchProducts with explicit type parameters, properly following the OpenIAP specification. This aligns with the learnings about using the correct OpenIAP terminology.


631-632: Fix method name inconsistency with OpenIAP.

The native iOS method is called getStorefrontIOS, but this should be consistent throughout the codebase as verified by the learnings.


672-672: Good consistency in iOS method naming.

All iOS-specific methods now consistently use the IOS suffix as per the coding guidelines: presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS, getPendingTransactionsIOS, etc. This follows the established naming convention.

Also applies to: 698-698, 1404-1404, 2005-2005, 2017-2017


744-796: Excellent data normalization for iOS fields.

The consistent use of toString() for all string fields ensures type safety and prevents runtime errors from mixed types. This is particularly important for fields like productId, price, currency that Dart expects as strings.


1214-1223: Smart error code handling with OpenIAP string codes.

The implementation correctly prioritizes OpenIAP string codes over legacy numeric codes, providing better cross-platform compatibility. The fallback mechanism ensures backward compatibility.


1993-1995: Verify the OpenIAP restore flow implementation.

The restore flow performs an end/init cycle which might cause state loss or event listener disruption. This approach seems unusual for a restore operation.

#!/bin/bash
# Check if OpenIAP module has a dedicated restore method
ast-grep --pattern 'restorePurchases' 
rg -n "restorePurchases|restoreTransactions" --type swift

Consider documenting why this approach was chosen or implementing a dedicated restore method if available in OpenIAP.


1354-1364: Remove deprecated parameter from method signature.

The prorationMode parameter is marked as deprecated but replacementModeAndroid is already available. The deprecated parameter should be removed to avoid confusion.

 Future<dynamic> requestSubscription(
   String productId, {
   int? replacementModeAndroid,
   String? obfuscatedAccountIdAndroid,
   String? obfuscatedProfileIdAndroid,
   String? purchaseTokenAndroid,
   int? offerTokenIndex,
 }) async {

Likely an incorrect or invalid review comment.

ios/Classes/FlutterInappPurchasePlugin.swift (8)

3-4: LGTM! Clean transition to OpenIAP.

The import changes clearly show the migration from StoreKit to OpenIAP, with appropriate comments explaining the architectural decision.


26-27: Good practice: Early listener setup.

Setting up OpenIAP listeners during plugin registration (Expo-style) ensures events aren't missed during initialization. This is a solid architectural decision.


136-137: LGTM! Proper no-op implementation.

Changed from returning FlutterMethodNotImplemented to returning nil, preventing unnecessary exceptions on the Dart side as suggested in previous reviews.


228-235: LGTM! Event name and payload format fixed.

The promoted product event now correctly uses iap-promoted-product with a string payload, matching what the Dart side expects. This fixes the issue identified in previous reviews.


472-472: LGTM! Return format fixed for getStorefrontIOS.

Now correctly returns a dictionary with countryCode key, matching what the Dart side expects when it reads result['countryCode'].


191-208: Good type coercion for Dart compatibility.

The explicit string conversion for iOS-specific fields ensures Dart receives the expected types, preventing runtime type errors. This is particularly important for fields like webOrderLineItemIdIOS and transactionId.


516-523: Excellent dual-field support for OpenIAP compatibility.

Providing both jwsRepresentation and purchaseToken (with the same value) ensures compatibility with both the OpenIAP specification and legacy code. This is a thoughtful approach to migration.


442-459: Verify promoted product serialization consistency.

The method fetches the promoted product and then re-fetches it to get full serialization. This seems inefficient and might return different data if the product changes between calls.

#!/bin/bash
# Check if OpenIAP provides complete product data in getPromotedProductIOS
ast-grep --pattern 'getPromotedProductIOS' 
rg -A 10 "getPromotedProductIOS.*OpenIapProduct" --type swift

Consider caching the promoted product data or ensuring getPromotedProductIOS returns complete product information.

Run tests in pre-commit only when RUN_PRECOMMIT_TESTS=1 is set. Keeps hooks fast while still enabling teams to opt into test runs locally. README updated with instructions.
- Pre-commit: run flutter analyze (non-blocking by default, block when ENFORCE_ANALYZE=1).\n- README: document analyzer and test env toggles.\n- .gitignore: ignore example/ios/Flutter/ephemeral.\n- Dart: use explicit IapPlatform.ios/android in error paths per review.
Use iap_err.getCurrentPlatform() in receipt validation error paths to report the actual runtime platform, as discussed in review. Keeps success path platform explicit for clarity.
Run dart format across the repo so that the CI step 'dart format --set-exit-if-changed .' no longer detects diffs. This addresses the formatting failure caused by test/ios_methods_test.dart changes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
.githooks/pre-commit (1)

21-27: Make analyzer blocking by default.

Don’t mask analyzer failures; fail the commit unless explicitly opted out.

Apply:

 echo "[pre-commit] Running analyzer..."
-if [ "${ENFORCE_ANALYZE:-0}" = "1" ]; then
-  flutter analyze
-else
-  # Do not fail commit on analyzer warnings by default
-  flutter analyze || true
-fi
+flutter analyze
🧹 Nitpick comments (2)
.githooks/pre-commit (1)

9-14: Handle filenames with spaces/newlines in staged formatting.

Use NUL-delimited paths to avoid word-splitting issues.

Apply:

-# Collect staged Dart files and format them in place
-STAGED_DART_FILES=$(git diff --cached --name-only --diff-filter=ACMRT | grep -E '\.dart$' || true)
-if [ -n "$STAGED_DART_FILES" ]; then
-  dart format $STAGED_DART_FILES >/dev/null
-  # Re-stage formatted files
-  echo "$STAGED_DART_FILES" | xargs git add -A --
-fi
+# Collect staged Dart files (NUL-delimited) and format them in place
+mapfile -d '' STAGED_DART_FILES < <(git diff --cached --name-only -z --diff-filter=ACMRT | grep -z -E '\.dart$' || true)
+if [ ${#STAGED_DART_FILES[@]} -gt 0 ]; then
+  printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 dart format >/dev/null
+  # Re-stage formatted files
+  printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 git add -A --
+fi
@@
-echo "[pre-commit] Verifying format on staged Dart files..."
-if [ -n "$STAGED_DART_FILES" ]; then
-  dart format --output=none --set-exit-if-changed $STAGED_DART_FILES
-fi
+echo "[pre-commit] Verifying format on staged Dart files..."
+if [ ${#STAGED_DART_FILES[@]} -gt 0 ]; then
+  printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 dart format --output=none --set-exit-if-changed
+fi

Also applies to: 16-19

lib/flutter_inapp_purchase.dart (1)

129-134: Unify platform reporting in errors/results.

For consistency and accurate diagnostics, prefer iap_err.getCurrentPlatform() over conditional _platform.isIOS ? ... : ... wherever setting platform in PurchaseError/ReceiptValidationResult.

Apply pattern:

-        platform: _platform.isIOS
-            ? iap_types.IapPlatform.ios
-            : iap_types.IapPlatform.android,
+        platform: iap_err.getCurrentPlatform(),

Note: Keep explicit platform only where the API is strictly single-platform and you intentionally want a fixed value.

Also applies to: 149-154, 175-180, 191-197, 254-260, 271-276, 285-289, 321-327, 383-387, 507-512, 548-553, 572-577, 609-614, 637-643, 649-654, 675-680, 701-706, 1267-1270, 1961-1967, 1736-1741, 1845-1846, 1851-1852

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d202b29 and 285c03e.

📒 Files selected for processing (4)
  • .githooks/pre-commit (1 hunks)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • lib/flutter_inapp_purchase.dart (17 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
🧰 Additional context used
📓 Path-based instructions (4)
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • lib/flutter_inapp_purchase.dart
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • lib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • lib/flutter_inapp_purchase.dart
🧠 Learnings (21)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Applied to files:

  • .gitignore
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)

Applied to files:

  • .gitignore
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code should pass `flutter analyze`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code must be formatted with `dart format`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)

Applied to files:

  • lib/flutter_inapp_purchase.dart
🔇 Additional comments (3)
.gitignore (1)

41-45: Good ignore additions for generated artifacts.

Ignoring *.cxx/ and example/ios/Flutter/ephemeral/ prevents noisy diffs from build/LLDB helper files. Looks good.

lib/flutter_inapp_purchase.dart (2)

208-212: Correct iOS fetch: using fetchProducts with {skus, type}.

Matches OpenIAP and the project’s “no params object” guideline for requestProducts. LGTM.


2037-2046: API shape for promoted product aligned (Map-returning).

Switch to Future<Map<String, dynamic>?> resolves the earlier type mismatch risk. Back-compat for string payload is a nice touch.

On iOS, perform syncIOS and ignore sync errors, then fetch available purchases. On Android, fetch available purchases directly. Matches desired behavior to always refresh state while soft-failing sync, and consolidates logic into a cross-platform implementation.
- Remove getAppTransactionTyped and clearTransactionCache (not in OpenIAP spec).\n- Replace getProductsAsync with fetchProducts wrapper that calls requestProducts.\n- Remove presentCodeRedemptionSheet and showManageSubscriptions; use iOS-suffixed methods from iOS mixin.\n- Add clearTransactionIOS and getPromotedProductIOS to iOS mixin.
- Remove getAppTransactionTyped, clearTransactionCache, presentCodeRedemptionSheet, showManageSubscriptions, getProductsAsync.\n- Add fetchProducts wrapper, clearTransactionIOS, getPromotedProductIOS.\n- Update examples to use presentCodeRedemptionSheetIOS and showManageSubscriptionsIOS.
Expose requestPurchaseOnPromotedProductIOS in the iOS mixin to mirror native support and align with the documented API surface.
- Pre-commit now runs flutter test by default (skip with SKIP_PRECOMMIT_TESTS=1; set PRECOMMIT_TEST_CONCURRENCY to tune).\n- Fix ios_methods_test by removing const FakePlatform and returning proper getStorefrontIOS map shape.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
lib/flutter_inapp_purchase.dart (1)

538-541: Fix iOS getAvailablePurchases double-encoding bug.

Passing json.encode(result) to extractPurchases will break when native already returns a JSON string (common on iOS). Pass the raw result so extractPurchases can handle String or List.

Apply:

-        dynamic result = await _channel.invokeMethod('getAvailableItems', args);
-        final items = extractPurchases(json.encode(result)) ?? [];
+        final dynamic result =
+            await _channel.invokeMethod('getAvailableItems', args);
+        final items = extractPurchases(result) ?? [];
         return items;
♻️ Duplicate comments (3)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)

9-19: Good defensive register/memory handling

Using FindRegister(), validating x0/x1, bounding write size, and checking WriteMemory results address prior concerns. Thanks for tightening this up.

Also applies to: 21-33

lib/flutter_inapp_purchase.dart (2)

1215-1224: Optional: prefer runtime platform for error mapping.

Consider using iap_err.getCurrentPlatform() instead of _platform.isIOS ternaries to keep diagnostics fully runtime-accurate (maintainer preference).

Apply:

-      final detected = iap_err.ErrorCodeUtils.fromPlatformCode(
-        result.code!,
-        _platform.isIOS ? IapPlatform.ios : IapPlatform.android,
-      );
+      final detected = iap_err.ErrorCodeUtils.fromPlatformCode(
+        result.code!,
+        iap_err.getCurrentPlatform(),
+      );

1962-1971: getPromotedProduct now returns Map — resolves prior mismatch.

Backwards-compatible wrapping of String → {'productIdentifier': ...} is a good touch.

🧹 Nitpick comments (4)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (2)

35-43: Make breakpoint setup idempotent to avoid duplicates on re-imports

If this script is imported multiple times (e.g., LLDB restart, lldbinit reload), you’ll create duplicate breakpoints. Name the bp and skip creation when it already exists. Uses SBTarget.FindBreakpointsByName/SBBreakpointList. (lldb.llvm.org)

 def __lldb_init_module(debugger: lldb.SBDebugger, _):
-    target = debugger.GetDummyTarget()
+    target = debugger.GetDummyTarget()
+    name = "openiap.rxpages"
+    try:
+        blist = lldb.SBBreakpointList(target)
+        if target.FindBreakpointsByName(name, blist) and blist.GetSize() > 0:
+            print("-- LLDB integration already loaded --")
+            return
+    except Exception:
+        # Best-effort: older LLDBs may lack SBBreakpointList/FindBreakpointsByName
+        pass
@@
-    bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
+    bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
+    if hasattr(bp, "AddNameWithErrorHandling"):
+        err = bp.AddNameWithErrorHandling(name)  # Newer LLDBs
+        if hasattr(err, "Success") and not err.Success():
+            print(f"Warning: failed to name breakpoint '{name}': {err}")
+    elif hasattr(bp, "AddName"):
+        bp.AddName(name)  # Fallback for older LLDBs
     bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
     bp.SetAutoContinue(True)
     print("-- LLDB integration loaded --")

37-39: Tighten comment phrasing

Suggest: “Use BreakpointCreateByRegex (not BreakpointCreateByName). The callback on the dummy target doesn’t carry over otherwise.”

-    # Caveat: must use BreakpointCreateByRegEx here and not
-    # BreakpointCreateByName. For some reasons callback function does not
-    # get carried over from dummy target for the later.
+    # Caveat: use BreakpointCreateByRegex (not BreakpointCreateByName).
+    # The callback on the dummy target otherwise doesn't carry over to later targets.
lib/modules/ios.dart (2)

115-118: New helper clearTransactionIOS().

Looks fine for iOS-only no-op on other platforms. Optional: return a bool to indicate whether a clear was performed.


121-128: getPromotedProductIOS() return-shape handling is solid.

Gracefully supports Map or String and normalizes to Map<String, dynamic>. Consider documenting expected keys (e.g., productIdentifier) to set API expectations for integrators.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 285c03e and ba8c526.

📒 Files selected for processing (7)
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1 hunks)
  • example/lib/src/screens/debug_purchases_screen.dart (1 hunks)
  • example/lib/src/screens/offer_code_screen.dart (1 hunks)
  • example/lib/storekit2_demo.dart (2 hunks)
  • lib/flutter_inapp_purchase.dart (16 hunks)
  • lib/modules/ios.dart (2 hunks)
  • test/ios_methods_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/ios_methods_test.dart
🧰 Additional context used
📓 Path-based instructions (5)
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • example/lib/storekit2_demo.dart
  • example/lib/src/screens/debug_purchases_screen.dart
  • example/lib/src/screens/offer_code_screen.dart
  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • example/lib/storekit2_demo.dart
  • example/lib/src/screens/debug_purchases_screen.dart
  • example/lib/src/screens/offer_code_screen.dart
  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
example/**

📄 CodeRabbit inference engine (GEMINI.md)

Update the example app when needed for new features

Files:

  • example/lib/storekit2_demo.dart
  • example/lib/src/screens/debug_purchases_screen.dart
  • example/lib/src/screens/offer_code_screen.dart
  • example/ios/Flutter/ephemeral/flutter_lldb_helper.py
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
🧠 Learnings (13)
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.

Applied to files:

  • example/lib/storekit2_demo.dart
  • example/lib/src/screens/debug_purchases_screen.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • example/lib/storekit2_demo.dart
  • lib/modules/ios.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)

Applied to files:

  • example/lib/storekit2_demo.dart
  • example/lib/src/screens/debug_purchases_screen.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: According to the OpenIAP specification at https://www.openiap.dev/docs/types#request-types, the parameter name for requesting products should be "skus" rather than "productIds" to maintain compliance with the OpenIAP standard.

Applied to files:

  • lib/flutter_inapp_purchase.dart
🪛 Ruff (0.12.2)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py

7-7: Unused function argument: bp_loc

(ARG001)


7-7: Unused function argument: extra_args

(ARG001)


7-7: Unused function argument: intern_dict

(ARG001)

🔇 Additional comments (17)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)

1-4: Confirm committing “ephemeral/” is intentional

This path is usually regenerated by Flutter (flutter clean can wipe it). If you intend to keep this script, consider moving it to a stable path and importing it from lldbinit, or add safeguards in tooling.

example/lib/src/screens/offer_code_screen.dart (1)

73-73: iOS-specific API rename looks good.

presentCodeRedemptionSheetIOS() aligns with the new IOS suffix guideline and the platform guard above. No further changes needed.

example/lib/src/screens/debug_purchases_screen.dart (1)

136-136: Correct iOS method usage.

showManageSubscriptionsIOS() matches the IOS suffix convention and existing iOS guard. Fallback flow is appropriate.

example/lib/storekit2_demo.dart (1)

205-206: Consistent migration to IOS APIs.

Both presentCodeRedemptionSheetIOS() and showManageSubscriptionsIOS() are correctly adopted with platform gating. Also good to see requestProducts using direct parameters (skus, type).

Also applies to: 225-226

lib/modules/ios.dart (2)

111-112: Channel method rename is correct.

presentCodeRedemptionSheetIOS channel route matches the Dart API.


139-139: showManageSubscriptionsIOS channel invocation aligned.

Route name matches Dart exposure; platform guard above is correct.

lib/flutter_inapp_purchase.dart (11)

10-10: Namespaced errors import is good.

Using errors.dart as iap_err avoids type/name collisions with types.dart.


166-167: endConnection wrapper calls channel directly — OK.

Method keeps state flag updates within the wrapper. No action needed.


206-209: iOS requestProducts path correctly uses fetchProducts arguments.

Complies with "direct parameters (skus, type)" guideline.


629-630: Updated iOS storefront channel route.

getStorefrontIOS path matches native side and iOS-only contract.


668-670: presentCodeRedemptionSheetIOS channel call aligned.

Good adherence to IOS suffix and error handling wrapper.


694-696: showManageSubscriptionsIOS channel call aligned.

Matches example usage and platform contract.


739-816: Product JSON normalization LGTM.

Consistent toString() normalization and IOS/Android field mapping reduce runtime type issues across platforms.

Also applies to: 819-855, 858-881


1333-1360: Android subscription replacement validation is correct.

DeveloperError when replacement mode set without purchaseTokenAndroid prevents bad upgrades/downgrades.


1631-1631: Good: use runtime platform in validation paths.

Using iap_err.getCurrentPlatform() in ReceiptValidationResult aligns with maintainer guidance.

Also applies to: 1637-1637, 1782-1782, 1788-1788, 1794-1794, 1801-1801, 1808-1808, 1814-1814


1895-1901: fetchProducts wrapper is fine.

Thin alias to requestProducts with direct parameters meets the simplified API design.


1930-1951: restorePurchases flow improvement LGTM.

Lightweight iOS sync via syncIOS() with soft-fail and then getAvailablePurchases() is a sensible cross-platform behavior.

…nsactionCache

- Handle 'requestPurchase' with OpenIAP props; remove 'buyProduct' case and its helper.\n- Remove 'clearTransactionCache' method case from iOS plugin.\n- Update Dart calls to invoke 'requestPurchase' on iOS.\n- Update tests to expect 'requestPurchase' and mock it accordingly.
- Update iOS invocation to 'requestPurchase' for purchaseAsync.\n- Update tests to mock/expect 'requestPurchase' instead of 'buyProduct'.
@hyochan hyochan merged commit b8fec85 into main Sep 9, 2025
2 of 3 checks passed
@hyochan hyochan deleted the feat/openiap-migration branch September 9, 2025 07:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
.githooks/pre-commit (1)

21-27: Nice: analyzer step added per earlier review

Thanks for adding the analyzer to the hook; this addresses the prior suggestion.

lib/flutter_inapp_purchase.dart (3)

1962-1973: Address type mismatch in getPromotedProduct.

Based on the past review comments, this method still has a type mismatch issue. The Dart method signature declares Future<Map<String, dynamic>?> but existing code may expect a string product identifier.

Consider providing a migration path or ensuring backward compatibility for code that expects a string result.


1215-1224: Fix unresolved identifier in error handling.

The code references IapPlatform without proper namespace qualification, which will cause a compilation error.

Apply this fix:

-        _platform.isIOS ? IapPlatform.ios : IapPlatform.android,
+        iap_err.getCurrentPlatform(),

This also aligns with the maintainer's preference for using runtime platform detection.


1373-1373: Fix method name to follow iOS naming convention.

The method call should use the iOS-specific naming pattern with the IOS suffix.

Apply this fix:

-      dynamic result = await _channel.invokeMethod('getPendingTransactionsIOS');
+      final dynamic result = await _channel.invokeMethod('getPendingTransactionsIOS');

The method name is already correct, but improve the variable declaration style for consistency.

🧹 Nitpick comments (13)
.githooks/pre-commit (5)

4-6: Gate pub get to avoid unnecessary network work

Running flutter pub get on every commit can slow local loops. Make it opt-in or conditional.

-echo "[pre-commit] Running flutter pub get..."
-flutter pub get >/dev/null
+if [ "${SKIP_PUB_GET:-0}" != "1" ]; then
+  echo "[pre-commit] Running flutter pub get..."
+  flutter pub get >/dev/null
+fi

7-14: Handle filenames with spaces/newlines when formatting and re-staging

Current approach splits on whitespace; switch to NUL-delimited files from git to be robust.

 echo "[pre-commit] Auto-formatting staged Dart files..."
-# Collect staged Dart files and format them in place
-STAGED_DART_FILES=$(git diff --cached --name-only --diff-filter=ACMRT | grep -E '\.dart$' || true)
-if [ -n "$STAGED_DART_FILES" ]; then
-  dart format $STAGED_DART_FILES >/dev/null
-  # Re-stage formatted files
-  echo "$STAGED_DART_FILES" | xargs git add -A --
-fi
+# Format staged Dart files robustly (handles spaces/newlines)
+while IFS= read -r -d '' file; do
+  case "$file" in
+    *.dart)
+      dart format "$file" >/dev/null
+      git add -- "$file"
+    ;;
+  esac
+done < <(git diff --cached --name-only -z --diff-filter=ACMRT)

16-19: Align with team learning: verify repo-wide formatting once before commit

Prior learnings emphasize running dart format --set-exit-if-changed . as a final check. Also removes reliance on the earlier STAGED_DART_FILES var.

-echo "[pre-commit] Verifying format on staged Dart files..."
-if [ -n "$STAGED_DART_FILES" ]; then
-  dart format --output=none --set-exit-if-changed $STAGED_DART_FILES
-fi
+echo "[pre-commit] Verifying format..."
+if [ "${VERIFY_ALL_FORMAT:-1}" = "1" ]; then
+  dart format --output=none --set-exit-if-changed .
+else
+  # Verify only staged Dart files when full verification is disabled
+  while IFS= read -r -d '' file; do
+    case "$file" in
+      *.dart) dart format --output=none --set-exit-if-changed "$file" ;;
+    esac
+  done < <(git diff --cached --name-only -z --diff-filter=ACMRT)
+fi

21-27: Enforce analyzer by default and skip implicit pub to speed runs

Matches retrieved learnings (“Run flutter analyze and ensure it passes before committing.”) and avoids redundant pub.

 echo "[pre-commit] Running analyzer..."
-if [ "${ENFORCE_ANALYZE:-0}" = "1" ]; then
-  flutter analyze
+if [ "${ENFORCE_ANALYZE:-1}" = "1" ]; then
+  flutter analyze --no-pub
 else
   # Do not fail commit on analyzer warnings by default
-  flutter analyze || true
+  flutter analyze --no-pub || true
 fi

Would you like me to also switch to dart analyze with --fatal-warnings/--fatal-infos for stricter gating?


29-32: Tweak tests step: remove “fast fail” wording and avoid implicit pub

flutter test doesn’t support a fail-fast flag; add --no-pub to save time since pub get already ran.

 if [ "${SKIP_PRECOMMIT_TESTS:-0}" != "1" ]; then
-  echo "[pre-commit] Running tests (fast fail)..."
-  flutter test --concurrency="${PRECOMMIT_TEST_CONCURRENCY:-4}"
+  echo "[pre-commit] Running tests..."
+  flutter test --no-pub --concurrency="${PRECOMMIT_TEST_CONCURRENCY:-4}"
 fi
ios/Classes/FlutterInappPurchasePlugin.swift (8)

43-46: canMakePayments always returns true — consider delegating to OpenIAP.

If OpenIAP exposes a capability check, prefer returning its result to avoid false positives on restricted devices.

-            // OpenIAP abstraction: assume payments can be made once initialized
-            result(true)
+            // Prefer real capability if available
+            if let can = try? await OpenIapModule.shared.canMakePayments() {
+                result(can)
+            } else {
+                result(true)
+            }

156-164: Return a truthy value for initConnection to preserve prior API shape.

Dart often expects a boolean. Returning nil can propagate as null.

-                _ = try await OpenIapModule.shared.initConnection()
-                result(nil)
+                _ = try await OpenIapModule.shared.initConnection()
+                result(true)

171-174: Same for endConnection: return true on success.

Keeps parity with Android path and common expectations.

-            _ = try? await OpenIapModule.shared.endConnection()
-            result(nil)
+            _ = try? await OpenIapModule.shared.endConnection()
+            result(true)

226-234: Remove unused variable in promotedProduct listener.

‘payload’ is computed but not used.

-                let payload: [String: Any] = ["productId": productId]
-                // Emit event that Dart expects: name 'iap-promoted-product' with String payload
-                self.channel?.invokeMethod("iap-promoted-product", arguments: productId)
+                // Emit event that Dart expects: name 'iap-promoted-product' with String payload
+                self.channel?.invokeMethod("iap-promoted-product", arguments: productId)

362-371: SwiftLint: redundant optional initialization.

Initialize optionals without “= nil”.

-        var withOffer: OpenIapDiscountOffer? = nil
+        var withOffer: OpenIapDiscountOffer?

525-533: Unify error shape in clearTransactionIOS.

For consistency with other methods, use standardized error codes/messages and move the underlying error into details.

-                await MainActor.run {
-                    result(FlutterError(code: OpenIapError.E_SERVICE_ERROR, message: error.localizedDescription, details: nil))
-                }
+                await MainActor.run {
+                    let code = OpenIapError.E_SERVICE_ERROR
+                    result(FlutterError(code: code, message: defaultMessage(for: code), details: error.localizedDescription))
+                }

176-183: Dead code cleanup: updateListenerTask appears unused.

If no longer needed, remove the property and related cleanup.

-    private var updateListenerTask: Task<Void, Never>?
...
-        updateListenerTask?.cancel()
-        updateListenerTask = nil

300-346: Minor: nested MainActor dispatch is redundant.

Methods are already @mainactor; the extra MainActor.run blocks can be dropped.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba8c526 and 79bf5c0.

📒 Files selected for processing (7)
  • .githooks/pre-commit (1 hunks)
  • ios/Classes/FlutterInappPurchasePlugin.swift (8 hunks)
  • lib/flutter_inapp_purchase.dart (18 hunks)
  • lib/modules/ios.dart (2 hunks)
  • test/ios_methods_test.dart (1 hunks)
  • test/purchase_flow_test.dart (1 hunks)
  • test/subscription_flow_test.dart (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/modules/ios.dart
🧰 Additional context used
📓 Path-based instructions (6)
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology

**/*.dart: Code must be formatted with dart format
Code should pass flutter analyze

Files:

  • test/purchase_flow_test.dart
  • test/ios_methods_test.dart
  • test/subscription_flow_test.dart
  • lib/flutter_inapp_purchase.dart
test/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

test/**/*.dart: Add tests for new functionality or bug fixes
Ensure new code is covered by tests
For new features, add comprehensive tests
For bug fixes, write a failing test that demonstrates the bug

Files:

  • test/purchase_flow_test.dart
  • test/ios_methods_test.dart
  • test/subscription_flow_test.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory

Files:

  • test/purchase_flow_test.dart
  • test/ios_methods_test.dart
  • test/subscription_flow_test.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}

📄 CodeRabbit inference engine (GEMINI.md)

Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
{android/**,ios/**}

📄 CodeRabbit inference engine (GEMINI.md)

For new features, implement for both iOS and Android when applicable

Files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
lib/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Follow the simplified API design where methods use direct parameters instead of parameter objects

Files:

  • lib/flutter_inapp_purchase.dart
🧠 Learnings (25)
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: All changes must pass `flutter test`

Applied to files:

  • test/purchase_flow_test.dart
  • test/ios_methods_test.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)

Applied to files:

  • test/purchase_flow_test.dart
  • test/subscription_flow_test.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)

Applied to files:

  • test/ios_methods_test.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to test/**/*.dart : Add tests for new functionality or bug fixes

Applied to files:

  • test/ios_methods_test.dart
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.

Applied to files:

  • test/ios_methods_test.dart
  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object

Applied to files:

  • test/subscription_flow_test.dart
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code should pass `flutter analyze`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code must be formatted with `dart format`

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0

Applied to files:

  • .githooks/pre-commit
📚 Learning: 2025-08-21T02:01:17.634Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Id consistently across identifiers (e.g., productId, transactionId, offerId)

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".

Applied to files:

  • ios/Classes/FlutterInappPurchasePlugin.swift
  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases

Applied to files:

  • lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: According to the OpenIAP specification at https://www.openiap.dev/docs/types#request-types, the parameter name for requesting products should be "skus" rather than "productIds" to maintain compliance with the OpenIAP standard.

Applied to files:

  • lib/flutter_inapp_purchase.dart
🪛 SwiftLint (0.57.0)
ios/Classes/FlutterInappPurchasePlugin.swift

[Warning] 362-362: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🔇 Additional comments (26)
.githooks/pre-commit (1)

1-3: Good: strict Bash mode in a shebanged hook

set -euo pipefail is appropriate for a CI-like pre-commit workflow.

test/purchase_flow_test.dart (1)

303-304: iOS rename to requestPurchase is correct.

Matches the new API and guideline to use the request prefix for event-driven calls.

test/subscription_flow_test.dart (3)

49-51: Mock handler updated to requestPurchase.

Aligned with OpenIAP rename; keeps tests consistent with the plugin surface.


83-97: iOS fetchProducts mock aligns with new contract.

Using { skus, type } and filtering by skus matches the updated API and naming spec (“skus”).


332-334: Assertion updated to requestPurchase.

Good: the iOS path now expects requestPurchase and passes sku.

test/ios_methods_test.dart (4)

68-77: IOS-specific method tests look good.

presentCodeRedemptionSheetIOS and showManageSubscriptionsIOS are invoked and asserted correctly.


79-83: getStorefrontIOS expectation matches wrapper behavior.

Native returns { countryCode }, Dart wrapper returns the String — test is correct.


92-98: getPendingTransactionsIOS test is solid.

Mocks a native list and asserts typed Purchase fields; channel method name matches.


85-90: Keep getPromotedProduct as-is. It’s the public, platform-agnostic API dispatching internally to getPromotedProductIOS, which aligns with our naming guidelines.

ios/Classes/FlutterInappPurchasePlugin.swift (1)

496-500: Storefront response shape matches Dart wrapper expectations.

Good fix to return { countryCode } map.

lib/flutter_inapp_purchase.dart (16)

10-10: LGTM! Namespaced import aligns with error handling preferences.

The change to namespace the import as iap_err follows the established pattern and supports the maintainer's preference for using iap_err.getCurrentPlatform() in error handling.


166-167: LGTM! Consistent channel method invocation.

The unified endConnection method call removes platform-specific duplication while maintaining the same functionality.


205-209: LGTM! iOS method properly uses fetchProducts with type parameter.

The iOS implementation correctly calls fetchProducts with explicit type parameter, aligning with OpenIAP conventions per the coding guidelines.


308-311: LGTM! iOS purchase request properly includes appAccountToken parameter.

The method call correctly includes the appAccountToken parameter for iOS purchase requests, maintaining OpenIAP compliance.


629-629: LGTM! Method name follows iOS naming convention.

The method name getStorefrontIOS correctly follows the coding guidelines for iOS-specific methods using the IOS suffix.


669-669: LGTM! Method name follows iOS naming convention.

The method call presentCodeRedemptionSheetIOS correctly follows the coding guidelines for iOS-specific methods using the IOS suffix.


695-695: LGTM! Method name follows iOS naming convention.

The method call showManageSubscriptionsIOS correctly follows the coding guidelines for iOS-specific methods using the IOS suffix.


741-863: LGTM! String normalization ensures consistent data types.

The extensive use of .toString() for various fields ensures consistent string types across iOS and Android platforms, preventing type-related issues in Dart.


1333-1333: LGTM! Simplified variable assignment.

The direct assignment of replacementModeAndroid to effectiveReplacementMode removes unnecessary intermediate processing while maintaining the same functionality.


1361-1361: LGTM! iOS subscription request uses correct method.

The method call requestPurchase for iOS subscriptions follows the OpenIAP pattern and coding guidelines.


1631-1631: LGTM! Error handling uses runtime platform detection.

Based on the retrieved learnings, using iap_err.getCurrentPlatform() in error handling is the preferred approach as it provides accurate runtime platform information for debugging and error reporting.

Also applies to: 1637-1637, 1781-1781, 1787-1787, 1793-1793, 1800-1800, 1807-1807, 1813-1813


1895-1901: LGTM! Generic fetchProducts method aligns with OpenIAP.

The new fetchProducts method with generic typing follows OpenIAP conventions and properly delegates to the existing requestProducts implementation.


1926-1926: LGTM! Documentation clarifies API changes.

The comment properly documents the removal of finishTransactionAsync in favor of the more consistent finishTransaction(Purchase) method.


1930-1952: LGTM! Cross-platform restore logic is well-implemented.

The restorePurchases method provides consistent behavior across platforms with appropriate error handling. iOS uses sync with soft-fail behavior, while both platforms fetch available purchases.


1954-1960: LGTM! Documentation clarifies API migration.

The comments clearly document the removal of deprecated methods and guide users to the appropriate iOS-specific replacements.


1975-1977: LGTM! Documentation clarifies API changes.

The comments properly document the removal of deprecated App Transaction methods in favor of iOS-specific alternatives.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant