Skip to content

test: enforce 90 percent SDK coverage - #308

Merged
hyochan merged 4 commits into
mainfrom
test/framework-codecov-90
Aug 10, 2026
Merged

hyochan merged 4 commits into
mainfrom
test/framework-codecov-90

Conversation

@hyochan

@hyochan hyochan commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Summary

  • enforce blocking 90% line coverage for React Native IAP, Expo IAP, flutter_inapp_purchase, and the IAPKit server in local CI and Codecov
  • add behavioral regression coverage for SDK bridges, error handling, cache and webhook paths without coverage exclusions
  • fix Flutter generated subscription handler purchase-error mapping and remove unreachable internal header compatibility branches from the GQL SSOT and synced framework copies
  • add parity guardrails for the exact four Codecov components, workflow contracts, and production-deploy path safety

Verification

  • two clean review-self snapshots more than five minutes apart
  • React Native: 90.24% lines, 550 tests
  • Expo: 91.69% lines, 408 tests
  • Flutter: 90.03% lines, 355 tests
  • IAPKit server: 90.01% lines, 1,088 tests
  • parity, docs, release-state, GQL generation, Flutter analyze, Kit build/smoke, MCP tests, actionlint, Prettier, and diff checks passed
  • commit hooks completed their CI-equivalent gates

Preview: not applicable; this is a CI, test, and internal bridge correctness change.

Summary by CodeRabbit

  • Bug Fixes

    • Improved purchase error conversion and handling across Flutter, React Native, and Expo integrations.
    • Strengthened request header handling, API error reporting, caching, and malformed-response behavior.
    • Improved platform-specific purchase, transaction, listener, and fallback behavior.
  • Quality Improvements

    • Added comprehensive regression coverage across purchase flows, webhooks, server behavior, and API integrations.
    • Enforced a minimum 90% line-coverage threshold in CI.
    • Added coverage reporting badges and server coverage tracking.

Add blocking per-component Codecov and local LCOV gates for React Native, Expo, Flutter, and IAPKit. Expand behavioral regression tests, fix Flutter purchase-error handler mapping, and remove unreachable internal header compatibility branches.
@hyochan hyochan added 💨 ci Cloud integration 🧪 test Issue or pr related to testing cross-platform Cross-platform (both Android & iOS) ⬡ protocol react-native-iap react-native-iap library expo-iap expo-iap library flutter_inapp_purchase flutter_inapp_purchase library openiap-kit packages/kit (IAPKit SaaS) labels Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hyochan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d95fc7d-9562-4b03-9cad-e7c9af392e83

📥 Commits

Reviewing files that changed from the base of the PR and between e989714 and a94ead5.

📒 Files selected for processing (9)
  • libraries/expo-iap/src/__tests__/index.kepler.test.ts
  • libraries/react-native-iap/src/__tests__/index.kepler.test.ts
  • libraries/react-native-iap/src/__tests__/index.test.ts
  • libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/index.ts
  • packages/kit/server/api/v1/webhooks.test.ts
  • packages/kit/server/server.test.ts
  • scripts/audit-non-godot-parity.mjs
📝 Walkthrough

Walkthrough

The PR adds 90% LCOV enforcement across CI workflows, expands Codecov configuration for IAP packages and the server, and adds regression coverage for Expo, React Native, Flutter, kit APIs, and IAPKit server behavior.

Changes

Coverage and regression validation

