diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..47b09a65 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# text +*.sh text eol=lf \ No newline at end of file diff --git a/.version b/.version index 7e98f24e..c9277c5a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -8.2.4 \ No newline at end of file +9.0.0 \ No newline at end of file diff --git a/RevenueCat/Editor/revenuecat.purchases-unity.Editor.asmdef b/RevenueCat/Editor/RevenueCat.Editor.asmdef similarity index 67% rename from RevenueCat/Editor/revenuecat.purchases-unity.Editor.asmdef rename to RevenueCat/Editor/RevenueCat.Editor.asmdef index e03ad620..933d1983 100644 --- a/RevenueCat/Editor/revenuecat.purchases-unity.Editor.asmdef +++ b/RevenueCat/Editor/RevenueCat.Editor.asmdef @@ -1,8 +1,10 @@ { - "name": "revenuecat.purchases-unity.Editor", - "rootNamespace": "", + "name": "RevenueCat.Editor", + "rootNamespace": "RevenueCat.Editor", "references": [], - "includePlatforms": [], + "includePlatforms": [ + "Editor" + ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, diff --git a/RevenueCat/Editor/revenuecat.purchases-unity.Editor.asmdef.meta b/RevenueCat/Editor/RevenueCat.Editor.asmdef.meta similarity index 100% rename from RevenueCat/Editor/revenuecat.purchases-unity.Editor.asmdef.meta rename to RevenueCat/Editor/RevenueCat.Editor.asmdef.meta diff --git a/RevenueCat/Editor/RevenueCatPostInstall.cs b/RevenueCat/Editor/RevenueCatPostInstall.cs index fcce1b58..03d5693a 100644 --- a/RevenueCat/Editor/RevenueCatPostInstall.cs +++ b/RevenueCat/Editor/RevenueCatPostInstall.cs @@ -1,7 +1,5 @@ -#if UNITY_EDITOR && (UNITY_IOS || UNITY_VISIONOS) - -using System.IO; +#if UNITY_IOS || UNITY_VISIONOS using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; @@ -13,9 +11,9 @@ public static class XcodeSwiftVersionPostProcess [PostProcessBuild(999)] public static void OnPostProcessBuild(BuildTarget buildTarget, string path) { - if (buildTarget == BuildTarget.iOS) + if (buildTarget == BuildTarget.iOS || buildTarget == BuildTarget.VisionOS) { - Debug.Log("Installing for iOS. Setting ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES and ENABLE_BITCODE to NO and adding StoreKit"); + Debug.Log($"RevenueCat OnPostProcessBuild {buildTarget}: Setting ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES and ENABLE_BITCODE to NO and adding StoreKit"); ModifyFrameworks(path); AddStoreKitFramework(path); } @@ -23,11 +21,11 @@ public static void OnPostProcessBuild(BuildTarget buildTarget, string path) private static void ModifyFrameworks(string path) { - string projPath = PBXProject.GetPBXProjectPath(path); + var projPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromFile(projPath); - string mainTargetGuid = project.GetUnityMainTargetGuid(); + var mainTargetGuid = project.GetUnityMainTargetGuid(); foreach (var targetGuid in new[] { mainTargetGuid, project.GetUnityFrameworkTargetGuid() }) { @@ -38,21 +36,17 @@ private static void ModifyFrameworks(string path) } project.SetBuildProperty(mainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); - project.WriteToFile(projPath); } private static void AddStoreKitFramework(string path) { - string projPath = PBXProject.GetPBXProjectPath(path); + var projPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromFile(projPath); - - string mainTargetGUID = project.GetUnityMainTargetGuid(); - project.AddFrameworkToProject(mainTargetGUID, "StoreKit.framework", false); - + project.AddFrameworkToProject(project.GetUnityMainTargetGuid(), "StoreKit.framework", false); project.WriteToFile(projPath); } } -#endif +#endif // UNITY_IOS || UNITY_VISIONOS diff --git a/RevenueCat/Plugins/Android/PurchasesWrapper.java b/RevenueCat/Plugins/Android/PurchasesWrapper.java index ac1197ef..edf647e1 100644 --- a/RevenueCat/Plugins/Android/PurchasesWrapper.java +++ b/RevenueCat/Plugins/Android/PurchasesWrapper.java @@ -1,4 +1,4 @@ -package com.revenuecat.purchasesunity; +package com.revenuecat.purchases.unity; import android.util.Log; @@ -38,85 +38,106 @@ import kotlin.Unit; public class PurchasesWrapper { - private static final String RECEIVE_STOREFRONT = "_receiveStorefront"; - private static final String RECEIVE_PRODUCTS = "_receiveProducts"; - private static final String GET_CUSTOMER_INFO = "_getCustomerInfo"; - private static final String MAKE_PURCHASE = "_makePurchase"; - private static final String RECEIVE_CUSTOMER_INFO = "_receiveCustomerInfo"; - private static final String RESTORE_PURCHASES = "_restorePurchases"; - private static final String LOG_IN = "_logIn"; - private static final String LOG_OUT = "_logOut"; - private static final String GET_OFFERINGS = "_getOfferings"; - private static final String GET_CURRENT_OFFERING_FOR_PLACEMENT = "_getCurrentOfferingForPlacement"; - private static final String SYNC_ATTRIBUTES_AND_OFFERINGS_IF_NEEDED = "_syncAttributesAndOfferingsIfNeeded"; - private static final String CHECK_ELIGIBILITY = "_checkTrialOrIntroductoryPriceEligibility"; - private static final String CAN_MAKE_PAYMENTS = "_canMakePayments"; - private static final String GET_PROMOTIONAL_OFFER = "_getPromotionalOffer"; - private static final String GET_LWA_CONSENT_STATUS = "_getAmazonLWAConsentStatus"; - private static final String SYNC_PURCHASES = "_syncPurchases"; - private static final String PARSE_AS_WEB_PURCHASE_REDEMPTION = "_parseAsWebPurchaseRedemption"; - private static final String REDEEM_WEB_PURCHASE = "_redeemWebPurchase"; - private static final String GET_VIRTUAL_CURRENCIES = "_getVirtualCurrencies"; - private static final String GET_ELIGIBLE_WIN_BACK_OFFERS_FOR_PRODUCT = "_getEligibleWinBackOffersForProduct"; - private static final String GET_ELIGIBLE_WIN_BACK_OFFERS_FOR_PACKAGE = "_getEligibleWinBackOffersForPackage"; - private static final String PURCHASE_PRODUCT_WITH_WIN_BACK_OFFER = "_purchaseProductWithWinBackOffer"; - private static final String PURCHASE_PACKAGE_WITH_WIN_BACK_OFFER = "_purchasePackageWithWinBackOffer"; - private static final String HANDLE_LOG = "_handleLog"; + // Java-side callbacks registered from C#; no UnitySendMessage fallback. + public interface Callback { + /** Called when the operation completes successfully. */ + void onReceived(String json); - private static final String PLATFORM_NAME = "unity"; - private static final String PLUGIN_VERSION = "8.2.4"; + /** Called when the operation fails with an error. */ + void onError(String json); + } - private static String gameObject; + // Java-side callbacks registered from C#; no UnitySendMessage fallback. + public interface CustomerInfoHandler { + /** Called when a customer info is received. */ + void onCustomerReceived(String json); + } + + public interface LogHandler { + /** Called when a Log is received. */ + void onLogReceived(String json); + } + + private static final String PLATFORM_NAME = "unity"; + private static final String PLUGIN_VERSION = "9.0.0"; + + private static volatile CustomerInfoHandler customerInfoHandler = null; + + public static void configure( + CustomerInfoHandler customerInfoHandler, + LogHandler logHandler, + String apiKey, + String appUserId, + String purchasesAreCompletedBy, + String userDefaultsSuiteName, + boolean useAmazon, + boolean shouldShowInAppMessagesAutomatically, + boolean autoSyncPurchases, + String entitlementVerificationMode, + boolean pendingTransactionsForPrepaidPlansEnabled) { + try { + PurchasesWrapper.customerInfoHandler = customerInfoHandler; + CommonKt.setLogHandlerWithOnResult(new OnResult() { + @Override + public void onReceived(@NonNull Map map) { + logHandler.onLogReceived(MappersHelpersKt.convertToJson(map).toString()); + } - private static UpdatedCustomerInfoListener listener = new UpdatedCustomerInfoListener() { - @Override - public void onReceived(@NonNull CustomerInfo customerInfo) { - CustomerInfoMapperKt.mapAsync( - customerInfo, - map -> { - sendCustomerInfo(map, RECEIVE_CUSTOMER_INFO); + @Override + public void onError(@NonNull ErrorContainer errorContainer) { + // Intentionally left blank since it will never be called + } + }); + PlatformInfo platformInfo = new PlatformInfo(PLATFORM_NAME, PLUGIN_VERSION); + Store store = useAmazon ? Store.AMAZON : Store.PLAY_STORE; + DangerousSettings dangerousSettings = new DangerousSettings(autoSyncPurchases); + CommonKt.configure( + UnityPlayer.currentActivity, + apiKey, + appUserId, + purchasesAreCompletedBy, + platformInfo, + store, + dangerousSettings, + shouldShowInAppMessagesAutomatically, + entitlementVerificationMode, + pendingTransactionsForPrepaidPlansEnabled); + Purchases.getSharedInstance().setUpdatedCustomerInfoListener(new UpdatedCustomerInfoListener() { + @Override + public void onReceived(@NonNull CustomerInfo customerInfo) { + CustomerInfoMapperKt.mapAsync(customerInfo, map -> { + sendCustomerInfo(map); return Unit.INSTANCE; - } - ); + }); + } + }); + } catch (Exception e) { + Log.e("Purchases", "Error during setup: " + e.getLocalizedMessage()); } - }; - - public static void setup(String apiKey, - String appUserId, - String gameObject, - String purchasesAreCompletedBy, - String userDefaultsSuiteName, - boolean useAmazon, - boolean shouldShowInAppMessagesAutomatically, - String dangerousSettingsJSON, - String entitlementVerificationMode, - boolean pendingTransactionsForPrepaidPlansEnabled) { - PurchasesWrapper.gameObject = gameObject; - PlatformInfo platformInfo = new PlatformInfo(PLATFORM_NAME, PLUGIN_VERSION); - Store store = useAmazon ? Store.AMAZON : Store.PLAY_STORE; - DangerousSettings dangerousSettings = getDangerousSettingsFromJSON(dangerousSettingsJSON); - CommonKt.configure(UnityPlayer.currentActivity, apiKey, appUserId, purchasesAreCompletedBy, platformInfo, store, - dangerousSettings, shouldShowInAppMessagesAutomatically, entitlementVerificationMode, - pendingTransactionsForPrepaidPlansEnabled); - Purchases.getSharedInstance().setUpdatedCustomerInfoListener(listener); - } - - public static void getStorefront() { - CommonKt.getStorefront(storefrontMap -> { - if (storefrontMap != null) { - sendJSONObject(MappersHelpersKt.convertToJson(storefrontMap), RECEIVE_STOREFRONT); - } else { - sendEmptyJSONObject(RECEIVE_STOREFRONT); - } - return null; - }); } - public static void getProducts(String jsonProducts, String type) { + public static void getStorefront(Callback callback) { + try { + CommonKt.getStorefront(storefrontMap -> { + if (storefrontMap != null) { + callback.onReceived(MappersHelpersKt.convertToJson(storefrontMap).toString()); + } else { + callback.onReceived("{}"); + } + return null; + }); + } catch (Exception e) { + Log.e("Purchases", "Error during getStorefront: " + e.getLocalizedMessage()); + callback.onError("{\"error\":\"Error during getStorefront: " + e.getLocalizedMessage() + "\"}"); + } + } + + public static void getProducts(Callback callback, String jsonProducts, String type) { try { JSONObject request = new JSONObject(jsonProducts); JSONArray products = request.getJSONArray("productIdentifiers"); List productIds = new ArrayList<>(); + for (int i = 0; i < products.length(); i++) { String product = products.getString(i); productIds.add(product); @@ -128,28 +149,32 @@ public void onReceived(List> map) { try { JSONObject object = new JSONObject(); object.put("products", MappersHelpersKt.convertToJsonArray(map)); - sendJSONObject(object, RECEIVE_PRODUCTS); + callback.onReceived(object.toString()); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); } } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, RECEIVE_PRODUCTS); + callback.onError(getError(errorContainer)); } }); } catch (JSONException e) { Log.e("Purchases", "Failure parsing product identifiers " + jsonProducts); + callback.onError("{\"error\":\"Failure parsing product identifiers " + jsonProducts + "\"}"); } } - public static void purchaseProduct(final String productIdentifier, - final String type, - @Nullable final String oldSKU, - final int prorationMode, - final boolean isPersonalized, - @Nullable final String presentedOfferingContextJSON) { + public static void purchaseProduct( + Callback callback, + final String productIdentifier, + final String type, + @Nullable final String oldSKU, + final int prorationMode, + final boolean isPersonalized, + @Nullable final String presentedOfferingContextJSON) { Map presentedOfferingContext = null; try { if (presentedOfferingContextJSON != null) { @@ -158,6 +183,8 @@ public static void purchaseProduct(final String productIdentifier, } } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"Failure parsing presentedOfferingContextJSON " + + presentedOfferingContextJSON + "\"}"); } CommonKt.purchaseProduct( @@ -172,25 +199,27 @@ public static void purchaseProduct(final String productIdentifier, new OnResult() { @Override public void onReceived(Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), MAKE_PURCHASE); + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); } @Override public void onError(ErrorContainer errorContainer) { - sendErrorPurchase(errorContainer); + callback.onError(getError(errorContainer)); } }); } - public static void purchaseProduct(String productIdentifier, String type) { - purchaseProduct(productIdentifier, type, null, 0, false, null); + public static void purchaseProduct(Callback callback, String productIdentifier, String type) { + purchaseProduct(callback, productIdentifier, type, null, 0, false, null); } - public static void purchasePackage(String packageIdentifier, - String presentedOfferingContextJSON, - @Nullable final String oldSKU, - final int prorationMode, - final boolean isPersonalized) { + public static void purchasePackage( + Callback callback, + String packageIdentifier, + String presentedOfferingContextJSON, + @Nullable final String oldSKU, + final int prorationMode, + final boolean isPersonalized) { try { JSONObject presentedOfferingContextJSONObject = new JSONObject(presentedOfferingContextJSON); Map presentedOfferingContext = MappersHelpersKt.convertToMap(presentedOfferingContextJSONObject); @@ -205,99 +234,142 @@ public static void purchasePackage(String packageIdentifier, new OnResult() { @Override public void onReceived(Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), MAKE_PURCHASE); + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); } @Override public void onError(ErrorContainer errorContainer) { - sendErrorPurchase(errorContainer); + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("error", MappersHelpersKt.convertToJson(errorContainer.getInfo())); + jsonObject.put("userCancelled", errorContainer.getInfo().get("userCancelled")); + } catch (JSONException e) { + logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); + return; + } + callback.onError(jsonObject.toString()); } }); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"Failure parsing presentedOfferingContextJSON " + + presentedOfferingContextJSON + "\"}"); } } - public static void purchasePackage(String packageIdentifier, - String offeringIdentifier) { - purchasePackage(packageIdentifier, offeringIdentifier, null, 0, false); + public static void purchasePackage( + Callback callback, + String packageIdentifier, + String offeringIdentifier) { + purchasePackage(callback, packageIdentifier, offeringIdentifier, null, 0, false); } - public static void purchaseSubscriptionOption(final String productIdentifer, - final String optionIdentifier, - @Nullable final String oldSKU, - final int prorationMode, - final boolean isPersonalized, - @Nullable final String presentedOfferingContextJSON) { + public static void purchaseSubscriptionOption( + Callback callback, + final String productIdentifier, + final String optionIdentifier, + @Nullable final String oldSKU, + final int prorationMode, + final boolean isPersonalized, + @Nullable final String presentedOfferingContextJSON) { Map presentedOfferingContext = null; try { if (presentedOfferingContextJSON != null) { JSONObject presentedOfferingContextJSONObject = new JSONObject(presentedOfferingContextJSON); presentedOfferingContext = MappersHelpersKt.convertToMap(presentedOfferingContextJSONObject); } + + CommonKt.purchaseSubscriptionOption( + UnityPlayer.currentActivity, + productIdentifier, + optionIdentifier, + oldSKU, + (prorationMode == 0) ? null : prorationMode, + isPersonalized, + presentedOfferingContext, + new OnResult() { + @Override + public void onReceived(Map map) { + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); + } + + @Override + public void onError(ErrorContainer errorContainer) { + callback.onError(getError(errorContainer)); + } + }); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"Failure parsing presentedOfferingContextJSON " + + presentedOfferingContextJSON + "\"}"); } - - CommonKt.purchaseSubscriptionOption( - UnityPlayer.currentActivity, - productIdentifer, - optionIdentifier, - oldSKU, - (prorationMode == 0) ? null : prorationMode, - isPersonalized, - presentedOfferingContext, - new OnResult() { - @Override - public void onReceived(Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), MAKE_PURCHASE); - } - - @Override - public void onError(ErrorContainer errorContainer) { - sendErrorPurchase(errorContainer); - } - }); } - public static void restorePurchases() { - CommonKt.restorePurchases(getCustomerInfoListener(RESTORE_PURCHASES)); + public static void restorePurchases(Callback callback) { + CommonKt.restorePurchases(getCustomerInfoListener(callback)); } - public static void logIn(String appUserId) { - CommonKt.logIn(appUserId, getLogInListener(LOG_IN)); + public static void logIn(Callback callback, String appUserId) { + CommonKt.logIn(appUserId, new OnResult() { + @Override + public void onReceived(Map map) { + JSONObject jsonObject = new JSONObject(); + try { + Map customerInfoMap = (Map) map.get("customerInfo"); + jsonObject.put("customerInfo", MappersHelpersKt.convertToJson(customerInfoMap)); + jsonObject.put("created", (Boolean) map.get("created")); + } catch (ClassCastException castException) { + Log.e("Purchases", "invalid casting Error: " + castException.getLocalizedMessage()); + callback.onError("{\"error\":\"invalid casting Error: " + + castException.getLocalizedMessage() + "\"}"); + return; + } catch (JSONException e) { + logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); + return; + } + callback.onReceived(jsonObject.toString()); + } + + @Override + public void onError(ErrorContainer errorContainer) { + callback.onError(getError(errorContainer)); + } + }); } - public static void logOut() { - CommonKt.logOut(getCustomerInfoListener(LOG_OUT)); + public static void logOut(Callback callback) { + CommonKt.logOut(getCustomerInfoListener(callback)); } public static void setAllowSharingStoreAccount(boolean allowSharingStoreAccount) { CommonKt.setAllowSharingAppStoreAccount(allowSharingStoreAccount); } - public static void getOfferings() { + public static void getOfferings(Callback callback) { CommonKt.getOfferings(new OnResult() { @Override public void onReceived(Map map) { try { JSONObject object = new JSONObject(); object.put("offerings", MappersHelpersKt.convertToJson(map)); - sendJSONObject(object, GET_OFFERINGS); + callback.onReceived(object.toString()); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); } } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, GET_OFFERINGS); + callback.onError(getError(errorContainer)); } }); } - public static void getCurrentOfferingForPlacement(String placementIdentifier) { + public static void getCurrentOfferingForPlacement(Callback callback, String placementIdentifier) { CommonKt.getCurrentOfferingForPlacement(placementIdentifier, new OnNullableResult() { @Override public void onReceived(Map map) { @@ -309,35 +381,37 @@ public void onReceived(Map map) { JSONObject object = new JSONObject(); object.put("offering", offering); - sendJSONObject(object, GET_CURRENT_OFFERING_FOR_PLACEMENT); + callback.onReceived(object.toString()); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); } } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, GET_CURRENT_OFFERING_FOR_PLACEMENT); + callback.onError(getError(errorContainer)); } }); } - public static void syncAttributesAndOfferingsIfNeeded() { + public static void syncAttributesAndOfferingsIfNeeded(Callback callback) { CommonKt.syncAttributesAndOfferingsIfNeeded(new OnResult() { @Override public void onReceived(Map map) { try { JSONObject object = new JSONObject(); object.put("offerings", MappersHelpersKt.convertToJson(map)); - sendJSONObject(object, SYNC_ATTRIBUTES_AND_OFFERINGS_IF_NEEDED); + callback.onReceived(object.toString()); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); } } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, SYNC_ATTRIBUTES_AND_OFFERINGS_IF_NEEDED); + callback.onError(getError(errorContainer)); } }); } @@ -347,13 +421,11 @@ public static void syncAmazonPurchase( String receiptID, String amazonUserID, String isoCurrencyCode, - double price - ) { - Purchases.getSharedInstance().syncAmazonPurchase(productID, receiptID, - amazonUserID, isoCurrencyCode, price); + double price) { + Purchases.getSharedInstance().syncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); } - public static void getAmazonLWAConsentStatus() { + public static void getAmazonLWAConsentStatus(Callback callback) { CommonKt.getAmazonLWAConsentStatus(new OnResultAny() { @Override public void onReceived(Boolean amazonLWAConsentStatus) { @@ -362,13 +434,15 @@ public void onReceived(Boolean amazonLWAConsentStatus) { object.put("amazonLWAConsentStatus", amazonLWAConsentStatus); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); + return; } - sendJSONObject(object, GET_LWA_CONSENT_STATUS); + callback.onReceived(object.toString()); } @Override public void onError(@Nullable ErrorContainer errorContainer) { - sendError(errorContainer, GET_LWA_CONSENT_STATUS); + callback.onError(getError(errorContainer)); } }); } @@ -377,20 +451,6 @@ public static void setLogLevel(String level) { CommonKt.setLogLevel(level); } - public static void setLogHandler() { - CommonKt.setLogHandlerWithOnResult(new OnResult() { - @Override - public void onReceived(@NonNull Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), HANDLE_LOG); - } - - @Override - public void onError(@NonNull ErrorContainer errorContainer) { - // Intentionally left blank since it will never be called - } - }); - } - public static void setDebugLogsEnabled(boolean enabled) { CommonKt.setDebugLogsEnabled(enabled); } @@ -403,12 +463,12 @@ public static String getAppUserID() { return CommonKt.getAppUserID(); } - public static void getCustomerInfo() { - CommonKt.getCustomerInfo(getCustomerInfoListener(GET_CUSTOMER_INFO)); + public static void getCustomerInfo(Callback callback) { + CommonKt.getCustomerInfo(getCustomerInfoListener(callback)); } - public static void syncPurchases() { - CommonKt.syncPurchases(getCustomerInfoListener(SYNC_PURCHASES)); + public static void syncPurchases(Callback callback) { + CommonKt.syncPurchases(getCustomerInfoListener(callback)); } public static boolean isAnonymous() { @@ -419,7 +479,7 @@ public static boolean isConfigured() { return Purchases.isConfigured(); } - public static void checkTrialOrIntroductoryPriceEligibility(String jsonProducts) { + public static void checkTrialOrIntroductoryPriceEligibility(Callback callback, String jsonProducts) { try { JSONObject request = new JSONObject(jsonProducts); JSONArray products = request.getJSONArray("productIdentifiers"); @@ -430,9 +490,10 @@ public static void checkTrialOrIntroductoryPriceEligibility(String jsonProducts) } Map> map = CommonKt.checkTrialOrIntroductoryPriceEligibility(productIds); - sendJSONObject(MappersHelpersKt.convertToJson(map), CHECK_ELIGIBILITY); + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); } catch (JSONException e) { Log.e("Purchases", "Failure parsing product identifiers " + jsonProducts); + callback.onError("{\"error\":\"Failure parsing product identifiers " + jsonProducts + "\"}"); } } @@ -529,7 +590,7 @@ public static void collectDeviceIdentifiers() { SubscriberAttributesKt.collectDeviceIdentifiers(); } - public static void canMakePayments(String featuresJson) { + public static void canMakePayments(Callback callback, String featuresJson) { try { JSONObject request = new JSONObject(featuresJson); JSONArray features = request.getJSONArray("features"); @@ -548,22 +609,24 @@ public void onReceived(Boolean canMakePayments) { } catch (JSONException e) { logJSONException(e); } - sendJSONObject(object, CAN_MAKE_PAYMENTS); + callback.onReceived(object.toString()); } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, CAN_MAKE_PAYMENTS); + callback.onError(getError(errorContainer)); } }); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"Failure parsing features " + featuresJson + "\"}"); } } - public static void getPromotionalOffer(String productIdentifier, String discountIdentifier) { - ErrorContainer errorContainer = CommonKt.getPromotionalOffer(); - sendError(errorContainer, GET_PROMOTIONAL_OFFER); + public static void getPromotionalOffer(Callback callback, String productIdentifier, String discountIdentifier) { + // TODO verify NOOP ? + // ErrorContainer errorContainer = CommonKt.getPromotionalOffer(); + // callback.onError(getError(errorContainer)); } public static void showInAppMessages(String messagesJson) { @@ -594,45 +657,45 @@ public static void showInAppMessages(String messagesJson) { } } - public static void parseAsWebPurchaseRedemption(String urlString) { + public static void parseAsWebPurchaseRedemption(Callback callback, String urlString) { boolean isWebPurchaseRedemptionURL = CommonKt.isWebPurchaseRedemptionURL(urlString); + JSONObject object = new JSONObject(); if (isWebPurchaseRedemptionURL) { - JSONObject object = new JSONObject(); try { object.put("redemptionLink", urlString); } catch (JSONException e) { logJSONException(e); + callback.onError("{\"error\":\"" + e.getLocalizedMessage() + "\"}"); + return; } - sendJSONObject(object, PARSE_AS_WEB_PURCHASE_REDEMPTION); - } else { - sendJSONObject(null, PARSE_AS_WEB_PURCHASE_REDEMPTION); } + callback.onReceived(object.toString()); } - public static void redeemWebPurchase(String redemptionLink) { + public static void redeemWebPurchase(Callback callback, String redemptionLink) { CommonKt.redeemWebPurchase(redemptionLink, new OnResult() { @Override public void onReceived(Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), REDEEM_WEB_PURCHASE); + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, REDEEM_WEB_PURCHASE); + callback.onError(getError(errorContainer)); } }); } - public static void getVirtualCurrencies() { + public static void getVirtualCurrencies(Callback callback) { CommonKt.getVirtualCurrencies(new OnResult() { @Override public void onReceived(Map map) { - sendJSONObject(MappersHelpersKt.convertToJson(map), GET_VIRTUAL_CURRENCIES); + callback.onReceived(MappersHelpersKt.convertToJson(map).toString()); } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, GET_VIRTUAL_CURRENCIES); + callback.onError(getError(errorContainer)); } }); } @@ -640,12 +703,11 @@ public void onError(ErrorContainer errorContainer) { @Nullable public static String getCachedVirtualCurrencies() { Map map = CommonKt.getCachedVirtualCurrencies(); - + if (map != null) { - JSONObject cachedVirtualCurrencies = MappersHelpersKt.convertToJson(map); - return cachedVirtualCurrencies.toString(); + return MappersHelpersKt.convertToJson(map).toString(); } - + return null; } @@ -655,145 +717,76 @@ public static void invalidateVirtualCurrenciesCache() { public static void getEligibleWinBackOffersForProduct(String productIdentifier) { // NOOP - PurchasesError error = new PurchasesError( - PurchasesErrorCode.UnsupportedError, - "Win-back offers are not supported on Android."); - - ErrorContainer errorContainer = PurchasesErrorKt.map( - error, - new HashMap<>()); - sendError(errorContainer, GET_ELIGIBLE_WIN_BACK_OFFERS_FOR_PRODUCT); + Log.e("Purchases", "Win-back offers are not supported on Android."); } // This function accepts a product identifier since the PHC code only fetches // eligible win-back offers for products public static void getEligibleWinBackOffersForPackage(String productIdentifier) { // NOOP - PurchasesError error = new PurchasesError( - PurchasesErrorCode.UnsupportedError, - "Win-back offers are not supported on Android."); - - ErrorContainer errorContainer = PurchasesErrorKt.map( - error, - new HashMap<>()); - sendError(errorContainer, GET_ELIGIBLE_WIN_BACK_OFFERS_FOR_PACKAGE); + Log.e("Purchases", "Win-back offers are not supported on Android."); } public static void purchaseProductWithWinBackOffer(String productIdentifier, String winBackOfferIdentifier) { // NOOP - PurchasesError error = new PurchasesError( - PurchasesErrorCode.UnsupportedError, - "Win-back offers are not supported on Android."); - - ErrorContainer errorContainer = PurchasesErrorKt.map(error, new HashMap<>()); - sendError(errorContainer, PURCHASE_PRODUCT_WITH_WIN_BACK_OFFER); + Log.e("Purchases", "Win-back offers are not supported on Android."); } - public static void purchasePackageWithWinBackOffer(String packageIdentifier, String presentedOfferingContextJson, String winBackOfferIdentifier) { + public static void purchasePackageWithWinBackOffer( + String packageIdentifier, + String presentedOfferingContextJson, + String winBackOfferIdentifier) { // NOOP - PurchasesError error = new PurchasesError( - PurchasesErrorCode.UnsupportedError, - "Win-back offers are not supported on Android."); - - ErrorContainer errorContainer = PurchasesErrorKt.map(error, new HashMap<>()); - sendError(errorContainer, PURCHASE_PACKAGE_WITH_WIN_BACK_OFFER); + Log.e("Purchases", "Win-back offers are not supported on Android."); } private static void logJSONException(JSONException e) { Log.e("Purchases", "JSON Error: " + e.getLocalizedMessage()); } - static void sendEmptyJSONObject(String method) { - UnityPlayer.UnitySendMessage(gameObject, method, "{}"); - } - - static void sendJSONObject(JSONObject object, String method) { - UnityPlayer.UnitySendMessage(gameObject, method, object.toString()); - } - - private static void sendError(ErrorContainer error, String method) { + private static String getError(ErrorContainer error) { JSONObject jsonObject = new JSONObject(); try { jsonObject.put("error", MappersHelpersKt.convertToJson(error.getInfo())); } catch (JSONException e) { logJSONException(e); + return "{\"error\":\"" + e.getLocalizedMessage() + "\"}"; } - sendJSONObject(jsonObject, method); - } - - private static void sendCustomerInfo(Map map, String method) { - JSONObject jsonObject = new JSONObject(); - try { - jsonObject.put("customerInfo", MappersHelpersKt.convertToJson(map)); - } catch (JSONException e) { - logJSONException(e); - } - sendJSONObject(jsonObject, method); - } - - private static void sendErrorPurchase(ErrorContainer errorContainer) { - JSONObject jsonObject = new JSONObject(); - try { - jsonObject.put("error", MappersHelpersKt.convertToJson(errorContainer.getInfo())); - jsonObject.put("userCancelled", errorContainer.getInfo().get("userCancelled")); - } catch (JSONException e) { - logJSONException(e); - } - sendJSONObject(jsonObject, MAKE_PURCHASE); - } - - @NonNull - private static OnResult getLogInListener(final String method) { - return new OnResult() { - @Override - public void onReceived(Map map) { - JSONObject jsonObject = new JSONObject(); - try { - Map customerInfoMap = (Map) map.get("customerInfo"); - jsonObject.put("customerInfo", MappersHelpersKt.convertToJson(customerInfoMap)); - jsonObject.put("created", (Boolean) map.get("created")); - } catch (ClassCastException castException) { - Log.e("Purchases", "invalid casting Error: " + castException.getLocalizedMessage()); - } catch (JSONException e) { - logJSONException(e); - } - sendJSONObject(jsonObject, method); - } - - @Override - public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, method); - } - }; + return jsonObject.toString(); } @NonNull - private static OnResult getCustomerInfoListener(final String method) { + private static OnResult getCustomerInfoListener(Callback callback) { return new OnResult() { @Override public void onReceived(Map map) { - sendCustomerInfo(map, method); + sendCustomerInfo(map, callback); } @Override public void onError(ErrorContainer errorContainer) { - sendError(errorContainer, method); + Log.e("Purchases", "Error fetching customer info: " + errorContainer.getInfo().toString()); + if (callback != null) { + callback.onError(getError(errorContainer)); + } } }; } - @Nullable - private static DangerousSettings getDangerousSettingsFromJSON(String dangerousSettingsJSON) { - JSONObject jsonObject; - DangerousSettings dangerousSettings = null; + private static void sendCustomerInfo(Map map, @Nullable Callback callback) { + JSONObject jsonObject = new JSONObject(); try { - jsonObject = new JSONObject(dangerousSettingsJSON); - boolean autoSyncPurchases = jsonObject.getBoolean("AutoSyncPurchases"); - dangerousSettings = new DangerousSettings(autoSyncPurchases); + jsonObject.put("customerInfo", MappersHelpersKt.convertToJson(map)); } catch (JSONException e) { - Log.e("Purchases", "Error parsing dangerousSettings JSON: " + dangerousSettingsJSON); logJSONException(e); } - return dangerousSettings; + + if (PurchasesWrapper.customerInfoHandler != null) { + PurchasesWrapper.customerInfoHandler.onReceived(jsonObject.toString()); + } + + if (callback != null) { + callback.onReceived(jsonObject.toString()); + } } } diff --git a/RevenueCat/Scripts/revenuecat.purchases-unity.asmdef b/RevenueCat/RevenueCat.asmdef similarity index 81% rename from RevenueCat/Scripts/revenuecat.purchases-unity.asmdef rename to RevenueCat/RevenueCat.asmdef index ffa41536..291d3ca3 100644 --- a/RevenueCat/Scripts/revenuecat.purchases-unity.asmdef +++ b/RevenueCat/RevenueCat.asmdef @@ -1,6 +1,6 @@ { - "name": "revenuecat.purchases-unity", - "rootNamespace": "", + "name": "RevenueCat", + "rootNamespace": "RevenueCat", "references": [], "includePlatforms": [], "excludePlatforms": [], diff --git a/RevenueCat/Scripts/revenuecat.purchases-unity.asmdef.meta b/RevenueCat/RevenueCat.asmdef.meta similarity index 100% rename from RevenueCat/Scripts/revenuecat.purchases-unity.asmdef.meta rename to RevenueCat/RevenueCat.asmdef.meta diff --git a/RevenueCat/Scripts/AttributionNetwork.cs b/RevenueCat/Scripts/AttributionNetwork.cs index 53ab4485..46d99c9d 100644 --- a/RevenueCat/Scripts/AttributionNetwork.cs +++ b/RevenueCat/Scripts/AttributionNetwork.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -public partial class Purchases +namespace RevenueCat { [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "UnusedMember.Global")] diff --git a/RevenueCat/Scripts/BillingFeature.cs b/RevenueCat/Scripts/BillingFeature.cs index c81c903a..6d3f7430 100644 --- a/RevenueCat/Scripts/BillingFeature.cs +++ b/RevenueCat/Scripts/BillingFeature.cs @@ -1,4 +1,4 @@ -public partial class Purchases +namespace RevenueCat { /// /// Enum for billing features. @@ -11,22 +11,18 @@ public enum BillingFeature /// Purchase/query for subscriptions /// Subscriptions = 0, - /// /// Subscriptions update/replace /// SubscriptionsUpdate = 1, - /// /// Purchase/query for in-app items on VR /// InAppItemsOnVR = 2, - /// /// Purchase/query for subscriptions on VR /// SubscriptionsOnVR = 3, - /// /// Launch a price change confirmation flow /// diff --git a/RevenueCat/Scripts/CustomerInfo.cs b/RevenueCat/Scripts/CustomerInfo.cs index 437738fc..bbf626c8 100644 --- a/RevenueCat/Scripts/CustomerInfo.cs +++ b/RevenueCat/Scripts/CustomerInfo.cs @@ -1,109 +1,136 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using JetBrains.Annotations; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; -public partial class Purchases +namespace RevenueCat { - /// /// /// CustomerInfo encapsulates the current status of subscriber. /// Use it to determine which entitlements to unlock, typically by checking /// ActiveSubscriptions or via LatestExpirationDate. /// - /// /// /// All DateTimes are in UTC, be sure to compare them with DateTime.UtcNow /// - /// - [SuppressMessage("ReSharper", "UnusedMember.Global")] - public class CustomerInfo + public sealed class CustomerInfo { - public EntitlementInfos Entitlements; - public List ActiveSubscriptions; - public List AllPurchasedProductIdentifiers; - public DateTime? LatestExpirationDate; - public DateTime FirstSeen; - public string OriginalAppUserId; - public DateTime RequestDate; - public DateTime? OriginalPurchaseDate; - public Dictionary AllExpirationDates; - public Dictionary AllPurchaseDates; - [CanBeNull] public string OriginalApplicationVersion; - [CanBeNull] public string ManagementURL; - public List NonSubscriptionTransactions; - public Dictionary SubscriptionsByProductIdentifier; - - public CustomerInfo(JSONNode response) + [JsonProperty("entitlements")] + public EntitlementInfos Entitlements { get; } + + [JsonProperty("activeSubscriptions")] + public IReadOnlyList ActiveSubscriptions { get; } + + [JsonProperty("allPurchasedProductIdentifiers")] + public IReadOnlyList AllPurchasedProductIdentifiers { get; } + + [JsonProperty("latestExpirationDateMillis")] + public long? LatestExpirationDateMillis { get; } + + [JsonIgnore] + public DateTime? LatestExpirationDate + => FromOptionalUnixTimeInMilliseconds(LatestExpirationDateMillis); + + [JsonProperty("firstSeenMillis")] + public long FirstSeenMillis { get; } + + [JsonIgnore] + public DateTime FirstSeen + => FromUnixTimeInMilliseconds(FirstSeenMillis); + + [JsonProperty("originalAppUserId")] + public string OriginalAppUserId { get; } + + [JsonProperty("requestDateMillis")] + public long RequestDateMillis { get; } + + [JsonIgnore] + public DateTime RequestDate + => FromUnixTimeInMilliseconds(RequestDateMillis); + + [JsonProperty("originalPurchaseDateMillis")] + public long? OriginalPurchaseDateMillis { get; } + + [JsonIgnore] + public DateTime? OriginalPurchaseDate + => FromOptionalUnixTimeInMilliseconds(OriginalPurchaseDateMillis); + + [JsonProperty("allExpirationDatesMillis")] + public IReadOnlyDictionary AllExpirationDatesMillis { get; } + + [JsonIgnore] + public IReadOnlyDictionary AllExpirationDates { get; } + + [JsonProperty("allPurchaseDatesMillis")] + public IReadOnlyDictionary AllPurchaseDatesMillis { get; } + + [JsonIgnore] + public IReadOnlyDictionary AllPurchaseDates { get; } + + [CanBeNull] + [JsonProperty("originalApplicationVersion")] + public string OriginalApplicationVersion { get; } + + [CanBeNull] + [JsonProperty("managementURL")] + public string ManagementURL { get; } + + [JsonProperty("nonSubscriptionTransactions")] + public IReadOnlyList NonSubscriptionTransactions { get; } + + [JsonProperty("subscriptionsByProductIdentifier")] + public IReadOnlyDictionary SubscriptionsByProductIdentifier { get; } + + [JsonConstructor] + internal CustomerInfo( + [JsonProperty("entitlements")] EntitlementInfos entitlements, + [JsonProperty("activeSubscriptions")] List activeSubscriptions, + [JsonProperty("allPurchasedProductIdentifiers")] List allPurchasedProductIdentifiers, + [JsonProperty("latestExpirationDateMillis")] long? latestExpirationDateMillis, + [JsonProperty("firstSeenMillis")] long firstSeenMillis, + [JsonProperty("originalAppUserId")] string originalAppUserId, + [JsonProperty("requestDateMillis")] long requestDateMillis, + [JsonProperty("originalPurchaseDateMillis")] long? originalPurchaseDateMillis, + [JsonProperty("allExpirationDatesMillis")] Dictionary allExpirationDatesMillis, + [JsonProperty("allPurchaseDatesMillis")] Dictionary allPurchaseDatesMillis, + [JsonProperty("originalApplicationVersion")] string originalApplicationVersion, + [JsonProperty("managementURL")] string managementURL, + [JsonProperty("nonSubscriptionTransactions")] List nonSubscriptionTransactions, + [JsonProperty("subscriptionsByProductIdentifier")] Dictionary subscriptionsByProductIdentifier) { - Entitlements = new EntitlementInfos(response["entitlements"]); - ActiveSubscriptions = new List(); - foreach (JSONNode subscription in response["activeSubscriptions"]) - { - ActiveSubscriptions.Add(subscription); - } + Entitlements = entitlements; + ActiveSubscriptions = activeSubscriptions; + AllPurchasedProductIdentifiers = allPurchasedProductIdentifiers; + LatestExpirationDateMillis = latestExpirationDateMillis; + FirstSeenMillis = firstSeenMillis; + OriginalAppUserId = originalAppUserId; + RequestDateMillis = requestDateMillis; + OriginalPurchaseDateMillis = originalPurchaseDateMillis; + AllExpirationDatesMillis = allExpirationDatesMillis; - AllPurchasedProductIdentifiers = new List(); - foreach (JSONNode productIdentifier in response["allPurchasedProductIdentifiers"]) - { - AllPurchasedProductIdentifiers.Add(productIdentifier); - } + var allExpirationDates = new Dictionary(); - FirstSeen = FromUnixTimeInMilliseconds(response["firstSeenMillis"].AsLong); - OriginalAppUserId = response["originalAppUserId"]; - RequestDate = FromUnixTimeInMilliseconds(response["requestDateMillis"].AsLong); - OriginalPurchaseDate = - FromOptionalUnixTimeInMilliseconds(response["originalPurchaseDateMillis"].AsLong); - LatestExpirationDate = - FromOptionalUnixTimeInMilliseconds(response["latestExpirationDateMillis"].AsLong); - ManagementURL = response["managementURL"]; - AllExpirationDates = new Dictionary(); - foreach (var keyValue in response["allExpirationDatesMillis"]) + foreach (var (productId, value) in allExpirationDatesMillis) { - var productID = keyValue.Key; - var expirationDateJSON = keyValue.Value; - if (expirationDateJSON != null && !expirationDateJSON.IsNull && expirationDateJSON.AsLong != 0L) - { - AllExpirationDates.Add(productID, FromUnixTimeInMilliseconds(expirationDateJSON.AsLong)); - } - else - { - AllExpirationDates.Add(productID, null); - } + allExpirationDates.Add(productId, FromOptionalUnixTimeInMilliseconds(value)); } - AllPurchaseDates = new Dictionary(); - foreach (var keyValue in response["allPurchaseDatesMillis"]) - { - var productID = keyValue.Key; - var purchaseDateJSON = keyValue.Value; - if (purchaseDateJSON != null && !purchaseDateJSON.IsNull && purchaseDateJSON.AsLong != 0L) - { - AllPurchaseDates.Add(productID, FromUnixTimeInMilliseconds(purchaseDateJSON.AsLong)); - } - else - { - AllPurchaseDates.Add(productID, null); - } - } + AllExpirationDates = allExpirationDates; - OriginalApplicationVersion = response["originalApplicationVersion"]; - - NonSubscriptionTransactions = new List(); - foreach (JSONNode transactionResponse in response["nonSubscriptionTransactions"]) - { - NonSubscriptionTransactions.Add(new StoreTransaction(transactionResponse)); - } + var allPurchaseDates = new Dictionary(); + AllPurchaseDatesMillis = allPurchaseDatesMillis; - SubscriptionsByProductIdentifier = new Dictionary(); - foreach (var keyValue in response["subscriptionsByProductIdentifier"]) + foreach (var (productId, value) in allPurchaseDatesMillis) { - var productID = keyValue.Key; - var subscriptionInfoJSON = keyValue.Value; - SubscriptionsByProductIdentifier.Add(productID, new SubscriptionInfo(subscriptionInfoJSON)); + allExpirationDates.Add(productId, FromOptionalUnixTimeInMilliseconds(value)); } + + AllExpirationDates = allPurchaseDates; + OriginalApplicationVersion = originalApplicationVersion; + ManagementURL = managementURL; + NonSubscriptionTransactions = nonSubscriptionTransactions; + SubscriptionsByProductIdentifier = subscriptionsByProductIdentifier; } public override string ToString() diff --git a/RevenueCat/Scripts/DangerousSettings.cs b/RevenueCat/Scripts/DangerousSettings.cs index e588b366..0e10047e 100644 --- a/RevenueCat/Scripts/DangerousSettings.cs +++ b/RevenueCat/Scripts/DangerousSettings.cs @@ -1,28 +1,21 @@ -using System; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// Advanced settings. Use only after contacting RevenueCat support and making sure you understand them. /// - [Serializable] - public class DangerousSettings + public sealed class DangerousSettings { - public readonly bool AutoSyncPurchases; + [JsonProperty] + public bool AutoSyncPurchases { get; } - public DangerousSettings(bool autoSyncPurchases) + [JsonConstructor] + public DangerousSettings(bool autoSyncPurchases = true) { AutoSyncPurchases = autoSyncPurchases; } - public JSONNode Serialize() - { - var n = new JSONObject(); - n["AutoSyncPurchases"] = AutoSyncPurchases; - return n; - } - public override string ToString() { return $"{nameof(AutoSyncPurchases)}: {AutoSyncPurchases}"; diff --git a/RevenueCat/Scripts/Discount.cs b/RevenueCat/Scripts/Discount.cs index b507783e..a16cf70c 100644 --- a/RevenueCat/Scripts/Discount.cs +++ b/RevenueCat/Scripts/Discount.cs @@ -1,57 +1,65 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// iOS only. Type that wraps StoreKit.Product.SubscriptionOffer and SKProductDiscount and provides access to their /// properties. Information about a subscription offer that you configured in App Store Connect. /// - public class Discount + public sealed class Discount { /// /// Identifier of the discount. /// - public readonly string Identifier; + public string Identifier { get; } /// /// Price in the local currency. /// - public readonly float Price; + public float Price { get; } /// /// Formatted price, including its currency sign, such as €3.99. /// - public readonly string PriceString; + public string PriceString { get; } /// /// Number of subscription billing periods for which the user will be given the discount, such as 3. /// - public readonly int Cycles; + public int Cycles { get; } /// /// Billing period of the discount, specified in ISO 8601 format. /// - public readonly string Period; + public string Period { get; } /// /// Unit for the billing period of the discount, can be DAY, WEEK, MONTH or YEAR. /// - public readonly string PeriodUnit; + public string PeriodUnit { get; } /// /// Number of units for the billing period of the discount. /// - public readonly int PeriodNumberOfUnits; + public int PeriodNumberOfUnits { get; } - public Discount(JSONNode response) + [JsonConstructor] + internal Discount( + [JsonProperty("identifier")] string identifier, + [JsonProperty("price")] float price, + [JsonProperty("priceString")] string priceString, + [JsonProperty("cycles")] int cycles, + [JsonProperty("period")] string period, + [JsonProperty("periodUnit")] string periodUnit, + [JsonProperty("periodNumberOfUnits")] int periodNumberOfUnits) { - Identifier = response["identifier"]; - Price = response["price"]; - PriceString = response["priceString"]; - Cycles = response["cycles"]; - Period = response["period"]; - PeriodUnit = response["periodUnit"]; - PeriodNumberOfUnits = response["periodNumberOfUnits"]; + Identifier = identifier; + Price = price; + PriceString = priceString; + Cycles = cycles; + Period = period; + PeriodUnit = periodUnit; + PeriodNumberOfUnits = periodNumberOfUnits; } public override string ToString() diff --git a/RevenueCat/Scripts/EntitlementInfo.cs b/RevenueCat/Scripts/EntitlementInfo.cs index a902469e..78e3c509 100644 --- a/RevenueCat/Scripts/EntitlementInfo.cs +++ b/RevenueCat/Scripts/EntitlementInfo.cs @@ -1,68 +1,69 @@ +using Newtonsoft.Json; using System; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; - -public partial class Purchases +namespace RevenueCat { - /// /// The EntitlementInfo object gives you access to all of the information about the status of a user entitlement. /// - public class EntitlementInfo + public sealed class EntitlementInfo { - public readonly string Identifier; - public readonly bool IsActive; - public readonly bool WillRenew; - public readonly string PeriodType; - public readonly DateTime LatestPurchaseDate; - public readonly DateTime OriginalPurchaseDate; - public readonly DateTime? ExpirationDate; - public readonly string Store; - public readonly string ProductIdentifier; - public readonly bool IsSandbox; - public readonly DateTime? UnsubscribeDetectedAt; - public readonly DateTime? BillingIssueDetectedAt; - public readonly VerificationResult Verification; + public string Identifier { get; } - public EntitlementInfo(JSONNode response) - { - Identifier = response["identifier"]; - IsActive = response["isActive"].AsBool; - WillRenew = response["willRenew"].AsBool; - PeriodType = response["periodType"]; - LatestPurchaseDate = FromUnixTimeInMilliseconds(response["latestPurchaseDateMillis"].AsLong); - OriginalPurchaseDate = FromUnixTimeInMilliseconds(response["originalPurchaseDateMillis"].AsLong); + public bool IsActive { get; } + + public bool WillRenew { get; } + + public string PeriodType { get; } + + public DateTime LatestPurchaseDate { get; } - var expirationDateJson = response["expirationDateMillis"]; - var hasExpirationDate = expirationDateJson != null && !expirationDateJson.IsNull && - expirationDateJson.AsLong != 0L; - if (hasExpirationDate) - { - ExpirationDate = FromUnixTimeInMilliseconds(expirationDateJson.AsLong); - } + public DateTime OriginalPurchaseDate { get; } - Store = response["store"]; - ProductIdentifier = response["productIdentifier"]; - IsSandbox = response["isSandbox"].AsBool; + public DateTime? ExpirationDate { get; } - var unsubscribeDetectedJson = response["unsubscribeDetectedAtMillis"]; - var hasUnsubscribeDetected = unsubscribeDetectedJson != null && !unsubscribeDetectedJson.IsNull && - unsubscribeDetectedJson.AsLong != 0L; - if (hasUnsubscribeDetected) - { - UnsubscribeDetectedAt = FromUnixTimeInMilliseconds(unsubscribeDetectedJson.AsLong); - } + public string Store { get; } - var billingIssueJson = response["billingIssueDetectedAtMillis"]; - var hasBillingIssue = billingIssueJson != null && !billingIssueJson.IsNull && - billingIssueJson.AsLong != 0L; - if (hasBillingIssue) - { - BillingIssueDetectedAt = FromUnixTimeInMilliseconds(billingIssueJson.AsLong); - } + public string ProductIdentifier { get; } - Verification = VerificationResultExtensions.ParseVerificationResultByName(response["verification"]); + public bool IsSandbox { get; } + + public DateTime? UnsubscribeDetectedAt { get; } + + public DateTime? BillingIssueDetectedAt { get; } + + public VerificationResult Verification { get; } + + [JsonConstructor] + public EntitlementInfo( + [JsonProperty("identifier")] string identifier, + [JsonProperty("isActive")] bool isActive, + [JsonProperty("willRenew")] bool willRenew, + [JsonProperty("periodType")] string periodType, + [JsonProperty("latestPurchaseDateMillis")] long latestPurchaseDateMillis, + [JsonProperty("originalPurchaseDateMillis")] long originalPurchaseDateMillis, + [JsonProperty("expirationDateMillis")] long? expirationDateMillis, + [JsonProperty("store")] string store, + [JsonProperty("productIdentifier")] string productIdentifier, + [JsonProperty("isSandbox")] bool isSandbox, + [JsonProperty("unsubscribeDetectedAtMillis")] long? unsubscribeDetectedAtMillis, + [JsonProperty("billingIssueDetectedAtMillis")] long? billingIssueDetectedAtMillis, + [JsonProperty("verification")] VerificationResult verification) + { + Identifier = identifier; + IsActive = isActive; + WillRenew = willRenew; + PeriodType = periodType; + LatestPurchaseDate = FromUnixTimeInMilliseconds(latestPurchaseDateMillis); + OriginalPurchaseDate = FromUnixTimeInMilliseconds(originalPurchaseDateMillis); + ExpirationDate = FromOptionalUnixTimeInMilliseconds(expirationDateMillis); + Store = store; + ProductIdentifier = productIdentifier; + IsSandbox = isSandbox; + UnsubscribeDetectedAt = FromOptionalUnixTimeInMilliseconds(unsubscribeDetectedAtMillis); + BillingIssueDetectedAt = FromOptionalUnixTimeInMilliseconds(billingIssueDetectedAtMillis); + Verification = verification; } public override string ToString() diff --git a/RevenueCat/Scripts/EntitlementInfos.cs b/RevenueCat/Scripts/EntitlementInfos.cs index c2ff6b9b..34360e4d 100644 --- a/RevenueCat/Scripts/EntitlementInfos.cs +++ b/RevenueCat/Scripts/EntitlementInfos.cs @@ -1,33 +1,32 @@ +using Newtonsoft.Json; using System.Collections.Generic; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; -public partial class Purchases +namespace RevenueCat { /// /// This class contains all the entitlements associated to the user. /// - public class EntitlementInfos + public sealed class EntitlementInfos { - public readonly Dictionary All; - public readonly Dictionary Active; - public readonly VerificationResult Verification; + [JsonProperty("all")] + public IReadOnlyDictionary All { get; } - public EntitlementInfos(JSONNode response) - { - All = new Dictionary(); - foreach (var keyValuePair in response["all"]) - { - All.Add(keyValuePair.Key, new EntitlementInfo(keyValuePair.Value)); - } + [JsonProperty("active")] + public IReadOnlyDictionary Active { get; } - Active = new Dictionary(); - foreach (var keyValuePair in response["active"]) - { - Active.Add(keyValuePair.Key, new EntitlementInfo(keyValuePair.Value)); - } + [JsonProperty("verification")] + public VerificationResult Verification { get; } - Verification = VerificationResultExtensions.ParseVerificationResultByName(response["verification"]); + [JsonConstructor] + internal EntitlementInfos( + [JsonProperty("all")] Dictionary all, + [JsonProperty("active")] Dictionary active, + [JsonProperty("verification")] VerificationResult verification) + { + All = all; + Active = active; + Verification = verification; } public override string ToString() diff --git a/RevenueCat/Scripts/EntitlementVerificationMode.cs b/RevenueCat/Scripts/EntitlementVerificationMode.cs index 205de4cb..86f8199e 100644 --- a/RevenueCat/Scripts/EntitlementVerificationMode.cs +++ b/RevenueCat/Scripts/EntitlementVerificationMode.cs @@ -1,7 +1,4 @@ -using System; -using System.ComponentModel; - -public partial class Purchases +namespace RevenueCat { /// /// Enum of entitlement verification modes. @@ -11,9 +8,7 @@ public enum EntitlementVerificationMode /// /// The SDK will not perform any entitlement verification. /// - [Description("DISABLED")] - Disabled, - + DISABLED, /// /// Enable entitlement verification. /// @@ -24,22 +19,8 @@ public enum EntitlementVerificationMode /// This can be useful if you want to handle verification failures to display an error/warning to the user /// or to track this situation but still grant access. /// - [Description("INFORMATIONAL")] - Informational, - + INFORMATIONAL, // WIP: Add Enforced mode once its ready. - // Enforced - } -} - -internal static class EntitlementVerificationModeExtensions -{ - internal static string Name(this Purchases.EntitlementVerificationMode verificationMode) - { - var type = verificationMode.GetType(); - var memInfo = type.GetMember(verificationMode.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); - var stringValue = ((DescriptionAttribute) attributes[0]).Description; - return stringValue; + // ENFORCED } } diff --git a/RevenueCat/Scripts/Error.cs b/RevenueCat/Scripts/Error.cs index 302ecf8f..b1f6e991 100644 --- a/RevenueCat/Scripts/Error.cs +++ b/RevenueCat/Scripts/Error.cs @@ -1,25 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { - [Serializable] - [SuppressMessage("ReSharper", "NotAccessedField.Global")] public class Error { - public readonly string Message; - public readonly int Code; - public readonly string UnderlyingErrorMessage; - public readonly string ReadableErrorCode; + [JsonProperty("message")] + public string Message; - public Error(JSONNode response) + [JsonProperty("code")] + public int Code; + + [JsonProperty("underlyingErrorMessage")] + public string UnderlyingErrorMessage; + + [JsonProperty("readableErrorCode")] + public string ReadableErrorCode; + + [JsonConstructor] + internal Error( + [JsonProperty("message")] string message, + [JsonProperty("code")] int code, + [JsonProperty("underlyingErrorMessage")] string underlyingErrorMessage, + [JsonProperty("readableErrorCode")] string readableErrorCode) { - Message = response["message"]; - Code = (int) response["code"]; - UnderlyingErrorMessage = response["underlyingErrorMessage"]; - ReadableErrorCode = response["readableErrorCode"]; + Message = message; + Code = code; + UnderlyingErrorMessage = underlyingErrorMessage; + ReadableErrorCode = readableErrorCode; } public override string ToString() diff --git a/RevenueCat/Scripts/GoogleProductChangeInfo.cs b/RevenueCat/Scripts/GoogleProductChangeInfo.cs index 416ff692..ed1e3132 100644 --- a/RevenueCat/Scripts/GoogleProductChangeInfo.cs +++ b/RevenueCat/Scripts/GoogleProductChangeInfo.cs @@ -1,6 +1,4 @@ -using JetBrains.Annotations; - -public partial class Purchases +namespace RevenueCat { /// /// Holds the information used when upgrading from another sku. For Android use only. @@ -10,11 +8,11 @@ public class GoogleProductChangeInfo /** * The old product identifier to change from. */ - public readonly string OldProductIdentifier; + public string OldProductIdentifier; /** * The ProrationMode to use when upgrading the given oldProductIdentifier. */ - public readonly ProrationMode ProrationMode; + public ProrationMode ProrationMode; public GoogleProductChangeInfo(string OldProductIdentifier, ProrationMode ProrationMode) { diff --git a/RevenueCat/Scripts/InAppMessageType.cs b/RevenueCat/Scripts/InAppMessageType.cs index 6a043469..41f3239d 100644 --- a/RevenueCat/Scripts/InAppMessageType.cs +++ b/RevenueCat/Scripts/InAppMessageType.cs @@ -1,23 +1,30 @@ -public partial class Purchases +namespace RevenueCat { + /// /// Enum for in-app message types. /// This can be used if you disable automatic in-app message from showing automatically. /// Then, you can pass what type of messages you want to show in the `showInAppMessages` /// method in Purchases. + /// public enum InAppMessageType { + /// /// In-app messages to indicate there has been a billing issue charging the user. + /// BillingIssue = 0, - + /// /// iOS-only. This message will show if you increase the price of a subscription and /// the user needs to opt-in to the increase. + /// PriceIncreaseConsent = 1, - + /// /// iOS-only. StoreKit generic messages. + /// Generic = 2, - - // iOS-only. This message will show if the subscriber is eligible for an iOS win-back - // offer and will allow the subscriber to redeem the offer. + /// + /// iOS-only. This message will show if the subscriber is eligible for an iOS win-back + /// offer and will allow the subscriber to redeem the offer. + /// WinBackOffer = 3, } } diff --git a/RevenueCat/Scripts/IntroEligibility.cs b/RevenueCat/Scripts/IntroEligibility.cs index 02e8b5d8..ea82c9cb 100644 --- a/RevenueCat/Scripts/IntroEligibility.cs +++ b/RevenueCat/Scripts/IntroEligibility.cs @@ -1,20 +1,26 @@ -using System.Collections.Generic; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { public class IntroEligibility { + /// /// The introductory price eligibility status - public readonly IntroEligibilityStatus Status; + /// + public IntroEligibilityStatus Status { get; } + /// /// Description of the status - public readonly string Description; + /// + public string Description { get; } - public IntroEligibility(JSONNode response) + [JsonConstructor] + internal IntroEligibility( + [JsonProperty("status")] IntroEligibilityStatus status, + [JsonProperty("description")] string description) { - Status = (IntroEligibilityStatus) response["status"].AsInt; - Description = response["description"]; + Status = status; + Description = description; } public override string ToString() diff --git a/RevenueCat/Scripts/IntroEligibilityStatus.cs b/RevenueCat/Scripts/IntroEligibilityStatus.cs index 8bf2873f..d74b3cae 100644 --- a/RevenueCat/Scripts/IntroEligibilityStatus.cs +++ b/RevenueCat/Scripts/IntroEligibilityStatus.cs @@ -1,4 +1,4 @@ -public partial class Purchases +namespace RevenueCat { public enum IntroEligibilityStatus { diff --git a/RevenueCat/Scripts/IntroductoryPrice.cs b/RevenueCat/Scripts/IntroductoryPrice.cs index 65fe7d5d..cf435fc4 100644 --- a/RevenueCat/Scripts/IntroductoryPrice.cs +++ b/RevenueCat/Scripts/IntroductoryPrice.cs @@ -1,24 +1,31 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { - public class IntroductoryPrice + public sealed class IntroductoryPrice { - public float Price; - public string PriceString; - public string Period; - public string Unit; - public int NumberOfUnits; - public int Cycles; + public float Price { get; } + public string PriceString { get; } + public string Period { get; } + public string Unit { get; } + public int NumberOfUnits { get; } + public int Cycles { get; } - public IntroductoryPrice(JSONNode response) + [JsonConstructor] + internal IntroductoryPrice( + [JsonProperty("price")] float price, + [JsonProperty("priceString")] string priceString, + [JsonProperty("period")] string period, + [JsonProperty("unit")] string unit, + [JsonProperty("numberOfUnits")] int numberOfUnits, + [JsonProperty("cycles")] int cycles) { - Price = response["price"]; - PriceString = response["priceString"]; - Period = response["period"]; - Unit = response["periodUnit"]; - NumberOfUnits = response["periodNumberOfUnits"]; - Cycles = response["cycles"]; + Price = price; + PriceString = priceString; + Period = period; + Unit = unit; + NumberOfUnits = numberOfUnits; + Cycles = cycles; } public override string ToString() diff --git a/RevenueCat/Scripts/LogLevel.cs b/RevenueCat/Scripts/LogLevel.cs index 54cdd2b1..7268210e 100644 --- a/RevenueCat/Scripts/LogLevel.cs +++ b/RevenueCat/Scripts/LogLevel.cs @@ -1,52 +1,14 @@ -using System; -using System.ComponentModel; - -public partial class Purchases +namespace RevenueCat { /// /// Enum of available log levels. /// public enum LogLevel { - [Description("VERBOSE")] - Verbose, - [Description("DEBUG")] - Debug, - [Description("INFO")] - Info, - [Description("WARN")] - Warn, - [Description("ERROR")] - Error - } -} - -public static class Extensions -{ - public static string Name(this Purchases.LogLevel level) - { - var type = level.GetType(); - var memInfo = type.GetMember(level.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); - var stringValue = ((DescriptionAttribute) attributes[0]).Description; - return stringValue; - } - - public static Purchases.LogLevel ParseLogLevelByName(string name) - { - foreach (var field in typeof(Purchases.LogLevel).GetFields()) - { - if (Attribute.GetCustomAttribute(field, - typeof(DescriptionAttribute)) is DescriptionAttribute attribute) - { - if (attribute.Description == name) return (Purchases.LogLevel) field.GetValue(null); - } - else - { - if (field.Name == name) return (Purchases.LogLevel) field.GetValue(null); - } - } - - return Purchases.LogLevel.Info; + VERBOSE, + DEBUG, + INFO, + WARN, + ERROR } } diff --git a/RevenueCat/Scripts/LoginResult.cs b/RevenueCat/Scripts/LoginResult.cs new file mode 100644 index 00000000..2df63ab2 --- /dev/null +++ b/RevenueCat/Scripts/LoginResult.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace RevenueCat +{ + public sealed class LoginResult + { + public CustomerInfo CustomerInfo { get; } + + public bool Created { get; } + + [JsonConstructor] + internal LoginResult( + [JsonProperty("customerInfo")] CustomerInfo customerInfo, + [JsonProperty("created")] bool created) + { + CustomerInfo = customerInfo; + Created = created; + } + } +} diff --git a/RevenueCat/Scripts/LoginResult.cs.meta b/RevenueCat/Scripts/LoginResult.cs.meta new file mode 100644 index 00000000..3650335b --- /dev/null +++ b/RevenueCat/Scripts/LoginResult.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c4ffe26453571404f9ba3a240caf3bc6 \ No newline at end of file diff --git a/RevenueCat/Scripts/Offering.cs b/RevenueCat/Scripts/Offering.cs index 4f8a90d1..6d183563 100644 --- a/RevenueCat/Scripts/Offering.cs +++ b/RevenueCat/Scripts/Offering.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; using JetBrains.Annotations; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; +using System.Collections.Generic; using static RevenueCat.Utilities; -public partial class Purchases +namespace RevenueCat { /// /// An offering is a collection of , and they let you control which products @@ -11,71 +11,60 @@ public partial class Purchases /// public class Offering { - public readonly string Identifier; - public readonly string ServerDescription; - public readonly List AvailablePackages; - [CanBeNull] public readonly Dictionary Metadata; - [CanBeNull] public readonly Package Lifetime; - [CanBeNull] public readonly Package Annual; - [CanBeNull] public readonly Package SixMonth; - [CanBeNull] public readonly Package ThreeMonth; - [CanBeNull] public readonly Package TwoMonth; - [CanBeNull] public readonly Package Monthly; - [CanBeNull] public readonly Package Weekly; - - public Offering(JSONNode response) - { - Identifier = response["identifier"]; - ServerDescription = response["serverDescription"]; - AvailablePackages = new List(); - - foreach (JSONNode packageResponse in response["availablePackages"]) - { - AvailablePackages.Add(new Package(packageResponse)); - } + public string Identifier { get; } + + public string ServerDescription { get; } + + public IReadOnlyList AvailablePackages { get; } + + [CanBeNull] + public IReadOnlyDictionary Metadata { get; } - if (response["lifetime"] != null && !response["lifetime"].IsNull) - { - Lifetime = new Package(response["lifetime"]); - } + [CanBeNull] + public Package Lifetime { get; } - if (response["annual"] != null && !response["annual"].IsNull) - { - Annual = new Package(response["annual"]); - } + [CanBeNull] + public Package Annual { get; } - if (response["sixMonth"] != null && !response["sixMonth"].IsNull) - { - SixMonth = new Package(response["sixMonth"]); - } + [CanBeNull] + public Package SixMonth { get; } - if (response["threeMonth"] != null && !response["threeMonth"].IsNull) - { - ThreeMonth = new Package(response["threeMonth"]); - } + [CanBeNull] + public Package ThreeMonth { get; } - if (response["twoMonth"] != null && !response["twoMonth"].IsNull) - { - TwoMonth = new Package(response["twoMonth"]); - } + [CanBeNull] + public Package TwoMonth { get; } + [CanBeNull] + public Package Monthly { get; } - if (response["monthly"] != null && !response["monthly"].IsNull) - { - Monthly = new Package(response["monthly"]); - } - if (response["weekly"] != null && !response["weekly"].IsNull) - { - Weekly = new Package(response["weekly"]); - } - Metadata = new Dictionary(); - if (response["metadata"] != null && !response["metadata"].IsNull) - { - foreach(var metadataEntry in response["metadata"]) - { - object value = ParseJsonValue(metadataEntry.Value); - Metadata.Add(metadataEntry.Key, value); - } - } + [CanBeNull] + public Package Weekly { get; } + + [JsonConstructor] + internal Offering( + [JsonProperty("identifier")] string identifier, + [JsonProperty("serverDescription")] string serverDescription, + [JsonProperty("availablePackages")] List availablePackages, + [JsonProperty("metadata")] Dictionary metadata = null, + [JsonProperty("lifetime")] Package lifetime = null, + [JsonProperty("annual")] Package annual = null, + [JsonProperty("sixMonth")] Package sixMonth = null, + [JsonProperty("threeMonth")] Package threeMonth = null, + [JsonProperty("twoMonth")] Package twoMonth = null, + [JsonProperty("monthly")] Package monthly = null, + [JsonProperty("weekly")] Package weekly = null) + { + Identifier = identifier; + ServerDescription = serverDescription; + AvailablePackages = availablePackages; + Metadata = metadata; + Lifetime = lifetime; + Annual = annual; + SixMonth = sixMonth; + ThreeMonth = threeMonth; + TwoMonth = twoMonth; + Monthly = monthly; + Weekly = weekly; } public override string ToString() @@ -92,34 +81,5 @@ public override string ToString() $"{nameof(Monthly)}: {Monthly}\n" + $"{nameof(Weekly)}: {Weekly}"; } - - private object ParseJsonValue(JSONNode jsonValue) - { - if (jsonValue.IsString) return jsonValue.Value; - if (jsonValue.IsNumber) return jsonValue.AsFloat; - if (jsonValue.IsBoolean) return jsonValue.AsBool; - - if (jsonValue.IsObject) - { - var dict = new Dictionary(); - foreach (var kvp in jsonValue.AsObject) - { - dict[kvp.Key] = ParseJsonValue(kvp.Value); - } - return dict; - } - - if (jsonValue.IsArray) - { - var list = new List(); - foreach (var item in jsonValue.AsArray) - { - list.Add(ParseJsonValue(item)); - } - return list; - } - - return null; - } } } \ No newline at end of file diff --git a/RevenueCat/Scripts/Offerings.cs b/RevenueCat/Scripts/Offerings.cs index 724da431..7bef3966 100644 --- a/RevenueCat/Scripts/Offerings.cs +++ b/RevenueCat/Scripts/Offerings.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; using JetBrains.Annotations; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; +using System.Collections.Generic; using static RevenueCat.Utilities; -public partial class Purchases +namespace RevenueCat { /// /// This class contains all the offerings configured in RevenueCat dashboard. @@ -12,27 +12,23 @@ public partial class Purchases /// public class Offerings { - public readonly Dictionary All; - [CanBeNull] public readonly Offering Current; + public IReadOnlyDictionary All { get; } - public Offerings(JSONNode response) - { - All = new Dictionary(); - foreach (var keyValuePair in response["all"]) - { - All.Add(keyValuePair.Key, new Offering(keyValuePair.Value)); - } + [CanBeNull] + public Offering Current { get; } - var currentJsonNode = response["current"]; - if (currentJsonNode != null && !currentJsonNode.IsNull) - { - Current = new Offering(currentJsonNode); - } + [JsonConstructor] + internal Offerings( + [JsonProperty("all")] Dictionary all, + [JsonProperty("current")] Offering current = null) + { + All = all; + Current = current; } public override string ToString() { - var currentString = Current != null ? $"{nameof(Current)}: {Current}": "current: "; + var currentString = Current != null ? $"{nameof(Current)}: {Current}" : "current: "; return $"{currentString}\n{nameof(All)}: {DictToString(All)}"; } } diff --git a/RevenueCat/Scripts/Package.cs b/RevenueCat/Scripts/Package.cs index c903f9ce..35814c1c 100644 --- a/RevenueCat/Scripts/Package.cs +++ b/RevenueCat/Scripts/Package.cs @@ -1,9 +1,7 @@ +using Newtonsoft.Json; using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using RevenueCat.SimpleJSON; -public partial class Purchases +namespace RevenueCat { /// /// Packages help abstract platform-specific products by grouping equivalent products across iOS, Android, and web. @@ -11,21 +9,25 @@ public partial class Purchases /// public class Package { - public readonly string Identifier; - public readonly string PackageType; - public readonly StoreProduct StoreProduct; - public readonly PresentedOfferingContext PresentedOfferingContext; + public string Identifier { get; } + public string PackageType { get; } + public StoreProduct StoreProduct { get; } + public PresentedOfferingContext PresentedOfferingContext { get; } [Obsolete("Deprecated, use PresentedOfferingContext instead.", false)] - public readonly string OfferingIdentifier; + public string OfferingIdentifier => PresentedOfferingContext?.OfferingIdentifier; - public Package(JSONNode response) + [JsonConstructor] + internal Package( + [JsonProperty("identifier")] string identifier, + [JsonProperty("packageType")] string packageType, + [JsonProperty("product")] StoreProduct storeProduct, + [JsonProperty("presentedOfferingContext")] PresentedOfferingContext presentedOfferingContext) { - Identifier = response["identifier"]; - PackageType = response["packageType"]; - StoreProduct = new StoreProduct(response["product"]); - PresentedOfferingContext = new PresentedOfferingContext(response["presentedOfferingContext"]); - OfferingIdentifier = PresentedOfferingContext.OfferingIdentifier; + Identifier = identifier; + PackageType = packageType; + StoreProduct = storeProduct; + PresentedOfferingContext = presentedOfferingContext; } public override string ToString() diff --git a/RevenueCat/Scripts/PresentedOfferingContext.cs b/RevenueCat/Scripts/PresentedOfferingContext.cs index 578580a9..1d84bf95 100644 --- a/RevenueCat/Scripts/PresentedOfferingContext.cs +++ b/RevenueCat/Scripts/PresentedOfferingContext.cs @@ -1,54 +1,41 @@ -using System.Collections.Generic; using JetBrains.Annotations; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// Stores context about the offering a package was presented from. /// public class PresentedOfferingContext { - public readonly string OfferingIdentifier; - [CanBeNull] public readonly string PlacementIdentifier; - [CanBeNull] public readonly PresentedOfferingTargetingContext TargetingContext; + [JsonProperty("offeringIdentifier")] + public string OfferingIdentifier { get; } - public PresentedOfferingContext(JSONNode response) - { - OfferingIdentifier = response["offeringIdentifier"]; - PlacementIdentifier = response["placementIdentifier"]; + [CanBeNull] + [JsonProperty("placementIdentifier")] + public string PlacementIdentifier { get; } + + [CanBeNull] + [JsonProperty("targetingContext")] + public PresentedOfferingTargetingContext TargetingContext { get; } - var targetingContextNode = response["targetingContext"]; - if (targetingContextNode != null && !targetingContextNode.IsNull) { - TargetingContext = new PresentedOfferingTargetingContext(targetingContextNode); - } + [JsonConstructor] + internal PresentedOfferingContext( + [JsonProperty("offeringIdentifier")] string offeringIdentifier, + [JsonProperty("placementIdentifier")] string placementIdentifier, + [JsonProperty("targetingContext")] PresentedOfferingTargetingContext targetingContext) + { + OfferingIdentifier = offeringIdentifier; + PlacementIdentifier = placementIdentifier; + TargetingContext = targetingContext; } public override string ToString() { return $"{nameof(OfferingIdentifier)}: {OfferingIdentifier}\n" + - $"{nameof(PlacementIdentifier)}: {PlacementIdentifier}\n" + + $"{nameof(PlacementIdentifier)}: {PlacementIdentifier}\n" + $"{nameof(TargetingContext)}: {TargetingContext}"; } - - public string ToJsonString() - { - var contextDict = new JSONObject(); - contextDict["offeringIdentifier"] = OfferingIdentifier; - if (PlacementIdentifier != null) { - contextDict["placementIdentifier"] = PlacementIdentifier; - } - - if (TargetingContext != null) { - var targetingDict = new JSONObject(); - targetingDict["revision"] = TargetingContext.Revision; - targetingDict["ruleId"] = TargetingContext.RuleId; - - contextDict["targetingContext"] = targetingDict; - } - - return contextDict.ToString(); - } } /// @@ -56,13 +43,19 @@ public string ToJsonString() /// public class PresentedOfferingTargetingContext { - public readonly int Revision; - public readonly string RuleId; + [JsonProperty("revision")] + public int Revision { get; } + + [JsonProperty("ruleId")] + public string RuleId { get; } - public PresentedOfferingTargetingContext(JSONNode response) + [JsonConstructor] + internal PresentedOfferingTargetingContext( + [JsonProperty("revision")] int revision, + [JsonProperty("ruleId")] string ruleId) { - Revision = response["revision"]; - RuleId = response["ruleId"]; + Revision = revision; + RuleId = ruleId; } public override string ToString() diff --git a/RevenueCat/Scripts/ProductCategory.cs b/RevenueCat/Scripts/ProductCategory.cs index 3109eb61..03524c17 100644 --- a/RevenueCat/Scripts/ProductCategory.cs +++ b/RevenueCat/Scripts/ProductCategory.cs @@ -1,14 +1,18 @@ -public partial class Purchases +namespace RevenueCat { public enum ProductCategory { + /// /// A type of product for non-subscription. + /// NON_SUBSCRIPTION = 0, - + /// /// A type of product for subscriptions. + /// SUBSCRIPTION = 1, - + /// /// The user is eligible for a free trial or intro pricing for this product. + /// UNKNOWN = 2 } } \ No newline at end of file diff --git a/RevenueCat/Scripts/PromotionalOffer.cs b/RevenueCat/Scripts/PromotionalOffer.cs index c157b08a..4f3f0235 100644 --- a/RevenueCat/Scripts/PromotionalOffer.cs +++ b/RevenueCat/Scripts/PromotionalOffer.cs @@ -1,6 +1,7 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; +using System; -public partial class Purchases +namespace RevenueCat { /// /// Represents a that has been validated and is ready to be used for a purchase. @@ -10,35 +11,47 @@ public class PromotionalOffer /// /// Identifier of the PromotionalOffer. /// - public readonly string Identifier; - + public string Identifier { get; } + /// /// A string that identifies the key used to generate the signature. /// - public readonly string KeyIdentifier; - + public string KeyIdentifier { get; } + /// /// A universally unique ID (UUID) value that you define. /// - public readonly string Nonce; - + public string Nonce { get; } + /// /// A string representing the properties of a specific promotional offer, cryptographically signed. /// - public readonly string Signature; - + public string Signature { get; } + + /// + /// The date and time of the signature's creation, represented as milliseconds since the Unix epoch. + /// + public long TimestampUnixMilliseconds { get; } + /// - /// The date and time of the signature's creation in milliseconds, formatted in Unix epoch time. + /// The date and time of the signature's creation. /// - public readonly long Timestamp; + public DateTime Timestamp + => Utilities.FromUnixTimeInMilliseconds(TimestampUnixMilliseconds); - public PromotionalOffer(JSONNode response) + [JsonConstructor] + internal PromotionalOffer( + [JsonProperty("identifier")] string identifier, + [JsonProperty("keyIdentifier")] string keyIdentifier, + [JsonProperty("nonce")] string nonce, + [JsonProperty("signature")] string signature, + [JsonProperty("timestamp")] long timestamp) { - Identifier = response["identifier"]; - KeyIdentifier = response["keyIdentifier"]; - Nonce = response["nonce"]; - Signature = response["signature"]; - Timestamp = response["timestamp"]; + Identifier = identifier; + KeyIdentifier = keyIdentifier; + Nonce = nonce; + Signature = signature; + TimestampUnixMilliseconds = timestamp; } public override string ToString() diff --git a/RevenueCat/Scripts/ProrationMode.cs b/RevenueCat/Scripts/ProrationMode.cs index 61094f30..8a5c3dc7 100644 --- a/RevenueCat/Scripts/ProrationMode.cs +++ b/RevenueCat/Scripts/ProrationMode.cs @@ -1,4 +1,4 @@ -public partial class Purchases +namespace RevenueCat { public enum ProrationMode { diff --git a/RevenueCat/Scripts/PurchaseResult.cs b/RevenueCat/Scripts/PurchaseResult.cs index 335cd308..76b52fba 100644 --- a/RevenueCat/Scripts/PurchaseResult.cs +++ b/RevenueCat/Scripts/PurchaseResult.cs @@ -1,72 +1,49 @@ -using System; -using RevenueCat.SimpleJSON; -using static RevenueCat.Utilities; +using JetBrains.Annotations; +using Newtonsoft.Json; - -public partial class Purchases +namespace RevenueCat { /// /// Class containing the data of the result of a purchase. /// - public class PurchaseResult + public sealed class PurchaseResult { - /** - * - * The updated object after the successful purchase. - * - */ - public readonly CustomerInfo CustomerInfo; - - /** - * - * The product identifier for which the purchase was attempted. - * - */ - public readonly string ProductIdentifier; - - /** - * - * A boolean that indicates whether the purchase was cancelled by the user. - * - */ - public readonly bool UserCancelled; - - /** - * - * An error, if one occurred. Null if the purchase was successful. - * - */ - public readonly Error Error; - - /** - * - * The object for the purchase that just happened. Null if the purchase was not successful. - * - */ - public readonly StoreTransaction StoreTransaction; - - public PurchaseResult(JSONNode response) + /// + /// The updated object after the successful purchase. + /// + [JsonProperty("customerInfo")] + public CustomerInfo CustomerInfo; + + /// + /// The product identifier for which the purchase was attempted. + /// + [JsonIgnore] + public string ProductIdentifier; + + /// + /// A boolean that indicates whether the purchase was cancelled by the user. + /// + public bool UserCancelled; + + /// + /// An error, if one occurred.Null if the purchase was successful. + /// + [CanBeNull] + public Error Error; + + /// + /// The object for the purchase that just happened.Null if the purchase was not successful. + /// + public StoreTransaction StoreTransaction; + + [JsonConstructor] + internal PurchaseResult(CustomerInfo customerInfo, bool userCancelled, Error error, StoreTransaction storeTransaction) { - if (response["customerInfo"] != null) - { - CustomerInfo = new CustomerInfo(response["customerInfo"]); - } - - if (response["transaction"] != null) - { - StoreTransaction = new StoreTransaction(response["transaction"]); - ProductIdentifier = StoreTransaction.ProductIdentifier; - } - - if (response["userCancelled"] != null) - { - UserCancelled = response["userCancelled"].AsBool; - } - - if (response["error"] != null) - { - Error = new Error(response["error"]); - } + CustomerInfo = customerInfo; + StoreTransaction = storeTransaction; + ProductIdentifier = storeTransaction.ProductIdentifier; + UserCancelled = userCancelled; + Error = error; } public override string ToString() diff --git a/RevenueCat/Scripts/Purchases.cs b/RevenueCat/Scripts/Purchases.cs index 852c3b78..610191fa 100644 --- a/RevenueCat/Scripts/Purchases.cs +++ b/RevenueCat/Scripts/Purchases.cs @@ -1,1882 +1,1701 @@ -using UnityEngine; -using UnityEngine.Serialization; -using System; -using System.Collections.Generic; -using RevenueCat.SimpleJSON; - -#pragma warning disable CS0649 - -public partial class Purchases : MonoBehaviour -{ - - [Tooltip("Activate if you plan to call Purchases.Configure or Purchases.Setup programmatically.")] - public bool useRuntimeSetup; - - [Tooltip("RevenueCat API Key specifically for Apple platforms.\nGet from https://app.revenuecat.com/ \n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public string revenueCatAPIKeyApple; - - [Tooltip("RevenueCat API Key specifically for Google Play.\nGet from https://app.revenuecat.com/ \n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public string revenueCatAPIKeyGoogle; - - [Header("Alternative Stores")] - [Tooltip("RevenueCat API Key specifically for Amazon Appstore.\nGet from https://app.revenuecat.com/ \n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public string revenueCatAPIKeyAmazon; - - [Tooltip("Enables Amazon Store support. Android only, on iOS it has no effect.\n" + - "If enabled, it will use the API key in RevenueCatAPIKeyAmazon.\n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public bool useAmazon; - - [Header("Dangerous Settings")] - [Tooltip("Disable or enable automatically detecting current subscriptions.\n" + - "If this is disabled, RevenueCat won't check current purchases, and it will not sync any purchase automatically " + - "when the app starts.\nCall syncPurchases whenever a new purchase is detected so the receipt is sent to " + - "RevenueCat's backend.\n" + - "In iOS, consumables disappear from the receipt after the transaction is finished, so make sure purchases " + - "are synced before finishing any consumable transaction, otherwise RevenueCat won't register the purchase.\n" + - "Auto syncing of purchases is enabled by default.\n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public bool autoSyncPurchases = true; - - [Tooltip("App user id. Pass in your own ID if your app has accounts.\n" + - "If blank, RevenueCat will generate a user ID for you.\n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - // ReSharper disable once InconsistentNaming - public string appUserID; - - [Tooltip("List of product identifiers.")] - public string[] productIdentifiers; - - [Tooltip("A subclass of Purchases.UpdatedCustomerInfoListener component.\n" + - "Use your custom subclass to define how to handle updated customer information.")] - public UpdatedCustomerInfoListener listener; - - [Tooltip("An optional string. iOS only.\n" + - "Set this to use a specific NSUserDefaults suite for RevenueCat. " + - "This might be handy if you are deleting all NSUserDefaults in your app " + - "and leaving RevenueCat in a bad state.\n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public string userDefaultsSuiteName; - - [Tooltip("Set this to MyApp and provide a StoreKitVersion if you have your own IAP implementation and\n" + - "want to only use RevenueCat's backend. Defaults to PurchasesAreCompletedBy.RevenueCat\n." + - "If you are on Android and setting this to MyApp, you will have to acknowledge the purchases yourself.\n" + - "If your app is only on Android, you may specify any StoreKit version, as it is ignored by the Android SDK.")] - public PurchasesAreCompletedBy purchasesAreCompletedBy = PurchasesAreCompletedBy.RevenueCat; - - [Tooltip("Version of StoreKit to use in iOS. By default, RevenueCat will decide for you.\n" + - "Set this if you're setting PurchasesAreCompletedBy to MyApp.")] - public StoreKitVersion storeKitVersion = StoreKitVersion.Default; - - [Tooltip("Whether we should show store in-app messages automatically. Both Google Play and the App Store provide in-app " + - "messages for some situations like billing issues. By default, those messages will be shown automatically.\n" + - "This allows to disable that behavior, so you can display those messages at your convenience. For more information, " + - "check: https://rev.cat/storekit-message and https://rev.cat/googleplayinappmessaging")] - public bool shouldShowInAppMessagesAutomatically = true; - - [Tooltip("The entitlement verification mode to use. For more information, check: https://rev.cat/trusted-entitlements")] - public EntitlementVerificationMode entitlementVerificationMode = EntitlementVerificationMode.Disabled; - - [Tooltip("Enable this setting if you want to allow pending purchases for prepaid subscriptions (only supported " + - "in Google Play). Note that entitlements are not granted until payment is done. Disabled by default.")] - public bool pendingTransactionsForPrepaidPlansEnabled = false; - - [Header("Advanced")] - [Tooltip("Set this property to your proxy URL before configuring Purchases *only* if you've received " + - "a proxy key value from your RevenueCat contact.\n" + - "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + - "it through PurchasesConfiguration instead")] - public string proxyURL; - - private IPurchasesWrapper _wrapper; - - private void Start() - { -#if UNITY_ANDROID && !UNITY_EDITOR - _wrapper = new PurchasesWrapperAndroid(); -#elif (UNITY_IOS || UNITY_VISIONOS) && !UNITY_EDITOR - _wrapper = new PurchasesWrapperiOS(); -#else - _wrapper = new PurchasesWrapperNoop(); -#endif - if (!string.IsNullOrEmpty(proxyURL)) - { - _wrapper.SetProxyURL(proxyURL); - } - - if (useRuntimeSetup) return; - - Configure(string.IsNullOrEmpty(appUserID) ? null : appUserID); - GetProducts(productIdentifiers, null); - } - - private void Configure(string newUserId) - { - var apiKey = ""; - - if (Application.platform == RuntimePlatform.IPhonePlayer - || Application.platform == RuntimePlatform.OSXPlayer) - apiKey = revenueCatAPIKeyApple; - else if (Application.platform == RuntimePlatform.Android - || IsAndroidEmulator()) - apiKey = useAmazon ? revenueCatAPIKeyAmazon : revenueCatAPIKeyGoogle; - - if (purchasesAreCompletedBy == PurchasesAreCompletedBy.MyApp && storeKitVersion == StoreKitVersion.Default) - { - Debug.Log("You must set a StoreKit version if you are setting PurchasesAreCompletedBy to MyApp. For Android, it doesn't matter which"); - return; - } - - var dangerousSettings = new DangerousSettings(autoSyncPurchases); - var builder = PurchasesConfiguration.Builder.Init(apiKey) - .SetAppUserId(newUserId) - .SetPurchasesAreCompletedBy(purchasesAreCompletedBy, storeKitVersion) - .SetUserDefaultsSuiteName(userDefaultsSuiteName) - .SetUseAmazon(useAmazon) - .SetDangerousSettings(dangerousSettings) - .SetStoreKitVersion(storeKitVersion) - .SetShouldShowInAppMessagesAutomatically(shouldShowInAppMessagesAutomatically) - .SetEntitlementVerificationMode(entitlementVerificationMode); - - Configure(builder.Build()); - } - - [Obsolete("Deprecated, use Configure instead.", false)] - public void Setup(PurchasesConfiguration purchasesConfiguration) - { - Configure(purchasesConfiguration); - } - - // ReSharper disable once MemberCanBePrivate.Global - /// - /// Use this method to configure the SDK programmatically. - /// To use this, you *must* check useRuntimeSetup in the Unity IDE UI. - /// The values used through this setup will override values set through the Unity IDE UI. - /// You should call this method as soon as possible in your app's lifecycle, and before any other calls to the SDK. - /// - /// To configure the SDK programmatically: - /// Create a configuration builder, set its properties, then call `Build` to obtain the configuration. - /// Lastly, call Purchases.Configure and with the obtained PurchasesConfiguration object. - /// - /// - /// - /// You should call this method as early in your app's lifecycle as possible, to make sure that the SDK doesn't - /// miss events that happen in the purchasing queue. - /// - /// - /// - /// For example: - /// - /// Purchases.PurchasesConfiguration.Builder builder = Purchases.PurchasesConfiguration.Builder.Init("api_key"); - /// Purchases.PurchasesConfiguration purchasesConfiguration = - /// builder - /// .SetAppUserId(appUserId) - /// .Build(); - /// purchases.Configure(purchasesConfiguration); - /// - /// - /// - public void Configure(PurchasesConfiguration purchasesConfiguration) - { - var dangerousSettings = purchasesConfiguration.DangerousSettings.Serialize().ToString(); - _wrapper.Setup(gameObject.name, purchasesConfiguration.ApiKey, purchasesConfiguration.AppUserId, - purchasesConfiguration.PurchasesAreCompletedBy, purchasesConfiguration.StoreKitVersion, purchasesConfiguration.UserDefaultsSuiteName, - purchasesConfiguration.UseAmazon, dangerousSettings, purchasesConfiguration.ShouldShowInAppMessagesAutomatically, - purchasesConfiguration.EntitlementVerificationMode, purchasesConfiguration.PendingTransactionsForPrepaidPlansEnabled); - } - - private bool IsAndroidEmulator() - { - try - { - // From https://stackoverflow.com/questions/51880866/detect-if-game-running-in-android-emulator - AndroidJavaClass osBuild = new AndroidJavaClass("android.os.Build"); - string fingerPrint = osBuild.GetStatic("FINGERPRINT"); - return fingerPrint.Contains("generic"); - } - catch - { - // Throws error when running on non-Android platforms - return false; - } - } - - /// - /// Callback type for . - /// Includes the info of the current store account. - /// - public delegate void GetStorefrontFunc(Storefront? storefront); - - private GetStorefrontFunc StorefrontCallback { get; set; } - - /// - /// Fetches the Storefront for the customer's current store account. - /// If there is an error, the callback will be called with a null value. - /// - public void GetStorefront(GetStorefrontFunc callback) - { - StorefrontCallback = callback; - _wrapper.GetStorefront(); - } - - /// - /// Callback type for . - /// Includes a list of products or an error. - /// - public delegate void GetProductsFunc(List products, Error error); - - private GetProductsFunc ProductsCallback { get; set; } - - // ReSharper disable once MemberCanBePrivate.Global - /// - /// Fetches the StoreProducts for your IAPs for given productIdentifiers. - /// This method is called automatically with products pre-configured through Unity IDE UI. - /// You can optionally call this if you want to fetch more products. - /// - /// - /// - /// A set of product identifiers for in-app purchases setup via AppStoreConnect.\n - /// This should be either hard coded in your application, from a file, or from a custom endpoint if \n - /// you want to be able to deploy new IAPs without an app update. - /// - /// - /// A callback that is called with the loaded products.\n - /// If the fetch fails for any reason it will return an empty array and an error. - /// - /// Android only. The type of product to purchase. - /// - /// completion may be called without s that you are expecting.\n - /// This is usually caused by iTunesConnect configuration errors.\n - /// Ensure your IAPs have the “Ready to Submit” status in iTunesConnect.\n - /// Also ensure that you have an active developer program subscription and you have signed the\n - /// latest paid application agreements.\n - /// If you’re having trouble, - /// - public void GetProducts(string[] products, GetProductsFunc callback, string type = "subs") - { - ProductsCallback = callback; - _wrapper.GetProducts(products, type); - } - - /// - /// Callback type for methods that make purchases, like ,\n - /// , , \n - /// , , \n - /// and . - /// - /// - /// The object for the purchase attempt that just happened. - public delegate void MakePurchaseFunc(PurchaseResult purchaseResult); - - private MakePurchaseFunc MakePurchaseCallback { get; set; } - - /// - /// - /// Initiates a purchase of a . - /// - /// Use this function if you are not using the system to purchase a . - /// If you are using the system, use instead. - /// - /// - /// Call this method when a user has decided to purchase a product. - /// Only call this in direct response to user input. - /// - /// - /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. - /// - /// - /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will - /// handle this for you. - /// - /// - /// The identifier of the the user intends to purchase. - /// A completion block that is called when the purchase completes. - /// Android only. The type of product to purchase. - /// Android only. Optional. The oldSku to upgrade from. - /// Android only. Optional. The to use when upgrading the given oldSku. - /// Android only. Optional. Indicates - /// personalized pricing on products available for purchase in the EU. - /// For compliance with EU regulations. User will see "This price has been - /// customized for you" in the purchase dialog when true. - /// See https://developer.android.com/google/play/billing/integrate#personalized-price - /// for more info. - /// - public void PurchaseProduct(string productIdentifier, MakePurchaseFunc callback, - string type = "subs", string oldSku = null, - ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false) - { - MakePurchaseCallback = callback; - _wrapper.PurchaseProduct(productIdentifier, type, oldSku, prorationMode, googleIsPersonalizedPrice); - } - - /// - /// - /// iOS only. Initiates a purchase of a with a . - /// You can get a PromotionalOffer by calling . - /// - /// Use this function if you are not using the system to purchase a . - /// If you are using the system, use instead. - /// - /// - /// Call this method when a user has decided to purchase a product. - /// Only call this in direct response to user input. - /// - /// - /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. - /// - /// - /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will - /// handle this for you. - /// - /// - /// The identifier of the the user intends to purchase. - /// A to apply to the purchase. - /// A completion block that is called when the purchase completes. - public void PurchaseDiscountedProduct(string productIdentifier, PromotionalOffer discount, - MakePurchaseFunc callback) - { - MakePurchaseCallback = callback; - _wrapper.PurchaseProduct(productIdentifier, discount: discount); - } - - /// - /// - /// Initiates a purchase of a . - /// - /// - /// - /// Call this method when a user has decided to purchase a product. - /// Only call this in direct response to user input. - /// - /// - /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. - /// - /// - /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will - /// handle this for you. - /// - /// - /// The the user intends to purchase. - /// A completion block that is called when the purchase completes. - /// Android only. Optional. The oldSku to upgrade from. - /// Android only. Optional. The to use when upgrading the given oldSku. - /// Android only. Optional. Indicates - /// personalized pricing on products available for purchase in the EU. - /// For compliance with EU regulations. User will see "This price has been - /// customized for you" in the purchase dialog when true. - /// See https://developer.android.com/google/play/billing/integrate#personalized-price - /// for more info. - /// - public void PurchasePackage(Package package, MakePurchaseFunc callback, string oldSku = null, - ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false) - { - MakePurchaseCallback = callback; - _wrapper.PurchasePackage(package, oldSku, prorationMode, googleIsPersonalizedPrice); - } - - /// - /// - /// iOS only. Initiates a purchase of a . - /// You can get a PromotionalOffer by calling . - /// - /// - /// - /// Call this method when a user has decided to purchase a product. - /// Only call this in direct response to user input. - /// - /// - /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. - /// - /// - /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will - /// handle this for you. - /// - /// - /// The the user intends to purchase. - /// A to apply to the purchase. - /// A completion block that is called when the purchase completes. - /// - public void PurchaseDiscountedPackage(Package package, PromotionalOffer discount, MakePurchaseFunc callback) - { - MakePurchaseCallback = callback; - _wrapper.PurchasePackage(package, discount: discount); - } - - public void PurchaseSubscriptionOption(Purchases.SubscriptionOption subscriptionOption, MakePurchaseFunc callback, - Purchases.GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false) - { - MakePurchaseCallback = callback; - _wrapper.PurchaseSubscriptionOption(subscriptionOption, googleProductChangeInfo, googleIsPersonalizedPrice); - } - - /// - /// Callback type for methods that return . - /// Includes a or an error. - /// - /// A if the request was successful, null otherwise. - /// An error if the request was not successful, null otherwise. - public delegate void CustomerInfoFunc(CustomerInfo customerInfo, Error error); - - private CustomerInfoFunc RestorePurchasesCallback { get; set; } - - /// - /// - /// This method will post all purchases associated with the current Store account to RevenueCat and become - /// associated with the current appUserID. If the receipt is being used by an existing user, the current - /// appUserID will be aliased together with the appUserID of the existing user. - /// Going forward, either appUserID will be able to reference the same user. - /// - /// - /// You shouldn't use this method if you have your own account system. In that case "restoration" is provided - /// by your app passing the same appUserID used to purchase originally. - /// - /// - Note: This may force your users to enter their Store password so should only be performed on request of - /// the user. Typically with a button in settings or near your purchase UI. Use - /// if you need to restore transactions programmatically. - /// - /// - /// - /// A which will contain a - /// if restoration was successful, or an error otherwise. - public void RestorePurchases(CustomerInfoFunc callback) - { - RestorePurchasesCallback = callback; - _wrapper.RestorePurchases(); - } - - [Obsolete("Deprecated, use set methods instead.", true)] - public void AddAttributionData(string dataJson, AttributionNetwork network, string networkUserId = null) { } - - /// - /// Callback function for . - /// - /// The of the user if the request was successful. - /// Null otherwise. - /// True if the user was registered in RevenueCat's database for the first time upon - /// making this call. False if a user with this appUserID already existed in RevenueCat's database for - /// this app. - /// If the request was unsuccessful, contains an error. Null otherwise. - public delegate void LogInFunc(CustomerInfo customerInfo, bool created, Error error); - - private LogInFunc LogInCallback { get; set; } - - /// - /// This function will log in the current user with an appUserID. - /// - /// The appUserID that should be linked to the current user. - /// - /// The callback block will be called with the latest and a bool specifying - /// whether the user was created for the first time in the RevenueCat backend. - /// - /// - /// RevenueCat provides a source of truth for a subscriber's status across different platforms. - /// To do this, each subscriber has an App User ID that uniquely identifies them within your application. - /// User identity is one of the most important components of many mobile applications, - /// and it's extra important to make sure the subscription status RevenueCat is - /// tracking gets associated with the correct user. - /// The Purchases SDK allows you to specify your own user identifiers or use anonymous identifiers - /// generated by RevenueCat. Some apps will use a combination - /// of their own identifiers and RevenueCat anonymous Ids - that's okay! - /// - /// - /// - /// - /// - public void LogIn(string appUserId, LogInFunc callback) - { - LogInCallback = callback; - _wrapper.LogIn(appUserId); - } - - private CustomerInfoFunc LogOutCallback { get; set; } - - - /// - /// - /// Logs out the Purchases client, clearing the saved appUserID. - /// - /// - /// This will generate a random user id and save it in the cache. - /// If this method is called and the current user is anonymous, it will return an error. - /// - /// The callback will contain a CustomerInfo if the logOut - /// operation was successful, or an error otherwise. - /// - /// - /// - /// - /// - /// - public void LogOut(CustomerInfoFunc callback) - { - LogOutCallback = callback; - _wrapper.LogOut(); - } - - // ReSharper disable once UnusedMember.Global - [Obsolete(@"Configure behavior through the RevenueCat dashboard instead. - If you have configured the 'Legacy' restore behavior in the RevenueCat Dashboard - and are currently setting this to true, keep this setting active.")] - public void SetAllowSharingStoreAccount(bool allow) - { - _wrapper.SetAllowSharingStoreAccount(allow); - } - - // ReSharper disable once UnusedMember.Global - /// - /// The appUserID used by Purchases. - /// If not passed on initialization this will be generated and cached by Purchases. - /// - /// The app user ID currently used by Purchases. - public string GetAppUserId() - { - return _wrapper.GetAppUserId(); - } - - // ReSharper disable once UnusedMember.Global - /// - /// Returns true if the appUserID has been generated by RevenueCat, false otherwise. - /// - public bool IsAnonymous() - { - return _wrapper.IsAnonymous(); - } - - // ReSharper disable once UnusedMember.Global - /// - /// Returns true if configure has been called and [Purchases.sharedInstance] is set. - /// - public bool IsConfigured() - { - return _wrapper.IsConfigured(); - } - - // ReSharper disable once UnusedMember.Global - /// - /// Enable debug logging. Useful for debugging issues with the lovely team @RevenueCat. - /// - /// - /// Whether debug logs should be enabled. - [Obsolete("Deprecated, use logLevel instead.")] - public void SetDebugLogsEnabled(bool logsEnabled) - { - _wrapper.SetDebugLogsEnabled(logsEnabled); - } - - // ReSharper disable once UnusedMember.Global - /// - /// Configure log level. Useful for debugging issues with the lovely team @RevenueCat. - /// - public void SetLogLevel(LogLevel level) - { - _wrapper.SetLogLevel(level); - } - - /// - /// Callback type for SetLogHandler. - /// - /// Log's . - /// The log's message. - public delegate void LogHandlerFunc(LogLevel logLevel, string message); - - private LogHandlerFunc LogHandler { get; set; } - - // ReSharper disable once UnusedMember.Global - /// - /// Set a custom log handler for redirecting logs to your own logging system. - /// By default, this sends info, warning, and error messages. - /// If you wish to receive Debug level messages, see . - /// - /// It will get called for each log event. Use this function to redirect the log - /// to your own logging system. Configure your own log handler. Useful for debugging issues - /// with the lovely team @RevenueCat. - public void SetLogHandler(LogHandlerFunc logHandler) - { - LogHandler = logHandler; - _wrapper.SetLogHandler(); - } - - private CustomerInfoFunc GetCustomerInfoCallback { get; set; } - - // ReSharper disable once UnusedMember.Global - /// - /// Get latest available . - /// - /// - /// A completion block called when customer info is available and not stale. - /// Called immediately if is cached. Customer info can be nil if an error occurred. - /// - /// - public void GetCustomerInfo(CustomerInfoFunc callback) - { - GetCustomerInfoCallback = callback; - _wrapper.GetCustomerInfo(); - } - - /// - /// Callback for . - /// - /// The object if the request was successful, null otherwise. - /// The error if the request was unsuccessful, null otherwise. - public delegate void GetOfferingsFunc(Offerings offerings, Error error); - - private GetOfferingsFunc GetOfferingsCallback { get; set; } - - /// - /// Callback for . - /// - /// The nullable object if the request was successful, null otherwise. - /// The error if the request was unsuccessful, null otherwise. - public delegate void GetCurrentOfferingForPlacementFunc(Offering offerings, Error error); - - private GetCurrentOfferingForPlacementFunc GetCurrentOfferingForPlacementCallback { get; set; } - - /// - /// Callback for . - /// - /// The object if the request was successful, null otherwise. - /// The error if the request was unsuccessful, null otherwise. - public delegate void SyncAttributesAndOfferingsIfNeededFunc(Offerings offerings, Error error); - - private SyncAttributesAndOfferingsIfNeededFunc SyncAttributesAndOfferingsIfNeededCallback { get; set; } - - /// - /// - /// Fetch the configured for this user. - /// - /// - /// allows you to configure your in-app products - /// via RevenueCat and greatly simplifies management. - /// - /// will be fetched and cached on instantiation so that, by the time they are needed, - /// your prices are loaded for your purchase flow. Time is money. - /// - /// A completion block called when offerings are available. - /// Called immediately if offerings are cached. will be null if an error occurred. - /// - /// - /// - /// - public void GetOfferings(GetOfferingsFunc callback) - { - GetOfferingsCallback = callback; - _wrapper.GetOfferings(); - } - - public void GetCurrentOfferingForPlacement(string placementIdentifier, GetCurrentOfferingForPlacementFunc callback) - { - GetCurrentOfferingForPlacementCallback = callback; - _wrapper.GetCurrentOfferingForPlacement(placementIdentifier); - } - - public void SyncAttributesAndOfferingsIfNeeded(SyncAttributesAndOfferingsIfNeededFunc callback) - { - SyncAttributesAndOfferingsIfNeededCallback = callback; - _wrapper.SyncAttributesAndOfferingsIfNeeded(); - } - - /// - /// This method will post all purchases associated with the current App Store account to RevenueCat and - /// become associated with the current appUserID. - /// - /// - /// If the receipt is being used by an existing user, the current appUserID will be aliased together with - /// the appUserID of the existing user. - /// Going forward, either appUserID will be able to reference the same user. - /// - /// - /// Warning: This function should only be called if you're not calling any purchase method. - /// - /// - /// - /// Note: This method will not trigger a login prompt from App Store. However, if the receipt currently - /// on the device does not contain subscriptions, but the user has made subscription purchases, this method - /// won't be able to restore them. Use to cover those cases. - /// - /// - /// - public void SyncPurchases() - { - _wrapper.SyncPurchases(); - } - - private CustomerInfoFunc SyncPurchasesCallback { get; set; } - - /// - /// This method will post all purchases associated with the current App Store account to RevenueCat and - /// become associated with the current appUserID. - /// - /// - /// If the receipt is being used by an existing user, the current appUserID will be aliased together with - /// the appUserID of the existing user. - /// Going forward, either appUserID will be able to reference the same user. - /// - /// - /// Warning: This function should only be called if you're not calling any purchase method. - /// - /// - /// - /// Note: This method will not trigger a login prompt from App Store. However, if the receipt currently - /// on the device does not contain subscriptions, but the user has made subscription purchases, this method - /// won't be able to restore them. Use to cover those cases. - /// - /// - /// - /// A which will contain a - /// if sync was successful, or an error otherwise. - public void SyncPurchases(CustomerInfoFunc callback) - { - SyncPurchasesCallback = callback; - _wrapper.SyncPurchases(); - } - - /// - /// Android only. Noop in iOS. - /// - /// This method will send a purchase to the RevenueCat backend. This function should only be called if you are - /// in Amazon observer mode or performing a client side migration of your current users to RevenueCat. - /// The receipt IDs are cached if successfully posted so they are not posted more than once. - /// - /// Product ID associated to the purchase. - /// ReceiptId that represents the Amazon purchase. - /// Amazon's userID. - /// Product's currency code in ISO 4217 format. - /// Product's price. - [Obsolete("Deprecated, use SyncAmazonPurchase instead.")] - public void SyncObserverModeAmazonPurchase(string productID, string receiptID, string amazonUserID, - string isoCurrencyCode, double price) - { - _wrapper.SyncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); - } - - /// - /// Android only. Noop in iOS. - /// - /// This method will send a purchase to the RevenueCat backend. This function should only be called if you are - /// in Amazon observer mode or performing a client side migration of your current users to RevenueCat. - /// The receipt IDs are cached if successfully posted so they are not posted more than once. - /// - /// Product ID associated to the purchase. - /// ReceiptId that represents the Amazon purchase. - /// Amazon's userID. - /// Product's currency code in ISO 4217 format. - /// Product's price. - public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, - string isoCurrencyCode, double price) - { - _wrapper.SyncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); - } - - // ReSharper disable once UnusedMember.Global - /// - /// Enable automatic collection of Apple Search Ads attribution using AdServices. Defaults to `false`. - /// - public void EnableAdServicesAttributionTokenCollection() - { - _wrapper.EnableAdServicesAttributionTokenCollection(); - } - - /// - /// iOS only. Callback for the method. - /// - /// A Dictionary mapping product identifiers to their eligibility status, - /// as objects. - public delegate void CheckTrialOrIntroductoryPriceEligibilityFunc(Dictionary products); - - private CheckTrialOrIntroductoryPriceEligibilityFunc CheckTrialOrIntroductoryPriceEligibilityCallback { get; set; } - - /// - /// - /// iOS only. Computes whether or not a user is eligible for the introductory pricing period of a given product. - /// You should use this method to determine whether or not you show the user the normal product price or - /// the introductory price. This also applies to trials (trials are considered a type of introductory pricing). - /// . - /// - /// - /// - /// Note: If you're looking to use Promotional Offers instead, - /// use . - /// - /// - /// - /// Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't - /// definitively compute the eligibility, most likely because of missing group information, it will return - /// . - /// The best course of action on unknown status is to display the non-intro - /// pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest - /// version of iOS so that the subscription group can be collected by the SDK. - /// - /// - /// The for which you want to compute eligibility. - /// The callback. - public void CheckTrialOrIntroductoryPriceEligibility(string[] products, - CheckTrialOrIntroductoryPriceEligibilityFunc callback) - { - CheckTrialOrIntroductoryPriceEligibilityCallback = callback; - _wrapper.CheckTrialOrIntroductoryPriceEligibility(products); - } - - /// - /// - /// Invalidates the cache for customer information. - /// - /// - /// Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state. - /// Refer to https://docs.revenuecat.com/docs/purchaserinfo#section-get-user-information - /// for more information on using the cache properly. - /// - /// - /// This is useful for cases where customer information might have been updated outside of the app, like if a - /// promotional subscription is granted through the RevenueCat dashboard. - /// - public void InvalidateCustomerInfoCache() - { - _wrapper.InvalidateCustomerInfoCache(); - } - - /// - /// iOS only. Displays a sheet that enables users to redeem subscription offer codes - /// that you generated in App Store Connect. - /// - public void PresentCodeRedemptionSheet() - { - _wrapper.PresentCodeRedemptionSheet(); - } - - /// - /// Callback for . - /// - /// The object if the request was successful, null otherwise. - /// The error if the request was unsuccessful, null otherwise. - public delegate void RecordPurchaseFunc(StoreTransaction transaction, Error error); - - private RecordPurchaseFunc RecordPurchaseCallback { get; set; } - - /// - /// iOS only. Always returns an error on iOS < 15. - /// Use this method only if you already have your own IAP implementation using StoreKit 2 and want to use - /// RevenueCat's backend. If you are using StoreKit 1 for your implementation, you do not need this method. - /// You only need to use this method with *new* purchases. Subscription updates are observed automatically. - /// Important: This should only be used if you have set PurchasesAreCompletedBy to MyApp during SDK configuration. - /// Important: You need to finish the transaction yourself after calling this method. - /// - /// Product ID that was just purchased. - /// A completion block called when the purchase has been recorded, with either a success or an error. - public void RecordPurchase(string productID, RecordPurchaseFunc callback) - { - RecordPurchaseCallback = callback; - _wrapper.RecordPurchase(productID); - } - - /// - /// - /// iOS only. - /// Set this property to true *only* when testing the ask-to-buy / SCA purchases flow. - /// - /// - /// Whether to start simulating ask-to-buy flow in sandbox. - public void SetSimulatesAskToBuyInSandbox(bool askToBuyEnabled) - { - _wrapper.SetSimulatesAskToBuyInSandbox(askToBuyEnabled); - } - - /// - /// - /// Subscriber attributes are useful for storing additional, structured information on a user. - /// Since attributes are writable using a public key they should not be used for - /// managing secure or sensitive information such as subscription status, coins, etc. - /// - /// - /// Key names starting with "$" are reserved names used by RevenueCat. For a full list of key - /// restrictions refer [to our guide](https://docs.revenuecat.com/docs/subscriber-attributes) - /// - /// - /// Map of attributes by key. Set the value as an empty string to delete an attribute. - public void SetAttributes(Dictionary attributes) - { - var jsonObject = new JSONObject(); - foreach (var keyValuePair in attributes) - { - if (keyValuePair.Value == null) - { - jsonObject[keyValuePair.Key] = JSONNull.CreateOrGet(); - } - else - { - jsonObject[keyValuePair.Key] = keyValuePair.Value; - } - } - - _wrapper.SetAttributes(jsonObject.ToString()); - } - - /// - /// - /// Sets the subscriber attribute associated with the email address for the user. - /// - /// - /// - /// The email to set. - /// Passing empty String or null will delete the subscriber attribute. - /// - /// - public void SetEmail(string email) - { - _wrapper.SetEmail(email); - } - - /// - /// Sets the subscriber attribute associated with the phone number for the user. - /// - /// - /// - /// The phone number to set. - /// Passing empty String or null will delete the subscriber attribute. - /// - /// - public void SetPhoneNumber(string phoneNumber) - { - _wrapper.SetPhoneNumber(phoneNumber); - } - - /// - /// Sets the subscriber attribute associated with the display name for the user. - /// - /// - /// - /// The display name to set. - /// Passing empty String or null will delete the subscriber attribute. - /// - /// - public void SetDisplayName(string displayName) - { - _wrapper.SetDisplayName(displayName); - } - - /// - /// Sets the subscriber attribute associated with the push token for the user. - /// - /// - /// - /// The push token to set. - /// Passing empty String or null will delete the subscriber attribute. - /// - /// - public void SetPushToken(string token) - { - _wrapper.SetPushToken(token); - } - - /** - * - * Sets the subscriber attribute associated with the Adjust Id for the user. - * Required for the RevenueCat Adjust integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetAdjustID(string adjustID) - { - _wrapper.SetAdjustID(adjustID); - } - - /** - * - * Sets the subscriber attribute associated with the Appsflyer Id for the user - * Required for the RevenueCat Appsflyer integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetAppsflyerID(string appsflyerID) - { - _wrapper.SetAppsflyerID(appsflyerID); - } - - /** - * - * Sets the subscriber attribute associated with the Facebook SDK Anonymous Id for the user - * Required for the RevenueCat Facebook integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetFBAnonymousID(string fbAnonymousID) - { - _wrapper.SetFBAnonymousID(fbAnonymousID); - } - - /** - * - * Sets the subscriber attribute associated with the mParticle Id for the user - * Required for the RevenueCat mParticle integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetMparticleID(string mparticleID) - { - _wrapper.SetMparticleID(mparticleID); - } - - /** - * - * Sets the subscriber attribute associated with the OneSignal Player Id for the user - * Required for the RevenueCat OneSignal integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetOnesignalID(string onesignalID) - { - _wrapper.SetOnesignalID(onesignalID); - } - - /** - * - * Sets the subscriber attribute associated with the Airship Channel Id for the user - * Required for the RevenueCat Airship integration - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetAirshipChannelID(string airshipChannelID) - { - _wrapper.SetAirshipChannelID(airshipChannelID); - } - - /** - * - * Sets the subscriber attribute associated with the CleverTap ID for the user. - * Required for the RevenueCat CleverTap integration. - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetCleverTapID(string cleverTapID) - { - _wrapper.SetCleverTapID(cleverTapID); - } - - /** - * - * Sets the subscriber attribute associated with the Mixpanel Distinct ID for the user. - * Optional for the RevenueCat Mixpanel integration. - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetMixpanelDistinctID(string mixpanelDistinctID) - { - _wrapper.SetMixpanelDistinctID(mixpanelDistinctID); - } - - /** - * - * Sets the subscriber attribute associated with the Firebase App Instance ID for the user. - * Required for the RevenueCat Firebase integration. - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) - { - _wrapper.SetFirebaseAppInstanceID(firebaseAppInstanceID); - } - - /** - * - * Sets the subscriber attribute associated with the install media source for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetMediaSource(string mediaSource) - { - _wrapper.SetMediaSource(mediaSource); - } - - /** - * - * Sets the subscriber attribute associated with the install campaign for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetCampaign(string campaign) - { - _wrapper.SetCampaign(campaign); - } - - /** - * - * Sets the subscriber attribute associated with the install ad group for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetAdGroup(string adGroup) - { - _wrapper.SetAdGroup(adGroup); - } - - /** - * - * Sets the subscriber attribute associated with the install ad for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetAd(string ad) - { - _wrapper.SetAd(ad); - } - - /** - * - * Sets the subscriber attribute associated with the install keyword for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetKeyword(string keyword) - { - _wrapper.SetKeyword(keyword); - } - - /** - * - * Sets the subscriber attribute associated with the install creative for the user - * - * Empty String or null will delete the subscriber attribute. - */ - public void SetCreative(string creative) - { - _wrapper.SetCreative(creative); - } - - /** - * - * Automatically collect subscriber attributes associated with the device identifiers. - * $idfa, $idfv, $ip on iOS - * $gpsAdId, $androidId, $ip on Android - * - */ - public void CollectDeviceIdentifiers() - { - _wrapper.CollectDeviceIdentifiers(); - } - - /// - /// Callback function containing the result of CanMakePayments - /// - /// - /// A bool value indicating whether billing - /// is supported for the current user (meaning IN-APP purchases are supported), - /// and, if provided, whether a list of specified BillingFeatures are supported. - /// This will be false if there is an error - /// An Error object or null if successful. - public delegate void CanMakePaymentsFunc(bool canMakePayments, Error error); - - private CanMakePaymentsFunc CanMakePaymentsCallback { get; set; } - - /// - /// Check if billing is supported for the current user (meaning IN-APP purchases are supported) - /// and whether a list of specified feature types are supported. - /// - /// Note: BillingFeatures are only relevant to Google Play Android users. - /// For other stores and platforms, BillingFeatures won't be checked. - /// - /// An array of BillingFeatures to check for support. - /// If empty, no features will be checked. - /// A callback receiving a bool for canMakePayments and potentially an Error - public void CanMakePayments(BillingFeature[] features, CanMakePaymentsFunc callback) - { - CanMakePaymentsCallback = callback; - _wrapper.CanMakePayments(features == null ? new BillingFeature[] { } : features); - } - - /// - /// Check if billing is supported for the current user (meaning IN-APP purchases are supported) - /// - /// A callback receiving a bool for canMakePayments and potentially an Error - public void CanMakePayments(CanMakePaymentsFunc callback) - { - CanMakePayments(new BillingFeature[] { }, callback); - } - - /// - /// Callback function containing the result of GetAmazonLWAConsentStatus - /// - /// - /// A bool value indicating whether user has given consent to - /// Login with Amazon. - /// - /// An Error object or null if successful. - public delegate void GetAmazonLWAConsentStatusFunc(bool hasConsented, Error error); - - private GetAmazonLWAConsentStatusFunc GetAmazonLWAConsentStatusCallback { get; set; } - - /// - /// Get the Login with Amazon consent status for the current user. Used to implement one-click account creation - /// using Quick Subscribe. - /// - /// For more information, check the documentation: - /// https://rev.cat/amazon-quicksubscribe - /// - /// Note: This method only works for the Amazon Appstore. There is no Google equivalent at this time. - /// Calling from a Google-configured app will always return False. - /// - /// A callback receiving a bool for hasConsented and potentially an Error - public void GetAmazonLWAConsentStatus(GetAmazonLWAConsentStatusFunc callback) - { - GetAmazonLWAConsentStatusCallback = callback; - _wrapper.GetAmazonLWAConsentStatus(); - } - - /// - /// Callback function containing the result of GetPromotionalOffer - /// - /// - /// A Purchases.PromotionalOffer. It will be Null if platform is Android or - /// the iOS version is not compatible with promotional offers - /// An Error object or null if successful. - public delegate void GetPromotionalOfferFunc(PromotionalOffer promotionalOffer, Error error); - - private GetPromotionalOfferFunc GetPromotionalOfferCallback { get; set; } - - /// - /// iOS only. Use this function to retrieve the Purchases.PromotionalOffer for a given Purchases.Package. - /// - /// The Purchases.StoreProduct the user intends to purchase - /// The Purchases.Discount to apply to the product. - /// A callback receiving a Purchases.PromotionalOffer. Null is returned for Android and - /// incompatible iOS versions. - public void GetPromotionalOffer(StoreProduct storeProduct, Discount discount, GetPromotionalOfferFunc callback) - { - GetPromotionalOfferCallback = callback; - _wrapper.GetPromotionalOffer(storeProduct.Identifier, discount.Identifier); - } - - /// Displays the specified store in-app message types to the user if there are any available to be shown. - /// - Important: This should only be used if you disabled these messages from showing automatically - /// during SDK configuration setting ``shouldShowInAppMessagesAutomatically`` to ``false``. - /// - /// @param [messageTypes] The types of messages to show. - public void ShowInAppMessages(Purchases.InAppMessageType[] messageTypes = null) - { - _wrapper.ShowInAppMessages(messageTypes); - } - - public delegate void ParseAsWebPurchaseRedemptionFunc(WebPurchaseRedemption webPurchaseRedemption); - - private ParseAsWebPurchaseRedemptionFunc ParseAsWebPurchaseRedemptionCallback { get; set; } - - public void ParseAsWebPurchaseRedemption(string urlString, ParseAsWebPurchaseRedemptionFunc callback) - { - ParseAsWebPurchaseRedemptionCallback = callback; - _wrapper.ParseAsWebPurchaseRedemption(urlString); - } - - public delegate void RedeemWebPurchaseFunc(WebPurchaseRedemptionResult result); - - private RedeemWebPurchaseFunc RedeemWebPurchaseCallback { get; set; } - - public void RedeemWebPurchase(WebPurchaseRedemption webPurchaseRedemption, RedeemWebPurchaseFunc callback) - { - RedeemWebPurchaseCallback = callback; - _wrapper.RedeemWebPurchase(webPurchaseRedemption); - } - - public delegate void GetVirtualCurrenciesFunc(VirtualCurrencies? virtualCurrencies, Error? error); - - private GetVirtualCurrenciesFunc GetVirtualCurrenciesCallback { get; set; } - - /// - /// Fetches the virtual currencies for the current user. - /// - public void GetVirtualCurrencies(GetVirtualCurrenciesFunc callback) - { - GetVirtualCurrenciesCallback = callback; - _wrapper.GetVirtualCurrencies(); - } - - /// - /// The currently cached VirtualCurrencies if one is available. - /// - /// - /// - /// This value will remain null until virtual currencies have been fetched at - /// least once with GetVirtualCurrencies or an equivalent function. - /// - public VirtualCurrencies? GetCachedVirtualCurrencies() - { - string cachedVirtualCurrenciesJSON = _wrapper.GetCachedVirtualCurrencies(); - - if (string.IsNullOrEmpty(cachedVirtualCurrenciesJSON)) - { - return null; - } - - var response = JSON.Parse(cachedVirtualCurrenciesJSON); - return new VirtualCurrencies(response); - } - - /// - /// Invalidates the cache for virtual currencies. - /// - /// - /// - /// This is useful for cases where a virtual currency's balance might have been updated - /// outside of the app, like if you decreased a user's balance from the user spending a virtual currency, - /// or if you increased the balance from your backend using the server APIs. - /// - public void InvalidateVirtualCurrenciesCache() - { - _wrapper.InvalidateVirtualCurrenciesCache(); - } - - public delegate void GetEligibleWinBackOffersForProductFunc(WinBackOffer[] winBackOffers, Error error); - - private GetEligibleWinBackOffersForProductFunc GetEligibleWinBackOffersForProductCallback { get; set; } - - /// - /// Gets eligible win-back offers for a given store product. Only available on iOS 18.0+ with StoreKit 2. - /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. - /// - /// The Purchases.StoreProduct to get win-back offers for - /// A callback receiving an array of Purchases.WinBackOffer objects or an error if unsuccessful - public void GetEligibleWinBackOffersForProduct(StoreProduct storeProduct, GetEligibleWinBackOffersForProductFunc callback) - { - GetEligibleWinBackOffersForProductCallback = callback; - _wrapper.GetEligibleWinBackOffersForProduct(storeProduct); - } - - public delegate void GetEligibleWinBackOffersForPackageFunc(WinBackOffer[] winBackOffers, Error error); - - private GetEligibleWinBackOffersForPackageFunc GetEligibleWinBackOffersForPackageCallback { get; set; } - - /// - /// Gets eligible win-back offers for a given package. Only available on iOS 18.0+ with StoreKit 2. - /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. - /// - /// The Purchases.Package to get win-back offers for - /// A callback receiving an array of Purchases.WinBackOffer objects or an error if unsuccessful - public void GetEligibleWinBackOffersForPackage(Package package, GetEligibleWinBackOffersForPackageFunc callback) - { - GetEligibleWinBackOffersForPackageCallback = callback; - _wrapper.GetEligibleWinBackOffersForPackage(package); - } - - /// - /// Purchase a product with a win-back offer. Only available on iOS 18.0+ with StoreKit 2. - /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. - /// - /// The Purchases.StoreProduct to purchase - /// The Purchases.WinBackOffer to use - /// A callback receiving the product identifier, customer info, user cancellation, and an error if the purchase fails - public void PurchaseProductWithWinBackOffer(StoreProduct storeProduct, WinBackOffer winBackOffer, MakePurchaseFunc callback) - { - MakePurchaseCallback = callback; - _wrapper.PurchaseProductWithWinBackOffer(storeProduct, winBackOffer); - } - - /// - /// Purchase a package with a win-back offer. Only available on iOS 18.0+ with StoreKit 2. - /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. - /// - /// The Purchases.Package to purchase - /// The Purchases.WinBackOffer to use - /// A callback receiving the product identifier, customer info, user cancellation, and an error if the purchase fails - public void PurchasePackageWithWinBackOffer(Package package, WinBackOffer winBackOffer, MakePurchaseFunc callback) - { - MakePurchaseCallback = callback; - _wrapper.PurchasePackageWithWinBackOffer(package, winBackOffer); - } - - private void _receiveStorefront(string storefrontJson) - { - Debug.Log("_receiveStorefront " + storefrontJson); - - if (StorefrontCallback == null) return; - var callback = StorefrontCallback; - StorefrontCallback = null; - - if (storefrontJson == null || storefrontJson == "{}") - { - callback(null); - } - else - { - var response = JSON.Parse(storefrontJson); - var countryCode = response["countryCode"]; - if (countryCode == null) - { - Debug.LogError("StorefrontCallback received null countryCode"); - callback(null); - } - else - { - callback(new Storefront(countryCode)); - } - } - } - - // ReSharper disable once UnusedMember.Local - private void _receiveProducts(string productsJson) - { - Debug.Log("_receiveProducts " + productsJson); - - if (ProductsCallback == null) return; - - var response = JSON.Parse(productsJson); - var callback = ProductsCallback; - ProductsCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var products = new List(); - foreach (JSONNode productResponse in response["products"]) - { - var product = new StoreProduct(productResponse); - products.Add(product); - } - - callback(products, null); - } - } - - // ReSharper disable once UnusedMember.Local - private void _getCustomerInfo(string customerInfoJson) - { - Debug.Log("_getCustomerInfo " + customerInfoJson); - var callback = GetCustomerInfoCallback; - GetCustomerInfoCallback = null; - ReceiveCustomerInfoMethod(customerInfoJson, callback); - } - - // ReSharper disable once UnusedMember.Local - private void _makePurchase(string makePurchaseResponseJson) - { - Debug.Log("_makePurchase " + makePurchaseResponseJson); - - if (MakePurchaseCallback == null) return; - var callback = MakePurchaseCallback; - MakePurchaseCallback = null; - - var response = JSON.Parse(makePurchaseResponseJson); - callback(new PurchaseResult(response)); - } - - // ReSharper disable once UnusedMember.Local - private void _receiveCustomerInfo(string customerInfoJson) - { - Debug.Log("_receiveCustomerInfo " + customerInfoJson); - - if (listener == null) return; - - var response = JSON.Parse(customerInfoJson); - if (response["customerInfo"] == null) return; - var info = new CustomerInfo(response["customerInfo"]); - listener.CustomerInfoReceived(info); - } - - // ReSharper disable once UnusedMember.Local - private void _handleLog(string logDetailsJson) - { - if (listener == null) return; - - var response = JSON.Parse(logDetailsJson); - var logLevelInResponse = response["logLevel"]; - if (logLevelInResponse == null) return; - var messageInResponse = response["message"]; - if (messageInResponse == null) return; - - var logLevel = Extensions.ParseLogLevelByName(logLevelInResponse); - - LogHandler(logLevel, messageInResponse); - } - - - // ReSharper disable once UnusedMember.Local - private void _restorePurchases(string customerInfoJson) - { - Debug.Log("_restorePurchases " + customerInfoJson); - var callback = RestorePurchasesCallback; - RestorePurchasesCallback = null; - ReceiveCustomerInfoMethod(customerInfoJson, callback); - } - - private void _syncPurchases(string customerInfoJson) - { - Debug.Log("_syncPurchases " + customerInfoJson); - var callback = SyncPurchasesCallback; - SyncPurchasesCallback = null; - ReceiveCustomerInfoMethod(customerInfoJson, callback); - } - - // ReSharper disable once UnusedMember.Local - private void _logIn(string logInResultJson) - { - Debug.Log("_logIn " + logInResultJson); - var callback = LogInCallback; - LogInCallback = null; - ReceiveLogInResultMethod(logInResultJson, callback); - } - - // ReSharper disable once UnusedMember.Local - private void _logOut(string customerInfoJson) - { - Debug.Log("_logOut " + customerInfoJson); - var callback = LogOutCallback; - LogOutCallback = null; - ReceiveCustomerInfoMethod(customerInfoJson, callback); - } - - // ReSharper disable once UnusedMember.Local - private void _getOfferings(string offeringsJson) - { - Debug.Log("_getOfferings " + offeringsJson); - if (GetOfferingsCallback == null) return; - var response = JSON.Parse(offeringsJson); - var callback = GetOfferingsCallback; - GetOfferingsCallback = null; - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var offeringsResponse = response["offerings"]; - callback(new Offerings(offeringsResponse), null); - } - } - - // ReSharper disable once UnusedMember.Local - private void _getCurrentOfferingForPlacement(string offeringJson) - { - if (GetCurrentOfferingForPlacementCallback == null) return; - var response = JSON.Parse(offeringJson); - var callback = GetCurrentOfferingForPlacementCallback; - GetCurrentOfferingForPlacementCallback = null; - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var offeringResponse = response["offering"]; - callback(new Offering(offeringResponse), null); - } - } - - // ReSharper disable once UnusedMember.Local - private void _syncAttributesAndOfferingsIfNeeded(string offeringsJson) - { - if (SyncAttributesAndOfferingsIfNeededCallback == null) return; - var response = JSON.Parse(offeringsJson); - var callback = SyncAttributesAndOfferingsIfNeededCallback; - SyncAttributesAndOfferingsIfNeededCallback = null; - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var offeringsResponse = response["offerings"]; - callback(new Offerings(offeringsResponse), null); - } - } - - private void _checkTrialOrIntroductoryPriceEligibility(string json) - { - Debug.Log("_checkTrialOrIntroductoryPriceEligibility " + json); - - if (CheckTrialOrIntroductoryPriceEligibilityCallback == null) return; - - var response = JSON.Parse(json); - var dictionary = new Dictionary(); - foreach (var keyValuePair in response) - { - dictionary[keyValuePair.Key] = new IntroEligibility(keyValuePair.Value); - } - - var callback = CheckTrialOrIntroductoryPriceEligibilityCallback; - CheckTrialOrIntroductoryPriceEligibilityCallback = null; - - callback(dictionary); - - } - - private void _recordPurchase(string json) - { - Debug.Log("_recordPurchase " + json); - - if (RecordPurchaseCallback == null) return; - - var response = JSON.Parse(json); - var callback = RecordPurchaseCallback; - RecordPurchaseCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var transaction = new StoreTransaction(response["transaction"]); - - callback(transaction, null); - } - } - - private void _canMakePayments(string canMakePaymentsJson) - { - Debug.Log("_canMakePayments" + canMakePaymentsJson); - - if (CanMakePaymentsCallback == null) return; - - var response = JSON.Parse(canMakePaymentsJson); - var callback = CanMakePaymentsCallback; - CanMakePaymentsCallback = null; - - if (ResponseHasError(response)) - { - callback(false, new Error(response["error"])); - } - else - { - var canMakePayments = response["canMakePayments"]; - callback(canMakePayments, null); - } - } - - private void _getAmazonLWAConsentStatus(string getAmazonLWAConsentStatusJson) - { - Debug.Log("_getAmazonLWAConsentStatus" + getAmazonLWAConsentStatusJson); - - if (GetAmazonLWAConsentStatusCallback == null) return; - - var response = JSON.Parse(getAmazonLWAConsentStatusJson); - var callback = GetAmazonLWAConsentStatusCallback; - GetAmazonLWAConsentStatusCallback = null; - - if (ResponseHasError(response)) - { - callback(false, new Error(response["error"])); - } - else - { - var amazonLWAConsentStatus = response["amazonLWAConsentStatus"]; - callback(amazonLWAConsentStatus, null); - } - - } - - private void _getPromotionalOffer(string getPromotionalOfferJson) - { - Debug.Log("_getPromotionalOffer" + getPromotionalOfferJson); - - if (GetPromotionalOfferCallback == null) return; - - var response = JSON.Parse(getPromotionalOfferJson); - var callback = GetPromotionalOfferCallback; - GetPromotionalOfferCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var promotionalOffer = new PromotionalOffer(response); - callback(promotionalOffer, null); - } - } - - private void _parseAsWebPurchaseRedemption(string parseAsWebPurchaseRedemptionJSON) - { - Debug.Log("_parseAsWebPurchaseRedemption " + parseAsWebPurchaseRedemptionJSON); - - if (ParseAsWebPurchaseRedemptionCallback == null) return; - - var response = JSON.Parse(parseAsWebPurchaseRedemptionJSON); - var callback = ParseAsWebPurchaseRedemptionCallback; - ParseAsWebPurchaseRedemptionCallback = null; - - if (ResponseHasError(response)) - { - callback(null); - } - else - { - var webPurchaseRedemption = new WebPurchaseRedemption(response["redemptionLink"]); - callback(webPurchaseRedemption); - } - } - - private void _redeemWebPurchase(string redeemWebPurchaseJSON) - { - Debug.Log("_redeemWebPurchase " + redeemWebPurchaseJSON); - - if (RedeemWebPurchaseCallback == null) return; - - var response = JSON.Parse(redeemWebPurchaseJSON); - var callback = RedeemWebPurchaseCallback; - RedeemWebPurchaseCallback = null; - - if (ResponseHasError(response)) - { - callback(null); - } - else - { - var result = WebPurchaseRedemptionResult.FromJson(response); - callback(result); - } - } - - private void _getVirtualCurrencies(string getVirtualCurrenciesJson) - { - Debug.Log("_getVirtualCurrencies " + getVirtualCurrenciesJson); - - if (GetVirtualCurrenciesCallback == null) return; - - var response = JSON.Parse(getVirtualCurrenciesJson); - var callback = GetVirtualCurrenciesCallback; - GetVirtualCurrenciesCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var virtualCurrencies = new VirtualCurrencies(response); - callback(virtualCurrencies, null); - } - } - - private void _getEligibleWinBackOffersForProduct(string eligibleWinBackOffersJson) - { - Debug.Log("_getEligibleWinBackOffersForProduct " + eligibleWinBackOffersJson); - - if (GetEligibleWinBackOffersForProductCallback == null) return; - - var response = JSON.Parse(eligibleWinBackOffersJson); - var callback = GetEligibleWinBackOffersForProductCallback; - GetEligibleWinBackOffersForProductCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var winBackOffers = new List(); - foreach (JSONNode offerResponse in response["eligibleWinBackOffers"]) - { - var offer = new WinBackOffer(offerResponse); - winBackOffers.Add(offer); - } - - callback(winBackOffers.ToArray(), null); - } - } - - private void _getEligibleWinBackOffersForPackage(string eligibleWinBackOffersJson) - { - Debug.Log("_getEligibleWinBackOffersForPackage " + eligibleWinBackOffersJson); - - if (GetEligibleWinBackOffersForPackageCallback == null) return; - - var response = JSON.Parse(eligibleWinBackOffersJson); - var callback = GetEligibleWinBackOffersForPackageCallback; - GetEligibleWinBackOffersForPackageCallback = null; - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var winBackOffers = new List(); - foreach (JSONNode offerResponse in response["eligibleWinBackOffers"]) - { - var offer = new WinBackOffer(offerResponse); - winBackOffers.Add(offer); - } - - callback(winBackOffers.ToArray(), null); - } - - } - - private void _purchaseProductWithWinBackOffer(string purchaseProductWithWinBackOfferJson) - { - Debug.Log("_purchaseProductWithWinBackOffer " + purchaseProductWithWinBackOfferJson); - - if (MakePurchaseCallback == null) return; - - var response = JSON.Parse(purchaseProductWithWinBackOfferJson); - var callback = MakePurchaseCallback; - MakePurchaseCallback = null; - - callback(new PurchaseResult(response)); - } - - private void _purchasePackageWithWinBackOffer(string purchasePackageWithWinBackOfferJson) - { - Debug.Log("_purchasePackageWithWinBackOffer " + purchasePackageWithWinBackOfferJson); - - if (MakePurchaseCallback == null) return; - - var response = JSON.Parse(purchasePackageWithWinBackOfferJson); - var callback = MakePurchaseCallback; - MakePurchaseCallback = null; - - callback(new PurchaseResult(response)); - } - - private static void ReceiveCustomerInfoMethod(string arguments, CustomerInfoFunc callback) - { - if (callback == null) return; - - var response = JSON.Parse(arguments); - - if (ResponseHasError(response)) - { - callback(null, new Error(response["error"])); - } - else - { - var info = new CustomerInfo(response["customerInfo"]); - callback(info, null); - } - } - - private static void ReceiveLogInResultMethod(string arguments, LogInFunc callback) - { - if (callback == null) return; - - var response = JSON.Parse(arguments); - - if (ResponseHasError(response)) - { - callback(null, false, new Error(response["error"])); - } - else - { - var info = new CustomerInfo(response["customerInfo"]); - var created = response["created"]; - callback(info, created, null); - } - } - - private static bool ResponseHasError(JSONNode response) - { - return response != null && response.HasKey("error") && response["error"] != null && !response["error"].IsNull; - } -} +//using Newtonsoft.Json; +//using Newtonsoft.Json.Linq; +//using RevenueCat; +//using System; +//using System.Collections.Generic; +//using UnityEngine; + +//public class Purchases : MonoBehaviour +//{ +// private IPurchasesWrapper _wrapper = new PurchasesWrapperNoop(); + +// /// +// /// Callback type for . +// /// Includes the info of the current store account. +// /// +// public delegate void GetStorefrontFunc(Storefront storefront); + +// private GetStorefrontFunc StorefrontCallback { get; set; } + +// /// +// /// Fetches the Storefront for the customer's current store account. +// /// If there is an error, the callback will be called with a null value. +// /// +// public void GetStorefront(GetStorefrontFunc callback) +// { +// StorefrontCallback = callback; +// _wrapper.GetStorefrontAsync(); +// } + +// /// +// /// Callback type for . +// /// Includes a list of products or an error. +// /// +// public delegate void GetProductsFunc(List products, Error error); + +// private GetProductsFunc ProductsCallback { get; set; } + +// // ReSharper disable once MemberCanBePrivate.Global +// /// +// /// Fetches the StoreProducts for your IAPs for given productIdentifiers. +// /// This method is called automatically with products pre-configured through Unity IDE UI. +// /// You can optionally call this if you want to fetch more products. +// /// +// /// +// /// +// /// A set of product identifiers for in-app purchases setup via AppStoreConnect.\n +// /// This should be either hard coded in your application, from a file, or from a custom endpoint if \n +// /// you want to be able to deploy new IAPs without an app update. +// /// +// /// +// /// A callback that is called with the loaded products.\n +// /// If the fetch fails for any reason it will return an empty array and an error. +// /// +// /// Android only. The type of product to purchase. +// /// +// /// completion may be called without s that you are expecting.\n +// /// This is usually caused by iTunesConnect configuration errors.\n +// /// Ensure your IAPs have the “Ready to Submit” status in iTunesConnect.\n +// /// Also ensure that you have an active developer program subscription and you have signed the\n +// /// latest paid application agreements.\n +// /// If you’re having trouble, +// /// +// public void GetProducts(string[] products, GetProductsFunc callback, string type = "subs") +// { +// ProductsCallback = callback; +// _wrapper.GetProductsAsync(products, type); +// } + +// /// +// /// Callback type for methods that make purchases, like ,\n +// /// , , \n +// /// , , \n +// /// and . +// /// +// /// +// /// The object for the purchase attempt that just happened. +// public delegate void MakePurchaseFunc(PurchaseResult purchaseResult); + +// private MakePurchaseFunc MakePurchaseCallback { get; set; } + +// /// +// /// +// /// Initiates a purchase of a . +// /// +// /// Use this function if you are not using the system to purchase a . +// /// If you are using the system, use instead. +// /// +// /// +// /// Call this method when a user has decided to purchase a product. +// /// Only call this in direct response to user input. +// /// +// /// +// /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. +// /// +// /// +// /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will +// /// handle this for you. +// /// +// /// +// /// The identifier of the the user intends to purchase. +// /// A completion block that is called when the purchase completes. +// /// Android only. The type of product to purchase. +// /// Android only. Optional. The oldSku to upgrade from. +// /// Android only. Optional. The to use when upgrading the given oldSku. +// /// Android only. Optional. Indicates +// /// personalized pricing on products available for purchase in the EU. +// /// For compliance with EU regulations. User will see "This price has been +// /// customized for you" in the purchase dialog when true. +// /// See https://developer.android.com/google/play/billing/integrate#personalized-price +// /// for more info. +// /// +// public void PurchaseProduct(string productIdentifier, MakePurchaseFunc callback, +// string type = "subs", string oldSku = null, +// ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, +// bool googleIsPersonalizedPrice = false) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchaseProductAsync(productIdentifier, type, oldSku, prorationMode, googleIsPersonalizedPrice); +// } + +// /// +// /// +// /// iOS only. Initiates a purchase of a with a . +// /// You can get a PromotionalOffer by calling . +// /// +// /// Use this function if you are not using the system to purchase a . +// /// If you are using the system, use instead. +// /// +// /// +// /// Call this method when a user has decided to purchase a product. +// /// Only call this in direct response to user input. +// /// +// /// +// /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. +// /// +// /// +// /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will +// /// handle this for you. +// /// +// /// +// /// The identifier of the the user intends to purchase. +// /// A to apply to the purchase. +// /// A completion block that is called when the purchase completes. +// public void PurchaseDiscountedProduct(string productIdentifier, PromotionalOffer discount, MakePurchaseFunc callback) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchaseProductAsync(productIdentifier, discount: discount); +// } + +// /// +// /// +// /// Initiates a purchase of a . +// /// +// /// +// /// +// /// Call this method when a user has decided to purchase a product. +// /// Only call this in direct response to user input. +// /// +// /// +// /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. +// /// +// /// +// /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will +// /// handle this for you. +// /// +// /// +// /// The the user intends to purchase. +// /// A completion block that is called when the purchase completes. +// /// Android only. Optional. The oldSku to upgrade from. +// /// Android only. Optional. The to use when upgrading the given oldSku. +// /// Android only. Optional. Indicates +// /// personalized pricing on products available for purchase in the EU. +// /// For compliance with EU regulations. User will see "This price has been +// /// customized for you" in the purchase dialog when true. +// /// See https://developer.android.com/google/play/billing/integrate#personalized-price +// /// for more info. +// /// +// public void PurchasePackage(Package package, MakePurchaseFunc callback, string oldSku = null, +// ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, +// bool googleIsPersonalizedPrice = false) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchasePackageAsync(package, oldSku, prorationMode, googleIsPersonalizedPrice); +// } + +// /// +// /// +// /// iOS only. Initiates a purchase of a . +// /// You can get a PromotionalOffer by calling . +// /// +// /// +// /// +// /// Call this method when a user has decided to purchase a product. +// /// Only call this in direct response to user input. +// /// +// /// +// /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. +// /// +// /// +// /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will +// /// handle this for you. +// /// +// /// +// /// The the user intends to purchase. +// /// A to apply to the purchase. +// /// A completion block that is called when the purchase completes. +// /// +// public void PurchaseDiscountedPackage(Package package, PromotionalOffer discount, MakePurchaseFunc callback) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchasePackageAsync(package, discount: discount); +// } + +// public void PurchaseSubscriptionOption(SubscriptionOption subscriptionOption, MakePurchaseFunc callback, +// GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchaseSubscriptionOptionAsync(subscriptionOption, googleProductChangeInfo, googleIsPersonalizedPrice); +// } + +// /// +// /// Callback type for methods that return . +// /// Includes a or an error. +// /// +// /// A if the request was successful, null otherwise. +// /// An error if the request was not successful, null otherwise. +// public delegate void CustomerInfoFunc(CustomerInfo customerInfo, Error error); + +// private CustomerInfoFunc RestorePurchasesCallback { get; set; } + +// /// +// /// This method will post all purchases associated with the current Store account to RevenueCat and become +// /// associated with the current appUserID. If the receipt is being used by an existing user, the current +// /// appUserID will be aliased together with the appUserID of the existing user. +// /// Going forward, either appUserID will be able to reference the same user. +// /// +// /// +// /// +// /// You shouldn't use this method if you have your own account system. In that case "restoration" is provided.
+// /// Note: This may force your users to enter their Store password so should only be performed on request of +// /// by your app passing the same appUserID used to purchase originally. +// /// the user. Typically, with a button in settings or near your purchase UI. Use +// /// if you need to restore transactions programmatically. +// ///
+// /// A which will contain a +// /// if restoration was successful, or an error otherwise. +// public void RestorePurchases(CustomerInfoFunc callback) +// { +// RestorePurchasesCallback = callback; +// _wrapper.RestorePurchasesAsync(); +// } + +// [Obsolete("Deprecated, use set methods instead.", true)] +// public void AddAttributionData(string dataJson, AttributionNetwork network, string networkUserId = null) { } + +// /// +// /// Callback function for . +// /// +// /// The of the user if the request was successful. +// /// Null otherwise. +// /// True if the user was registered in RevenueCat's database for the first time upon +// /// making this call. False if a user with this appUserID already existed in RevenueCat's database for +// /// this app. +// /// If the request was unsuccessful, contains an error. Null otherwise. +// public delegate void LogInFunc(CustomerInfo customerInfo, bool created, Error error); + +// private LogInFunc LogInCallback { get; set; } + +// /// +// /// This function will log in the current user with an appUserID. +// /// +// /// +// /// RevenueCat provides a source of truth for a subscriber's status across different platforms. +// /// To do this, each subscriber has an App User ID that uniquely identifies them within your application. +// /// User identity is one of the most important components of many mobile applications, +// /// and it's extra important to make sure the subscription status RevenueCat is +// /// tracking gets associated with the correct user. +// /// The Purchases SDK allows you to specify your own user identifiers or use anonymous identifiers +// /// generated by RevenueCat. Some apps will use a combination +// /// of their own identifiers and RevenueCat anonymous Ids - that's okay! +// /// +// /// The appUserID that should be linked to the current user. +// /// +// /// The callback block will be called with the latest and a bool specifying +// /// whether the user was created for the first time in the RevenueCat backend. +// /// +// /// +// /// +// /// +// /// +// public void LogIn(string appUserId, LogInFunc callback) +// { +// LogInCallback = callback; +// _wrapper.LogInAsync(appUserId); +// } + +// private CustomerInfoFunc LogOutCallback { get; set; } + + +// /// +// /// +// /// Logs out the Purchases client, clearing the saved appUserID. +// /// +// /// +// /// This will generate a random user id and save it in the cache. +// /// If this method is called and the current user is anonymous, it will return an error. +// /// +// /// +// /// The callback will contain a CustomerInfo if the logOut +// /// operation was successful, or an error otherwise. +// /// +// /// +// /// +// /// +// /// +// /// +// public void LogOut(CustomerInfoFunc callback) +// { +// LogOutCallback = callback; +// _wrapper.LogOutAsync(); +// } + +// // ReSharper disable once UnusedMember.Global +// [Obsolete(@"Configure behavior through the RevenueCat dashboard instead. +// If you have configured the 'Legacy' restore behavior in the RevenueCat Dashboard +// and are currently setting this to true, keep this setting active.")] +// public void SetAllowSharingStoreAccount(bool allow) +// { +// _wrapper.SetAllowSharingStoreAccount(allow); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// The appUserID used by Purchases. +// /// If not passed on initialization this will be generated and cached by Purchases. +// /// +// /// The app user ID currently used by Purchases. +// public string GetAppUserId() +// { +// return _wrapper.GetAppUserId(); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Returns true if the appUserID has been generated by RevenueCat, false otherwise. +// /// +// public bool IsAnonymous() +// { +// return _wrapper.IsAnonymous(); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Returns true if configure has been called and [Purchases.sharedInstance] is set. +// /// +// public bool IsConfigured() +// { +// return _wrapper.IsConfigured(); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Enable debug logging. Useful for debugging issues with the lovely team @RevenueCat. +// /// +// /// +// /// Whether debug logs should be enabled. +// [Obsolete("Deprecated, use logLevel instead.")] +// public void SetDebugLogsEnabled(bool logsEnabled) +// { +// _wrapper.SetDebugLogsEnabled(logsEnabled); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Configure log level. Useful for debugging issues with the lovely team @RevenueCat. +// /// +// public void SetLogLevel(LogLevel level) +// { +// _wrapper.SetLogLevel(level); +// } + +// private CustomerInfoFunc GetCustomerInfoCallback { get; set; } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Get latest available . +// /// +// /// +// /// A completion block called when customer info is available and not stale. +// /// Called immediately if is cached. Customer info can be nil if an error occurred. +// /// +// /// +// public void GetCustomerInfo(CustomerInfoFunc callback) +// { +// GetCustomerInfoCallback = callback; +// _wrapper.GetCustomerInfoAsync(); +// } + +// /// +// /// Callback for . +// /// +// /// The object if the request was successful, null otherwise. +// /// The error if the request was unsuccessful, null otherwise. +// public delegate void GetOfferingsFunc(Offerings offerings, Error error); + +// private GetOfferingsFunc GetOfferingsCallback { get; set; } + +// /// +// /// Callback for . +// /// +// /// The nullable object if the request was successful, null otherwise. +// /// The error if the request was unsuccessful, null otherwise. +// public delegate void GetCurrentOfferingForPlacementFunc(Offering offerings, Error error); + +// private GetCurrentOfferingForPlacementFunc GetCurrentOfferingForPlacementCallback { get; set; } + +// /// +// /// Callback for . +// /// +// /// The object if the request was successful, null otherwise. +// /// The error if the request was unsuccessful, null otherwise. +// public delegate void SyncAttributesAndOfferingsIfNeededFunc(Offerings offerings, Error error); + +// private SyncAttributesAndOfferingsIfNeededFunc SyncAttributesAndOfferingsIfNeededCallback { get; set; } + +// /// +// /// +// /// Fetch the configured for this user. +// /// +// /// +// /// allows you to configure your in-app products +// /// via RevenueCat and greatly simplifies management. +// /// +// /// will be fetched and cached on instantiation so that, by the time they are needed, +// /// your prices are loaded for your purchase flow. Time is money. +// /// +// /// A completion block called when offerings are available. +// /// Called immediately if offerings are cached. will be null if an error occurred. +// /// +// /// +// /// +// /// +// public void GetOfferings(GetOfferingsFunc callback) +// { +// GetOfferingsCallback = callback; +// _wrapper.GetOfferingsAsync(); +// } + +// public void GetCurrentOfferingForPlacement(string placementIdentifier, GetCurrentOfferingForPlacementFunc callback) +// { +// GetCurrentOfferingForPlacementCallback = callback; +// _wrapper.GetCurrentOfferingForPlacementAsync(placementIdentifier); +// } + +// public void SyncAttributesAndOfferingsIfNeeded(SyncAttributesAndOfferingsIfNeededFunc callback) +// { +// SyncAttributesAndOfferingsIfNeededCallback = callback; +// _wrapper.SyncAttributesAndOfferingsIfNeededAsync(); +// } + +// /// +// /// This method will post all purchases associated with the current App Store account to RevenueCat and +// /// become associated with the current appUserID. +// /// +// /// +// /// If the receipt is being used by an existing user, the current appUserID will be aliased together with +// /// the appUserID of the existing user. +// /// Going forward, either appUserID will be able to reference the same user. +// /// +// /// +// /// Warning: This function should only be called if you're not calling any purchase method. +// /// +// /// +// /// +// /// Note: This method will not trigger a login prompt from App Store. However, if the receipt currently +// /// on the device does not contain subscriptions, but the user has made subscription purchases, this method +// /// won't be able to restore them. Use to cover those cases. +// /// +// /// +// /// +// public void SyncPurchases() +// { +// _wrapper.SyncPurchasesAsync(); +// } + +// private CustomerInfoFunc SyncPurchasesCallback { get; set; } + +// /// +// /// This method will post all purchases associated with the current App Store account to RevenueCat and +// /// become associated with the current appUserID. +// /// +// /// +// /// If the receipt is being used by an existing user, the current appUserID will be aliased together with +// /// the appUserID of the existing user. +// /// Going forward, either appUserID will be able to reference the same user. +// /// +// /// +// /// Warning: This function should only be called if you're not calling any purchase method. +// /// +// /// +// /// +// /// Note: This method will not trigger a login prompt from App Store. However, if the receipt currently +// /// on the device does not contain subscriptions, but the user has made subscription purchases, this method +// /// won't be able to restore them. Use to cover those cases. +// /// +// /// +// /// +// /// A which will contain a +// /// if sync was successful, or an error otherwise. +// public void SyncPurchases(CustomerInfoFunc callback) +// { +// SyncPurchasesCallback = callback; +// _wrapper.SyncPurchasesAsync(); +// } + +// /// +// /// Android only. Noop in iOS. +// /// +// /// This method will send a purchase to the RevenueCat backend. This function should only be called if you are +// /// in Amazon observer mode or performing a client side migration of your current users to RevenueCat. +// /// The receipt IDs are cached if successfully posted so they are not posted more than once. +// /// +// /// Product ID associated to the purchase. +// /// ReceiptId that represents the Amazon purchase. +// /// Amazon's userID. +// /// Product's currency code in ISO 4217 format. +// /// Product's price. +// [Obsolete("Deprecated, use SyncAmazonPurchase instead.")] +// public void SyncObserverModeAmazonPurchase(string productID, string receiptID, string amazonUserID, +// string isoCurrencyCode, double price) +// { +// _wrapper.SyncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); +// } + +// /// +// /// Android only. Noop in iOS. +// /// +// /// This method will send a purchase to the RevenueCat backend. This function should only be called if you are +// /// in Amazon observer mode or performing a client side migration of your current users to RevenueCat. +// /// The receipt IDs are cached if successfully posted so they are not posted more than once. +// /// +// /// Product ID associated to the purchase. +// /// ReceiptId that represents the Amazon purchase. +// /// Amazon's userID. +// /// Product's currency code in ISO 4217 format. +// /// Product's price. +// public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, +// string isoCurrencyCode, double price) +// { +// _wrapper.SyncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); +// } + +// // ReSharper disable once UnusedMember.Global +// /// +// /// Enable automatic collection of Apple Search Ads attribution using AdServices. Defaults to `false`. +// /// +// public void EnableAdServicesAttributionTokenCollection() +// { +// _wrapper.EnableAdServicesAttributionTokenCollection(); +// } + +// /// +// /// iOS only. Callback for the method. +// /// +// /// A Dictionary mapping product identifiers to their eligibility status, +// /// as objects. +// public delegate void CheckTrialOrIntroductoryPriceEligibilityFunc(Dictionary products); + +// private CheckTrialOrIntroductoryPriceEligibilityFunc CheckTrialOrIntroductoryPriceEligibilityCallback { get; set; } + +// /// +// /// +// /// iOS only. Computes whether or not a user is eligible for the introductory pricing period of a given product. +// /// You should use this method to determine whether or not you show the user the normal product price or +// /// the introductory price. This also applies to trials (trials are considered a type of introductory pricing). +// /// . +// /// +// /// +// /// +// /// Note: If you're looking to use Promotional Offers instead, +// /// use . +// /// +// /// +// /// +// /// Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't +// /// definitively compute the eligibility, most likely because of missing group information, it will return +// /// . +// /// The best course of action on unknown status is to display the non-intro +// /// pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest +// /// version of iOS so that the subscription group can be collected by the SDK. +// /// +// /// +// /// The for which you want to compute eligibility. +// /// The callback. +// public void CheckTrialOrIntroductoryPriceEligibility(string[] products, +// CheckTrialOrIntroductoryPriceEligibilityFunc callback) +// { +// CheckTrialOrIntroductoryPriceEligibilityCallback = callback; +// _wrapper.CheckTrialOrIntroductoryPriceEligibilityAsync(products); +// } + +// /// +// /// +// /// Invalidates the cache for customer information. +// /// +// /// +// /// Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state. +// /// Refer to https://docs.revenuecat.com/docs/purchaserinfo#section-get-user-information +// /// for more information on using the cache properly. +// /// +// /// +// /// This is useful for cases where customer information might have been updated outside of the app, like if a +// /// promotional subscription is granted through the RevenueCat dashboard. +// /// +// public void InvalidateCustomerInfoCache() +// { +// _wrapper.InvalidateCustomerInfoCache(); +// } + +// /// +// /// iOS only. Displays a sheet that enables users to redeem subscription offer codes +// /// that you generated in App Store Connect. +// /// +// public void PresentCodeRedemptionSheet() +// { +// _wrapper.PresentCodeRedemptionSheet(); +// } + +// /// +// /// Callback for . +// /// +// /// The object if the request was successful, null otherwise. +// /// The error if the request was unsuccessful, null otherwise. +// public delegate void RecordPurchaseFunc(StoreTransaction transaction, Error error); + +// private RecordPurchaseFunc RecordPurchaseCallback { get; set; } + +// /// +// /// iOS only. Always returns an error on iOS < 15. +// /// Use this method only if you already have your own IAP implementation using StoreKit 2 and want to use +// /// RevenueCat's backend. If you are using StoreKit 1 for your implementation, you do not need this method. +// /// You only need to use this method with *new* purchases. Subscription updates are observed automatically. +// /// Important: This should only be used if you have set PurchasesAreCompletedBy to MyApp during SDK configuration. +// /// Important: You need to finish the transaction yourself after calling this method. +// /// +// /// Product ID that was just purchased. +// /// A completion block called when the purchase has been recorded, with either a success or an error. +// public void RecordPurchase(string productID, RecordPurchaseFunc callback) +// { +// RecordPurchaseCallback = callback; +// _wrapper.RecordPurchase(productID); +// } + +// /// +// /// iOS only. +// /// Set this property to true *only* when testing the ask-to-buy / SCA purchases flow. +// /// +// /// +// /// Whether to start simulating ask-to-buy flow in sandbox. +// public void SetSimulatesAskToBuyInSandbox(bool askToBuyEnabled) +// { +// _wrapper.SetSimulatesAskToBuyInSandbox(askToBuyEnabled); +// } + +// /// +// /// Subscriber attributes are useful for storing additional, structured information on a user. +// /// Since attributes are writable using a public key they should not be used for +// /// managing secure or sensitive information such as subscription status, coins, etc. +// /// +// /// Key names starting with "$" are reserved names used by RevenueCat. For a full list of key +// /// restrictions refer [to our guide](https://docs.revenuecat.com/docs/subscriber-attributes) +// /// +// /// Map of attributes by key. Set the value as an empty string to delete an attribute. +// public void SetAttributes(Dictionary attributes) +// { +// _wrapper.SetAttributes(attributes); +// } + +// /// +// /// +// /// Sets the subscriber attribute associated with the email address for the user. +// /// +// /// +// /// +// /// The email to set. +// /// Passing empty String or null will delete the subscriber attribute. +// /// +// /// +// public void SetEmail(string email) +// { +// _wrapper.SetEmail(email); +// } + +// /// +// /// Sets the subscriber attribute associated with the phone number for the user. +// /// +// /// +// /// +// /// The phone number to set. +// /// Passing empty String or null will delete the subscriber attribute. +// /// +// /// +// public void SetPhoneNumber(string phoneNumber) +// { +// _wrapper.SetPhoneNumber(phoneNumber); +// } + +// /// +// /// Sets the subscriber attribute associated with the display name for the user. +// /// +// /// +// /// +// /// The display name to set. +// /// Passing empty String or null will delete the subscriber attribute. +// /// +// /// +// public void SetDisplayName(string displayName) +// { +// _wrapper.SetDisplayName(displayName); +// } + +// /// +// /// Sets the subscriber attribute associated with the push token for the user. +// /// +// /// +// /// +// /// The push token to set. +// /// Passing empty String or null will delete the subscriber attribute. +// /// +// /// +// public void SetPushToken(string token) +// { +// _wrapper.SetPushToken(token); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Adjust Id for the user. +// * Required for the RevenueCat Adjust integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetAdjustID(string adjustID) +// { +// _wrapper.SetAdjustID(adjustID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Appsflyer Id for the user +// * Required for the RevenueCat Appsflyer integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetAppsflyerID(string appsflyerID) +// { +// _wrapper.SetAppsflyerID(appsflyerID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Facebook SDK Anonymous Id for the user +// * Required for the RevenueCat Facebook integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetFBAnonymousID(string fbAnonymousID) +// { +// _wrapper.SetFBAnonymousID(fbAnonymousID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the mParticle Id for the user +// * Required for the RevenueCat mParticle integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetMparticleID(string mparticleID) +// { +// _wrapper.SetMparticleID(mparticleID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the OneSignal Player Id for the user +// * Required for the RevenueCat OneSignal integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetOnesignalID(string onesignalID) +// { +// _wrapper.SetOnesignalID(onesignalID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Airship Channel Id for the user +// * Required for the RevenueCat Airship integration +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetAirshipChannelID(string airshipChannelID) +// { +// _wrapper.SetAirshipChannelID(airshipChannelID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the CleverTap ID for the user. +// * Required for the RevenueCat CleverTap integration. +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetCleverTapID(string cleverTapID) +// { +// _wrapper.SetCleverTapID(cleverTapID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Mixpanel Distinct ID for the user. +// * Optional for the RevenueCat Mixpanel integration. +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetMixpanelDistinctID(string mixpanelDistinctID) +// { +// _wrapper.SetMixpanelDistinctID(mixpanelDistinctID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the Firebase App Instance ID for the user. +// * Required for the RevenueCat Firebase integration. +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) +// { +// _wrapper.SetFirebaseAppInstanceID(firebaseAppInstanceID); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install media source for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetMediaSource(string mediaSource) +// { +// _wrapper.SetMediaSource(mediaSource); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install campaign for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetCampaign(string campaign) +// { +// _wrapper.SetCampaign(campaign); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install ad group for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetAdGroup(string adGroup) +// { +// _wrapper.SetAdGroup(adGroup); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install ad for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetAd(string ad) +// { +// _wrapper.SetAd(ad); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install keyword for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetKeyword(string keyword) +// { +// _wrapper.SetKeyword(keyword); +// } + +// /** +// * +// * Sets the subscriber attribute associated with the install creative for the user +// * +// * Empty String or null will delete the subscriber attribute. +// */ +// public void SetCreative(string creative) +// { +// _wrapper.SetCreative(creative); +// } + +// /** +// * +// * Automatically collect subscriber attributes associated with the device identifiers. +// * $idfa, $idfv, $ip on iOS +// * $gpsAdId, $androidId, $ip on Android +// * +// */ +// public void CollectDeviceIdentifiers() +// { +// _wrapper.CollectDeviceIdentifiers(); +// } + +// /// +// /// Callback function containing the result of CanMakePayments +// /// +// /// +// /// A bool value indicating whether billing +// /// is supported for the current user (meaning IN-APP purchases are supported), +// /// and, if provided, whether a list of specified BillingFeatures are supported. +// /// This will be false if there is an error +// /// An Error object or null if successful. +// public delegate void CanMakePaymentsFunc(bool canMakePayments, Error error); + +// private CanMakePaymentsFunc CanMakePaymentsCallback { get; set; } + +// /// +// /// Check if billing is supported for the current user (meaning IN-APP purchases are supported) +// /// and whether a list of specified feature types are supported. +// /// +// /// Note: BillingFeatures are only relevant to Google Play Android users. +// /// For other stores and platforms, BillingFeatures won't be checked. +// /// +// /// An array of BillingFeatures to check for support. +// /// If empty, no features will be checked. +// /// A callback receiving a bool for canMakePayments and potentially an Error +// public void CanMakePayments(BillingFeature[] features, CanMakePaymentsFunc callback) +// { +// CanMakePaymentsCallback = callback; +// _wrapper.CanMakePaymentsAsync(features ?? new BillingFeature[] { }); +// } + +// /// +// /// Check if billing is supported for the current user (meaning IN-APP purchases are supported) +// /// +// /// A callback receiving a bool for canMakePayments and potentially an Error +// public void CanMakePayments(CanMakePaymentsFunc callback) +// { +// CanMakePayments(new BillingFeature[] { }, callback); +// } + +// /// +// /// Callback function containing the result of GetAmazonLWAConsentStatus +// /// +// /// +// /// A bool value indicating whether user has given consent to +// /// Login with Amazon. +// /// +// /// An Error object or null if successful. +// public delegate void GetAmazonLWAConsentStatusFunc(bool hasConsented, Error error); + +// private GetAmazonLWAConsentStatusFunc GetAmazonLWAConsentStatusCallback { get; set; } + +// /// +// /// Get the Login with Amazon consent status for the current user. Used to implement one-click account creation +// /// using Quick Subscribe. +// /// +// /// For more information, check the documentation: +// /// https://rev.cat/amazon-quicksubscribe +// /// +// /// Note: This method only works for the Amazon Appstore. There is no Google equivalent at this time. +// /// Calling from a Google-configured app will always return False. +// /// +// /// A callback receiving a bool for hasConsented and potentially an Error +// public void GetAmazonLWAConsentStatus(GetAmazonLWAConsentStatusFunc callback) +// { +// GetAmazonLWAConsentStatusCallback = callback; +// _wrapper.GetAmazonLWAConsentStatusAsync(); +// } + +// /// +// /// Callback function containing the result of GetPromotionalOffer +// /// +// /// +// /// A Purchases.PromotionalOffer. It will be Null if platform is Android or +// /// the iOS version is not compatible with promotional offers +// /// An Error object or null if successful. +// public delegate void GetPromotionalOfferFunc(PromotionalOffer promotionalOffer, Error error); + +// private GetPromotionalOfferFunc GetPromotionalOfferCallback { get; set; } + +// /// +// /// iOS only. Use this function to retrieve the Purchases.PromotionalOffer for a given Purchases.Package. +// /// +// /// The Purchases.StoreProduct the user intends to purchase +// /// The Purchases.Discount to apply to the product. +// /// A callback receiving a Purchases.PromotionalOffer. Null is returned for Android and +// /// incompatible iOS versions. +// public void GetPromotionalOffer(StoreProduct storeProduct, Discount discount, GetPromotionalOfferFunc callback) +// { +// GetPromotionalOfferCallback = callback; +// _wrapper.GetPromotionalOfferAsync(storeProduct.Identifier, discount.Identifier); +// } + +// /// Displays the specified store in-app message types to the user if there are any available to be shown. +// /// - Important: This should only be used if you disabled these messages from showing automatically +// /// during SDK configuration setting ``shouldShowInAppMessagesAutomatically`` to ``false``. +// /// +// /// @param [messageTypes] The types of messages to show. +// public void ShowInAppMessages(InAppMessageType[] messageTypes = null) +// { +// _wrapper.ShowInAppMessages(messageTypes); +// } + +// public delegate void ParseAsWebPurchaseRedemptionFunc(WebPurchaseRedemption webPurchaseRedemption); + +// private ParseAsWebPurchaseRedemptionFunc ParseAsWebPurchaseRedemptionCallback { get; set; } + +// public void ParseAsWebPurchaseRedemption(string urlString, ParseAsWebPurchaseRedemptionFunc callback) +// { +// ParseAsWebPurchaseRedemptionCallback = callback; +// _wrapper.ParseAsWebPurchaseRedemptionAsync(urlString); +// } + +// public delegate void RedeemWebPurchaseFunc(WebPurchaseRedemptionResult result); + +// private RedeemWebPurchaseFunc RedeemWebPurchaseCallback { get; set; } + +// public void RedeemWebPurchase(WebPurchaseRedemption webPurchaseRedemption, RedeemWebPurchaseFunc callback) +// { +// RedeemWebPurchaseCallback = callback; +// _wrapper.RedeemWebPurchaseAsync(webPurchaseRedemption); +// } + +// public delegate void GetVirtualCurrenciesFunc(VirtualCurrencies virtualCurrencies, Error error); + +// private GetVirtualCurrenciesFunc GetVirtualCurrenciesCallback { get; set; } + +// /// +// /// Fetches the virtual currencies for the current user. +// /// +// public void GetVirtualCurrencies(GetVirtualCurrenciesFunc callback) +// { +// GetVirtualCurrenciesCallback = callback; +// _wrapper.GetVirtualCurrenciesAsync(); +// } + +// /// +// /// The currently cached VirtualCurrencies if one is available. +// /// +// /// +// /// This value will remain null until virtual currencies have been fetched at +// /// least once with GetVirtualCurrencies or an equivalent function. +// /// +// public VirtualCurrencies GetCachedVirtualCurrencies() +// { +// return _wrapper.GetCachedVirtualCurrencies(); +// } + +// /// +// /// Invalidates the cache for virtual currencies. +// /// +// /// +// /// +// /// This is useful for cases where a virtual currency's balance might have been updated +// /// outside of the app, like if you decreased a user's balance from the user spending a virtual currency, +// /// or if you increased the balance from your backend using the server APIs. +// /// +// public void InvalidateVirtualCurrenciesCache() +// { +// _wrapper.InvalidateVirtualCurrenciesCache(); +// } + +// public delegate void GetEligibleWinBackOffersForProductFunc(WinBackOffer[] winBackOffers, Error error); + +// private GetEligibleWinBackOffersForProductFunc GetEligibleWinBackOffersForProductCallback { get; set; } + +// /// +// /// Gets eligible win-back offers for a given store product. Only available on iOS 18.0+ with StoreKit 2. +// /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. +// /// +// /// The Purchases.StoreProduct to get win-back offers for +// /// A callback receiving an array of Purchases.WinBackOffer objects or an error if unsuccessful +// public void GetEligibleWinBackOffersForProduct(StoreProduct storeProduct, GetEligibleWinBackOffersForProductFunc callback) +// { +// GetEligibleWinBackOffersForProductCallback = callback; +// _wrapper.GetEligibleWinBackOffersForProductAsync(storeProduct); +// } + +// public delegate void GetEligibleWinBackOffersForPackageFunc(WinBackOffer[] winBackOffers, Error error); + +// private GetEligibleWinBackOffersForPackageFunc GetEligibleWinBackOffersForPackageCallback { get; set; } + +// /// +// /// Gets eligible win-back offers for a given package. Only available on iOS 18.0+ with StoreKit 2. +// /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. +// /// +// /// The Purchases.Package to get win-back offers for +// /// A callback receiving an array of Purchases.WinBackOffer objects or an error if unsuccessful +// public void GetEligibleWinBackOffersForPackage(Package package, GetEligibleWinBackOffersForPackageFunc callback) +// { +// GetEligibleWinBackOffersForPackageCallback = callback; +// _wrapper.GetEligibleWinBackOffersForPackageAsync(package); +// } + +// /// +// /// Purchase a product with a win-back offer. Only available on iOS 18.0+ with StoreKit 2. +// /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. +// /// +// /// The Purchases.StoreProduct to purchase +// /// The Purchases.WinBackOffer to use +// /// A callback receiving the product identifier, customer info, user cancellation, and an error if the purchase fails +// public void PurchaseProductWithWinBackOffer(StoreProduct storeProduct, WinBackOffer winBackOffer, MakePurchaseFunc callback) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchaseProductWithWinBackOfferAsync(storeProduct, winBackOffer); +// } + +// /// +// /// Purchase a package with a win-back offer. Only available on iOS 18.0+ with StoreKit 2. +// /// Returns an error if the platform is not iOS 18.0+ or if StoreKit 2 is not used. +// /// +// /// The Purchases.Package to purchase +// /// The Purchases.WinBackOffer to use +// /// A callback receiving the product identifier, customer info, user cancellation, and an error if the purchase fails +// public void PurchasePackageWithWinBackOffer(Package package, WinBackOffer winBackOffer, MakePurchaseFunc callback) +// { +// MakePurchaseCallback = callback; +// _wrapper.PurchasePackageWithWinBackOfferAsync(package, winBackOffer); +// } + +// private void _receiveStorefront(string storefrontJson) +// { +// Debug.Log("_receiveStorefront " + storefrontJson); + +// if (StorefrontCallback == null) +// { +// return; +// } + +// var callback = StorefrontCallback; +// StorefrontCallback = null; + +// if (storefrontJson is null or "{}") +// { +// callback(null); +// } +// else +// { +// var response = JObject.Parse(storefrontJson); +// var countryCode = response["countryCode"]; + +// if (countryCode == null) +// { +// Debug.LogError("StorefrontCallback received null countryCode"); +// callback(null); +// } +// else +// { +// var storefront = countryCode.ToObject(); +// callback(storefront); +// } +// } +// } + +// // ReSharper disable once UnusedMember.Local +// private void _receiveProducts(string productsJson) +// { +// Debug.Log("_receiveProducts " + productsJson); + +// if (ProductsCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(productsJson); +// var callback = ProductsCallback; +// ProductsCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var products = new List(); + +// foreach (var productResponse in response["products"]!) +// { +// var product = productResponse?.ToObject(); + +// if (product != null) +// { +// products.Add(product); +// } +// } + +// callback(products, null); +// } +// } + +// // ReSharper disable once UnusedMember.Local +// private void _getCustomerInfo(string customerInfoJson) +// { +// Debug.Log("_getCustomerInfo " + customerInfoJson); +// var callback = GetCustomerInfoCallback; +// GetCustomerInfoCallback = null; +// ReceiveCustomerInfoMethod(customerInfoJson, callback); +// } + +// // ReSharper disable once UnusedMember.Local +// private void _makePurchase(string makePurchaseResponseJson) +// { +// Debug.Log("_makePurchase " + makePurchaseResponseJson); + +// if (MakePurchaseCallback == null) +// { +// return; +// } + +// var callback = MakePurchaseCallback; +// MakePurchaseCallback = null; + +// var purchaseResult = JsonConvert.DeserializeObject(makePurchaseResponseJson); +// callback(purchaseResult); +// } + +// // ReSharper disable once UnusedMember.Local +// private void _receiveCustomerInfo(string customerInfoJson) +// { +// Debug.Log("_receiveCustomerInfo " + customerInfoJson); + +// //if (listener == null) +// //{ +// // return; +// //} + +// var response = JObject.Parse(customerInfoJson); +// var info = response["customerInfo"]?.ToObject(); + +// //if (info != null) +// //{ +// // listener.CustomerInfoReceived(info); +// //} +// } + + +// // ReSharper disable once UnusedMember.Local +// private void _restorePurchases(string customerInfoJson) +// { +// Debug.Log("_restorePurchases " + customerInfoJson); +// var callback = RestorePurchasesCallback; +// RestorePurchasesCallback = null; +// ReceiveCustomerInfoMethod(customerInfoJson, callback); +// } + +// private void _syncPurchases(string customerInfoJson) +// { +// Debug.Log("_syncPurchases " + customerInfoJson); +// var callback = SyncPurchasesCallback; +// SyncPurchasesCallback = null; +// ReceiveCustomerInfoMethod(customerInfoJson, callback); +// } + +// // ReSharper disable once UnusedMember.Local +// private void _logIn(string logInResultJson) +// { +// Debug.Log("_logIn " + logInResultJson); +// var callback = LogInCallback; +// LogInCallback = null; +// ReceiveLogInResultMethod(logInResultJson, callback); +// } + +// // ReSharper disable once UnusedMember.Local +// private void _logOut(string customerInfoJson) +// { +// Debug.Log("_logOut " + customerInfoJson); +// var callback = LogOutCallback; +// LogOutCallback = null; +// ReceiveCustomerInfoMethod(customerInfoJson, callback); +// } + +// // ReSharper disable once UnusedMember.Local +// private void _getOfferings(string offeringsJson) +// { +// Debug.Log("_getOfferings " + offeringsJson); + +// if (GetOfferingsCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(offeringsJson); +// var callback = GetOfferingsCallback; +// GetOfferingsCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var offerings = response["offerings"]?.ToObject(); +// callback(offerings, null); +// } +// } + +// // ReSharper disable once UnusedMember.Local +// private void _getCurrentOfferingForPlacement(string offeringJson) +// { +// if (GetCurrentOfferingForPlacementCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(offeringJson); +// var callback = GetCurrentOfferingForPlacementCallback; +// GetCurrentOfferingForPlacementCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var offering = response["offering"]?.ToObject(); +// callback(offering, null); +// } +// } + +// // ReSharper disable once UnusedMember.Local +// private void _syncAttributesAndOfferingsIfNeeded(string offeringsJson) +// { +// if (SyncAttributesAndOfferingsIfNeededCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(offeringsJson); +// var callback = SyncAttributesAndOfferingsIfNeededCallback; +// SyncAttributesAndOfferingsIfNeededCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var offerings = response["offerings"]?.ToObject(); +// callback(offerings, null); +// } +// } + +// private void _checkTrialOrIntroductoryPriceEligibility(string json) +// { +// Debug.Log("_checkTrialOrIntroductoryPriceEligibility " + json); + +// if (CheckTrialOrIntroductoryPriceEligibilityCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(json); +// var dictionary = new Dictionary(); + +// foreach (var (key, value) in response) +// { +// dictionary[key] = value?.ToObject(); +// } + +// var callback = CheckTrialOrIntroductoryPriceEligibilityCallback; +// CheckTrialOrIntroductoryPriceEligibilityCallback = null; +// callback(dictionary); + +// } + +// private void _recordPurchase(string json) +// { +// Debug.Log("_recordPurchase " + json); + +// if (RecordPurchaseCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(json); +// var callback = RecordPurchaseCallback; +// RecordPurchaseCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var transaction = response["transaction"]?.ToObject(); +// callback(transaction, null); +// } +// } + +// private void _canMakePayments(string canMakePaymentsJson) +// { +// Debug.Log("_canMakePayments" + canMakePaymentsJson); + +// if (CanMakePaymentsCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(canMakePaymentsJson); +// var callback = CanMakePaymentsCallback; +// CanMakePaymentsCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(false, error); +// } +// else +// { +// var canMakePayments = response["canMakePayments"]?.Value() ?? false; +// callback(canMakePayments, null); +// } +// } + +// private void _getAmazonLWAConsentStatus(string getAmazonLWAConsentStatusJson) +// { +// Debug.Log("_getAmazonLWAConsentStatus" + getAmazonLWAConsentStatusJson); + +// if (GetAmazonLWAConsentStatusCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(getAmazonLWAConsentStatusJson); +// var callback = GetAmazonLWAConsentStatusCallback; +// GetAmazonLWAConsentStatusCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(false, error); +// } +// else +// { +// var hasConsented = response["amazonLWAConsentStatus"]?.Value() ?? false; +// callback(hasConsented, null); +// } + +// } + +// private void _getPromotionalOffer(string getPromotionalOfferJson) +// { +// Debug.Log("_getPromotionalOffer" + getPromotionalOfferJson); + +// if (GetPromotionalOfferCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(getPromotionalOfferJson); +// var callback = GetPromotionalOfferCallback; +// GetPromotionalOfferCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var promotionalOffer = response.ToObject(); +// callback(promotionalOffer, null); +// } +// } + +// private void _parseAsWebPurchaseRedemption(string parseAsWebPurchaseRedemptionJSON) +// { +// Debug.Log("_parseAsWebPurchaseRedemption " + parseAsWebPurchaseRedemptionJSON); + +// if (ParseAsWebPurchaseRedemptionCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(parseAsWebPurchaseRedemptionJSON); +// var callback = ParseAsWebPurchaseRedemptionCallback; +// ParseAsWebPurchaseRedemptionCallback = null; + +// if (TryGetResponseError(response, out _)) +// { +// callback(null); +// } +// else +// { +// var webPurchaseRedemption = response["redemptionLink"]?.ToObject(); +// callback(webPurchaseRedemption); +// } +// } + +// private void _redeemWebPurchase(string redeemWebPurchaseJSON) +// { +// Debug.Log("_redeemWebPurchase " + redeemWebPurchaseJSON); + +// if (RedeemWebPurchaseCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(redeemWebPurchaseJSON); +// var callback = RedeemWebPurchaseCallback; +// RedeemWebPurchaseCallback = null; + +// if (TryGetResponseError(response, out _)) +// { +// callback(null); +// } +// else +// { +// var result = WebPurchaseRedemptionResult.FromJson(response); +// callback(result); +// } +// } + +// private void _getVirtualCurrencies(string getVirtualCurrenciesJson) +// { +// Debug.Log("_getVirtualCurrencies " + getVirtualCurrenciesJson); + +// if (GetVirtualCurrenciesCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(getVirtualCurrenciesJson); +// var callback = GetVirtualCurrenciesCallback; +// GetVirtualCurrenciesCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var virtualCurrencies = response?.ToObject(); +// callback(virtualCurrencies, null); +// } +// } + +// private void _getEligibleWinBackOffersForProduct(string eligibleWinBackOffersJson) +// { +// Debug.Log("_getEligibleWinBackOffersForProduct " + eligibleWinBackOffersJson); + +// if (GetEligibleWinBackOffersForProductCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(eligibleWinBackOffersJson); +// var callback = GetEligibleWinBackOffersForProductCallback; +// GetEligibleWinBackOffersForProductCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, response["error"]?.ToObject()); +// } +// else +// { +// var winBackOffers = new List(); + +// foreach (var offerResponse in response["eligibleWinBackOffers"]!) +// { +// var offer = offerResponse?.ToObject(); +// winBackOffers.Add(offer); +// } + +// callback(winBackOffers.ToArray(), null); +// } +// } + +// private void _getEligibleWinBackOffersForPackage(string eligibleWinBackOffersJson) +// { +// Debug.Log("_getEligibleWinBackOffersForPackage " + eligibleWinBackOffersJson); + +// if (GetEligibleWinBackOffersForPackageCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(eligibleWinBackOffersJson); +// var callback = GetEligibleWinBackOffersForPackageCallback; +// GetEligibleWinBackOffersForPackageCallback = null; + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var winBackOffers = new List(); + +// foreach (var offerResponse in response["eligibleWinBackOffers"]!) +// { +// var offer = offerResponse?.ToObject(); +// winBackOffers.Add(offer); +// } + +// callback(winBackOffers.ToArray(), null); +// } + +// } + +// private void _purchaseProductWithWinBackOffer(string purchaseProductWithWinBackOfferJson) +// { +// Debug.Log("_purchaseProductWithWinBackOffer " + purchaseProductWithWinBackOfferJson); + +// if (MakePurchaseCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(purchaseProductWithWinBackOfferJson); +// var callback = MakePurchaseCallback; +// MakePurchaseCallback = null; + +// callback(response?.ToObject()); +// } + +// private void _purchasePackageWithWinBackOffer(string purchasePackageWithWinBackOfferJson) +// { +// Debug.Log("_purchasePackageWithWinBackOffer " + purchasePackageWithWinBackOfferJson); + +// if (MakePurchaseCallback == null) +// { +// return; +// } + +// var response = JObject.Parse(purchasePackageWithWinBackOfferJson); +// var callback = MakePurchaseCallback; +// MakePurchaseCallback = null; + +// callback(response.ToObject()); +// } + +// private static void ReceiveCustomerInfoMethod(string arguments, CustomerInfoFunc callback) +// { +// if (callback == null) +// { +// return; +// } + +// var response = JObject.Parse(arguments); + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, error); +// } +// else +// { +// var info = response["customerInfo"]?.ToObject(); +// callback(info, null); +// } +// } + +// private static void ReceiveLogInResultMethod(string arguments, LogInFunc callback) +// { +// if (callback == null) +// { +// return; +// } + +// var response = JObject.Parse(arguments); + +// if (TryGetResponseError(response, out var error)) +// { +// callback(null, false, error); +// } +// else +// { +// var info = response["customerInfo"]!.ToObject(); +// var created = response["created"]!.ToObject(); +// callback(info, created, null); +// } +// } + +// private static bool TryGetResponseError(JObject response, out Error error) +// { +// error = null; + +// if (response.TryGetValue("error", out var e)) +// { +// error = e.ToObject(); +// } + +// return error != null; +// } +//} diff --git a/RevenueCat/Scripts/PurchasesAreCompletedBy.cs b/RevenueCat/Scripts/PurchasesAreCompletedBy.cs index 4bd10b44..8428c2c4 100644 --- a/RevenueCat/Scripts/PurchasesAreCompletedBy.cs +++ b/RevenueCat/Scripts/PurchasesAreCompletedBy.cs @@ -1,31 +1,13 @@ -using System; -using System.ComponentModel; - -public partial class Purchases +namespace RevenueCat { public enum PurchasesAreCompletedBy { /// RevenueCat will automatically acknowledge verified purchases. No action is required by you. - [Description("REVENUECAT")] - RevenueCat, - + REVENUECAT, /// RevenueCat will **not** automatically acknowledge any purchases. You will have to do so manually. /// **Note:** failing to acknowledge a purchase within 3 days will lead to Google Play automatically issuing a /// refund to the user. /// For more info, see [revenuecat.com](https://docs.revenuecat.com/docs/observer-mode#option-2-client-side). - [Description("MY_APP")] - MyApp, - } -} - -internal static class PurchasesAreCompletedByExtensions -{ - internal static string Name(this Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy) - { - var type = purchasesAreCompletedBy.GetType(); - var memInfo = type.GetMember(purchasesAreCompletedBy.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); - var stringValue = ((DescriptionAttribute) attributes[0]).Description; - return stringValue; + MY_APP, } } diff --git a/RevenueCat/Scripts/PurchasesBehaviour.cs b/RevenueCat/Scripts/PurchasesBehaviour.cs new file mode 100644 index 00000000..0b5a240e --- /dev/null +++ b/RevenueCat/Scripts/PurchasesBehaviour.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +namespace RevenueCat +{ + public class PurchasesBehaviour : MonoBehaviour + { + } +} diff --git a/RevenueCat/Scripts/PurchasesBehaviour.cs.meta b/RevenueCat/Scripts/PurchasesBehaviour.cs.meta new file mode 100644 index 00000000..258178ed --- /dev/null +++ b/RevenueCat/Scripts/PurchasesBehaviour.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b620148e07db734abb4cb57a5f62782 \ No newline at end of file diff --git a/RevenueCat/Scripts/PurchasesConfiguration.cs b/RevenueCat/Scripts/PurchasesConfiguration.cs index 642b59f5..db288b45 100644 --- a/RevenueCat/Scripts/PurchasesConfiguration.cs +++ b/RevenueCat/Scripts/PurchasesConfiguration.cs @@ -1,160 +1,91 @@ using System; +using UnityEngine; -public partial class Purchases +namespace RevenueCat { /// /// Class used to configure the SDK programmatically. - /// Create a configuration builder, set its properties, then call `Build` to obtain the configuration. - /// Lastly, call Purchases.Configure and with the obtained PurchasesConfiguration object. /// - /// - /// - /// For example: - /// - /// Purchases.PurchasesConfiguration.Builder builder = Purchases.PurchasesConfiguration.Builder.Init("api_key"); - /// Purchases.PurchasesConfiguration purchasesConfiguration = - /// builder - /// .SetAppUserId(appUserId) - /// .Build(); - /// purchases.Configure(purchasesConfiguration); - /// - /// - /// - public class PurchasesConfiguration + public sealed class PurchasesConfiguration { - public readonly string ApiKey; - public readonly string AppUserId; - public readonly bool ObserverMode; - public readonly PurchasesAreCompletedBy PurchasesAreCompletedBy; - public readonly string UserDefaultsSuiteName; - public readonly bool UseAmazon; - public readonly DangerousSettings DangerousSettings; - public readonly StoreKitVersion StoreKitVersion; - public readonly bool ShouldShowInAppMessagesAutomatically; - public readonly EntitlementVerificationMode EntitlementVerificationMode; - public readonly bool PendingTransactionsForPrepaidPlansEnabled; - - private PurchasesConfiguration(string apiKey, string appUserId, PurchasesAreCompletedBy purchasesAreCompletedBy, string userDefaultsSuiteName, - bool useAmazon, DangerousSettings dangerousSettings, StoreKitVersion storeKitVersion, bool shouldShowInAppMessagesAutomatically, - EntitlementVerificationMode entitlementVerificationMode, bool pendingTransactionsForPrepaidPlansEnabled) + public string ApiKey { get; } + + public string AppUserId { get; } + + public PurchasesAreCompletedBy PurchasesAreCompletedBy { get; } + + public string UserDefaultsSuiteName { get; } + + public bool UseAmazon { get; } + + public DangerousSettings DangerousSettings { get; } + + public StoreKitVersion StoreKitVersion { get; } + + public bool ShouldShowInAppMessagesAutomatically { get; } + + public EntitlementVerificationMode EntitlementVerificationMode { get; } + + public bool PendingTransactionsForPrepaidPlansEnabled { get; } + + public PurchasesConfiguration( + string apiKey, + string appUserId = null, + PurchasesAreCompletedBy purchasesAreCompletedBy = PurchasesAreCompletedBy.REVENUECAT, + string userDefaultsSuiteName = null, + bool useAmazon = false, + DangerousSettings dangerousSettings = null, + StoreKitVersion storeKitVersion = StoreKitVersion.Default, + bool shouldShowInAppMessagesAutomatically = true, + EntitlementVerificationMode entitlementVerificationMode = EntitlementVerificationMode.DISABLED, + bool pendingTransactionsForPrepaidPlansEnabled = false) { + if (string.IsNullOrWhiteSpace(apiKey)) + { + throw new ArgumentException("API key cannot be null or empty.", nameof(apiKey)); + } + ApiKey = apiKey; AppUserId = appUserId; PurchasesAreCompletedBy = purchasesAreCompletedBy; UserDefaultsSuiteName = userDefaultsSuiteName; UseAmazon = useAmazon; - DangerousSettings = dangerousSettings; + DangerousSettings = dangerousSettings ?? new DangerousSettings(); StoreKitVersion = storeKitVersion; ShouldShowInAppMessagesAutomatically = shouldShowInAppMessagesAutomatically; EntitlementVerificationMode = entitlementVerificationMode; PendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled; } - /// - /// Use this object to create a PurchasesConfiguration object that can be used to configure - /// the SDK programmatically. - /// Create a configuration builder, set its properties, then call `Build` to obtain the configuration. - /// Lastly, call Purchases.Configure and with the obtained PurchasesConfiguration object. - /// - /// - /// - /// For example: - /// - /// Purchases.PurchasesConfiguration.Builder builder = Purchases.PurchasesConfiguration.Builder.Init("api_key"); - /// Purchases.PurchasesConfiguration purchasesConfiguration = - /// builder - /// .SetAppUserId(appUserId) - /// .Build(); - /// purchases.Configure(purchasesConfiguration); - /// - /// - /// - public class Builder + public PurchasesConfiguration(string appUserId = null) { - private readonly string _apiKey; - private string _appUserId; - private PurchasesAreCompletedBy _purchasesAreCompletedBy; - private string _userDefaultsSuiteName; - private bool _useAmazon; - private DangerousSettings _dangerousSettings; - private StoreKitVersion _storeKitVersion; - private bool _shouldShowInAppMessagesAutomatically; - private EntitlementVerificationMode _entitlementVerificationMode; - private bool _pendingTransactionsForPrepaidPlansEnabled; - - private Builder(string apiKey) - { - _apiKey = apiKey; - } + var config = RevenueCatConfig.Instance; - public static Builder Init(string apiKey) + if (Application.platform == RuntimePlatform.IPhonePlayer || + Application.platform == RuntimePlatform.OSXPlayer) { - return new Builder(apiKey); - } + ApiKey = config.RevenueCatApiKeyApple; - public PurchasesConfiguration Build() - { - _dangerousSettings = _dangerousSettings ?? new DangerousSettings(false); - return new PurchasesConfiguration(_apiKey, _appUserId, _purchasesAreCompletedBy, _userDefaultsSuiteName, - _useAmazon, _dangerousSettings, _storeKitVersion, _shouldShowInAppMessagesAutomatically, - _entitlementVerificationMode, _pendingTransactionsForPrepaidPlansEnabled); + if (config.PurchasesAreCompletedBy == PurchasesAreCompletedBy.MY_APP && config.StoreKitVersion == StoreKitVersion.Default) + { + throw new InvalidOperationException("You must set a StoreKit version if you are setting PurchasesAreCompletedBy to MyApp"); + } } - - public Builder SetAppUserId(string appUserId) + else if (Application.platform == RuntimePlatform.Android || + Utilities.IsAndroidEmulator()) { - _appUserId = appUserId; - return this; - } - - public Builder SetPurchasesAreCompletedBy(PurchasesAreCompletedBy purchasesAreCompletedBy, StoreKitVersion storeKitVersion) - { - _purchasesAreCompletedBy = purchasesAreCompletedBy; - _storeKitVersion = storeKitVersion; - return this; - } - - public Builder SetUserDefaultsSuiteName(string userDefaultsSuiteName) - { - _userDefaultsSuiteName = userDefaultsSuiteName; - return this; - } - - public Builder SetUseAmazon(bool useAmazon) - { - _useAmazon = useAmazon; - return this; - } - - public Builder SetDangerousSettings(DangerousSettings dangerousSettings) - { - _dangerousSettings = dangerousSettings; - return this; - } - - public Builder SetStoreKitVersion(StoreKitVersion storeKitVersion) - { - _storeKitVersion = storeKitVersion; - return this; - } - - public Builder SetShouldShowInAppMessagesAutomatically(bool shouldShowInAppMessagesAutomatically) - { - _shouldShowInAppMessagesAutomatically = shouldShowInAppMessagesAutomatically; - return this; - } - - public Builder SetEntitlementVerificationMode(EntitlementVerificationMode entitlementVerificationMode) - { - _entitlementVerificationMode = entitlementVerificationMode; - return this; - } - - public Builder SetPendingTransactionsForPrepaidPlansEnabled(bool pendingTransactionsForPrepaidPlansEnabled) - { - _pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled; - return this; + ApiKey = config.UseAmazon ? config.RevenueCatApiKeyAmazon : config.RevenueCatApiKeyGoogle; } + AppUserId = appUserId; + PurchasesAreCompletedBy = config.PurchasesAreCompletedBy; + UserDefaultsSuiteName = config.UserDefaultsSuiteName; + UseAmazon = config.UseAmazon; + DangerousSettings = new DangerousSettings(config.AutoSyncPurchases); + StoreKitVersion = config.StoreKitVersion; + ShouldShowInAppMessagesAutomatically = config.ShouldShowInAppMessagesAutomatically; + EntitlementVerificationMode = config.EntitlementVerificationMode; + PendingTransactionsForPrepaidPlansEnabled = config.PendingTransactionsForPrepaidPlansEnabled; } public override string ToString() diff --git a/RevenueCat/Scripts/PurchasesSdk.cs b/RevenueCat/Scripts/PurchasesSdk.cs new file mode 100644 index 00000000..648317d0 --- /dev/null +++ b/RevenueCat/Scripts/PurchasesSdk.cs @@ -0,0 +1,597 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace RevenueCat +{ + /// + /// + /// + public static class PurchasesSdk + { + #region Instance + + internal static IPurchasesWrapper Instance => _instance ??= CreatePlatformWrapper(); + private static IPurchasesWrapper _instance; + + private static IPurchasesWrapper CreatePlatformWrapper() + { + // ReSharper disable once JoinDeclarationAndInitializer + IPurchasesWrapper wrapper; +#if UNITY_IOS && !UNITY_EDITOR + wrapper = new IOSPurchasesWrapper(); +#elif UNITY_ANDROID && !UNITY_EDITOR + wrapper = new AndroidPurchasesWrapper(); +#else + // Editor/other: unsupported implementation + wrapper = new PurchasesWrapperNoop(); +#endif + return wrapper; + } + + private static void Reset() + { + _instance?.Dispose(); + _instance = null; + Configuration = null; + } + + #endregion Instance + + #region Concurrency + + internal static SynchronizationContext MainThreadSynchronizationContext { get; private set; } + +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] +#else + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] +#endif + internal static void SetMainThreadSynchronizationContext() + { + MainThreadSynchronizationContext = SynchronizationContext.Current; + } + + #endregion Concurrency + + internal static JsonSerializerSettings JsonSerializerSettings { get; } = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Converters = new List + { + new StringEnumConverter(new DefaultNamingStrategy()), + } + }; + + internal static JsonSerializer JsonSerializer { get; } = JsonSerializer.Create(JsonSerializerSettings); + + public static PurchasesConfiguration Configuration { get; private set; } + + /// + /// + /// + public static event Action OnCustomerInfoUpdated + { + add => Instance.OnCustomerInfoUpdated += value; + remove => Instance.OnCustomerInfoUpdated -= value; + } + + /// + /// + /// + public static event Action OnLogMessage + { + add => Instance.OnLogMessage += value; + remove => Instance.OnLogMessage -= value; + } + + /// + /// + /// + /// + public static void Configure(PurchasesConfiguration configuration) + { + configuration ??= new PurchasesConfiguration(); + + if (Configuration != null) + { + Reset(); + } + + Configuration = configuration; + Instance.Configure(Configuration); + } + + /// + /// + /// + /// + public static void Configure(string userId) + { + if (Configuration != null) + { + Reset(); + } + + Configuration = new PurchasesConfiguration(userId); + Instance.Configure(Configuration); + } + + /// + /// Fetches the Storefront for the customer's current store account. + /// If there is an error, the callback will be called with a null value. + /// + /// Optional, . + /// . + public static Task GetStorefrontAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetStorefrontAsync(cancellationToken); + } + + /// + /// Fetches the StoreProducts for your IAPs for given productIdentifiers. + /// This method is called automatically with products pre-configured through Unity IDE UI. + /// You can optionally call this if you want to fetch more products. + /// + /// + /// A set of product identifiers for in-app purchases setup via AppStoreConnect. + /// This should be either hard coded in your application, from a file, or from a custom endpoint if + /// you want to be able to deploy new IAPs without an app update. + /// + /// Android only. The type of product to purchase. + /// Optional, . + /// A list of s. + /// + /// completion may be called without s that you are expecting. + /// This is usually caused by iTunesConnect configuration errors. + /// Ensure your IAPs have the “Ready to Submit” status in iTunesConnect. + /// Also ensure that you have an active developer program subscription, and you have signed the + /// latest paid application agreements.
+ /// If you’re having trouble, + ///
+ public static Task> GetProductsAsync( + string[] productIdentifiers, + string type = "subs", + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetProductsAsync(productIdentifiers, type, cancellationToken); + } + + public static Task PurchaseProductAsync( + string productIdentifier, + string type = "subs", + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + string presentedOfferingIdentifier = null, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.PurchaseProductAsync( + productIdentifier, + type, + oldSku, + prorationMode, + googleIsPersonalizedPrice, + presentedOfferingIdentifier, + discount, + cancellationToken); + } + + public static Task PurchaseDiscountedProductAsync(string productIdentifier, PromotionalOffer discount, CancellationToken cancellationToken) + { + return PurchaseProductAsync(productIdentifier, discount: discount, cancellationToken: cancellationToken); + } + + public static Task PurchasePackageAsync( + Package packageToPurchase, + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.PurchasePackageAsync( + packageToPurchase, + oldSku, + prorationMode, + googleIsPersonalizedPrice, + discount, + cancellationToken); + } + + public static Task PurchaseDiscountedPackage(Package packageToPurchase, PromotionalOffer discount, CancellationToken cancellationToken = default) + { + return PurchasePackageAsync(packageToPurchase, discount: discount, cancellationToken: cancellationToken); + } + + public static Task PurchaseSubscriptionOptionAsync( + SubscriptionOption subscriptionOption, + GoogleProductChangeInfo googleProductChangeInfo = null, + bool googleIsPersonalizedPrice = false, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.PurchaseSubscriptionOptionAsync( + subscriptionOption, + googleProductChangeInfo, + googleIsPersonalizedPrice, + cancellationToken); + } + + public static Task RestorePurchasesAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.RestorePurchasesAsync(cancellationToken); + } + + public static Task LogInAsync(string appUserId, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + + if (string.IsNullOrWhiteSpace(appUserId)) + { + throw new ArgumentException("appUserId cannot be null or empty.", nameof(appUserId)); + } + + return Instance.LogInAsync(appUserId, cancellationToken); + } + + public static Task LogOutAsync() + { + ValidateConfiguration(); + return Instance.LogOutAsync(); + } + + [Obsolete("Configure behavior through the RevenueCat dashboard instead. " + + "If you have configured the 'Legacy' restore behavior in the " + + "RevenueCat Dashboard and are currently setting this to true, " + + "keep this setting active.")] + public static void SetAllowSharingStoreAccount(bool allow) + { + ValidateConfiguration(); + Instance.SetAllowSharingStoreAccount(allow); + } + + public static Task GetOfferingsAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetOfferingsAsync(cancellationToken); + } + + public static Task GetCurrentOfferingForPlacementAsync(string placementIdentifier, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetCurrentOfferingForPlacementAsync(placementIdentifier, cancellationToken); + } + + public static Task SyncAttributesAndOfferingsIfNeededAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.SyncAttributesAndOfferingsIfNeededAsync(cancellationToken); + } + + public static void SyncAmazonPurchase(string productId, string receiptId, string amazonUserId, string isoCurrencyCode, double price) + { + ValidateConfiguration(); + Instance.SyncAmazonPurchase(productId, receiptId, amazonUserId, isoCurrencyCode, price); + } + + public static Task GetAmazonLWAConsentStatusAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetAmazonLWAConsentStatusAsync(cancellationToken); + } + + public static void SetLogLevel(LogLevel level) + { + ValidateConfiguration(); + Instance.SetLogLevel(level); + } + + public static void SetDebugLogsEnabled(bool enabled) + { + ValidateConfiguration(); + Instance.SetDebugLogsEnabled(enabled); + } + + public static void SetProxyURL(string proxyURL) + { + ValidateConfiguration(); + Instance.SetProxyURL(proxyURL); + } + + public static string GetAppUserId() + { + ValidateConfiguration(); + return Instance.GetAppUserId(); + } + + public static Task GetCustomerInfoAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetCustomerInfoAsync(cancellationToken); + } + + public static Task SyncPurchasesAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.SyncPurchasesAsync(cancellationToken); + } + + public static void EnableAdServicesAttributionTokenCollection() + { + ValidateConfiguration(); + Instance.EnableAdServicesAttributionTokenCollection(); + } + + public static bool IsAnonymous() + { + ValidateConfiguration(); + return Instance.IsAnonymous(); + } + + public static bool IsConfigured() + { + ValidateConfiguration(); + return Instance.IsConfigured(); + } + + public static Task> CheckTrialOrIntroductoryPriceEligibilityAsync( + string[] productIdentifiers, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.CheckTrialOrIntroductoryPriceEligibilityAsync(productIdentifiers, cancellationToken); + } + + public static void InvalidateCustomerInfoCache() + { + ValidateConfiguration(); + Instance.InvalidateCustomerInfoCache(); + } + + public static void PresentCodeRedemptionSheet() + { + ValidateConfiguration(); + Instance.PresentCodeRedemptionSheet(); + } + + public static void RecordPurchase(string productId) + { + ValidateConfiguration(); + Instance.RecordPurchase(productId); + } + + public static void SetSimulatesAskToBuyInSandbox(bool enabled) + { + ValidateConfiguration(); + Instance.SetSimulatesAskToBuyInSandbox(enabled); + } + + public static void SetAttributes(Dictionary attributes) + { + ValidateConfiguration(); + Instance.SetAttributes(attributes); + } + + public static void SetEmail(string email) + { + ValidateConfiguration(); + Instance.SetEmail(email); + } + + public static void SetPhoneNumber(string phoneNumber) + { + ValidateConfiguration(); + Instance.SetPhoneNumber(phoneNumber); + } + + public static void SetDisplayName(string displayName) + { + ValidateConfiguration(); + Instance.SetDisplayName(displayName); + } + + public static void SetPushToken(string token) + { + ValidateConfiguration(); + Instance.SetPushToken(token); + } + + public static void SetAdjustID(string adjustID) + { + ValidateConfiguration(); + Instance.SetAdjustID(adjustID); + } + + public static void SetAppsflyerID(string appsFlyerID) + { + ValidateConfiguration(); + Instance.SetAppsflyerID(appsFlyerID); + } + + public static void SetFBAnonymousID(string fbAnonymousID) + { + ValidateConfiguration(); + Instance.SetFBAnonymousID(fbAnonymousID); + } + + public static void SetMparticleID(string mparticleID) + { + ValidateConfiguration(); + Instance.SetMparticleID(mparticleID); + } + + public static void SetOnesignalID(string onesignalID) + { + ValidateConfiguration(); + Instance.SetOnesignalID(onesignalID); + } + + public static void SetAirshipChannelID(string airshipChannelID) + { + ValidateConfiguration(); + Instance.SetAirshipChannelID(airshipChannelID); + } + + public static void SetCleverTapID(string cleverTapID) + { + ValidateConfiguration(); + Instance.SetCleverTapID(cleverTapID); + } + + public static void SetMixpanelDistinctID(string mixpanelDistinctID) + { + ValidateConfiguration(); + Instance.SetMixpanelDistinctID(mixpanelDistinctID); + } + + public static void SetFirebaseAppInstanceID(string firebaseAppInstanceID) + { + ValidateConfiguration(); + Instance.SetFirebaseAppInstanceID(firebaseAppInstanceID); + } + + public static void SetMediaSource(string mediaSource) + { + ValidateConfiguration(); + Instance.SetMediaSource(mediaSource); + } + + public static void SetCampaign(string campaign) + { + ValidateConfiguration(); + Instance.SetCampaign(campaign); + } + + public static void SetAdGroup(string adGroup) + { + ValidateConfiguration(); + Instance.SetAdGroup(adGroup); + } + + public static void SetAd(string ad) + { + ValidateConfiguration(); + Instance.SetAd(ad); + } + + public static void SetKeyword(string keyword) + { + ValidateConfiguration(); + Instance.SetKeyword(keyword); + } + + public static void SetCreative(string creative) + { + ValidateConfiguration(); + Instance.SetCreative(creative); + } + + public static void CollectDeviceIdentifiers() + { + ValidateConfiguration(); + Instance.CollectDeviceIdentifiers(); + } + + public static Task CanMakePaymentsAsync( + BillingFeature[] features, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.CanMakePaymentsAsync(features, cancellationToken); + } + + public static Task GetPromotionalOfferAsync( + string productIdentifier, + string discountIdentifier, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetPromotionalOfferAsync(productIdentifier, discountIdentifier, cancellationToken); + } + + public static void ShowInAppMessages(InAppMessageType[] messageTypes) + { + ValidateConfiguration(); + Instance.ShowInAppMessages(messageTypes); + } + + public static Task ParseAsWebPurchaseRedemptionAsync( + string urlString, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.ParseAsWebPurchaseRedemptionAsync(urlString, cancellationToken); + } + + public static Task RedeemWebPurchaseAsync( + WebPurchaseRedemption webPurchaseRedemption, + CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.RedeemWebPurchaseAsync(webPurchaseRedemption, cancellationToken); + } + + public static Task GetVirtualCurrenciesAsync(CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetVirtualCurrenciesAsync(cancellationToken); + } + + public static VirtualCurrencies GetCachedVirtualCurrencies() + { + ValidateConfiguration(); + return Instance.GetCachedVirtualCurrencies(); + } + + public static void InvalidateVirtualCurrenciesCache() + { + ValidateConfiguration(); + Instance.InvalidateVirtualCurrenciesCache(); + } + + public static Task> GetEligibleWinBackOffersForProductAsync(StoreProduct storeProduct, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetEligibleWinBackOffersForProductAsync(storeProduct, cancellationToken); + } + + public static Task> GetEligibleWinBackOffersForPackageAsync(Package package, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.GetEligibleWinBackOffersForPackageAsync(package, cancellationToken); + } + + public static Task PurchaseProductWithWinBackOfferAsync(StoreProduct storeProduct, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.PurchaseProductWithWinBackOfferAsync(storeProduct, winBackOffer, cancellationToken); + } + + public static Task PurchasePackageWithWinBackOfferAsync(Package package, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + ValidateConfiguration(); + return Instance.PurchasePackageWithWinBackOfferAsync(package, winBackOffer, cancellationToken); + } + + private static void ValidateConfiguration() + { + if (Configuration == null) + { + throw new InvalidOperationException("Configure must be called before using the SDK."); + } + } + } +} diff --git a/RevenueCat/Scripts/PurchasesSdk.cs.meta b/RevenueCat/Scripts/PurchasesSdk.cs.meta new file mode 100644 index 00000000..45947d72 --- /dev/null +++ b/RevenueCat/Scripts/PurchasesSdk.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3608ca05a1305bf4abc805ee2ccb7452 \ No newline at end of file diff --git a/RevenueCat/Scripts/PurchasesWrapper.cs b/RevenueCat/Scripts/PurchasesWrapper.cs index d073e438..734e33d9 100644 --- a/RevenueCat/Scripts/PurchasesWrapper.cs +++ b/RevenueCat/Scripts/PurchasesWrapper.cs @@ -1,90 +1,215 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; -public interface IPurchasesWrapper +namespace RevenueCat { - void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, bool pendingTransactionsForPrepaidPlansEnabled); - - void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, Purchases.EntitlementVerificationMode entitlementVerificationMode, - bool pendingTransactionsForPrepaidPlansEnabled); - - void GetStorefront(); - void GetProducts(string[] productIdentifiers, string type = "subs"); - - void PurchaseProduct(string productIdentifier, string type = "subs", string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, string presentedOfferingIdentifier = null, - Purchases.PromotionalOffer discount = null); - - void PurchasePackage(Purchases.Package packageToPurchase, string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, Purchases.PromotionalOffer discount = null); - - void PurchaseSubscriptionOption(Purchases.SubscriptionOption subscriptionOption, - Purchases.GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false); - - void RestorePurchases(); - void LogIn(string appUserId); - void LogOut(); - void SetAllowSharingStoreAccount(bool allow); - void SetDebugLogsEnabled(bool enabled); - void SetLogLevel(Purchases.LogLevel level); - void SetLogHandler(); - void SetProxyURL(string proxyURL); - string GetAppUserId(); - void GetCustomerInfo(); - void GetOfferings(); - void GetCurrentOfferingForPlacement(string placementIdentifier); - void SyncAttributesAndOfferingsIfNeeded(); - void SyncPurchases(); - - void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, string isoCurrencyCode, - double price); - - void GetAmazonLWAConsentStatus(); - void EnableAdServicesAttributionTokenCollection(); - bool IsAnonymous(); - bool IsConfigured(); - void CheckTrialOrIntroductoryPriceEligibility(string[] productIdentifiers); - void InvalidateCustomerInfoCache(); - void PresentCodeRedemptionSheet(); - void RecordPurchase(string productID); - void SetSimulatesAskToBuyInSandbox(bool enabled); - void SetAttributes(string attributesJson); - void SetEmail(string email); - void SetPhoneNumber(string phoneNumber); - void SetDisplayName(string displayName); - void SetPushToken(string token); - void SetAdjustID(string adjustID); - void SetAppsflyerID(string appsflyerID); - void SetFBAnonymousID(string fbAnonymousID); - void SetMparticleID(string mparticleID); - void SetOnesignalID(string onesignalID); - void SetAirshipChannelID(string airshipChannelID); - void SetCleverTapID(string cleverTapID); - void SetMixpanelDistinctID(string mixpanelDistinctID); - void SetFirebaseAppInstanceID(string firebaseAppInstanceID); - void SetMediaSource(string mediaSource); - void SetCampaign(string campaign); - void SetAdGroup(string adGroup); - void SetAd(string ad); - void SetKeyword(string keyword); - void SetCreative(string creative); - void CollectDeviceIdentifiers(); - void CanMakePayments(Purchases.BillingFeature[] features); - void GetPromotionalOffer(string productIdentifier, string discountIdentifier); - void ShowInAppMessages(Purchases.InAppMessageType[] messageTypes); - void ParseAsWebPurchaseRedemption(string urlString); - void RedeemWebPurchase(Purchases.WebPurchaseRedemption webPurchaseRedemption); - void GetVirtualCurrencies(); - string GetCachedVirtualCurrencies(); - void InvalidateVirtualCurrenciesCache(); - void GetEligibleWinBackOffersForProduct(Purchases.StoreProduct storeProduct); - void GetEligibleWinBackOffersForPackage(Purchases.Package package); - void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer); - void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer); -} + internal interface IPurchasesWrapper : IDisposable + { + [SuppressMessage("ReSharper", "EventNeverInvoked.Global")] + event Action OnCustomerInfoUpdated; + + [SuppressMessage("ReSharper", "EventNeverInvoked.Global")] + event Action OnLogMessage; + + void Configure(PurchasesConfiguration configuration); + + Task GetStorefrontAsync(CancellationToken cancellationToken = default); + + Task> GetProductsAsync( + string[] productIdentifiers, + string type = "subs", + CancellationToken cancellationToken = default); + + /// + /// Initiates a purchase of a . + /// + /// + /// Call this method when a user has decided to purchase a product. + /// Only call this in direct response to user input. + /// Use this function if you are not using the system to purchase a . + /// If you are using the system, use instead. + /// + Task PurchaseProductAsync( + string productIdentifier, + string type = "subs", + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + string offeringIdentifier = null, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default); + + /// + /// Initiates a purchase of a . + /// From here the SDK will handle the purchase with StoreKit and call the PurchaseCompletedBlock. + /// + /// + /// Call this method when a user has decided to purchase a product. + /// Only call this in direct response to user input.
+ /// Note: You do not need to finish the transaction yourself in the completion callback, RevenueCat will + /// handle this for you. + ///
+ Task PurchasePackageAsync( + Package packageToPurchase, + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default); + + Task PurchaseSubscriptionOptionAsync( + SubscriptionOption subscriptionOption, + GoogleProductChangeInfo googleProductChangeInfo = null, + bool googleIsPersonalizedPrice = false, + CancellationToken cancellationToken = default); + + /// + /// This method will post all purchases associated with the current Store account to RevenueCat and become + /// associated with the current appUserID. If the receipt is being used by an existing user, the current + /// appUserID will be aliased together with the appUserID of the existing user. + /// Going forward, either appUserID will be able to reference the same user. + /// + /// + /// + /// You shouldn't use this method if you have your own account system. In that case "restoration" is provided.
+ /// Note: This may force your users to enter their Store password so should only be performed on request of + /// by your app passing the same appUserID used to purchase originally. + /// the user. Typically, with a button in settings or near your purchase UI. Use + /// if you need to restore transactions programmatically. + ///
+ Task RestorePurchasesAsync(CancellationToken cancellationToken = default); + + /// + /// This function will log in the current user with an appUserID. + /// + /// + /// RevenueCat provides a source of truth for a subscriber's status across different platforms. + /// To do this, each subscriber has an App User ID that uniquely identifies them within your application. + /// User identity is one of the most important components of many mobile applications, + /// and it's extra important to make sure the subscription status RevenueCat is + /// tracking gets associated with the correct user. + /// The Purchases SDK allows you to specify your own user identifiers or use anonymous identifiers + /// generated by RevenueCat. Some apps will use a combination + /// of their own identifiers and RevenueCat anonymous Ids - that's okay! + /// + Task LogInAsync(string appUserId, CancellationToken cancellationToken = default); + + /// + /// Logs out the Purchases client, clearing the saved appUserID. + /// + /// + /// This will generate a random user id and save it in the cache. + /// If this method is called and the current user is anonymous, it will throw an exception. + /// + Task LogOutAsync(); + + void SetAllowSharingStoreAccount(bool allow); + + Task GetOfferingsAsync(CancellationToken cancellationToken = default); + + Task GetCurrentOfferingForPlacementAsync(string placementIdentifier, CancellationToken cancellationToken = default); + + Task SyncAttributesAndOfferingsIfNeededAsync(CancellationToken cancellationToken = default); + + void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, string isoCurrencyCode, double price); + + Task GetAmazonLWAConsentStatusAsync(CancellationToken cancellationToken = default); + + void SetLogLevel(LogLevel level); + + void SetDebugLogsEnabled(bool enabled); + + void SetProxyURL(string proxyURL); + + string GetAppUserId(); + + Task GetCustomerInfoAsync(CancellationToken cancellationToken = default); + + Task SyncPurchasesAsync(CancellationToken cancellationToken = default); + + void EnableAdServicesAttributionTokenCollection(); + + bool IsAnonymous(); + + bool IsConfigured(); + + Task> CheckTrialOrIntroductoryPriceEligibilityAsync(string[] productIdentifiers, CancellationToken cancellationToken = default); + + void InvalidateCustomerInfoCache(); + + void PresentCodeRedemptionSheet(); + + void RecordPurchase(string productID); + + void SetSimulatesAskToBuyInSandbox(bool enabled); + + void SetAttributes(Dictionary attributes); + + void SetEmail(string email); + + void SetPhoneNumber(string phoneNumber); + + void SetDisplayName(string displayName); + + void SetPushToken(string token); + + void SetAdjustID(string adjustID); + + void SetAppsflyerID(string appsflyerID); + + void SetFBAnonymousID(string fbAnonymousID); + + void SetMparticleID(string mparticleID); + + void SetOnesignalID(string onesignalID); + + void SetAirshipChannelID(string airshipChannelID); + + void SetCleverTapID(string cleverTapID); + + void SetMixpanelDistinctID(string mixpanelDistinctID); + + void SetFirebaseAppInstanceID(string firebaseAppInstanceID); + + void SetMediaSource(string mediaSource); + + void SetCampaign(string campaign); + + void SetAdGroup(string adGroup); + + void SetAd(string ad); + + void SetKeyword(string keyword); + + void SetCreative(string creative); + + void CollectDeviceIdentifiers(); + + Task CanMakePaymentsAsync(BillingFeature[] features, CancellationToken cancellationToken = default); + + Task GetPromotionalOfferAsync(string productIdentifier, string discountIdentifier, CancellationToken cancellationToken = default); + + void ShowInAppMessages(InAppMessageType[] messageTypes); + + Task ParseAsWebPurchaseRedemptionAsync(string urlString, CancellationToken cancellationToken = default); + + Task RedeemWebPurchaseAsync(WebPurchaseRedemption webPurchaseRedemption, CancellationToken cancellationToken = default); + + Task GetVirtualCurrenciesAsync(CancellationToken cancellationToken = default); + + VirtualCurrencies GetCachedVirtualCurrencies(); + + void InvalidateVirtualCurrenciesCache(); + + Task> GetEligibleWinBackOffersForProductAsync(StoreProduct storeProduct, CancellationToken cancellationToken = default); + + Task> GetEligibleWinBackOffersForPackageAsync(Package package, CancellationToken cancellationToken = default); + + Task PurchaseProductWithWinBackOfferAsync(StoreProduct storeProduct, WinBackOffer winBackOffer, CancellationToken cancellationToken = default); + + Task PurchasePackageWithWinBackOfferAsync(Package package, WinBackOffer winBackOffer, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs index 7fcf67df..a182ecaa 100644 --- a/RevenueCat/Scripts/PurchasesWrapperAndroid.cs +++ b/RevenueCat/Scripts/PurchasesWrapperAndroid.cs @@ -1,447 +1,843 @@ -using System.Diagnostics.CodeAnalysis; -using RevenueCat.SimpleJSON; +#if UNITY_ANDROID // && !UNITY_EDITOR +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using UnityEngine; -#if UNITY_ANDROID -public class PurchasesWrapperAndroid : IPurchasesWrapper +namespace RevenueCat { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class ProductsRequest + public class PurchasesWrapperAndroid : IPurchasesWrapper { - public string[] productIdentifiers; - } + private static readonly object _lock = new object(); + private static ConcurrentDictionary _pendingTasks = new ConcurrentDictionary(); - public void GetStorefront() - { - CallPurchases("getStorefront"); - } + private LogHandler _logHandler; + private CustomerInfoHandler _customerInfoHandler; - public void GetProducts(string[] productIdentifiers, string type = "subs") - { - var request = new ProductsRequest - { - productIdentifiers = productIdentifiers - }; - CallPurchases("getProducts", JsonUtility.ToJson(request), type); - } + public event Action OnCustomerInfoUpdated; + public event Action OnLogMessage; - public void PurchaseProduct(string productIdentifier, string type = "subs", string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, string presentedOfferingIdentifier = null, - Purchases.PromotionalOffer discount = null) - { - string presentedOfferingContextJSON = null; - if (presentedOfferingIdentifier != null) { - var contextDict = new JSONObject(); - contextDict["offeringIdentifier"] = presentedOfferingIdentifier; - presentedOfferingContextJSON = contextDict.ToString(); + ~PurchasesWrapperAndroid() + { + Dispose(); } - if (oldSku == null) + public void Dispose() { - CallPurchases("purchaseProduct", productIdentifier, type); + // clear event handlers to clean up memory + OnCustomerInfoUpdated = null; + OnLogMessage = null; } - else + + public void Configure(PurchasesConfiguration configuration) { - CallPurchases("purchaseProduct", productIdentifier, type, oldSku, (int)prorationMode, googleIsPersonalizedPrice, presentedOfferingContextJSON); + _customerInfoHandler = new CustomerInfoHandler(OnCustomerInfoUpdated); + _logHandler = new LogHandler(OnLogMessage); + CallPurchases("configure", + _customerInfoHandler, + _logHandler, + configuration.ApiKey, + configuration.AppUserId, + configuration.PurchasesAreCompletedBy.ToString(), + configuration.UserDefaultsSuiteName, + configuration.UseAmazon, + configuration.ShouldShowInAppMessagesAutomatically, + configuration.DangerousSettings.AutoSyncPurchases, + configuration.EntitlementVerificationMode.ToString(), + configuration.PendingTransactionsForPrepaidPlansEnabled); } - } - - public void PurchasePackage(Purchases.Package packageToPurchase, string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, Purchases.PromotionalOffer discount = null) - { - string presentedOfferingContextJSON = packageToPurchase.PresentedOfferingContext.ToJsonString(); - if (oldSku == null) + public Task GetStorefrontAsync(CancellationToken cancellationToken = default) { - CallPurchases("purchasePackage", packageToPurchase.Identifier, presentedOfferingContextJSON); + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetStorefrontAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetStorefrontAsync)] = callback; + CallPurchases("getStorefront", callback); + return callback.Task; + } } - else + + public Task> GetProductsAsync( + string[] productIdentifiers, + string type = "subs", + CancellationToken cancellation = default) { - CallPurchases("purchasePackage", packageToPurchase.Identifier, presentedOfferingContextJSON, oldSku, - (int)prorationMode, googleIsPersonalizedPrice); + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetProductsAsync), out var value) && + value is AndroidPurchasesCallback> pending) + { + return pending.Task; + } + var callback = new AndroidPurchasesCallback>(cancellation); + _pendingTasks[nameof(GetProductsAsync)] = callback; + CallPurchases("getProducts", callback, JsonConvert.SerializeObject(new { productIdentifiers }), type); + return callback.Task; + } } - } - - public void PurchaseSubscriptionOption(Purchases.SubscriptionOption subscriptionOption, - Purchases.GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false) - { - string presentedOfferingContextJSON = null; - if (subscriptionOption.PresentedOfferingContext != null) { - presentedOfferingContextJSON = subscriptionOption.PresentedOfferingContext.ToJsonString(); - } - - if (googleProductChangeInfo == null) + public Task PurchaseProductAsync( + string productIdentifier, + string type = "subs", + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + string offeringIdentifier = null, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) { - CallPurchases("purchaseSubscriptionOption", subscriptionOption.ProductId, subscriptionOption.Id, - null, 0, googleIsPersonalizedPrice, presentedOfferingContextJSON); + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchaseProductAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(PurchaseProductAsync)] = callback; + + if (oldSku == null) + { + CallPurchases( + "purchaseProduct", + callback, + productIdentifier, + type); + } + else + { + CallPurchases( + "purchaseProduct", + callback, + productIdentifier, + type, + oldSku, + (int)prorationMode, + googleIsPersonalizedPrice, + JsonConvert.SerializeObject(new { offeringIdentifier })); + } + + return callback.Task; + } } - else + + public Task PurchasePackageAsync( + Package packageToPurchase, + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) { - CallPurchases("purchaseSubscriptionOption", subscriptionOption.ProductId, subscriptionOption.Id, - googleProductChangeInfo.OldProductIdentifier, (int)googleProductChangeInfo.ProrationMode, googleIsPersonalizedPrice, - presentedOfferingContextJSON); + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchasePackageAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(PurchasePackageAsync)] = callback; + + if (oldSku == null) + { + CallPurchases( + "purchasePackage", + callback, + packageToPurchase.Identifier, + JsonConvert.SerializeObject(packageToPurchase.PresentedOfferingContext)); + } + else + { + CallPurchases("purchasePackage", + callback, + packageToPurchase.Identifier, + JsonConvert.SerializeObject(packageToPurchase.PresentedOfferingContext), + oldSku, + (int)prorationMode, + googleIsPersonalizedPrice); + } + + return callback.Task; + } } - } - - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, Purchases.StoreKitVersion storeKitVersion, - string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, bool shouldShowInAppMessagesAutomatically, - bool pendingTransactionsForPrepaidPlansEnabled) - { - Setup(gameObject, apiKey, appUserId, purchasesAreCompletedBy, storeKitVersion, userDefaultsSuiteName, useAmazon, - dangerousSettingsJson, shouldShowInAppMessagesAutomatically, Purchases.EntitlementVerificationMode.Disabled, pendingTransactionsForPrepaidPlansEnabled); - } - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, Purchases.StoreKitVersion storeKitVersion, - string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, bool shouldShowInAppMessagesAutomatically, - Purchases.EntitlementVerificationMode entitlementVerificationMode, bool pendingTransactionsForPrepaidPlansEnabled) - { - CallPurchases("setup", apiKey, appUserId, gameObject, purchasesAreCompletedBy.Name(), userDefaultsSuiteName, useAmazon, shouldShowInAppMessagesAutomatically, - dangerousSettingsJson, entitlementVerificationMode.Name(), pendingTransactionsForPrepaidPlansEnabled); - } - public void RestorePurchases() - { - CallPurchases("restorePurchases"); - } + public Task PurchaseSubscriptionOptionAsync( + SubscriptionOption subscriptionOption, + GoogleProductChangeInfo googleProductChangeInfo = null, + bool googleIsPersonalizedPrice = false, + CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchaseSubscriptionOptionAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(PurchaseSubscriptionOptionAsync)] = callback; + + if (googleProductChangeInfo == null) + { + CallPurchases("purchaseSubscriptionOption", + callback, + subscriptionOption.ProductId, + subscriptionOption.Id, + null, + 0, + googleIsPersonalizedPrice, + JsonConvert.SerializeObject(subscriptionOption.PresentedOfferingContext)); + } + else + { + CallPurchases("purchaseSubscriptionOption", + callback, + subscriptionOption.ProductId, + subscriptionOption.Id, + googleProductChangeInfo.OldProductIdentifier, + (int)googleProductChangeInfo.ProrationMode, + googleIsPersonalizedPrice, + JsonConvert.SerializeObject(subscriptionOption.PresentedOfferingContext)); + } + + return callback.Task; + } + } - public void LogIn(string appUserId) - { - CallPurchases("logIn", appUserId); - } + public Task RestorePurchasesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(RestorePurchasesAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(RestorePurchasesAsync)] = callback; + CallPurchases("restorePurchases", callback); + return callback.Task; + } + } - public void LogOut() - { - CallPurchases("logOut"); - } + public Task LogInAsync(string appUserId, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(LogInAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(LogInAsync)] = callback; + CallPurchases("logIn", callback, appUserId); + return callback.Task; + } + } - public void SetAllowSharingStoreAccount(bool allow) - { - CallPurchases("setAllowSharingStoreAccount", allow); - } + public Task LogOutAsync() + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(LogOutAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(CancellationToken.None); + _pendingTasks[nameof(LogOutAsync)] = callback; + CallPurchases("logOut", callback); + return callback.Task; + } + } - public void SetDebugLogsEnabled(bool enabled) - { - CallPurchases("setDebugLogsEnabled", enabled); - } + public void SetAllowSharingStoreAccount(bool allow) + { + CallPurchases("setAllowSharingStoreAccount", allow); + } - public void SetLogLevel(Purchases.LogLevel level) - { - CallPurchases("setLogLevel", level.Name()); - } + public Task GetOfferingsAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetOfferingsAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetOfferingsAsync)] = callback; + CallPurchases("getOfferings", callback); + return callback.Task; + } + } - public void SetLogHandler() - { - CallPurchases("setLogHandler"); - } + public Task GetCurrentOfferingForPlacementAsync(string placementIdentifier, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetCurrentOfferingForPlacementAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetCurrentOfferingForPlacementAsync)] = callback; + CallPurchases("getCurrentOfferingForPlacement", callback, placementIdentifier); + return callback.Task; + } + } - public void SetProxyURL(string proxyURL) - { - CallPurchases("setProxyURL", proxyURL); - } + public Task SyncAttributesAndOfferingsIfNeededAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(SyncAttributesAndOfferingsIfNeededAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(SyncAttributesAndOfferingsIfNeededAsync)] = callback; + CallPurchases("syncAttributesAndOfferingsIfNeeded", callback); + return callback.Task; + } + } - public string GetAppUserId() - { - return CallPurchases("getAppUserID"); - } + public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, string isoCurrencyCode, double price) + { + CallPurchases("syncAmazonPurchase", productID, receiptID, amazonUserID, isoCurrencyCode, price); + } - public void GetCustomerInfo() - { - CallPurchases("getCustomerInfo"); - } + public Task GetAmazonLWAConsentStatusAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetAmazonLWAConsentStatusAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetAmazonLWAConsentStatusAsync)] = callback; + CallPurchases("getAmazonLWAConsentStatus", callback); + return callback.Task; + } + } - public void GetOfferings() - { - CallPurchases("getOfferings"); - } + public void SetLogLevel(LogLevel level) + { + CallPurchases("setLogLevel", level.ToString()); + } - public void GetCurrentOfferingForPlacement(string placementIdentifier) - { - CallPurchases("getCurrentOfferingForPlacement", placementIdentifier); - } + public void SetDebugLogsEnabled(bool enabled) + { + CallPurchases("setDebugLogsEnabled", enabled); + } - public void SyncAttributesAndOfferingsIfNeeded() - { - CallPurchases("syncAttributesAndOfferingsIfNeeded"); - } + public void SetProxyURL(string proxyURL) + { + CallPurchases("setProxyURL", proxyURL); + } - public void SyncPurchases() - { - CallPurchases("syncPurchases"); - } + public string GetAppUserId() + { + try + { + return CallPurchases("getAppUserID"); + } + catch (Exception e) + { + Debug.LogException(e); + return null; + } + } - public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, - string isoCurrencyCode, double price) - { - CallPurchases("syncAmazonPurchase", productID, receiptID, amazonUserID, isoCurrencyCode, price); - } + public Task GetCustomerInfoAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetCustomerInfoAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetCustomerInfoAsync)] = callback; + CallPurchases("getCustomerInfo", callback); + return callback.Task; + } + } - public void GetAmazonLWAConsentStatus() - { - CallPurchases("getAmazonLWAConsentStatus"); - } + public Task SyncPurchasesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(SyncPurchasesAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(SyncPurchasesAsync)] = callback; + CallPurchases("syncPurchases", callback); + return callback.Task; + } + } - public void EnableAdServicesAttributionTokenCollection() - { - // NOOP - } + public void EnableAdServicesAttributionTokenCollection() + { + // NOOP + } - public bool IsAnonymous() - { - return CallPurchases("isAnonymous"); - } + public bool IsAnonymous() + { + try + { + return CallPurchases("isAnonymous"); + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + } - public bool IsConfigured() - { - return CallPurchases("isConfigured"); - } + public bool IsConfigured() + { + try + { + return CallPurchases("isConfigured"); + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + } - public void CheckTrialOrIntroductoryPriceEligibility(string[] productIdentifiers) - { - var request = new ProductsRequest + public Task> CheckTrialOrIntroductoryPriceEligibilityAsync(string[] productIdentifiers, CancellationToken cancellationToken = default) { - productIdentifiers = productIdentifiers - }; - CallPurchases("checkTrialOrIntroductoryPriceEligibility", JsonUtility.ToJson(request)); - } + var callback = new AndroidPurchasesCallback>(cancellationToken); + CallPurchases("checkTrialOrIntroductoryPriceEligibility", callback, JsonConvert.SerializeObject(new { productIdentifiers })); + return callback.Task; + } - public void InvalidateCustomerInfoCache() - { - CallPurchases("invalidateCustomerInfoCache"); - } + public void InvalidateCustomerInfoCache() + { + CallPurchases("invalidateCustomerInfoCache"); + } - public void PresentCodeRedemptionSheet() - { - // NOOP - } + public void PresentCodeRedemptionSheet() + { + // NOOP + } - public void RecordPurchase(string productID) - { - // NOOP - } + public void RecordPurchase(string productID) + { + // NOOP + } - public void SetSimulatesAskToBuyInSandbox(bool enabled) - { - // NOOP - } + public void SetSimulatesAskToBuyInSandbox(bool enabled) + { + // NOOP + } - public void SetAttributes(string attributesJson) - { - CallPurchases("setAttributes", attributesJson); - } + public void SetAttributes(Dictionary attributes) + { + CallPurchases("setAttributes", JsonConvert.SerializeObject(attributes)); + } - public void SetEmail(string email) - { - CallPurchases("setEmail", email); - } + public void SetEmail(string email) + { + CallPurchases("setEmail", email); + } - public void SetPhoneNumber(string phoneNumber) - { - CallPurchases("setPhoneNumber", phoneNumber); - } + public void SetPhoneNumber(string phoneNumber) + { + CallPurchases("setPhoneNumber", phoneNumber); + } - public void SetDisplayName(string displayName) - { - CallPurchases("setDisplayName", displayName); - } + public void SetDisplayName(string displayName) + { + CallPurchases("setDisplayName", displayName); + } - public void SetPushToken(string token) - { - CallPurchases("setPushToken", token); - } + public void SetPushToken(string token) + { + CallPurchases("setPushToken", token); + } - public void SetAdjustID(string adjustID) - { - CallPurchases("setAdjustID", adjustID); - } + public void SetAdjustID(string adjustID) + { + CallPurchases("setAdjustID", adjustID); + } - public void SetAppsflyerID(string appsflyerID) - { - CallPurchases("setAppsflyerID", appsflyerID); - } + public void SetAppsflyerID(string appsflyerID) + { + CallPurchases("setAppsflyerID", appsflyerID); + } - public void SetFBAnonymousID(string fbAnonymousID) - { - CallPurchases("setFBAnonymousID", fbAnonymousID); - } + public void SetFBAnonymousID(string fbAnonymousID) + { + CallPurchases("setFBAnonymousID", fbAnonymousID); + } - public void SetMparticleID(string mparticleID) - { - CallPurchases("setMparticleID", mparticleID); - } + public void SetMparticleID(string mparticleID) + { + CallPurchases("setMparticleID", mparticleID); + } - public void SetOnesignalID(string onesignalID) - { - CallPurchases("setOnesignalID", onesignalID); - } + public void SetOnesignalID(string onesignalID) + { + CallPurchases("setOnesignalID", onesignalID); + } - public void SetAirshipChannelID(string airshipChannelID) - { - CallPurchases("setAirshipChannelID", airshipChannelID); - } + public void SetAirshipChannelID(string airshipChannelID) + { + CallPurchases("setAirshipChannelID", airshipChannelID); + } - public void SetCleverTapID(string cleverTapID) - { - CallPurchases("setCleverTapID", cleverTapID); - } + public void SetCleverTapID(string cleverTapID) + { + CallPurchases("setCleverTapID", cleverTapID); + } - public void SetMixpanelDistinctID(string mixpanelDistinctID) - { - CallPurchases("setMixpanelDistinctID", mixpanelDistinctID); - } + public void SetMixpanelDistinctID(string mixpanelDistinctID) + { + CallPurchases("setMixpanelDistinctID", mixpanelDistinctID); + } - public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) - { - CallPurchases("setFirebaseAppInstanceID", firebaseAppInstanceID); - } + public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) + { + CallPurchases("setFirebaseAppInstanceID", firebaseAppInstanceID); + } - public void SetMediaSource(string mediaSource) - { - CallPurchases("setMediaSource", mediaSource); - } + public void SetMediaSource(string mediaSource) + { + CallPurchases("setMediaSource", mediaSource); + } - public void SetCampaign(string campaign) - { - CallPurchases("setCampaign", campaign); - } + public void SetCampaign(string campaign) + { + CallPurchases("setCampaign", campaign); + } - public void SetAdGroup(string adGroup) - { - CallPurchases("setAdGroup", adGroup); - } + public void SetAdGroup(string adGroup) + { + CallPurchases("setAdGroup", adGroup); + } - public void SetAd(string ad) - { - CallPurchases("setAd", ad); - } + public void SetAd(string ad) + { + CallPurchases("setAd", ad); + } - public void SetKeyword(string keyword) - { - CallPurchases("setKeyword", keyword); - } + public void SetKeyword(string keyword) + { + CallPurchases("setKeyword", keyword); + } - public void SetCreative(string creative) - { - CallPurchases("setCreative", creative); - } + public void SetCreative(string creative) + { + CallPurchases("setCreative", creative); + } - public void CollectDeviceIdentifiers() - { - CallPurchases("collectDeviceIdentifiers"); - } + public void CollectDeviceIdentifiers() + { + CallPurchases("collectDeviceIdentifiers"); + } - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class CanMakePaymentsRequest - { - public int[] features; - } + public Task CanMakePaymentsAsync(BillingFeature[] features, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(CanMakePaymentsAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(CanMakePaymentsAsync)] = callback; + CallPurchases("canMakePayments", callback, JsonConvert.SerializeObject(new { features })); + return callback.Task; + } + } - public void CanMakePayments(Purchases.BillingFeature[] features) - { - int[] featuresAsInts = new int[features.Length]; - for (int i = 0; i < features.Length; i++) + public Task GetPromotionalOfferAsync(string productIdentifier, string discountIdentifier, CancellationToken cancellationToken = default) { - Purchases.BillingFeature feature = features[i]; - featuresAsInts[i] = (int)feature; + // TODO verify NOOP ? + // CallPurchases("getPromotionalOffer", productIdentifier, discountIdentifier); + return Task.FromResult(null); } - var request = new CanMakePaymentsRequest + public void ShowInAppMessages(InAppMessageType[] messageTypes) { - features = featuresAsInts - }; - CallPurchases("canMakePayments", JsonUtility.ToJson(request)); - } + CallPurchases("showInAppMessages", JsonConvert.SerializeObject(new { messageTypes })); + } - public void GetPromotionalOffer(string productIdentifier, string discountIdentifier) - { - CallPurchases("getPromotionalOffer", productIdentifier, discountIdentifier); - } + public Task ParseAsWebPurchaseRedemptionAsync(string urlString, CancellationToken cancellationToken = default) + { + var callback = new AndroidPurchasesCallback(cancellationToken); + CallPurchases("parseAsWebPurchaseRedemption", callback, urlString); + return callback.Task; + } - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class ShowInAppMessagesRequest - { - public int[] messageTypes; - } + public Task RedeemWebPurchaseAsync(WebPurchaseRedemption webPurchaseRedemption, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(RedeemWebPurchaseAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(RedeemWebPurchaseAsync)] = callback; + CallPurchases("redeemWebPurchase", callback, webPurchaseRedemption.RedemptionLink); + return callback.Task; + } + } - public void ShowInAppMessages(Purchases.InAppMessageType[] messageTypes) - { - int[] messageTypesAsInts = new int[messageTypes.Length]; - for (int i = 0; i < messageTypes.Length; i++) { - Purchases.InAppMessageType messageType = messageTypes[i]; - messageTypesAsInts[i] = (int)messageType; + public Task GetVirtualCurrenciesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetVirtualCurrenciesAsync), out var value) && + value is AndroidPurchasesCallback pending) + { + return pending.Task; + } + + var callback = new AndroidPurchasesCallback(cancellationToken); + _pendingTasks[nameof(GetVirtualCurrenciesAsync)] = callback; + CallPurchases("getVirtualCurrencies", callback); + return callback.Task; + } } - var request = new ShowInAppMessagesRequest + public VirtualCurrencies GetCachedVirtualCurrencies() { - messageTypes = messageTypesAsInts - }; - CallPurchases("showInAppMessages", JsonUtility.ToJson(request)); - } + try + { + var json = CallPurchases("getCachedVirtualCurrencies"); + return !string.IsNullOrWhiteSpace(json) + ? JsonConvert.DeserializeObject(json) + : null; + } + catch (Exception e) + { + Debug.LogException(e); + return null; + } + } - public void ParseAsWebPurchaseRedemption(string urlString) - { - CallPurchases("parseAsWebPurchaseRedemption", urlString); - } + public void InvalidateVirtualCurrenciesCache() + { + CallPurchases("invalidateVirtualCurrenciesCache"); + } - public void RedeemWebPurchase(Purchases.WebPurchaseRedemption webPurchaseRedemption) - { - CallPurchases("redeemWebPurchase", webPurchaseRedemption.RedemptionLink); - } + public Task> GetEligibleWinBackOffersForProductAsync(StoreProduct storeProduct, CancellationToken cancellationToken = default) + { + // NOOP + return Task.FromResult>(null); + } - public void GetVirtualCurrencies() - { - CallPurchases("getVirtualCurrencies"); - } + public Task> GetEligibleWinBackOffersForPackageAsync(Package package, CancellationToken cancellationToken = default) + { + // NOOP + return Task.FromResult>(null); + } - public string GetCachedVirtualCurrencies() - { - return CallPurchases("getCachedVirtualCurrencies"); - } + public Task PurchaseProductWithWinBackOfferAsync(StoreProduct storeProduct, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + // NOOP + return Task.FromResult(null); + } - public void InvalidateVirtualCurrenciesCache() - { - CallPurchases("invalidateVirtualCurrenciesCache"); - } + public Task PurchasePackageWithWinBackOfferAsync(Package package, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + // NOOP + return Task.FromResult(null); + } - public void GetEligibleWinBackOffersForProduct(Purchases.StoreProduct storeProduct) - { - CallPurchases("getEligibleWinBackOffersForProduct", storeProduct.Identifier); - } + #region Native Interop - public void GetEligibleWinBackOffersForPackage(Purchases.Package package) - { - CallPurchases("getEligibleWinBackOffersForPackage", package.StoreProduct.Identifier); - } + private const string PurchasesJavaClass = "com.revenuecat.unity.Purchases"; - public void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer) - { - CallPurchases("purchaseProductWithWinBackOffer", storeProduct.Identifier, winBackOffer.Identifier); - } + private static void CallPurchases(string methodName, params object[] args) + { + using (var purchases = new AndroidJavaClass(PurchasesJavaClass)) + { + purchases.CallStatic(methodName, args); + } + } - public void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer) - { - CallPurchases("purchasePackageWithWinBackOffer", package.Identifier, package.PresentedOfferingContext.ToJsonString(), winBackOffer.Identifier); - } + private static TReturnType CallPurchases(string methodName, params object[] args) + { + using (var purchases = new AndroidJavaClass(PurchasesJavaClass)) + { + return purchases.CallStatic(methodName, args); + } + } - private const string PurchasesWrapper = "com.revenuecat.purchasesunity.PurchasesWrapper"; + internal class AndroidPurchasesCallback : AndroidJavaProxy + { + private string _callingMethod; + private TaskCompletionSource _tcs; + + public AndroidPurchasesCallback(CancellationToken cancellationToken, [CallerMemberName] string callingMethod = null) : base("com.revenuecat.purchases.unity.PurchasesWrapper$Callback") + { + _callingMethod = callingMethod; + _tcs = new TaskCompletionSource(); + + if (cancellationToken != CancellationToken.None) + { + cancellationToken.Register(() => { _tcs.TrySetCanceled(); }); + } + } + + // ReSharper disable once InconsistentNaming + // matches name of Java method + [UsedImplicitly] + public void onReceived(string json) + { + lock (_lock) + { + if (_pendingTasks.TryRemove(_callingMethod, out _)) + { + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + try + { + var data = JsonConvert.DeserializeObject(json); + _tcs.SetResult(data); + } + catch (Exception e) + { + _tcs.SetException(e); + } + }, null); + } + } + } + + // ReSharper disable once InconsistentNaming + // matches name of Java method + [UsedImplicitly] + public void onError(string json) + { + lock (_lock) + { + if (_pendingTasks.TryRemove(_callingMethod, out _)) + { + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + try + { + var error = JsonConvert.DeserializeObject(json); + _tcs.SetException(new Exception(error.ToString())); + } + catch (Exception e) + { + _tcs.SetException(e); + } + }, null); + } + } + } + + public Task Task => _tcs.Task; + } - private static void CallPurchases(string methodName, params object[] args) - { - using (var purchases = new AndroidJavaClass(PurchasesWrapper)) + internal class CustomerInfoHandler : AndroidJavaProxy { - purchases.CallStatic(methodName, args); + private Action _action; + + public CustomerInfoHandler(Action action) : base("com.revenuecat.purchases.unity.PurchasesWrapper$CustomerInfoHandler") + { + _action = action; + } + + // ReSharper disable once InconsistentNaming + // matches name of Java method + [UsedImplicitly] + public void onCustomerReceived(string json) + { + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + try + { + var customerInfo = JsonConvert.DeserializeObject(json, PurchasesSdk.JsonSerializerSettings); + _action?.Invoke(customerInfo); + } + catch (Exception e) + { + Debug.LogException(e); + } + }, null); + } + } - } - private static ReturnType CallPurchases(string methodName, params object[] args) - { - using (var purchases = new AndroidJavaClass(PurchasesWrapper)) + internal class LogHandler : AndroidJavaProxy { - return purchases.CallStatic(methodName, args); + private Action _action; + + public LogHandler(Action action) : base("com.revenuecat.purchases.unity.PurchasesWrapper$LogHandler") + { + _action = action; + } + + // ReSharper disable once InconsistentNaming + // matches name of Java method + [UsedImplicitly] + public void onLogReceived(string json) + { + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + try + { + var logMessage = JsonConvert.DeserializeObject(json, PurchasesSdk.JsonSerializerSettings); + _action?.Invoke(logMessage); + } + catch (Exception e) + { + Debug.LogException(e); + } + }, null); + } } - } + #endregion Native Interop + } } -#endif +#endif // UNITY_ANDROID && !UNITY_EDITOR diff --git a/RevenueCat/Scripts/PurchasesWrapperNoop.cs b/RevenueCat/Scripts/PurchasesWrapperNoop.cs index 4da81a90..7cea88fe 100644 --- a/RevenueCat/Scripts/PurchasesWrapperNoop.cs +++ b/RevenueCat/Scripts/PurchasesWrapperNoop.cs @@ -1,116 +1,128 @@ +using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; -public partial class Purchases +namespace RevenueCat { - private class PurchasesWrapperNoop : IPurchasesWrapper + internal class PurchasesWrapperNoop : IPurchasesWrapper { - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, bool pendingTransactionsForPrepaidPlansEnabled) - { - } - - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, Purchases.EntitlementVerificationMode entitlementVerificationMode, - bool pendingTransactionsForPrepaidPlansEnabled) - { - } +#pragma warning disable CS0067 // Event is never used + public event Action OnCustomerInfoUpdated; + public event Action OnLogMessage; +#pragma warning restore CS0067 // Event is never used - public void GetStorefront() + public void Configure(PurchasesConfiguration configuration) { } - public void GetProducts(string[] productIdentifiers, string type = "subs") + public Task GetStorefrontAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void MakePurchase(string productIdentifier, string type = "subs", string oldSku = null) + public Task> GetProductsAsync(string[] productIdentifiers, string type = "subs", CancellationToken cancellationToken = default) { + return Task.FromResult>(Array.Empty()); } - public void PurchaseProduct(string productIdentifier, string type = "subs", string oldSku = null, + public Task PurchaseProductAsync( + string productIdentifier, + string type = "subs", + string oldSku = null, ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, string presentedOfferingIdentifier = null, - Purchases.PromotionalOffer discount = null) + bool googleIsPersonalizedPrice = false, + string offeringIdentifier = null, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void PurchasePackage(Package packageToPurchase, string oldSku = null, + public Task PurchasePackageAsync( + Package packageToPurchase, + string oldSku = null, ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, Purchases.PromotionalOffer discount = null) + bool googleIsPersonalizedPrice = false, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void PurchaseSubscriptionOption(Purchases.SubscriptionOption subscriptionOption, - Purchases.GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false) + public Task PurchaseSubscriptionOptionAsync(SubscriptionOption subscriptionOption, GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void RestorePurchases() + public Task RestorePurchasesAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void LogIn(string appUserId) + public Task LogInAsync(string appUserId, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void LogOut() + public Task LogOutAsync() { + return Task.FromResult(null); } public void SetAllowSharingStoreAccount(bool allow) { } - public string GetAppUserId() + public Task GetOfferingsAsync(CancellationToken cancellationToken = default) { - return null; + return Task.FromResult(null); } - public void SetDebugLogsEnabled(bool enabled) + public Task GetCurrentOfferingForPlacementAsync(string placementIdentifier, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void SetLogLevel(LogLevel level) + public Task SyncAttributesAndOfferingsIfNeededAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void SetLogHandler() + public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, string isoCurrencyCode, double price) { } - public void SetProxyURL(string proxyURL) + public Task GetAmazonLWAConsentStatusAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(false); } - public void GetCustomerInfo() + public void SetLogLevel(LogLevel level) { } - public void GetOfferings() + public void SetDebugLogsEnabled(bool enabled) { } - public void GetCurrentOfferingForPlacement(string placementIdentifier) + public void SetProxyURL(string proxyURL) { } - public void SyncAttributesAndOfferingsIfNeeded() + public string GetAppUserId() { + return null; } - public void SyncPurchases() + public Task GetCustomerInfoAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, - string isoCurrencyCode, double price) - { - } - - public void GetAmazonLWAConsentStatus() + public Task SyncPurchasesAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } public void EnableAdServicesAttributionTokenCollection() @@ -127,8 +139,9 @@ public bool IsConfigured() return false; } - public void CheckTrialOrIntroductoryPriceEligibility(string[] productIdentifiers) + public Task> CheckTrialOrIntroductoryPriceEligibilityAsync(string[] productIdentifiers, CancellationToken cancellationToken = default) { + return Task.FromResult>(new Dictionary()); } public void InvalidateCustomerInfoCache() @@ -147,7 +160,7 @@ public void SetSimulatesAskToBuyInSandbox(bool enabled) { } - public void SetAttributes(string attributesJson) + public void SetAttributes(Dictionary attributes) { } @@ -231,31 +244,36 @@ public void CollectDeviceIdentifiers() { } - public void CanMakePayments(Purchases.BillingFeature[] features) + public Task CanMakePaymentsAsync(BillingFeature[] features, CancellationToken cancellationToken = default) { + return Task.FromResult(false); } - public void GetPromotionalOffer(string productIdentifier, string discountIdentifier) + public Task GetPromotionalOfferAsync(string productIdentifier, string discountIdentifier, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void ShowInAppMessages(Purchases.InAppMessageType[] messageTypes) + public void ShowInAppMessages(InAppMessageType[] messageTypes) { } - public void ParseAsWebPurchaseRedemption(string urlString) + public Task ParseAsWebPurchaseRedemptionAsync(string urlString, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void RedeemWebPurchase(Purchases.WebPurchaseRedemption webPurchaseRedemption) + public Task RedeemWebPurchaseAsync(WebPurchaseRedemption webPurchaseRedemption, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void GetVirtualCurrencies() + public Task GetVirtualCurrenciesAsync(CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public string GetCachedVirtualCurrencies() + public VirtualCurrencies GetCachedVirtualCurrencies() { return null; } @@ -264,19 +282,27 @@ public void InvalidateVirtualCurrenciesCache() { } - public void GetEligibleWinBackOffersForProduct(Purchases.StoreProduct storeProduct) + public Task> GetEligibleWinBackOffersForProductAsync(StoreProduct storeProduct, CancellationToken cancellationToken = default) + { + return Task.FromResult>(Array.Empty()); + } + + public Task> GetEligibleWinBackOffersForPackageAsync(Package package, CancellationToken cancellationToken = default) { + return Task.FromResult>(Array.Empty()); } - public void GetEligibleWinBackOffersForPackage(Purchases.Package package) + public Task PurchaseProductWithWinBackOfferAsync(StoreProduct storeProduct, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer) + public Task PurchasePackageWithWinBackOfferAsync(Package package, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) { + return Task.FromResult(null); } - public void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer) + public void Dispose() { } } diff --git a/RevenueCat/Scripts/PurchasesWrapperiOS.cs b/RevenueCat/Scripts/PurchasesWrapperiOS.cs index b9991c55..2c136bd1 100644 --- a/RevenueCat/Scripts/PurchasesWrapperiOS.cs +++ b/RevenueCat/Scripts/PurchasesWrapperiOS.cs @@ -1,528 +1,979 @@ -using System.Diagnostics.CodeAnalysis; +#if UNITY_IOS || UNITY_VISIONOS +using AOT; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Runtime.InteropServices; -using JetBrains.Annotations; +using System.Threading; +using System.Threading.Tasks; using UnityEngine; -#if UNITY_IOS || UNITY_VISIONOS -public class PurchasesWrapperiOS : IPurchasesWrapper +namespace RevenueCat { - [DllImport("__Internal")] - private static extern void _RCSetupPurchases(string gameObject, string apiKey, string appUserId, string purchasesAreCompletedBy, - string storeKitVersion, string userDefaultsSuiteName, - string dangerousSettingsJson, bool shouldShowInAppMessagesAutomatically, - string entitlementVerificationMode); - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, bool pendingTransactionsForPrepaidPlansEnabled) + public class PurchasesWrapperiOS : IPurchasesWrapper { - Setup(gameObject, apiKey, appUserId, purchasesAreCompletedBy, storeKitVersion, - userDefaultsSuiteName, useAmazon, dangerousSettingsJson, shouldShowInAppMessagesAutomatically, - Purchases.EntitlementVerificationMode.Disabled, pendingTransactionsForPrepaidPlansEnabled); - } + private PurchasesWrapperiOS() + { + if (_instance != null) + { + throw new Exception($"There should never be more than one instance of {nameof(PurchasesWrapperiOS)}"); + } - public void Setup(string gameObject, string apiKey, string appUserId, Purchases.PurchasesAreCompletedBy purchasesAreCompletedBy, - Purchases.StoreKitVersion storeKitVersion, string userDefaultsSuiteName, bool useAmazon, string dangerousSettingsJson, - bool shouldShowInAppMessagesAutomatically, Purchases.EntitlementVerificationMode entitlementVerificationMode, - bool pendingTransactionsForPrepaidPlansEnabled) - { - _RCSetupPurchases(gameObject, apiKey, appUserId, purchasesAreCompletedBy.Name(), storeKitVersion.Name(), - userDefaultsSuiteName, dangerousSettingsJson, shouldShowInAppMessagesAutomatically, entitlementVerificationMode.Name()); - } + _instance = this; + } - [DllImport("__Internal")] - private static extern void _RCGetStorefront(); - public void GetStorefront() - { - _RCGetStorefront(); - } + ~PurchasesWrapperiOS() + { + _instance = null; + Dispose(); + } - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class ProductsRequest - { - public string[] productIdentifiers; - } + public void Dispose() + { + OnCustomerInfoUpdated = null; + OnLogMessage = null; + } - [DllImport("__Internal")] - private static extern void _RCGetProducts(string productIdentifiersJson, string type); - public void GetProducts(string[] productIdentifiers, string type = "subs") - { - var request = new ProductsRequest + private static PurchasesWrapperiOS _instance; + private static readonly object _lock = new object(); + private static ConcurrentDictionary _pendingTasks = new ConcurrentDictionary(); + + public event Action OnCustomerInfoUpdated; + public event Action OnLogMessage; + + private delegate void StaticCallbackDelegate(IntPtr dataPtr); + private delegate void NativeInteropDelegate(IntPtr methodPtr, IntPtr dataPtr); + + [MonoPInvokeCallback(typeof(StaticCallbackDelegate))] + private static void CustomerInfoReceived(IntPtr dataPtr) { - productIdentifiers = productIdentifiers - }; + if (dataPtr == IntPtr.Zero) { return; } + + try + { + var json = Marshal.PtrToStringAuto(dataPtr) ?? string.Empty; + var customerInfo = JsonConvert.DeserializeObject(json); + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + _instance.OnCustomerInfoUpdated?.Invoke(customerInfo); + }, null); + } + catch (Exception e) + { + Debug.LogError(e); + } + } - _RCGetProducts(JsonUtility.ToJson(request), type); - } + [MonoPInvokeCallback(typeof(StaticCallbackDelegate))] + private static void LogReceived(IntPtr dataPtr) + { + if (_instance == null) { return; } + try + { + var json = Marshal.PtrToStringAuto(dataPtr) ?? string.Empty; + var logMessage = JsonConvert.DeserializeObject(json); + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + _instance.OnLogMessage?.Invoke(logMessage); + }, null); + } + catch (Exception e) + { + Debug.LogError(e); + } + } - [DllImport("__Internal")] - private static extern void _RCPurchaseProduct(string productIdentifier, string signedDiscountTimestamp); - public void PurchaseProduct(string productIdentifier, string type = "subs", string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, string presentedOfferingIdentifier = null, - Purchases.PromotionalOffer discount = null) - { - string discountTimestamp = null; - if (discount != null) + [MonoPInvokeCallback(typeof(NativeInteropDelegate))] + private static void NativeInteropCallback(IntPtr methodPtr, IntPtr dataPtr) { - discountTimestamp = discount.Timestamp.ToString(); + lock (_lock) + { + if (methodPtr == IntPtr.Zero) { return; } + + try + { + var method = Marshal.PtrToStringAuto(methodPtr) ?? string.Empty; + + if (_pendingTasks.TryRemove(method, out var value) && value is TaskCompletionSource tcs) + { + var json = Marshal.PtrToStringAuto(dataPtr) ?? string.Empty; + var jObject = JObject.Parse(json); + + if (jObject["error"] != null) + { + var error = jObject["error"].ToObject(); + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + tcs.SetException(new Exception(error.ToString())); + }, null); + } + else + { + var result = jObject["result"]?.ToObject(value.GetType().GetGenericArguments()[0]); + PurchasesSdk.MainThreadSynchronizationContext.Post(_ => + { + tcs.SetResult(result); + }, null); + } + } + } + catch (Exception e) + { + Debug.LogException(e); + } + } } - _RCPurchaseProduct(productIdentifier, discountTimestamp); - } - [DllImport("__Internal")] - private static extern void _RCPurchasePackage(string packageIdentifier, string presentedOfferingContextJSON, string signedDiscountTimestamp); - public void PurchasePackage(Purchases.Package packageToPurchase, string oldSku = null, - Purchases.ProrationMode prorationMode = Purchases.ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, - bool googleIsPersonalizedPrice = false, Purchases.PromotionalOffer discount = null) - { - string discountTimestamp = null; - if (discount != null) + [DllImport("__Internal")] + private static extern void _RCSetupPurchases( + StaticCallbackDelegate nativeInterop, + StaticCallbackDelegate logHandler, + NativeInteropDelegate nativeInteropHandler, + string apiKey, + string appUserId, + string purchasesAreCompletedBy, + string storeKitVersion, + string userDefaultsSuiteName, + string dangerousSettingsJson, + bool shouldShowInAppMessagesAutomatically, + string entitlementVerificationMode); + + public void Configure(PurchasesConfiguration configuration) { - discountTimestamp = discount.Timestamp.ToString(); + _RCSetupPurchases( + CustomerInfoReceived, + LogReceived, + NativeInteropCallback, + configuration.ApiKey, + configuration.AppUserId, + configuration.PurchasesAreCompletedBy.ToString(), + configuration.StoreKitVersion.Name(), + configuration.UserDefaultsSuiteName, + JsonConvert.SerializeObject(configuration.DangerousSettings), + configuration.ShouldShowInAppMessagesAutomatically, + configuration.EntitlementVerificationMode.ToString()); } - _RCPurchasePackage(packageToPurchase.Identifier, packageToPurchase.PresentedOfferingContext.ToJsonString(), discountTimestamp); - } - public void PurchaseSubscriptionOption(Purchases.SubscriptionOption subscriptionOption, - Purchases.GoogleProductChangeInfo googleProductChangeInfo = null, bool googleIsPersonalizedPrice = false) - { - // No-Op - } + [DllImport("__Internal")] + private static extern void _RCGetStorefront(); - [DllImport("__Internal")] - private static extern void _RCRestorePurchases(); - public void RestorePurchases() - { - _RCRestorePurchases(); - } + public Task GetStorefrontAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetStorefrontAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetStorefrontAsync)] = tcs; + _RCGetStorefront(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSyncPurchases(); - public void SyncPurchases() - { - _RCSyncPurchases(); - } - public void SyncAmazonPurchase(string productID, string receiptID, string amazonUserID, - string isoCurrencyCode, double price) - { - // No-Op - } + [DllImport("__Internal")] + private static extern void _RCGetProducts(string productIdentifiersJson, string type); - public void GetAmazonLWAConsentStatus() - { - // No-Op - } + public Task> GetProductsAsync( + string[] productIdentifiers, + string type = "subs", + CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetProductsAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource> pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource>(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetProductsAsync)] = tcs; + _RCGetProducts(JsonConvert.SerializeObject(new { productIdentifiers }), type); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCLogIn(string appUserId); - public void LogIn(string appUserId) - { - _RCLogIn(appUserId); - } - [DllImport("__Internal")] - private static extern void _RCLogOut(); - public void LogOut() - { - _RCLogOut(); - } + [DllImport("__Internal")] + private static extern void _RCPurchaseProduct(string productIdentifier, string signedDiscountTimestamp); - [DllImport("__Internal")] - private static extern void _RCSetAllowSharingStoreAccount(bool allow); - public void SetAllowSharingStoreAccount(bool allow) - { - _RCSetAllowSharingStoreAccount(allow); - } + public Task PurchaseProductAsync( + string productIdentifier, + string type = "subs", + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + string offeringIdentifier = null, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchaseProductAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(PurchaseProductAsync)] = tcs; + _RCPurchaseProduct(productIdentifier, discount?.TimestampUnixMilliseconds.ToString()); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetDebugLogsEnabled(bool enabled); - public void SetDebugLogsEnabled(bool enabled) - { - _RCSetDebugLogsEnabled(enabled); - } - [DllImport("__Internal")] - private static extern void _RCSetLogLevel(string level); - public void SetLogLevel(Purchases.LogLevel level) - { - _RCSetLogLevel(level.Name()); - } + [DllImport("__Internal")] + private static extern void _RCPurchasePackage(string packageIdentifier, string presentedOfferingContextJSON, string signedDiscountTimestamp); - [DllImport("__Internal")] - private static extern void _RCSetLogHandler(); - public void SetLogHandler() - { - _RCSetLogHandler(); - } + public Task PurchasePackageAsync( + Package packageToPurchase, + string oldSku = null, + ProrationMode prorationMode = ProrationMode.UnknownSubscriptionUpgradeDowngradePolicy, + bool googleIsPersonalizedPrice = false, + PromotionalOffer discount = null, + CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchasePackageAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(PurchasePackageAsync)] = tcs; + _RCPurchasePackage( + packageToPurchase.Identifier, + JsonConvert.SerializeObject(packageToPurchase.PresentedOfferingContext), + discount?.TimestampUnixMilliseconds.ToString()); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetProxyURLString(string proxyURL); - public void SetProxyURL(string proxyURL) - { - _RCSetProxyURLString(proxyURL); - } + public Task PurchaseSubscriptionOptionAsync( + SubscriptionOption subscriptionOption, + GoogleProductChangeInfo googleProductChangeInfo = null, + bool googleIsPersonalizedPrice = false, + CancellationToken cancellationToken = default) + { + // NOOP + return Task.FromResult(null); + } - [DllImport("__Internal")] - private static extern string _RCGetAppUserID(); - public string GetAppUserId() - { - return _RCGetAppUserID(); - } + [DllImport("__Internal")] + private static extern void _RCRestorePurchases(); - [DllImport("__Internal")] - private static extern void _RCGetCustomerInfo(); - public void GetCustomerInfo() - { - _RCGetCustomerInfo(); - } + public Task RestorePurchasesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(RestorePurchasesAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(RestorePurchasesAsync)] = tcs; + _RCRestorePurchases(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCGetOfferings(); - public void GetOfferings() - { - _RCGetOfferings(); - } + [DllImport("__Internal")] + private static extern void _RCLogIn(string appUserId); - [DllImport("__Internal")] - private static extern void _RCGetCurrentOfferingForPlacement(string placementIdentifier); - public void GetCurrentOfferingForPlacement(string placementIdentifier) - { - _RCGetCurrentOfferingForPlacement(placementIdentifier); - } + public Task LogInAsync(string appUserId, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(LogInAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(LogInAsync)] = tcs; + _RCLogIn(appUserId); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSyncAttributesAndOfferingsIfNeeded(); - public void SyncAttributesAndOfferingsIfNeeded() - { - _RCSyncAttributesAndOfferingsIfNeeded(); - } + [DllImport("__Internal")] + private static extern void _RCLogOut(); - [DllImport("__Internal")] - private static extern void _RCEnableAdServicesAttributionTokenCollection(); - public void EnableAdServicesAttributionTokenCollection() - { - _RCEnableAdServicesAttributionTokenCollection(); - } + public Task LogOutAsync() + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(LogOutAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + _pendingTasks[nameof(LogOutAsync)] = tcs; + _RCLogOut(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern bool _RCIsAnonymous(); - public bool IsAnonymous() - { - return _RCIsAnonymous(); - } + [DllImport("__Internal")] + private static extern void _RCSyncAttributesAndOfferingsIfNeeded(); - [DllImport("__Internal")] - private static extern bool _RCIsConfigured(); - public bool IsConfigured() - { - return _RCIsConfigured(); - } + public Task SyncAttributesAndOfferingsIfNeededAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(SyncAttributesAndOfferingsIfNeededAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(SyncAttributesAndOfferingsIfNeededAsync)] = tcs; + _RCSyncAttributesAndOfferingsIfNeeded(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCCheckTrialOrIntroductoryPriceEligibility(string productIdentifiersJson); - public void CheckTrialOrIntroductoryPriceEligibility(string[] productIdentifiers) - { - var request = new ProductsRequest + public void SyncAmazonPurchase( + string productID, + string receiptID, + string amazonUserID, + string isoCurrencyCode, + double price) { - productIdentifiers = productIdentifiers - }; + // No-Op + } - _RCCheckTrialOrIntroductoryPriceEligibility(JsonUtility.ToJson(request)); - } + public Task GetAmazonLWAConsentStatusAsync(CancellationToken cancellationToken = default) + { + // No-Op + return Task.FromResult(false); + } - [DllImport("__Internal")] - private static extern void _RCInvalidateCustomerInfoCache(); - public void InvalidateCustomerInfoCache() - { - _RCInvalidateCustomerInfoCache(); - } + [DllImport("__Internal")] + private static extern void _RCSetAllowSharingStoreAccount(bool allow); - [DllImport("__Internal")] - private static extern void _RCPresentCodeRedemptionSheet(); - public void PresentCodeRedemptionSheet() - { - _RCPresentCodeRedemptionSheet(); - } + public void SetAllowSharingStoreAccount(bool allow) + { + _RCSetAllowSharingStoreAccount(allow); + } - [DllImport("__Internal")] - private static extern void _RCRecordPurchase(string productID); - public void RecordPurchase(string productID) - { - _RCRecordPurchase(productID); - } + [DllImport("__Internal")] + private static extern void _RCGetOfferings(); - [DllImport("__Internal")] - private static extern void _RCSetSimulatesAskToBuyInSandbox(bool enabled); - public void SetSimulatesAskToBuyInSandbox(bool enabled) - { - _RCSetSimulatesAskToBuyInSandbox(enabled); - } + public Task GetOfferingsAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetOfferingsAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetOfferingsAsync)] = tcs; + _RCGetOfferings(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetAttributes(string attributesJson); - public void SetAttributes(string attributesJson) - { - _RCSetAttributes(attributesJson); - } + [DllImport("__Internal")] + private static extern void _RCGetCurrentOfferingForPlacement(string placementIdentifier); - [DllImport("__Internal")] - private static extern void _RCSetEmail(string email); - public void SetEmail(string email) - { - _RCSetEmail(email); - } + public Task GetCurrentOfferingForPlacementAsync(string placementIdentifier, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetCurrentOfferingForPlacementAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetCurrentOfferingForPlacementAsync)] = tcs; + _RCGetCurrentOfferingForPlacement(placementIdentifier); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetPhoneNumber(string phoneNumber); - public void SetPhoneNumber(string phoneNumber) - { - _RCSetPhoneNumber(phoneNumber); - } + [DllImport("__Internal")] + private static extern void _RCSetDebugLogsEnabled(bool enabled); - [DllImport("__Internal")] - private static extern void _RCSetDisplayName(string displayName); - public void SetDisplayName(string displayName) - { - _RCSetDisplayName(displayName); - } + public void SetDebugLogsEnabled(bool enabled) + { + _RCSetDebugLogsEnabled(enabled); + } - [DllImport("__Internal")] - private static extern void _RCSetPushToken(string token); - public void SetPushToken(string token) - { - _RCSetPushToken(token); - } + [DllImport("__Internal")] + private static extern void _RCSetLogLevel(string level); - [DllImport("__Internal")] - private static extern void _RCSetAdjustID(string adjustID); - public void SetAdjustID(string adjustID) - { - _RCSetAdjustID(adjustID); - } + public void SetLogLevel(LogLevel level) + { + _RCSetLogLevel(level.ToString()); + } - [DllImport("__Internal")] - private static extern void _RCSetAppsflyerID(string appsflyerID); - public void SetAppsflyerID(string appsflyerID) - { - _RCSetAppsflyerID(appsflyerID); - } + [DllImport("__Internal")] + private static extern void _RCSetProxyURLString(string proxyURL); - [DllImport("__Internal")] - private static extern void _RCSetFBAnonymousID(string fbAnonymousID); - public void SetFBAnonymousID(string fbAnonymousID) - { - _RCSetFBAnonymousID(fbAnonymousID); - } + public void SetProxyURL(string proxyURL) + { + _RCSetProxyURLString(proxyURL); + } - [DllImport("__Internal")] - private static extern void _RCSetMparticleID(string mparticleID); - public void SetMparticleID(string mparticleID) - { - _RCSetMparticleID(mparticleID); - } + [DllImport("__Internal")] + private static extern string _RCGetAppUserID(); - [DllImport("__Internal")] - private static extern void _RCSetOnesignalID(string onesignalID); - public void SetOnesignalID(string onesignalID) - { - _RCSetOnesignalID(onesignalID); - } + public string GetAppUserId() + { + try + { + return _RCGetAppUserID(); + } + catch (Exception e) + { + Debug.LogError(e); + return null; + } + } - [DllImport("__Internal")] - private static extern void _RCSetAirshipChannelID(string airshipChannelID); - public void SetAirshipChannelID(string airshipChannelID) - { - _RCSetAirshipChannelID(airshipChannelID); - } + [DllImport("__Internal")] + private static extern void _RCGetCustomerInfo(); - [DllImport("__Internal")] - private static extern void _RCSetCleverTapID(string cleverTapID); - public void SetCleverTapID(string cleverTapID) - { - _RCSetCleverTapID(cleverTapID); - } + public Task GetCustomerInfoAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetCustomerInfoAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetCustomerInfoAsync)] = tcs; + _RCGetCustomerInfo(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetMixpanelDistinctID(string mixpanelDistinctID); - public void SetMixpanelDistinctID(string mixpanelDistinctID) - { - _RCSetMixpanelDistinctID(mixpanelDistinctID); - } + [DllImport("__Internal")] + private static extern void _RCSyncPurchases(); - [DllImport("__Internal")] - private static extern void _RCSetFirebaseAppInstanceID(string firebaseAppInstanceID); - public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) - { - _RCSetFirebaseAppInstanceID(firebaseAppInstanceID); - } + public Task SyncPurchasesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(SyncPurchasesAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(SyncPurchasesAsync)] = tcs; + _RCSyncPurchases(); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCSetMediaSource(string mediaSource); - public void SetMediaSource(string mediaSource) - { - _RCSetMediaSource(mediaSource); - } + [DllImport("__Internal")] + private static extern void _RCEnableAdServicesAttributionTokenCollection(); - [DllImport("__Internal")] - private static extern void _RCSetCampaign(string campaign); - public void SetCampaign(string campaign) - { - _RCSetCampaign(campaign); - } + public void EnableAdServicesAttributionTokenCollection() + { + _RCEnableAdServicesAttributionTokenCollection(); + } - [DllImport("__Internal")] - private static extern void _RCSetAdGroup(string adGroup); - public void SetAdGroup(string adGroup) - { - _RCSetAdGroup(adGroup); - } + [DllImport("__Internal")] + private static extern bool _RCIsAnonymous(); - [DllImport("__Internal")] - private static extern void _RCSetAd(string ad); - public void SetAd(string ad) - { - _RCSetAd(ad); - } + public bool IsAnonymous() + { + try + { + return _RCIsAnonymous(); + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + } - [DllImport("__Internal")] - private static extern void _RCSetKeyword(string keyword); - public void SetKeyword(string keyword) - { - _RCSetKeyword(keyword); - } + [DllImport("__Internal")] + private static extern bool _RCIsConfigured(); - [DllImport("__Internal")] - private static extern void _RCSetCreative(string creative); - public void SetCreative(string creative) - { - _RCSetCreative(creative); - } + public bool IsConfigured() + { + try + { + return _RCIsConfigured(); + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + } - [DllImport("__Internal")] - private static extern void _RCCollectDeviceIdentifiers(); - public void CollectDeviceIdentifiers() - { - _RCCollectDeviceIdentifiers(); - } + [DllImport("__Internal")] + private static extern void _RCCheckTrialOrIntroductoryPriceEligibility(string productIdentifiersJson); - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class CanMakePaymentsRequest - { - public int[] features; - } + public Task> CheckTrialOrIntroductoryPriceEligibilityAsync( + string[] productIdentifiers, + CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(CheckTrialOrIntroductoryPriceEligibilityAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource> pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource>(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(CheckTrialOrIntroductoryPriceEligibilityAsync)] = tcs; + _RCCheckTrialOrIntroductoryPriceEligibility(JsonConvert.SerializeObject(new { productIdentifiers })); + return tcs.Task; + } + } - [DllImport("__Internal")] - private static extern void _RCCanMakePayments(string featuresJson); - public void CanMakePayments(Purchases.BillingFeature[] features) - { - int[] featuresAsInts = new int[features.Length]; - for (int i = 0; i < features.Length; i++) { - Purchases.BillingFeature feature = features[i]; - featuresAsInts[i] = (int)feature; + [DllImport("__Internal")] + private static extern void _RCInvalidateCustomerInfoCache(); + + public void InvalidateCustomerInfoCache() + { + _RCInvalidateCustomerInfoCache(); } - var request = new CanMakePaymentsRequest + [DllImport("__Internal")] + private static extern void _RCPresentCodeRedemptionSheet(); + + public void PresentCodeRedemptionSheet() { - features = featuresAsInts - }; + _RCPresentCodeRedemptionSheet(); + } - _RCCanMakePayments(JsonUtility.ToJson(request)); - } + [DllImport("__Internal")] + private static extern void _RCRecordPurchase(string productID); - [DllImport("__Internal")] - private static extern void _RCGetPromotionalOffer(string productIdentifier, string discountIdentifier); - public void GetPromotionalOffer(string productIdentifier, string discountIdentifier) - { - _RCGetPromotionalOffer(productIdentifier, discountIdentifier); - } + public void RecordPurchase(string productID) + { + _RCRecordPurchase(productID); + } - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private class ShowInAppMessagesRequest - { - public int[] messageTypes; - } + [DllImport("__Internal")] + private static extern void _RCSetSimulatesAskToBuyInSandbox(bool enabled); - [DllImport("__Internal")] - private static extern void _RCShowInAppMessages(string messagesJson); - public void ShowInAppMessages(Purchases.InAppMessageType[] messageTypes) - { - int[] messageTypesAsInts = new int[messageTypes.Length]; - for (int i = 0; i < messageTypes.Length; i++) { - Purchases.InAppMessageType messageType = messageTypes[i]; - messageTypesAsInts[i] = (int)messageType; + public void SetSimulatesAskToBuyInSandbox(bool enabled) + { + _RCSetSimulatesAskToBuyInSandbox(enabled); } - var request = new ShowInAppMessagesRequest + public void SetAttributes(Dictionary attributes) { - messageTypes = messageTypesAsInts - }; + _RCSetAttributes(JsonConvert.SerializeObject(attributes)); + } - _RCShowInAppMessages(JsonUtility.ToJson(request)); - } + [DllImport("__Internal")] + private static extern void _RCSetAttributes(string attributesJson); - [DllImport("__Internal")] - private static extern void _RCParseAsWebPurchaseRedemption(string urlString); - public void ParseAsWebPurchaseRedemption(string urlString) - { - _RCParseAsWebPurchaseRedemption(urlString); - } + [DllImport("__Internal")] + private static extern void _RCSetEmail(string email); - [DllImport("__Internal")] - private static extern void _RCRedeemWebPurchase(string resultJson); - public void RedeemWebPurchase(Purchases.WebPurchaseRedemption webPurchaseRedemption) - { - _RCRedeemWebPurchase(webPurchaseRedemption.RedemptionLink); - } + public void SetEmail(string email) + { + _RCSetEmail(email); + } - [DllImport("__Internal")] - private static extern void _RCGetVirtualCurrencies(); - public void GetVirtualCurrencies() - { - _RCGetVirtualCurrencies(); - } + [DllImport("__Internal")] + private static extern void _RCSetPhoneNumber(string phoneNumber); - [DllImport("__Internal")] - private static extern string _RCGetCachedVirtualCurrencies(); - public string GetCachedVirtualCurrencies() - { - return _RCGetCachedVirtualCurrencies(); - } + public void SetPhoneNumber(string phoneNumber) + { + _RCSetPhoneNumber(phoneNumber); + } - [DllImport("__Internal")] - private static extern void _RCInvalidateVirtualCurrenciesCache(); - public void InvalidateVirtualCurrenciesCache() - { - _RCInvalidateVirtualCurrenciesCache(); - } + [DllImport("__Internal")] + private static extern void _RCSetDisplayName(string displayName); - [DllImport("__Internal")] - private static extern void _RCGetEligibleWinBackOffersForProduct(string productIdentifier); - public void GetEligibleWinBackOffersForProduct(Purchases.StoreProduct storeProduct) - { - _RCGetEligibleWinBackOffersForProduct(storeProduct.Identifier); - } + public void SetDisplayName(string displayName) + { + _RCSetDisplayName(displayName); + } - [DllImport("__Internal")] - private static extern void _RCGetEligibleWinBackOffersForPackage(string productIdentifier); - public void GetEligibleWinBackOffersForPackage(Purchases.Package package) - { - _RCGetEligibleWinBackOffersForPackage(package.StoreProduct.Identifier); - } + [DllImport("__Internal")] + private static extern void _RCSetPushToken(string token); - [DllImport("__Internal")] - private static extern void _RCPurchaseProductWithWinBackOffer(string productIdentifier, string winBackOfferIdentifier); - public void PurchaseProductWithWinBackOffer(Purchases.StoreProduct storeProduct, Purchases.WinBackOffer winBackOffer) - { - _RCPurchaseProductWithWinBackOffer(storeProduct.Identifier, winBackOffer.Identifier); - } + public void SetPushToken(string token) + { + _RCSetPushToken(token); + } - [DllImport("__Internal")] - private static extern void _RCPurchasePackageWithWinBackOffer(string packageIdentifier, string presentedOfferingContextJson, string winBackOfferIdentifier); - public void PurchasePackageWithWinBackOffer(Purchases.Package package, Purchases.WinBackOffer winBackOffer) - { - _RCPurchasePackageWithWinBackOffer(package.Identifier, package.PresentedOfferingContext.ToJsonString(), winBackOffer.Identifier); + [DllImport("__Internal")] + private static extern void _RCSetAdjustID(string adjustID); + + public void SetAdjustID(string adjustID) + { + _RCSetAdjustID(adjustID); + } + + [DllImport("__Internal")] + private static extern void _RCSetAppsflyerID(string appsflyerID); + + public void SetAppsflyerID(string appsflyerID) + { + _RCSetAppsflyerID(appsflyerID); + } + + [DllImport("__Internal")] + private static extern void _RCSetFBAnonymousID(string fbAnonymousID); + + public void SetFBAnonymousID(string fbAnonymousID) + { + _RCSetFBAnonymousID(fbAnonymousID); + } + + [DllImport("__Internal")] + private static extern void _RCSetMparticleID(string mparticleID); + + public void SetMparticleID(string mparticleID) + { + _RCSetMparticleID(mparticleID); + } + + [DllImport("__Internal")] + private static extern void _RCSetOnesignalID(string onesignalID); + + public void SetOnesignalID(string onesignalID) + { + _RCSetOnesignalID(onesignalID); + } + + [DllImport("__Internal")] + private static extern void _RCSetAirshipChannelID(string airshipChannelID); + + public void SetAirshipChannelID(string airshipChannelID) + { + _RCSetAirshipChannelID(airshipChannelID); + } + + [DllImport("__Internal")] + private static extern void _RCSetCleverTapID(string cleverTapID); + + public void SetCleverTapID(string cleverTapID) + { + _RCSetCleverTapID(cleverTapID); + } + + [DllImport("__Internal")] + private static extern void _RCSetMixpanelDistinctID(string mixpanelDistinctID); + + public void SetMixpanelDistinctID(string mixpanelDistinctID) + { + _RCSetMixpanelDistinctID(mixpanelDistinctID); + } + + [DllImport("__Internal")] + private static extern void _RCSetFirebaseAppInstanceID(string firebaseAppInstanceID); + + public void SetFirebaseAppInstanceID(string firebaseAppInstanceID) + { + _RCSetFirebaseAppInstanceID(firebaseAppInstanceID); + } + + [DllImport("__Internal")] + private static extern void _RCSetMediaSource(string mediaSource); + + public void SetMediaSource(string mediaSource) + { + _RCSetMediaSource(mediaSource); + } + + [DllImport("__Internal")] + private static extern void _RCSetCampaign(string campaign); + + public void SetCampaign(string campaign) + { + _RCSetCampaign(campaign); + } + + [DllImport("__Internal")] + private static extern void _RCSetAdGroup(string adGroup); + + public void SetAdGroup(string adGroup) + { + _RCSetAdGroup(adGroup); + } + + [DllImport("__Internal")] + private static extern void _RCSetAd(string ad); + + public void SetAd(string ad) + { + _RCSetAd(ad); + } + + [DllImport("__Internal")] + private static extern void _RCSetKeyword(string keyword); + + public void SetKeyword(string keyword) + { + _RCSetKeyword(keyword); + } + + [DllImport("__Internal")] + private static extern void _RCSetCreative(string creative); + + public void SetCreative(string creative) + { + _RCSetCreative(creative); + } + + [DllImport("__Internal")] + private static extern void _RCCollectDeviceIdentifiers(); + + public void CollectDeviceIdentifiers() + { + _RCCollectDeviceIdentifiers(); + } + + [DllImport("__Internal")] + private static extern void _RCCanMakePayments(string featuresJson); + + public Task CanMakePaymentsAsync(BillingFeature[] features, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(CanMakePaymentsAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(CanMakePaymentsAsync)] = tcs; + _RCCanMakePayments(JsonConvert.SerializeObject(new { features })); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCGetPromotionalOffer(string productIdentifier, string discountIdentifier); + + public Task GetPromotionalOfferAsync(string productIdentifier, string discountIdentifier, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetPromotionalOfferAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + _pendingTasks[nameof(GetPromotionalOfferAsync)] = tcs; + _RCGetPromotionalOffer(productIdentifier, discountIdentifier); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCShowInAppMessages(string messagesJson); + + public void ShowInAppMessages(InAppMessageType[] messageTypes) + { + _RCShowInAppMessages(JsonConvert.SerializeObject(messageTypes)); + } + + [DllImport("__Internal")] + private static extern void _RCParseAsWebPurchaseRedemption(string urlString); + + public Task ParseAsWebPurchaseRedemptionAsync(string urlString, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(ParseAsWebPurchaseRedemptionAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(ParseAsWebPurchaseRedemptionAsync)] = tcs; + _RCParseAsWebPurchaseRedemption(urlString); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCRedeemWebPurchase(string resultJson); + + public Task RedeemWebPurchaseAsync(WebPurchaseRedemption webPurchaseRedemption, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(RedeemWebPurchaseAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(RedeemWebPurchaseAsync)] = tcs; + _RCRedeemWebPurchase(webPurchaseRedemption.RedemptionLink); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCGetVirtualCurrencies(); + + public Task GetVirtualCurrenciesAsync(CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetVirtualCurrenciesAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetVirtualCurrenciesAsync)] = tcs; + _RCGetVirtualCurrencies(); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern string _RCGetCachedVirtualCurrencies(); + + public VirtualCurrencies GetCachedVirtualCurrencies() + { + try + { + var json = _RCGetCachedVirtualCurrencies(); + return !string.IsNullOrWhiteSpace(json) + ? JsonConvert.DeserializeObject(json) + : null; + } + catch (Exception e) + { + Debug.LogError(e); + return null; + } + } + + [DllImport("__Internal")] + private static extern void _RCInvalidateVirtualCurrenciesCache(); + + public void InvalidateVirtualCurrenciesCache() + { + _RCInvalidateVirtualCurrenciesCache(); + } + + [DllImport("__Internal")] + private static extern void _RCGetEligibleWinBackOffersForProduct(string productIdentifier); + + public Task> GetEligibleWinBackOffersForProductAsync(StoreProduct storeProduct, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetEligibleWinBackOffersForProductAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource> pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource>(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetEligibleWinBackOffersForProductAsync)] = tcs; + _RCGetEligibleWinBackOffersForProduct(storeProduct.Identifier); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCGetEligibleWinBackOffersForPackage(string productIdentifier); + + public Task> GetEligibleWinBackOffersForPackageAsync(Package package, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(GetEligibleWinBackOffersForPackageAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource> pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource>(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(GetEligibleWinBackOffersForPackageAsync)] = tcs; + _RCGetEligibleWinBackOffersForPackage(package.Identifier); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCPurchaseProductWithWinBackOffer(string productIdentifier, string winBackOfferIdentifier); + + public Task PurchaseProductWithWinBackOfferAsync(StoreProduct storeProduct, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchaseProductWithWinBackOfferAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(PurchaseProductWithWinBackOfferAsync)] = tcs; + _RCPurchaseProductWithWinBackOffer(storeProduct.Identifier, winBackOffer.Identifier); + return tcs.Task; + } + } + + [DllImport("__Internal")] + private static extern void _RCPurchasePackageWithWinBackOffer( + string packageIdentifier, + string presentedOfferingContextJson, + string winBackOfferIdentifier); + + public Task PurchasePackageWithWinBackOfferAsync(Package package, WinBackOffer winBackOffer, CancellationToken cancellationToken = default) + { + lock (_lock) + { + if (_pendingTasks.TryGetValue(nameof(PurchasePackageWithWinBackOfferAsync), out var pendingTcs) && + pendingTcs is TaskCompletionSource pending) + { + return pending.Task; + } + var tcs = new TaskCompletionSource(); + cancellationToken.Register(() => tcs.SetCanceled()); + _pendingTasks[nameof(PurchasePackageWithWinBackOfferAsync)] = tcs; + _RCPurchasePackageWithWinBackOffer( + package.Identifier, + JsonConvert.SerializeObject(package.PresentedOfferingContext), + winBackOffer.Identifier); + return tcs.Task; + } + } } } -#endif +#endif // UNITY_IOS || UNITY_VISIONOS diff --git a/RevenueCat/Scripts/RevenueCatConfig.cs b/RevenueCat/Scripts/RevenueCatConfig.cs new file mode 100644 index 00000000..083ad23e --- /dev/null +++ b/RevenueCat/Scripts/RevenueCatConfig.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace RevenueCat +{ + [CreateAssetMenu(fileName = "RevenueCatConfig.asset", menuName = "RevenueCat/RevenueCatConfig")] + public class RevenueCatConfig : ScriptableObject + { + public static RevenueCatConfig Instance + { + get + { + if (_instance == null) + { + _instance = Resources.Load(nameof(RevenueCatConfig)); + } + +#if UNITY_EDITOR + if (_instance == null) + { + _instance = CreateInstance(); + const string resourcesDirectory = "Assets/Resources"; + + if (!System.IO.Directory.Exists(resourcesDirectory)) + { + System.IO.Directory.CreateDirectory(resourcesDirectory); + } + + var assetPath = System.IO.Path.Combine(resourcesDirectory, "RevenueCatConfig.asset"); + UnityEditor.AssetDatabase.CreateAsset(_instance, assetPath); + } +#endif // UNITY_EDITOR + return _instance; + } + } + private static RevenueCatConfig _instance; + +#if UNITY_EDITOR + private void OnValidate() + { + // always make sure an instance of this config exists when the Editor loads + NUnit.Framework.Assert.NotNull(Instance); + } +#endif // UNITY_EDITOR + + [field: SerializeField] + [field: Tooltip("RevenueCat API Key specifically for Apple platforms.\nGet from https://app.revenuecat.com/ \n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public string RevenueCatApiKeyApple { get; private set; } + + [field: SerializeField] + [field: Tooltip("RevenueCat API Key specifically for Google Play.\nGet from https://app.revenuecat.com/ \n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public string RevenueCatApiKeyGoogle { get; private set; } + + [field: SerializeField] + [field: Tooltip("RevenueCat API Key specifically for Amazon Appstore.\nGet from https://app.revenuecat.com/ \n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public string RevenueCatApiKeyAmazon { get; private set; } + + [field: SerializeField] + [field: Tooltip("Enables Amazon Store support. Android only, on iOS it has no effect.\n" + + "If enabled, it will use the API key in RevenueCatAPIKeyAmazon.\n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + + public bool UseAmazon { get; private set; } + + public List ProductIdentifiers => productIdentifiers; + + [SerializeField] + [Tooltip("List of product identifiers.")] + private List productIdentifiers; + + [field: SerializeField] + [field: Tooltip("An optional string. iOS only.\n" + + "Set this to use a specific NSUserDefaults suite for RevenueCat. " + + "This might be handy if you are deleting all NSUserDefaults in your app " + + "and leaving RevenueCat in a bad state.\n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public string UserDefaultsSuiteName { get; private set; } + + [field: SerializeField] + [field: Tooltip("Set this to MyApp and provide a StoreKitVersion if you have your own IAP implementation and\n" + + "want to only use RevenueCat's backend. Defaults to PurchasesAreCompletedBy.RevenueCat\n." + + "If you are on Android and setting this to MyApp, you will have to acknowledge the purchases yourself.\n" + + "If your app is only on Android, you may specify any StoreKit version, as it is ignored by the Android SDK.")] + public PurchasesAreCompletedBy PurchasesAreCompletedBy { get; private set; } = PurchasesAreCompletedBy.REVENUECAT; + + [field: SerializeField] + [field: Tooltip("Version of StoreKit to use in iOS. By default, RevenueCat will decide for you.\n" + + "Set this if you're setting PurchasesAreCompletedBy to MyApp.")] + public StoreKitVersion StoreKitVersion { get; private set; } = StoreKitVersion.Default; + + [field: SerializeField] + [field: Tooltip("Whether we should show store in-app messages automatically. Both Google Play and the App Store provide in-app " + + "messages for some situations like billing issues. By default, those messages will be shown automatically.\n" + + "This allows to disable that behavior, so you can display those messages at your convenience. For more information, " + + "check: https://rev.cat/storekit-message and https://rev.cat/googleplayinappmessaging")] + public bool ShouldShowInAppMessagesAutomatically { get; private set; } = true; + + [field: SerializeField] + [field: Tooltip("The entitlement verification mode to use. For more information, check: https://rev.cat/trusted-entitlements")] + public EntitlementVerificationMode EntitlementVerificationMode { get; private set; } = EntitlementVerificationMode.DISABLED; + + [field: SerializeField] + [field: Tooltip("Enable this setting if you want to allow pending purchases for prepaid subscriptions (only supported " + + "in Google Play). Note that entitlements are not granted until payment is done. Disabled by default.")] + public bool PendingTransactionsForPrepaidPlansEnabled { get; private set; } + + [field: Header("Advanced")] + [field: SerializeField] + [field: Tooltip("Set this property to your proxy URL before configuring Purchases *only* if you've received " + + "a proxy key value from your RevenueCat contact.\n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public string ProxyUrl { get; private set; } + + [field: Header("Dangerous Settings")] + [field: SerializeField] + [field: Tooltip("Disable or enable automatically detecting current subscriptions.\n" + + "If this is disabled, RevenueCat won't check current purchases, and it will not sync any purchase automatically " + + "when the app starts.\nCall syncPurchases whenever a new purchase is detected so the receipt is sent to " + + "RevenueCat's backend.\n" + + "In iOS, consumables disappear from the receipt after the transaction is finished, so make sure purchases " + + "are synced before finishing any consumable transaction, otherwise RevenueCat won't register the purchase.\n" + + "Auto syncing of purchases is enabled by default.\n" + + "NOTE: This value will be ignored if \"Use Runtime Setup\" is true. For Runtime Setup, you can configure " + + "it through PurchasesConfiguration instead")] + public bool AutoSyncPurchases { get; private set; } = true; + } +} \ No newline at end of file diff --git a/RevenueCat/Scripts/RevenueCatConfig.cs.meta b/RevenueCat/Scripts/RevenueCatConfig.cs.meta new file mode 100644 index 00000000..0f2ba48b --- /dev/null +++ b/RevenueCat/Scripts/RevenueCatConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e57b5e91c73f2854892541d1a8cf0323 \ No newline at end of file diff --git a/RevenueCat/Scripts/RevenueCatLogMessage.cs b/RevenueCat/Scripts/RevenueCatLogMessage.cs new file mode 100644 index 00000000..097eaf1c --- /dev/null +++ b/RevenueCat/Scripts/RevenueCatLogMessage.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace RevenueCat +{ + public class RevenueCatLogMessage + { + public LogLevel Level { get; } + + public string Message { get; } + + [JsonConstructor] + internal RevenueCatLogMessage(LogLevel level, string message) + { + Level = level; + Message = message; + } + } +} diff --git a/RevenueCat/Scripts/RevenueCatLogMessage.cs.meta b/RevenueCat/Scripts/RevenueCatLogMessage.cs.meta new file mode 100644 index 00000000..b2705528 --- /dev/null +++ b/RevenueCat/Scripts/RevenueCatLogMessage.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bb40cb59f01365f49bb95dfac6f84ae1 \ No newline at end of file diff --git a/RevenueCat/Scripts/SimpleJSON.cs b/RevenueCat/Scripts/SimpleJSON.cs deleted file mode 100644 index 92644ce1..00000000 --- a/RevenueCat/Scripts/SimpleJSON.cs +++ /dev/null @@ -1,1352 +0,0 @@ -/* * * * * - * A simple JSON Parser / builder - * ------------------------------ - * - * It mainly has been written as a simple JSON parser. It can build a JSON string - * from the node-tree, or generate a node tree from any valid JSON string. - * - * Written by Bunny83 - * 2012-06-09 - * - * Changelog now external. See Changelog.txt - * - * The MIT License (MIT) - * - * Copyright (c) 2012-2019 Markus Göbel (Bunny83) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * * * * */ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace RevenueCat.SimpleJSON -{ - public enum JSONNodeType - { - Array = 1, - Object = 2, - String = 3, - Number = 4, - NullValue = 5, - Boolean = 6, - None = 7, - Custom = 0xFF, - } - public enum JSONTextMode - { - Compact, - Indent - } - - public abstract partial class JSONNode - { - #region Enumerators - public struct Enumerator - { - private enum Type { None, Array, Object } - private Type type; - private Dictionary.Enumerator m_Object; - private List.Enumerator m_Array; - public bool IsValid { get { return type != Type.None; } } - public Enumerator(List.Enumerator aArrayEnum) - { - type = Type.Array; - m_Object = default(Dictionary.Enumerator); - m_Array = aArrayEnum; - } - public Enumerator(Dictionary.Enumerator aDictEnum) - { - type = Type.Object; - m_Object = aDictEnum; - m_Array = default(List.Enumerator); - } - public KeyValuePair Current - { - get - { - if (type == Type.Array) - return new KeyValuePair(string.Empty, m_Array.Current); - else if (type == Type.Object) - return m_Object.Current; - return new KeyValuePair(string.Empty, null); - } - } - public bool MoveNext() - { - if (type == Type.Array) - return m_Array.MoveNext(); - else if (type == Type.Object) - return m_Object.MoveNext(); - return false; - } - } - public struct ValueEnumerator - { - private Enumerator m_Enumerator; - public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } - public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } - public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } - public JSONNode Current { get { return m_Enumerator.Current.Value; } } - public bool MoveNext() { return m_Enumerator.MoveNext(); } - public ValueEnumerator GetEnumerator() { return this; } - } - public struct KeyEnumerator - { - private Enumerator m_Enumerator; - public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } - public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } - public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } - public string Current { get { return m_Enumerator.Current.Key; } } - public bool MoveNext() { return m_Enumerator.MoveNext(); } - public KeyEnumerator GetEnumerator() { return this; } - } - - public class LinqEnumerator : IEnumerator>, IEnumerable> - { - private JSONNode m_Node; - private Enumerator m_Enumerator; - internal LinqEnumerator(JSONNode aNode) - { - m_Node = aNode; - if (m_Node != null) - m_Enumerator = m_Node.GetEnumerator(); - } - public KeyValuePair Current { get { return m_Enumerator.Current; } } - object IEnumerator.Current { get { return m_Enumerator.Current; } } - public bool MoveNext() { return m_Enumerator.MoveNext(); } - - public void Dispose() - { - m_Node = null; - m_Enumerator = new Enumerator(); - } - - public IEnumerator> GetEnumerator() - { - return new LinqEnumerator(m_Node); - } - - public void Reset() - { - if (m_Node != null) - m_Enumerator = m_Node.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return new LinqEnumerator(m_Node); - } - } - - #endregion Enumerators - - #region common interface - - public static bool forceASCII = false; // Use Unicode by default - public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber - public static bool allowLineComments = true; // allow "//"-style comments at the end of a line - - public abstract JSONNodeType Tag { get; } - - public virtual JSONNode this[int aIndex] { get { return null; } set { } } - - public virtual JSONNode this[string aKey] { get { return null; } set { } } - - public virtual string Value { get { return ""; } set { } } - - public virtual int Count { get { return 0; } } - - public virtual bool IsNumber { get { return false; } } - public virtual bool IsString { get { return false; } } - public virtual bool IsBoolean { get { return false; } } - public virtual bool IsNull { get { return false; } } - public virtual bool IsArray { get { return false; } } - public virtual bool IsObject { get { return false; } } - - public virtual bool Inline { get { return false; } set { } } - - public virtual void Add(string aKey, JSONNode aItem) - { - } - public virtual void Add(JSONNode aItem) - { - Add("", aItem); - } - - public virtual JSONNode Remove(string aKey) - { - return null; - } - - public virtual JSONNode Remove(int aIndex) - { - return null; - } - - public virtual JSONNode Remove(JSONNode aNode) - { - return aNode; - } - - public virtual JSONNode Clone() - { - return null; - } - - public virtual IEnumerable Children - { - get - { - yield break; - } - } - - public IEnumerable DeepChildren - { - get - { - foreach (var C in Children) - foreach (var D in C.DeepChildren) - yield return D; - } - } - - public virtual bool HasKey(string aKey) - { - return false; - } - - public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) - { - return aDefault; - } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); - return sb.ToString(); - } - - public virtual string ToString(int aIndent) - { - StringBuilder sb = new StringBuilder(); - WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); - return sb.ToString(); - } - internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); - - public abstract Enumerator GetEnumerator(); - public IEnumerable> Linq { get { return new LinqEnumerator(this); } } - public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } } - public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } } - - #endregion common interface - - #region typecasting properties - - - public virtual double AsDouble - { - get - { - double v = 0.0; - if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) - return v; - return 0.0; - } - set - { - Value = value.ToString(CultureInfo.InvariantCulture); - } - } - - public virtual int AsInt - { - get { return (int)AsDouble; } - set { AsDouble = value; } - } - - public virtual float AsFloat - { - get { return (float)AsDouble; } - set { AsDouble = value; } - } - - public virtual bool AsBool - { - get - { - bool v = false; - if (bool.TryParse(Value, out v)) - return v; - return !string.IsNullOrEmpty(Value); - } - set - { - Value = (value) ? "true" : "false"; - } - } - - public virtual long AsLong - { - get - { - long val = 0; - if (long.TryParse(Value, out val)) - return val; - return 0L; - } - set - { - Value = value.ToString(); - } - } - - public virtual JSONArray AsArray - { - get - { - return this as JSONArray; - } - } - - public virtual JSONObject AsObject - { - get - { - return this as JSONObject; - } - } - - - #endregion typecasting properties - - #region operators - - public static implicit operator JSONNode(string s) - { - return new JSONString(s); - } - public static implicit operator string(JSONNode d) - { - return (d == null) ? null : d.Value; - } - - public static implicit operator JSONNode(double n) - { - return new JSONNumber(n); - } - public static implicit operator double(JSONNode d) - { - return (d == null) ? 0 : d.AsDouble; - } - - public static implicit operator JSONNode(float n) - { - return new JSONNumber(n); - } - public static implicit operator float(JSONNode d) - { - return (d == null) ? 0 : d.AsFloat; - } - - public static implicit operator JSONNode(int n) - { - return new JSONNumber(n); - } - public static implicit operator int(JSONNode d) - { - return (d == null) ? 0 : d.AsInt; - } - - public static implicit operator JSONNode(long n) - { - if (longAsString) - return new JSONString(n.ToString()); - return new JSONNumber(n); - } - public static implicit operator long(JSONNode d) - { - return (d == null) ? 0L : d.AsLong; - } - - public static implicit operator JSONNode(bool b) - { - return new JSONBool(b); - } - public static implicit operator bool(JSONNode d) - { - return (d == null) ? false : d.AsBool; - } - - public static implicit operator JSONNode(KeyValuePair aKeyValue) - { - return aKeyValue.Value; - } - - public static bool operator ==(JSONNode a, object b) - { - if (ReferenceEquals(a, b)) - return true; - bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; - bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; - if (aIsNull && bIsNull) - return true; - return !aIsNull && a.Equals(b); - } - - public static bool operator !=(JSONNode a, object b) - { - return !(a == b); - } - - public override bool Equals(object obj) - { - return ReferenceEquals(this, obj); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - #endregion operators - - [ThreadStatic] - private static StringBuilder m_EscapeBuilder; - internal static StringBuilder EscapeBuilder - { - get - { - if (m_EscapeBuilder == null) - m_EscapeBuilder = new StringBuilder(); - return m_EscapeBuilder; - } - } - internal static string Escape(string aText) - { - var sb = EscapeBuilder; - sb.Length = 0; - if (sb.Capacity < aText.Length + aText.Length / 10) - sb.Capacity = aText.Length + aText.Length / 10; - foreach (char c in aText) - { - switch (c) - { - case '\\': - sb.Append("\\\\"); - break; - case '\"': - sb.Append("\\\""); - break; - case '\n': - sb.Append("\\n"); - break; - case '\r': - sb.Append("\\r"); - break; - case '\t': - sb.Append("\\t"); - break; - case '\b': - sb.Append("\\b"); - break; - case '\f': - sb.Append("\\f"); - break; - default: - if (c < ' ' || (forceASCII && c > 127)) - { - ushort val = c; - sb.Append("\\u").Append(val.ToString("X4")); - } - else - sb.Append(c); - break; - } - } - string result = sb.ToString(); - sb.Length = 0; - return result; - } - - private static JSONNode ParseElement(string token, bool quoted) - { - if (quoted) - return token; - string tmp = token.ToLower(); - if (tmp == "false" || tmp == "true") - return tmp == "true"; - if (tmp == "null") - return JSONNull.CreateOrGet(); - double val; - if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) - return val; - else - return token; - } - - public static JSONNode Parse(string aJSON) - { - Stack stack = new Stack(); - JSONNode ctx = null; - int i = 0; - StringBuilder Token = new StringBuilder(); - string TokenName = ""; - bool QuoteMode = false; - bool TokenIsQuoted = false; - while (i < aJSON.Length) - { - switch (aJSON[i]) - { - case '{': - if (QuoteMode) - { - Token.Append(aJSON[i]); - break; - } - stack.Push(new JSONObject()); - if (ctx != null) - { - ctx.Add(TokenName, stack.Peek()); - } - TokenName = ""; - Token.Length = 0; - ctx = stack.Peek(); - break; - - case '[': - if (QuoteMode) - { - Token.Append(aJSON[i]); - break; - } - - stack.Push(new JSONArray()); - if (ctx != null) - { - ctx.Add(TokenName, stack.Peek()); - } - TokenName = ""; - Token.Length = 0; - ctx = stack.Peek(); - break; - - case '}': - case ']': - if (QuoteMode) - { - - Token.Append(aJSON[i]); - break; - } - if (stack.Count == 0) - throw new Exception("JSON Parse: Too many closing brackets"); - - stack.Pop(); - if (Token.Length > 0 || TokenIsQuoted) - ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); - TokenIsQuoted = false; - TokenName = ""; - Token.Length = 0; - if (stack.Count > 0) - ctx = stack.Peek(); - break; - - case ':': - if (QuoteMode) - { - Token.Append(aJSON[i]); - break; - } - TokenName = Token.ToString(); - Token.Length = 0; - TokenIsQuoted = false; - break; - - case '"': - QuoteMode ^= true; - TokenIsQuoted |= QuoteMode; - break; - - case ',': - if (QuoteMode) - { - Token.Append(aJSON[i]); - break; - } - if (Token.Length > 0 || TokenIsQuoted) - ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); - TokenIsQuoted = false; - TokenName = ""; - Token.Length = 0; - TokenIsQuoted = false; - break; - - case '\r': - case '\n': - break; - - case ' ': - case '\t': - if (QuoteMode) - Token.Append(aJSON[i]); - break; - - case '\\': - ++i; - if (QuoteMode) - { - char C = aJSON[i]; - switch (C) - { - case 't': - Token.Append('\t'); - break; - case 'r': - Token.Append('\r'); - break; - case 'n': - Token.Append('\n'); - break; - case 'b': - Token.Append('\b'); - break; - case 'f': - Token.Append('\f'); - break; - case 'u': - { - string s = aJSON.Substring(i + 1, 4); - Token.Append((char)int.Parse( - s, - System.Globalization.NumberStyles.AllowHexSpecifier)); - i += 4; - break; - } - default: - Token.Append(C); - break; - } - } - break; - case '/': - if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i + 1] == '/') - { - while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ; - break; - } - Token.Append(aJSON[i]); - break; - case '\uFEFF': // remove / ignore BOM (Byte Order Mark) - break; - - default: - Token.Append(aJSON[i]); - break; - } - ++i; - } - if (QuoteMode) - { - throw new Exception("JSON Parse: Quotation marks seems to be messed up."); - } - if (ctx == null) - return ParseElement(Token.ToString(), TokenIsQuoted); - return ctx; - } - - } - // End of JSONNode - - public partial class JSONArray : JSONNode - { - private List m_List = new List(); - private bool inline = false; - public override bool Inline - { - get { return inline; } - set { inline = value; } - } - - public override JSONNodeType Tag { get { return JSONNodeType.Array; } } - public override bool IsArray { get { return true; } } - public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); } - - public override JSONNode this[int aIndex] - { - get - { - if (aIndex < 0 || aIndex >= m_List.Count) - return new JSONLazyCreator(this); - return m_List[aIndex]; - } - set - { - if (value == null) - value = JSONNull.CreateOrGet(); - if (aIndex < 0 || aIndex >= m_List.Count) - m_List.Add(value); - else - m_List[aIndex] = value; - } - } - - public override JSONNode this[string aKey] - { - get { return new JSONLazyCreator(this); } - set - { - if (value == null) - value = JSONNull.CreateOrGet(); - m_List.Add(value); - } - } - - public override int Count - { - get { return m_List.Count; } - } - - public override void Add(string aKey, JSONNode aItem) - { - if (aItem == null) - aItem = JSONNull.CreateOrGet(); - m_List.Add(aItem); - } - - public override JSONNode Remove(int aIndex) - { - if (aIndex < 0 || aIndex >= m_List.Count) - return null; - JSONNode tmp = m_List[aIndex]; - m_List.RemoveAt(aIndex); - return tmp; - } - - public override JSONNode Remove(JSONNode aNode) - { - m_List.Remove(aNode); - return aNode; - } - - public override JSONNode Clone() - { - var node = new JSONArray(); - node.m_List.Capacity = m_List.Capacity; - foreach(var n in m_List) - { - if (n != null) - node.Add(n.Clone()); - else - node.Add(null); - } - return node; - } - - public override IEnumerable Children - { - get - { - foreach (JSONNode N in m_List) - yield return N; - } - } - - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append('['); - int count = m_List.Count; - if (inline) - aMode = JSONTextMode.Compact; - for (int i = 0; i < count; i++) - { - if (i > 0) - aSB.Append(','); - if (aMode == JSONTextMode.Indent) - aSB.AppendLine(); - - if (aMode == JSONTextMode.Indent) - aSB.Append(' ', aIndent + aIndentInc); - m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); - } - if (aMode == JSONTextMode.Indent) - aSB.AppendLine().Append(' ', aIndent); - aSB.Append(']'); - } - } - // End of JSONArray - - public partial class JSONObject : JSONNode - { - private Dictionary m_Dict = new Dictionary(); - - private bool inline = false; - public override bool Inline - { - get { return inline; } - set { inline = value; } - } - - public override JSONNodeType Tag { get { return JSONNodeType.Object; } } - public override bool IsObject { get { return true; } } - - public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); } - - - public override JSONNode this[string aKey] - { - get - { - if (m_Dict.ContainsKey(aKey)) - return m_Dict[aKey]; - else - return new JSONLazyCreator(this, aKey); - } - set - { - if (value == null) - value = JSONNull.CreateOrGet(); - if (m_Dict.ContainsKey(aKey)) - m_Dict[aKey] = value; - else - m_Dict.Add(aKey, value); - } - } - - public override JSONNode this[int aIndex] - { - get - { - if (aIndex < 0 || aIndex >= m_Dict.Count) - return null; - return m_Dict.ElementAt(aIndex).Value; - } - set - { - if (value == null) - value = JSONNull.CreateOrGet(); - if (aIndex < 0 || aIndex >= m_Dict.Count) - return; - string key = m_Dict.ElementAt(aIndex).Key; - m_Dict[key] = value; - } - } - - public override int Count - { - get { return m_Dict.Count; } - } - - public override void Add(string aKey, JSONNode aItem) - { - if (aItem == null) - aItem = JSONNull.CreateOrGet(); - - if (aKey != null) - { - if (m_Dict.ContainsKey(aKey)) - m_Dict[aKey] = aItem; - else - m_Dict.Add(aKey, aItem); - } - else - m_Dict.Add(Guid.NewGuid().ToString(), aItem); - } - - public override JSONNode Remove(string aKey) - { - if (!m_Dict.ContainsKey(aKey)) - return null; - JSONNode tmp = m_Dict[aKey]; - m_Dict.Remove(aKey); - return tmp; - } - - public override JSONNode Remove(int aIndex) - { - if (aIndex < 0 || aIndex >= m_Dict.Count) - return null; - var item = m_Dict.ElementAt(aIndex); - m_Dict.Remove(item.Key); - return item.Value; - } - - public override JSONNode Remove(JSONNode aNode) - { - try - { - var item = m_Dict.Where(k => k.Value == aNode).First(); - m_Dict.Remove(item.Key); - return aNode; - } - catch - { - return null; - } - } - - public override JSONNode Clone() - { - var node = new JSONObject(); - foreach (var n in m_Dict) - { - node.Add(n.Key, n.Value.Clone()); - } - return node; - } - - public override bool HasKey(string aKey) - { - return m_Dict.ContainsKey(aKey); - } - - public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) - { - JSONNode res; - if (m_Dict.TryGetValue(aKey, out res)) - return res; - return aDefault; - } - - public override IEnumerable Children - { - get - { - foreach (KeyValuePair N in m_Dict) - yield return N.Value; - } - } - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append('{'); - bool first = true; - if (inline) - aMode = JSONTextMode.Compact; - foreach (var k in m_Dict) - { - if (!first) - aSB.Append(','); - first = false; - if (aMode == JSONTextMode.Indent) - aSB.AppendLine(); - if (aMode == JSONTextMode.Indent) - aSB.Append(' ', aIndent + aIndentInc); - aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); - if (aMode == JSONTextMode.Compact) - aSB.Append(':'); - else - aSB.Append(" : "); - k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); - } - if (aMode == JSONTextMode.Indent) - aSB.AppendLine().Append(' ', aIndent); - aSB.Append('}'); - } - - } - // End of JSONObject - - public partial class JSONString : JSONNode - { - private string m_Data; - - public override JSONNodeType Tag { get { return JSONNodeType.String; } } - public override bool IsString { get { return true; } } - - public override Enumerator GetEnumerator() { return new Enumerator(); } - - - public override string Value - { - get { return m_Data; } - set - { - m_Data = value; - } - } - - public JSONString(string aData) - { - m_Data = aData; - } - public override JSONNode Clone() - { - return new JSONString(m_Data); - } - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); - } - public override bool Equals(object obj) - { - if (base.Equals(obj)) - return true; - string s = obj as string; - if (s != null) - return m_Data == s; - JSONString s2 = obj as JSONString; - if (s2 != null) - return m_Data == s2.m_Data; - return false; - } - public override int GetHashCode() - { - return m_Data.GetHashCode(); - } - } - // End of JSONString - - public partial class JSONNumber : JSONNode - { - private double m_Data; - - public override JSONNodeType Tag { get { return JSONNodeType.Number; } } - public override bool IsNumber { get { return true; } } - public override Enumerator GetEnumerator() { return new Enumerator(); } - - public override string Value - { - get { return m_Data.ToString(CultureInfo.InvariantCulture); } - set - { - double v; - if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) - m_Data = v; - } - } - - public override double AsDouble - { - get { return m_Data; } - set { m_Data = value; } - } - public override long AsLong - { - get { return (long)m_Data; } - set { m_Data = value; } - } - - public JSONNumber(double aData) - { - m_Data = aData; - } - - public JSONNumber(string aData) - { - Value = aData; - } - - public override JSONNode Clone() - { - return new JSONNumber(m_Data); - } - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append(Value); - } - private static bool IsNumeric(object value) - { - return value is int || value is uint - || value is float || value is double - || value is decimal - || value is long || value is ulong - || value is short || value is ushort - || value is sbyte || value is byte; - } - public override bool Equals(object obj) - { - if (obj == null) - return false; - if (base.Equals(obj)) - return true; - JSONNumber s2 = obj as JSONNumber; - if (s2 != null) - return m_Data == s2.m_Data; - if (IsNumeric(obj)) - return Convert.ToDouble(obj) == m_Data; - return false; - } - public override int GetHashCode() - { - return m_Data.GetHashCode(); - } - } - // End of JSONNumber - - public partial class JSONBool : JSONNode - { - private bool m_Data; - - public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } - public override bool IsBoolean { get { return true; } } - public override Enumerator GetEnumerator() { return new Enumerator(); } - - public override string Value - { - get { return m_Data.ToString(); } - set - { - bool v; - if (bool.TryParse(value, out v)) - m_Data = v; - } - } - public override bool AsBool - { - get { return m_Data; } - set { m_Data = value; } - } - - public JSONBool(bool aData) - { - m_Data = aData; - } - - public JSONBool(string aData) - { - Value = aData; - } - - public override JSONNode Clone() - { - return new JSONBool(m_Data); - } - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append((m_Data) ? "true" : "false"); - } - public override bool Equals(object obj) - { - if (obj == null) - return false; - if (obj is bool) - return m_Data == (bool)obj; - return false; - } - public override int GetHashCode() - { - return m_Data.GetHashCode(); - } - } - // End of JSONBool - - public partial class JSONNull : JSONNode - { - static JSONNull m_StaticInstance = new JSONNull(); - public static bool reuseSameInstance = true; - public static JSONNull CreateOrGet() - { - if (reuseSameInstance) - return m_StaticInstance; - return new JSONNull(); - } - private JSONNull() { } - - public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } - public override bool IsNull { get { return true; } } - public override Enumerator GetEnumerator() { return new Enumerator(); } - - public override string Value - { - get { return "null"; } - set { } - } - public override bool AsBool - { - get { return false; } - set { } - } - - public override JSONNode Clone() - { - return CreateOrGet(); - } - - public override bool Equals(object obj) - { - if (object.ReferenceEquals(this, obj)) - return true; - return (obj is JSONNull); - } - public override int GetHashCode() - { - return 0; - } - - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append("null"); - } - } - // End of JSONNull - - internal partial class JSONLazyCreator : JSONNode - { - private JSONNode m_Node = null; - private string m_Key = null; - public override JSONNodeType Tag { get { return JSONNodeType.None; } } - public override Enumerator GetEnumerator() { return new Enumerator(); } - - public JSONLazyCreator(JSONNode aNode) - { - m_Node = aNode; - m_Key = null; - } - - public JSONLazyCreator(JSONNode aNode, string aKey) - { - m_Node = aNode; - m_Key = aKey; - } - - private T Set(T aVal) where T : JSONNode - { - if (m_Key == null) - m_Node.Add(aVal); - else - m_Node.Add(m_Key, aVal); - m_Node = null; // Be GC friendly. - return aVal; - } - - public override JSONNode this[int aIndex] - { - get { return new JSONLazyCreator(this); } - set { Set(new JSONArray()).Add(value); } - } - - public override JSONNode this[string aKey] - { - get { return new JSONLazyCreator(this, aKey); } - set { Set(new JSONObject()).Add(aKey, value); } - } - - public override void Add(JSONNode aItem) - { - Set(new JSONArray()).Add(aItem); - } - - public override void Add(string aKey, JSONNode aItem) - { - Set(new JSONObject()).Add(aKey, aItem); - } - - public static bool operator ==(JSONLazyCreator a, object b) - { - if (b == null) - return true; - return System.Object.ReferenceEquals(a, b); - } - - public static bool operator !=(JSONLazyCreator a, object b) - { - return !(a == b); - } - - public override bool Equals(object obj) - { - if (obj == null) - return true; - return System.Object.ReferenceEquals(this, obj); - } - - public override int GetHashCode() - { - return 0; - } - - public override int AsInt - { - get { Set(new JSONNumber(0)); return 0; } - set { Set(new JSONNumber(value)); } - } - - public override float AsFloat - { - get { Set(new JSONNumber(0.0f)); return 0.0f; } - set { Set(new JSONNumber(value)); } - } - - public override double AsDouble - { - get { Set(new JSONNumber(0.0)); return 0.0; } - set { Set(new JSONNumber(value)); } - } - - public override long AsLong - { - get - { - if (longAsString) - Set(new JSONString("0")); - else - Set(new JSONNumber(0.0)); - return 0L; - } - set - { - if (longAsString) - Set(new JSONString(value.ToString())); - else - Set(new JSONNumber(value)); - } - } - - public override bool AsBool - { - get { Set(new JSONBool(false)); return false; } - set { Set(new JSONBool(value)); } - } - - public override JSONArray AsArray - { - get { return Set(new JSONArray()); } - } - - public override JSONObject AsObject - { - get { return Set(new JSONObject()); } - } - internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) - { - aSB.Append("null"); - } - } - // End of JSONLazyCreator - - public static class JSON - { - public static JSONNode Parse(string aJSON) - { - return JSONNode.Parse(aJSON); - } - } -} \ No newline at end of file diff --git a/RevenueCat/Scripts/SimpleJSON.cs.meta b/RevenueCat/Scripts/SimpleJSON.cs.meta deleted file mode 100644 index dcf5bfdc..00000000 --- a/RevenueCat/Scripts/SimpleJSON.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7aae9850938064db299b2f103344b9fa -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCat/Scripts/StoreKitVersion.cs b/RevenueCat/Scripts/StoreKitVersion.cs index 445c52d9..48f2a62e 100644 --- a/RevenueCat/Scripts/StoreKitVersion.cs +++ b/RevenueCat/Scripts/StoreKitVersion.cs @@ -1,7 +1,6 @@ -using System; using System.ComponentModel; -public partial class Purchases +namespace RevenueCat { public enum StoreKitVersion { @@ -19,16 +18,16 @@ public enum StoreKitVersion [Description("DEFAULT")] Default, } -} -internal static class StoreKitVersionExtensions -{ - internal static string Name(this Purchases.StoreKitVersion storeKitVersion) + internal static class StoreKitVersionExtensions { - var type = storeKitVersion.GetType(); - var memInfo = type.GetMember(storeKitVersion.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); - var stringValue = ((DescriptionAttribute) attributes[0]).Description; - return stringValue; + internal static string Name(this StoreKitVersion storeKitVersion) + { + var type = storeKitVersion.GetType(); + var memInfo = type.GetMember(storeKitVersion.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + var stringValue = ((DescriptionAttribute)attributes[0]).Description; + return stringValue; + } } } diff --git a/RevenueCat/Scripts/StoreProduct.cs b/RevenueCat/Scripts/StoreProduct.cs index 5101d708..3147775a 100644 --- a/RevenueCat/Scripts/StoreProduct.cs +++ b/RevenueCat/Scripts/StoreProduct.cs @@ -1,46 +1,41 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// Type that abstracts products from App Store, Google Play and Amazon into a single interface. /// - public class StoreProduct + public sealed class StoreProduct { /// /// Title of the product. /// - /// - public readonly string Title; + public string Title { get; } /// /// Product Id. /// - /// - public readonly string Identifier; + public string Identifier { get; } /// /// Description of the product. /// - /// - public readonly string Description; + public string Description { get; } /// /// Price of the product in the local currency. /// Contains the price value of DefaultOption for Google Play. /// - /// - public readonly float Price; + public float Price { get; } /// /// Formatted price of the item, including its currency sign. /// Contains the formatted price value of DefaultOption for Google Play. /// - /// - public readonly string PriceString; + public string PriceString { get; } /// /// Null for non-subscription products. The price of the StoreProduct in a weekly recurrence. @@ -48,8 +43,7 @@ public class StoreProduct /// divided by 4. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly float? PricePerWeek; + public float? PricePerWeek { get; } /// /// Null for non-subscription products. The price of the StoreProduct in a monthly recurrence. @@ -57,8 +51,7 @@ public class StoreProduct /// divided by 12. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly float? PricePerMonth; + public float? PricePerMonth { get; } /// /// Null for non-subscription products. The price of the StoreProduct in a yearly recurrence. @@ -66,8 +59,7 @@ public class StoreProduct /// multiplied by 12. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly float? PricePerYear; + public float? PricePerYear { get; } /// /// Null for non-subscription products. The price of the StoreProduct formatted for the current @@ -76,8 +68,7 @@ public class StoreProduct /// given locale. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly string? PricePerWeekString; + public string PricePerWeekString { get; } /// /// Null for non-subscription products. The price of the StoreProduct formatted for the current @@ -86,8 +77,7 @@ public class StoreProduct /// given locale. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly string? PricePerMonthString; + public string PricePerMonthString { get; } /// /// Null for non-subscription products. The price of the StoreProduct formatted for the current @@ -96,58 +86,58 @@ public class StoreProduct /// given locale. Note that this value may be an approximation. For Google subscriptions, /// this value will use the basePlan to calculate the value. /// - /// - public readonly string? PricePerYearString; + public string PricePerYearString { get; } /// /// Currency code for price and original price. /// Contains the currency code of DefaultOption for Google Play. /// - /// - [CanBeNull] public readonly string CurrencyCode; - + [CanBeNull] + public string CurrencyCode { get; } + /// /// Introductory price of the product. Null if no introductory price is available. /// It contains the free trial if available and user is eligible for it. /// Otherwise, it contains the introductory price of the product if the user is eligible for it. /// This will be null for non-subscription products. /// - /// - public IntroductoryPrice IntroductoryPrice; + public IntroductoryPrice IntroductoryPrice { get; } /// /// Product category of the product. /// - /// - [CanBeNull] public readonly ProductCategory ProductCategory; + public ProductCategory ProductCategory { get; } /// /// Default subscription option for a product. Google Play only. /// - /// - [CanBeNull] public readonly SubscriptionOption DefaultOption; + [CanBeNull] + public SubscriptionOption DefaultOption { get; } /// /// Collection of subscription options for a product. Google Play only. /// - /// - [CanBeNull] public readonly SubscriptionOption[] SubscriptionOptions; + [CanBeNull] + public IReadOnlyList SubscriptionOptions { get; } /// /// Offering context this package belongs to. /// Null if not using offerings or if fetched directly from store via GetProducts. /// - [CanBeNull] public readonly PresentedOfferingContext PresentedOfferingContext; + [CanBeNull] + public PresentedOfferingContext PresentedOfferingContext { get; } + [CanBeNull] + [JsonIgnore] [Obsolete("Deprecated, use PresentedOfferingContext instead.", false)] - [CanBeNull] public readonly string PresentedOfferingIdentifier; + public string PresentedOfferingIdentifier => PresentedOfferingContext?.OfferingIdentifier; /// /// Collection of iOS promotional offers for a product. Null for Android and Amazon. /// - /// - [CanBeNull] public readonly Discount[] Discounts; - + [CanBeNull] + public IReadOnlyList Discounts { get; } + /// /// Subscription period, specified in ISO 8601 format. For example, /// P1W equates to one week, P1M equates to one month, @@ -155,77 +145,50 @@ public class StoreProduct /// and P1Y equates to one year. /// Note: Not available for Amazon. /// - /// - [CanBeNull] public readonly string SubscriptionPeriod; - - public StoreProduct(JSONNode response) + [CanBeNull] + public string SubscriptionPeriod { get; } + + [JsonConstructor] + internal StoreProduct( + [JsonProperty("title")] string title, + [JsonProperty("identifier")] string identifier, + [JsonProperty("description")] string description, + [JsonProperty("price")] float price, + [JsonProperty("priceString")] string priceString, + [JsonProperty("pricePerWeek")] float? pricePerWeek, + [JsonProperty("pricePerMonth")] float? pricePerMonth, + [JsonProperty("pricePerYear")] float? pricePerYear, + [JsonProperty("pricePerWeekString")] string pricePerWeekString, + [JsonProperty("pricePerMonthString")] string pricePerMonthString, + [JsonProperty("pricePerYearString")] string pricePerYearString, + [JsonProperty("currencyCode")] string currencyCode, + [JsonProperty("productCategory")] ProductCategory productCategory, + [JsonProperty("defaultOption")] SubscriptionOption defaultOption, + [JsonProperty("subscriptionOptions")] List subscriptionOptions, + [JsonProperty("presentedOfferingContext")] PresentedOfferingContext presentedOfferingContext, + [JsonProperty("introductoryPrice")] IntroductoryPrice introductoryPrice, + [JsonProperty("discounts")] List discounts, + [JsonProperty("subscriptionPeriod")] string subscriptionPeriod) { - Title = response["title"]; - Identifier = response["identifier"]; - Description = response["description"]; - Price = response["price"]; - PriceString = response["priceString"]; - CurrencyCode = response["currencyCode"]; - SubscriptionPeriod = response["subscriptionPeriod"]; - - PricePerWeek = response["pricePerWeek"]; - PricePerMonth = response["pricePerMonth"]; - PricePerYear = response["pricePerYear"]; - PricePerWeekString = response["pricePerWeekString"]; - PricePerMonthString = response["pricePerMonthString"]; - PricePerYearString = response["pricePerYearString"]; - - var introPriceJsonNode = response["introPrice"]; - if (introPriceJsonNode != null && !introPriceJsonNode.IsNull) - { - IntroductoryPrice = new IntroductoryPrice(introPriceJsonNode); - } - - var presentedOfferingContexNode = response["presentedOfferingContext"]; - if (presentedOfferingContexNode != null && !presentedOfferingContexNode.IsNull) { - PresentedOfferingContext = new PresentedOfferingContext(presentedOfferingContexNode); - PresentedOfferingIdentifier = PresentedOfferingContext.OfferingIdentifier; - } - - if (!Enum.TryParse(response["productCategory"].Value, out ProductCategory)) - { - ProductCategory = ProductCategory.UNKNOWN; - } - var defaultOptionJsonNode = response["defaultOption"]; - if (defaultOptionJsonNode != null && !defaultOptionJsonNode.IsNull) - { - DefaultOption = new SubscriptionOption(defaultOptionJsonNode); - } - - var subscriptionOptionsResponse = response["subscriptionOptions"]; - if (subscriptionOptionsResponse == null) - { - SubscriptionOptions = null; - } - else - { - var subscriptionOptionsTemporaryList = new List(); - foreach (var subscriptionOptionResponse in subscriptionOptionsResponse) - { - subscriptionOptionsTemporaryList.Add(new SubscriptionOption(subscriptionOptionResponse)); - } - SubscriptionOptions = subscriptionOptionsTemporaryList.ToArray(); - } - - var discountsResponse = response["discounts"]; - if (discountsResponse == null) - { - Discounts = null; - } - else - { - var temporaryList = new List(); - foreach (var discountResponse in discountsResponse) - { - temporaryList.Add(new Discount(discountResponse)); - } - Discounts = temporaryList.ToArray(); - } + Title = title; + Identifier = identifier; + Description = description; + Price = price; + PriceString = priceString; + PricePerWeek = pricePerWeek; + PricePerMonth = pricePerMonth; + PricePerYear = pricePerYear; + PricePerWeekString = pricePerWeekString; + PricePerMonthString = pricePerMonthString; + PricePerYearString = pricePerYearString; + CurrencyCode = currencyCode; + ProductCategory = productCategory; + DefaultOption = defaultOption; + SubscriptionOptions = subscriptionOptions; + PresentedOfferingContext = presentedOfferingContext; + IntroductoryPrice = introductoryPrice; + Discounts = discounts; + SubscriptionPeriod = subscriptionPeriod; } public override string ToString() @@ -243,7 +206,9 @@ public override string ToString() $"{nameof(PricePerYearString)}: {PricePerYearString}\n" + $"{nameof(CurrencyCode)}: {CurrencyCode}\n" + $"{nameof(ProductCategory)}: {ProductCategory}\n" + +#pragma warning disable CS0618 // Type or member is obsolete $"{nameof(PresentedOfferingIdentifier)}: {PresentedOfferingIdentifier}\n" + +#pragma warning restore CS0618 // Type or member is obsolete $"{nameof(PresentedOfferingContext)}: {PresentedOfferingContext}\n" + $"{nameof(DefaultOption)}: {DefaultOption}\n" + $"{nameof(SubscriptionOptions)}: {SubscriptionOptions}\n" + diff --git a/RevenueCat/Scripts/StoreTransaction.cs b/RevenueCat/Scripts/StoreTransaction.cs index c2a97bec..bb213335 100644 --- a/RevenueCat/Scripts/StoreTransaction.cs +++ b/RevenueCat/Scripts/StoreTransaction.cs @@ -1,9 +1,8 @@ +using Newtonsoft.Json; using System; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; - -public partial class Purchases +namespace RevenueCat { /// /// Abstract class that provides access to properties of a transaction. StoreTransactions can represent @@ -12,32 +11,37 @@ public partial class Purchases /// public class StoreTransaction { - /** - * - * Id associated with the transaction in RevenueCat. - * - */ - public readonly string TransactionIdentifier; + /// + /// Id associated with the transaction in RevenueCat. + /// + [JsonProperty("transactionIdentifier")] + public string TransactionIdentifier { get; } + + /// + /// Product Id associated with the transaction. + /// + [JsonProperty("productIdentifier")] + public string ProductIdentifier { get; } - /** - * - * Product Id associated with the transaction. - * - */ - public readonly string ProductIdentifier; + [JsonProperty("purchaseDate")] + public long PurchaseDateUnixTimeMilliseconds { get; } - /** - * - * Purchase date of the transaction in UTC, be sure to compare them with DateTime.UtcNow - * - */ - public readonly DateTime PurchaseDate; + /// + /// Purchase date of the transaction in UTC, be sure to compare them with DateTime.UtcNow + /// + [JsonIgnore] + public DateTime PurchaseDate { get; } - public StoreTransaction(JSONNode response) + [JsonConstructor] + internal StoreTransaction( + [JsonProperty("transactionIdentifier")] string transactionIdentifier, + [JsonProperty("productIdentifier")] string productIdentifier, + [JsonProperty("purchaseDate")] long purchaseDate) { - TransactionIdentifier = response["transactionIdentifier"]; - ProductIdentifier = response["productIdentifier"]; - PurchaseDate = FromUnixTimeInMilliseconds(response["purchaseDateMillis"].AsLong); + TransactionIdentifier = transactionIdentifier; + ProductIdentifier = productIdentifier; + PurchaseDateUnixTimeMilliseconds = purchaseDate; + PurchaseDate = FromUnixTimeInMilliseconds(purchaseDate); } public override string ToString() diff --git a/RevenueCat/Scripts/Storefront.cs b/RevenueCat/Scripts/Storefront.cs index e4a7457d..e7b8534a 100644 --- a/RevenueCat/Scripts/Storefront.cs +++ b/RevenueCat/Scripts/Storefront.cs @@ -1,18 +1,20 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// Contains the information about the current store account. /// - public class Storefront + public sealed class Storefront { /// /// Country code of the current store account. /// - public readonly string CountryCode; + [JsonProperty("countryCode")] + public string CountryCode { get; } - public Storefront(string countryCode) + [JsonConstructor] + internal Storefront([JsonProperty("countryCode")] string countryCode) { CountryCode = countryCode; } diff --git a/RevenueCat/Scripts/SubscriptionInfo.cs b/RevenueCat/Scripts/SubscriptionInfo.cs index 2c8f3d51..d71c1617 100644 --- a/RevenueCat/Scripts/SubscriptionInfo.cs +++ b/RevenueCat/Scripts/SubscriptionInfo.cs @@ -1,54 +1,87 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; using UnityEngine; using System; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; - -public partial class Purchases +namespace RevenueCat { - /// /// The SubscriptionInfo object gives you access to all of the information about the status of a subscription. /// public class SubscriptionInfo { - public readonly string ProductIdentifier; - public readonly DateTime PurchaseDate; - public readonly DateTime? OriginalPurchaseDate; - public readonly DateTime? ExpiresDate; - public readonly string Store; - public readonly DateTime? UnsubscribeDetectedAt; - public readonly bool IsSandbox; - public readonly DateTime? BillingIssuesDetectedAt; - public readonly DateTime? GracePeriodExpiresDate; - public readonly string OwnershipType; - public readonly string PeriodType; - public readonly DateTime? RefundedAt; - public readonly string? StoreTransactionId; - public readonly bool IsActive; - public readonly bool WillRenew; - - public SubscriptionInfo(JSONNode response) + public string ProductIdentifier { get; } + + public DateTime PurchaseDate { get; } + + public DateTime? OriginalPurchaseDate { get; } + + public DateTime? ExpiresDate { get; } + + public string Store { get; } + + public DateTime? UnsubscribeDetectedAt { get; } + + public bool IsSandbox { get; } + + public DateTime? BillingIssuesDetectedAt { get; } + + public DateTime? GracePeriodExpiresDate { get; } + + public string OwnershipType { get; } + + public string PeriodType { get; } + + public DateTime? RefundedAt { get; } + + [CanBeNull] + public string StoreTransactionId { get; } + + public bool IsActive { get; } + + public bool WillRenew { get; } + + [JsonConstructor] + internal SubscriptionInfo( + [JsonProperty("productIdentifier")] string productIdentifier, + [JsonProperty("purchaseDate")] string purchaseDate, + [JsonProperty("originalPurchaseDate")] string originalPurchaseDate, + [JsonProperty("expiresDate")] string expiresDate, + [JsonProperty("store")] string store, + [JsonProperty("unsubscribeDetectedAt")] string unsubscribeDetectedAt, + [JsonProperty("isSandbox")] bool isSandbox, + [JsonProperty("billingIssuesDetectedAt")] string billingIssuesDetectedAt, + [JsonProperty("gracePeriodExpiresDate")] string gracePeriodExpiresDate, + [JsonProperty("ownershipType")] string ownershipType, + [JsonProperty("periodType")] string periodType, + [JsonProperty("refundedAt")] string refundedAt, + [JsonProperty("storeTransactionId")] string storeTransactionId, + [JsonProperty("isActive")] bool isActive, + [JsonProperty("willRenew")] bool willRenew) { - ProductIdentifier = response["productIdentifier"]; - var purchaseDateTime = FromISO8601(response["purchaseDate"]); - if (purchaseDateTime == null) { + ProductIdentifier = productIdentifier; + var purchaseDateTime = FromISO8601(purchaseDate); + + if (purchaseDateTime == null) + { Debug.LogError("Purchase date is null or has an invalid format. Defaulting to 1970-01-01."); } + PurchaseDate = purchaseDateTime ?? new DateTime(1970, 1, 1); - OriginalPurchaseDate = FromResponseISO8601String(response, "originalPurchaseDate"); - ExpiresDate = FromResponseISO8601String(response, "expiresDate"); - Store = response["store"]; - UnsubscribeDetectedAt = FromResponseISO8601String(response, "unsubscribeDetectedAt"); - IsSandbox = response["isSandbox"].AsBool; - BillingIssuesDetectedAt = FromResponseISO8601String(response, "billingIssuesDetectedAt"); - GracePeriodExpiresDate = FromResponseISO8601String(response, "gracePeriodExpiresDate"); - OwnershipType = response["ownershipType"]; - PeriodType = response["periodType"]; - RefundedAt = FromResponseISO8601String(response, "refundedAt"); - StoreTransactionId = response["storeTransactionId"]; - IsActive = response["isActive"].AsBool; - WillRenew = response["willRenew"].AsBool; + OriginalPurchaseDate = FromISO8601(originalPurchaseDate); + ExpiresDate = FromISO8601(expiresDate); + Store = store; + UnsubscribeDetectedAt = FromISO8601(unsubscribeDetectedAt); + IsSandbox = isSandbox; + BillingIssuesDetectedAt = FromISO8601(billingIssuesDetectedAt); + GracePeriodExpiresDate = FromISO8601(gracePeriodExpiresDate); + OwnershipType = ownershipType; + PeriodType = periodType; + RefundedAt = FromISO8601(refundedAt); + StoreTransactionId = storeTransactionId; + IsActive = isActive; + WillRenew = willRenew; } public override string ToString() @@ -70,19 +103,5 @@ public override string ToString() $"{nameof(IsActive)}: {IsActive}\n" + $"{nameof(WillRenew)}: {WillRenew}"; } - - private DateTime? FromResponseISO8601String(JSONNode response, string key) - { - var dateJson = response[key]; - var hasDate = dateJson != null && !dateJson.IsNull; - if (hasDate) - { - return FromISO8601(dateJson); - } - else - { - return null; - } - } } } diff --git a/RevenueCat/Scripts/SubscriptionOption.cs b/RevenueCat/Scripts/SubscriptionOption.cs index ba69a832..4de78046 100644 --- a/RevenueCat/Scripts/SubscriptionOption.cs +++ b/RevenueCat/Scripts/SubscriptionOption.cs @@ -1,145 +1,129 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; -using RevenueCat.SimpleJSON; -using UnityEngine; -public partial class Purchases +using Newtonsoft.Json; + +namespace RevenueCat { + /// /// Contains all details associated with a SubscriptionOption /// Used only for Google - public class SubscriptionOption + /// + public sealed class SubscriptionOption { - /** - * Identifier of the subscription option - * If this SubscriptionOption represents a base plan, this will be the basePlanId. - * If it represents an offer, it will be {basePlanId}:{offerId} - */ - public readonly string Id; - - /** - * Identifier of the StoreProduct associated with this SubscriptionOption - * This will be {subId}:{basePlanId} - */ - public readonly string StoreProductId; - - /** - * Identifer of the subscription associated with this SubscriptionOption - * This will be {subId} - */ - public readonly string ProductId; - - /** - * Pricing phases defining a user's payment plan for the product over time. - */ - public readonly PricingPhase[] PricingPhases; - - /** - * Tags defined on the base plan or offer. Empty for Amazon. - */ - public readonly string[] Tags; - - /** - * True if this SubscriptionOption represents a subscription base plan (rather than an offer). - */ - public readonly bool IsBasePlan; - - /** - * The subscription period of fullPricePhase (after free and intro trials). - */ - public readonly Period BillingPeriod; - - /** - * True if the subscription is pre-paid. - */ - public readonly bool IsPrepaid; - - /** - * The full price PricingPhase of the subscription. - * Looks for the last price phase of the SubscriptionOption. - */ - [CanBeNull] public readonly PricingPhase FullPricePhase; - - /** - * The free trial PricingPhase of the subscription. - * Looks for the first pricing phase of the SubscriptionOption where amountMicros is 0. - * There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. - */ - [CanBeNull] public readonly PricingPhase FreePhase; - - /** - * The intro trial PricingPhase of the subscription. - * Looks for the first pricing phase of the SubscriptionOption where amountMicros is greater than 0. - * There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. - */ - [CanBeNull] public readonly PricingPhase IntroPhase; - - [CanBeNull] public readonly PresentedOfferingContext PresentedOfferingContext; - - /** - * Offering identifier the subscription option was presented from - */ + /// + /// Identifier of the subscription option + /// If this SubscriptionOption represents a base plan, this will be the basePlanId. + /// If it represents an offer, it will be {basePlanId}:{offerId} + /// + public string Id { get; } + + /// + /// Identifier of the StoreProduct associated with this SubscriptionOption + /// This will be {subId}:{basePlanId} + /// + public string StoreProductId { get; } + + /// + /// Identifier of the subscription associated with this SubscriptionOption + /// This will be {subId} + /// + public string ProductId { get; } + + /// + /// Pricing phases defining a user's payment plan for the product over time. + /// + public IReadOnlyList PricingPhases { get; } + + /// + /// Tags defined on the base plan or offer. Empty for Amazon. + /// + public IReadOnlyList Tags { get; } + + /// + /// True if this SubscriptionOption represents a subscription base plan (rather than an offer). + /// + public bool IsBasePlan { get; } + + /// + /// The subscription period of fullPricePhase (after free and intro trials). + /// + public Period BillingPeriod { get; } + + /// + /// True if the subscription is pre-paid. + /// + public bool IsPrepaid { get; } + + /// + /// The full price PricingPhase of the subscription. + /// Looks for the last price phase of the SubscriptionOption. + /// + [CanBeNull] + public PricingPhase FullPricePhase { get; } + + /// + /// The free trial PricingPhase of the subscription. + /// Looks for the first pricing phase of the SubscriptionOption where amountMicros is 0. + /// There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. + /// + [CanBeNull] + public PricingPhase FreePhase { get; } + + /// + /// The intro trial PricingPhase of the subscription. + /// Looks for the first pricing phase of the SubscriptionOption where amountMicros is greater than 0. + /// There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. + /// + [CanBeNull] + public PricingPhase IntroPhase { get; } + + [CanBeNull] + public PresentedOfferingContext PresentedOfferingContext { get; } + + /// + /// Offering identifier the subscription option was presented from + /// [Obsolete("Deprecated, use PresentedOfferingContext instead.", false)] - [CanBeNull] public readonly string PresentedOfferingIdentifier; - - /** - * Information about the installment subscription. Currently only supported in Google Play. - */ - [CanBeNull] public readonly InstallmentsInfo OptionInstallmentsInfo; - - public SubscriptionOption(JSONNode response) + [CanBeNull] + [JsonIgnore] + public string PresentedOfferingIdentifier => PresentedOfferingContext?.OfferingIdentifier; + + /// + /// Information about the installment subscription. Currently only supported in Google Play. + /// + [CanBeNull] + public InstallmentsInfo OptionInstallmentsInfo { get; } + + [JsonConstructor] + internal SubscriptionOption( + [JsonProperty("id")] string id, + [JsonProperty("storeProductId")] string storeProductId, + [JsonProperty("productId")] string productId, + [JsonProperty("pricingPhases")] List pricingPhases, + [JsonProperty("tags")] List tags, + [JsonProperty("isBasePlan")] bool isBasePlan, + [JsonProperty("billingPeriod")] Period billingPeriod, + [JsonProperty("isPrepaid")] bool isPrepaid, + [JsonProperty("fullPricePhase")] PricingPhase fullPricePhase, + [JsonProperty("freePhase")] PricingPhase freePhase, + [JsonProperty("introPhase")] PricingPhase introPhase, + [JsonProperty("presentedOfferingContext")] PresentedOfferingContext presentedOfferingContext, + [JsonProperty("installmentsInfo")] InstallmentsInfo optionInstallmentsInfo) { - Id = response["id"]; - StoreProductId = response["storeProductId"]; - ProductId = response["productId"]; - var tagsResponse = response["tags"]; - var tagsTemporaryList = new List(); - foreach (var tag in tagsResponse) - { - tagsTemporaryList.Add(tag.Value); - } - Tags = tagsTemporaryList.ToArray(); - IsBasePlan = response["isBasePlan"]; - BillingPeriod = new Period(response["billingPeriod"]); - IsPrepaid = response["isPrepaid"]; - - var pricingPhasesNode = response["pricingPhases"]; - var pricingPhasesTemporaryList = new List(); - if (pricingPhasesNode != null && !pricingPhasesNode.IsNull) { - foreach (var phase in pricingPhasesNode) - { - pricingPhasesTemporaryList.Add(new PricingPhase(phase)); - } - PricingPhases = pricingPhasesTemporaryList.ToArray(); - } - - - var fullPricePhaseNode = response["fullPricePhase"]; - if (fullPricePhaseNode != null && !fullPricePhaseNode.IsNull) - { - FullPricePhase = new PricingPhase(fullPricePhaseNode); - } - var freePhaseNode = response["freePhase"]; - if (freePhaseNode != null && !freePhaseNode.IsNull) - { - FreePhase = new PricingPhase(freePhaseNode); - } - var introPhaseNode = response["introPhase"]; - if (introPhaseNode != null && !introPhaseNode.IsNull) - { - IntroPhase = new PricingPhase(introPhaseNode); - } - - var presentedOfferingContexNode = response["presentedOfferingContext"]; - if (presentedOfferingContexNode != null && !presentedOfferingContexNode.IsNull) { - PresentedOfferingContext = new PresentedOfferingContext(presentedOfferingContexNode); - PresentedOfferingIdentifier = PresentedOfferingContext.OfferingIdentifier; - } - - var installmentsInfoNode = response["installmentsInfo"]; - if (installmentsInfoNode != null && !installmentsInfoNode.IsNull) - { - OptionInstallmentsInfo = new InstallmentsInfo(installmentsInfoNode); - } + Id = id; + StoreProductId = storeProductId; + ProductId = productId; + PricingPhases = pricingPhases; + Tags = tags; + IsBasePlan = isBasePlan; + BillingPeriod = billingPeriod; + IsPrepaid = isPrepaid; + FullPricePhase = fullPricePhase; + FreePhase = freePhase; + IntroPhase = introPhase; + PresentedOfferingContext = presentedOfferingContext; + OptionInstallmentsInfo = optionInstallmentsInfo; } public override string ToString() @@ -155,90 +139,96 @@ public override string ToString() $"{nameof(FullPricePhase)}: {FullPricePhase}\n" + $"{nameof(FreePhase)}: {FreePhase}\n" + $"{nameof(IntroPhase)}: {IntroPhase}\n" + +#pragma warning disable CS0618 // Type or member is obsolete $"{nameof(PresentedOfferingIdentifier)}: {PresentedOfferingIdentifier}\n" + +#pragma warning restore CS0618 // Type or member is obsolete $"{nameof(OptionInstallmentsInfo)}: {OptionInstallmentsInfo}\n"; } - public class PricingPhase + public sealed class PricingPhase { - /** - * Billing period for which the PricingPhase applies - */ - public readonly Period BillingPeriod; - - /** - * Recurrence mode of the PricingPhase - */ - public readonly RecurrenceMode RecurrenceMode; - - /** - * Number of cycles for which the pricing phase applies. - * Null for infiniteRecurring or finiteRecurring recurrence modes. - */ - [CanBeNull] public readonly int BillingCycleCount; - - /** - * Price of the PricingPhase - */ - public readonly Price Price; - - /** - * Indicates how the pricing phase is charged for finiteRecurring pricing phases - */ - [CanBeNull] public readonly OfferPaymentMode OfferPaymentMode; - - public PricingPhase(JSONNode response) + /// + /// Billing period for which the PricingPhase applies + /// + public Period BillingPeriod { get; } + + /// + /// Recurrence mode of the PricingPhase + /// + public RecurrenceMode RecurrenceMode { get; } + + /// + /// Number of cycles for which the pricing phase applies. + /// Null for infiniteRecurring or finiteRecurring recurrence modes. + /// + public int? BillingCycleCount { get; } + + /// + /// Price of the PricingPhase + /// + public Price Price { get; } + + /// + /// Indicates how the pricing phase is charged for finiteRecurring pricing phases + /// + public OfferPaymentMode OfferPaymentMode { get; } + + [JsonConstructor] + internal PricingPhase( + [JsonProperty("billingPeriod")] Period billingPeriod, + [JsonProperty("recurrenceMode")] RecurrenceMode recurrenceMode, + [JsonProperty("billingCycleCount")] int? billingCycleCount, + [JsonProperty("price")] Price price, + [JsonProperty("offerPaymentMode")] OfferPaymentMode offerPaymentMode) { - BillingPeriod = new Period(response["billingPeriod"]); - if (!Enum.TryParse(response["recurrenceMode"].Value, out RecurrenceMode)) - { - RecurrenceMode = RecurrenceMode.UNKNOWN; - } - if (!Enum.TryParse(response["offerPaymentMode"].Value, out OfferPaymentMode)) - { - OfferPaymentMode = OfferPaymentMode.UNKNOWN; - } - BillingCycleCount = response["billingCycleCount"]; - Price = new Price(response["price"]); + BillingPeriod = billingPeriod; + RecurrenceMode = recurrenceMode; + BillingCycleCount = billingCycleCount; + Price = price; + OfferPaymentMode = offerPaymentMode; } public override string ToString() { return $"{nameof(BillingPeriod)}: {BillingPeriod}\n" + - $"{nameof(RecurrenceMode)}: {RecurrenceMode}\n" + - $"{nameof(BillingCycleCount)}: {BillingCycleCount}\n" + - $"{nameof(Price)}: {Price}\n" + - $"{nameof(OfferPaymentMode)}: {OfferPaymentMode}\n"; + $"{nameof(RecurrenceMode)}: {RecurrenceMode}\n" + + $"{nameof(BillingCycleCount)}: {BillingCycleCount}\n" + + $"{nameof(Price)}: {Price}\n" + + $"{nameof(OfferPaymentMode)}: {OfferPaymentMode}\n"; } } - /** - * The number of period units: day, week, month, year, unknown - */ - public class Period + /// + /// The number of period units: day, week, month, year, unknown + /// + public sealed class Period { - public readonly PeriodUnit Unit; - - /** - * The increment of time that a subscription period is specified in - */ - public readonly int Value; - - /** - * Specified in ISO 8601 format. For example, P1W equates to one week, - * P1M equates to one month, P3M equates to three months, P6M equates to six months, - * and P1Y equates to one year - */ - public readonly string ISO8601; - - public Period(JSONNode response) + /// + /// The increment of time that a subscription period is specified in + /// + public PeriodUnit Unit { get; } + + /// + /// The increment of time that a subscription period is specified in + /// + public int Value { get; } + + /// + /// Specified in ISO 8601 format. For example, P1W equates to one week, + /// P1M equates to one month, P3M equates to three months, P6M equates to six months, + /// and P1Y equates to one year + /// + public string ISO8601 { get; } + + [JsonConstructor] + internal Period( + [JsonProperty("unit")] PeriodUnit unit, + [JsonProperty("value")] int value, + [JsonProperty("iso8601")] string iso8601) { - if (!Enum.TryParse(response["unit"].Value, out Unit)) - { - Unit = PeriodUnit.UNKNOWN; - } - Value = (int) response["value"]; - ISO8601 = response["iso8601"]; + Unit = unit; + Value = value; + ISO8601 = iso8601; } public override string ToString() @@ -249,114 +239,129 @@ public override string ToString() } } - public enum PeriodUnit { + /// + /// Period units for a subscription period + /// + public enum PeriodUnit + { DAY = 0, - WEEK = 1, - MONTH = 2, - YEAR = 3, - UNKNOWN = 4 } - /** - * Recurrence mode for a pricing phase - */ - public enum RecurrenceMode { - /** - * Pricing phase repeats infinitely until cancellation - */ + /// + /// Recurrence mode for a pricing phase + /// + public enum RecurrenceMode + { + /// + /// Pricing phase repeats infinitely until cancellation + /// INFINITE_RECURRING = 1, - /** - * Pricing phase repeats for a fixed number of billing periods - */ + /// + /// Pricing phase repeats for a fixed number of billing periods + /// FINITE_RECURRING = 2, - /** - * Pricing phase does not repeat - */ + /// + /// Pricing phase does not repeat + /// NON_RECURRING = 3, UNKNOWN = 4, } - /** - * Payment mode for offer pricing phases. Google Play only. - */ - public enum OfferPaymentMode { - /** - * Subscribers don't pay until the specified period ends - */ + /// + /// Payment mode for offer pricing phases. Google Play only. + /// + public enum OfferPaymentMode + { + /// + /// Subscribers don't pay until the specified period ends + /// FREE_TRIAL = 0, - /** - * Subscribers pay up front for a specified period - */ + /// + /// Subscribers pay up front for a specified period + /// SINGLE_PAYMENT = 1, - /** - * Subscribers pay a discounted amount for a specified number of periods - */ + /// + /// Subscribers pay a discounted amount for a specified number of periods + /// DISCOUNTED_RECURRING_PAYMENT = 2, UNKNOWN = 3, } - /** - * Contains all the details associated with a Price - */ - public class Price { - /** - * Formatted price of the item, including its currency sign. For example $3.00 - */ - public readonly string Formatted; - - /** - * Price in micro-units, where 1,000,000 micro-units equal one unit of the currency. - * - * For example, if price is "€7.99", price_amount_micros is 7,990,000. This value represents - * the localized, rounded price for a particular currency. - */ - public readonly int AmountMicros; - - /** - * Returns ISO 4217 currency code for price and original price. - * - * For example, if price is specified in British pounds sterling, price_currency_code is "GBP". - * If currency code cannot be determined, currency symbol is returned. - */ - public readonly string CurrencyCode; - - public Price(JSONNode response) + /// + /// Contains all the details associated with a Price + /// + public sealed class Price + { + /// + /// Formatted price of the item, including its currency sign. For example $3.00 + /// + public string Formatted { get; } + + /// + /// Price in micro-units, where 1,000,000 micro-units equal one unit of the currency. + /// For example, if price is "€7.99", price_amount_micros is 7,990,000. This value represents + /// the localized, rounded price for a particular currency. + /// + public int AmountMicros { get; } + + /// + /// Returns ISO 4217 currency code for price and original price. + /// For example, if price is specified in British pounds sterling, price_currency_code is "GBP". + /// If currency code cannot be determined, currency symbol is returned. + /// + public string CurrencyCode { get; } + + [JsonConstructor] + internal Price( + [JsonProperty("formatted")] string formatted, + [JsonProperty("amountMicros")] int amountMicros, + [JsonProperty("currencyCode")] string currencyCode) { - Formatted = response["formatted"]; - AmountMicros = response["amountMicros"]; - CurrencyCode = response["currencyCode"]; + Formatted = formatted; + AmountMicros = amountMicros; + CurrencyCode = currencyCode; } public override string ToString() { return $"{nameof(Formatted)}: {Formatted}\n" + - $"{nameof(AmountMicros)}: {AmountMicros}\n" + - $"{nameof(CurrencyCode)}: {CurrencyCode}\n"; + $"{nameof(AmountMicros)}: {AmountMicros}\n" + + $"{nameof(CurrencyCode)}: {CurrencyCode}\n"; } } + /// /// Type containing information of installment subscriptions. Currently only supported in Google Play. - public class InstallmentsInfo { + /// + public sealed class InstallmentsInfo + { + /// /// Number of payments the customer commits to in order to purchase the subscription. - public readonly int CommitmentPaymentsCount; + /// + public int CommitmentPaymentsCount { get; } + /// /// After the commitment payments are complete, the number of payments the user commits to upon a renewal. - public readonly int RenewalCommitmentPaymentsCount; + /// + public int RenewalCommitmentPaymentsCount { get; } - public InstallmentsInfo(JSONNode response) + [JsonConstructor] + internal InstallmentsInfo( + [JsonProperty("commitmentPaymentsCount")] int commitmentPaymentsCount, + [JsonProperty("renewalCommitmentPaymentsCount")] int renewalCommitmentPaymentsCount) { - CommitmentPaymentsCount = response["commitmentPaymentsCount"]; - RenewalCommitmentPaymentsCount = response["renewalCommitmentPaymentsCount"]; + CommitmentPaymentsCount = commitmentPaymentsCount; + RenewalCommitmentPaymentsCount = renewalCommitmentPaymentsCount; } public override string ToString() { return $"{nameof(CommitmentPaymentsCount)}: {CommitmentPaymentsCount}\n" + - $"{nameof(RenewalCommitmentPaymentsCount)}: {RenewalCommitmentPaymentsCount}\n"; + $"{nameof(RenewalCommitmentPaymentsCount)}: {RenewalCommitmentPaymentsCount}\n"; } } } diff --git a/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs b/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs deleted file mode 100644 index 98c4fe13..00000000 --- a/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs +++ /dev/null @@ -1,9 +0,0 @@ -using UnityEngine; - -public partial class Purchases -{ - public abstract class UpdatedCustomerInfoListener : MonoBehaviour - { - public abstract void CustomerInfoReceived(CustomerInfo customerInfo); - } -} \ No newline at end of file diff --git a/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs.meta b/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs.meta deleted file mode 100644 index 2576e43c..00000000 --- a/RevenueCat/Scripts/UpdatedCustomerInfoListener.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7be2a28dc29c74f729d853e3feca52b5 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCat/Scripts/Utilities.cs b/RevenueCat/Scripts/Utilities.cs index 9ff4ada8..5aee65d6 100644 --- a/RevenueCat/Scripts/Utilities.cs +++ b/RevenueCat/Scripts/Utilities.cs @@ -1,48 +1,72 @@ -using UnityEngine; using System; using System.Collections.Generic; using System.Linq; +using UnityEngine; namespace RevenueCat { - internal static class Utilities { + internal static class Utilities + { + internal static bool IsAndroidEmulator() + { + try + { + // From https://stackoverflow.com/questions/51880866/detect-if-game-running-in-android-emulator + var osBuild = new AndroidJavaClass("android.os.Build"); + var fingerPrint = osBuild.GetStatic("FINGERPRINT"); + return fingerPrint.Contains("generic"); + } + catch + { + // Throws error when running on non-Android platforms + return false; + } + } + internal static DateTime FromUnixTimeInMilliseconds(long unixTimeInMilliseconds) { return Epoch.AddSeconds(unixTimeInMilliseconds / 1000.0); } - - internal static DateTime? FromOptionalUnixTimeInMilliseconds(long unixTimeInMilliseconds) + + internal static DateTime? FromOptionalUnixTimeInMilliseconds(long? unixTimeInMilliseconds) { DateTime? value = null; - if (unixTimeInMilliseconds != 0L) { - value = FromUnixTimeInMilliseconds(unixTimeInMilliseconds); + + if (unixTimeInMilliseconds.HasValue) + { + value = FromUnixTimeInMilliseconds(unixTimeInMilliseconds.Value); } + return value; } - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - internal static String DictToString(Dictionary dictionary) + private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + internal static String DictToString(IReadOnlyDictionary dictionary) { var items = dictionary.Select(kvp => $"{kvp.Key} : Id={kvp.Value.ToString()}"); - return $"{{ \n { string.Join(Environment.NewLine, items) }\n }} \n "; + return $"{{ \n {string.Join(Environment.NewLine, items)}\n }} \n "; } - - internal static String ListToString(List list) + + internal static String ListToString(IEnumerable list) { var items = list.Select(arg => $"{arg.ToString()}"); - return $"{{ \n { string.Join(Environment.NewLine, items) }\n }} \n"; + return $"{{ \n {string.Join(Environment.NewLine, items)}\n }} \n"; } internal static DateTime? FromISO8601(string iso8601) { - if (iso8601 == null) { + if (string.IsNullOrWhiteSpace(iso8601)) + { return null; } - try { + try + { return DateTime.Parse(iso8601); - } catch (FormatException e) { - Debug.Log($"Error parsing ISO8601 date: {e.Message}"); + } + catch (FormatException e) + { + Debug.Log($"Error parsing ISO8601 date: {iso8601}: {e.Message}"); return null; } } diff --git a/RevenueCat/Scripts/VerificationResult.cs b/RevenueCat/Scripts/VerificationResult.cs index 2f769f5a..38a0ce0f 100644 --- a/RevenueCat/Scripts/VerificationResult.cs +++ b/RevenueCat/Scripts/VerificationResult.cs @@ -1,11 +1,7 @@ -using System; -using System.ComponentModel; - -public partial class Purchases +namespace RevenueCat { /// /// The result of the verification process. For more details check: http://rev.cat/trusted-entitlements - /// /// This is accomplished by preventing MiTM attacks between the SDK and the RevenueCat server. /// With verification enabled, the SDK ensures that the response created by the server was not /// modified by a third-party, and the response received is exactly what was sent. @@ -17,46 +13,18 @@ public enum VerificationResult /// /// No verification was done. This value is returned when verification is not enabled in PurchasesConfiguration /// - [Description("NOT_REQUESTED")] - NotRequested, - + NOT_REQUESTED, /// /// Verification with our server was performed successfully. /// - [Description("VERIFIED")] - Verified, - + VERIFIED, /// /// Verification failed, possibly due to a MiTM attack. /// - [Description("FAILED")] - Failed, - + FAILED, /// /// Verification was performed on device. /// - [Description("VERIFIED_ON_DEVICE")] - VerifiedOnDevice, + VERIFIED_ON_DEVICE, } } - -internal static class VerificationResultExtensions -{ - internal static Purchases.VerificationResult ParseVerificationResultByName(string name) - { - foreach (var field in typeof(Purchases.VerificationResult).GetFields()) - { - if (Attribute.GetCustomAttribute(field, - typeof(DescriptionAttribute)) is DescriptionAttribute attribute) - { - if (attribute.Description == name) return (Purchases.VerificationResult) field.GetValue(null); - } - else - { - if (field.Name == name) return (Purchases.VerificationResult) field.GetValue(null); - } - } - - return Purchases.VerificationResult.NotRequested; - } -} \ No newline at end of file diff --git a/RevenueCat/Scripts/VirtualCurrencies.cs b/RevenueCat/Scripts/VirtualCurrencies.cs index a9fbc6a7..1c80d65c 100644 --- a/RevenueCat/Scripts/VirtualCurrencies.cs +++ b/RevenueCat/Scripts/VirtualCurrencies.cs @@ -1,26 +1,23 @@ +using Newtonsoft.Json; using System.Collections.Generic; -using RevenueCat.SimpleJSON; using static RevenueCat.Utilities; -public partial class Purchases +namespace RevenueCat { /// /// The VirtualCurrencies object contains all the virtual currencies associated to the user. /// - public class VirtualCurrencies + public sealed class VirtualCurrencies { /// /// Map of all VirtualCurrency objects keyed by virtual currency code. /// - public readonly Dictionary All; + public IReadOnlyDictionary All { get; } - public VirtualCurrencies(JSONNode response) + [JsonConstructor] + internal VirtualCurrencies([JsonProperty("all")] Dictionary all) { - All = new Dictionary(); - foreach (var keyValuePair in response["all"]) - { - All.Add(keyValuePair.Key, new VirtualCurrency(keyValuePair.Value)); - } + All = all; } public override string ToString() diff --git a/RevenueCat/Scripts/VirtualCurrency.cs b/RevenueCat/Scripts/VirtualCurrency.cs index bede551d..d72a51b2 100644 --- a/RevenueCat/Scripts/VirtualCurrency.cs +++ b/RevenueCat/Scripts/VirtualCurrency.cs @@ -1,6 +1,7 @@ -using RevenueCat.SimpleJSON; +using JetBrains.Annotations; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// The VirtualCurrency object represents information about a virtual currency in the app. @@ -11,29 +12,39 @@ public class VirtualCurrency /// /// The virtual currency's balance. /// - public readonly int Balance; + [JsonProperty("balance")] + public int Balance { get; } /// /// The virtual currency's name. /// - public readonly string Name; + [JsonProperty("name")] + public string Name { get; } /// /// The virtual currency's code. /// - public readonly string Code; + [JsonProperty("code")] + public string Code { get; } /// /// The virtual currency's description defined in the RevenueCat dashboard. /// - public readonly string? ServerDescription; + [CanBeNull] + [JsonProperty("serverDescription")] + public string ServerDescription { get; } - public VirtualCurrency(JSONNode response) + [JsonConstructor] + internal VirtualCurrency( + [JsonProperty("balance")] int balance, + [JsonProperty("name")] string name, + [JsonProperty("code")] string code, + [JsonProperty("serverDescription")] string serverDescription) { - Balance = response["balance"]; - Name = response["name"]; - Code = response["code"]; - ServerDescription = response["serverDescription"]; + Balance = balance; + Name = name; + Code = code; + ServerDescription = serverDescription; } public override string ToString() diff --git a/RevenueCat/Scripts/WebPurchaseRedemption.cs b/RevenueCat/Scripts/WebPurchaseRedemption.cs index 448a83a3..7edec41c 100644 --- a/RevenueCat/Scripts/WebPurchaseRedemption.cs +++ b/RevenueCat/Scripts/WebPurchaseRedemption.cs @@ -1,18 +1,19 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// Represents a web redemption link, that can be redeemed using `Purchases.redeemWebPurchase`. /// - public class WebPurchaseRedemption + public sealed class WebPurchaseRedemption { /// /// Actual Redemption Link used. /// - public readonly string RedemptionLink; + public string RedemptionLink { get; } - public WebPurchaseRedemption(string redemptionLink) + [JsonConstructor] + internal WebPurchaseRedemption([JsonProperty("redemption_link")] string redemptionLink) { RedemptionLink = redemptionLink; } diff --git a/RevenueCat/Scripts/WebPurchaseRedemptionResult.cs b/RevenueCat/Scripts/WebPurchaseRedemptionResult.cs index 80fe8142..f38887dd 100644 --- a/RevenueCat/Scripts/WebPurchaseRedemptionResult.cs +++ b/RevenueCat/Scripts/WebPurchaseRedemptionResult.cs @@ -1,8 +1,7 @@ +using Newtonsoft.Json.Linq; using System; -using RevenueCat.SimpleJSON; -using UnityEngine; -public partial class Purchases +namespace RevenueCat { /// Represents the result of a web purchase redemption process. public abstract class WebPurchaseRedemptionResult @@ -14,7 +13,7 @@ private WebPurchaseRedemptionResult() { } /// public sealed class Success : WebPurchaseRedemptionResult { - public readonly CustomerInfo CustomerInfo; + public CustomerInfo CustomerInfo; public Success(CustomerInfo customerInfo) => CustomerInfo = customerInfo; } @@ -24,7 +23,7 @@ public sealed class Success : WebPurchaseRedemptionResult /// public sealed class RedemptionError : WebPurchaseRedemptionResult { - public readonly Error Error; + public Error Error; public RedemptionError(Error error) => Error = error; } @@ -59,29 +58,24 @@ private PurchaseBelongsToOtherUser() { } public static PurchaseBelongsToOtherUser Instance { get; } = new PurchaseBelongsToOtherUser(); } - public static WebPurchaseRedemptionResult FromJson(JSONNode response) + public static WebPurchaseRedemptionResult FromJson(JObject response) { - string resultType = response["result"]; + var resultType = response["result"].Value(); switch (resultType) { case "SUCCESS": - var customerInfo = new CustomerInfo(response["customerInfo"]); + var customerInfo = response["customerInfo"].ToObject(); return new Success(customerInfo); - case "ERROR": - var errorDetails = new Error(response["error"]); + var errorDetails = response["error"].ToObject(); return new RedemptionError(errorDetails); - case "INVALID_TOKEN": return InvalidToken.Instance; - case "EXPIRED": - string obfuscatedEmail = response["obfuscatedEmail"]; + var obfuscatedEmail = response["obfuscatedEmail"].Value(); return new Expired(obfuscatedEmail); - case "PURCHASE_BELONGS_TO_OTHER_USER": return PurchaseBelongsToOtherUser.Instance; - default: throw new ArgumentException($"Invalid result type: {resultType}"); } diff --git a/RevenueCat/Scripts/WinBackOffer.cs b/RevenueCat/Scripts/WinBackOffer.cs index 245f53ff..3b2e484a 100644 --- a/RevenueCat/Scripts/WinBackOffer.cs +++ b/RevenueCat/Scripts/WinBackOffer.cs @@ -1,56 +1,64 @@ -using RevenueCat.SimpleJSON; +using Newtonsoft.Json; -public partial class Purchases +namespace RevenueCat { /// /// iOS only. Requires StoreKit 2 and iOS 18.0+. Describes a win-back offer that you configured in App Store Connect. /// - public class WinBackOffer + public sealed class WinBackOffer { /// /// Identifier of the discount. /// - public readonly string Identifier; + public string Identifier { get; } /// /// Price in the local currency. /// - public readonly float Price; + public float Price { get; } /// /// Formatted price, including its currency sign, such as €3.99. /// - public readonly string PriceString; + public string PriceString { get; } /// /// Number of subscription billing periods for which the user will be given the discount, such as 3. /// - public readonly int Cycles; + public int Cycles { get; } /// /// Billing period of the discount, specified in ISO 8601 format. /// - public readonly string Period; + public string Period { get; } /// /// Unit for the billing period of the discount, can be DAY, WEEK, MONTH or YEAR. /// - public readonly string PeriodUnit; + public string PeriodUnit { get; } /// /// Number of units for the billing period of the discount. /// - public readonly int PeriodNumberOfUnits; + public int PeriodNumberOfUnits { get; } - public WinBackOffer(JSONNode response) + [JsonConstructor] + internal WinBackOffer( + [JsonProperty("identifier")] string identifier, + [JsonProperty("price")] float price, + [JsonProperty("priceString")] string priceString, + [JsonProperty("cycles")] int cycles, + [JsonProperty("period")] string period, + [JsonProperty("periodUnit")] string periodUnit, + [JsonProperty("periodNumberOfUnits")] int periodNumberOfUnits) { - Identifier = response["identifier"]; - Price = response["price"]; - PriceString = response["priceString"]; - Cycles = response["cycles"]; - Period = response["period"]; - PeriodUnit = response["periodUnit"]; - PeriodNumberOfUnits = response["periodNumberOfUnits"]; + Identifier = identifier; + Price = price; + PriceString = priceString; + Cycles = cycles; + Period = period; + PeriodUnit = periodUnit; + PeriodNumberOfUnits = periodNumberOfUnits; } public override string ToString() diff --git a/RevenueCat/package.json b/RevenueCat/package.json index 6b7b03b8..312fa261 100644 --- a/RevenueCat/package.json +++ b/RevenueCat/package.json @@ -1,6 +1,6 @@ { "name": "com.revenuecat.purchases-unity", - "version": "8.2.4", + "version": "9.0.0", "displayName": "RevenueCat SDK for Unity", "description": "Unity in-app purchases and subscriptions made easy. Supports iOS and Android.", "documentationUrl": "https://www.revenuecat.com/docs", @@ -22,5 +22,8 @@ ], "repoUrl": "https://github.com/RevenueCat/purchases-unity", "dependencies": { - } + "com.google.external-dependency-manager": "1.2.186", + "com.unity.nuget.newtonsoft-json": "1.3.2" + }, + "unity": "2022.3" } \ No newline at end of file diff --git a/RevenueCatUI/Editor/AssemblyInfo.cs b/RevenueCatUI/Editor/AssemblyInfo.cs new file mode 100644 index 00000000..e69de29b diff --git a/RevenueCatUI/Editor/AssemblyInfo.cs.meta b/RevenueCatUI/Editor/AssemblyInfo.cs.meta new file mode 100644 index 00000000..ea006579 --- /dev/null +++ b/RevenueCatUI/Editor/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09257c36c3a041443a2d821d0bdc8daa \ No newline at end of file diff --git a/RevenueCatUI/README.md.meta b/RevenueCatUI/README.md.meta deleted file mode 100644 index b033aee6..00000000 --- a/RevenueCatUI/README.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 4181f8939ea324c7f8014d9ec9d6a417 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/RevenueCatUI/package.json b/RevenueCatUI/package.json index 18b5ab7c..74dc48ad 100644 --- a/RevenueCatUI/package.json +++ b/RevenueCatUI/package.json @@ -25,6 +25,7 @@ ], "repoUrl": "https://github.com/RevenueCat/purchases-unity", "dependencies": { - "com.revenuecat.purchases-unity": "file:../RevenueCat" - } -} + "com.revenuecat.purchases-unity": "9.0.0" + }, + "unity": "2022.3" +} \ No newline at end of file diff --git a/scripts/create-unity-package.sh b/scripts/create-unity-package.sh index 91007cbc..903874ac 100755 --- a/scripts/create-unity-package.sh +++ b/scripts/create-unity-package.sh @@ -48,7 +48,7 @@ if [ ! -z "$CI" ]; then verbose_echo "CI detected: $CI" verbose_echo "Using copy operation instead of symlink for CI compatibility" cp -r "$PWD/RevenueCat" "$PROJECT/Assets/" - + # Verify copy was successful if [ -d "$PROJECT/Assets/RevenueCat" ]; then echo "✅ RevenueCat folder copied successfully" @@ -62,7 +62,7 @@ else verbose_echo "Local development environment detected" verbose_echo "Using symlink operation for local development" ln -s "$PWD/RevenueCat" "$PROJECT/Assets/" - + # Verify symlink was created successfully if [ -L "$PROJECT/Assets/RevenueCat" ]; then echo "✅ Symlink created successfully" @@ -124,7 +124,7 @@ fi if [ -d "Assets/PlayServicesResolver" ]; then FOLDERS_TO_EXPORT="$FOLDERS_TO_EXPORT Assets/PlayServicesResolver" verbose_echo "Found PlayServicesResolver folder" -fi +fi if [ -d "Assets/ExternalDependencyManager" ]; then FOLDERS_TO_EXPORT="$FOLDERS_TO_EXPORT Assets/ExternalDependencyManager" verbose_echo "Found ExternalDependencyManager folder" @@ -185,10 +185,10 @@ else echo "⬇️ Downloading External Dependency Manager..." EDM_URL="https://github.com/googlesamples/unity-jar-resolver/raw/master/external-dependency-manager-latest.unitypackage" EDM_FILE="$PROJECT/external-dependency-manager-latest.unitypackage" - + verbose_echo "Download URL: $EDM_URL" verbose_echo "Download destination: $EDM_FILE" - + # Try wget first (more reliable in CI), fallback to curl if command -v wget >/dev/null 2>&1; then verbose_echo "Using wget to download External Dependency Manager" @@ -201,7 +201,7 @@ else rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null exit 1 fi - + if [ ! -f "$EDM_FILE" ]; then echo "❌ Failed to download External Dependency Manager" rm -rf "$PROJECT/Assets/RevenueCat" 2>/dev/null