diff --git a/android/build.gradle b/android/build.gradle index 5673e2b..37ae63d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -73,7 +73,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "io.qonversion.android.sdk:sdk:3.0.0" + implementation "io.qonversion.android.sdk:sdk:3.1.0" } def configureReactNativePom(def pom) { diff --git a/android/src/main/java/com/reactlibrary/QonversionModule.java b/android/src/main/java/com/reactlibrary/QonversionModule.java index 58a8709..1e05f0a 100644 --- a/android/src/main/java/com/reactlibrary/QonversionModule.java +++ b/android/src/main/java/com/reactlibrary/QonversionModule.java @@ -14,6 +14,7 @@ import com.qonversion.android.sdk.Qonversion; import com.qonversion.android.sdk.QonversionEligibilityCallback; import com.qonversion.android.sdk.QonversionError; +import com.qonversion.android.sdk.QonversionErrorCode; import com.qonversion.android.sdk.QonversionExperimentsCallback; import com.qonversion.android.sdk.QonversionLaunchCallback; import com.qonversion.android.sdk.QonversionOfferingsCallback; @@ -38,12 +39,15 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.NotNull; +import android.app.Activity; import android.content.SharedPreferences; import androidx.preference.PreferenceManager; public class QonversionModule extends ReactContextBaseJavaModule { private final ReactApplicationContext reactContext; + private QonversionSDKInfo sdkInfoToSave; + private static final HashMap userPropertiesMap = new HashMap() {{ put(0, QUserProperties.Email); put(1, QUserProperties.Name); @@ -59,6 +63,17 @@ public QonversionModule(ReactApplicationContext reactContext) { this.reactContext = reactContext; } + private void storeSDKInfoToPreferences(QonversionSDKInfo sdkInfo,Activity currentActivity){ + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(currentActivity.getApplication()).edit(); + editor.putString(sdkInfo.sdkVersionKey, sdkInfo.sdkVersion); + editor.putString(sdkInfo.sourceKey, sdkInfo.source); + editor.apply(); + } + + private QonversionError generateActivityError () { + return new QonversionError(QonversionErrorCode.UnknownError, "Android current activity is null, cannot perform the process."); + } + @Override public String getName() { return "RNQonversion"; @@ -66,15 +81,31 @@ public String getName() { @ReactMethod public void storeSDKInfo(String sourceKey, String source, String sdkVersionKey, String sdkVersion) { - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getCurrentActivity().getApplication()).edit(); - editor.putString(sdkVersionKey, sdkVersion); - editor.putString(sourceKey, source); - editor.apply(); + Activity currentActivity = getCurrentActivity(); + QonversionSDKInfo sdkInfo = new QonversionSDKInfo(sourceKey, source, sdkVersionKey, sdkVersion); + + if(currentActivity == null){ + this.sdkInfoToSave = sdkInfo; + return; + } + + storeSDKInfoToPreferences(sdkInfo, currentActivity); } @ReactMethod public void launchWithKey(String key, Boolean observeMode, final Promise promise) { - Qonversion.launch(getCurrentActivity().getApplication(), key, observeMode, new QonversionLaunchCallback() + Activity currentActivity = getCurrentActivity(); + if(currentActivity == null){ + QonversionError qonversionError = generateActivityError(); + promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + return; + } + + if(this.sdkInfoToSave != null){ + storeSDKInfoToPreferences(this.sdkInfoToSave, currentActivity); + } + + Qonversion.launch(currentActivity.getApplication(), key, observeMode, new QonversionLaunchCallback() { @Override public void onSuccess(@NotNull QLaunchResult qLaunchResult) { @@ -84,14 +115,21 @@ public void onSuccess(@NotNull QLaunchResult qLaunchResult) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @ReactMethod public void purchase(String productId, final Promise promise) { - Qonversion.purchase(getCurrentActivity(), productId, new QonversionPermissionsCallback() { + Activity currentActivity = getCurrentActivity(); + if(currentActivity == null){ + QonversionError qonversionError = generateActivityError(); + promise.reject(qonversionError.getCode().toString(),qonversionError.getDescription()); + return; + } + + Qonversion.purchase(currentActivity, productId, new QonversionPermissionsCallback() { @Override public void onSuccess(@NotNull Map map) { WritableMap result = EntitiesConverter.mapPermissions(map); @@ -100,14 +138,21 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @ReactMethod public void updatePurchaseWithProrationMode(String productId, String oldProductId, Integer prorationMode, final Promise promise) { - Qonversion.updatePurchase(getCurrentActivity(), productId, oldProductId, prorationMode, new QonversionPermissionsCallback() { + Activity currentActivity = getCurrentActivity(); + if(currentActivity == null){ + QonversionError qonversionError = generateActivityError(); + promise.reject(qonversionError.getCode().toString(),qonversionError.getDescription()); + return; + } + + Qonversion.updatePurchase(currentActivity, productId, oldProductId, prorationMode, new QonversionPermissionsCallback() { @Override public void onSuccess(@NotNull Map map) { WritableMap result = EntitiesConverter.mapPermissions(map); @@ -116,7 +161,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -183,7 +228,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -199,7 +244,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -214,8 +259,8 @@ public void onSuccess(@NotNull QOfferings offerings) { } @Override - public void onError(@NotNull QonversionError error) { - promise.reject(error.getCode().toString(), error.getDescription()); + public void onError(@NotNull QonversionError qonversionError) { + rejectWithError(qonversionError, promise); } }); } @@ -232,7 +277,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -248,7 +293,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -264,7 +309,7 @@ public void onSuccess(@NotNull Map map) { @Override public void onError(@NotNull QonversionError qonversionError) { - promise.reject(qonversionError.getCode().toString(), qonversionError.getDescription()); + rejectWithError(qonversionError, promise); } }); } @@ -293,4 +338,9 @@ public void identify(String userID) { public void logout() { Qonversion.logout(); } + + private void rejectWithError(@NotNull QonversionError qonversionError, final Promise promise) { + String errorMessage = qonversionError.getDescription() + "\n" + qonversionError.getAdditionalMessage(); + promise.reject(qonversionError.getCode().toString(), errorMessage); + } } diff --git a/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java b/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java new file mode 100644 index 0000000..6ab9098 --- /dev/null +++ b/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java @@ -0,0 +1,15 @@ +package com.reactlibrary; + +public class QonversionSDKInfo { + public String sourceKey; + public String source; + public String sdkVersionKey; + public String sdkVersion; + + public QonversionSDKInfo(String sourceKey, String source, String sdkVersionKey, String sdkVersion){ + this.sourceKey = sourceKey; + this.source = source; + this.sdkVersionKey = sdkVersionKey; + this.sdkVersion = sdkVersion; + } +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 515bbc5..5d89c6f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -79,7 +79,7 @@ PODS: - glog (0.3.5) - libevent (2.1.12) - OpenSSL-Universal (1.1.180) - - Qonversion (2.15.0) + - Qonversion (2.16.2) - RCTRequired (0.63.4) - RCTTypeSafety (0.63.4): - FBLazyVector (= 0.63.4) @@ -247,7 +247,7 @@ PODS: - React-jsi (= 0.63.4) - React-jsinspector (0.63.4) - react-native-qonversion (2.6.1): - - Qonversion (= 2.15.0) + - Qonversion (= 2.16.2) - React - React-RCTActionSheet (0.63.4): - React-Core/RCTActionSheetHeaders (= 0.63.4) @@ -452,7 +452,7 @@ SPEC CHECKSUMS: glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b - Qonversion: 034fea872ffa6ec79047745ada7577bb4e02a227 + Qonversion: f847c5967b8fb7eb280372ab3cbc63056deae974 RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b React: b0a957a2c44da4113b0c4c9853d8387f8e64e615 @@ -463,7 +463,7 @@ SPEC CHECKSUMS: React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31 React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949 React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a - react-native-qonversion: ff70d28b6fc1df27c1ee55c119e0d2a7a2068f0a + react-native-qonversion: 5ee9a79c99a0f1aa54204c77c609869d596c8af2 React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336 React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0 diff --git a/ios/EntitiesConverter.m b/ios/EntitiesConverter.m index fd243da..b0032eb 100644 --- a/ios/EntitiesConverter.m +++ b/ios/EntitiesConverter.m @@ -142,6 +142,8 @@ + (NSDictionary *)convertDiscount:(SKProductDiscount *)discount API_AVAILABLE(io introductoryPrice[@"subscriptionPeriod"] = [introductorySubscriptionPeriod copy]; introductoryPrice[@"paymentMode"] = @(discount.paymentMode); + + introductoryPrice[@"currencySymbol"] = discount.priceLocale.currencySymbol; if (@available(iOS 12.2, *)) { introductoryPrice[@"identifier"] = discount.identifier; diff --git a/package.json b/package.json index 217c964..281dd96 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-qonversion", "title": "React Native Qonversion", - "version": "3.0.0", + "version": "3.1.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": "build/index.js", "scripts": { diff --git a/react-native-qonversion.podspec b/react-native-qonversion.podspec index 19f7212..7401bb6 100644 --- a/react-native-qonversion.podspec +++ b/react-native-qonversion.podspec @@ -22,5 +22,5 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency "Qonversion", "2.15.0" + s.dependency "Qonversion", "2.16.2" end diff --git a/src/classes/Mapper.ts b/src/classes/Mapper.ts index 127490b..51c6f0b 100644 --- a/src/classes/Mapper.ts +++ b/src/classes/Mapper.ts @@ -99,6 +99,7 @@ type QProductDiscount = { paymentMode: keyof typeof SKProductDiscountPaymentMode; identifier?: string; type: keyof typeof SKProductDiscountType; + currencySymbol: string; }; type QPermission = { @@ -121,6 +122,8 @@ type QOffering = { products: Array; }; +const skuDetailsPriceRatio = 1000000; + class Mapper { static convertLaunchResult(launchResult: QLaunchResult): LaunchResult { const products: Map = this.convertProducts( @@ -198,18 +201,33 @@ class Mapper { let skuDetails: SkuDetails | null = null; let price: number | undefined; let currencyCode: string | undefined; + let storeTitle: string | undefined; + let storeDescription: string | undefined; + let prettyIntroductoryPrice: string | undefined; if (product.storeProduct != null) { if (Platform.OS === "ios") { skProduct = Mapper.convertSKProduct(product.storeProduct as QSKProduct); price = parseFloat(skProduct.price); currencyCode = skProduct.currencyCode; + storeTitle = skProduct.localizedTitle; + storeDescription = skProduct.localizedDescription; + + if (skProduct.productDiscount) { + prettyIntroductoryPrice = skProduct.productDiscount.currencySymbol + skProduct.productDiscount.price; + } } else { skuDetails = Mapper.convertSkuDetails( product.storeProduct as QSkuDetails ); - price = skuDetails.priceAmountMicros / 1000000; + price = skuDetails.priceAmountMicros / skuDetailsPriceRatio; currencyCode = skuDetails.priceCurrencyCode; + storeTitle = skuDetails.title; + storeDescription = skuDetails.description; + + if (skuDetails.introductoryPrice.length > 0) { + prettyIntroductoryPrice = skuDetails.introductoryPrice; + } } } @@ -223,7 +241,10 @@ class Mapper { product.prettyPrice, trialDuration, price, - currencyCode + currencyCode, + storeTitle, + storeDescription, + prettyIntroductoryPrice ); return mappedProduct; @@ -349,7 +370,8 @@ class Mapper { subscriptionPeriod, SKProductDiscountPaymentMode[discount.paymentMode], discount.identifier, - SKProductDiscountType[discount.type] + SKProductDiscountType[discount.type], + discount.currencySymbol ); } diff --git a/src/classes/Product.ts b/src/classes/Product.ts index d98d0fb..848cedb 100644 --- a/src/classes/Product.ts +++ b/src/classes/Product.ts @@ -13,6 +13,9 @@ class Product { trialDuration?: TrialDurations; price?: number; currencyCode?: string; + storeTitle?: string; + storeDescription?: string; + prettyIntroductoryPrice?: string; constructor( qonversionID: string, @@ -24,7 +27,10 @@ class Product { prettyPrice: string | undefined, trialDuration: TrialDurations | undefined, price: number | undefined, - currencyCode: string | undefined + currencyCode: string | undefined, + storeTitle: string | undefined, + storeDescription: string | undefined, + prettyIntroductoryPrice: string | undefined ) { this.qonversionID = qonversionID; this.storeID = storeID; @@ -36,6 +42,9 @@ class Product { this.trialDuration = trialDuration; this.price = price; this.currencyCode = currencyCode; + this.storeTitle = storeTitle; + this.storeDescription = storeDescription; + this.prettyIntroductoryPrice = prettyIntroductoryPrice; } } diff --git a/src/classes/Qonversion.ts b/src/classes/Qonversion.ts index 68d3dd8..c3555f3 100644 --- a/src/classes/Qonversion.ts +++ b/src/classes/Qonversion.ts @@ -13,7 +13,7 @@ const { RNQonversion } = NativeModules; const keyPrefix = "com.qonversion.keys"; const sourceKey = keyPrefix + ".source"; const versionKey = keyPrefix + ".sourceVersion"; -const sdkVersion = "3.0.0"; +const sdkVersion = "3.1.0"; export default class Qonversion { static async launchWithKey( diff --git a/src/classes/storeProducts/SKProductDiscount.ts b/src/classes/storeProducts/SKProductDiscount.ts index 245f90d..ade138c 100644 --- a/src/classes/storeProducts/SKProductDiscount.ts +++ b/src/classes/storeProducts/SKProductDiscount.ts @@ -12,6 +12,7 @@ class SKProductDiscount { paymentMode: SKProductDiscountPaymentModes; identifier?: string; type: SKProductDiscountTypes; + currencySymbol: string; constructor( price: string, @@ -20,7 +21,8 @@ class SKProductDiscount { subscriptionPeriod: SKSubscriptionPeriod | undefined, paymentMode: SKProductDiscountPaymentModes, identifier: string | undefined, - type: SKProductDiscountTypes + type: SKProductDiscountTypes, + currencySymbol: string ) { this.price = price; this.localeIdentifier = localeIdentifier; @@ -29,6 +31,7 @@ class SKProductDiscount { this.paymentMode = paymentMode; this.identifier = identifier; this.type = type; + this.currencySymbol = currencySymbol; } }