Layer / File(s) Summary
Coverage contracts and CI enforcement
.github/workflows/*, codecov.yml, scripts/*, packages/kit/README.md, packages/kit/package.json
CI validates 90% LCOV coverage before uploads. Codecov defines project, patch, package, and server coverage contracts. The audit validates workflow and configuration parity.
Kit API request and cache coverage
libraries/expo-iap/src/kit-api.ts, libraries/expo-iap/src/__tests__/kit-api.test.ts, libraries/react-native-iap/src/kit-api.ts, libraries/react-native-iap/src/__tests__/kit-api.test.ts, packages/gql/src/kit-api.ts
Kit API headers now use internal string records. Tests cover request construction, fetch fallback, responses, ETags, and cache failures.
Expo and React Native Vega API coverage
libraries/expo-iap/src/__tests__/index.kepler.test.ts, libraries/react-native-iap/src/__tests__/index.kepler.test.ts, libraries/react-native-iap/src/__tests__/index.test.ts, libraries/react-native-iap/src/__tests__/utils/*
Tests cover native-module availability, API delegation, fallbacks, listeners, request validation, payload conversion, and error normalization.
Flutter bridge conversion and event coverage
libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart, libraries/flutter_inapp_purchase/test/*
Purchase errors now map fields explicitly. Tests cover platform behavior, payload conversion, native events, generated handlers, and typed product queries.
IAPKit server coverage
packages/kit/server/api/v1/webhooks.test.ts, packages/kit/server/server.test.ts
Tests cover webhook validation and lifecycle processing, Pub/Sub behavior, HTTP responses, SPA caching, and shutdown handling.

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

Sequence Diagram(s)

sequenceDiagram
  participant CIWorkflow
  participant CoverageTests
  participant LCOVValidator
  participant Codecov
  CIWorkflow->>CoverageTests: run coverage-enabled tests
  CoverageTests-->>CIWorkflow: write coverage/lcov.info
  CIWorkflow->>LCOVValidator: assert 90% line coverage
  LCOVValidator-->>CIWorkflow: pass or fail verification
  CIWorkflow->>Codecov: upload coverage/lcov.info
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: enforcing 90 percent coverage across the SDKs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/framework-codecov-90

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.

@codecov-commenter

codecov-commenter commented Aug 10, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 92.30769% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.11%. Comparing base (661e9eb) to head (a94ead5).

Files with missing lines Patch % Lines
libraries/react-native-iap/src/index.ts 80.00% 1 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main     #308       +/-   ##
===========================================
+ Coverage   72.14%   84.11%   +11.97%     
===========================================
  Files          35       55       +20     
  Lines        5870     7317     +1447     
  Branches     1269     1701      +432     
===========================================
+ Hits         4235     6155     +1920     
+ Misses       1254      714      -540     
- Partials      381      448       +67     
Flag Coverage Δ
expo-iap 84.28% <100.00%> (+5.54%) ⬆️
flutter-inapp-purchase 90.03% <100.00%> (+18.52%) ⬆️
iapkit-server 82.32% <ø> (?)
react-native-iap 80.79% <83.33%> (+12.49%) ⬆️

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

Components Coverage Δ
React Native IAP 80.79% <83.33%> (+12.49%) ⬆️
Expo IAP 84.28% <100.00%> (+5.54%) ⬆️
flutter_inapp_purchase 90.03% <100.00%> (+18.52%) ⬆️
IAPKit Server 82.32% <ø> (∅)
Files with missing lines Coverage Δ
libraries/expo-iap/src/kit-api.ts 98.07% <100.00%> (+49.86%) ⬆️
...ter_inapp_purchase/lib/flutter_inapp_purchase.dart 88.84% <100.00%> (+22.33%) ⬆️
libraries/react-native-iap/src/kit-api.ts 96.00% <100.00%> (+54.33%) ⬆️
libraries/react-native-iap/src/index.ts 82.64% <80.00%> (+12.55%) ⬆️

... and 29 files with indirect coverage changes

🚀 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts (1)

172-182: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The undefined row tests null, not an undefined code.

Line 181 converts the undefined row into isNetworkError(null). The row label reports an undefined error code, but the input never contains {code: undefined}. That branch stays uncovered.

♻️ Suggested table change
-    [undefined, false],
-  ])('classifies network error %s', (code, expected) => {
-    expect(isNetworkError(code ? {code} : null)).toBe(expected);
-  });
+    [undefined, false],
+  ])('classifies network error %s', (code, expected) => {
+    expect(isNetworkError({code} as any)).toBe(expected);
+  });
+
+  test('treats a missing error object as a non-network error', () => {
+    expect(isNetworkError(null)).toBe(false);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts` around
lines 172 - 182, Update the isNetworkError parameterized test so the undefined
case passes an error object with code: undefined instead of converting it to
null. Preserve the existing expected false result and all other test cases.
libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts (1)

573-580: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the console spy setup.

The jest.spyOn(console, ...) plus try/finally restore pattern repeats at lines 182-208, 256-265 and 573-580. Create the spies in a beforeEach and call jest.restoreAllMocks() in an afterEach for this file. That removes the boilerplate and keeps each case focused on the assertion.

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

In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts` around
lines 573 - 580, In the test file, move the repeated console.error spy setup
into a beforeEach and clean up all mocks with jest.restoreAllMocks() in an
afterEach. Remove the local spy and try/finally restoration blocks from the
affected tests around validateNitroPurchase and the other repeated cases,
keeping each test focused on its assertions.
libraries/react-native-iap/src/__tests__/index.test.ts (2)

1647-1670: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the native removal call.

The test name states that it removes listeners. Lines 1662-1663 call first.remove() and second.remove(), but no assertion covers mockIap.removeUserChoiceBillingListenerAndroid. The removal path runs without verification, so a regression that skips the native removal still passes.

💚 Suggested assertion
       expect(mockIap.addUserChoiceBillingListenerAndroid).toHaveBeenCalledTimes(
         1,
       );
+      expect(
+        mockIap.removeUserChoiceBillingListenerAndroid,
+      ).toHaveBeenCalledTimes(1);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/react-native-iap/src/__tests__/index.test.ts` around lines 1647 -
1670, Extend the test around userChoiceBillingListenerAndroid so it asserts
mockIap.removeUserChoiceBillingListenerAndroid is called after first.remove()
and second.remove(). Keep the existing fan-out, callback isolation, and single
native-add assertions unchanged.

3200-3263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore shared mock implementations after the failure cases.

jest.clearAllMocks() does not restore methods reassigned on mockIap. These rejecting implementations persist into later tests. Restore each previous implementation or use jest.spyOn(...).mockRestore() in afterEach. The existing beforeEach already resets Platform.OS.

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

In `@libraries/react-native-iap/src/__tests__/index.test.ts` around lines 3200 -
3263, Restore the original mockIap method implementations after these failure
tests, since jest.clearAllMocks() does not undo direct reassignments in the
iosFailureCases, connection lifecycle, and Android failure cases. Add
appropriate cleanup using saved implementations or
jest.spyOn(...).mockRestore(), while preserving the existing Platform.OS reset
in beforeEach.
libraries/expo-iap/src/__tests__/index.kepler.test.ts (1)

113-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert listener removal and avoid hardcoded mock call indices.

Two points in this segment:

  1. Line 116 calls Kepler.emitter.removeListener, but the test does not assert that module.removeListener received the event and callback. The removal path is executed without verification.
  2. Lines 124-125 index module.addListener.mock.calls[1] and [2]. These indices depend on the order of the preceding emitter.addListener call. If emitter.addListener stops delegating to module.addListener, the indices shift and the test can assert against the wrong handler.

Capture the handlers from the return values of the specific registration calls instead.

♻️ Suggested hardening
     Kepler.emitter.removeListener(
       Kepler.OpenIapEvent.PurchaseUpdated,
       listener,
     );
+    expect(module.removeListener).toHaveBeenCalledWith(
+      Kepler.OpenIapEvent.PurchaseUpdated,
+      listener,
+    );
+    module.addListener.mockClear();
     const purchaseSub = Kepler.purchaseUpdatedListener(listener);
     const errorSub = Kepler.purchaseErrorListener(listener);
     expect(purchaseSub).toBe(subscription);
     expect(errorSub).toBe(subscription);
-    module.addListener.mock.calls[1][1]({productId: 'premium'});
-    module.addListener.mock.calls[2][1]({code: 'network-error'});
+    module.addListener.mock.calls[0][1]({productId: 'premium'});
+    module.addListener.mock.calls[1][1]({code: 'network-error'});
     expect(listener).toHaveBeenCalledTimes(2);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-iap/src/__tests__/index.kepler.test.ts` around lines 113 -
126, Update the listener test around Kepler.emitter.removeListener,
purchaseUpdatedListener, and purchaseErrorListener to assert
module.removeListener received the PurchaseUpdated event and listener callback.
Capture each registration’s handler from its specific module.addListener return
or call result, then invoke those handlers directly instead of relying on
hardcoded mock call indices, preserving the two listener invocation assertions.
libraries/react-native-iap/src/__tests__/index.kepler.test.ts (1)

14-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the shared mock and reset its implementations between tests.

Two points:

  1. The name module shadows the CommonJS module binding inside the describe scope. Rename it to vegaModule to prevent confusion and accidental shadowing.
  2. jest.clearAllMocks() in beforeEach clears calls, but it does not reset mock implementations. Line 164 uses fetchProductsNative.mockResolvedValue([...]) instead of mockResolvedValueOnce, so that resolved value persists for every test that runs after it. Any future fetchProducts test added below line 190 will receive the subscription fixture. Use mockResolvedValueOnce there, or call jest.resetAllMocks() and re-apply the default implementations in beforeEach.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/react-native-iap/src/__tests__/index.kepler.test.ts` around lines
14 - 32, Rename the shared mock object from module to vegaModule and update all
references within the test setup. In the fetchProducts test around
fetchProductsNative.mockResolvedValue, use mockResolvedValueOnce so the
subscription fixture is consumed by only that test while preserving the existing
beforeEach mock reset behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/kit/server/api/v1/webhooks.test.ts`:
- Around line 12-15: Update the test cleanup and missing-audience case around
afterEach and the test at lines 262–279 to preserve each test’s original
GOOGLE_PUBSUB_PUSH_AUDIENCE and KIT_ALLOW_UNAUTHENTICATED_PUBSUB values,
restoring them afterward rather than unconditionally deleting them. Explicitly
delete GOOGLE_PUBSUB_PUSH_AUDIENCE within the missing-audience test before
making the request.

In `@packages/kit/server/server.test.ts`:
- Around line 103-116: Reset the serverState.readFile mock at the start of the
“caches a missing SPA shell failure” test before configuring its rejection, then
update the final assertion to expect one call so the test remains independent of
prior tests.

In `@scripts/audit-non-godot-parity.mjs`:
- Line 869: Update the workflow validation around the assertion-command check to
locate both the lcov assertion step and the codecov/codecov-action@v7 upload
step, then require the assertion index to precede the upload index. Preserve the
existing assertion-presence validation while failing audits where coverage is
uploaded before the local threshold check.

---

Nitpick comments:
In `@libraries/expo-iap/src/__tests__/index.kepler.test.ts`:
- Around line 113-126: Update the listener test around
Kepler.emitter.removeListener, purchaseUpdatedListener, and
purchaseErrorListener to assert module.removeListener received the
PurchaseUpdated event and listener callback. Capture each registration’s handler
from its specific module.addListener return or call result, then invoke those
handlers directly instead of relying on hardcoded mock call indices, preserving
the two listener invocation assertions.

In `@libraries/react-native-iap/src/__tests__/index.kepler.test.ts`:
- Around line 14-32: Rename the shared mock object from module to vegaModule and
update all references within the test setup. In the fetchProducts test around
fetchProductsNative.mockResolvedValue, use mockResolvedValueOnce so the
subscription fixture is consumed by only that test while preserving the existing
beforeEach mock reset behavior.

In `@libraries/react-native-iap/src/__tests__/index.test.ts`:
- Around line 1647-1670: Extend the test around userChoiceBillingListenerAndroid
so it asserts mockIap.removeUserChoiceBillingListenerAndroid is called after
first.remove() and second.remove(). Keep the existing fan-out, callback
isolation, and single native-add assertions unchanged.
- Around line 3200-3263: Restore the original mockIap method implementations
after these failure tests, since jest.clearAllMocks() does not undo direct
reassignments in the iosFailureCases, connection lifecycle, and Android failure
cases. Add appropriate cleanup using saved implementations or
jest.spyOn(...).mockRestore(), while preserving the existing Platform.OS reset
in beforeEach.

In `@libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts`:
- Around line 172-182: Update the isNetworkError parameterized test so the
undefined case passes an error object with code: undefined instead of converting
it to null. Preserve the existing expected false result and all other test
cases.

In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts`:
- Around line 573-580: In the test file, move the repeated console.error spy
setup into a beforeEach and clean up all mocks with jest.restoreAllMocks() in an
afterEach. Remove the local spy and try/finally restoration blocks from the
affected tests around validateNitroPurchase and the other repeated cases,
keeping each test focused on its assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10ce68c6-b29f-46b8-9c67-622647716aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 661e9eb and 8e1de91.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .github/workflows/ci-expo-iap.yml
  • .github/workflows/ci-flutter-inapp-purchase.yml
  • .github/workflows/ci-react-native-iap.yml
  • .github/workflows/deploy-kit.yml
  • codecov.yml
  • libraries/expo-iap/src/__tests__/index.kepler.test.ts
  • libraries/expo-iap/src/__tests__/kit-api.test.ts
  • libraries/expo-iap/src/kit-api.ts
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/flutter_inapp_purchase/test/coverage_regression_test.dart
  • libraries/flutter_inapp_purchase/test/fetch_products_all_test.dart
  • libraries/flutter_inapp_purchase/test/helpers_coverage_test.dart
  • libraries/react-native-iap/src/__tests__/index.kepler.test.ts
  • libraries/react-native-iap/src/__tests__/index.test.ts
  • libraries/react-native-iap/src/__tests__/kit-api.test.ts
  • libraries/react-native-iap/src/__tests__/utils/coverage-regression.test.ts
  • libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/kit-api.ts
  • packages/gql/src/kit-api.ts
  • packages/kit/README.md
  • packages/kit/package.json
  • packages/kit/server/api/v1/webhooks.test.ts
  • packages/kit/server/server.test.ts
  • scripts/assert-lcov-coverage.mjs
  • scripts/assert-lcov-coverage.test.mjs
  • scripts/audit-non-godot-parity.mjs

Comment thread packages/kit/server/api/v1/webhooks.test.ts
Comment thread packages/kit/server/server.test.ts Outdated
Comment thread scripts/audit-non-godot-parity.mjs
@hyochan
hyochan merged commit 1ede6c5 into main Aug 10, 2026
27 checks passed
@hyochan
hyochan deleted the test/framework-codecov-90 branch August 10, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💨 ci Cloud integration cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter_inapp_purchase flutter_inapp_purchase library openiap-kit packages/kit (IAPKit SaaS) ⬡ protocol react-native-iap react-native-iap library 🧪 test Issue or pr related to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants