diff --git a/android/build.gradle b/android/build.gradle
index 81a0a2e0..461defa9 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -80,7 +80,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation "io.qonversion:sandwich:7.10.1"
+ implementation "io.qonversion:sandwich:7.11.0"
}
if (isNewArchitectureEnabled()) {
diff --git a/example/src/App.tsx b/example/src/App.tsx
index ee2ade15..58ae41d6 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -24,7 +24,6 @@ import ProductsScreen from './screens/ProductsScreen';
import ProductDetailScreen from './screens/ProductDetailScreen';
import EntitlementsScreen from './screens/EntitlementsScreen';
import EntitlementDetailScreen from './screens/EntitlementDetailScreen';
-import OfferingsScreen from './screens/OfferingsScreen';
import RemoteConfigsScreen from './screens/RemoteConfigsScreen';
import UserScreen from './screens/UserScreen';
import NoCodesScreen from './screens/NoCodesScreen';
@@ -113,8 +112,6 @@ const App: React.FC = () => {
) : (
);
- case 'offerings':
- return ;
case 'remoteConfigs':
return ;
case 'user':
diff --git a/example/src/index.ts b/example/src/index.ts
index c8aa9dec..e6464d8c 100644
--- a/example/src/index.ts
+++ b/example/src/index.ts
@@ -14,7 +14,6 @@ export { default as SkeletonLoader } from './components/SkeletonLoader';
export { default as MainScreen } from './screens/MainScreen';
export { default as ProductsScreen } from './screens/ProductsScreen';
export { default as EntitlementsScreen } from './screens/EntitlementsScreen';
-export { default as OfferingsScreen } from './screens/OfferingsScreen';
export { default as RemoteConfigsScreen } from './screens/RemoteConfigsScreen';
export { default as UserScreen } from './screens/UserScreen';
export { default as NoCodesScreen } from './screens/NoCodesScreen';
diff --git a/example/src/screens/MainScreen/index.tsx b/example/src/screens/MainScreen/index.tsx
index 83f2dd13..6ab0f21d 100644
--- a/example/src/screens/MainScreen/index.tsx
+++ b/example/src/screens/MainScreen/index.tsx
@@ -21,7 +21,6 @@ const MainScreen: React.FC = () => {
const menuItems = [
{ id: 'products', title: 'Products' },
{ id: 'entitlements', title: 'Entitlements' },
- { id: 'offerings', title: 'Offerings' },
{ id: 'remoteConfigs', title: 'Remote Configs' },
{ id: 'user', title: 'User' },
{ id: 'noCodes', title: 'No-Codes' },
diff --git a/example/src/screens/OfferingsScreen/index.tsx b/example/src/screens/OfferingsScreen/index.tsx
deleted file mode 100644
index d4eea491..00000000
--- a/example/src/screens/OfferingsScreen/index.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import React from 'react';
-import { Text, View, TouchableOpacity, Alert, ScrollView } from 'react-native';
-import Qonversion from '@qonversion/react-native-sdk';
-import { AppContext } from '../../store/AppStore';
-import SkeletonLoader from '../../components/SkeletonLoader';
-import ProductCard from '../../components/ProductCard';
-import styles from './styles';
-
-const OfferingsScreen: React.FC = () => {
- const context = React.useContext(AppContext);
- if (!context) return null;
- const { state, dispatch } = context;
-
- const loadOfferings = async () => {
- try {
- console.log('🔄 [Qonversion] Starting offerings() call...');
- dispatch({ type: 'SET_LOADING', payload: true });
- const offerings = await Qonversion.getSharedInstance().offerings();
- console.log('✅ [Qonversion] offerings() call successful:', offerings);
- if (offerings) {
- dispatch({ type: 'SET_OFFERINGS', payload: offerings });
- }
- } catch (error: any) {
- console.error('❌ [Qonversion] offerings() call failed:', error);
- Alert.alert('Error', error.message);
- } finally {
- dispatch({ type: 'SET_LOADING', payload: false });
- }
- };
-
- if (state.loading) {
- return ;
- }
-
- return (
-
-
- Load Offerings
-
-
- {state.offerings ? (
-
-
- Main Offering ID: {state.offerings.main?.id}
-
-
- {state.offerings.availableOffering.map((offering) => (
-
- {offering.id}
- Tag: {offering.tag}
-
- {offering.products.length > 0 ? (
-
- Products:
- {offering.products.map((product) => (
-
- ))}
-
- ) : (
-
- No products in this offering
-
- )}
-
- ))}
-
- ) : (
-
- No Offerings Loaded
-
- Tap the button above to load available offerings
-
-
- )}
-
- );
-};
-
-export default OfferingsScreen;
diff --git a/example/src/screens/OfferingsScreen/styles.ts b/example/src/screens/OfferingsScreen/styles.ts
deleted file mode 100644
index f5140077..00000000
--- a/example/src/screens/OfferingsScreen/styles.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { StyleSheet } from 'react-native';
-
-export default StyleSheet.create({
- container: {
- flex: 1,
- padding: 16,
- paddingBottom: 32,
- },
- contentContainer: {
- paddingBottom: 32,
- },
- button: {
- backgroundColor: '#007AFF',
- padding: 16,
- borderRadius: 8,
- marginBottom: 12,
- alignItems: 'center',
- },
- buttonText: {
- color: '#ffffff',
- fontSize: 16,
- fontWeight: '600',
- },
- listContainer: {
- marginTop: 20,
- },
- sectionTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- marginBottom: 12,
- color: '#000000',
- },
- listItem: {
- backgroundColor: '#ffffff',
- padding: 16,
- borderRadius: 8,
- marginBottom: 12,
- shadowColor: '#000000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 3,
- },
- listItemTitle: {
- fontSize: 16,
- fontWeight: 'bold',
- marginBottom: 4,
- color: '#000000',
- },
- listItemSubtitle: {
- fontSize: 14,
- color: '#666666',
- marginBottom: 4,
- },
- emptyState: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- paddingVertical: 40,
- },
- emptyStateTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- color: '#666666',
- marginBottom: 8,
- textAlign: 'center',
- },
- emptyStateSubtitle: {
- fontSize: 14,
- color: '#999999',
- textAlign: 'center',
- lineHeight: 20,
- },
- offeringContainer: {
- backgroundColor: '#ffffff',
- padding: 16,
- borderRadius: 8,
- marginBottom: 16,
- shadowColor: '#000000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 3,
- borderWidth: 1,
- borderColor: '#e0e0e0',
- },
- offeringTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- marginBottom: 4,
- color: '#000000',
- },
- offeringSubtitle: {
- fontSize: 14,
- color: '#666666',
- marginBottom: 12,
- },
- productsContainer: {
- marginTop: 8,
- },
- productsTitle: {
- fontSize: 16,
- fontWeight: '600',
- marginBottom: 8,
- color: '#333333',
- },
- noProductsText: {
- fontSize: 14,
- color: '#999999',
- fontStyle: 'italic',
- marginTop: 8,
- },
-});
diff --git a/example/src/screens/ProductDetailScreen/index.tsx b/example/src/screens/ProductDetailScreen/index.tsx
index 7a0e7e30..2804f1f6 100644
--- a/example/src/screens/ProductDetailScreen/index.tsx
+++ b/example/src/screens/ProductDetailScreen/index.tsx
@@ -101,7 +101,6 @@ const ProductDetailScreen: React.FC = ({
{renderField('Store ID', product.storeId)}
{renderField('Base Plan ID', product.basePlanId)}
{renderField('Type', product.type)}
- {renderField('Offering ID', product.offeringId)}
diff --git a/example/src/store/AppStore.ts b/example/src/store/AppStore.ts
index 9f2d5c6f..62ddbd45 100644
--- a/example/src/store/AppStore.ts
+++ b/example/src/store/AppStore.ts
@@ -1,6 +1,5 @@
import React from 'react';
import {
- Offerings,
Product,
RemoteConfigList,
User,
@@ -10,7 +9,6 @@ import Entitlement from '../../../src/dto/Entitlement';
// Global Store (Redux-like pattern)
export interface AppState {
products: Map | null;
- offerings: Offerings | null;
entitlements: Map | null;
remoteConfigs: RemoteConfigList | null;
userInfo: User | null;
@@ -29,7 +27,6 @@ export interface AppState {
export type AppAction =
| { type: 'SET_PRODUCTS'; payload: Map }
- | { type: 'SET_OFFERINGS'; payload: Offerings }
| { type: 'SET_ENTITLEMENTS'; payload: Map }
| { type: 'SET_REMOTE_CONFIGS'; payload: RemoteConfigList }
| { type: 'SET_USER_INFO'; payload: User }
@@ -48,7 +45,6 @@ export type AppAction =
export const initialState: AppState = {
products: null,
- offerings: null,
entitlements: null,
remoteConfigs: null,
userInfo: null,
@@ -65,8 +61,6 @@ export function appReducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
case 'SET_PRODUCTS':
return { ...state, products: action.payload };
- case 'SET_OFFERINGS':
- return { ...state, offerings: action.payload };
case 'SET_ENTITLEMENTS':
return { ...state, entitlements: action.payload };
case 'SET_REMOTE_CONFIGS':
diff --git a/package.json b/package.json
index 3af5c8ad..d0b1fe0e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@qonversion/react-native-sdk",
"title": "React Native Qonversion",
- "version": "10.9.0",
+ "version": "10.10.0",
"description": "Qonversion provides full in-app purchases infrastructure, so you do not need to build your own server for receipt validation. Implement in-app subscriptions, validate user receipts, check subscription status, and provide access to your app features and content using our StoreKit wrapper and Google Play Billing wrapper.",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
diff --git a/qonversion-react-native-sdk.podspec b/qonversion-react-native-sdk.podspec
index 7504df7b..e89e2698 100644
--- a/qonversion-react-native-sdk.podspec
+++ b/qonversion-react-native-sdk.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
s.private_header_files = "ios/**/*.h"
- s.dependency "QonversionSandwich", "7.10.1"
+ s.dependency "QonversionSandwich", "7.11.0"
install_modules_dependencies(s)
end
diff --git a/src/QonversionApi.ts b/src/QonversionApi.ts
index 8395e515..c4df2bbd 100644
--- a/src/QonversionApi.ts
+++ b/src/QonversionApi.ts
@@ -111,9 +111,10 @@ export interface QonversionApi {
* set of products with discounts later on if a user has not converted.
* Offerings allow changing the products offered remotely without releasing app updates.
*
+ * @deprecated Offerings are deprecated. Manage paywall products with Remote Configs instead: https://documentation.qonversion.io/docs/migrate-offerings-to-remote-configs
* @returns the promise with Qonversion offerings
*
- * @see [Offerings](https://qonversion.io/docs/offerings) for more details
+ * @see [Migrate Offerings to Remote Configs](https://documentation.qonversion.io/docs/migrate-offerings-to-remote-configs) for more details
*/
offerings(): Promise;
diff --git a/src/dto/Product.ts b/src/dto/Product.ts
index 7cd45035..dd5101e1 100644
--- a/src/dto/Product.ts
+++ b/src/dto/Product.ts
@@ -11,10 +11,17 @@ class Product {
storeId: string | null;
/**
- * Identifier of the base plan for Google product.
+ * Identifier of the base plan for a Google Play subscription product.
+ * Android only. Null for one-time products - use {@link purchaseOptionId} for those.
*/
basePlanId: string | null;
+ /**
+ * Identifier of the Google Play purchase option for a one-time (in-app) product.
+ * Android only. Null for subscription products - use {@link basePlanId} for those.
+ */
+ purchaseOptionId: string | null;
+
/**
* Google Play Store details of this product.
* Android only. Null for iOS, or if the product was not found.
@@ -66,6 +73,7 @@ class Product {
qonversionId: string,
storeId: string,
basePlanId: string | null,
+ purchaseOptionId: string | null,
storeDetails: ProductStoreDetails | null,
skProduct: SKProduct | null,
offeringId: string | null,
@@ -82,6 +90,7 @@ class Product {
this.qonversionId = qonversionId;
this.storeId = storeId;
this.basePlanId = basePlanId;
+ this.purchaseOptionId = purchaseOptionId;
this.storeDetails = storeDetails;
this.skProduct = skProduct;
this.offeringId = offeringId;
diff --git a/src/dto/storeProducts/ProductStoreDetails.ts b/src/dto/storeProducts/ProductStoreDetails.ts
index 0a2526fe..98636243 100644
--- a/src/dto/storeProducts/ProductStoreDetails.ts
+++ b/src/dto/storeProducts/ProductStoreDetails.ts
@@ -8,11 +8,17 @@ import ProductInAppDetails from "./ProductInAppDetails";
*/
class ProductStoreDetails {
/**
- * Identifier of the base plan to which these details relate.
- * Null for in-app products.
+ * Identifier of the base plan for a Google Play subscription product.
+ * Null for one-time products - use {@link purchaseOptionId} for those.
*/
basePlanId: string | null;
+ /**
+ * Identifier of the Google Play purchase option for a one-time (in-app) product.
+ * Null for subscription products - use {@link basePlanId} for those.
+ */
+ purchaseOptionId: string | null;
+
/**
* Identifier of the subscription or the in-app product.
*/
@@ -111,6 +117,7 @@ class ProductStoreDetails {
constructor(
basePlanId: string | null,
+ purchaseOptionId: string | null,
productId: string,
name: string,
title: string,
@@ -129,6 +136,7 @@ class ProductStoreDetails {
isInstallment: boolean,
) {
this.basePlanId = basePlanId;
+ this.purchaseOptionId = purchaseOptionId;
this.productId = productId;
this.name = name;
this.title = title;
diff --git a/src/internal/Mapper.ts b/src/internal/Mapper.ts
index 0ee2d367..4c4d37b0 100644
--- a/src/internal/Mapper.ts
+++ b/src/internal/Mapper.ts
@@ -63,6 +63,7 @@ export type QProduct = {
id: string;
storeId: string;
basePlanId?: string | null;
+ purchaseOptionId?: string | null;
type: string;
subscriptionPeriod?: QSubscriptionPeriod | null;
trialPeriod?: QSubscriptionPeriod | null;
@@ -74,6 +75,7 @@ export type QProduct = {
type QProductStoreDetails = {
basePlanId?: string | null,
+ purchaseOptionId?: string | null,
productId: string,
name: string,
title: string
@@ -622,6 +624,7 @@ class Mapper {
product.id,
product.storeId,
product.basePlanId ?? null,
+ product.purchaseOptionId ?? null,
storeDetails,
skProduct,
offeringId,
@@ -877,6 +880,7 @@ class Mapper {
return new ProductStoreDetails(
productStoreDetails.basePlanId ?? null,
+ productStoreDetails.purchaseOptionId ?? null,
productStoreDetails.productId,
productStoreDetails.name,
productStoreDetails.title,
diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts
index f5f9877f..f4599a45 100644
--- a/src/internal/QonversionInternal.ts
+++ b/src/internal/QonversionInternal.ts
@@ -25,7 +25,7 @@ import PromotionalOffer from '../dto/PromotionalOffer';
import RNQonversion from './specs/NativeQonversionModule';
import type { QPromoOfferDetails } from './specs/NativeQonversionModule';
-export const sdkVersion = "10.9.0";
+export const sdkVersion = "10.10.0";
export const sdkSource = "rn";
export default class QonversionInternal implements QonversionApi {
@@ -246,6 +246,10 @@ export default class QonversionInternal implements QonversionApi {
return mappedProducts;
}
+ /**
+ * @deprecated Offerings are deprecated. Manage paywall products with Remote Configs instead: https://documentation.qonversion.io/docs/migrate-offerings-to-remote-configs
+ * @see [Migrate Offerings to Remote Configs](https://documentation.qonversion.io/docs/migrate-offerings-to-remote-configs) for more details
+ */
async offerings(): Promise {
let offerings = await RNQonversion.offerings();
const mappedOfferings = Mapper.convertOfferings(offerings);