Skip to content

feat: sync with openiap v1.3.12#3125

Merged
hyochan merged 7 commits intomainfrom
feat/openiap-sync-1.3.12
Jan 17, 2026
Merged

feat: sync with openiap v1.3.12#3125
hyochan merged 7 commits intomainfrom
feat/openiap-sync-1.3.12

Conversation

@hyochan
Copy link
Owner

@hyochan hyochan commented Jan 16, 2026

Summary

  • Sync with openiap v1.3.12 (gql: 1.3.12, apple: 1.3.10, google: 1.3.22)
  • Add new cross-platform DiscountOffer and SubscriptionOffer types
  • New discountOffers and subscriptionOffers fields on Product types

Changes

Types (auto-generated)

  • New DiscountOffer type with cross-platform fields
  • New SubscriptionOffer type with cross-platform fields
  • Platform-specific fields use Android/IOS suffixes
  • Existing subscriptionOfferDetailsAndroid and oneTimePurchaseOfferDetailsAndroid still available for compatibility

Test plan

  • yarn typecheck passes
  • yarn test passes (172 tests)
  • yarn build passes
  • Nitro code generation succeeds

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Unified cross-platform subscription and discount offer data now exposed to apps and UI; product details show standardized subscription and discount offers.
    • Improved subscription replacement support during purchase flows.
  • Bug Fixes

    • More robust handling when standardized offer data is missing or malformed.
  • Documentation

    • API docs and guides updated with unified offer types, examples, and deprecation notes for legacy fields.
  • Tests

    • Added tests covering parsing and display of standardized offers.

✏️ Tip: You can customize this high-level summary in your review settings.

- Update openiap-versions.json (gql: 1.3.12, apple: 1.3.10, google: 1.3.22)
- Regenerate TypeScript types with new cross-platform offer types
- New DiscountOffer and SubscriptionOffer types available
- New discountOffers and subscriptionOffers fields on Product types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd6269 and f8c4680.

📒 Files selected for processing (1)
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
📝 Walkthrough

Walkthrough

Adds standardized cross-platform subscription and discount offer types and plumbing: native serialization on Android/iOS, NitroProduct fields for JSON-serialized offers and subscription replacement params, TypeScript types and bridge parsing, docs and example UI/tests updated to surface unified subscriptionOffers and discountOffers.

Changes

