diff --git a/CHANGELOG.md b/CHANGELOG.md index cec7f934..cc50852e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.5.0 +- Adds create alias, identify and reset + ## 1.4.4 - Fixes download script issue diff --git a/RNPurchases.podspec b/RNPurchases.podspec index cae5ecb7..35f92b1f 100644 --- a/RNPurchases.podspec +++ b/RNPurchases.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |spec| spec.exclude_files = "ios/Purchases.framework" spec.dependency "React" - spec.dependency "Purchases" + spec.dependency "Purchases", "~> 1.2.0" end diff --git a/android/build.gradle b/android/build.gradle index 9a171d9a..da5a7a50 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,6 +29,6 @@ repositories { dependencies { provided 'com.facebook.react:react-native:+' - compile 'com.revenuecat.purchases:purchases:1.3.7' + compile 'com.revenuecat.purchases:purchases:1.4.0' compile 'com.android.billingclient:billing:1.1' } diff --git a/android/src/main/java/com/reactlibrary/RNPurchasesModule.java b/android/src/main/java/com/reactlibrary/RNPurchasesModule.java index a547f38e..43727bc0 100644 --- a/android/src/main/java/com/reactlibrary/RNPurchasesModule.java +++ b/android/src/main/java/com/reactlibrary/RNPurchasesModule.java @@ -21,6 +21,7 @@ import com.revenuecat.purchases.Purchases; import com.revenuecat.purchases.util.Iso8601Utils; +import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -38,7 +39,6 @@ public class RNPurchasesModule extends ReactContextBaseJavaModule implements Pur private static final String TRANSACTIONS_RESTORED = "Purchases-RestoredTransactions"; private final ReactApplicationContext reactContext; - private Purchases purchases; public RNPurchasesModule(ReactApplicationContext reactContext) { super(reactContext); @@ -51,37 +51,43 @@ public String getName() { } private void checkPurchases() { - if (purchases == null) { + if (Purchases.getSharedInstance() == null) { throw new RuntimeException("You must call setupPurchases first"); } } + public void onCatalystInstanceDestroy() { - if (purchases != null) { - purchases.close(); - purchases = null; + if (Purchases.getSharedInstance() != null) { + Purchases.getSharedInstance().close(); } } @ReactMethod public void setupPurchases(String apiKey, String appUserID, final Promise promise) { - if (purchases != null) { - purchases.close(); + if (Purchases.getSharedInstance() != null) { + Purchases.getSharedInstance().close(); } - purchases = new Purchases.Builder(reactContext, apiKey, this).appUserID(appUserID).build(); + Purchases purchases = new Purchases.Builder(reactContext, apiKey).appUserID(appUserID).build(); + purchases.setListener(this); + Purchases.setSharedInstance(purchases); promise.resolve(null); } @ReactMethod - public void setIsUsingAnonymousID(boolean isUsingAnonymousID) { + public void setAllowSharingStoreAccount(boolean allowSharingStoreAccount) { checkPurchases(); - purchases.setIsUsingAnonymousID(isUsingAnonymousID); + Purchases.getSharedInstance().setAllowSharingPlayStoreAccount(allowSharingStoreAccount); } @ReactMethod public void addAttributionData(ReadableMap data, Integer network) { checkPurchases(); try { - purchases.addAttributionData(convertMapToJson(data), network); + for (Purchases.AttributionNetwork attributionNetwork : Purchases.AttributionNetwork.values()) { + if (attributionNetwork.getServerValue() == network) { + Purchases.getSharedInstance().addAttributionData(convertMapToJson(data), attributionNetwork); + } + } } catch (JSONException e) { Log.e("RNPurchases", "Error parsing attribution date to JSON: " + e.getLocalizedMessage()); } @@ -114,7 +120,8 @@ private WritableMap mapForSkuDetails(final SkuDetails detail) { public void getEntitlements(final Promise promise) { checkPurchases(); - purchases.getEntitlements(new Purchases.GetEntitlementsHandler() { + Purchases.getSharedInstance().getEntitlements(new Purchases.GetEntitlementsHandler() { + @Override public void onReceiveEntitlements(Map entitlementMap) { WritableMap response = Arguments.createMap(); @@ -142,7 +149,7 @@ public void onReceiveEntitlements(Map entitlementMap) { } @Override - public void onReceiveEntitlementsError(int domain, int code, String message) { + public void onReceiveEntitlementsError(@NotNull Purchases.ErrorDomains domain, int code, @NotNull String message) { promise.reject("ERROR_FETCHING_ENTITLEMENTS", message); } }); @@ -170,9 +177,9 @@ public void onReceiveSkus(List skus) { }; if (type.toLowerCase().equals("subs")) { - purchases.getSubscriptionSkus(productIDList, handler); + Purchases.getSharedInstance().getSubscriptionSkus(productIDList, handler); } else { - purchases.getNonSubscriptionSkus(productIDList, handler); + Purchases.getSharedInstance().getNonSubscriptionSkus(productIDList, handler); } } @@ -185,18 +192,47 @@ public void makePurchase(String productIdentifier, ReadableArray oldSkus, String oldSkusList.add((String)oldSku); } - purchases.makePurchase(getCurrentActivity(), productIdentifier, type, oldSkusList); + Purchases.getSharedInstance().makePurchase(getCurrentActivity(), productIdentifier, type, oldSkusList); } @ReactMethod public void getAppUserID(final Promise promise) { - promise.resolve(purchases.getAppUserID()); + promise.resolve(Purchases.getSharedInstance().getAppUserID()); } @ReactMethod public void restoreTransactions() { checkPurchases(); - purchases.restorePurchasesForPlayStoreAccount(); + Purchases.getSharedInstance().restorePurchasesForPlayStoreAccount(); + } + + @ReactMethod + public void reset() { + checkPurchases(); + Purchases.getSharedInstance().reset(); + } + + @ReactMethod + public void identify(String appUserID) { + checkPurchases(); + Purchases.getSharedInstance().identify(appUserID); + } + + @ReactMethod + public void createAlias(String newAppUserID, final Promise promise) { + checkPurchases(); + Purchases.getSharedInstance().createAlias(newAppUserID, new Purchases.AliasHandler() { + + @Override + public void onSuccess() { + promise.resolve(null); + } + + @Override + public void onError(@NotNull Purchases.ErrorDomains errorDomains, int i, @NotNull String s) { + promise.reject("ERROR_ALIASING", s); + } + }); } private void sendEvent(String eventName, @@ -267,19 +303,16 @@ public void onCompletedPurchase(String sku, PurchaserInfo purchaserInfo) { sendEvent(PURCHASE_COMPLETED_EVENT, map); } - private WritableMap errorMap(int domain, int code, String message) { + private WritableMap errorMap(Purchases.ErrorDomains domain, int code, String message) { WritableMap errorMap = Arguments.createMap(); String domainString; - switch (domain) { - case Purchases.ErrorDomains.REVENUECAT_BACKEND: - domainString = "RevenueCat Backend"; - break; - case Purchases.ErrorDomains.PLAY_BILLING: - domainString = "Play Billing"; - break; - default: - domainString = "Unknown"; + if (domain == Purchases.ErrorDomains.REVENUECAT_BACKEND) { + domainString = "RevenueCat Backend"; + } else if (domain == Purchases.ErrorDomains.PLAY_BILLING) { + domainString = "Play Billing"; + } else { + domainString = "Unknown"; } errorMap.putString("message", message); @@ -290,7 +323,7 @@ private WritableMap errorMap(int domain, int code, String message) { } @Override - public void onFailedPurchase(int domain, int code, String message) { + public void onFailedPurchase(@NotNull Purchases.ErrorDomains domain, int code, @org.jetbrains.annotations.Nullable String message) { WritableMap map = Arguments.createMap(); map.putMap("error", errorMap(domain, code, message)); @@ -316,7 +349,7 @@ public void onRestoreTransactions(PurchaserInfo purchaserInfo) { } @Override - public void onRestoreTransactionsFailed(int domain, int code, String reason) { + public void onRestoreTransactionsFailed(@NotNull Purchases.ErrorDomains domain, int code, @org.jetbrains.annotations.Nullable String reason) { sendEvent(TRANSACTIONS_RESTORED, errorMap(domain, code, reason)); } @@ -374,5 +407,4 @@ private static JSONArray convertArrayToJson(ReadableArray readableArray) throws } return array; } - } diff --git a/index.js b/index.js index 80fdcdf2..cc384dc4 100644 --- a/index.js +++ b/index.js @@ -158,4 +158,27 @@ export default class Purchases { static getAppUserID() { return RNPurchases.getAppUserID(); } + + /** This function will alias two appUserIDs together. + * @param alias The new appUserID that should be linked to the currently identified appUserID + * */ + static createAlias(newAppUserID) { + return RNPurchases.createAlias(newAppUserID); + } + + /** + * This function will identify the current user with an appUserID. Typically this would be used after a logout to identify a new user without calling configure + * @param appUserID The appUserID that should be linked to the currently user + */ + static identify(newAppUserID) { + return RNPurchases.identify(newAppUserID); + } + + /** + * Resets the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. + */ + static reset() { + return RNPurchases.reset(); + } + }; diff --git a/ios/RNPurchases.m b/ios/RNPurchases.m index 6f9c1ecc..46d45fd9 100644 --- a/ios/RNPurchases.m +++ b/ios/RNPurchases.m @@ -9,7 +9,6 @@ @interface RNPurchases () -@property (nonatomic, retain) RCPurchases *purchases; @property (nonatomic, retain) NSMutableDictionary *products; @end @@ -38,33 +37,33 @@ - (dispatch_queue_t)methodQueue appUserID:(NSString *)appUserID resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -{ - self.purchases.delegate = nil; +{ + RCPurchases.sharedPurchases.delegate = nil; self.products = [NSMutableDictionary new]; - self.purchases = [[RCPurchases alloc] initWithAPIKey:apiKey appUserID:appUserID]; - self.purchases.delegate = self; + [RCPurchases configureWithAPIKey:apiKey appUserID:appUserID]; + RCPurchases.sharedPurchases.delegate = self; resolve(nil); } -RCT_EXPORT_METHOD(setIsUsingAnonymousID:(BOOL)isUsingAnonymousID) +RCT_EXPORT_METHOD(setAllowSharingStoreAccount:(BOOL)allowSharingStoreAccount) { - self.purchases.isUsingAnonymousID = isUsingAnonymousID; + RCPurchases.sharedPurchases.allowSharingAppStoreAccount = allowSharingStoreAccount; } RCT_REMAP_METHOD(addAttributionData, addAttributionData:(NSDictionary *)data forNetwork:(NSInteger)network) { - NSAssert(self.purchases, @"You must call setup first."); - [self.purchases addAttributionData:data fromNetwork:(RCAttributionNetwork)network]; + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases addAttributionData:data fromNetwork:(RCAttributionNetwork)network]; } RCT_REMAP_METHOD(getEntitlements, getEntitlementsWithResolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - NSAssert(self.purchases, @"You must call setup first."); - [self.purchases entitlements:^(NSDictionary * _Nonnull entitlements) { + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases entitlements:^(NSDictionary * _Nonnull entitlements) { NSMutableDictionary *result = [NSMutableDictionary new]; for (NSString *entId in entitlements) { RCEntitlement *entitlement = entitlements[entId]; @@ -90,9 +89,9 @@ - (dispatch_queue_t)methodQueue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - NSAssert(self.purchases, @"You must call setup first."); + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); - [self.purchases productsWithIdentifiers:products completion:^(NSArray * _Nonnull products) { + [RCPurchases.sharedPurchases productsWithIdentifiers:products completion:^(NSArray * _Nonnull products) { NSMutableArray *productObjects = [NSMutableArray new]; for (SKProduct *p in products) { self.products[p.productIdentifier] = p; @@ -107,26 +106,50 @@ - (dispatch_queue_t)methodQueue oldSkus:(NSArray *)oldSkus type:(NSString *)type) { - NSAssert(self.purchases, @"You must call setup first."); + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); if (self.products[productIdentifier] == nil) { NSLog(@"Purchases cannot find product. Did you call getProductInfo first?"); return; } - [self.purchases makePurchase:self.products[productIdentifier]]; + [RCPurchases.sharedPurchases makePurchase:self.products[productIdentifier]]; } RCT_EXPORT_METHOD(restoreTransactions) { - NSAssert(self.purchases, @"You must call setup first."); - [self.purchases restoreTransactionsForAppStoreAccount]; + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases restoreTransactionsForAppStoreAccount]; } RCT_REMAP_METHOD(getAppUserID, getAppUserIDWithResolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - resolve(self.purchases.appUserID); + resolve(RCPurchases.sharedPurchases.appUserID); +} + +RCT_EXPORT_METHOD(createAlias:(NSString * _Nullable)newAppUserID + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases createAlias:newAppUserID completion:^(NSError * _Nullable error) { + if (error) { + reject("ERROR_ALIASING"); + } else { + resolve(nil); + } + }]; +} + +RCT_EXPORT_METHOD(identify:(NSString * _Nullable)appUserID) { + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases identify:appUserID]; +} + +RCT_EXPORT_METHOD(reset) { + NSAssert(RCPurchases.sharedPurchases, @"You must call setup first."); + [RCPurchases.sharedPurchases reset]; } #pragma mark - diff --git a/package.json b/package.json index 7a2fc048..65e9c258 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "react-native-purchases", - "version": "1.4.4", + "version": "1.5.0", "description": "", "main": "index.js", "scripts": { - "fetch:ios:sdk": "./scripts/download-purchases-framework.sh 1.1.5", + "fetch:ios:sdk": "./scripts/download-purchases-framework.sh 1.2.0", "preinstall": "npm run fetch:ios:sdk", "test": "echo \"Error: no test specified\" && exit 1" },