Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -402,43 +402,32 @@ class HybridRnIap : HybridRnIapSpec() {
val skusList = skus.toList()

val products: List<ProductCommon> = try {
when (queryType) {
ProductQueryType.All -> {
collectAllQueryProducts(
skusList = skusList,
fetchKind = { kind ->
RnIapLog.payload(
"fetchProducts.native",
mapOf("skus" to skusList, "type" to kind.rawValue)
)
val fetched = openIap.fetchProducts(ProductRequest(skusList, kind)).productsOrEmpty()
RnIapLog.result(
"fetchProducts.native",
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
)
fetched
},
onFailure = { kind, error ->
RnIapLog.failure("fetchProducts.native[${kind.rawValue}]", error)
},
)
}
else -> {
RnIapLog.payload(
"fetchProducts.native",
mapOf("skus" to skusList, "type" to queryType.rawValue)
)
val fetched = openIap.fetchProducts(ProductRequest(skusList, queryType)).productsOrEmpty()
RnIapLog.result(
"fetchProducts.native",
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
)
RnIapLog.payload(
"fetchProducts.native",
mapOf("skus" to skusList, "type" to queryType.rawValue)
)
// Pass the query type through unchanged: the native module resolves
// ProductQueryType.All itself, so each sku keeps its real product type
// instead of being shadowed by a per-type not-found placeholder.
val fetched = openIap.fetchProducts(ProductRequest(skusList, queryType)).productsOrEmpty()
RnIapLog.result(
"fetchProducts.native",
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
)

// Preserve input order for non-All queries
val byId = fetched.associateBy { it.id }
skusList.mapNotNull { byId[it] }
}
// Match the iOS bridge's result shape: preserve input order,
// drop duplicate skus, then append any fetched products that
// were not requested.
val byId = fetched.associateBy { it.id }
val seenIds = mutableSetOf<String>()
val orderedProducts = mutableListOf<ProductCommon>()
skusList.forEach { sku ->
byId[sku]?.takeIf { seenIds.add(it.id) }?.let(orderedProducts::add)
}
fetched.forEach { product ->
if (seenIds.add(product.id)) orderedProducts.add(product)
}
orderedProducts
} catch (e: OpenIapError) {
throw OpenIapException(toErrorJson(e))
}
Expand Down

This file was deleted.

This file was deleted.

104 changes: 104 additions & 0 deletions packages/docs/src/pages/docs/updates/releases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ interface Note {
element: React.ReactNode;
}

const fetchProductsAllFixReleases = [
['react-native-iap 15.5.4', 'react-native-iap-15.5.4'],
] as const;

const crossSdkAuditReleases = [
['react-native-iap 15.5.3', 'react-native-iap-15.5.3'],
['expo-iap 4.6.0', 'expo-iap-4.6.0'],
Expand Down Expand Up @@ -70,6 +74,106 @@ function Releases() {
useScrollToHash();

const allNotes: Note[] = [
// July 20, 2026 - react-native-iap 15.5.4
{
id: 'react-native-iap-fetch-products-all-fix-2026-07-20',
date: new Date('2026-07-20'),
element: (
<div
key="react-native-iap-fetch-products-all-fix-2026-07-20"
style={noteCardStyle}
>
<AnchorLink
id="react-native-iap-fetch-products-all-fix-2026-07-20"
level="h4"
>
July 20, 2026 - react-native-iap 15.5.4
</AnchorLink>

<p
style={{
marginBottom: '1rem',
color: 'var(--text-secondary)',
}}
>
Publishes a focused Android patch for the React Native library. The
OpenIAP Spec and native package versions are unchanged. The
regression was reported in{' '}
<a
href="https://github.com/hyodotdev/openiap/issues/238"
target="_blank"
rel="noopener noreferrer"
className="external-link"
>
issue #238
</a>{' '}
and fixed in{' '}
<a
href="https://github.com/hyodotdev/openiap/pull/239"
target="_blank"
rel="noopener noreferrer"
className="external-link"
>
PR #239
</a>
.
</p>

<h5 style={{ margin: '0 0 0.5rem 0' }}>React Native</h5>
<ul
style={{
marginBottom: '1rem',
paddingLeft: '1.25rem',
fontSize: '0.9rem',
}}
>
<li>
<strong>react-native-iap 15.5.4</strong> - fixes Android{' '}
<code>fetchProducts(&#123; type: &apos;all&apos; &#125;)</code>{' '}
returning subscriptions as <code>in-app</code> with{' '}
<code>productStatusAndroid: &apos;not-found&apos;</code>. The
Android bridge previously expanded <code>all</code> into separate
in-app and subscription queries and merged them first-match-wins,
so the in-app query&apos;s not-found placeholder row shadowed the
real subscription. The bridge now delegates <code>all</code> to
the native query, so each sku keeps its real product type and
status, and the internal product-type cache no longer mislabels
subscription skus (which could break a subsequent{' '}
<code>requestPurchase</code>).
</li>
</ul>

<div
style={{
paddingTop: '1rem',
borderTop: '1px solid var(--border-color)',
}}
>
<h5 style={{ margin: '0 0 0.5rem 0' }}>Package Releases</h5>
<ul
style={{
margin: 0,
paddingLeft: '1.25rem',
fontSize: '0.9rem',
}}
>
{fetchProductsAllFixReleases.map(([label, tag]) => (
<li key={tag}>
<a
href={`https://github.com/hyodotdev/openiap/releases/tag/${tag}`}
target="_blank"
rel="noopener noreferrer"
>
{label}
</a>
</li>
))}
</ul>
</div>
</div>
),
},

// July 20, 2026 - Cross-SDK audit fixes and IAPKit verified-state accuracy
{
id: 'cross-sdk-audit-fixes-2026-07-20',
Expand Down