Cohort / File(s) Summary
Native Bridge & Offer Serialization
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt, ios/RnIapHelper.swift
Serialize cross-platform SubscriptionOffer/DiscountOffer arrays to JSON and attach as subscriptionOffers/discountOffers on NitroProduct; parse Android subscriptionProductReplacementParams and map replacement modes.
Nitro Specs & Types
src/specs/RnIap.nitro.ts, src/types.ts
Added Nitro fields `subscriptionOffers?: string
Type Bridge & Tests
src/utils/type-bridge.ts, src/__tests__/utils/type-bridge.test.ts
Parse JSON subscriptionOffers/discountOffers in convertNitroProductToProduct for iOS/Android; tests for valid/missing/invalid JSON and offer mapping added.
Docs & Guides
docs/docs/api/types.md, docs/docs/guides/subscription-offers.md, docs/static/llms*.txt
Documented cross-platform SubscriptionOffer/DiscountOffer types, examples updated to use subscriptionOffers/discountOffers; deprecation notes for legacy platform-specific fields; duplicated block observed in types.md.
Examples & Tests (UI)
example/screens/AllProducts.tsx, example/screens/SubscriptionFlow.tsx, example/screens/PurchaseFlow.tsx, example/__tests__/screens/PurchaseFlow.test.tsx
Replaced Android-only offer UIs with cross-platform rendering of subscriptionOffers and discountOffers; updated imports, UI labels, and test assertions to new schema.
OpenIAP Versions
openiap-versions.json
Bumped OpenIAP packages: apple 1.3.9→1.3.10, google 1.3.21→1.3.23, gql 1.3.11→1.3.12.

Sequence Diagram(s)

sequenceDiagram
  participant App as React Native App
  participant Native as Native (Android / iOS)
  participant Nitro as NitroProduct (native payload)
  participant Bridge as JS Type Bridge

  App->>Native: request products
  Native->>Native: gather product data + platform offers
  Native->>Nitro: serialize product including subscriptionOffers/discountOffers JSON and replacement params
  Nitro->>Bridge: deliver NitroProduct payload (JSON strings fields)
  Bridge->>Bridge: parse subscriptionOffers/discountOffers JSON -> arrays, map replacement params
  Bridge->>App: return Product objects with parsed subscriptionOffers & discountOffers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I nibble code and hop with glee,
Offers stitched cross-platform for me.
JSON strings bundled neat and bright,
Bridge parses, apps hold them tight.
Hooray for unified offers—hop! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: syncing with openiap v1.3.12, which is the primary objective evident throughout the PR's numerous coordinated changes across types, documentation, Android/iOS implementations, and test files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@gemini-code-assist
Copy link
Contributor

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 primarily focuses on enhancing the openiap library by synchronizing with the latest versions of its core components and introducing a more unified and cross-platform approach to handling product discounts and subscription offers. The changes aim to streamline the integration of in-app purchase functionalities by providing standardized data structures, reducing the need for platform-specific conditional logic when dealing with offer details.

Highlights

  • Version Sync: The openiap dependencies have been updated to version 1.3.12 for gql, 1.3.10 for apple, and 1.3.22 for google.
  • New Cross-Platform Types: Introduced new standardized DiscountOffer and SubscriptionOffer types to provide a unified interface for offers across different platforms (iOS and Android).
  • Product Type Enhancements: Added discountOffers and subscriptionOffers fields to existing ProductAndroid, ProductIOS, ProductSubscriptionAndroid, and ProductSubscriptionIOS interfaces to expose the new standardized offer types. Several platform-specific offer fields have been deprecated in favor of these new cross-platform types.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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.

@codecov
Copy link

codecov bot commented Jan 16, 2026

Codecov Report

❌ Patch coverage is 80.76923% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.68%. Comparing base (71a8f73) to head (f8c4680).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/type-bridge.ts 80.76% 5 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3125      +/-   ##
==========================================
+ Coverage   65.39%   65.68%   +0.29%     
==========================================
  Files           9        9              
  Lines        1679     1705      +26     
  Branches      561      570       +9     
==========================================
+ Hits         1098     1120      +22     
- Misses        576      580       +4     
  Partials        5        5              
Flag Coverage Δ
library 65.68% <80.76%> (+0.29%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/utils/type-bridge.ts 76.98% <80.76%> (+0.83%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
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 primarily focuses on standardizing product and subscription offer types across iOS and Android platforms within the src/types.ts file. It introduces new interfaces like DiscountOffer for one-time product discounts and SubscriptionOffer for subscription offers, both designed for cross-platform compatibility, and adds related enumerations such as DiscountOfferType, PaymentMode, SubscriptionPeriod, and SubscriptionPeriodUnit. Consequently, several existing platform-specific interfaces and properties (e.g., DiscountIOS, DiscountOfferIOS, oneTimePurchaseOfferDetailsAndroid, subscriptionOfferDetailsAndroid, subscriptionInfoIOS, discountsIOS, SubscriptionOfferIOS) have been deprecated in favor of these new standardized types. Additionally, the openiap-versions.json file was updated to increment the version numbers for apple, google, and gql components.

@hyochan hyochan added 🎯 feature New feature 🚽 migration Activities due to changes in framework labels Jan 16, 2026
hyochan and others added 2 commits January 17, 2026 08:41
- Add subscriptionOffers and discountOffers to NitroProduct interface
- Update iOS RnIapHelper.swift to extract standardized offers from OpenIAP
- Update Android HybridRnIap.kt with serialization for new offer types
- Update type-bridge.ts to parse new JSON offer fields
- Add comprehensive tests for new SubscriptionOffer and DiscountOffer types
- Update documentation with new cross-platform offer types (v14.8.0+)
- Update llms.txt and llms-full.txt AI reference documentation
- Update example AllProducts screen to display standardized offers

These new types provide a unified API for subscription and discount offers
across iOS and Android, replacing the deprecated platform-specific fields
subscriptionInfoIOS and subscriptionOfferDetailsAndroid.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…untOffers

- Update PurchaseFlow.tsx to use discountOffers instead of deprecated
  oneTimePurchaseOfferDetailsAndroid
- Update SubscriptionFlow.tsx to use subscriptionOffers instead of deprecated
  subscriptionOfferDetailsAndroid
- Update subscription-offers guide documentation with cross-platform types
- Remove AndroidOneTimeOfferDetails component usage (deprecated)
- Update PurchaseFlow.test.tsx to test cross-platform discountOffers

This aligns with expo-iap PR #302 and OpenIAP spec for unified offer handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@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

🤖 Fix all issues with AI agents
In `@src/utils/type-bridge.ts`:
- Around line 280-303: The parsing for nitroProduct.subscriptionOffers (and
similarly for discountOffers) can produce null or non-array values but public
types (ProductSubscriptionAndroid) expect a non-null array; update the logic in
the subscriptionOffers parsing block (and the analogous blocks around lines
referenced) to JSON.parse inside a try, then validate Array.isArray(result) and
assign iosProduct.subscriptionOffers = result if true, otherwise assign []; do
the same validation/default-for-[] behavior for discountOffers and for the other
subscription-related blocks (e.g., subscriptionOfferDetailsAndroid) so all
subscription fields always resolve to arrays for subscription products.
🧹 Nitpick comments (3)
example/screens/AllProducts.tsx (1)

447-543: Gate Android-only fields with Platform.OS for clarity.

basePlanIdAndroid / offerTokenAndroid are platform-specific; adding explicit Android guards aligns with the project guideline and avoids accidental cross-platform UI leakage. As per coding guidelines, use Platform.OS for platform-specific logic.

♻️ Suggested tweak
-import {
-  View,
-  Text,
-  StyleSheet,
-  TouchableOpacity,
-  Modal,
-  SectionList,
-  ScrollView,
-} from 'react-native';
+import {
+  View,
+  Text,
+  StyleSheet,
+  TouchableOpacity,
+  Modal,
+  SectionList,
+  ScrollView,
+  Platform,
+} from 'react-native';

...

-                              {offer.basePlanIdAndroid && (
+                              {Platform.OS === 'android' &&
+                                offer.basePlanIdAndroid && (
                                 <View style={styles.offerRow}>
                                   <Text style={styles.offerLabel}>
                                     Base Plan (Android):
                                   </Text>
                                   <Text style={styles.offerValue}>
                                     {offer.basePlanIdAndroid}
                                   </Text>
                                 </View>
                               )}

-                              {offer.offerTokenAndroid && (
+                              {Platform.OS === 'android' &&
+                                offer.offerTokenAndroid && (
                                 <>
                                   <Text style={styles.offerLabel}>
                                     Offer Token (Android):
                                   </Text>
                                   <Text
                                     style={[
                                       styles.offerValue,
                                       styles.offerToken,
                                     ]}
                                     numberOfLines={2}
                                   >
                                     {offer.offerTokenAndroid}
                                   </Text>
                                 </>
                               )}
example/screens/PurchaseFlow.tsx (1)

387-509: Guard Android-only offer details with Platform.OS.

Several fields shown here are Android-specific (micros, time windows, preorder, rental, tags). Wrapping those details in a Platform.OS === 'android' block keeps cross-platform UI clean and matches the project guideline. As per coding guidelines, use Platform.OS for platform-specific logic.

♻️ Suggested tweak
-                            {offer.fullPriceMicrosAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>
-                                  Full Price (micros):
-                                </Text>
-                                <Text style={styles.offerValue}>
-                                  {offer.fullPriceMicrosAndroid}
-                                </Text>
-                              </>
-                            )}
-                            {offer.percentageDiscountAndroid && (
-                              <Text style={styles.offerValueDiscount}>
-                                {offer.percentageDiscountAndroid}% off
-                              </Text>
-                            )}
-                            {offer.formattedDiscountAmountAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>Discount:</Text>
-                                <Text style={styles.offerValueDiscount}>
-                                  {offer.formattedDiscountAmountAndroid}
-                                </Text>
-                              </>
-                            )}
-                            {offer.validTimeWindowAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>
-                                  Valid Window:
-                                </Text>
-                                <Text style={styles.offerValue}>
-                                  {new Date(
-                                    Number(
-                                      offer.validTimeWindowAndroid
-                                        .startTimeMillis,
-                                    ),
-                                  ).toLocaleDateString()}{' '}
-                                  -{' '}
-                                  {new Date(
-                                    Number(
-                                      offer.validTimeWindowAndroid
-                                        .endTimeMillis,
-                                    ),
-                                  ).toLocaleDateString()}
-                                </Text>
-                              </>
-                            )}
-                            {offer.limitedQuantityInfoAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>
-                                  Limited Quantity:
-                                </Text>
-                                <Text style={styles.offerValue}>
-                                  {
-                                    offer.limitedQuantityInfoAndroid
-                                      .remainingQuantity
-                                  }{' '}
-                                  /{' '}
-                                  {
-                                    offer.limitedQuantityInfoAndroid
-                                      .maximumQuantity
-                                  }{' '}
-                                  remaining
-                                </Text>
-                              </>
-                            )}
-                            {offer.preorderDetailsAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>
-                                  Pre-order Release:
-                                </Text>
-                                <Text style={styles.offerValue}>
-                                  {new Date(
-                                    Number(
-                                      offer.preorderDetailsAndroid
-                                        .preorderReleaseTimeMillis,
-                                    ),
-                                  ).toLocaleDateString()}
-                                </Text>
-                              </>
-                            )}
-                            {offer.rentalDetailsAndroid && (
-                              <>
-                                <Text style={styles.offerLabel}>Rental:</Text>
-                                <Text style={styles.offerValue}>
-                                  Period:{' '}
-                                  {
-                                    offer.rentalDetailsAndroid
-                                      .rentalExpirationPeriod
-                                  }
-                                </Text>
-                              </>
-                            )}
-                            {Array.isArray(offer.offerTagsAndroid) &&
-                              offer.offerTagsAndroid.length > 0 && (
-                                <>
-                                  <Text style={styles.offerLabel}>Tags:</Text>
-                                  <Text style={styles.offerValue}>
-                                    {offer.offerTagsAndroid.join(', ')}
-                                  </Text>
-                                </>
-                              )}
+                            {Platform.OS === 'android' && (
+                              <>
+                                {offer.fullPriceMicrosAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Full Price (micros):
+                                    </Text>
+                                    <Text style={styles.offerValue}>
+                                      {offer.fullPriceMicrosAndroid}
+                                    </Text>
+                                  </>
+                                )}
+                                {offer.percentageDiscountAndroid && (
+                                  <Text style={styles.offerValueDiscount}>
+                                    {offer.percentageDiscountAndroid}% off
+                                  </Text>
+                                )}
+                                {offer.formattedDiscountAmountAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Discount:
+                                    </Text>
+                                    <Text style={styles.offerValueDiscount}>
+                                      {offer.formattedDiscountAmountAndroid}
+                                    </Text>
+                                  </>
+                                )}
+                                {offer.validTimeWindowAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Valid Window:
+                                    </Text>
+                                    <Text style={styles.offerValue}>
+                                      {new Date(
+                                        Number(
+                                          offer.validTimeWindowAndroid
+                                            .startTimeMillis,
+                                        ),
+                                      ).toLocaleDateString()}{' '}
+                                      -{' '}
+                                      {new Date(
+                                        Number(
+                                          offer.validTimeWindowAndroid
+                                            .endTimeMillis,
+                                        ),
+                                      ).toLocaleDateString()}
+                                    </Text>
+                                  </>
+                                )}
+                                {offer.limitedQuantityInfoAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Limited Quantity:
+                                    </Text>
+                                    <Text style={styles.offerValue}>
+                                      {
+                                        offer.limitedQuantityInfoAndroid
+                                          .remainingQuantity
+                                      }{' '}
+                                      /{' '}
+                                      {
+                                        offer.limitedQuantityInfoAndroid
+                                          .maximumQuantity
+                                      }{' '}
+                                      remaining
+                                    </Text>
+                                  </>
+                                )}
+                                {offer.preorderDetailsAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Pre-order Release:
+                                    </Text>
+                                    <Text style={styles.offerValue}>
+                                      {new Date(
+                                        Number(
+                                          offer.preorderDetailsAndroid
+                                            .preorderReleaseTimeMillis,
+                                        ),
+                                      ).toLocaleDateString()}
+                                    </Text>
+                                  </>
+                                )}
+                                {offer.rentalDetailsAndroid && (
+                                  <>
+                                    <Text style={styles.offerLabel}>
+                                      Rental:
+                                    </Text>
+                                    <Text style={styles.offerValue}>
+                                      Period:{' '}
+                                      {
+                                        offer.rentalDetailsAndroid
+                                          .rentalExpirationPeriod
+                                      }
+                                    </Text>
+                                  </>
+                                )}
+                                {Array.isArray(offer.offerTagsAndroid) &&
+                                  offer.offerTagsAndroid.length > 0 && (
+                                    <>
+                                      <Text style={styles.offerLabel}>
+                                        Tags:
+                                      </Text>
+                                      <Text style={styles.offerValue}>
+                                        {offer.offerTagsAndroid.join(', ')}
+                                      </Text>
+                                    </>
+                                  )}
+                              </>
+                            )}
example/screens/SubscriptionFlow.tsx (1)

2164-2173: Consider filtering out offers with missing offerTokenAndroid.

The code uses offer.offerTokenAndroid ?? '' which will include offers with empty tokens in the request. Android requires valid offer tokens for subscription purchases.

Consider filtering instead:

♻️ Suggested improvement
                 ? (
                     subscription as ProductSubscriptionAndroid
                   ).subscriptionOffers.map((offer) => ({
                     sku: itemId,
                     offerToken: offer.offerTokenAndroid ?? '',
                   }))
+                   .filter((o) => o.offerToken !== '')
                 : [],

hyochan and others added 4 commits January 17, 2026 09:17
…bscriptions

Address coderabbit review: ProductSubscriptionAndroid.subscriptionOffers is
non-nullable in the public types, so parsing failures should default to []
instead of null.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add item-level subscription replacement parameters support for Android
Billing Library 8.1.0+. This allows specifying replacement mode per
product instead of globally, enabling more flexible subscription
upgrade/downgrade flows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix typo in OpenIAP enum mapping: ChargeProratedPrice (not ChargeProRatedPrice)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hyochan hyochan merged commit a02210b into main Jan 17, 2026
11 checks passed
@hyochan hyochan deleted the feat/openiap-sync-1.3.12 branch January 17, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎯 feature New feature 🚽 migration Activities due to changes in framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant